Redis 学习笔记
1. Redis 包含哪些数据结构
String、Hash、List、Set、SortedSet
底层由简单动态字符串(SDS),链表,字典,跳跃表,整数集合,压缩列表,对象
/*
* Redis 对象
*/
typedef struct redisObject {
// 类型
unsigned type:4;
// 不使用(对齐位)
unsigned notused:2;
// 编码方式
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock)
unsigned lru:22;
// 引用计数
int refcount;
// 指向对象的值
void *ptr;
} robj;
-
String 由 SDS (Simple Dynamic String) 实现, SDS 包含 已占用长度,剩余长度, 存储内容。传统C的 String 获取 String 长度 O(n)效率,SDS 获取长度 O(1) 效率。
-
List 由 链表实现。
-
Hash 由两个hashtable组成, 一个使用,一个备用,备用的是在扩容和缩容的情况下进行。
-
sortSet 是由 skipList 或者 zipList 实现的, sortSet 又叫zSet
skipList: 平均时间复杂度为O(log n).
skipList 介绍
skiplist与平衡树、哈希表的比较
-
skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
-
在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
-
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
-
从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
-
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
-
从算法实现难度上来比较,skiplist比平衡树要简单得多。
高阶结构
bitmap
优势
1.基于最小的单位bit进行存储,所以非常省空间。
2.设置时候时间复杂度O(1)、读取时候时间复杂度O(n),操作是非常快的。
3.二进制数据的存储,进行相关计算的时候非常快。
4.方便扩容
限制
redis中bit映射被限制在512MB之内,所以最大是2^32位。建议每个key的位数都控制下,因为读取时候时间复杂度O(n),越大的串读的时间花销越多。
使用场景:
1.一种是某一用户的横向扩展,即此个key值中记录这当前用户的各种状态值,允许无限扩展(2^32内)
点评:这种用法基本上是很少用的,因为每个key携带uid信息,如果存储的key的空间大于value,从空间角度看有一定的优化空间,如果是记录长尾的则可以考虑。
2.一种是某一用户的纵向扩展,即每个key只记录当前业务属性的状态,每个uid当作bit位来记录信息(用户超过2^32内需要分片存储)
点评:基本上项目使用的场景都是基于这种方式的,按业务区分方便回收资源,key值就一个,将uid的存储转为了位的存储,十分巧妙的通过uid即可找到相应的值,主要存储量在value上,符合预期。
HyperLogLog
原理比较复杂 https://juejin.im/post/5c7900bf518825407c7eafd0
低存储,概率统计大数据。
什么情况下rehash
负载因子 = 哈希表保存的key的数量 / 哈希表的大小
当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行操作:
服务器执行 BGSAVE/BGREWRITEAOF 命令且负载因子大于 5 时,Redis 会对 dictht 扩容;
服务器没有执行 BGSAVE/BGREWRITEAOF 命令且负载因子大于 1 时,Redis 会对 dictht 扩容;
负载因子小于 0.1 时,Redis 会对 dictht 缩容。
渐进式rehash
操作辅助rehash:每次操作, 会有辅助rehash, rehash一个槽位。
定时辅助rehash:但是如果长时间redis是空闲的,那么redis长时间是两个表的状态,所以还会进行定时rehash。 定时辅助rehash 每次rehash100个槽,但是占用CPU的时间不能超过1ms,超过则直接退出。
渐进式rehash执行期间的哈希表操作
因为在rehash的过程中,字典会同时使用两个哈希表,所以在rehash期间,字典的删除、查找、更新、增加等操作会在两个哈希表中进行。比如:
要在字典中查找某一个键,程序会现在ht[0]里面进行查找,如果没找到,就会继续到ht[1]里面进行查找。
如果是添加操作,则新添加的键值对会一律被保存到ht[1]中,而ht[0]不再进行任何添加操作:这一措施保证ht[0]中的键值对数量只减不增,并且随着操作的执行,最终变成空表
redis 缓存一致性解决
先修改数据库再删缓存,或者先修改数据库再更新缓存。
1.第一种方案:采用延时双删策略
先删除缓存
再写数据库
休眠500毫秒
再次删除缓存
弊端:结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时
2.第二种方案:异步更新缓存
读Redis:热数据基本都在Redis
写MySQL:增删改都是操作MySQL
更新Redis数据:MySQ的数据操作binlog,来更新到Redis
redis 键失效的内部实现
Redis 删除失效主键的方法主要有两种:
消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它
积极方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除
近似LRU算法
为什么不用LRU 算法。 LRU 算法需要维持一个双向链表,存储一个pre,next的指针需要额外的存储空间比较浪费。 近似LRU算法可以节省24 bit用于存储时间戳。 24bit 会不会不够? 可以维持一个194天的失效期,缓存一般不可能这么久。
redis 的主键失效机制会不会影响系统性能?
通过以上对 Redis 主键失效机制的介绍,我们知道虽然 Redis 会定期地检查设置了失效时间的主键并删除已经失效的主键,但是通过对每次处理数据库个数的限制、activeExpireCycle 函数在一秒钟内执行次数的限制、分配给 activeExpireCycle 函数CPU时间的限制、继续删除主键的失效主键数百分比的限制,Redis 已经大大降低了主键失效机制对系统整体性能的影响,但是如果在实际应用中出现大量主键在短时间内同时失效的情况还是会使得系统的响应能力降低,所以这种情况无疑应该避免。
redis 能支持高并发的原因
- redis 是基于内存的,读写在内存中进行,所以高效
- redis 是基于单线程的, 避免了线程切换的损耗。能最大化的利用cpu。此外,虽然是单线程的,但是可以使用多进程,多开几个worker。
- redis 是使用多路复用的技术,可以处理并发的链接。
- 此外,redis支持各类数据结构,也是它能高效处理请求的原因。
redis 持久化
redis 支持两种
RDB:在指定的时间间隔能对你的数据进行快照存储。
AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
我们都知道RDB的快照、AOF的重写都需要fork,这是一个重量级操作,会对Redis造成阻塞。因此为了不影响Redis主进程响应,我们需要尽可能降低阻塞:
- 降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写;
- 控制Redis最大使用内存,防止fork耗时过长;
- 使用更牛逼的硬件;
- 合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。fork时会复制主进程,如果主进程占用内存较大,就容易崩溃。
持久化的技巧:
- 如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回;
- 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据;
- 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行;
- 可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令;
RDB持久化与AOF持久化可以同时存在,配合使用。
redis 哨兵
Redis实现了Sentinel哨兵机制解决方案。由一个或多个Sentinel去监听任意多个主服务以及主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线的主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已经下线的从服务器,并且Sentinel可以互相监视。
redis 集群进入fail状态
如果某一台机器宕机, 会去自动寻找它的从服务器。
必要条件:
A、某个主节点和所有从节点全部挂掉,我们集群就进入faill状态。
B、如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.
C、如果集群任意master挂掉,且当前master没有slave.集群进入fail状态
redis 的多路复用
多路-指的是多个socket连接,复用-指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。
redis 击穿,雪崩, 击透
击穿: 指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。
方案:1. key 永远不过期 2.互斥锁
雪崩:雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升,从而崩溃。
方案:1.失效时间加随机函数,避免同时失效。
击透:一般是出现这种情况是因为恶意频繁查询才会对系统造成很大的问题: key缓存并且数据库不存在,所以每次查询都会查询数据库从而导致数据库崩溃。
方案:1.击透的key做一个短暂的缓存。
memcached 和 redis 的区别和应用场景
memcached
- 对象存储小于 1M
- 只支持string
- 不支持持久化
redis
- 支持持久化
- 支持多种数据类型
- 单线程多路复用,支持高并发
相对来说,memcached 适合只需要单一 key-value的场景,不需要数据结构支持的情况,并且不需要持久化。