【8. Redis 的设计、实现】
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 的设计、实现】相关推荐
- 深度剖析不一样的Redis架构设计!
- 01.不一样的Redis - 提到Redis,大家一定会想到的几个点是什么呢? 高并发.KV存储.内存数据库.丰富的数据结构.单线程(版本6之前)等. 那么,接下来,上面提到的这些 ...
- 阿里JAVA面试题剖析:一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?...
面试原题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 其实一般问问题,都是这么问的,先 ...
- Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理
基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3 ...
- 17 redis -key设计原则
书签系统 create table book ( bookid int, title char(20) )engine myisam charset utf8;insert into book val ...
- 【作者面对面问答】包邮送《Redis 5设计与源码分析》5本
墨墨导读:本文节选自<Redis 5设计与源码分析>,主要为读者分析Redis高性能内幕,重点从源码层次讲解了Redis事件模型,网络IO事件重在使用IO复用模型,时间事件重在限制最大执行 ...
- python文本框与数据库的关联_Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理...
基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3. ...
- 霸榜巨作、阿里内部顶级专家整理(Redis 5设计与源码分析)
前言 在开源界,高性能服务的典型代表就是Nginx和Redis.纵观这两个软件的源码,都是非常简洁高效的,也都是基于异步网络I/O机制的,所以对于要学习高性能服务的程序员或者爱好者来说,研究这两个网络 ...
- 刘奇-豌豆荚分布式redis的设计与实现
刘奇-豌豆荚分布式redis的设计与实现 codis是豌豆荚开源分布式redis解决方案,整体基本有golang编写,和大家聊聊整个设计过程中的各种纠结,考量,和官方的redis-cluster的差异 ...
- 新书推荐 |《Redis 5设计与源码分析》
新书推荐 <Redis 5设计与源码分析> 点击上图了解及购买 好未来.滴滴.百度等公司专家联合撰写,掌握Redis 5设计与命令实现,透彻掌握分布式缓存. 编辑推荐 多名专家联袂推荐,资 ...
最新文章
- 知乎千万级高性能长连接网关是如何搭建的
- Mybatis实现分库分表
- 清华大学冯珺:基于强化学习的关系抽取和文本分类 | 实录·PhD Talk
- java7 AIO初体验
- 2017第四季度移动行业数据报告
- Sql Server全局变量(转)
- [hackinglab][CTF][综合关][2020] hackinglab 综合关 writeup
- 金蝶云拿下客户满意度第一,中国SaaS企业觅得“后发先至”良机
- 【安卓笔记】抽屉式布局----DrawerLayout
- php连接数据库(一)
- Sigar 编译笔记
- 导出期刊对应格式的参考_3.2怎样按照某个期刊的格式要求生成文后的参考文献.PDF...
- 首次项目经验总结(一)
- Charles接口模拟404/502
- 如何将.frm,.MYD,.MYI文件导入数据库
- 新版itunes添加铃声
- 宜青春 信未来,用科技创新拓宽金融科技的护城河
- 计算机技术前沿知识,计算机前沿技术综述_相关文章专题_写写帮文库
- webpack之常见性能优化
- iqoo3可以安装鸿蒙系统吗,鸿蒙系统属于安卓吗?