



























缓存
穿透, 击穿, 雪崩, 双写一致, 持久化, 数据过期, 淘汰策略
分布式锁
setnx, redission
消息队列, 延迟队列
查询不存在的数据, mysql 中查询不到也不会写入缓存, 导致每次都查询数据库
缓存空数据, 把空结果进行缓存
Redisson 布隆过滤器
bitmap: 一个以bit为单位的数组, 每个单元只存储二进制的 0 或 1
使用: 将数据库中存在的数据通过多个哈希函数获取哈希值, 并将数组对应位置改为 1. 对于请求参数进行相同的哈希获取数组对应元素的值, 如果有元素为 0 则说明该元素一定不存在, 全 1 则说明该元素可能存在. 存储的数据越多则数组中的 1 越多, 误判率越大.
注意: 使用中可能需要注意定时(或增加数据时)更新布隆过滤器解决缓存一致性问题
作用: 检索一个元素是否在一个集合中
给某个 key 设置过期时间, 但恰好 key 过期时有大量并发请求, 这些请求会把数据库压垮
互斥锁
缓存未命中时获取互斥锁: 获取成功则查询数据库缓存数据; 获取失败则休眠重试获取锁
逻辑过期
设置一个expire字段表示过期时间, 后续检验这个字段判断是否过期
缓存未命中时获取互斥锁: 获取成功则开启新线程查询数据库缓存数据, 但此时这个线程不用等待锁, 可以直接返回过期数据; 互斥锁获取失败则直接返回过期数据
同一时间大量的缓存 key 同时失效或者 redis 宕机, 导致大量请求到达数据库
修改了数据库的数据也要同时更新缓存的数据, 缓存和数据库的数据要保持一致性
读操作: 缓存命中, 直接返回; 未命中则查询数据库, 写入缓存, 设定超时时间
写操作:
延迟双删
先删缓存, 操作数据库, 延迟一会然后再写入缓存 (延迟为了数据库的同步)
延迟时间不好控制, 期间会出现脏数据
读写锁 (强一致性)
读的时候添加共享锁: 读读不互斥, 读写互斥
写的时候添加排他锁: 读读和读写均互斥, 避免脏数据
异步 mq (最终一致性)
使用 MQ 中间件, 更新数据后通知缓存删除
使用 Canal (最终一致性)
伪装成 mysql 的从节点, 通过读取 binlog 数据更新缓存, 不需要修改业务代码
RDB
Redis Database Backup file, 将内存中的所有数据都记录到磁盘中, 当 redis 重启后从磁盘读取快照文件, 恢复数据
bgsave 开始时 fork 主进程得到子进程, 子进程共享主进程的内存数据, 完成 fork 后读取内存数据并写入 RDB 文件
fork 采用 copy-on-write 技术:
- 主进程执行读操作时, 访问共享内存
- 主进程执行写操作时, 拷贝一份数据, 执行写操作
AOF
Append Only File: 将每一个写命令记录在 AOF 文件中, 恢复数据时就是在执行一遍命令
redis 对数据设置有效时间, 数据过期以后就需要从内存中删除, 删除数据可以有不同的规则
设置 key 过期时间后在使用 key 时进行检查是否过期, 如果过期就删除, 否则返回 key
每隔一段时间对一些 key 检查, 删除过期的 key (从一定数量的数据库中取出一定数量的随机 key 进行检查, 删除过期的 key)
SLOW 模式: 定时任务, 执行频率默认 10hz, 每次不超过 25ms
FAST 模式: 执行频率不固定, 两次间隔不低于 2ms, 每次耗时不超过 1ms
当 redis 内存不够用时, 此时再向 redis 中添加新的 key, 那么 redis 就会按照某一种规则将内存中的数据删除掉, 这种删除数据的规则称之为内存的淘汰策略
使用 setnx (set if not exists) 实现
获取锁:
# NX 互斥 EX 设置超时时间
SET lock value NX EX 10
释放锁:
redisson 使用 watch dog 机制给锁续期 (默认每 10s 查看一次), 从而合理控制锁的时间

