redis特性
- 快
- 基于内存操作,操作不需要跟磁盘交互
- 本身就是k-v结构,类似与hashMap,所以查询速度接近O(1)。
- 同时redis自己底层数据结构支持,比如跳表、SDS
- 命令执行是单线程,同时通信采用多路复用
- lO多路复用,单个线程中通过记录跟踪每一个sock(I/O流) 的状态来
管理多个I/O流 。
- 更丰富的数据类型 ,虽然都是k、v结构, value可以存储很多的数据类
型。String、List、Hash、Set、ZSet、BitMap等。 - 完善的内存管理机制、保证数据一致性。持久化机制、过期策略。
- 支持多种编程语言。
- 高可用,集群、保证高可用。主从、哨兵、cluster。
Redis的数据类型以及使用场景
Redis的基本数据类型包括String、Hash、List、Set、SortSet、BitMap等。
String
基本指令
应用场景
- 缓存,需要设置过期时间,过期时间之后保持与数据库最终一致性。
- incr命令计数相关场景、自增的分布式id、点赞数、评论数。因为单线程,是线程安全的。
Hash
基本指令
hset h1 f 6 :对象h1的f字段设置值为6
hset h1 e 5:对象h1的e字段设置值为5
hmset h1 a 1 b 2 c 3 d 4:批量设置h1的字段
hget h1 a:获取对象h1的a字段
hmget h1 a b c d:批量获取h1对象的a、b、c、d字段
hkeys h1:h1对象的所有字段名字
hvals h1:h1对象的所有value
hgetall h1:获取h1整个对象
hincrby h1 a 10 给某个字段添加值
hexists h1 y :查询h1中y字段是否存在
hdel h1 a :删除h1对象的a字段
hlen h1:h1对象有多杀个字段
应用场景
- 缓存
- 存储对象类数据
- 多维度的统计
List
可重复的有序列表
基本命令
lpush queue a:从左边添加元素a
lpush queue b c:从左边添加元素b c
rpush queue d e :从右边添加元素d e
lpop queue:左边弹出一条数据
rpop queue:右边弹出一条数据
blpop queue 10:阻塞从左边弹出
brpop queue 10:阻塞从右边弹出
lindex queue 0:取左边第一个元素
lrange queue 0 -1:获取所有元素
应用场景
- 双端可阻塞队列
- 朋友圈
Set
无序、不可重复的集合。如果集合中的元素全是整型,则是有序的。
基本命令
sadd huihuiset a b c d e f g:添加一个或者多个元素。
smembers huihuiset:获取所有元素。
scard huihuiset:获取集合中元素的个数。
srandmember huihuiset:随机获取一个元素。
spop huihuiset:随机弹出一个元素。
srem huihuiset g a:弹出指定元素。
sismember huihuiset e:查看元素是否存在。
sdiff huihuiset huihuiset1:获取huihuiset集合中有的,huihuiset1集合中没有的。
sinter huihuiset huihuiset1:获取两个集合的交集。
sunion huihuiset huihuiset1:获取两个集合的并集。
应用场景
- 抽奖。spop和srandmember随机弹出(不可重复抽奖)、获取一个元素(可重复抽奖)。
- 点赞、签到。sadd
- 交集、并集,共同关注的场景。
Sort Set(ZSet)
有序的set,每个元素都有个score排序,score相同则按照key的ASCII码排序。
基本命令
zadd z1 10 a 20 b 30 d 40 c :放入元素。
zrange z1 0 -1 withscores:根据分数从低到高排序。
zrevrange z1 0 -1 withscores:根据分数从高到低排序。
zrangebyscore z1 20 30:根据分数范围取值。
zrem z1 a:移除元素。
zcard z1:获取z1中元素的个数。
zincrby z1 20 b:给z1集合中的b元素加分。
zcount z1 50 60:获取分数范围内元素的个数。
zrank z1 d:返回指定元素的索引。
zscore z1 d:获取元素的分数。
应用场景
- 排行榜(热搜榜、游戏评分榜)
BitMap
基本命令
setbit permission 5 1 //把位5设置为1
getbit permission 5 //得到位5的值
bitcount permission //获取位为1的总数
bitpos permission 1 //获取 permission位为1的第一个位置
bitop AND hbit bitkey permission //获取bitkey与permission的&运算 并且赋值给hbit
应用场景
- 每个学员每天的签到统计
- 用户权限
- 布隆过滤器
Redis的原子性
redis事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr myid
QUEUED
127.0.0.1:6379> incr testid
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> exec
- (integer) 1
- (integer) 1
命令说明:
- multi:开启事务
- incr myid、 incr testid事务中执行的内容
- exec:执行事务
- discard:回滚事务。
事务中的错误处理:
1.可能排队就错误,比如语法的错误 会报错给用户 并且丢失这次事务。
2.执行指令的失败,比如你对string类型执行incr 这种事务里面的其他指令
会正常执行,并且不提供回滚。
事务的特点及场景:
- 命令是原子的 在执行事务中的指令时,服务器阻塞,不能执行其他指令。
- 不能拿到事务中间的指令的结果 来决定后面指令的逻辑。
Lua脚本
lua语言是一个轻量级的脚本语言,致力于很容易嵌入其他语言中使用的语
言。所以依赖于其他宿主语言,自己不会有大而全的功能,但是可以调用
宿主语言的功能。
redis中完美契合了lua脚本功能 redis可以调用lua脚本中的api,lua脚本也
可以调用redis中的指令。
Lua脚本的使用场景:
- 需要原子性地执行多个命令
- 需要中间值来组合后面的命令
pipelining管道技术
什么事pipelining?
redis的请求:
客户端发送一个请求给服务端----->客户端等待服务器响应—>服务器收到请
求并处理---->返回客户端
客户端到服务端是需要时间的,包括网络传输、建立连接,这个时间叫RTT时间(Round Trip Time)。为了避免多次的RTT时间,Redis有了管道技术。
使用场景
- 在客户端下次写之前不需要获取读的场景的大数据量并发写入的场景。
- 对性能有要求
- 管道里的命令不具备原子性,只能保证某个客户端发送的指令是顺序执 行的,但是多客户端的命令会交叉执行
- 不需要以上一个指令的返回结果来判断后续的逻辑
订阅与发布
订阅
subscribe指令
127.0.0.1:6379> subscribe huihui huihui1
Reading messages… (press Ctrl-C to quit)
- “subscribe”
- “huihui”
- (integer) 1
- “subscribe”
- “huihui1”
- (integer) 2
也可以模糊订阅 psubscribe
代表0个或者多个字符 ?代表1个字符
127.0.0.1:6379> psubscribe h j*
Reading messages… (press Ctrl-C to quit)
- “psubscribe”
- “h*”
- (integer) 1
- “psubscribe”
- “j*”
- (integer) 2
- “pmessage”
- “h*”
- “huihui”
- “message”
发布
192.168.8.129:6379> publish huihui message
(integer) 2 //接收消息的客户端的数量
我们消费端得到消息
127.0.0.1:6379> subscribe huihui huihui1
Reading messages… (press Ctrl-C to quit)
- “subscribe”
- “huihui”
- (integer) 1
- “subscribe”
- “huihui1”
- (integer) 2
- “message”
- “huihui”
- “message”
Redis实现分布式锁
什么是分布式锁?
synchronize、Lock接口等都是单机锁,分布式系统中的分布式锁应该独立于我们的业务服务,如用redis中间件实现。
分布式锁的特性:
- 只能有一个线程访问互斥资源
- 其他线程要么等待要么报错
Redis实现分布式锁思路
exist+set+expire
判断key是否存在,存在则说明有人持锁,自旋等待。否则set锁的值,并加上过期时间,过期时间是给锁加个超时时间,避免死锁。释放锁的时候,把这个key删除掉。
上面的思路仍然有问题:
- 几个命令不能保证原子性,因此需要lua脚本。
- 如果锁30秒过期,但是业务未执行完,锁失效,其他线程进入,导致加锁失败。需要看门狗续过期时间。
- 做成可重入锁,非重入锁如果遇到自己等自己释放,容易死锁。
Redission实现的分布式锁
Redission分布式锁的流程图
- 分布式锁中存的是hash类型,根据key(大key)获取锁。
- hash对象中有个字段UUID+线程ID,值是这个线程的重入次数。
- waitTime是获取锁时的等待时间,超过这个时间则获取锁失败。
- releaseTime是锁的释放时间。
- 锁到时间了,但是但是业务没执行完了,需要看门狗机制,看门狗用的是时间轮。
- 通过发布订阅,释放锁时,别人可以感应到。解锁时发布消息。
- 时间轮:HahsedWheelTimer,异步延时执行任务的工具类。
Redis扩容机制
数据机构图
Redis发生Hash冲突为什么要头插?而HashMap为什么1.8之后要尾插?
- 因为头插在并发情况下会形成死链 而Redis是单线程执行,不会存在并发
问题,也不会形成死链表。 - 而头插的性能比尾插的性能高很多,尾插必须找到链表的最后一个位置,
而头插只需要加到数据下标,并且把next指向之前的第一个数据。
为什么需要扩容?
因为不扩容,查询速度就是接近链表的速度,是O(n)。链表过长,遍历性能差。
什么时候扩容?
- 当没有fork子进程在进行RDB或者AOF持久化(内存的数据保存到磁盘防
止丢失,后面详细讲解)时,ht[0]的used大于等于size时,触发扩容 - 如果有子进程在进行RDB或者AOF时,ht[0]的used大于等于size的5倍的
时候,会触发扩容 。
怎么扩容
- 当满足我扩容条件,触发扩容时,判断是否在扩容,如果在扩容,或者
扩容的大小跟我现在的ht[0].size一样,这次扩容不做。 - new一个新的dictht,大小为ht[0].used * 2(但是必须向上2的幂,比
如6 ,那么大小为8) ,并且ht[1]=新创建的dictht。 - 我们有个更大的table了,但是需要把数据迁移到ht[1].table ,所以将
dict的rehashidx(数据迁移的偏移量)赋值为0 ,代表可以进行数据迁
移了,也就是可以rehash了。 - 等待数据迁移完成,数据不会马上迁移,而是采用渐进式rehash,慢慢
的把数据迁移到ht[1],等下我们讲 - 当数据迁移完成,ht[0].table=ht[1] ,ht[1] .table = NULL、ht[1] .size
= 0、ht[1] .sizemask = 0、 ht[1] .used = 0; - 把dict的rehashidex=-1
怎么进行数据迁移?
采用渐进式迁移
何时迁移:
1.每次进行key的crud操作都会进行一个hash桶的数据迁移
2.定时任务,进行部分数据迁移
Redis持久化
为什么需要持久化?
redis是基于内存的,数据存储在内存中。如果断电或者关机,数据就会丢失,因此需要持久化。
持久化的方式有哪些?
- RDB:RDB是redis默认的持久化方案,RDB快照(Redis DataBase),当满足一定条件时,会把内存中的数据写入磁盘,生成一个快照文件dump.rdb(默认)。
- AOF:由于RDB的数据可靠性低,所以redis又提供了另外一种持久化方案,Append Only File,简称AOF。AOF默认是关闭的,以追加文件的形式,每次更改的命令都会追加到AOF文件中。
RDB
RDB默认开启,默认生成的快照文件时dump.rdb,文件名字和路径可以通过下面形式配置。
# The filename where to dump the DB
dbfilename dump.rdb //dunmp.rdb文件名
dir ./ //文件路径
什么时候触发RDB呢?
自动触发
- 配置触发
save 900 1 900s检查一次,至少有1个key被修改就触发
save 300 10 300s检查一次,至少有10个key被修改就触发
save 60 10000 60s检查一次,至少有10000个key被修改
- shutdown正常关闭
任何组件,再正常关闭的时候,都会去完成该完成的事情,比如Mysql
中的Redolog持久化 正常关闭的时候也会去持久化。 - flushall指令触发
数据清空指令会触发RDB操作,并且是触发一个空的RDB文件,所以,
如果在没有开启其他的持久化的时候,flushall是可以删库跑路的,在
生产环境慎用。
手动触发
- save 主线程去进行备份,备份期间不会去处理其他的指令,其他指令
必须等待 - bgsave 子线程去进行备份,其他指令正常执行。
备份流程
- 新起子线程,子线程会将当前Redis的数据写入一个临时文件。
- 当临时文件写完成后,会替换旧的RDB文件。
- 当Redis启动的时候,如果只开启了RDB持久化,会从RDB文件中加载数
据。
RDB的优势与不足
优势(快)
- 是个非常紧凑型的文件,非常适合备份与灾难恢复。
- 最大限度的提升了性能,会fork一个子进程,父进程永远不会产于磁盘
IO或者类似操作。 - 更快的重启。
劣势
- 数据安全性不是很高,因为是根据配置的时间来备份,假如每5分钟备份
一次,也会有5分钟数据的丢失 - 经常fork子进程,所以比较耗CPU,对CPU不是很友好。
AOF
由于RDB的数据可靠性非常低,所以Redis又提供了另外一种持久化方案:
Append Only File 简称:AOF
AOF默认是关闭的,你可以再配置文件中进行开启:
appendonly no //默认关闭,可以进行开启,yes 是开启
# The name of the append only file (default:
"appendonly.aof")
appendfilename "appendonly.aof" //AOF文件名
追加文件,即每次更改的命令都会附加到我的AOF文件中。
AOF同步机制(什么时候写AOF文件)
AOF,会记录每个写的操作,那么问题来了?我难道每次操作命令又得跟磁
盘交互呢?不跟mysql一样每次需要跟磁盘交互呢?
当然不行,所以,redis支持几种策略,由你们自己来决定要不要每次都跟
磁盘交互。
# appendfsync always 表示每次写入都执行fsync(刷新)函数 性能会
非常非常慢 但是非常安全
appendfsync everysec 每秒执行一次fsync函数 可能丢失1s的数据。(默认)
# appendfsync no 由操作系统保证数据同步到磁盘,速度最快 你的数
据只需要交给操作系统就行
AOF重写机制
由于AOF是追加的形式,所以文件会越来越大,越大的话,数据加载越慢。
所以我们需要对AOF文件进行重写。
何为重写
比如 我们的incr指令,假如我们incr了100次,现在数据是100,但是我们
的aof文件中会有100条incr指令,但是我们发现这个100条指令用处不大,
假如我们能把最新的内存里的数据保存下来的话。
所以,重写就是做了这么一件事情,把当前内存的数据重写下来,然后把
之前的追加的文件删除。
重写流程
在Redis7之前:
- Redis fork一个子进程,在一个临时文件中写入新的AOF (当前内存的
数据生成的新的AOF) - 那么在写入新的AOF的时候,主进程还会有指令进入,那么主进程会在
内存缓存区中累计新的指令 (但是同时也会写在旧的AOF文件中,就算
重写失败,也不会导致AOF损坏或者数据丢失) - 如果子进程重写完成,父进程会收到完成信号,并且把内存缓存中的指
令追加到新的AOF文件中 - 替换旧的AOF文件 ,并且将新的指令附加到重写好的AOF文件中。
何时重写
配置文件redis.conf
# 重写触发机制
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb 就算达到了第一个百分比的大小,也
必须大于 64M
举例说明上述配置的意义
在 aof 文件小于64mb的时候不进行重写,当到达64mb的时候,就重写一
次。重写后的 aof 文件可能是40mb。上面配置了auto-aof-rewritepercentag
为100,即 aof 文件到了80mb的时候,进行重写。
AOF的优势与不足
优势
1.安全性高,就算默认的持久化同步机制,也最多只会导致1s丢失。
2.AOF由于某些原因,比如磁盘满了等导致追加失败,也能通过redischeck-
aof 工具来修复
3.格式都是追加的日志,所以可读性更高
劣势
- 数据集一般比RDB大
- 持久化跟数据加载比RDB更慢
- 在7.0之前,重写的时候,因为重写的时候,新的指令会缓存在内存
区,所以会导致大量的内存使用 - 并且重写期间,会跟磁盘进行2次IO,一个是写入老的AOF文件,一个
是写入新的AOF文件
Redis集群
主从
为什么要有主从?
- 故障恢复 主挂了或者数据丢失了,我从还会有数据冗余
- 负载均衡,流量分发 我们可以主写,从库读,减少单实例的读写压力
- 高可用 我们等下讲的集群 等等,都是基于主从去实现的。
主从数据同步
info replication
我们发现,我们的信息中,有个slave_read_only,如果是1 代表只读,也
就是默认,当然也可以配置成可写,但是写的数据不会同步到主库,需要
自己去保证数据一致性,可能从库写的数据会被主库的数据操作覆盖。出
现在从库set一个值后,获取到的是主库覆盖的数据。
从库如何从主库同步数据的?
建立连接
当首次成为主节点的从节点时,执行replicaof ip port命令的时候就会保存
主服务器的IP与端口
并且与主服务器建立连接,接收主节点返回的命令
判断主节点是否有密码,如果有,进行权限校验
保证主从之间保存了各自的信息,并正常连接
slave发起同步master数据指令
在slave的serverCron方法调用replicationCron方法,里面会发起跟
master的数据同步。
全量同步
-
master服务器收到slave的命令后(psync),判断slave传给我的
master_replid 是否跟我的replid一致,如果不一致或者传的是个空
的,那么就需要全量同步。
备注:4.0之前,用的是runId,但是runId每次实例重启时都会改变,会导致主从切换或者重启的时候,都需要全量同步,所以4.0之后采用replid。
replid有master_replid跟master_replid2,当主从切换的时候,我升级
为master的slave会继承之前master的replid,所以不需要每次主从切
换,都触发全量同步。同时slave会把master的master_replid持久化到磁盘。 -
slave首次关联master,从主同步数据,slave肯定是没有主的replid,所以需要进行全量同步。
-
进行全量同步
- master开始执行bgsave,生成一个RDB文件,并且把RDB文件传输
给我们的slave,同时把master的replid以及offerset(master的数
据进度,处理完命令后,都会写入自身的offerset)。 - slave接收到rdb文件后,清空slave自己内存中的数据,然后用rdb
来加载数据,这样保证了slave拿到的数据是master生成rdb时候的
最新数据。 - 由于master生成RDB文件是用的bgsave生成,所以,在生成文件的
时候,是可以接收新的指令的。那么这些指令,我们需要找一个地
方保存,等到slave加载完RDB文件以后要同步给slave。- 看在master生成rdb文件期间,会接收新的指令,这些新的指令会
保存在一个内存区间,这个内存区间就是replication_buffer。
我们可以通过以下方式设置replication_buffer的大小: - 这个空间不能太小,如果太小,为了数据安全,会关闭跟从库
的网络连接。再次连接得重新全量同步,但是问题还在,会导
致无限的在同步。
- 看在master生成rdb文件期间,会接收新的指令,这些新的指令会
- master开始执行bgsave,生成一个RDB文件,并且把RDB文件传输
client-output-buffer-limit replica 256mb 64mb 60
256mb 硬性限制,大于256M断开连接
64mb 60 软限制 超过64M 并且超过了60s还没进行同步
内存数据就会断开连接
增量同步
我们刚才有说过,每个节点都会保存数据的偏移量(从0开始,随着数据的
添加递增)。那么就有可能出现slave跟master网络断开一小会,然后发起
数据同步的场景,如图:
slave1由于网络断开,偏移量跟master差了3。
当slave1重新与master连接上时,不需要全量同步,只需要增量同步即可。
所以当master收到slave的指令时:
- 判断slave1传给我的master_replid 是否跟master的replid一致,由于之前已经连接过保过了master的replid,满足条件。
- 所以我希望只同步断开连接后没有同步到的数据,slave1只差了3的数据。那么我需要找到这个3的数据。所以master中有个另外的积压缓存(replication_backlog_buffer)。我们可以设置replication_backlog_buffer的大小:
# The bigger the replication backlog, the longer the
time the replica can be 复制积压越大,复制副本的时间越长
# disconnected and later be able to perform a partial
resynchronization. 已断开连接,稍后可以执行部分重新同步。
#
# The backlog is only allocated once there is at least
a replica connected. 只有在至少连接了一个副本后,才会分配积压工
作。
#
# repl-backlog-size 1mb
我们也不可能无限制的往里面写数据, replication_backlog_buffer的数据是会覆盖的。
3. 所以,我们slave1跟master相差的3条数据可能会被覆盖,如果覆盖了,触发全量,如果没有覆盖,即能找到相差的3条数据。增量即可。
指令同步
master写入的指令,异步同步至slave,如果有slave,写入
replication_backlog_buffer
哨兵
主从解决了负载、数据备份的问题,但是主节点挂掉后,从节点不能自动升级为主节点,必须手动升级。哨兵就能解决自动升级的问题。
哨兵提供了监测、通知、自动故障转移、配置提供等功能。
- 监测:监测redis各个实例是否正常工作。
- 通知:如果redis的实例出现问题,能够通知其他redis实例,以及sentinel。
- 自动故障转移:当master宕机,slave可以自动升级为master。
- 配置提供:sentinel可以提供redis的master地址,因此客户端只需要和sentinel连接即可。
哨兵故障转移的流程
首先,我们要知道,sentinel与sentinel之间,以及sentinel与master/slave之间都会进行通信。
发现故障
- 当我们某个sentinel 跟master通信时(默认1s发送ping),发现我在一定时间内(down-after-milliseconds) 没有收到master的有效的回复。这个时候这个sentinel就会人为master是不可用,但是有多个sentinel,它现在只有1个人觉得宕机了,这个时候不会触发故障转移,只会标记一个状态,这个状态就是SDOWN(Subjectively Down
condition ),也就是我们讲的主观下线。 - SDOWN时,不会触发故障转移,会去询问其他的sentinel,其他的sentinel是否能连上master,如果超过Quorum(法定人数)的sentinel都认为master不可用,都标记SDOWN状态,这个时候,master可能就真的是down了。那么就会将master标为ODOWN(Objectively Downcondition 客观下线)
进行故障转移(哪个sentinel做?)
- 当状态为ODWON的时候,我们就需要去触发故障转移,但是有这么多的sentinel,我们需要选一个sentinel去做故障转移这件事情,并且这个sentinel在做故障转移的时候,其他sentinel不能进行故障转移。
- 所以,我们需要选举一个sentinel来做这件事情:其中这个选举过程有2个因素。
- Quorum如果小于等于一半,那么必须超过半数的sentinel授权,你才能去做故障迁移,比如5台 sentinel,你配置的Quorum=2,那么选举的时候必须有3(5台的一半以上)人同意
- Quorum如果大于一半,那么必须Quorum的sentinel授权,故障迁移才能启动。
选举问题(哪个slave成为master?)
- 与master的断开连接时间
如果slave与主服务器断开的连接时间超过主服务器配置的超时时间(down-after-milliseconds)的十倍,被认为不适合成为master。直接去除资格 - 优先级
https://redis.io/docs/manual/sentinel/#replica-selection-and-priority
配置 replica-priority,replica-priority越小,优先级越高,但是配置为0的时候,永远没有资格升为master - 已复制的偏移量
比较slave的赋值的数据偏移量,数据最新的优先升级为master - Run ID (每个实例启动都会有个Run ID )
通过info server可以查看。
sentinel导致的数据一致性问题
官方建议配置至少3个sentinel。且redis的实例个数建议是奇数,因为4个和3个是一样的。
脑裂问题
脑裂问题其实就是我会有2个master,client会从不同的master写数据,从而在master恢复的时候数据丢失。所以只要发生分区容错行,不管多少个节点都可能出现脑裂。
脑裂问题解决方案
在Redis.cfg文件中有2个配置:
min-replicas-to-write 1 至少有1个从节点同步到我主节点的数
据,但是由于是异步同步,所以是最终一致性 不会确保有数据写入
min-replicas-max-lag 10 判断上面1个的延迟时间必须小于等于10s
Cluster
sentinel提供了比如监控、自动故障转移、客户端配置等,高可用方案,但是没有分片功能。Cluster是比sentinel更高级的集群方案。
什么是分片?
把数据分配到不同节点,如果某些节点数据异常,其他节点可以正常工作。类似于微服务的思想。
Cluster的特点?
- 多个节点之间的数据拆分,即数据分片。
- 某个节点故障,其他节点还能提供服务。
虚拟槽(hash slot)
如何进行分片?
分片就是把不同的数据分配到对应的实例中,相当于分表。
如何将数据分配的对应的实例中呢,一般是按实例数进行取模。但是有缺点,实例需要扩容或者缩容,全部数据需要迁移,因此引入虚拟槽的概念,也就是虚拟节点。
redisCluster中有16384个虚拟槽。
key与虚拟槽的关系?
slot = CRC16(key) & 16383
key跟虚拟槽的关系是根据key算出来的,后序不变。
虚拟槽与真实节点间的关系?
我们的虚拟槽会跟我们的真实节点进行对应,这个关系是可以变更的。可以设置某个实例节点包含哪些虚拟槽,但是至少得有3个主实例节点,主节点可以有自己的从节点。
假如,我们现在有6台实例,三主三从。 主跟槽的对应关系如下
master1 0-5460虚拟槽
master2 5461-10922虚拟槽
master3 10923-16383虚拟槽
我们可以知道,k1 的虚拟槽是10001 ,所以放入master3,k2的虚拟槽是10 放入master1 ,k3的虚拟槽为666,放入master2。从节点的数据全部来源于主,所以k1放入master3的从,以此对应。
我们可以通过指令查看当前节点的虚拟槽信息
127.0.0.1:6380> cluster nodes 查看当前节点的虚拟槽信息
每次的节点的扩容与缩容,只需要改变节点跟虚拟槽的关系即可,不需要全部变动。