Redis 的设计、实现

数据结构和内部编码

数据结构和内部编码

type命令

type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符 串)hash(哈希)、list(列表)、set(集合)、zset (有序集合),但这些只是 Redis 对外的 数据结构。

每种数据结构都有两种以上的编码

例如 list 数据结构包含了 linkedlist 和 ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种 外部数据结构的内部实现,可以通过 object encoding 命令查询内部编码。

好处:

  • 可以改进内部编码, 而对外的数据结构没有影响
  • 多种内部编码可以在不同的场景发挥优势

redisObject 对象

redis存储的所有值对象内部都定义为redisObject结构体

Redis 存储的数据都使用 redis0bject 来封装,包括 string、hash、list、set,zset 在内的所有数据类型。

type字段:表示当前对象使用的数据类型,Redis主要支持5种数据类型:string, hash、 list,set,zset。可以使用 type { key}命令查看对象所属类型,type 命令返回的 是值对象类型,键都是 string 类型。

encoding 字段:表示 Redis 内部编码类型,encoding 在 Redis 内部使用,代表当 前对象内部采用哪种数据结构实现。理解 Redis 内部编码方式对于优化内存非常重要,同一个对象采用不同的编码实现内存占用存在明显差异。

lru 字段:记录对象最后次被访问的时间,当配置了 maxmemory 和 maxmemory-policy=volatile-lru 或者 allkeys-lru 时,用于辅助 LRU 算法删除键数据。 可以使用 object idletime {key}命令在不更新 lru 字段情况下查看当前键的空闲时 间。

PS:可以使用 scan + object idletime 命令批量查询哪些键长时间未被访问, 找出长时间不访问的键进行清理,可降低内存占用。

也就是说,redis在内存不足时候可以将低频访问的数据从内存中替换掉, 但是并不是删除没了,而是放入硬盘中去,

refcount 字段:记录当前对象被引用的次数,用于通过引用次数回收内存,当 refcount=0 时,可以安全回收当前对象空间。使用 object refcount(key}获取当前 对象引用。当对象为整数且范围在[0-9999]时,Redis 可以使用共享对象的方式来 节省内存。

PS:当数据大量使用[0-9999]的整数时,共享对象池可以节约大量内存,最 多可以达到 30%。但是当设置 maxmemory 并启用 LRU 相关淘汰策略 如:volatile-lru,allkeys-lru 时,Redis 禁止使用共享对象池。同时 ziplist 编码的值对象,
即使内部数据为整数也无法使用共享对象池,因为 ziplist 使用压缩且内存连续的 结构,对象共享判断成本过高。

​ *ptr 字段:与对象的数据内容相关,如果是整数,直接存储数据;否则表示指 向数据的指针。Redis 在 3.0 之后对值对象是字符串且长度<=39 字节的数据,内 部编码为 embstr 类型,字符串 sds 和 redisobject 一起分配,从而只要一次内存操 作即可。

字符串

内部编码

​ int: 8字节长整型

​ embstr: < 44 字节 的字符串

​ raw: > 44 字节的字符串

redis字符串是可以修改的字符串

我们知道 C 语言里面的字符串标准形式是以 NULL 作为结束符,但是在 Redis 里面字符串不是这么表示的。因为要获取 NULL 结尾的字符串的长度使用 的是 strlen 标准库函数,这个函数的算法复杂度是 O(n),它需要对字节数组进 行遍历扫描。

Redis 的字符串叫着「SDS」,也就是 Simple Dynamic String。

​ 在字符串比较小时,SDS 对象头的大小是 capacity+3,至少是 3。意味着分配一个字符串的最小空间占用为 19 字节 (16+3)。

struct SDS<T> { T capacity; // 数组容量, 至少1bytes T len; // 数组长度, 至少1bytesbyte flags; // 特殊标识位,不理睬它 , 至少1bytesbyte[] content; // 数组内容, 长度为capacity
}

当长度超过 44 时,使用 raw 形式存储。?

struct RedisObject {

​ int4 type; // 4bits

​ int4 encoding; // 4bits

​ int24 lru; // 24bits

​ int32 refcount; // 4bytes

​ void *ptr; // 8bytes,64-bit system

} robj; 共16bytes,

​ embstr 存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对 象连续存在一起,使用 malloc 方法一次分配。而 raw 存储形式不一样,它需要两次 malloc,两个对象头在内存地址上一般是不连续的。

​ 而内存分配器 jemalloc/tcmalloc 等分配内存大小的单位都是 2、4、8、16、 32、64 等等,为了能容纳一个完整的 embstr 对象,jemalloc 最少会分配 32 字 节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。

