Redis进阶知识

Redis

  • 1.事务
  • [2. 主从复制](#2. 主从复制)
    • [2.1 如何启动多个Redis服务器](#2.1 如何启动多个Redis服务器)
    • [2.2 监控主从节点的状态](#2.2 监控主从节点的状态)
    • [2.3 断开主从复制关系](#2.3 断开主从复制关系)
    • [2.4 额外注意](#2.4 额外注意)
    • 2.5拓扑结构
    • [2.6 复制过程](#2.6 复制过程)
      • [2.6.1 数据同步](#2.6.1 数据同步)
  • 3.哨兵
  • 4.集群
    • [4.1 数据分片算法](#4.1 数据分片算法)
    • [4.2 故障检测](#4.2 故障检测)
  • [5. 缓存](#5. 缓存)
    • [5.1 缓存问题](#5.1 缓存问题)
  • [6. 分布式锁](#6. 分布式锁)

1.事务

Redis的事务只能保持命令是连续执行的,仅此而已。

  • MULTI:开启一个事务,执行成功返回OK
  • EXEC:真正执行事务
  • DISCARD:放弃当前事务。此时直接清空事务队列。之前的操作都不会真正执行到。
  • WATCH:开启事务时,如果对watch的事务进行修改,就会记录当前key的"版本号"。在真正提交事务的时候,如果发现当前服务器上的key版本号已经超过了事务开始时的版本号,就会让事务执行失败(事务中所有的操作都不执行)。
  • UNWATCH:取消对key的监控

客户端1事务先不执行呢:

bash 复制代码
127.0.0.1:6379> watch k1 # 开始监控 k1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 100 # 进⾏修改, 从服务器获取 k1 的版本号是 0. 记录 k1 的
版本号. (还没真修改呢, 版本号不变)
QUEUED
127.0.0.1:6379> set k2 1000
QUEUED

客户端2然后修改k1:

bash 复制代码
127.0.0.1:6379> set k1 200 # 修改成功, 使服务器端的 k1 的版本号 0 -> 1
OK

客户端1再执行,发现命令被撤回了。

bash 复制代码
127.0.0.1:6379> EXEC # 真正执⾏修改操作, 此时对⽐版本发现, 客⼾端的 k1 的版
本号是 0, 服务器上的版本号是 1, 版本不⼀致! 说明有其他客⼾端在事务中间修改了 k1 !!!
(nil)
127.0.0.1:6379> get k1
"200"
127.0.0.1:6379> get k2
(nil)

2. 主从复制

节点:每台redis-server主机就是一个节点

主节点:可读可写的节点。

从节点:只能读数据的节点。

主从复制:将主节点的数据复制到从节点,使从节点和主节点保持一致。

  • 可用性:一个节点挂了,其他节点可以顶上。
  • 性能提升:对于读操作可以平均分摊到多个机器上。

2.1 如何启动多个Redis服务器

正常来说,每个redis服务器程序,应该在一个单独的主机上。学习阶段可以部署到一台机器上,但是得要求端口不一样。

  • 配置文件在/etc/redis/redis.conf,复制出来修改port,和daemonize yes
  • 启动 redis-server [新配置文件路径] ,下图中就把另外两个redis启动起来了
  • 设置主从结构,修改配置文件加上主从结构配置。在末尾加上 slave [主节点ip] [主节点port]
  • 再次启动查看端口号。

    最终达到的效果:主节点这边数据产生任何修改,从节点就能立即感知到。

2.2 监控主从节点的状态

info replication

bash 复制代码
主节点
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=100,lag=0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:100
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:100
bash 复制代码
从节点
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:170
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:170
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:170

2.3 断开主从复制关系

slaveof no one 来断开主节点复制关系

1)断开与主节点复制关系。

2)从节点晋升为主节点。
slaveof 新主机 新port 认另一个服务作为主节点

1)断开与旧主节点复制关系。

2)与新主节点建⽴复制关系。

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

4)从新主节点进⾏复制操作。

以上方法仅仅是临时性的,但是当我们重启之后,还会以配置文件为准。

2.4 额外注意

安全性:主节点可以使用密码来保证自己服务器的安全性

只读:对于从节点进行修改,主节点是感知不到的。

传输延迟:

2.5拓扑结构

  • 一主一从

    写请求较多时,主节点负载较大。此时可以将主节点的AOF关闭,从节点开启AOF。当主机宕机的时候,需要先从从节点获取AOF文件再重启。

  • 一主多从

    实际开发中,读请求够多,可以选择一主多从,但是同步时,由于从节点过多,增加带宽,同样对于主节点是一个压力。

    优点:同步速度快

  • 树形主从

    解决了主节点同步时带宽过高的场景,因为从节点也可以同步它的子节点。

    缺点:同步的延时变长。

2.6 复制过程

2.6.1 数据同步

Redis使用psync完成主从数据同步,同步过程分为:全量复制和部分复制。

  • 全量复制:一般用于初次复制场景,当数据量大时,可能会对主从节点和网络造成很大开销。
  • 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连接上主节点后,如果条件允许,主节点会补发数据给从节点。
bash 复制代码
主节点
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=100,lag=0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06      主节点ID
master_replid2:0000000000000000000000000000000000000000    备用ID 
master_repl_offset:100
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:100
bash 复制代码
从节点
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:170
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:170
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:170

PSYNC语法格式:

PSYNC replicationed offset

replicationed=?,offset=-1,表示全量复制

具体的值就是尝试进行部分复制

  1. replicationid/replid(复制ID)

    主节点的复制id,主节点重新启动,或者从节点晋升为主节点,都会生成一个replicationid。(同一个节点每次重启,replicationid都会变化)

    从节点和主节点连接后,就会获取到主节点的replicationid。

  2. offset(偏移量)

    主节点每进行一次写操作会记录一下偏移量,统计在master_repl_offset中。并且也会记录从节点的复制偏移量,保存在slave0中。

    从节也会记录自己的偏移量在slave_repl_offset

    因此可以通过对比主从节点的偏移量,来看数据是否一致。

psync 运⾏流程

步骤:1.从节点发送psync命令给主节点,replied和offset的默认值是?和-1。

2.主节点根据psync参数和自身数据情况决定响应结果 +FULLRESYNC replid offset,从节点需要进行全量;+CONTINEU,部分复制;

-ERR,说明 Redis 主节点版本过低,不⽀持 psync 命令。

全量复制

1)从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的运

⾏ ID 和复制偏移量,所以发送 psync ? -1。

2)主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应。

3)从节点接收主节点的运⾏信息进⾏保存。

4)主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化。

5)从节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘。

6)主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件

后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收

到的 rdb ⽂件中. 保持主从⼀致性。

7)从节点清空⾃⾝原有旧数据。

