对于一些操作来说,返回值就那几个。对于整数来说,存入的数据也通常不会太大,所以redis通过预分配一些常见的值对象,并在多个数据结构之间(很不幸,你得时指针才能指到这里)共享这些对象,避免了重复分配,节约内存。同时也节省了CPU时间
如图所示:
三个列表的值分别为:
列表 A : [20130101, 300, 10086]
列表 B : [81, 12345678910, 999]
列表 C : [100, 0, -25, 123]
最后一个:redis对对象的管理是通过最原始的引用计数方法。
2. 字符串字符串是redis使用最多的数据结构,除了本身作为SET/GET的操作对象外,数据库中的所有key,以及执行命令时提供的参数,都是用字符串作为载体的。
在上面的图中,我们可以看见,字符串的底层可以有两种实现:
REDIS_ENCODING_INT使用long类型保存long的值
REDIS_ENCODING_ROW使用sdshdr保存sds、long long、double、long double等
说白了就是除了long是通过第一种存储以外,其他类型都是通过第二种存储滴。
然后新创建的字符串,都会默认使用第二种编码,在将字符串作为键或者值保存进数据库时,程序会尝试转为第一种(为了节省空间)
3. 哈希表哈希表,嗯,它的底层实现也有两种:
REDIS_ENCODING_ZIPLIST
REDIS_ENCODING_HT(字典)
当创建新的哈希表时,默认是使用压缩列表作为底层数据结构的,因为省内存呀。只有当触发了阈值才会转为字典:
哈希表中某个键或者值的长度大于server.hash_max_ziplist_value(默认为64)
压缩列表中的节点数量大于server.hash_max_ziplist_entries(默认为512)
4. 列表列表嘛,其实就是队列。它的底层实现也有2种:
REDIS_ENCODING_ZIPLIST
REDIS_ENCODING_LINKEDLIST
当创建新的列表时,默认是使用压缩列表作为底层数据结构的,还是因为省内存- -。同样有一个触发阈值:
试图往列表中插入一个字符串值,长度大于server..list_max_ziplist_value(默认是64)
ziplist包含的节点超过server.list_max_ziplist_entries(默认值为512)
阻塞命令
对于列表,基本的操作就不介绍了,因为列表本身的操作和底层实现基本一致,所以我们可以简单的认为它具有双端队列的操作即可。重点讨论一下列表的阻塞命令比较好玩。
当我们执行BLPOP/BRPOP/BRPOPLPUSH的时候,都可能造成客户端的阻塞,它们被称为列表的阻塞原语,当然阻塞原语并不是一定会造成客户端阻塞:
只有当这些命令作用于空列表,才会造成客户端阻塞
如果被处理的列表不为空,它们就执行无阻塞版本的LPOP/RPOP/RPOPLPUSH
上面两条的意思很简单,因为POP命令是删除一个节点,那么当没有节点的时候,客户端会阻塞直到一个元素添加进来,然后再执行POP命令,那么,对客户端的阻塞过程是这样的:
将客户端的连接状态更改为“正在阻塞”,并记录这个客户端是被那些键阻塞(可以有多个),以及阻塞的最长时间
将客户端的信息加入到字典server.db[i].blocking_keys中,i就是客户端使用的数据库编号
继续保持客户端和服务器端的连接,但是不发送任何信息,造成客户端阻塞
响应的,解铃须有系铃人:
被动脱离:有其他客户端为造成阻塞的键加入了元素
主动脱离:超过阻塞的最长时间
强制脱离:关闭客户端或者服务器
上面的过程说的很简单,但是在redis内部要执行的操作可以很多的,我们用一段伪代码来演示一下被动脱离的过程:
def handleClientsBlockedOnLists(): # 执行直到 ready_keys 为空 while server.ready_keys != NULL: # 弹出链表中的第一个 readyList rl = server.ready_keys.pop_first_node() # 遍历所有因为这个键而被阻塞的客户端 for client in all_client_blocking_by_key(rl.key, rl.db): # 只要还有客户端被这个键阻塞,就一直从键中弹出元素 # 如果被阻塞客户��执行的是 BLPOP ,那么对键执行 LPOP # 如果执行的是 BRPOP ,那么对键执行 RPOP element = rl.key.pop_element() if element == NULL: # 键为空,跳出 for 循环 # 余下的未解除阻塞的客户端只能等待下次新元素的进入了 break else: # 清除客户端的阻塞信息 server.blocking_keys.remove_blocking_info(client) # 将元素返回给客户端,脱离阻塞状态 client.reply_list_item(element)