64 - 16 = 50 -10 + 14 - 6  = 40 + 8 = 48
48 -3 = 45(头部最少字节数3)
45 -1 = 44(字符串是以0结尾的)

​ 而且字符串在长度小于 1M 之前,扩容空间采用加倍策略,也就是保留 100% 的冗余空间。当长度超过 1M 之后,为了避免加倍后的冗余空间过大而导 致浪费,每次扩容只会多分配 1M 大小的冗余空间。

字典/哈希

​ dict 是 Redis 服务器中出现最为频繁的复合型数据结构,除了 hash 结构 的数据会用到字典外整个 Redis 数据库的所有 key 和 value 也组成了一个全 局字典,还有带过期时间的 key 集合也是一个字典。zset 集合中存储 value 和 score 值的映射关系也是通过 dict 结构实现的。

struct RedisDb {dict* dict; // all keys key=>value dict* expires; // all expired keys key=>long(timestamp) ...
}struct zset { // dict *dict; // all values value=>score zskiplist *zsl;
}

​ dict 结构内部包含两个 hashtable,通常情况下只有一个 hashtable 是有值 的。但是在 dict 扩容缩容时,需要分配新的 hashtable,然后进行渐进式搬迁, 这时候两个 hashtable 存储的分别是旧的 hashtable 和新的 hashtable。待搬迁 结束后,旧的 hashtable 被删除,新的 hashtable 取而代之。

​ 字典数据结构的精华就落在了 hashtable 结构上了。hashtable 的结 构和 Java 的 HashMap 几乎是一样的,都是通过分桶的方式解决 hash 冲突。 第一维是数组,第二维是链表。数组中存储的是第二维链表的第一个元素的指针。

渐进式 rehash

​ 两个hashtable慢慢搬迁数据, 避免单线程的阻塞

  • 被动搬迁: hset/hdel的指令触发

  • 主动搬迁: 定时任务触发

查找过程

​ Redis 代码中有个 hash_func,它会将 key 映射为一个整数,不同的 key 会 被映射成分布比较均匀散乱的整数。

hash 函数

​ **hashtable 的性能好不好完全取决于 hash 函数的质量。**hash 函数如果可以 将 key 打散的比较均匀,那么这个 hash 函数就是个好函数。Redis 的字典默认 的 hash 函数是 siphash。

siphash 算法即使在输入 key 很小的情况下,也可以 产生随机性特别好的输出,而且它的性能也非常突出,同时还能很大程度避免哈希洪水攻击。对于 Redis 这样的单线程来说,字典数据结构如此普遍,字典操 作也会非常频繁,hash 函数自然也是越快越好。

hash洪水攻击:

利用hash算法的偏向性, 输入特定模式的key, 导致hash集中到一个链表, 查询效率从O(1)变成O(n)

​ siphash 算法针对这种情况做了很好的改进。Java 自带的字符串哈希函数, 使用的是“DJBX33A 算法”的变种,安全性不是很高

这个成本具体有多低呢?

​ 依 2011 年的实验数据,攻击一台基于 Java (Tomcat)的服务器时,仅仅需要 6KB/s 的流量就能打瘫一颗 Intel i7 处理器, 1GB/s 的流量可以打瘫 100000 颗 Intel i7 处理器。

​ 算法中加入一个黑客不知道的秘 密参数?每建一张哈希表,我们就随机生成一个新的秘密参数。

​ 这个黑客不知道的秘密参数,称之为哈希种子(Hash Seed)。而 这类使用哈希种子的哈希算法,我们称之为带密钥哈希算法(Keyed Hash Function)。这些年来,设计了许多新的哈希函数:SipHash、MurmurHash、CityHash 等等。Rust、Python、Ruby 等语言更是把 SipHash 作为默认的哈希表实现方法, 用这些语言编写的项目天生免疫哈希洪水攻击。

​ Java 提出的解决方案其实大家应该知道,HashMap、LinkedHashMap 和 ConcurrentHashMap 三个类引入了一套新的策略来处理哈希碰撞。当一个位置存储的元素个数小于 8 个时,仍然使用链表存储。当一个位置存储的元素个数大于 等于 8 个时,改为使用平衡树来存储。(所以红黑树另一个作用就是防洪水攻击), 一个位置, table默认是16

为什么要设立“8 个元素”(TREEIFY threshold)这样一个限制呢?

因为平衡树相比链表而言有着更高的开销以及更散乱的内存布局(影响缓存命中率)。 在正常情况下,哈希表的一个位置大约只会存储 1~4 个左右的元素,所以没有必要专门开一个平衡树来存储冲突的元素,对一些性能敏感的应用来说会造成显著的负面影响。

