一对一直播平台源码,该扩容时就扩容
一对一直播平台源码,该扩容时就扩容
Redis的扩容步骤
当一旦需要进行扩容时,此时会使用到dict中的ht[1],一对一直播平台源码的Redis的扩容步骤如下所示。
1、计算ht[1]的容量size,即扩容后的容量,ht[1]的容量为大于等于ht[0].used * 2且同时为2的幂次方的最小值;
2、为ht[1]设置size,sizemask字段的值,初始化used字段为0,并为dictEntry数组分配空间;
3、将dict的rehashidx字段设置为0,表示此时开启渐进式rehash,Redis会通过渐进式rehash的方式逐步将ht[0]上的dictEntry迁移到ht[1]上;
4、当ht[0]的所有键值对全部存放到ht[1]中后,释放ht[0]的内存空间,然后ht[1]变为ht[0]。
特别注意,上述的步骤仅针对正常的扩容,如果是ht[0]的初始化,则与上述的步骤稍有不同,这里不再赘述。当dict中键值对特别多时,rehash会特别耗时,所以Redis采用一种渐进式rehash的方式来完成扩容,dict中的rehashidx字段用于记录当前已经rehash到的哈希桶的索引,而渐进式rehash就是Redis不会一次性将ht[0]上的键值对迁移到ht[1]上,而是会在某些时间点迁移一部分,这些时间点如下所示。
1、当对数据进行增删改查时会从ht[0]迁移一个哈希桶到ht[1]上;
2、Redis会定时的从ht[0]迁移一部分哈希桶到ht[1]上。
特别注意,如果在渐进式rehash的过程中有新的键值对添加,那么会直接添加到ht[1]中。
下面将结合Redis源码对一对一直播平台源码中Redis的扩容步骤进行学习。在第一节中已知,执行扩容逻辑的方法是dict.c文件的dictExpand()方法,其源码实现如下所示。
int dictExpand(dict *d, unsigned long size) { // 如果正在rehash或者ht[0]的当前大小大于了扩容后的大小的最小值 // 此时返回状态码1,表示扩容发生异常 if (dictIsRehashing(d) || d->ht[0].used > size) return DICT_ERR; // n就是扩容后的哈希表 dictht n; // 得到一个大于等于size的满足2的幂次方的最小值作为n的容量 unsigned long realsize = _dictNextPower(size); // 如果扩容后的哈希表的容量与老哈希表容量一样 // 此时返回状态码1,表示扩容发生异常 if (realsize == d->ht[0].size) return DICT_ERR; // 为n设置容量size n.size = realsize; // 为n设置掩码sizemask n.sizemask = realsize-1; // 为n的数组分配空间 n.table = zcalloc(realsize*sizeof(dictEntry*)); // 初始化n的当前大小used为0 n.used = 0; // 如果是初始化哈希表,那么直接将ht[0]置为n if (d->ht[0].table == NULL) { d->ht[0] = n; return DICT_OK; } // 执行到这里,表明是非初始化哈希表的扩容,将ht[1]置为n d->ht[1] = n; // 将dict的rehashidx字段设置为0,表示开启渐进式rehash d->rehashidx = 0; return DICT_OK; }
dictExpand()方法主要完成的逻辑就是为ht[1]设置size,sizemask字段的值,初始化used字段为0,并为dictEntry数组分配空间,最后将dict的rehashidx字段设置为0以开启渐进式rehash。下面再结合源码看一下什么时候进行键值对的迁移,首先在第一节中分析dictAddRaw()方法时有提到,dictAddRaw()方法一开始就会判断当前是否处于rehash阶段,如果正在rehash,则触发一次哈希桶的迁移操作,这个迁移操作对应的方法是dict.c文件的_dictRehashStep()方法,其源码实现如下。
static void _dictRehashStep(dict *d) { if (d->iterators == 0) dictRehash(d,1); }
继续看dictRehash()方法的实现。
// 参数n表示本次rehash需要迁移的哈希桶个数 int dictRehash(dict *d, int n) { // 允许遍历的最大空桶数 int empty_visits = n*10; // 如果没有在进行渐进式rehash,则返回 if (!dictIsRehashing(d)) return 0; // 在ht[0]当前大小不为0的前提下 // 需要迁移多少个哈希桶,就循环多少次,每次循环迁移一个哈希桶 while(n-- && d->ht[0].used != 0) { dictEntry *de, *nextde; // rehashidx的值不能大于等于ht[0]的容量 assert(d->ht[0].size > (unsigned long)d->rehashidx); // 如果哈希表ht[0]的rehashidx位置的哈希桶是空,则继续遍历下一个哈希桶 // 如果遍历的空桶数达到了empty_visits,则本次rehash结束,直接返回 while(d->ht[0].table[d->rehashidx] == NULL) { d->rehashidx++; if (--empty_visits == 0) return 1; } // 得到ht[0]的rehashidx位置的哈希桶 de = d->ht[0].table[d->rehashidx]; // 遍历并将rehashidx位置的哈希桶的链表上的节点全部迁移到ht[1]上 while(de) { uint64_t h; nextde = de->next; // 将链表节点的键的hash值与ht[1]的掩码相与得到当前节点在ht[1]上的索引 h = dictHashKey(d, de->key) & d->ht[1].sizemask; // 使用头插法插入ht[1] de->next = d->ht[1].table[h]; d->ht[1].table[h] = de; // ht[0]的当前大小减1 d->ht[0].used--; // ht[1]的当前大小加1 d->ht[1].used++; // 继续迁移链表的下一节点 de = nextde; } // 全部迁移完成后,将ht[0]的rehashidx位置置为空 d->ht[0].table[d->rehashidx] = NULL; d->rehashidx++; } // 判断是否将ht[0]上的键值对全部迁移到了ht[1] if (d->ht[0].used == 0) { // 如果ht[0]上的键值对全部迁移到了ht[1] // 先释放ht[0]的数组空间 zfree(d->ht[0].table); // 然后将ht[0]置为ht[1] d->ht[0] = d->ht[1]; // 重置ht[1] // 即将ht[1]的数组置为空,容量,掩码和当前大小全部置为0 _dictReset(&d->ht[1]); // 将dict的rehashidx字段设置为-1,表示rehash结束 d->rehashidx = -1; return 0; } return 1; }
dictRehash()方法有两个参数,第一个是需要进行rehash的dict,第二个是需要迁移的哈希桶的个数,可知如果是对一对一直播平台源码的数据的增删改查而触发的rehash,需要迁移的哈希桶的个数为1。在dictRehash()方法一开始就定义了一个最大空桶数,其值为本次迁移数的10倍,因为在遍历哈希表时,可能会遇到很多空桶,所以为了避免遍历大量空桶而带来的时间消耗,Redis规定本次rehash迁移时,如果遇到的空桶数达到了本次需要迁移的哈希桶数的10倍,则停止迁移并返回。在dictRehash()方法中对于每一个哈希桶的迁移,其实就是遍历这个哈希桶上的链表,将每个链表节点重新基于ht[1]计算一个索引并迁移到ht[1]上。在dictRehash()方法的最后需要判断一下是否将ht[0]上的数据全部迁移到了ht[1]上,如果已经全部完成迁移,此时需要先释放老的ht[0]的数组空间,然后将ht[0]置为ht[1],接着重置ht[1]即将其数组置为空,容量,掩码和当前大小全部置为0,最后将dict的rehashidx字段设置为-1,表示rehash结束。
除了对一对一直播平台源码的数据的增删改查会调用到dictRehash()方法来迁移哈希桶外,Redis也会定时的调用到dictRehash()方法来迁移哈希桶,这个定时任务方法是server.c文件的serverCron()方法,在该方法中会调用到server.c文件的databasesCron()方法,该方法会处理Redis数据库中的增量执行的后台操作,这些操作中就包括渐进式rehash,所以在databasesCron()方法中又通过调用server.c文件的incrementallyRehash()方法来执行rehash,接着又在incrementallyRehash()方法中调用到了dict.c文件的dictRehashMilliseconds()方法,在dictRehashMilliseconds()方法中就真正调用到了dictRehash()方法来执行迁移哈希桶的逻辑,dictRehashMilliseconds()方法的源码实现如下所示。
int dictRehashMilliseconds(dict *d, int ms) { long long start = timeInMilliseconds(); int rehashes = 0; // 在1毫秒的时间内循环进行迁移 // 每次循环迁移100个哈希桶 while(dictRehash(d,100)) { rehashes += 100; if (timeInMilliseconds()-start > ms) break; } return rehashes; }
那么至此,Redis的扩容步骤的源码就分析完毕。
以上就是一对一直播平台源码,该扩容时就扩容, 更多内容欢迎关注之后的文章