> For the complete documentation index, see [llms.txt](https://shuixn.gitbook.io/notebook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://shuixn.gitbook.io/notebook/cun-chu/redis.md).

# Redis

redis相关

## nosql分类

1. 键值类：redis
2. 列存储：HBase
3. 文档型：mongoldb
4. 图形：neo4j

## cap定理

1. 一致性：所有节点在同一时间具有相同的数据
2. 可用性：保证每个请求不管成功或者失败都有响应
3. 分区容忍：系统中任意信息的丢失或失败不会影响系统的继续运作

大多数分布式数据产品满足AP

## 并发算法cas原理

1. 定义：CAS（Compare-and-Swap），即比较并替换，是一种实现并发算法时常用到的技术
2. 构成
   1. 内存地址V
   2. 旧预期值A
   3. 即将要更新的目标值B
   4. CAS指令执行时，当且仅当内存地址V的值与预期值A相等时，将内存地址V的值修改为B，否则就什么都不做。整个比较并替换的操作是一个原子操作
   5. CAS是通过无限循环来获取数据的，如果在第一轮循环中，a线程获取地址里面的值被b线程修改了，那么a线程需要自旋，到下次循环才有可能有机会执行
3. 缺点
   1. 循环时间长开销大
   2. 只能保证一个共享变量的原子操作：当对一个共享变量执行操作时，我们可以使用循环CAS的方式来保证原子操作，但是对多个共享变量操作时，循环CAS就无法保证操作的原子性，这个时候就可以用锁来保证原子性。
4. 问题
   1. ABA问题

## 持久化

[Redis的持久化原理](https://juejin.im/post/5b70dfcf518825610f1f5c16)

1. RDB：指定时间间隔内将内存中的数据集快照写入磁盘
   1. 触发
      1. 手动：`save` 会阻塞当前Redis服务器，直到持久化完成，线上应该禁止使用。
      2. redis定时：`bgsave` 该触发方式会fork一个子进程，由子进程负责持久化过程，因此阻塞只会发生在fork子进程的时候。
         1. 根据我们的 `save m n` 配置规则自动触发；
         2. 从节点全量复制时，主节点发送rdb文件给从节点完成复制操作，主节点会触发 `bgsave`；
         3. 执行 `debug reload` 时；
         4. 执行 `shutdown` 时，如果没有开启aof，也会触发。
   2. 优势
      1. 只包含一个文件，方便备份
      2. 恢复大数据集时的速度比AOF的速度快
      3. 最大化redis性能：通过fork子进程进行，父进程无须执行任何磁盘IO操作
   3. 劣势
      1. 丢失数据
      2. 数据集庞大时，fork操作会非常耗时。我们可以控制单个Redis实例的最大内存，来尽可能降低Redis在fork时的事件消耗。以及上面提到的自动触发的频率减少fork次数，或者使用手动触发，根据自己的机制来完成持久化。
2. AOF：
   1. 模式
      1. always：把每个写命令都立即同步到aof，很慢，但是很安全
      2. `everysec`：每秒同步一次，是折中方案
      3. no：redis不处理交给os来处理，非常快，但是也最不安全
   2. 优势
      1. 耐久
      2. 不断电
      3. 易读
   3. 劣势
      1. 数据量大
      2. 慢
3. 同时使用两种

### 从持久化中恢复数据

AOF文件保存的数据更完整，基本上最多损失1s的数据

1. 直接重启redis
2. 先检测aof文件是否存在，存在则加载aof，然后直接启动
3. 如果aof不存在，则检测rdb文件是否存在，存在则加载rdb文件，然后启动
4. 如果都不存在，则直接启动。

### 持久化的性能

通过上面的分析，我们都知道RDB的快照、AOF的重写都需要fork，这是一个重量级操作，会对Redis造成阻塞。因此为了不影响Redis主进程响应，我们需要尽可能降低阻塞。

1. 降低fork的频率，比如可以手动来触发RDB生成快照、与AOF重写；
2. 控制Redis最大使用内存，防止fork耗时过长；
3. 使用更牛逼的硬件；
4. 合理配置Linux的内存分配策略，避免因为物理内存不足导致fork失败。

具体实践

1. 如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据，可以关闭持久化，如果丢失数据可以通过其它途径补回；
2. 自己制定策略定期检查Redis的情况，然后可以手动触发备份、重写数据；
3. 单机如果部署多个实例，要防止多个机器同时运行持久化、重写操作，防止出现内存、CPU、IO资源竞争，让持久化变为串行；
4. 可以加入主从机器，利用一台从机器进行备份处理，其它机器正常响应客户端的命令；
5. RDB持久化与AOF持久化可以同时存在，配合使用。

## 事务

可以一次执行多个命令，本质是一组命令的集合。一个事务中的，所有命令都会序列化，按顺序地串行化执行而不会被其它命令插入

### 阶段

1. 开启：MULTI
2. 入队：将多个命令入队到事务中，接到这些命令不会立即执行，而是放到等待执行的事务队列里面
3. 执行：EXEC

### 特性

1. 单独的隔离操作：不会被其他客户端发来的命令请求所打断
2. 没有隔离级别的概念：事务提交前任何指令都不会被实际执行
3. 不保证原子性：同一个事务中如果有一条命令执行失败，其后的命令仍然会被执行，没有回滚

## 锁机制

使用乐观锁CAS

## redis集群方案

1. 主从复制
2. 哨兵模式
3. redis cluster
4. 代理（twemproxy）
5. codis（广泛使用）
6. 客户端分片（余数法、随机法）

### 主从复制

1. 目的
   1. 读写分离
   2. 容灾恢复
2. 方式
   1. 全量同步
   2. 增量同步
3. 问题
   1. 从库不可写
   2. 在Redis2.8版本之前，从库死后复活会发送sync请求和主机全量同步，所以死后复活还是从库，但是，当多个从库同时复活的话会导致主机IO剧增而宕机；2.8版本之后，主服务器只需要将断线期间执行的命令发给从服务器即
   3. 哨兵模式：主机死后，从机待命。并且主库回来后，他将仍然是主库，原来的“主库”变为了从库

## 过期策略（redis同时使用惰性过期和定期过期）

1. 定时过期：通过对key设置定时器
   1. 优点
      1. 过期立即清除，对内存友好
   2. 缺点
      1. 占用大量cpu资源去处理过期数据，影响缓存的响应时间和吞吐量
2. 惰性过期：获取key时判断是否过期
   1. 优点
      1. 可最大化节省cpu资源
   2. 缺点
      1. 可能会出现大量的过期key没有再次被访问，从而不会被清除，占用大量内存
3. 定期过期：每隔一定的时间，会扫描一定数量的数据库的expires字典中一定数量的key，并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时，可以在不同情况下使得CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据，其中，key是指向键空间中的某个键的指针，value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

## 内存淘汰策略

1. noeviction：当内存不足以容纳新写入数据时，新写入操作会报错
2. alleys-lru：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的key
3. alleys-random：当内存不足以容纳新写入数据时，在键空间中，随机移除某个key
4. violation-lru：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，移除最近最少使用的key
5. violation-random：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，随机移除某个key。
6. violation-ttl：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，有更早过期时间的key优先移除

## 线程模型

1. 为什么 Redis 在最初的版本中选择单线程模型？
   1. 使用单线程模型能带来更好的可维护性，方便开发和调试；(多线程的竞态条件)
   2. 使用单线程模型也能并发的处理客户端的请求；（IO多路复用）
   3. Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU；
2. 为什么 Redis 在 4.0 之后的版本中加入了多线程的支持？
   1. Redis 在最新的几个版本中加入了一些可以被其他线程异步处理的删除操作： UNLINK、FLUSHALL ASYNC 和 FLUSHDB ASYNC
   2. 耗时长的操作通过后台线程异步进行处理

## 适用场景

1. 数据高并发读写
2. 海量数据读写
3. 扩展性要求高的数据
4. 例子
   1. 保存热点信息
   2. 排行榜
   3. 计数
   4. 单点登录（session共享）

## 题型

### 百万级数据排行榜

1. 单个排行榜可以通过`sorted set`实现
2. 多个排行榜需要进行`分桶`设计

分桶排名

1. 统计用户在各个分段得分的直方图分布情况
2. 合理拆分各个分段的范围，如100万可以拆分为0\~10W，10～30W，30～50W，50～100W。把对应得分的用户分别存储在相应的桶`sorted set`中
3. 排名时简单缓存每个桶的用户总数，如果要查询`Top K`排名，只需要查看最高得分的桶`sorted set`即可

### 原子计数器incr

1. set num 0
2. 每次有相关操作的时候，就向Redis服务器发送一个incr命令：incr num

应用

1. 用户访问网站的次数：web应用只需要通过拼接用户id和代表当前时间的字符串作为key，每次用户访问这个页面的时候对这个key执行一下incr命令
2. 限速器：限制某个api每秒每个ip的请求次数不超过10次

## 参考

1. [千万用户排名设计](https://zhuanlan.zhihu.com/p/50770947)
2. [Redis原子计数器incr](https://blog.csdn.net/Roy_70/article/details/78260826)
3. [为什么Redis选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