8)从节点加载 RDB ⽂件得到与主节点⼀致的数据。

9)如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最

近的 AOF ⽂件。

生成RDB文件在发送比较麻烦:

即使是省去了读写硬盘的开销,但是此操作的大头是网络传输,这个没法省,所以开销仍然很大。

部分复制

1)当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终端

复制连接。

2)主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所

以暂时将这些命令滞留在复制积压缓冲区中。

3)当主从节点⽹络恢复后,从节点再次连上主节点。

4)从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进⾏部分

复制。

5)主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据,

并响应 +CONTINUE 给从节点。

6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。

复制积压缓冲区

复制积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节

点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊

复制积压缓冲区,如图所⽰。

由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近已复制数据的功能,⽤于部分复制和复制命令丢失的数据补救。复制缓冲区相关统计信息可以通过主节点的 info replication 中:

实时复制:

主从节点在建⽴复制连接后,主节点会把⾃⼰收到的 修改操作 , 通过 tcp ⻓连接的⽅式, 源源不断的传输给从节点. 从节点就会根据这些请求来同时修改⾃⾝的数据. 从⽽保持和主节点数据的⼀致性.

另外, 这样的⻓连接, 需要通过⼼跳包的⽅式来维护连接状态. (这⾥的⼼跳是指应⽤层⾃⼰实现的⼼跳,

⽽不是 TCP ⾃带的⼼跳).

