1.Redis集群
1.1 搭建主从集群
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写的分离。一般情况下,主节点负责写操作,从节点负责读操作。而从节点如何得知数据呢?就需要做一个数据的同步。
这里我采用docker的多服务部署docker-compose.yaml,网络采用host模式,直接成为主机的进程,而非docker内部的容器。
version: "3.2"
services:
r1:
image: redis
container_name: r1
network_mode: "host"
entrypoint: ["redis-server", "--port", "7001"]
r2:
image: redis
container_name: r2
network_mode: "host"
entrypoint: ["redis-server", "--port", "7002"]
r3:
image: redis
container_name: r3
network_mode: "host"
entrypoint: ["redis-server", "--port", "7003"]
运行集群
docker compose up -d
虽然我们启动了3个Redis实例,但是它们并没有形成主从关系。我们需要通过命令来配置主从关系:
在建立之前,首先得进入redis内部
# 连接r2
docker exec -it r2 redis-cli -p 7002
# 认r1主,也就是7001
slaveof 192.168.150.101 7001
# 连接r3
docker exec -it r3 redis-cli -p 7003
# 认r1主,也就是7001
slaveof 192.168.150.101 7001
然后连接r1
,查看集群状态:
# 连接r1
docker exec -it r1 redis-cli -p 7001
# 查看集群状态
info replication
这样后,从节点就只能读了,不能写。
1.2 主从同步原理
当主从第一次同步连接或者断开重连时,从节点都会发送psync请求,尝试数据同步:
问题一:master如何知道这个从节点是否是第一次来连接或者是断开重连的
要搞明白这个问题,首先我们得知道每一个master节点刚开始创建的时候有一个replicationID(简称replid),且每一个节点刚开始创建的时候,都认为自己是master。而建立主从关系后,他们的replid都会发生变化,且都会变成一样的。所以重连的时候,master就会判断这个replid是否和自己一样,如果一样,代表这个节点是断开重连的,如果不一样,就代表这个节点是第一次连接自己,作为自己的从节点。
问题二:master如何与从节点做数据同步?
master会通过bgsave的命令,生成RGB文件,这个文件是之前保存在磁盘中的,包含了master的所有数据。然后把这个RGB文件发送给从节点。从节点就根据这个RGB文件做数据同步,所以从节点连接主节点的时候,不仅会发psync,还会携带自己的replid。
问题三:主节点如何得知从节点缺失了哪些数据来做增量同步呢?
每个节点都有一个内存缓冲区repl_backlog,这个缓冲区的衡量值是offset,也就是衡量这个缓冲区数据的多少。这个缓冲区是在主从关系建立后双方产生的,来记录自己执行过的命令。假如从节点重连了,那么从节点在和主节点重新建立连接的时候,不仅会发psync,replid,还要发送自己的offset,然后主节点拿到从节点的offset,就和自己的offset做比较,看看缺失了哪些offset,然后把缺失的这些命令发给从节点。实现增量同步。
主从集群优化方案:
可以从以下几个方面来优化Redis主从就集群:
在master中配置
repl-diskless-sync yes
启用无磁盘复制,避免全量同步时的磁盘IO。Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高
repl_baklog
的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步限制一个master上的slave节点数量,如果实在是太多slave,则可以采用
主-从-从
链式结构,减少master压力
主从从架构图:
1.3 哨兵工作原理:
哨兵的作用如下:
状态监控:
Sentinel
会不断检查你的master
和slave
是否按预期工作故障恢复(failover):如果
master
故障,Sentinel
会将一个slave
提升为master
。当故障实例恢复后会成为slave
状态通知:
Sentinel
充当Redis
客户端的服务发现来源,当集群发生failover
时,会将最新集群信息推送给Redis
的客户端
那sentinel如何知道,是哪个redis挂了?
Sentinel
基于心跳机制监测服务状态,每隔1秒向集群的每个节点发送ping命令,并通过实例的响应结果来做出判断:
主观下线(sdown):如果某sentinel节点发现某Redis节点未在规定时间响应,则认为该节点主观下线。
客观下线(odown):若超过指定数量(通过
quorum
设置)的sentinel都认为该节点主观下线,则该节点客观下线。quorum值最好超过Sentinel节点数量的一半,Sentinel节点数量至少3台。
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
-
首先会判断slave节点与master节点断开时间长短,如果超过
down-after-milliseconds * 10
则会排除该slave节点 -
然后判断slave节点的
slave-priority
值,越小优先级越高,如果是0则永不参与选举(默认都是1)。 -
如果
slave-prority
一样,则判断slave节点的offset
值,越大说明数据越新,优先级越高 -
最后是判断slave节点的
run_id
大小,越小优先级越高(通过info server可以查看run_id
)。
此外,还有redis分片集群
分片集群就是多个主从集群,各自处理不同的数据。
分片集群特征:
集群中有多个master,每个master保存不同分片数据 ,解决海量数据存储问题
每个master都可以有多个slave节点 ,确保高可用
master之间通过ping监测彼此健康状态 ,类似哨兵作用
客户端请求可以访问集群任意节点,最终都会被转发到数据所在节点
在分片集群中,我们如何去存数据呢,我们怎么知道这个数据应该存在哪一个节点呢?那么在这里就涉及到一个概念,叫散列插槽:
在Redis集群中,共有16384个
hash slots
,集群中的每一个master节点都会分配一定数量的hash slots
。具体的分配在集群创建时就已经指定了。当我们读写数据时,Redis基于
CRC16
算法对key
做hash
运算,得到的结果与16384
取余,就计算出了这个key
的slot
值。然后到slot
所在的Redis节点执行读写操作。不过
hash slot
的计算也分两种情况:
当
key
中包含{}
时,根据{}
之间的字符串计算hash slot
当
key
中不包含{}
时,则根据整个key
字符串计算hash slot
所以总结一下:
Redis分片集群如何判断某个key应该在哪个实例?
-
将16384个插槽分配到不同的实例
-
根据key计算哈希值,对16384取余
-
余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
-
Redis计算key的插槽值时会判断key中是否包含
{}
,如果有则基于{}
内的字符计算插槽 -
数据的key中可以加入
{类型}
,例如key都以{typeId}
为前缀,这样同类型数据计算的插槽一定相同