Redis 一文扫盲

目录

Redis中的数据结构

Redis7 中支持的数据结构类型

Redis 内部结构类型

RedisObject对象

SDS对象结构

embstr 编码方式

raw编码方式

压缩列表 ziplist

跳跃链表skiplist

快速列表quicklist

String数据结构

基本介绍

内部实现

常用命令

Hash数据结构

基本介绍

内部实现

常用命令

List数据结构

基本介绍

内部实现

常用命令

set数据结构

基本介绍

内部实现

常用命令

SoredSet 数据结构

基本介绍

内部实现

常用命令

Redis 中的持久化

Redis 的核心架构

IO多路复用线程模型

核心工作单线程模型

Redis持久化介绍

持久化的意义

持久化机制

持久化方式对比

Redis 持久化 - RDB

RDB简介

RDB的触发机制

Redis持久化- AOF

AOF简介

AOF 的重写机制

AOF的触发机制

AOF重写机制的副作用

REDIS持久化 - AOF和RDB混合模式

四种持久化方式的对比

额外注意

Redis 管道

管道和原生批命令的比较

管道和脚本的区别

使用管道需要注意的事项

Redis 发布订阅

Redis 流技术

Redis 中的过期策略

Redis 内存淘汰策略

LRU

LFU

LRU和LFU对比

Redis内存淘汰配置

1. volatile-lru

2. allkeys-lru:

3. volatile-lfu:

4. allkeys-lfu:

5. volatile-random:

6. allkeys-random:

7. volatile-ttl:

8. noeviction:

Redis 事务

事务的定义

传统数据库的事务特性

Redis 事务的三大特性

1. 单独的隔离操作:

2. 没有隔离级别的概念:

3. 不保证原子性:

Redis 事务的三个阶段

开始事务

命令入队

执行事务

Redis 事务相关命令

Redis 事务原子性

Redis 锁

锁 的概念与分类

Redis中的乐观锁

Redis分布式锁

Redis 客户端缓存

Abstract

之前存在问题

How

三种缓存模式:

Demo

默认模式

广播模式

转发模式(不要使用)

Redis 主从模式

Redis 的集群历史

Redis 主从模式架构

Redis主从复制原理

redis < 2.8

redis >= 2.8

notice

SYNC 工作原理

PSYNC 工作原理

Redis 主从模式环境搭建

1. 静态配置

2. 动态配置

3. 启动参数(不推荐)

Redis Sentinel集群方案(哨兵)

架构梳理

工作原理

集群的常见管理手段

启停管理

info replication 查看主从关系

sentinel master 查看主信息

sentinel replicas 查看从信息

在线扩容

如何彻底下线从节点

常见命令

SENTINEL master mymaster

SENTINEL masters

SENTINEL replicas mymaster

SENTINEL sentinels

SENTINEL get-master-addr-by-name mymaster

SENTINEL config get *

SENTINEL config set [name]

SENTINEL set mymaster down-after-milliseconds 30000

SENTINEL CKQUORUM mymaster

SENTINEL MONITOR mymaster 192.168.128.167 6379 2

SENTINEL REMOVE

SENTINEL FAILOVER mymaster

RedisCluster 集群方案

简介

核心原理

配置

节点配置

集群配置

健康检查

常见管理手段

扩容-增加 Redis 节点


Redis中的数据结构

Redis7 中支持的数据结构类型

数据类型

类型名称

特征说明

场景使用

String

字符串

String 是 redis 最基本的类型,一个 key 对应一个 value

String 是二进制安全的,redis 的 String 可以包含任何数据。如 jpg 图片或者序列化对象

String 类型是 Redis 最基本的数据类型,String 类型的值最大能存储 512MB

非常多,万物皆可 String

Hash

哈希

Redis Hash 是一个键值(key=>value)对集合。

Redis Hash 特别适合用于存储对象

存储带有属性的对象

List

列表

Redis 列表是简单的字符串列表,按照插入顺序排序

我们可以添加一个元素到列表的头部(左边)或者尾部(右边)

1、最新消息排行等功能

2、消息队列

Set

集合

Redis 的 Set 是 string 类型的无序集合

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)

为集合提供了求交集、并集、差集等操作

1、共同好友

2、分布式统计&计数

sored Set

有序集合

Redis 的 Set 是 string 类型的有序集合

1、排行榜

2、带权重的消息

HyperLogLogs

基数

用来做基数统计的,即计算在一批数据中,不重复元素有多少个

1. 登陆用户总

2. 销售产品数

Bitmaps

位图

Bitmap 即位图数据结构,都是操作二进制位来进行记录

1、存储状态(只有 0、1)

Geospatial

地理

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作

1、两地之间的距离

2、方圆几里的人

Redis 内部结构类型

RedisObject对象

Redis 内部所有存储的数据都使用 redisObject 来封装。

struct redisObject { unsigned type:4; // 对象类型如 zset/set/hash 等等 unsigned encoding:4; // 对象编码如 ziplist/intset/skiplist 等等 unsigned lru:24; // 对象的「热度」 int refcount; // 引用计数 void *ptr; // 数据指针 
} ;

SDS对象结构

Redis 的字符串叫着 SDS,也就是 Simple Dynamic String。它的结构是一个带长度信息的字节数组

struct SDS { T capacity; // 数组容量 T len; // 数组长度 byte flags; // 特殊标识位,不理睬它 byte[] content; // 数组内容
}

embstr 编码方式

embstr 编码方式,当 String 类型(非 int)长度小于或等于 44 时,默认采用 embstr 编码方式,如下图所示字符串的 SDS 对象就紧挨着 ptr 指针存放

raw编码方式

当 String 类型长度大于 44 时,会使用 raw 编码方式,如下图所示 SDS 与 redisObject 内存不连续

压缩列表 ziplist

Redis 为了节约内存的使用,当元素个数与元素大小比较小时,对于 hash、zset、list,会使用 ziplist 编码方式节约内存。ziplist 通过将每个值紧 挨着存储同时避免使用 redisObject 与 SDS,节约大量内存空间(PS: 这里说的避免使用SDS和redisObj 指的是ziplist中存储的元素,而不是指ziplist本身)

struct ziplist { int32 zlbytes; // 整个压缩列表占用字节数 int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点 int16 zllength; // 元素个数 T[] entries; // 元素内容列表,挨个挨个紧凑存储 int8 zlend; // 标志压缩列表的结束,值恒为 0xFF 
}

从 Redis7 开始,ziplist 统一由 listpack 进行替换,listpack 与 ziplist 极为类似,可以理解为 ziplist 的升级版

  • 头比ziplist少了4字节
  • 每个元素中只有自己的总长度来实现逆序遍历,更新时不会出现级联更新