1)主从节点彼此都有⼼跳检测机制,各⾃模拟成对⽅的客⼾端进⾏通信。

2)主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。

3)从节点默认每隔 1 秒向主节点发送 replconf ack {offset} 命令,给主节点上报⾃⾝当前的复制偏移

量。

如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断

开复制客⼾端连接。从节点恢复连接后,⼼跳机制继续进⾏。

3.哨兵

基本流程是:

  1. 哨兵监控
  2. 哨兵发现某个节点挂了,会协商选出一个leader
  3. leader去完成后续故障转移工作。从节点中选一个作为新的主节点;让其他从节点同步新主节点;通知应用层转移到新主节点上;

选举原理

  1. 主观下线,这个哨兵通过心跳包,判定redis是否正常工作。
  2. 客观下线,多个哨兵都认为这个主节点挂了,(达到了法定票数)
  3. 让多个哨兵节点选一个leader
  4. leader选完后,leader就需要选一个从节点,作为新的主节点。
    优先级:每个redis数据节点,都会在配置文件中有一个优先级,slave-priority优先级大的胜出
    offset:offset越大说明同步数据的进度越大。
    run id:每个redis节点启动的时候随机生成一串数字。
  5. 选出之后,leader就会控制这个节点,执行slave no one,成为master。再控制其他节点,执行slave of,换主结点即可。

注意事项

  • 哨兵节点不能只有⼀个. 否则哨兵节点挂了也会影响系统可⽤性.
  • 哨兵节点最好是奇数个. ⽅便选举 leader, 得票更容易超过半数.
  • 哨兵节点不负责存储数据. 仍然是 redis 主从节点负责存储.
  • 哨兵 + 主从复制解决的问题是 "提⾼可⽤性", 不能解决 "数据极端情况下写丢失" 的问题.
  • 哨兵 + 主从复制不能提⾼数据的存储容量. 当我们需要存的数据接近或者超过机器的物理内存, 这样的结构就难以胜任了.

4.集群

Redis 的集群就是引⼊多组 Master / Slave , 每⼀组 Master / Slave 存储数据全集的⼀部分, 从⽽构成⼀个更⼤的整体, 称为 Redis 集群 (Cluster).

4.1 数据分片算法

  • 哈希求余

    优点:简单高效、数据分配均匀

    缺点:一旦需要扩容,N改变了,原有的规则映射被破坏,就需要重新排列数据,开销大。

  • 一致性哈希算法

    第⼀步, 把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上. 数据按照顺时针⽅向增⻓.

    第⼆步, 假设当前存在三个分⽚, 就把分⽚放到圆环的某个位置上.

    第三步, 假定有⼀个 key, 计算得到 hash 值 H, 规则很简单, 就是从 H 所在位置, 顺时针往下找, 找到的第⼀个分⽚, 即为该 key 所从属的分⽚.

    当增加分片时,只有相邻的分片才会进行搬运,大大减少了搬运规模

    优点: ⼤⼤降低了扩容时数据搬运的规模, 提⾼了扩容操作的效率.

    缺点: 数据分配不均匀 (有的多有的少, 数据倾斜).

  • 哈希槽分区算法 (Redis 使⽤) (解决搬运成本高,数据分配不均匀)

    hash_slot = crc16(key) % 16384 [0-16383]个槽位

    现在需要扩容一个分片,只需要从0、1、2号分片中适当拿出一点,就可以凑够了。

    分片的规则很灵活:每个分片持有的槽位不一定连续。每个节点使用位图来表示自己持有哪些槽位。16384个槽位,需要2KB大小的内存空间表示。

问题1:Redis集群最多有16384个分片吗?

不对,每个分片的槽位应该不超过1000。

有的槽位可能有多个key,有的槽位可能没有key。因为多个key的哈希可能映射到同一个槽位。