扩容条件

​ 正常情况下,当 hash 表中元素的个数等于第一维数组的长度时,就会开始 扩容,扩容的新数组是原数组大小的 2 倍。不过如果 Redis 正在做 bgsave,为 了减少内存页的过多分离 (Copy On Write),Redis 尽量不去扩容 (dict_can_resize),但是如果 hash 表已经非常满了,元素的个数已经达到了第一 维数组长度的 5 倍 (dict_force_resize_ratio),说明 hash 表已经过于拥挤了,这 个时候就会强制扩容。

缩容条件

​ 当 hash 表因为元素的逐渐删除变得越来越稀疏时,Redis 会对 hash 表进 行缩容来减少 hash 表的第一维数组空间占用。缩容的条件是元素个数低于数组长度的 10%缩容不会考虑 Redis 是否正在做 bgsave。

列表

​ **ziplist(压缩列表)

【8. Redis 的设计、实现】相关推荐

  1. 深度剖析不一样的Redis架构设计!

    -      01.不一样的Redis    - 提到Redis,大家一定会想到的几个点是什么呢? 高并发.KV存储.内存数据库.丰富的数据结构.单线程(版本6之前)等. 那么,接下来,上面提到的这些 ...

  2. 阿里JAVA面试题剖析:一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?...

    面试原题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 其实一般问问题,都是这么问的,先 ...

  3. Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理

    基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客  QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3 ...

  4. 17 redis -key设计原则

    书签系统 create table book ( bookid int, title char(20) )engine myisam charset utf8;insert into book val ...

  5. 【作者面对面问答】包邮送《Redis 5设计与源码分析》5本

    墨墨导读:本文节选自<Redis 5设计与源码分析>,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行 ...

  6. python文本框与数据库的关联_Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理...

    基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3. ...

  7. 霸榜巨作、阿里内部顶级专家整理(Redis 5设计与源码分析)

    前言 在开源界,高性能服务的典型代表就是Nginx和Redis.纵观这两个软件的源码,都是非常简洁高效的,也都是基于异步网络I/O机制的,所以对于要学习高性能服务的程序员或者爱好者来说,研究这两个网络 ...

  8. 刘奇-豌豆荚分布式redis的设计与实现

    刘奇-豌豆荚分布式redis的设计与实现 codis是豌豆荚开源分布式redis解决方案,整体基本有golang编写,和大家聊聊整个设计过程中的各种纠结,考量,和官方的redis-cluster的差异 ...

  9. 新书推荐 |《Redis 5设计与源码分析》

    新书推荐 <Redis 5设计与源码分析> 点击上图了解及购买 好未来.滴滴.百度等公司专家联合撰写,掌握Redis 5设计与命令实现,透彻掌握分布式缓存. 编辑推荐 多名专家联袂推荐,资 ...

最新文章

  1. 知乎千万级高性能长连接网关是如何搭建的
  2. Mybatis实现分库分表
  3. 清华大学冯珺:基于强化学习的关系抽取和文本分类 | 实录·PhD Talk
  4. java7 AIO初体验
  5. 2017第四季度移动行业数据报告
  6. Sql Server全局变量(转)
  7. [hackinglab][CTF][综合关][2020] hackinglab 综合关 writeup
  8. 金蝶云拿下客户满意度第一,中国SaaS企业觅得“后发先至”良机
  9. 【安卓笔记】抽屉式布局----DrawerLayout
  10. php连接数据库(一)
  11. Sigar 编译笔记
  12. 导出期刊对应格式的参考_3.2怎样按照某个期刊的格式要求生成文后的参考文献.PDF...
  13. 首次项目经验总结(一)
  14. Charles接口模拟404/502
  15. 如何将.frm,.MYD,.MYI文件导入数据库
  16. 新版itunes添加铃声
  17. 宜青春 信未来,用科技创新拓宽金融科技的护城河
  18. 计算机技术前沿知识,计算机前沿技术综述_相关文章专题_写写帮文库
  19. webpack之常见性能优化
  20. iqoo3可以安装鸿蒙系统吗,鸿蒙系统属于安卓吗?

热门文章

  1. PAC(probably approximately correct) 学习架构介绍
  2. 文件系统FATFS的移植教程
  3. vuex的state数据丢失
  4. EAUML日拱一卒-微信小程序实战:位置闹铃 (6)-播放音频
  5. Redis用来干嘛的?
  6. AFN(上传、下载)
  7. compat-wireles的理解
  8. 除尘机器人毕业_一种除尘机器人的制作方法
  9. Java按钮监听器ActionListener 事件监听教程.
  10. QT——开发入门简介