跳跃链表skiplist

skiplist 采用了多级索引,末端采用原始单链表进行存储

快速列表quicklist

quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来

String数据结构

基本介绍

String 类型是 Redis 最基本的数据类型,一个 key 对应一个 value

String 是二进制安全的,redis 的 String 可以包含任何数据。如 jpg 图片或者序列化对象

String 类型的值最大能存储 512MB

内部实现

Redis 提供了命令 OBJECT ENCODING key 来查询内部编码方式。

Redis 在使用的时候,Value 值不宜过大,当超过 10K 的时候,就要特别注意性能的问题。(会导致big key

常用命令

  • SET key value,指定 key 的值
  • GET key,获取指定 key 的值
  • SETRANGE key offset value,用 value 参数覆写给定 key 所储存的字符串值, 从偏移量 offset 开始
  • GETRANGE key start end,返回 key 中字符串值的子字符
  • GETSET key value,将给定 key 的值设为 value,并返回 key 的旧值
  • GETDEL key,获取某个 key 的值,将删除
  • MSET key value [key value ...],同时设置一个或多个 key-value 对
  • MGET key1 [key2..],获取所有(一个或多个)给定 key 的值
  • GETBIT key offset,对 key 所储存的字符串值,获取指定偏移量上的位(bit)
  • SETBIT key offset value,设置或清除指定偏移量上的位(bit)
  • SETEX key seconds value,将值 value 关联到 key,并设定过期时间 seconds
  • GETEX key ……获取 key 的值,并设置过期时间
  • PSETEX key milliseconds value,这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位
  • SETNX key value,只有在 key 不存在时设置 key 的值
  • STRLEN key,返回 key 所储存的字符串值的长度。
  • MSETNX key value [key value ...],同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
  • APPEND key value,将指定的 value 追加到该 key 值的末尾。

Hash数据结构

基本介绍

Redis Hash 是一个键值(key=>value)对集合。

Redis Hash 特别适合用于存储对象

内部实现

1. hashTable 大家很熟悉,和 java 里的 hashMap 很像,用 hashTable 编码的话,则会花费至少大于存储的数据 25%的空间才能存下这些数据

2. zipList,又叫压缩链表,zipList 最大的特点就是,它不是 hash 结构,而是一个比较长的字符串,将 key-value 都按顺序依次摆放到一个长长的字

符串里来存储,从内存占用上讲,zipList 可比 hashTable 降低了极多

3. listPack,与 zipList 极为相似,可以理解为 zipList 的优化版

4. 那么何时用 hashTable、ziplist/listPack 呢?

在 redis.conf 文件中可以找到(Redis 7 版本):

hash-max-listpack-entries 512

hash-max-listpack-value 64

如果是 6 或以前的版本:

hash-max-ziplist-entries 512

hash-max-ziplist-value 64

常用命令

HSET key field value,将哈希表 key 中的字段 field 的值设为 value

HGET key field,获取存储在哈希表中指定字段的值

HDEL key field1 [field2],删除一个或多个哈希表字段

HEXISTS key field,查看哈希表 key 中,指定的字段是否存在

HMGET key field1 [field2],获取所有给定字段的值

HMSET key field1 value1 [field2 value2 ],同时将多个 field-value (域-值)对设置到哈希表 key 中

HGETALL key,获取在哈希表中指定 key 的所有字段和值

HINCRBY key field increment,为哈希表 key 中的指定字段的整数值加上增量 increment

HVALS key,获取哈希表中所有值

HKEYS key,获取所有哈希表中的字段

HLEN key,获取哈希表中字段的数量

HSCAN key cursor [MATCH pattern] [COUNT count],迭代哈希表中的键值对

List数据结构

基本介绍

Redis 列表是简单的字符串列表,按照插入顺序排序

我们可以添加一个元素到列表的头部(左边)或者尾部(右边)

内部实现

1. redis 的 list 数据类型最早使用的是一个双向链表 linkedlist,但双向链表的 prev 与 next 指针会占用大量的内存空间。为了节约内存开销,redis 从 3.2 版本开始,使用一个快速列表 quicklist 保存数据

2. quicklist 是 ziplist/listpack 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist/listpack 来紧凑存储,多个 ziplist/listpack 之间使用 双向指针串接起来

常用命令

LPUSH key value1 [value2],将一个或多个值插入到列表头部

RPUSH key value1 [value2],在列表中添加一个或多个值

LPOP key,移出并获取列表的第一个元素

RPOP key,移除列表的最后一个元素,返回值为移除的元素

LPUSHX key value,将一个值插入到已存在的列表头部

RPUSHX key value,为已存在的列表添加值

LSET key index value,通过索引设置列表元素的值

LINDEX key index,通过索引获取列表中的元素

LINSERT key BEFORE|AFTER pivot value,在列表的元素前或者后插入元素

LREM key count value,移除列表元素

LRANGE key start stop,获取列表指定范围内的元素

LLEN key,获取列表长度

BLPOP key1 [key2 ] timeout,移出并获取列表的第一个元素,如果 列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

BRPOP key1 [key2 ] timeout,移出并获取列表的最后一个元素, 如 果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

LTRIM key start stop,对一个列表进行修剪(trim),就是说,让列 表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
BRPOPLPUSH source destination timeout,从列表中弹出一个值,将 弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻 塞列表直到等待超时或发现可弹出元素为止。

RPOPLPUSH source destination,移除列表的最后一个元素,并将该 元素添加到另一个列表并返回

set数据结构

基本介绍

Redis 的 Set 是 string 类型的无序集合

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)

为集合提供了求交集、并集、差集等操作

内部实现

  1. redis 为了节省内存开销,对于集合类型,如果集合的数据都是整数,且个数比较少的情况下,会优先使用 inset 实现
  2. inset 实现时,内部数量不宜过大,默认为 512 以下

常用命令

SADD key member1 [member2],向集合添加一个或多个成员

SREM key member1 [member2],移除集合中一个或多个成员

SMEMBERS key,返回集合中的所有成员

SCARD key,获取集合的成员数

SISMEMBER key member,判断 member 元素是否是集合 key 的成员

SINTER key1 [key2],返回给定所有集合的交集

SINTERSTORE destination key1 [key2],返回给定所有集合的交集并存储 在 destination 中

SUNION key1 [key2],返回所有给定集合的并集

SDIFF key1 [key2],返回第一个集合与其他集合之间的差异。

SPOP key,移除并返回集合中的一个随机元素

SRANDMEMBER key [count],返回集合中一个或多个随机数

SoredSet 数据结构

基本介绍