• 节点之间通过⼼跳包通信. ⼼跳包中包含了该节点持有哪些 slots. 这个是使⽤位图这样的数据结构

表⽰的. 表⽰ 16384 (16k) 个 slots, 需要的位图⼤⼩是 2KB. 如果给定的 slots 数更多了, ⽐如 65536

个了, 此时就需要消耗更多的空间, 8 KB 位图表⽰了. 8 KB, 对于内存来说不算什么, 但是在频繁的⽹

络⼼跳包中, 还是⼀个不⼩的开销的.

• 另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚. 所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤

的, 同时也会使对应的槽位配置位图体积不⾄于很⼤.

4.2 故障检测

1)故障判定

集群中的所有节点,都会周期性的使用心跳包进行通信。

  1. 节点A给节点B发送ping包,B就会给A返回一个pong包。ping喝pong除了message type不一样外,其它都一样。都包含了集群中的配置信息(该节点的id,该节点从属于哪个分片,是主节点还是从节点,从属于谁,持有哪些slots图)。
  2. 每个节点,每秒钟,都会给一些随机的节点发起ping包,而不是全发一遍。
  3. 当节点A给节点B发起ping包,B不能如期回应的时候,此时A就会尝试重置和B的TCP连接,看能否连接成功。如果仍然连接失败,A就会把B设置为PFALL状态。
  4. A判定B为PFALL之后,就会通过redis内置的Gossip协议,和其他节点进行沟通,向其他节点确认B的状态。
  5. 此时A发现其他很多节点,也认为B为PFALL,并且数目超过总集群个数的一半,那么A就会把B标记成FAIL(客观下线),并且这个消息同步给其他节点。

2)故障迁移(把从节点提拔成主节点)(选举过程:Raft算法)

上面的例子,B故障,A会把B FAIL的消息告知集群中的其他节点

  • 如果B是从节点,那么不需要进行故障迁移。
  • 如果B是主节点,那么就会由B的从节点(比如C和D)触发故障迁移。
    流程如下:
  1. 从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没通信(数据差距太大),时间超过阈值,就是去竞选资格
  2. 具有资格的从节点,比如C和D,会先休眠一定时间,休眠时间=500ms挤出时间+[0, 500ms]随机时间+排名*1000ms。offset值越大,排名越靠考前。
  3. 比如C的休眠时间到了,C就会给其他所有集群中的节点,进行拉票操作。但是只有主节点才有投票资格。
  4. 主节点会把自己的票投给C,当C收到的票数超过主节点数目的一半,C就会晋升成主节点。
    10.同时C还会把自己成为主节点的消息,同步给其他集群的节点。

5. 缓存

最重要的是热key,热key如何生成呢?

1)定期生成

每隔一段时间,会访问的数据频次进行统计。挑选出访问频次最高的前N%数据。

优点:实现起来比较简单,过程更可控,方便排查问题。

缺点:实时性不够,如果出现一些突发事件,有一些不是热词的内容,成了热词了。新的热词就可能给后面的数据库带来较大的压力。

2)实时生成

先给缓存设定容量上限(可以通过 Redis 配置⽂件的 maxmemory 参数设定).

  • 如果redis查到了直接返回。
  • redis中不存在,就从数据库查,把查到的结果同时写入redis。
  • 如果缓存已经满了(达到上限), 就触发缓存淘汰策略, 把⼀些 "相对不那么热⻔" 的数据淘汰掉.

淘汰策略:
FIFO (First In First Out) 先进先出

把缓存中存在时间最久的 (也就是先来的数据) 淘汰掉.

LRU (Least Recently Used) 淘汰最久未使⽤的

记录每个 key 的最近访问时间. 把最近访问时间最⽼的 key 淘汰掉.

LFU (Least Frequently Used) 淘汰访问次数最少的

记录每个 key 最近⼀段时间的访问次数. 把访问次数最少的淘汰掉.

Random 随机淘汰

从所有的 key 中抽取幸运⼉被随机淘汰掉.

Redis内置淘汰策略如下:

