redis数据结构
字符串
redis的字符串采用了空间换时间的方法来提高效率, 一个redis的字符串结构称为SDS(simple dynamic string):
1 | struct sdshdr { |
这样的一个好处是:
- 直接记录了字符串的长度, 获取字符串长度的代价是$O(1)$, c字符串的是$O(n)$
- 杜绝缓冲区溢出的问题, 拼接字符串的话先检查buf大小够不够, 不够的话分配足够的空间然后再拼接字符串.
为了提高修改字符串的效率, 提供了预分配和惰性删除两种策略:
- 预分配
修改字符串的时候, 除了分配额外的需要空间外, 还根据分配后len的大小:
- 如果sds的长度小于1mb, 那么将free设置大小为len
如果sds长度大于1mb, 那么free的value设置为1mb
惰性删除
如果减少字符串长度的话, 会修改字符串为需要的值, 修改len属性, 然后修改free属性, 但是不会直接的将这部分内存释放, 而是留着为今后的增长需要, 或者在需要的时候调用sdsfree释放内存.
sds使用len属性读取一个字符串而不是用\0来处理, 这样一个好处是O1的读取效率, 而且不用担心字符串的字符安全问题.
链表
redis的链表可以用作一个简单的消息队列, 或者一些别的功能. \
redis列表键的底层实现之一就是链表, 当一个列表键包含了数量比较多的元素, 或者列表中包含的元素都是比较长的字符串的时候, redis就会使用列表键的底层实现.
内部实现上, 一个链表结构如下:
1 | typedef struct listNode { |
redis链表的特性有:
- 双端链表
- 无环, prev和next的指针都指向null
- 带有链表的长度
- 多态, 使用void*指针保存节点值, 实现了dup, free, match三个属性
字典
一个redis的字典的结构如下:
1 | typedef struct dictht { |
扩容
redis通过rehash方法实现hash表的扩展和收缩.
这里比较关键的两点:
hash的ht[0]和ht[1]交替操作, 扩容和收缩的单位是
>2或者<2渐进rehash
为了不影响使用性能, redis不是一次性将ht[0]里面的所有数据全部rehash到ht[1]上, 而是渐进式进行.
- 为ht[1]分配空间, 让字典同时持有ht[0]和ht[1]两个hash表
- 将字典的索引计数器rehashidx设置为0, 表示rehash开始, 默认这个值没有的时候是-1
- 字典的删除查找和更新操作会同在两个hash表上进行, 例如如果需要查找一个key, 会先在ht[0]上查找, 没有的话在ht[1]上查找
- 在执行rehash的时候, 新添加到字典的key-value一律会保存到ht[1]里面, 而ht[0]则不再进行任何添加操作, 这个措施保证了ht[0]中包含的键值对数量只会减小不会增加最后随着rehash变成空表
小问题, 啥时候进行扩容, python貌似是死的2/3
脑洞
python字典的实现上, 扩容也是左移或者右移
分布式系统的分片数量增加, 也是一个rehash的过程.
跳跃表/跳表
redis的有序集合通过dict和跳表同时实现, 兼顾dict的O1和跳表的有序性.
跳表可以说是对平衡树的一种工程上的妥协.
redis使用跳表作为有序集合键的底层实现之一. 如果一个有序集合包含的元素数量较多, 或者有序集合中元素是比较长的字符串的话, 会使用跳表作为有序集合的底层实现.
todo, 这是因为如果量小的话, 直接使用数组效率会更高???
redis中, 跳表用在了两个地方:
- 实现有序集合键
- 集群节点中用作内部数据结构
redis中跳表新添加数据和删除数据如何操作