Redis 的 Set 是 string 类型的有序集合

内部实现

1. redis 为了节省内存开销,对于集合类型,如果集合的数据都是整数,且个数比较少的情况下,会优先使用 ziplist/listpack 实现

2. inset 实现时,内部数量不宜过大,默认为 128 以下

常用命令

ZADD key score1 member1 [score2 member2],向有序集合添加一个或多个成员,或者更新已存在成员的分数

ZREM key member [member ...],移除有序集合中的一个或多个成员

ZCARD key,获取有序集合的成员数

ZINCRBY key increment member,对指定成员的分数加上增量 increment

ZCOUNT key min max,计算指定分数区间的成员数

ZLEXCOUNT key min max,计算指定字典区间内成员数量

ZRANGE key start stop [WITHSCORES],通过索引区间返回区间内的成员

ZRANGEBYLEX key min max [LIMIT offset count],通过字典区间返回有序集合的成员

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT],通过分数返回有序集合指定区间内的成员

ZREMRANGEBYRANK key start stop,移除有序集合中给定排名区间所有成员

ZREMRANGEBYLEX key min max,移除有序集合中给定的字典区间的所有成员

ZREMRANGEBYSCORE key min max,移除有序集合中给定分数区间的所有成员

ZREVRANGE key start stop [WITHSCORES],返回有序集中指定区间内的成员,通过索引,分数从高到低

ZREVRANGEBYLEX key max min,返回有序集中指定区间内的成员,通过字典区间,分数从高到低

ZREVRANGEBYSCORE key max min [WITHSCORES],返回有序集中指定分数区间内的成员,分数从高到低排序

ZRANK key member,返回有序集合中指定成员的索引

Redis 中的持久化

Redis 的核心架构

IO多路复用线程模型

IO 多路复用也是一种同步阻塞 IO 模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出 cpu。

核心工作单线程模型

1. 单线程源由

因为 CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存或者网络带宽,既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地

采用单线程的方案了。关于 Redis 的性能,官方网站也有,普通笔记本轻松处理每秒十万的请求

2. 所有版本

Redis 是单线程模型的,而单线程避免了 CPU 不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如 hgetall 命

令),会造成阻塞。Redis 是面向快速执行场景的数据库。所以要慎用如 smembers 和 lrange、hgetall 等命令

Redis 单线程指的是核心工作使用了一个线程,即一个线程处理所有命令请求

Redis 其他模块仍然有一些用了多个进程/线程,比如通过 fork 子进程,RDB 输出等

3. 6.0 后版本

Redis 6.0 引入了多线程提速,但多线程主要用于在 IO 处理,核心 work 线程(执行命令操作内存)仍然是个单线程

Redis持久化介绍

持久化的意义

Redis 的数据全部在内存中,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据在发生突发状况时不会丢失、或者只丢失少量;因此便产生了根据一些策略来把 Redis 内存中的数据写到磁盘中,这样当 Redis 服务重启时,就会将硬盘中的数据恢复到内存中。

Redis 持久化的意义就是为了保证突然宕机,内存数据不会全部丢失 。

持久化机制

Redis 有两种持久化机制:RDB 和 AOF,每种持久化机制各有优缺点;Redis 4.0 后支持 RDB 和 AOF 两种持久化机制混合使用,所以实际上存在三种持久化策略

RDB 方式:把内存数据以快照的形式保存到磁盘上,可以周期性执行,重启的时候再执行 RDB 文件来进行数据恢复

AOF 方式:采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行 AOF 文件中的命令来恢复数据

混合持久化:采用两者相结合的方式

持久化方式对比

RDB 持久方式

适合大规模的数据恢复场景, 如备份,全量复制等

没办法做到实时持久化/秒级持久化。

新老版本存在 RDB 格式兼容问题

AOF 持久方式

数据的一致性和完整性更高

AOF 记录的内容越多,文件越大,数据恢复变慢

混合持久化

结合了 RDB 和 AOF 的优点,使得数据恢复的效率大幅提升

跨版本兼容性不是很好,Redis-4.x 新增,虽然最终的文

件也是.aof 格式的文件,但在 4.0 之前版本都不识别

Redis 持久化 - RDB

RDB简介

RDB 是基于快照的一次全量备份,即周期性的把 Redis 当前内存中的全量数据写入到一个快照文件中

Redis 是使用操作系统的多进程 COW(Copy On Write)机制来实现快照的持久化,在持久化过程中调用函数 fork()产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端的读写请求

子进程对当前内存中的数据进行持久化时,并不会修改当前的数据结构,如果父进程收到了读写请求,那么会把处理的一部分数据复制一份到内存,对复制后的数据进行修改,因此 RDB 持久化过程中,会产生一个内存扩张的问题,极限情况为 Redis 的 2 倍(我们在线上需要适当考虑)。

RDB的触发机制

自动触发:

在 Redis.conf 配置文件中的 SNAPSHOTTING 下配置 save 参数,来触发 Redis 的 RDB 持久化条件,

比如“save m n”:表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave

save 900 1:每隔 900s(15min),如果有超过 1 个 key 发生了变化,就写一份新的 RDB 文件

save 300 10:每隔 300s(5min),如果有超过 10 个 key 发生了变化,就写一份新的 RDB 文件

save 60 10000:每隔 60s(1min),如果有超过 10000 个 key 发生了变化,就写一份新的 RDB 文件

手动触发:

手动生成新的 RDB 文件,执行 Redis 的 save、bgsave 命令。

save:阻塞主进程,直到生成新的 RDB 文件;执行 save 命令期间,Redis 不能处理其他命令。

bgsave:异步生成 RDB 文件,fork 子进程去生成新的 RDB 文件,主进程不阻塞。

另外,配置多种策略可以同时生效,无论满足哪一种条件都会写一份新的 RDB 文件;Redis 进程在 kill 时也会尝试生成 RDB 文件

Redis持久化- AOF

AOF简介

AOF(Append-only file)日志存储的是 Redis 服务器的顺序指令序列,即对内存中数据进行修改的指令记录。

当 Redis 收到修改指令后,先进行参数校验,如果校验通过,先把该指令存储到 AOF 日志文件中,也就是先存到磁盘,然后再执行该修改指令。

Redis 把操作指令追加到 AOF 文件这个过程,可以采用 always,everysec,no 三种模式

always:每次事件都会刷盘,最安全、效率最低

everysec:每秒同秒一次,安全性略差、效率提高

no:交由操作系统,安全性最差、性能最高

AOF 的重写机制

