redis-持久化

### 0 命令

#### 常用命令 [参考](https://blog.csdn.net/IT_faquir/article/details/79774821)

- type key 查看数据类型

- select 1 切换数据库

- 查看所有key

    - keys * 阻塞

    - scan 0 不阻塞游标

### 1 数据结构

#### 1.1 层次结构(key 通常以“:”作为分隔符)

- 一个实例:n个db

- 一个db:n个key:value对啊

- 常用5个value类型:

    - string

    - list

    - map

    - set

    - sorted-set

#### 1.2 value对象的通用结构

```c

typedef struct redisObject {

//unsigned 无符号数

    // String ,list 的结构类型

    unsigned type:4;

    // 实现方式 如list, ziplist还是linkedlist

    unsigned encoding:4;

    // 对象的空转时长(当前时间-lru ),用于有限内存下长久不访问对象的清理

    // 最后一次被命令程序访问的时间

    unsigned lru:REDIS_LRU_BITS;

    // 引用计数

    int refcount;

    // 以encoding方式实现这个对象实际地址, 如 string 对于sds 地址

    void *ptr;

} robj;

```

#### 1.3 String

- 值类型

    - 字符串

    - 整数

    - 浮点数

- 内存数据结构

    - value 以 int,SDS(simple dynamic string),作为存储结构

        - int 存放整形数据

        - sds存放字符串和浮点数

- sds结构

```c

typedf struct sdshdr {

  // 实际长度

  unsigned int len;

  // 剩余可用长度  如 buf 实际长度为8 存入 hello , len 为 5,free 为2

  unsigned int free;

  // 存储字符串内容 '\0'结尾

  char buf;

};

```

- buf的扩容与缩容, 当对传进行业务操作后,结构超过buf现有容量,sds 会进行扩容,扩容条件如下

    - 字符串初始化时 buf的大小=len+1

    - 预期串小于1MB,buf的大小=业务预期长度*2+1

    - 预期串大于1MB,buf的大小=业务预期长度 + 1MB+1

#### 1.4 List

- 内存数据结构

    - linkedlist 双向链表

    - ziplist 通常用于list个数不多且元素本身长度不大的情况

        - 所有内容放置在连续的内存中

#### 1.5 Map

- 链地址解决hash冲突

- 内存数据结构

    - hashtable

    - ziplist  适用数据量较小的map


#### 1.6 Set

- 内存数据结构

    - intset    当set中只包含整形元素时候使用

    - hashtable

#### 1.7 SortSet

- 内存数据结构

    - ziplist 通常用于list个数不多且元素本身长度不大的情况

    - skiplist+hashtable 实现

### 2 过期键删除策略

- Redis服务器实际使用的是**惰性删除**和**定期删除**两种策略

#### 2.1 定时删除

- 在设置键的过期时间的同时,创建一个定时器,当定时器在键过期时间来临,进行删除

- 好处: 对内存友好,

- 缺点: 对cpu时间不友好。

#### 2.2 惰性删除

- 获取键的时候,检查键有没有过期。

- 好处:对cpu时间友好

- 缺点: 对内存不友好

#### 2.3 定期删除

- 每隔一段时间,程序对数据库进行一次检查,删除里面的过期键。 至于要删除多少过期键,以及要检查多少个数据库,则由算法决定

- 两种折中。

### 3 缓存穿透,缓存击穿,缓存雪崩解决方案分析

