redis随笔
一.字典
1.dictEntry:哈希表节点,保存key及其对应的value,并且每个dictEntry有一个next指针,当发生键冲突时,指向下一个dictEntry
例如:对于redis命令 lpush mylist a b c,redis使用一个dictEntry来保存,其key是mylist,value是a b c组成的链表
2.dictht: 哈希表
table是dictEntry**类型,它指向图中的dictEntry*[4],每个dictEntry指向一个dictEntry组成的链表
size一定是2的n次方
3.dict:字典
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx;
} dict;
4.rehash
当hash table的负载因子需要维持在一个合理的范围,否则就需要扩展或者收缩hash table,可通过rehash来执行。如果字典比较大的话,一次集中式rehash会使服务器一段时间停止服务(redis是单线程),所以需要使用多次渐进式rehash,这也是使用两个dictht的原因。
渐进式rehash具体步骤如下:
(1) 为ht[1]分配空间,并将rehashidx置0,表示正在rehash过程中
(2) 在rehash期间,每次对字典的增删改查操作,首先要执行一步rehash,即将ht[0]中的dictEntry迁移到ht[1]中,然后再做各自的操作。
(3) 当全部迁移完成后,将ht[0]空间释放,ht[0]与ht[1]交换指针,并令rehashidx为-1
在这个过程中,rehashidx还起到了索引的作用
在渐进式rehash过程中,会同时使用ht[0]和ht[1]两个hash table,所以对字典的增删改查需要在两个hash table上进行,例如查找,则先在ht[0]上查找,如果没有,再从ht[1]中查找。插入的话,则直接插入到ht[1]中
5.dictScan(src/dict.c +836),每次dictScan都遍历一个key下的所有元素,并返回下一个遍历的key的游标
游标演变:
v |= ~m0;
v = rev(v);
v++;
v = rev(v);
遍历顺序:
table size为8时: 000------------->100------------>010------------>110------------->001------------->101------------>011------------>111
table size为16时:0000→1000→0100→1100→0010→1010→0110→1110-->0001→1001→0101→1101→0011→1011→0111→1111
优点:哈希表扩展时不会重复遍历,缩小时重复遍历很少
数字增长顺序:
table size为8时:000-->001-->010-->011-->100-->101→110→111
table size为16时:0000-->0001-->0010-->0011-->0100-->0101-->0110-->0111-->1000-->1001-->1010-->1011-->1100-->1101-->1110-->1111
缺点:哈希表扩展时重复遍历较多,缩小时有些游标会被漏掉
二、数据库
db中每个元素的类型是redisDb,其定义如下:
struct redisDb {
dict *dict; // 保存键值对的字典
dict *expires; // 过期字典,保存数据库中所有键的过期时间,key-dict中的某个键对象,value-long long类型,保存该键过期时间
}
过期键删除:
1.惰性删除策略实现:expireIfNeeded,所有的读写数据库的Redis命令在执行前都会调用。在该函数执行时,如果该键已经过期,则删除;如果没有过期,则该函数不做任何操作
2.定期删除策略实现:activeExpireCycle。每当Redis的周期操作函数ServerCron函数执行时,便调用一遍activeExpireCycle函数。该函数每次运行时,都选取部分数据库中的部分键进行检查,删除过期键。
activeExpireCycle代码(src/expire.c +97):
每次操作dbs_per_call个db,对每个db,分多次去操作,每次操作ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP个key,当其中一次删除的键数量少于ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 4时停止,同时有一个该函数总共运行时间,如果超过1ms直接退出
三、订阅PUB/SUB功能
1.每个redisServer内有一个dict(pubsub_channels),保存该redis server上的所有channel以及监听该channel的所有client(channel为key,client list为value)
2.每个redisServer内有一个list(pubsub_patterns),保存该server使用模式订阅的所有channel及其client
3.发送消息
例:PUBLISH channel msg
流程:
1> 在dict pubsub_channels中找到key为channel的client链表,逐个发送msg
2> 遍历list pubsub_patterns,找到所有与channel匹配的pubsubPattern,向其中的client发送msg