Redis 在长期运行过程中,AOF 日志会越来越大,如果 Redis 服务重启后根据很大的 AOF 文件来顺序执行指令,将会非常耗时,导致 Redis 服务长时间无法对外提供服务,所以需要对 AOF 文件进行"瘦身"。"瘦身"的过程称作 AOF 重写(rewrite)。

AOF Rewrite 的过程是,主进程 fork 一个子进程,将当前内存中的数据进转换成一系列的 Redis 操作指令,并序列化到一个新的 AOF 日志中,

然后把序列化操作期间新收到的操作指令追加到新的 AOF 文件中,这样就完成了"瘦身"工作,即 AOF Rewrite,注意,AOF 在 Rewrite 过程中同样用 到了多进程 COW(Copy On Write)机制。AOF Rewrite 过程如下:

AOF的触发机制

AOF 触发机制有两种,手动触发和自动触发

1. 自动触发

通过配置参数触发

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

2. 手动触发

执行命令 bgrewriteaof,通过子进程生成更小体积的 aof,然后替换掉旧的、大体量的 aof 文件

AOF重写机制的副作用

AOF 在重写的时候,采用操作系统的多进程的 COW(Copy On Write)机制,因此在这期间,因此会出现内存扩张现象,极限情况是 Redis 当前内存的 2 倍

AOF Rewrite 带来的内存开销有可能导致 Redis 内存突然达到 maxmemory 限制,从而影响正常命令的写入,甚至会触发操作系统限制被 OOM Killer 杀死,导致 Redis 不可服务

AOF Rewrite 也会带来 CPU 和额外开销,有可能造成 Redis 在执行命令时出现 RT 上的抖动,甚至造成客户端超时的问题

RDB 在快照期间,也会造成内存和 CPU 的额外开销,但通常时间要远小于由行 AOF 重写时间,因此 AOF 重写的影响要远大于 RDB 快照

REDIS持久化 - AOF和RDB混合模式

简介

Redis-4.x 后支持了 RDB 和 AOF 混合使用。重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重

放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。Redis-4.0 为了解决这个问题,

带来了一个新的持久化选项——混合持久化。将 RDB 文件的内容和增量的 AOF 日志文件存在一起,这里的 AOF 日志不再是全量 的日志,而是 RDB 久 化开始 到 RDB 持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。Redis-4.x 混合持久化机制如下图:

四种持久化方式的对比

RDB 持久方式

正常周期、手动生成、kill

从 RDB 文件恢复

AOF 持久方式

正常周期、手动生成

从 AOF 文件恢复

两种方式组合

RDB 和 AOF 按各自规则生成

从 AOF 文件恢复

混合持久化(AOF 包括了 RDB 头部+AOF 混写)

RDB 和 AOF 按各自规则生成

从 AOF 文件恢复

额外注意

当存在主从复制时,Redis 默认会开启 rdb 复制,除非强制使用无盘复制

禁用 rdb 持久化模式下,我们仍然可以使用命令 save、bgsave 生成 rdb 文件

禁用 aof 持久化模式下,我们仍然可以使用命令 bgrewriteaof 生成 aof 文件

Redis 管道

Abstract

Redis 客户端执行一条命令分 4 个过程:

发送命令-〉命令排队-〉命令执行-〉返回结果

这个过程称为 Round trip time(简称 RTT, 往返时间),Redis 的原生批命令(mget 和 mset)有效节约了 RTT,但大部分命令不支持批量操作,

需要消耗 N 次 RTT ,这个时候需要 pipeline 来解决这个问题

Pipeline 是为了解决 RTT 往返回时,仅仅是将命令打包一次性发送,对整个 Redis 的执行不选成其它任何影响

管道和原生批命令的比较

实现方式:

管道命令需要服务端与客户端共同完成,而原生的批命令只由服务端去实现

原子性:

管道命令不具有原子性,出现问题不回滚。

管道和脚本的区别

1. Redis 的 Script 会当成一个命令,具有原子性,在执行 Script 的时候不会被其他的命令插入,因此更适合于处理事务;

2. 管道虽然也会将多个命令一次性传输到服务端,但在服务端执行的时候仍然是多个命令,如在执行 CMD1 的时候,外部另一个客户端提交了 CMD9,会先执行完 CMD9 再执行管道中的 CMD2,因此事实上管道是不具有原子性的

使用管道需要注意的事项

1. 使用 pipeline 组装的命令个数不能太多,否则数据量过大,增加客户端的等待时间,还可能造成网络阻塞,可以适当地将大量命令的拆分成多个小的 pipeline 命令完成(看具体情况)

2. pipeline 特别命令数据初始化加载程序,可大大提供效率

Redis 发布订阅

What

发布/订阅模式模式包含两种角色:发布者和订阅者。订阅者可以订阅一个或多个通道(channel),而发布者可以向指定的通道(channel)中发送 消息,所有此通道的订阅者都会收到消息。拿一个现实生活中的例子来比喻的话,发布者就像一个电台广播员,订阅者就像听众,广播员将消息通 过将消息发送到一个频道中,而这个频道的听众就会听到播音员的声音

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端

How

PUBLISH channel message - 将信息发送到指定的频道

SUBSCRIBE channel [channel ...] - 订阅给定的一个或多个频道的信息

PSUBSCRIBE pattern [pattern ...] - 批量订阅多个频道

PUNSUBSCRIBE [pattern [pattern ...]] - 批量退订多个频道

PUBSUB subcommand [argument [argument ...]]查看订阅与发布系统状态。

UNSUBSCRIBE [channel [channel ...]] - 指退订给定的频道。

Redis 流技术

What

Redis Stream 是 Redis 5.0 版本新增加的数据结构。

Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有 个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。

简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户

端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

Redis 流ID规则

流(stream)中信息条目(一个 Stream 条目,由一个或多个键值对组成的)的 ID 必须是单调增的,为此,redis 采用时间戳+自增 id 这种方式 来保证,并且这两个数都是 64bit,不会有溢出问题,最后一点,redis 在增加信息条目时会检查当前 id 与上一条目的 id,自动纠正错误的情况, 一定要保证后面的 id 比前面大。

一个流中信息条目的 ID 必须是单调增的,这是流的基础

几个特殊的 ID:

- + :最小和最大可能出现的 Id

$ :当前流中最大的 id,可用于将要到来的信息

> :用于 XREADGROUP 命令中,表示迄今还没有发送给组中使用者的信息,会更新消费者组的最后 ID

* :用于 XADD 命令中,让系统自动生成 id

Redis 消息结构

Consumer Group:消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。

last_delivered_id:游标,每个消费组会有个 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。

pending_ids:消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。pending_ids 记录了当前已经被客户端读取的消息,但是还没 有 ack (Acknowledge character:确认字符)。

Demo