5.1 缓存问题

  1. 缓存预热(Cache preheating)

    通过统计的方式把热点数据导入到redis中,通过实时生成,达到缓存预热的工作。

  2. 缓存穿透Cache penetration

    查询的某个key,在redis中没有,mysql也没有。这个key也不会更新到key中。

    原因:业务设计不合理,非法的key也被进行查询了;数据库的数据不小心被删除了;黑客恶意攻击。

    解决办法:对参数进行严格校验,是否符合查询的格式;针对数据库上不存在的key,也存到Redis中,比如value就设置"";使用布隆过滤器先判断key是否真存在,再真正查询。

  3. 缓存雪崩Cache avalanche

    短时间内大量的key再缓存上失效,导致数据库压力骤增。

    原因:Redis挂了;Redis大量的key同时过期。

    如何解决:部署高可用的集群,并完善监控报警体系;不给key设置过期事件或者设置过期事件的时候添加随机事件因子。

  4. 缓存击穿(Cache breakdown)

    原因:缓存雪崩的特殊情况,针对热key突然过期了,导致大量的请求直接打到数据库上,引起数据库宕机。

    如何解决:基于统计方式发现热key,设置永不过期;进行必要的服务降级,利用访问数据库时使用分布式锁,限制同时请求数据库的并发数。

6. 分布式锁

分布式锁是一种或一组单独的服务器程序,给其他服务器提供加锁服务。

AB进程在访问Mysql时,在之间设置一个锁Redis服务器。

A进行买票时,会往Redis设置一个键值对,B想设置时,发现该键值对已经设置过了,就加不了锁了。A操作之后,就将该键值对删除。

问题1:若A没有执行完挂掉了,谁来释放(删除)锁呢?

解决:设置key的过期时间,来防止A挂掉。

问题2:有没有可能B去解锁呢?

解决:首先给服务器编号,加锁时,value对应着服务器的编号。后续解锁时候,就可以进行校验了。(就可能避免B可能会误解锁了)

问题3:解锁是两步的,先查询判定,在del。有可能A服务器里有两个线程会导致重复del。在这两个线程del之间,有B加锁了。因此需要保证操作为原子性。

解决:使用lua写一些逻辑,存到服务器上。 客户端可以调用lua这个API来保证操作原子性。

问题4:过期时间续约问题?设置多少合适呢?

专门负责续约的线程,每次快过期的时候再重新设置时间,叫做"看门狗"(watch dog)。这个是加锁的服务器上的。

问题5:RedLock?(少数服从多数)

加锁的时候, 按照⼀定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". ⽐如

50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败.

如果给某个节点加锁失败, 就⽴即再尝试下⼀个节点.

当加锁成功的节点数超过总节点数的⼀半, 才视为加锁成功.

同理, 释放锁的时候, 也需要把所有节点都进⾏解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保

证逻辑严密).

相关推荐
数据皮皮侠39 分钟前
最新上市公司业绩说明会文本数据(2017.02-2025.08)
大数据·数据库·人工智能·笔记·物联网·小程序·区块链
小云数据库服务专线1 小时前
GaussDB数据库架构师修炼(十六) 如何选择磁盘
数据库·数据库架构·gaussdb
码出财富2 小时前
SQL语法大全指南
数据库·mysql·oracle
异世界贤狼转生码农4 小时前
MongoDB Windows 系统实战手册:从配置到数据处理入门
数据库·mongodb
QuZhengRong4 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
码农阿豪4 小时前
Windows从零到一安装KingbaseES数据库及使用ksql工具连接全指南
数据库·windows
时序数据说10 小时前
时序数据库市场前景分析
大数据·数据库·物联网·开源·时序数据库
听雪楼主.13 小时前
Oracle Undo Tablespace 使用率暴涨案例分析
数据库·oracle·架构
我科绝伦(Huanhuan Zhou)13 小时前
KINGBASE集群日常维护管理命令总结
数据库·database
妖灵翎幺13 小时前
Java应届生求职八股(2)---Mysql篇
数据库·mysql