设为首页 收藏本站
查看: 795|回复: 0

[经验分享] 《Redis源码学习笔记》数据结构-字典

[复制链接]

尚未签到

发表于 2016-12-20 08:17:37 | 显示全部楼层 |阅读模式
《Redis源码学习笔记》文章列表
由于图片较大,缩放较为模糊,请双击打开查看原图 ^_^
要看懂redis代码,其中重要的一步就是要看懂它里面所使用的数据结构,而在这不算少的数据结构中,最重要的就是字典,它几乎就是redis实现各种功能的骨架,所以理解好字典至关重要!
redis作为一个nosql数据库,所有的key-value都是存储在一个字典中,而字典则是用哈希表实现的;关于哈希表原理,随便上网查一下都能找到一大堆资料,因此这里我也不想做过多赘述,直接开门见山,看下在redis中哈希表是什么样的:
DSC0000.png
上图所示结构对应代码如下:

// 字典
typedef struct dict {
// 哈希表(2个)
dictht ht[2];
// rehash索引,若rehashidx == -1,则表示未开始进行rehash,
// 否则rehashidx的值表示rehash正进行到ht[0]这个hash表上哪个索引节点
int rehashidx;  
// 安全迭代器数量
int iterators;
} dict;
// 哈希表
typedef struct dictht {
// 哈希表 (指向一个dictEntry*数组,俗称“桶”)
dictEntry **table;      
// 哈希表大小
unsigned long size;     
// 位掩码,通过hash_value & sizemask得出节点在哈希表索引
unsigned long sizemask;
// 哈希表中节点数量
unsigned long used;     
} dictht;
// 哈希节点
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下一个节点
struct dictEntry *next;
} dictEntry;

关于redis中的字典,最特别的无疑是dict中维护着两个哈希表(ht[0],ht[1]),为什么要有两个呢?在解释这个之前我们先看下哈希表的rehash;
rehash目的:
当我们不断的往哈希表(ht[0])插入新的键值对,如果两个键的hash值相同,那么它们将以链表的形式放入到同一个“桶”中,如下图key1和key4:
DSC0001.png
这样带来的问题就是,随着我们往哈希表里插入越来越多的键值对,哈希表性能会急剧下降(查找操作都退化成链表查找);所以,我们就需要扩大原来的哈希表,使得哈希表大小和哈希表中的节点数的比例能够维持在1:1(dictht.size:dictht.used),这时候哈希表才能达到最佳查询性能O(1)
rehash过程:
创建一个新的哈希表,大小是当前的两倍(准确说还必须是2的幂次),然后把全部键值对重新散列到新的哈希表中,最后再用它替换原来的哈希表;
rehash问题:
我们考虑下面一种情况:客户端A插入一个键值对,这时候发现dictht.used与dictht.size的比例大于1(查询性能开始下降),于是执行rehash操作,假设目前哈希表中有10万个键值对,那么redis就会一直埋头苦干,直到完成对这个10万个键值对的rehash操作,并且在这个过程中,其他客户端请求都会被阻塞(因为redis是单线程);很显然我们是无法忍受这种情况的发生,那redis是如何解决这个问题呢?
渐进式rehash:
“渐进式”意味着rehash过程不是一次做完而是每次做一点,这样就可以避免由于rehash过程太久导致其他客户端请求被阻塞,具体过程如下:
1. 在ht[1]上分配一个更大的哈希表;
2. “分多次”把ht[0]上的键值对重新散列到ht[1]上;
3. 当处理完所有键值对时,让ht[0]指向新的哈希表;
DSC0002.png
现在还有一个问题,我们说了“分多次把ht[0]上的键值对重新散列到ht[1]上”,那么这个分多次究竟是多少次?并且每次处理多少键值对才最合适?
redis准备rehash时,会把dict.rehashidx置为0(标示rehash开始),然后当执行任意一个哈希表操作(添加,删除,查找等),就会执行一次_dictRehashStep函数;
_dictRehashStep函数每次rehash把ht[0]上的第一个不为空索引上的全部键值对迁移到ht[1]上,并且用dict.rehashidx的值标示当前rehash正进行到了哪个索引;
也就是说按照上图,第一次迁移key1,key4键值对(这时候dict.rehashidx的值为0),第二次迁移key2键值对(这时候dict.rehashidx的值为1),第三次迁移key3键值对(这时候dict.rehashidx的值为2),至此rehash完毕(dict.rehashidx被复位成-1),相关伪代码:
# 任意dict操作(添加,删除,查找)
def anyDictOperation(dict):
# 如果正在进行rehash
if dict.rehashidx != -1:
_dictRehashStep(dict)
# 执行字典操作
dictOperation(dict)
def _dictRehashStep(dict):
# 如果当前安全迭代器数量不为0,暂停此次rehash
if dict.iterators > 0 : return
idx = dict.rehashidx
# 获取第一个不为空的索引
while len(dict.ht[0].table[idx]) <= 0:
idx++
# 迁移该索引节点上的所有键值对到ht[1]上
for key, val in dict.ht[0].table[idx].getKeyValuePairs:
redisServer.ht[0].table.used--
redisServer.ht[1].table.used++
redisServer.ht[1].table.hash(key, val)  
# 当所有键值对迁移完毕,用新的哈希表替换老的,并且重置ht[1]
if dict.ht[0].table.used == 0:
dict.ht[0] = dict.ht[1]
dict.ht[1].reset()
dict.rehashindx = -1 # 复位
另外,redis在服务器执行例行任务时(serverCron),也会定期去做一部分rehash操作,伪代码:
def serverCron():
# 循环redis所有数据库
for num in redisServer.dbnums:
if redisServer.db[num].dict.rehashidx != -1 :
# 第二个参数用来限制rehash执行多久,单位毫秒
dictRehashMilliseconds(redisServer.db[num].dict, 1)
# 其他例行任务...
def dictRehashMilliseconds(dict, timeout_ms):
start_time =  now_time()
while now_time() - start_time < timeout_ms:
_dictRehashStep(dict)
至此,我们已经分析完:为什么要在dict中维护两个哈希表(ht[0],ht[1]);
关于redis字典的更多细节,请参看:dict.h和dict.c代码以及redis.c/serverCron函数
总结:
1.了解redis中字典是如何设计的以及这样设计的原因;
2.了解哈希表的rehash过程以及rehash时机;

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-316652-1-1.html 上篇帖子: 《Redis源码学习笔记》数据结构-字典 下篇帖子: tomcat集群 nginx+tomcat+redis 单机模拟
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表