xadd mystream * id 1 name longge #生产消息

xdel mystream 1654521221581-0 #删除某条消息

xrange mystream - + #查看消息列表

XREAD count 1 streams mystream 0 #读取一条消息

xgroup create mystream group1 0 #创建消费者组

xreadgroup group group1 c1 count 1 streams mystream > #消费者读消息

xpending stream1 group1 - + 10 c1 #查看待确认的列表

XACK mystream group1 1654522733049-0 #确认消息

How

XADD - 添加消息到末尾

XTRIM - 对流进行修剪,限制长度

XDEL - 删除消息

XLEN - 获取流包含的元素数量,即消息长度

XRANGE - 获取消息列表,会自动过滤已经删除的消息

XREVRANGE - 反向获取消息列表,ID 从大到小

XREAD - 以阻塞或非阻塞方式获取消息列表

XGROUP CREATE - 创建消费者组

XACK - 将消息标记为"已处理"

XGROUP SETID - 为消费者组设置新的最后递送消息 ID

XGROUP DELCONSUMER - 删除消费者

XGROUP DESTROY - 删除消费者组

XPENDING - 显示待处理消息的相关信息

XCLAIM - 转移消息的归属权

XINFO - 查看流和消费者组的相关信息

XREADGROUP GROUP - 读取消费者组中的消息

Redis 中的过期策略

What

过期策略种类

1. 定时过期

每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即对 key 进行清除。该策略可以立即清除过期的数据,对内存很友好;但 是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

2. 惰性过期

只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能 出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存

3. 定期过期

每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通 过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。

redis使用的过期策略

Redis 中同时使用了惰性过期和定期过期两种过期策略

假设 Redis 当前存放 30 万个 key,并且都设置了过期时间,如果你每隔 100ms 就去检查这全部的 key,CPU 负载会特别高,最后可能会挂掉。

因此,Redis 采取的是定期过期,每隔 100ms 就随机抽取一定数量的 key 来检查和删除的。

但是呢,最后可能会有很多已经过期的 key 没被删除。这时候,Redis 采用惰性删除。在你获取某个 key 的时候,Redis 会检查一下,这个 key 如果设置了过期时间并且已经过期了,此时就会删除。

Redis 内存淘汰策略

LRU

What

RU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

LRU算法需要实现如下特性

  1. 实现get/put方法(都为O(1)的时间复杂度)
  2. 每次get时需要将访问的节点提前至队首
  3. 每次put需要判断队列是否已满,满了则将最后的节点删除,并且将该节点放至队首,不满则直接放队首

基于上述特性需要实现如下数据结构

  1. 首先需要实现队列,如果使用单向链表,当我们需要使用删除操作时,需要获得前置节点的指针,单向链表则不能做到直接获取。因此使用双向链表。
  2. 又我们需要get方法达到O(1)的时间复杂度,因此需要一个Hashmap,可以根据key定位到我们双向链表的Node节点。
  3. 由于我们HashMap中有key,所以我们可不可以Node中只存value,其实是不可以的,后续会提到这个原因。

