数据库结构
Server:DB = 1:N
DB内kv由字段dict(dict类型)存储;过期时间由字段expires(dict类型)负责,key均为SDS对象。
过期Key由访问时删除(惰性删除) 或 定时删除(随机访问过期Key的过期时间,删除过期Key)
RDB保存时不保存过期Key,加载时忽略过期Key。
AOF删除过期Key时显示追加DEL命令,加载忽略过期Key。
复制时主服务器显示发送DEL命令,从库不删除过期Key(从库可能返回已过期Key?)。
业务主逻辑
main
serverCron
更新服务器的各类统计信息
清理数据库中的过期键值对(执行时间上限为HZ的25%,从上次执行结束的数据库开始清理,每次测试20个Key,若25%的key失效,则继续清理;数据库过期Dict中若不满1%,不进行清理)
关闭和清理连接失效的客户端
渐进式Hash
触发RDB备份和AOF重写、Append
acceptTcpHandler
默认每次事件处理1000个accept请求。
创建Client时设置fd为非阻塞,并添加对应的readable事件
readQueryFromClient
默认每次事件处理最多读16k数据。
事件处理库封装了epoll(linux, 水平触发),select(其他),kqueue(BSE),以及evport(Solaris)。
epoll边沿触发不确保只通知一次,需要配合EPOLLONESHOT选项实现仅一次通知。
Select监控列表以位掩码实现,上限1024;每次调用,fd集合需要在用户态和内核态传递;返回后需要轮询整个监控列表,否则无法确定事件是否发生,复杂度O(n)。
Poll以链表代替位掩码,无上限(影响性能);每次调用,fd集合需要在用户态和内核态传递;返回后需要轮询,复杂度O(n)
handleClientsWithPendingWrites
Redis在写入fd失败后注册epoll读事件。
集群
Redis默认slot数量为16384,hash函数为CRC16。
客户端直接向节点发送命令,若key不在对应节点上,返回MOVED错误。
节点内,slot所有的key由clusterState结构中的slots_to_keys维护。5.0版本以后slots_to_keys为radix tree基数树,树的key为hashSlot+kv.key。5.0版本之前由skipList存储。
发布和订阅
普通订阅由pubsub_channels(dict)维护,key为channel,value为client列表。
模式订阅由pubsub_patterns(list)维护,node为pubsubPattern对象{client, pattern}。
PUBLISH时服务器优先向pubsub_channels中对应client列表发送消息,然后遍历模式订阅列表,逐个匹配。
事务
Redis通过MULTI,EXEC,WATCH等命令实现事务功能。Redis不支持回滚,即不支持事务的原子性,错误出现通常为指令格式错误,即编程错误。
数据结构
SDS(Simple Dynamic String)
定义
1 | struct sdshdr { |
优化
空间预分配(APPEND/SETRANGE): len < 1M 时分配两倍空间;至多多分配1M
空间释放:修改操作不会导致空间缩小(空间只增加不缩小,空间扩充行为类似Java的ArrayList、HashMap)
链表(adlist)
标准双向链表
QuickList(3.2引入)
双向链表,Node的具体数据zl类型为ZipList,中间节点可以设置LZF压缩
Dict(字典)
基于HashMap实现,HashMap以链地址法处理Hash冲突。考虑到Redis主逻辑为单线程,
字典主体结构由 类型 + 双Hash表 + RehashIndex + 迭代器计数器 组成。字典Rehash过程为渐进式Hash,Hash内Entry不保存hash值,因此需要重新计算Hash值。
渐进式Hash过程:
分配 ht[1] 空间, rehashidx 设置为 0。
在 rehash 进行期间, 对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值+1。
随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。
字典迭代器分为安全和不安全两种,安全迭代器用于KEYS,可以在迭代过程中对字典进行修改。
IntSets
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
encoding仅升级不降级。
ZipList
ZipList内存分布:
Entry内存分布:
编码字段包含data长度信息。(字符串类型在类型位后显示指定,其他隐式指定)
列表插入时若空间不足会除非内存分配,由于prevlen字段长度非固定,可能会导致连锁更新。
ZipMap
2.6版本废弃。
Skip List
复杂度:平均O(logN),最差O(N)
1 | typedef struct zskiplistNode { |
redisObject
类型
OBJ_STRING
OBJ_LIST
OBJ_SET
OBJ_ZSET
OBJ_HASH
OBJ_MODULE
OBJ_STREAM(Redis 5.0 新增特性)
编码
OBJ_ENCODING_RAW
OBJ_ENCODING_INT
OBJ_ENCODING_HT(Dict类型)
OBJ_ENCODING_ZIPMAP (ZipMap类型,2.6版本废弃,数量小时采用ZipList存储Hash)
OBJ_ENCODING_LINKEDLIST
OBJ_ENCODING_ZIPLIST
OBJ_ENCODING_INTSET
OBJ_ENCODING_SKIPLIST
OBJ_ENCODING_EMBSTR
OBJ_ENCODING_QUICKLIST
OBJ_ENCODING_STREAM
具体编码
OBJ_STRING 编码类型可以为 OBJ_ENCODING_RAW(SDS) 或 OBJ_ENCODING_INT 或 OBJ_ENCODING_EMBSTR(最大限制不同版本不一致,39 -> 39,以能匹配Jemalloc的64字节Arena为准)
OBJ_LIST 编码类型 3.2版本之后为 OBJ_ENCODING_QUICKLIST, 3.2版本之前为 OBJ_ENCODING_LINKEDLIST 或 OBJ_ENCODING_ZIPLIST (控制参数:list_max_ziplist_entries,默认512; list-max-ziplist-value 默认 64)
OBJ_SET 编码类型为 OBJ_ENCODING_HT 或 REDIS_ENCODING_INTSET
OBJ_ZSET 编码类型为 OBJ_ENCODING_ZIPLIST 或 REDIS_ENCODING_SKIPLIST(实际为skipList+dict) (控制参数zset_max_ziplist_entries, zset_max_ziplist_value)
OBJ_HASH 编码类型为 OBJ_ENCODING_ZIPLIST 或 OBJ_ENCODING_HT
内存回收
Redis内存回收机制由引用计数实现,即RedisObject的refcount。
Redis 4.0引入Lazy Free,空间回收交由Bio线程处理。(Lazy Free仅仅是异步处理free,在交由bio处理之前,主线程负责unlink)