加锁、设置过期时间等操作都是基于 lua 脚本完成, 保证操作的原子性
redisson 的锁是可重入锁, 多个锁重入需要判断是否是当前线程, 在 redis 中使用 hash 结构 来存储 线程信息和重入次数
可以使用redisson 提供的红锁解决主从数据一致性, 实现是对多个 $(\frac{n}{2} + 1)$ 节点同时创建锁, 但是性能太低、实现复杂、运维繁琐, 所以不推荐
redis 是 AP 思想, 如果要保证数据的强一致性建议使用 CP 思想的 zookeeper
CAP: C (一致性), A (可用性), P (分区容错)
单节点 Redis 的并发能力有上限, 搭建主从集群, 实现读写分离可以提高 Redis 并发能力
全量同步
replid: Replication Id, 数据集标志, 每个 master 有唯一的 id, slave 继承 master 的 replid
offset: 偏移量, 随着记录在 repl_backlog 中的数据增多而增大, slave 完成同步时也会记录当前同步的 offset, 如果 slave 的 offset 小于 master 的则说明需要更新
增量同步

sentinel 不断检查 master 和 slave 是否正常工作master 故障时 sentinel 会将一个 slave 提升为 master, 原 master恢复后也以新的master 为主sentinel 充当 redis 客户端发现来源, 集群发生故障转移时会将最新的信息推送给 redis 客户端服务状态监控
哨兵基于心跳检测服务, 每隔 1 秒向集群的每个实例发送一次 ping 命令
哨兵选主原则
脑裂
由于网络等原因可能会出现脑裂的情况, 比如, 由于 redis 的 master 节点和 salve 节点和 sentinel 处于不同的网络分区,使得 sentinel 没有能够心跳感知到 master,所以通过选举的方式提升了一个 salve 为 master,这样就存在了两个 master, 这种现象称之为脑裂. 当网络恢复后, sentinel 会将 old master 降级为 slave, 以新的 master 为主. 由于期间客户端仍会向 old master 节点中写入数据, 所以这样会导致大量数据丢失.
设置至少有一个从节点才能写入数据: min-replicas-to-write 1
设置主从数据复制和同步的延迟时间: min-replicas-max-lag 5
优点
实现
set key value # 使用 CRC16 算法计算 key 的 hash 值取模进行分片
set {aaa} key value # 使用 CRC16 算法计算 aaa 的 hash 值取模进行分片
单线程为什么那么快
I/O 多路复用模型
利用单个线程来同时监听多个 Socket, 并在某个 Socket 可读、可写时得到通知, 从而避免无效的等待, 充分利用 CPU 资源. 目前的 I/O 多路复用都是采用的 epoll 模式实现 (以前是 select 和 poll), 它会在通知用户进程 Socket 就绪的同时, 把已就绪的 Socket 写入用户空间, 不需要挨个遍历 Socket 来判断是否就绪, 提升了性能.
Redis 网络模型
使用 I/O 多路复用结合事件处理器应对多个 Socket 请求
Redis 常用数据结构有 String, List, Hash, Set, Zset,此外还有其他数据结构如 Geospatial, HyperLogLog, Bitmap 等1。

String 类型以 int/SDS(simple dynamic string) 作为结构存储,int 用来存放整型数据,sds 存放字节/字符串和浮点型数据。sds 相比于 char* 的好处有:使用 O(1)的时间复杂度获取字符串长度 (利用 sdshdr);制定内存重分配方法,减少因修改字符串而导致的内存分配和释放的次数。
List 类型使用 QuickList 这样来存储数据,QuickList 是一个双向链表,链表的每个节点保存一个 ziplist,所有的数据实际上是存储在 ziplist 中。Redis 7.0 中使用 listpack 代替 ziplist,区别是 listpack 不再记录前一个节点的长度,避免连锁更新的隐患2。

Hash 类型使用 ziplist 和 hashtable 存储,当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认 512 个)且所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时使用 ziplist,否则使用 hashtable。
Set 类型以 intset 或者 hashtable 来存储。当 set 中只包含整数型的元素时,采用 intset 来存储,inset 数组内数据按照顺序存储,查找使用二分查找;否则采用 hashtable 存储,但是对于 set 来说,该 hashtable 的 value 值用于为 NULL,通过 key 来存储元素。
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
Zset 类型使用 skiplist 和 dict 实现。skiplist 存储按分值排序的成员,用来支持平均复杂度为 O(logn)的按照分值定位成员的操作,以及范围查找操作,dict 中 key 表示 zset 的成员数据,value 表示 zset 的分值,用来支持 O(1)复杂度的按照成员取分值的操作。当元素个数要小于 128 个且所有元素成员的长度必须小于 64 个字节时,使用 ziplist 代替 skiplist 存储。

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。