#include
#include
#include
using namespace std;struct ListNode {ListNode* pre;ListNode* next;int val;int key;ListNode(int _key, int _val):key(_key),val(_val),pre(nullptr),next(nullptr) {};
};class LRU {
private:const static int default_capacity = 50;ListNode* head, * tail;unordered_mapmp;int size;int capacity;
public:LRU():size(0), capacity(default_capacity) {head = new ListNode(0, 0);tail = new ListNode(0, 0);head->next = tail;tail->pre = head;}LRU(int _capacity) :size(0), capacity(_capacity) {head = new ListNode(0, 0);tail = new ListNode(0, 0);head->next = tail;tail->pre = head;}~LRU() {while (head) {ListNode* tmp = head;head = head->next;delete tmp;}}void put(int key, int value) { // 插入或者更新if (mp.find(key) != mp.end()) {ListNode* node = mp[key];node->val = value;move_to_head(node);}else {ListNode* newNode = new ListNode(key, value);mp[key] = newNode;add_to_head(newNode);++size; // 记得更新if (size > capacity) {ListNode* node = delete_tail();mp.erase(node->key); // 记得erasedelete node;--size; // 记得更新}}}int get(int key) { // 获取数据if (mp.find(key) != mp.end()) {ListNode* node = mp[key];move_to_head(node);return node->val;}return -1;}void print() { // 为了测试,打印整个链表(不打印头尾节点)ListNode* cur = head->next;while (cur != tail) {cout << cur->val;cur = cur->next;}cout << endl;}
private:void add_to_head(ListNode* node) { // 向链表头部添加nodehead->next->pre = node;node->next = head->next;node->pre = head;head->next = node;}void move_to_head(ListNode* node) { // 将node移到链表头部remove(node);add_to_head(node);}void remove(ListNode* node) { // 移除node(注意:是从链表中移除,并没有删除节点)node->pre->next = node->next;node->next->pre = node->pre;}ListNode* delete_tail() { // 移除尾部节点(最近最少使用节点),并返回该节点便于erase和delete操作ListNode* tmp = tail->pre;remove(tmp);return tmp;}
};int main() {LRU lru(2);lru.put(1, 1);lru.print();lru.put(4, 2);lru.print();cout << lru.get(4) << endl;lru.put(4, 3);lru.print();return 0;
}

LFU

what

LFU的全称为Least Frequently Used,意思就是最不频繁使用,所以,LFU算法会淘汰掉使用频率最低的数据。如果存在相同使用频率的数据,则再根据使用时间间隔,将最久未使用的数据淘汰。

我们需要快速找到同一频率的所有节点,然后按照需要淘汰掉最久没被使用过的数据。所以,首先我们要有一个hash表来存储每个频次对应的所有节点信息,同时为了保证操作效率,节点与节点之间同样要组成一个双向链表,得到如下结构:

hash表中的key表示访问次数,value就是一个双向链表,链表中所有节点都是被访问过相同次数的数据节点。可以看到,相比较于LRU算法中的节点信息,LFU算法中节点的要素中除了包含具体的key和value之外,还包含了一个freq要素,这个要素就是访问次数,同hash表中的key值一致。这样做的好处是当根据key查找得到一个节点时,我们可以同时得到该节点被访问的次数,从而得到当前访问次数的所有节点。

有了LFU算法的主体结构之后,我们发现还缺少一个重要功能,就是如何根据key值获取value值。所以,参考LRU算法的数据结构,我们还需要有一个hash表来存储key值与节点之间的对应关系。最终,我们就可以得到LFU算法完整的数据结构:

LRU和LFU对比

LRU算法淘汰数据的注重点是时间间隔,只淘汰最久未使用的数据;LFU算法淘汰数据的注重点是使用频率,只淘汰最不经常使用的数据。

LRU算法实现简单,只需要一个hash表+一个双向链表即可实现;LFU算法实现就复杂很多,需要两个hash表+多个双向链表才能实现。

具体使用时,选择哪种算法作为内存淘汰策略要看具体场景,如果对于热点数据的查询要求比较高,则最好采用LFU算法作为内存淘汰策略。如果没有那么高的热点数据要求,则可以选择实现更为简单的LRU算法。

Redis内存淘汰配置

1. volatile-lru

当内存不足以容纳新写入数据时,从设置了过期时间的 key 中使用 LRU(最近最少使用)算法进行淘汰;

2. allkeys-lru:

当内存不足以容纳新写入数据时,从所有 key 中使用 LRU(最近最少使用)算法进行淘汰。

3. volatile-lfu:

4.0 版本新增,当内存不足以容纳新写入数据时,在过期的 key 中,使用 LFU 算法进行删除 key。

4. allkeys-lfu:

4.0 版本新增,当内存不足以容纳新写入数据时,从所有 key 中使用 LFU 算法进行淘汰;

5. volatile-random:

当内存不足以容纳新写入数据时,从设置了过期时间的 key 中,随机淘汰数据;。

6. allkeys-random:

当内存不足以容纳新写入数据时,从所有 key 中随机淘汰数据。

7. volatile-ttl:

当内存不足以容纳新写入数据时,在设置了过期时间的 key 中,根据过期时间进行淘汰,越早过期的优先被淘汰;

8. noeviction:

默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

Redis 事务

事务的定义

Redis 事务是一个单独的隔离操作:事务中所有的命令都会序列化、按顺序执行。事务在执行过程中,不会被其他客户端的命令请求所打断

Redis 事务的主要作用就是串联多个命令防止别的命令插队

传统数据库的事务特性

传统事务有 4 大特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)

数据库隔离的四个级别分别是:1、读取未提交内容;2、读取提交内容;3、可重读;4、可串行化

Redis 事务的三大特性

1. 单独的隔离操作:

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

2. 没有隔离级别的概念:

队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

3. 不保证原子性:

事务中如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚(但如果是组队时失败会回滚)

Redis 事务的三个阶段

开始事务

multi 命令标志着 Redis 事务开始

命令入队

mult 与 exec 中间的命令类似打包的方式,统一入队

执行事务

exec 命令标志着事务正式执行

Redis 事务相关命令

MULTI 标记一个事务块的开始

EXEC 执行所有事务块内的命令

DISCARD 取消事务,放弃执行事务块内的所有命令
WATCH key [key ...] 监视一个(或多个) key 如果在事务执行之前 key 被其他命令所改动,那么事务将被打断

UNWATCH 取消 WATCH 命令对所有 key 的监视

Redis 事务原子性

在事务 multi 开始之后到 exec 结束之前,我们可以开启其它窗口查看并修改相关值,不难看出:

Redis 中的事务是一个原子操作,不会被其它命令插队执行

Demo

事务操作

语法错误 - 事务异常

取消事务

运行错误 - 事务异常

Redis 锁

锁 的概念与分类

1. 悲观锁

(Pessimistic Lock), 顾名思义,就是很悲观;每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数 据就会 block 直到它拿到锁。

传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁

2. 乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观;每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在 此期间别人有没有去更新这个数据,可以使用版本号等机制

Redis 采用的是乐观锁实现

Redis中的乐观锁

1. 在第一个窗口中执行 1,2,3 步

2. 在第2个窗口中执行第 4 步

第一个窗口执行结果为空,也就是相当于是失败

这是因为 watch 命令是一种乐观锁的实现,Redis 在修改的时候会检测数据是否被更改,如果更改了,则执行失败

Redis分布式锁

在很多业务场景中,比如秒杀下单、抢红包等等业务场景,都需要用到分布式锁

程序的分布式锁的实现方式有多种,比如有 Redis、Zookeeper、数据库

Redis 客户端缓存

Abstract

这是redis6.0 的新特性

Redis 有其性能上限,并且访问 Redis 必然有一定的网络 I/O 以及序列化反序列化损耗。所以,往往会引入本地缓存,将最热的数据存储在本地,进一步加快访问速度。

客户端缓存是一种用于创建高性能服务的技术。它利用应用服务器(与数据库节点相比,应用服务器通常是不同的计算机)中的可用内存,以便将数据库信息的某些子集直接存储在应用程序端。

《Client side caching in Redis 6》中提到redis 接下来的一个重点是配合客户端,因为客户端缓存显而易见的可以减轻 redis 的压力,速度也快很多。大公司或多或少都有实现这种应用端缓存的机制,antirez 想通过 server 端的一些设计来减少客户端缓存实现的复杂度和成本,甚至不惜在 redis 协议上做修改。

之前存在问题

只使用 Redis 分布式缓存时,遇到数据更新时,应用程序更新完 MySQL 中的数据,可以直接将 Redis 中对应缓存失效掉,保持数据的一致性。

而进程内缓存的数据一致性比分布式的缓存面临更大的挑战。数据更新的时候,如何通知其他进程也更新自己的缓存呢?

如果按照分布式缓存的思路,我们可以设置极短的缓存失效时间,这样不必实现复杂的通知机制。

但是不同进程内的数据依然会面临不一致的问题,并且不同进程缓存失效时间不统一,同一个请求到了不同的进程,可能出现反复幻读的情况。

How

Ben 在 RedisConf18 给出了一个方案(视频和 PPT 链接在文末),通过 Redis 的 Pub/Sub,可以通知其他进程缓存对此缓存进行删除。如果 Redis 挂了或者订阅机制不靠谱,依靠超时设定,依然可以做兜底处理。

Antirez(Redis 的作者) 也正是听取 Ben 这个方案后,才决定在 Redis Server 支持客户端缓存的,因为在有服务端参与的情况下可以更好的处理上述这些问题。

三种缓存模式:

缓存模式

特征描述

优缺点

默认模式

1. 服务端会记录某个客户端具体访问过哪一些 key,当这些 key 对应的

值发生变化时,会发送失效消息给这些客户端。

2. 这个模式会在服务端消耗一些内存,但是发送失效消息的范围,被限

制在了客户端存储了的 key 的集合范围内

优点:省 CPU 和带宽

缺点:多用占内存

广播模式

1. 服务端不会再记录某个客户端访问了哪些 key。取而代之的是,客户

端需要订阅 key 的特定前缀,每当符合这个前缀的 key 对应的值发生

改变时,客户端都会收到通知消息

2. 这个模式下不消耗服务端的内存,但是会消耗更多的 CPU

优点:少占用内存

缺点:多用占 CPU

转发模式

1. 主要是为了兼容 resp2 协议,是一个过渡模式

优点:少占用内存

缺点:多用占 CPU

在客户端缓存,redis 本身不提供在应用服务端缓存数据的功能,这个功能要由访问 redis 的业务端自己去实现。也就是说,redis 服务端只负责通知你,你缓存在应用服务本地的这个 key 已经作废了,至于你本地如何缓存的这些数据,redis 并不关心,也不可能去关心。

Demo

默认模式

前提:redis 6.x 版本+开启 RESP3 协议+开启 tracking 机制

1. 客户端 A 采用 telnet 模式连接上 redis,命令:telnet 127.0.0.1 6379

2. 开启 resp3 协议,命令:hello 3

3. 开启 tracking 机制,命令:client tracking on

4. 接下来,我们在客户端 A 中先执行 get key1,然后在客户端 B 中执行 set key1 newval,然后我们发现客户端 A 中收到了作废信息

广播模式

前提:redis 6.x 版本+开启 RESP3 协议+开启 tracking 机制

接下来,我们在客户端 B 中执行 set 任意 key 的值,然后我们发现客户端 A 中均能收到作废信息

转发模式(不要使用)

前提:redis 6.x 版本+开启 tracking 机制

1. 客户端 A 采用 telnet 模式连接上 redis,并订阅_redis_:invalidate 事件,命令:telnet 127.0.0.1 6379,subscribe _redis_:invalidate

2. 客户 B 上开启转发模式,命令:client tracking on bcast redirect 49

3. 这时候我们发现,在其它客户端修改的消息均能出现在客户端 A

Redis 主从模式

Redis 的集群历史

Redis 有三种集群模式,分别是:主从模式、哨兵模式、Cluster 模式。

Rdis 最开始使用主从模式做集群,若 master 宕机,需要手动配置 slave 转为 master

为了高可用后来提出来哨兵模式,该模式下有一个哨兵集群监视 master 和 slave,若 master 宕机可自动将 slave 转为 master

但哨兵模式有一个问题,就是内存有限,写性能上限为单点,不能扩容;所以 Redis 在 3.x 后,官方发布了 cluster 集群模式

集群模式

特性

缺点

代表产品

主从模式

通过主从模式可以提升读性能

故障时需要手工切换,内存瓶颈

主从

哨兵模式

仍然采用主从模式,增加了从自动转主功能

故障主从可自动切换,但是写性能仍有限,内存瓶颈

哨兵

代理模式

通过代理进行节点路由,达到水平扩展

某些原生命令不支持,代理降低部分性能,

某些不支持最新 Redis 功能

Twemproxy、Codis

集群模式

数据分布式存储、节点水平扩容、故障自动转移

需要轻量级客户端计算路由

最新集群

Redis 主从模式架构

在主从复制中,Redis 数据库分为两种角色:主数据库(master)和从数据库(slave),从可以是多个

1. 主数据库可以进行读写操作,当读写操作导致数据变化时会自 动将数据同步给从数据库

2. 复制的数据流是单向的,只能由主节点复制到从节点。

3. 从数据库一般都是只读的,并且接收主数据库同步过来的数据

4. 一个 master 可以拥有多个 slave,但是一个 slave 只能对应一个 master

5. slave 挂了不影响其他 slave 的读和 master 的读和写,重新启动后会将数据从 master 同步过来

6. master 挂了以后,不影响 slave 的读,但 redis 不再提供写服务,master 恢复后 redis 将重新对外提供写服务

7. master 挂了以后,不会在 slave 节点中重新选一个 master

Redis主从复制原理

redis < 2.8

场景

实现方式

说明

第一次启动

sync 复制

全量复制

正常运行

缓冲区复制

增量复制

第二次启动

sync 复制

全量复制,短暂的停启会对主造成较影响

redis >= 2.8

场景

实现方式

说明

第一次启动

psync 复制

全量复制

正常运行

缓冲区复制

增量复制

第二次启动((Master 缓冲区未溢出)

缓冲区复制

部分复制,短暂的停启不会对主造成影响

Redis>=2.8 第二次启动(Master

psync 复制

全量复制

notice

无是哪种场景,Redis 的主从复制机制均采用异步复制,也称为乐观复制,因此不能完全保证主从数据的一致性。

SYNC 工作原理

总体思路: 先RDB 再 类AOF 发送补充指令

1. 当 slave 启动后或者断开重连后,会向 master 发送 sync 命令

2. master 节点收到 sync 命令后会开始在后台保存快照(即 RDB 持久化,主从复制时会触发 RDB),并将保存快照期间接收到的命令缓存起来

3. master 节点执行 RDB 持久化完后,向所有 slave 节点发送快照 RDB 文件,并在发送快照期间继续记录被执行的写命令

4. slave 节点收到快照文件后丢弃所有旧数据(会清空所有数据),载入收到的快照

5. master 节点快照发送完毕、slave 节点载入快照完毕后,master 节点开始向 slave 节点发送缓冲区中的写命令

6. master 节点完成对快照的载入,开始接收命令请求,并执行来自主数据库缓冲区的写命令。(从数据库初始化完成)

7. 后续 master 节点每执行一个写命令就会向 slave 节点发送相同的写命令,slave 节点接收并执行收到的写命令

PSYNC 工作原理

总体思路:判断是否是第一次同步,以及缓存区是否溢出,来决定是不是全量同步。

1. 当 slave 启动后或者断开重连后,slave 根据自己是否保存 Master runid 来判断是否是第一次连接

2. 如果是第一次同步则向 Master 发送 psync -1 命令来进行全量同步;如果是重连接,则发送 PSYNC runid offset 命令(runid 是身份 ID,offset

是从节点同步命令的全局迁移量)

3. Master 接收到 PSYNC 命令后,首先判断 runid 是否和本机的 id 一致,同时会判断 offset 偏移量有没有超过复制积压缓冲区大小,如果没有那

么就给 Slave 发送 CONTINUE,此时 Slave 只需要等待 Master 传回失去连接期间丢失的命令。

4. 如果 runid 和本机 id 不一致或者 offset 偏差超过了复制积压缓冲区大小,那么就会返回 FULLRESYNC runid offset,Slave 将 runid 保存起来,

并进行全量同步

5. 后续过程中,主数据库会将每一个写命令传递给从数据库的同时,都会将写命令存放到复制积压队列,并记录当前积压队列中存放命令的全局

偏移量 offset。当 salve 重连接时,会从复制积压队列进行增量复制。

Redis 主从模式环境搭建

谁从谁配

1. 静态配置

在6380 的 redis.conf 中我们增加如下配置,代理 6380(从)追随 6379(主)

replicaof 192.168.128.167 6379

2. 动态配置

分别启动 192.168.128.167:6379,192.168.128.167:6380,连接 6380 端口,输入如下命令

replicaof 192.168.128.167 6379

一般这种模式需要回写配置,通过 config rewrite 命令,将内存中的配置重写写入 redis.conf

如果想断开,输入

replicaof no one,然后再执行 config rewrite

如果想切换新主节点

slaveof 新 ip 新 port

1. 断开与旧主节点主从关系。

2. 与新主节点建立主从关系。

3. 删除从节点当前所有数据。

4. 对新主节点进行复制操作。

3. 启动参数(不推荐)

在 6380 进程启动时加入参数

./redis-server ../conf/redis_6380.conf --replicaof 192.168.128.167 6379

Redis Sentinel集群方案(哨兵)

架构梳理

Redis Sentinel 是一个分布式架构,包含若干个 Sentinel 节点和若干个 Redis 数据节点

每个 Sentinel 节点会对 Redis 数据节点和其余 Sentinel 节点进行监控,当发现节点不可达时,会对节点做下线标识。

如果被标识的是 Redis 主节点,它会和其他 Sentinel 节点进行“协商”,当大多数的 Sentinel 节点都认为主节点不可达时,他们会选举出一个 Sentinel 节点来完成自动故障转移工作,同时将这个变化通知Redis 应用方

工作原理

每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令。

如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所指定的值,则这个实例会被 Sentinel 标记 为主观下线。

如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 是否进入了主观下线状态

若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除

当有足够数量的 Sentinel(大于等于配置值)在指定的时间范围内确认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线

若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除

Master 客观下线后,会在 Master 所在的 Slave 中进行重新选取 Master

注意:哨兵本身作用于集群状态的高可用,作用于 master 节点挂掉后的主备切换,不能解决 redis 本身数据不一致的问题

集群的常见管理手段

启停管理

启动时:

先启 redis 进程,再启 sentinel 进程(按顺序可防止主从切换)

redis-server redis-{port}.conf

redis-sentinel conf/sentinel_{port}.conf

停止时:

先停 sentinel 进程,再停 redis 进程(按顺序可防止主从切换)

redis-cli -p 26378 shutdown

redis-cli -p 6378 shutdown

或者

kill 进程号

一般情况下,我们不必要用 kill -9,这样 redis 不会进行持久化操作,除此之外,还会造成缓冲区等资源不能优雅关闭,极端情况下会造成 AOF 和复制丢失数据的情况

info replication 查看主从关系

sentinel master 查看主信息

sentinel replicas 查看从信息

在线扩容

场景

说明

操作

节点内存扩容

节点内存不够,但主机存在足够内存

通过 config set maxmemory 在线修改配置参数

添加从节点

单纯增加一个从节点

1. 搭建新节点

2. 建立主从关系即可

从节点迁移扩容

节点内存不够,主机内存也不够,通过新机器来承载节点

由 A->B,A->C 变成 A->B,A->D

1. 将新节点加入到集群中

2. 下线原有从节点

主节点迁移扩容

节点内存不够,主机内存也不够,通过新机器来承载节点

由 A->B,A->C 变成 D->B,D->C

1. 将新节点加入到集群中

2. 将主节点进行强制转移

3. 下线原有主节点

如何彻底下线从节点

将每一个 sentinel_.conf 中的副本节点删除掉

常见命令

SENTINEL master mymaster

查看集群的 master

SENTINEL masters

查看所有 master(一个 sentinel 节点可以多个 Redis 主)

SENTINEL replicas mymaster

看某个集群的 salves

SENTINEL sentinels

查看 sentinel 节点信息

SENTINEL get-master-addr-by-name mymaster

查看集群的主地址

SENTINEL config get *

查询通用配置信息

SENTINEL config set [name]

设置通用配置信息

SENTINEL set mymaster down-after-milliseconds 30000

设置 sentinel 配置信息

SENTINEL CKQUORUM mymaster

用来判断 sentinel 集群是否可故障转移

SENTINEL MONITOR mymaster 192.168.128.167 6379 2

针对某个 sentinel 节点加入对 Redis 集群的监控

SENTINEL REMOVE

针对某个 sentinel 节点停止监控(会清空配置信息,慎用)

SENTINEL FAILOVER mymaster

强制主服务器转移

RedisCluster 集群方案

简介

Redis 集群实现了一个数据分布式存储、节点水平扩容、故障自动转移的集群方案

1. 存储:

Redis 集群实现了对 Redis 的水平扩容,即启动 N 个 Redis 节点,将整个数据库分布存储在这 N 个节点中,每个节点存储总数据的 1/N

2. 水平扩容:

Redis 集群模式用来解决单 Redis 数据量瓶颈,并且不再需要配置单独的哨兵,可以进行水平扩展

3. 故障自动转移:

Redis 集群将 key 通过 crc16 运算后与 16384 取模,将 key 分布到固定 16384 个槽位(slot)中的一个(0-16383),每个 redis 分别保管不同 的槽位,不同的 Redis 子集群分管不同的槽位,每个子集可用一主多从实现,进行故障自动转移

4. 注意:

并且集群模式默认使用 db0 不支持 select 别的 db

核心原理

Redis 集群采用了哈希 Slot+主从节点实现了一个数据分布式存储、节点水平扩容、故障自动转移的集群方案

为了提高效率,Redis 集群客户端维护当前插槽配置的映射,比如像 Java 实现的 JedisCluster

配置

节点配置

1. redis.conf 单点增加配置

cluster-enabled yes

cluster-config-file nodes-{port}.conf

2. 节点数量

要求节点数量为 2*N=2 *N个节点

生产要求至少 2 台机器,保证挂掉一台机器或者一个节点后,服务仍能正常进行

而且主从不能配对在同一台机器上

3. 启动所有节点

以 cluster 模式启动 2*N 个 redis 服务

集群配置

1. 创建集群 ./redis-cli --cluster create 192.168.128.167:6381 192.168.128.167:6382 192.168.128.167:6383

192.168.128.167:6384

192.168.128.167:6385 192.168.128.167:6386 --cluster-replicas 1

2. 数据目录:这时候在数据目录下面会生成集群配置文件

健康检查

redis-cli --cluster info 192.168.128.167:6381

redis-cli --cluster check 192.168.128.167:6381

常见管理手段

扩容-增加 Redis 节点

准备单节点 (一主一丛)

按集群环境搭建的要求准备好单节点


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部