[可参考](https://juejin.im/post/6844903575865262094)

#### 3.1 缓存穿透

-  含义: 查询一个一定不存在的数据,然后导致查数据库,导致DB挂掉,有人利用不存在的key频繁攻击我们的应用,这就是漏洞

- 解决方案:

    - 布隆过滤器,所有可能存在的key hash到一个足够大的bitmap中。不存在的key,被bitmap拦截掉。

    - 如果数据库查询为空(不管数据是不存在,还是系统故障),缓存都对空结果key缓存,过期时间会很短,最长不超过5分钟。

#### 3.2 缓存雪崩

- 含义: 某一时刻缓存全部失效,可能是 设置了相同的过期时间,可能redis挂掉

- 解决方案:

    - 大多数 加锁,或者队列方式

    - 简单方案 在原有失效时间基础上增加一个随机值 比如1-5分钟随机。

#### 3.3 缓存击穿

- 含义 某一个key,在某个时间点被超高并发访问,恰好这个时间点过期了。 与雪崩区别这里 雪崩是很多key。

- 解决方案:

    - 使用互斥锁 redis分布式锁

    - 设置永不过期

        - 如 我解析规则 一直不过期,如果修改了, 手动清除缓存。

    - hystrix 做资源隔离

#### 3.4 如何保证缓存与数据库双写的一致性

- 读

    - 先读缓存

        - 缓存没有 读数据库,然后取出数据放入缓存,同时响应

- 更新

    - 先更新数据库,然后删除缓存

        - 为什么是删除缓存,而不是更新缓存

            - 在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值

        - 问题:

            - 先更新数据库,删除缓存失败

                - 导致 数据库是新数据,缓存时旧的数据

            -  解决思路:

                - 先删除缓存,再更新数据库

                    - 如果数据库修改失败,缓存时空的,数据库是旧的。

    - 最终 先删除缓存,再更新数据库

        - 问题:

            - 大并发,还没修改,但是缓存时空的,查到缓存时空的。

            - 解决思路

                - 还是要串行,考虑如何串行

                    - 队列

                        - 更新缓存 不管是读,还是写, 生成唯一标识,放入java队列中。

                            - 队列还可以做过滤,相同的更新缓存没有意义。

                    - 锁实现呢?

                    -

### 4 集群与高可用

#### 4.1 集群方案

- codis

- redis cluster 3.0 自带集群

    - 16384 个槽

        - 每个槽都有都有节点处理,集群式在线的。

- 在业务代码层实现,几个无关联redis,hash 计算。

#### 4.2 cluster 方案

- 基本命令

    - cluster meet ip 端口 节点握手

    - cluster addslots {0..16383} 分配槽点

    - cluster replicate nodeid  让某个节点成为从节点。

- 分配槽点算法

    - crc16(key) & 16383

        - 16384 = 2^14

        - key % 2^n = key & (2^n - 1) 当作定理

        - crc16(key) 值有16bit 值分布在0 - 65535.

    - 为什么是 16384

        -背景

            -集群交换消息的发送 结构体![image.png](https://upload-images.jianshu.io/upload_images/9547570-8fd15cb947218eb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

            - 最占空间的是 un char myslots[cluster_slots/8]的bitmap 16384/8/1028 = 2kb

        - 原因

            - 如果65536 myslots 将占用 8kb,导致心跳包庞大。 65536/8/1024 = 8kb

            - redis建议节点数量不能超过1000k,会导致网络拥堵

            - bitmap 传输过程会压缩,槽越多压缩率越低

#### 4.2 高可用

- Sentinel哨兵模式

    - 能够自动完成故障发现和故障转移并通知应用方,从而实现真正的高可用的分布式架构。

        - 监控

        - 通知

        - 自动故障转移等功能。

##### 基本原理

- 心跳机制

    - sentinel 会创建两个连向主服务器的异步网络连接

        - 一个是命令连接, 专门向主服务器发送命令,并接受命令回复

        - 一个是订阅连接,专门订阅主服务器_sentinel_:hello频道。

    - 三个定时任务来完成对各个节点的发现和监控

        - 每隔 10 秒,每个 Sentinel 节点会向已知的主从节点发送 info 命令获取最新的主从架构

            - Sentinel 节点通过解析响应信息,就可以知道当前 Redis 数据节点的最新拓扑结构。如果是新增的节点,Sentinel 就会与其建立连接

        - 每隔 2 秒,Sentinel 节点都会向主从节点的 _sentinel_:hello 频道发送自己的信息

            - 发现新的 Sentinel 节点

            - Sentinel 节点之间交换主节点的状态,作为后面客观下线以及领导者选择的依据。

        - 每隔一秒,哨兵会向每个主从节点、Sentinel 节点发送 PING 命令,并通过回复判断实例是否在线。

- 主观下线,客观下线

    - 主观下线

        - 三个定时任务中,每隔一秒的定时任务 超过 down-after-milliseconds 设定的时间没有收到响应,则会对该节点做出失败判断,这种行为叫做主观下线。

    - 客观下线

        - 当 Sentinel 节点判定一个主节点主观下线后,会通过 sentinel is-master-down-by-addr 命令询问其他 Sentinel 节点对该主节点的状态,当有超过 个 Sentinel 节点认为主节点存在问题,这时该 Sentinel节点会对主节点做客观下线的决定。

- Sentinel 选举

    - 通过Raft投票算法,选举出来一个领导者来完成故障转移工作

- 故障转移

    - 某个sentinel 节点通过选举成为了领导者,他就要承担转移的工作

    - 从从服务器里面,挑选一个强其转换为主服务器

    - 其他所有从服务器改为复制新的主服务器

    - 将下线的主服务器设置为从服务器。

### 5 事务

#### 5.1 命令

``` bash

multi #开始事务

lpush xxp 1 2 3

lindex xxp 0

lrange xxp 0 2

exec/discard # 提交事务/取消整个事务

```

- MULTI 标志着事务的开始,可以将执行该命令的客户端从非事务态切换的事务态。

    - 总是会返回 OK 字符串

- EXEC 按照顺序执行先前放入事务对立中的命令,然后将客户端恢复到非事务状态。

    - 该命令返回的是一个数组,数组中的每个元素都是每个命令的返回值。该命令不会中断命令的执行,除非遇到如下几个错误

        - 服务宕机

        - 入队列的命令是错误的,比如使用不存在的命令或者命令格式错误

        - 使用了 WATCH,有其他客户端修改了当前事务的 key

- DISCARD 清除所有先前在一个事务中放入的队列的命令,然后恢复到非事务状态

    -  返回ok

    -  果当前事务使用了 WATCHE,那么 DISCARD 也会将当前连接监控的所有键取消监控。

- WATCH 当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的

#### 5.2 事务的ACID原则

- 原子性 支持

    - 整个事务中的所有操作,要么全部完成,要么全部不完成

    -  但是不支持回滚

- 一致性 支持

    - 个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。

- 隔离性 支持

    - 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作

    - Redis 使用的单线程方式来执行事务的且可以保证在整个事务执行期间不会中断去执行其他命令

- 持久性 不一定支持

    - 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚

    - 如果 Redis 采用内存模式,因为数据不会持久化,所以持久化无法保证

    - 如果 Redis 采用 RDB 模式,服务器只会在特定的条件才会触发,如果 Redis 执行事务后,没有触发 bgsave 操作,则无法保证持久化

    - 如果 Redis 采用 AOF 模式,则分为以下三种情况

        - 如果选择的是 always,则会同步调用不同操作,将命令数据写入到硬盘中,所以具有持久性

        - 如果现在的是 everysec 或者 no,则依然需要在特定条件下触发,否则无法命令数据可以刷入硬盘,所以不具有持久性

### 6 持久化

#### 6.1 RDB

- 数据生成快照保持到硬盘。

- 手动触发

    - save 阻塞当前redis服务器

    - bgsave 执行fork操作创建子进程。父进程继续处理命令请求。

- 优缺点

    - 优点

        - 是一个紧凑二进制数据,用户备份和全量复制

        - 价值rdb恢复,远远快于AOF方式

    - 缺点

        - 无法做到实时持久化/秒级持久化

#### 6.2 AOF

- 独立日志记录每次写命令,重启时再重新执行AOF文件中的命令恢复数据的目的。

- 作用

    - 解决了数据持久化的实时性。

- appendfsync 值

    - no 让操作系统来决定何时调用

    - always 每个写命令同步到aof文件

    - everysec 每秒执行一次同步。

- 重写机制

    - 不需要对之前aof读取,把数据转化为写命令同步到新的AOF文件。

    - 为什么aof还可以变小

        - 超时数据不再写入aof

        - 旧的aof文件包含无效命令

        - 多条命令可以合并为一个。

### 7 复制

#### 7.1 命令

- slaveof ip 端口

    - ip 端口 是主服务器,当前服务器变成从服务器

#### 7.2 同步

- 1 客服端向从服务器发送slaveof 命令,要求复制主服务

- 2 从服务器判断是否第一次执行复制

    - 是向主服务器发送psync ? -1

        - 主服务器返回 +fullresync

            - 执行完整重同步

    - 否向从服务器发送 psync  

        - 主服务器 返回 +continue

            - 执行部分重同步

        - 主服务器返回 +fullresync

            - 执行完整重同步

        - 主服务器返回 -Err回复 表示主服务器版本低于redis 2.8,识别不了,从服务会发送sync命令。

#### 7.3 完整重同步

- 1 主服务执行bgsave命令,在后台生产一个rdb快照文件

    - bgsave命令执行玩,将rdb文件发送给从服务器。

- 2 主服务器使用一个缓冲区记录从现在开始执行的所有写命令

    - 将记录在缓冲区里面的所有命令发送给从服务器

#### 7.4 部分重同步

- 用于处理断线后重新回到一致状态

- 部分重同步三部分组成

    - 主服务器从服务器的复制偏移量

        - 对比复制偏移量 ,判断主从服务器是否处于一致状态。

    - 主服务器的复制积压缓冲区(可以进行调整)

        - 保持着一部分最近传播的写命令,并未队列中每个字节记录相应的复制偏移量

        - 判断offset 之后偏移量的数据在复制积压缓冲区中

            - 是 可以重同步, 向从服务器发送 +continue 回复

            - 否 完整重同步

    - 服务器的运行id

        - 判断主服务器的运行id是否变化

            - 不同 执行完整重同步。

-  [参考](https://www.cnblogs.com/rjzheng/p/11430592.html)

你可能感兴趣的:(redis-持久化)