Redis深度历险 核心原理与应用实践-笔记
1.2 5种基础数据结构
string(字符串)
字符串string是Redis最简单的数据结构,其内部表示就是一个字符数组。Redis所有的数据结构都是以唯一的key字符串作为名称,然后通过这唯一的key来获取相应的value数据。不同类型的数据结构差异就在于value的结果不一样。
Redis的字符串 是动态字符串,是可以修改的字符串。才用预分配冗余空间的方式来减少内存的频繁分配。当字符串长度小于1MB时,扩容都是加倍现有的空间,如果超过1MB,每次扩容只会多扩1MB,注意字符串最大长度为512MB。
list(列表)
Redis的列表 相当于Java语言里的LinkList, 链表结构。Redis的列表结构常用来做异步队列。 将需要延后处理的任务结构体序列化成字符串,塞进redis 列表中,另一个线程从这个列表中轮训数据进行处理。
【右边进左边出:对列】
【右边进右边出:栈】
【慢操作】 lindex相当于java链表的get(int index)方法,需要对链表进行遍历,性能会随着index增大而变差
【快速列表】redis底层存储的是一个 “快速链表”的结构。首先在列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist(压缩列表)。它将所有元素彼此紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。 所以redis将链表与ziplist结合起来形成quicklist,也就是将多个ziplist使用双向指针串起来使用。既能满足快速插入删除性能,也不会出现太大的空间冗余。
hash(字典)
Redis 中的字典相当于Java中的hashMap.都是数组加链表。
当hash移除最后一个元素之后,该数据结构会被自动删除,内存被回收
set(集合)
Redis的集合相当于Java中的hashset,它内部的键值对是无序的、唯一的。
当set移除最后一个元素之后,该数据结构会被自动删除,内存被回收.
当zset移除最后一个元素之后,该数据结构会被自动删除,内存被回收.
1.2.3 容器型数据结构的通用规则
list、set、zset、hash 这四种数据结构是容器型数据结构,它们共享下面两条通用规则。
a.如果容器不存在,那就创建一个。
b.如果容器中没有元素了,那么立即删除容器。
1.2.4 过期时间
Redis所有数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象,需要注意的是,过期是以对象为单位的,比如一个hash结构的过期是整个hash对象的过期,而不是其中某个子key过期。
如果一个字符串设置了过期时间,然后调用set方法修改了它,它的过期时间会消失。
1.3 分布式锁
setNx
超时问题
可重入性(考虑下thraedlocal 增加计数器
1.4 延时队列
redis的消息队列不是专业的消息队列,没有ack保证,如果对消息的可靠性有极高要求,它就不适用。
1.4.1 异步消息队列
1.5 位图
1.6 HyperLogLog
1.7 布隆过滤器
每个布隆过滤器对应到redis的数据结构中就是一个大型的位数组和几个不一样的无偏hash函数。所谓无偏就是能够把元素的hash算的比较均匀,让元素被hash映射到位数组中的位置比较随机。
向布隆过滤器添加数据的时,会使用多个hash函数对key进行hash,算的一个整数索引值,然后对位数组长度取模运算得到一个位置。每个hash函数会得到一个不同的位置,再把位数组的这几个位置都置为1.
向布隆过滤器询问key是否存在时,会看hash函数对应的索引出的值是否为1.如果有位置为0,则一定不存在,如果都为1,则说明这个key有可能存在。
1.8 简单限流
zset,简单实现某个行为在指定时间内最多执行N次。 这个限流需求存在一个滑动时间窗口(定宽)。可以使用zset的score来圈出这个时间窗口。 用score记录时间戳。
思路:每一个行为到来,都维护一次时间窗口,将时间窗口外的记录全部清除掉。zset集合中只有score值比较重要,value值没实际意义,保证唯一性即可。
这种方案如果量很大的情况,会消耗大量内存。
1.9 漏斗限流
原理:在每次灌水前都会调用算法计算两次灌水之间可以漏掉多少空间,空间大小取决于 时间跟流水的速率。
如何保证取数据+计算+存数据的原子性,可以使用redis 中的 Redis-Cell,提供了原子的限流指令。
1.9 GeoHash
Redis在3.2版本后增加了地址位置Geo模块。
1.11 scan
简单粗暴的查询命令 keys,查询所有满足特定正则字符串规则的key
有两个缺点:
- 没有offset、limit参数,一次性返回所有数据,不好查看
- keys算法是遍历算法,复杂度是O(n)。数据量大的情况会导致Redis服务卡顿。
作为替换可以使用scan命令,拥有以下特点:
- 通过游标分布进行,不阻塞线程
- 提供limit参数
- 返回结果可能有重复,需要客户端去重
- 单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为0
1.11.8 大key扫描
在集群环境下,如果某个key太大,会导致数据迁移卡顿。
如果一个key太大,那么当它需要扩容的时候,会一次性申请更大的一块内存,可以导致卡顿。大key被删除,内存会被一次性回收,卡顿现象也会再次产生。
当Redis的内存大起大落,极有可能是大key导致的。
2.1 线程IO模型
redis 是一个单线程程序,通过 多路复用 解决并发客户端连接。
使用事件轮训,感知事件到来或者指定的时间(timeout)到达后,立即返回。拿到事件后,线程可以顺序处理事件,结束后继续轮轮询。
2.1.5 定时任务
如果线程一直阻塞在select系统调用上,定时任务将无法得到处理。
redis将定时任务记录在“最小堆”的数据结构中,在每个循环周期中,redis会对最小堆里面已经到事件点的任务进行处理.处理完成后,计算下一次将要执行任务的时间差,作为再次循环的timeuot.
2.2 通信协议
RESP 直观的文本协议
2.3 持久化
Redis 持久化机制有两种:
1.快照,一次全量备份。快照是内存数据的二进制序列化形式。
2.AOF日志,连续的增量备份,记录的是内存数据修改的指令记录文本,AOF日志在运行中会变的臃肿,需要定期进行AOF重新,给AOF日志进行瘦身。
快照原理:fork进程+ COW.子进程做持久化的时候,不会修改现有的内存数据结构。父进程会修改现有的内存数据结构。