# Redis 相关
[TOC]
## Redis 基础
### Redis 的数据结构
Redis 常见的数据结构有 string、list、set、sorted set、Hash。
**string**
**list**
**set**
**sorted set**
**hash**
**HyperLogLog**、Geo、Sub/Pub
### Redis 实现消息队列
`1:1` 的消息队列:
Redis 可以通过 list 结构实现消息队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息时需要 sleep 重试,也可以通过 `blpop` 当没有消息时它会阻塞直到消息到来。
```shell
redis > rpush key 1,2,3
redis > lpop key
```
`1:N` 的消息队列:
也可以通过 sub/pub,实现 1:N 的消息队列,但是当消费者下线时,会产生消息的丢失,这种场景下使用专业的消息队列会比较合适。如 Kafka、**RocketMQ**
`延时`消息队列:
使用 `sorted set` 结构来实现延时消息队列,拿时间戳作为 score,消费内容作为 key 调用 zadd 来生产消息,消费者使用 `zrangebyscore` 获取 N 秒前的数据,进行轮询处理。
### Redis 的淘汰策略
一般的剔除策略有 **FIFO** 淘汰最早数据、**LRU**(Least Recently Used) 剔除最近最少使用、和 **LFU**( Least Frequently Used) 剔除最近使用频率最低的数据几种策略
- **noeviction**(默认):返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- **allkeys-lru**: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- **volatile-lru**: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- **allkeys-random**: 回收随机的键使得新添加的数据有空间存放。
- **volatile-random**: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- **volatile-ttl**: 回收在过期
- 集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
记忆:
allkeys:针对所有的 key,两组,共 2 个
volatile:针对已经过期的 key,两组 + ttl,共 3 个
noeviction:不淘汰,共 1 个
### bitmap 实现布隆过滤器
### Redis 持久化,它们之前有什么区别
RDB、AOF
+ RDB 把内容中数据以快照的方式写入磁盘,通过 fork 子进程的方式实现。但是在 RDB 保存之前宕机,会造成数据丢失
+ AOF 以文本日志的形式记录 Redis 的操作日志(写入、删除)。数据规模比 RDB 大,AOF的运行效率低于 RDB
RDB 的原理:
关键词:`fork`、`COW`
fork 一个子进程,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。COW,即为 Copy On Write
### 缓存常见问题
+ 缓存更新方式
**产生原因:**缓存的数据源是发生变更时需要对缓存中的数据进行更新,数据源有可能是 DB、也有可能是其他的服务(可能无法及时主动感知数据变更)。
**解决办法**:
+ DB可以在更新后,更新缓存。
+ 无法主动感知数据变更的场景,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。或者选择异步更新。
+ 数据一致性
**产生原因**:DB 数据更新,缓存数据更新失败(网络延迟、异步更新失败)
**解决办法**:
+ 操作不耗时:选择重试
+ 操作耗时:异步补偿任务更新数据
+ 缓存穿透
**产生原因**:缓存中 key 不存在,然后穿透 DB 依然不命中,大量的请求穿透缓存访问到 DB。常见的是恶意请求不存在的 id,查询不到缓存而直接查询 DB。
**解决办法:**
+ 使用布隆过滤器,利用其**肯定不存在**或者**可能存在**的特性
+ 对不存在的 id 进行空对象标记,避免相同 id 在访问 DB。(可能会导致大量无用数据)
+ 缓存击穿
**产生原因**:某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。
**解决方案:**
+ 可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力;
+ 使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。
+ 针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效
+ 缓存雪崩
**产生原因**:缓存挂掉,这时所有的请求都会穿透到 DB。
**解决方案:**
+ 使用快速失败的熔断策略,减少 DB 瞬间压力;
+ 使用主从模式和集群模式来尽量保证缓存服务的高可用。
## Redis 与 Memecahe
### Memcache 的优势和劣势
**特点**:
- MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;
- MC 功能简单,使用内存存储数据;
- MC 对缓存的数据可以设置失效期,过期后的数据会被清除;
**劣势**:
- key 不能超过 250 个字节;
- value 不能超过 1M 字节;
- key 的最大失效时间是 30 天;
- 只支持 K-V 结构,不提供持久化和主从同步功能。
### Redis 的特点
+ Redis 采用单线程模式处理请求,为什么采用单线程?
+ 因为采用了非阻塞的异步事件处理机制
+ 缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价
+ **Redis** 支持持久化
+ 除了 K-V 之外,还支持多种数据格式,如 list、set、sorted set、hash 等。
+ **Redis** 提供主从同步机制,以及 **Cluster** 集群部署能力,能够提供高可用服
## Redis 高可用
### Sentinel 哨兵
> Sentinel 着眼于 Redis 的高可用,当 master 节点宕机后,会自动将 slave 提升为 master 继续提供服务。
>
> Cluster 着眼于扩展,当单个 redis 内存不足时,使用 Cluster 进行分片存储。
Redis 支持主从同步,提供 Cluster 的部署模式,通过 Sentinel 哨兵监控主服务器的状态。如果主服务器宕机了,根据一定策略从从服务器中选出新主(自动故障转移)。
选主的策略:
+ slave 的 priority 设置的越低,优先级越高;
+ 同等情况下,slave 复制的数据越多优先级越高;
+ 相同的条件下 runid 越小越容易被选中。
> Sentinel 通过`Raft 协议`来保证自身的高可用。
哨兵组件的主要功能:
- 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个 **Redis** 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
### 主从
一般情况下一主多从,主写从读。
1. 启动一个 slave 时,发送一个 psync 到 master
2. slave 第一次连接到 master,触发全量复制,,
3. master 启动一个线程,生成 RDB 快照(master 做一次 `bgsave`),并将其发送给 slave(master 将后续修改操作记录到内存 buffer 中)
4. slave 拿到 RDB 后写入磁盘,并载入内存,master 将内存中的新命令发送给 slave
> slave 第一次使用 RDB,后的的新数据 master 将 AOF 增量同步给 slave。
###
### key 失效机制
redis 中的 key 可以设置过期时间,过期后 Redis 采用主动和被动结合的失效机制。
+ 在访问时触发被动删除
+ 定期主动删除(时间事件,位于 redis.c/serverCron )
+ 当前已用内存超过 maxmemory 限定时,触发主动清理策略(通过 maxmemory-policy 设置,并不会针对所有的 key,会根据 maxmemory-samples 个key的个数作为样本池来抽样清理。默认为 5),在 redis.conf 中可通过 `hz` 设置,默认是 10,表示 `1s 运行 10 次`
# 参考
+ [避免缓存击穿之布隆过滤器](https://juejin.im/post/5db69365518825645656c0de)
+ [Redis 面试基础](https://juejin.im/post/5db66ed9e51d452a2f15d833)
+ [敖丙 Redis 系列](https://github.com/AobingJava/JavaFamily/tree/master/docs/redis)
