Shellbye.github.io
Shellbye.github.io copied to clipboard
《redis深度历险:核心原理和应用实践》读书笔记
开篇:授人以鱼不若授人以渔—— Redis 可以用来做什么?
由 Redis 面试想到的
小册的内容范围
Redis 可以做什么?
小结
基础:万丈高楼平地起 ——Redis 基础数据结构
Redis 安装
Redis 基础数据结构
string (字符串)
list (列表)
Redis 的列表结构常用来做异步队列使用
hash (字典)
Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略
set (集合)
zset (有序列表)
它的内部实现用的是一种叫着「跳跃列表」的数据结构
跳跃列表
容器型数据结构的通用规则
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则
1、create if not exists
2、drop if no elements
过期时间
如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它的过期时间会消失
应用 1:千帆竞发 —— 分布式锁
分布式锁
setnx(set if not exists)
超时问题
Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻辑执行的太长,以至 于超出了锁的超时限制,就会出现问题。因为这时候锁过期了,第二个线程重新持有了这把锁, 但是紧接着第一个线程执行完了业务逻辑,就把锁给释放了,第三个线程就会在第二个线程逻 辑执行完之间拿到了锁
可重入性
应用 2:缓兵之计 —— 延时队列
异步消息队列
Redis 的 list(列表) 数据结构常用来作为异步消息队列使用,使用rpush/lpush操作入队列, 使用 lpop 和 rpop 来出队列
队列延迟
blpop/brpop
空闲连接自动断开
锁冲突处理
1、直接抛出异常,通知用户稍后重试;
2、sleep 一会再重试;
3、将请求转移至延时队列,过一会再试;
延时队列的实现
应用 3:节衣缩食 —— 位图
基本使用
零存零取
setbit
getbit
整存零取
统计和查找
bitcount
bitpos
魔术指令 bitfield
饱和截断 SAT
失败不执行 FAIL
应用 4:四两拨千斤 —— HyperLogLog
UV 粗略统计
使用方法
pfadd
pfcount
pfmerge
HyperLogLog 实现原理
应用 5:层峦叠嶂 —— 布隆过滤器
布隆过滤器是什么?
当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在
Redis 中的布隆过滤器
布隆过滤器基本使用
bf.add
bf.exists
bf.madd
bf.mexists
布隆过滤器的原理
布隆过滤器的其它应用
爬虫系统
NoSQL 数据库
通过内存中的布隆过滤器过滤掉大量不存在的 row 请求
垃圾邮件过滤
应用 6:断尾求生 —— 简单限流
如何使用 Redis 来实现简单限流策略?
应用 7:一毛不拔 —— 漏斗限流
Redis-Cell
应用 8:近水楼台 —— GeoHash
GeoHash 算法
Redis 的 Geo 指令基本使用
应用 9:大海捞针 —— Scan
Scan 的基础使用
keys 的两个缺点
1、没有 offset、limit 参数,一次性吐出所有满足条件的 key
2、keys 算法是遍历算法,复杂度是 O(n)
scan 相比 keys 具备有以下特点:
1、复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程;
2、提供 limit 参数,可以控制每次返回结果的最大条数,limit 只是一个 hint,返回的 结果可多可少;
3、同 keys 一样,它也提供模式匹配功能;
4、服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;
5、返回的结果可能会有重复,需要客户端去重复,这点非常重要;
6、遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的;
7、单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零;
字典的结构
scan 遍历顺序
普通加法和高位进位加法的区别
字典扩容
对比扩容缩容前后的遍历顺序
渐进式 rehash
更多的 scan 指令
zscan
hscan
sscan
大 key 扫描
在平时的业务开发中,要尽量避免大 key 的产生
那如何定位大 key 呢
原理 1:鞭辟入里 —— 线程 IO 模型
非阻塞 IO
事件轮询 (多路复用)
指令队列
响应队列
定时任务
原理 2:交头接耳 —— 通信协议
RESP(Redis Serialization Protocol)
客户端 -> 服务器
服务器 -> 客户端
原理 3:未雨绸缪 —— 持久化
快照原理
copyOnWrite
fork(多进程)
AOF 原理
AOF 重写
fsync
glibc 提供的 fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘
运维
Redis 4.0 混合持久化
原理 4:雷厉风行 —— 管道
Redis 的消息交互
多条合并一条
管道压力测试
深入理解管道本质
原理 5:同舟共济 —— 事务
Redis 事务的基本使用
multi/exec/discard
原子性
不保证原子性
discard(丢弃)
优化
Watch
原理 6:小道消息 —— PubSub
消息多播
PubSub
模式订阅
消息结构
PubSub 缺点
原理 7:开源节流 —— 小对象压缩
小对象压缩存储 (ziplist)
内存回收机制
操作系统回收内存是以页为单位,如果这个页上只要有一个 key 还在使用,那么它就不能被回收
内存分配算法
原理 8:有备无患 —— 主从同步
CAP 原理
C - Consistent ,一致性
A - Availability ,可用性
P - Partition tolerance ,分区容忍性
网络断开的场景的专业词汇叫着「网络分区」
一句话概括 CAP 原理就是——网络分区发生时,一致性和可用性两难全
最终一致
主从同步
增量同步
Redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本 地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指 令流来达到和主节点一样的状态,一遍向主节点反馈自己同步到哪里了
快照同步
增加从节点
无盘复制
集群 1:李代桃僵 —— Sentinel
消息丢失
Sentinel 基本使用
集群 2:分而治之 —— Codis
略
集群 3:众志成城 —— Cluster
槽位定位算法
跳转
迁移
拓展 1:耳听八方 —— Stream
消息 ID
timestampInMillis-sequence
消息内容
增删改查
1、xadd 追加消息
2、xdel 删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度
3、xrange 获取消息列表,会自动过滤已经删除的消息
4、xlen 消息长度
5、del 删除 Stream
独立消费
xread
创建消费组
略
拓展 2:无所不知 —— Info 指令
Info
1、Server 服务器运行的环境参数 2、Clients 客户端相关信息 3、Memory 服务器运行内存统计数据 4、Persistence 持久化信息
5、Stats 通用统计数据
6、Replication 主从复制相关信息
7、CPU CPU 使用情况
8、Cluster 集群信息
9、KeySpace 键值对统计数量信息
Redis 每秒执行多少次指令?
> redis-cli info stats | grep ops
instantaneous_ops_per_sec:789
Redis 连接了多少客户端?
> redis-cli info clients
connected_clients:124
Redis 内存占用多大 ?
> redis-cli info memory | grep used | grep human
used_memory_human:827.46K # 内存分配器 (jemalloc) 从操作系统分配的内存总量
used_memory_rss_human:3.61M # 操作系统看到的内存占用 ,top 命令看到的内存
used_memory_peak_human:829.41K # Redis 内存消耗的峰值
used_memory_lua_human:37.00K # lua 脚本引擎占用的内存大小
复制积压缓冲区多大?
> redis-cli info replication |grep backlog
repl_backlog_active:0
repl_backlog_size:1048576 # 这个就是积压缓冲区大小
复制积压缓冲区大小非常重要,它严重影响到主从复制的效率。当从库因为网络原因临 时断开了主库的复制,然后网络恢复了,又重新连上的时候,这段断开的时间内发生在 master 上的修改操作指令都会放在积压缓冲区中,这样从库可以通过积压缓冲区恢复中断的 主从同步过程。
积压缓冲区是环形的,后来的指令会覆盖掉前面的内容。如果从库断开的时间过长,或 者缓冲区的大小设置的太小,都会导致从库无法快速恢复中断的主从同步过程,因为中间的 修改指令被覆盖掉了。这时候从库就会进行全量同步模式,非常耗费 CPU 和网络资源。
拓展 3:拾遗漏补 —— 再谈分布式锁
略
拓展 4:朝生暮死 —— 过期策略
过期的 key 集合
定时扫描策略
从库的过期策略
从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令
拓展 5:优胜劣汰 —— LRU
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让 用户自己决定该如何腾出新的空间以继续提供读写服务
LRU 算法
近似 LRU 算法
Redis 为实现近似 LRU 算法,它给每个 key 增加了一个额外的小字 段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳
拓展 6:平波缓进 —— 懒惰删除
Redis 为什么要懒惰删除(lazy free)?
del
删除大对象会导致卡顿
unlink
源自 4.0
flush
异步队列
AOF Sync 也很慢
更多异步删除点
拓展 7:妙手仁心 —— 优雅地使用 Jedis
略
拓展 8:居安思危 —— 保护 Redis
指令安全
rename-command
端口安全
Lua 脚本安全
SSL 代理
拓展 9:隔墙有耳 —— Redis 安全通信
spiped
源码 1:极度深寒 —— 探索「字符串」内部结构
Redis 的字符串叫着「SDS」,也就是 Simple Dynamic String。它的结构是一个带长度信 息的字节数组。
struct SDS<T> {
T capacity; // 数组容量
T len; // 数组长度
byte flags; // 特殊标识位,不理睬它
byte[] content; // 数组内容
}
embstr vs raw
Redis 的字符串有两种存储方式,在长度特别短时,使用 emb 形式存储 (embeded),当 长度超过 44 时,使用 raw 形式存储
源码 2:极度深寒 —— 探索「字典」内部
dict 内部结构
dict 结构内部包含两个 hashtable,扩容缩容时,进行渐进式搬迁
渐进式 rehash
查找过程
hash 函数
hash 攻击
元素都集中到个别链表中,直接导致查找效率急剧下降,从 O(1)退化到 O(n)
缩容条件
set 的结构
Redis 里面 set 的结构底层实现也是字典,只不过所有的 value 都是 NULL,其它的特性和字典一模一样。
源码 3:极度深寒 —— 探索「压缩列表」内部
增加元素
级联更新
IntSet 小整数集合
源码 4:极度深寒 —— 探索「快速列表」内部
略
源码 5:极度深寒 —— 探索「跳跃列表」内部结构
源码 6:极度深寒 —— 探索「紧凑列表」内部
源码 7:极度深寒 —— 探索「基数树」内部