【从零开始学Redis】高级篇--超全总结笔记
Redis从入门到精通,超全笔记专栏
Hello,大家好,我是姜来可期(全网同名),一名在读985转码大学生,一起学习,共同交流!!!
分布式缓存
分布式缓存是指将缓存数据分布存储在多个服务器上,解觉单台缓存服务器的弊端问题
假设你有一个大型网站,用户访问的速度非常快,你希望能够快速响应每个请求,避免每次都去查数据库。为了加速这个过程,你把一些经常访问的数据保存在内存中(就是缓存),这样下次请求的时候直接从缓存中获取,速度会非常快。
但是,随着数据量和用户量的增加,单个服务器的内存容量有限,无法存储所有数据,同时也容易出现单点故障。分布式缓存就像是将多个单点服务器连接在一起,每个服务器负责存储一部分数据,大家协同工作,确保可以存储更多的数据,避免一个服务器坏了就无法取到数据。
分布式缓存的特点
1. 数据分布:数据不是存储在单个缓存服务器上,而是分布在多个服务器上。
2. 高可用性:避免单台服务器宕机导致缓存不可用,分布式缓存通常设置多个副本(主从复制)。如果某个服务器宕机了,可以从其他副本中取数据,保证系统的高可用性。
3. 扩展性:随着数据量增加,可以不断地向集群中添加新的缓存服务器,系统的容量和性能都能随之扩展。
4. 负载均衡:通过负载均衡策略,分布式缓存能够分散访问压力,使得每个服务器处理的请求量合理且相对均匀,提高系统的整体性能。
主要的分布式缓存工具:
Redis集群:Redis支持通过集群模式,将数据分布到多个节点上,每个节点负责一部分数据,同时保证数据的一致性和高可用性。
应用场景:
1. 网站加速:通过缓存热点数据,减少数据库的访问频率,加速网站响应的速度。
举个例子:
假设你在一个电商网站上购买商品,商品的库存信息会存储在数据库中。每次查询库存时,直接查询数据库会导致响应慢,尤其是在高峰时段。于是你将库存数据缓存到 Redis 中,这样查询库存时首先会访问 Redis,如果没有数据,再访问数据库。随着访问量增大,Redis 集群会将缓存数据分布到多个服务器上,确保整个系统的高效和稳定。
总结,分布式缓存,就是将缓存分布到多个服务器上面,解决了单一服务器的容量、性能、故障等问题,使得系统可以高效地处理大量数据和请求。
Redis集群
基于Redis集群解决单机Redis存在的一些问题
1. 数据丢失问题——实现数据持久化
Redis是内存存储,服务重启可能会丢失数据
2. 并发能力问题——搭建主从集群,实现读写分离
单节点Redis并发能力虽然不错,但也无法满足像618这样的高并发场景
3. 故障恢复问题——利用Redis哨兵,实现健康检测和自动恢复
如果Redis宕机,那么服务就不可用,需要一种自动的故障恢复手段
4. 存储能力问题——搭建分片集群,利用插槽机制实现动态扩容
Redis存储是基于内存的,单节点能够存储的数据量很那满足海量数据的要求
1.Redis持久化
是为了解决数据丢失问题(Redis存储数据在内存,服务重启后先前的数据会丢失)
1.1.RDB持久化
RDB全称为——Redis Database Backup File (Redis数据备份文件),也会叫做 Redis数据快照。简单来说,就是把内存中存储的所有数据都记录到磁盘中。当Redis实例重启后,从磁盘中读取快照文件,恢复之前存储的数据。
快照文件称为RDB文件,默认是保存在当前的运行目录(在哪运行Redis,就保存到哪里)
1.1.1.执行时机
RDB持久化会在下面四种情况发生:
- 执行 save 命令
- 执行 bgsave 命令
- Redis 停机时
- 触发 RDB 条件时
1)save 命令
当执行下面的命令的时候,会将 Redis 中的所有数据持久化磁盘上(即创建一个RDB快照文件) 。但是在执行这个命令的时候,他会导致 Redis 阻塞其他所有的命令,也就是说,在执行 save 命令的时候,Redis 不会响应任何其他的请求,知道持久化的过程结束。
为什么save会阻塞?
1.持久化操作:save 命令会将 Redis 内存中的数据写入磁盘中(以 RDB 格式保存),这个过程会需要一些时间,尤其是当数据量非常大的时候。而且,写入磁盘的操作,本身就很耗时。
2. 阻塞:在这个过程中,Redis 不会处理其他客户端的请求,包括读写命令,所有的操作都会被“暂停”直到 SAVE
完成。因为 Redis 进程在执行 SAVE
时会占用大部分 CPU 和磁盘 I/O 资源,所以其他请求会被阻塞。
为什么不推荐使用 save 命令?
1. 性能问题:save 在执行的过程中 会导致Redis停止处理其他请求,这会导致显著的性能下降,造成延迟。
2. 同步阻塞:save 是一个 同步阻塞 操作,意味着它会等到持久化完成后才返回,这个过程会阻塞一段时间
何时使用 save 命令?
SAVE
命令在进行 数据迁移 或 故障恢复 时可能有用。比如你想要将当前 Redis 的数据保存到磁盘上以便之后恢复,或者你希望确保数据在某些特定情况下已经被写入磁盘。然而,通常情况下,Redis 推荐使用 异步的持久化方式(例如 BGSAVE
命令或者 AOF 持久化),它们可以在后台执行持久化操作,不会阻塞其他命令的执行。
2)bgsave 命令
下面的命令可以异步执行RDB:
是 Redis 中用于持久化数据的一种异步方式,和 SAVE
命令不同,BGSAVE
不会阻塞 Redis 主进程。它是通过开启一个 子进程 来执行 RDB 快照的生成过程,而 Redis 主进程则可以继续处理用户的请求。
bgsave命令的工作原理
1. 异步执行:当执行 BGSAVE
命令时,Redis 主进程会启动一个 子进程 来生成数据的 RDB 快照。这个子进程会负责将 Redis 内存中的数据写入磁盘,并且这个过程是独立于主进程的,不会影响主进程继续接收和响应客户端请求。
2. 主进程不好受影响:由于子进程负责持久化任务,所以主进程仍然可以处理用户的命令和请求。不会因持久化操作的阻塞而影响到系统的整体性能。
与 save 命令的对比:
SAVE
:同步执行,执行时会阻塞 Redis,直到数据写入磁盘完成,无法处理其他命令。BGSAVE
:异步执行,Redis 会创建一个子进程来执行 RDB 持久化操作,主进程可以继续处理请求,性能更高,适合生产环境。
3)停机时
redis停机会自动执行一次 save 命令,实现一次 RDB持久化。
4)触发 RDB 条件
Redis内部有触发RDB的机制,允许你根据特定条件自动创建数据的快照。你可以在 redis.conf
文件中配置这些条件,以决定何时执行 RDB 持久化操作。
在 redis.conf
文件中,save
配置项定义了触发 RDB 快照生成的条件。格式如下:
save <seconds> <changes>
# 900秒内,如果至少有1个key被修改,则执行bgsave
# 如果是save "" 则表示禁用RDB
save 900 1
# 300秒内,如果至少有10个key被修改,则执行bgsave
save 300 10
save 60 10000
除了触发条件以外,Redis还提供了一些其他的配置选项 来控制 RDB的行为:
# 是否压缩 ,建议不开启,虽然压缩体积会变小,减少磁盘占用空间,但是压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
虽然启用压缩可以减小 RDB 文件的体积,节省磁盘空间,但压缩和解压过程会消耗一定的 CPU 资源。因为磁盘相对便宜,通常建议关闭压缩(rdbcompression no
)。
RDB方案的默认配置是已经开启了的,但是他存在一些缺陷:如果 Redis崩溃或断电,可能会丢失最后一次快照之后的数据。因此,在高可用要求较高的场景中,会结合使用 AOF(Append-Only File)持久化机制来弥补 RDB 的不足。
1.1.2. RDB原理
执行 basave 命令,开始时,会 fork 主进程得到子进程,这个子进程共享主进程的内存数据。完成fork后,子进程会读取内存数据,并写入磁盘的RDB文件中。
fork 是一种在操作系统中用于创建子进程的系统调用。简单来说,就是主进程(父进程)调用 fork 后,操作系统会创建一个与主进程几乎完全相同的新进程,这个新进程叫做 子进程。
在fork调用之后,父进程和子进程各自独立执行。
通常,父进程在创建子进程后继续执行,而子进程可以执行一些不同的操作。在实际应用中,fork 通常用于进程并发、创建后台任务等场景。
fork采用的是 copy-on-write (写时复制)操作
1.当主进程访问读操作时,访问共享内存;
2. 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
3. 每一次只要有写,都会拷贝,避免这个读和写同时发生导致的一些问题
copy-on-write 技术
- 内存共享机制:在 Linux 系统中,进程不能直接操作物理内存,而是操作虚拟内存,物理内存和虚拟内存之间通过页表存在一个映射关系。当 fork 操作发生时,会复制一份页表,由于映射关系也被复制,所以子进程和主进程实际上共享同一块物理内存 ,这使得 fork 操作的开销相对较小。
- 读操作情况:当主进程执行读操作时,直接访问共享内存。从图中可以看到,主进程和子进程的读操作箭头都指向同一块物理内存中的数据(如数据 A 和数据 B),因为此时数据是只读状态,共享内存不会产生冲突。
- 写操作情况:当主进程执行写操作时,就会触发 copy - on - write 技术。系统会拷贝一份需要写入的数据(如图片 2 中的数据 B 副本),然后在这份拷贝的数据上执行写操作。这样做可以避免读和写同时发生导致的数据不一致等问题,保证了数据的一致性和操作的正确性。
展示了 bgsave 开始 fork 主进程得到子进程,子进程共享主进程内存数据的整体流程,以及 fork 时复制页表共享内存的原理,还说明了 Linux 系统中进程与内存的操作关系。
- 重点展示了主进程执行写操作时 copy - on - write 技术的具体过程,即拷贝数据副本并在副本上执行写操作,而子进程仍访问原来的共享数据。
1.1.3. 小结
RDB方式bgsave的基本流程?
-
fork主进程得到一个子进程,共享内存空间
-
子进程读取内存数据并写入新的RDB文件
-
用新RDB文件替换旧的RDB文件
RDB会在什么时候执行?save 60 1000代表什么含义?
-
默认是服务停止时
-
代表60秒内至少执行1000次修改则触发RDB
RDB的缺点?
-
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
-
fork子进程、压缩、写出RDB文件都比较耗时(因为这些操作比较耗时,所以你也不能把RDB执行间隔设置的太短)
1.2.AOF持久化
1.2.1.AOF原理
AOF全称为 Append Only File (追加文件)。Redis 处理的每一个命令都会被记录到 AOF文件中,可以看做是——命令记录文件。
1.2.2.AOF配置
AOF默认是关闭的,需要修改 reids.conf 配置文件 来开启 AOF:
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
AOF的命令记录的频率也可以通过redis.conf文件来配
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
三种策略的对比
我们可以查看一下 AOF 文件中记录的内容
1.2.3.AOF文件重写
因为AOF文件是记录命令,AOF 文件会比 RDB 文件大得多。而且 AOF 文件会记录对同一个 key 的多次写操作,但只有最后一次写操作才会有意义。
我们可以通过执行 barewriteaof 命令,让AOF文件执行一次重写,用最少的命令达到相同的效果,减少 AOF 文件的体积。
如图中所示,AOF 原本记录着三个 set 命令,但是set num 123 和 set num 666
都是对num的操作,第二次会覆盖第一次的值,因此第一个命令记录下来没有意义。
所以重写命令后,AOF文件内容就是:mset name jack num 666
,用最少的命令实现相同效果!
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
1.3.RDB与AOF对比
只能说各有优缺点。在实际开发中,针对不同的场景,往往会结合两者来使用。
2.Redis主从
搭建Redis主从集群,是为了解决 Redis 并发能力问题。
2.1.搭建主从架构
单节点的Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
1. 一个主节点,多个从节点。
2. 大部分应用场景式——读多写少,读写分离
共包含三个节点,一个主节点,两个从节点。
我们可以选择在同一台虚拟机中的不同端口上运行3个Redis实例,模拟主从集群。
具体的搭建流程可以参考其他资料,网上非常全面。
1. 实现主从节点的读写分离
2. 实现主从节点之间的数据同步机制。
假设有A、B两个Redis实例,如何让 B 作为 A 的slave节点?
在B节点执行命令:slaveof A的IP A的port
2.2.主从数据同步原理
2.2.1.全量同步
当主从节点之间第一次建立连接的时候,会执行全量同步,将master节点中的全部数据都拷贝给slave节点,流程如图所示。
数据同步原理
这里有一个问题,master如何得知salve是第一次来连接呢??
这里就会有几个概念,需要了解一下,可以作为判断依据:
- Replication ID : 简称 replid,是数据集的标记,如果 replid 一致则说明是同一个数据集。每个 master 都有一个唯一的 replid,master 的从节点 slave 都会继承 master 节点的 replid。
- offser :偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步的时候也会记录当前同步的 offset。如果 slave 的offset 小于 master 的offset,说明 slave 数据落后于 master, 需要更新。
因此,slave 做数据同步的时候,必须向 master 声明自己的 replication id 和 offset, master 才可以进行判断。
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,这是主从节点之间第一次建立连接,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
因此,master 判断一个节点是否是第一次建立连接进行同步,就是判断 replid 是否一致。
如图所示
完整的流程描述
- slave 节点请求增量同步
- master 节点判断 replid ,发现不一致,说明是第一次建立连接,拒绝增量同步,执行全量同步。
- master 将所有的内存数据生成 RDB, 然后发送 RDB 到 slave
- slave 清空本地数据,然后加载 master 的 RDB
- master 将 RDB 期间的命令都记录到 repl_baklog中,并且持续将 log 中的命令发送给 slave
- slave 执行接收到的命令,进一步保持与 master 之间的同步
2.2.2.增量同步
通过上面的学习,我们知道要完成全量同步,需要先做 RDB, 然后将整个 RDB 文件通过网络传输给 slave,这样子成本很高的。
因此,除了第一次建立连接数据同步的时候做的是全量同步,其他大多数时候 slave 与 master 都是做的增量同步。
那么,什么是增量同步??就是只更新 slave 与 master 存在差异的部分数据。如图所示,更加清晰:
从图中我们可以知道,master 会判断 replid 与自己的是否一致,如果一致,说明不是第一次,进行增量同步。然后,master 会到 repl_baklog 文件中去获取 slave 传过来的 offset 之后的命令,将 offset 之后的命令发送给 slave,然后 slave 执行这些命令,完成增量同步。
2.2.3.repl_backlog 原理
但是,master 是如何知道 slave 与自己的数据差异在哪里呢?
这个就要说到做全量同步的时候,有一个 repl_baklog 文件了。
这个文件是一个固定大小的数组,只不过这个数据是环形的,也就是说当角标到达数组末尾之后,就会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录 Redis 处理过的命令日志以及 offset,包括当前这个状态 master 的 offset,以及 slave 已经拷贝了的 offset。如图所示,非常形象。
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满。
此时此刻,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是 slave 已经同步了的数据,被覆盖了并没有什么影响。因为未同步的数据仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步、但是已经被覆盖了的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
2.3.主从同步优化
主从同步可以保证主从数据的一致性,这对与读写分离非常重要。
我们可以从以下几个方面来优化Redis主从集群:
-
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。 写RDB文件时,不往磁盘中写,直接通过网络发送给从节点,提高全量同步的性能
-
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO 提高全量同步的性能
-
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
-
限制一个master上的slave节点数量(减少master的压力),如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
主从从架构图:
2.4.小结
简述全量同步和增量同步之间的区别???
- 全量同步:master 将完整的内存数据生成 RDB 文件,然后发送 RDB 文件到 slave。后续的命令则记录到 repl_baklog 中,逐个发送给 slave。
- 增量同步:slave 提交自己的 offset 到 master,master 从 repl_baklog 中获取 offset 之后的命令发送给 slave 。
什么时候执行全量同步?
- slave 节点第一次连接 master 节点的时候
- slave 节点断开时间太久,repl_baklog 中的 offset 已经被覆盖了
什么时候执行增量同步?
- 能够在 repl_baklog 中找到 offset。
3.Redis哨兵
3.1.哨兵原理
3.1.1.集群结构和作用
哨兵的结构如图:
哨兵的作用如下:
- 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作。
- 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master (主从切换)。当故障实例恢复以后也以新的 master 为主
- 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息(主从状态切换以后的主从地址)推送给 Redis 的客户端。
3.1.2.集群监控原理
Sentinel 基于心跳机制检测服务状态,每隔 1s 向集群的每个实例发送 ping 命令:
- 主观下线:如果某 sentinel 节点发现某实例未在规定的时间内响应,则认为该实例主观下线
- 客观下线:若超过指定的数量(quorum)(在Redis配置文件中可以修改)的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。
3.1.3.集群故障恢复原理
一旦发现 master 故障,sentinel 需要在 slave 中选择一个作为新的 master,选择依据如下:
- 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
-
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
-
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
-
最后是判断slave节点的运行id大小,越小优先级越高。
当选出一个新的master后,该如何实现切换呢?
流程如下:
-
sentinel给备选的slave1节点发送 slaveof no one ,让该节点成为master
-
sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
-
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
3.1.4.小结
Sentinel的三个作用是什么?
-
监控
-
故障转移
-
通知
Sentinel如何判断一个redis实例是否健康?
-
每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
-
如果大多数sentinel都认为实例主观下线,则判定服务下线
故障转移步骤有哪些?
-
首先选定一个slave作为新的master,执行slaveof no one
-
然后让所有节点都执行slaveof 新master
-
修改故障节点配置,添加slaveof 新master
3.2.搭建哨兵集群
具体的搭建流程参考其他资料。
3.3.RedisTemplate的哨兵模式
在 Sentinel 集群监督下的 Redis 主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须能够感知到这种变化,及时更新连接信息。Spring 的 RedisTemplate 底层利用 lettuce 实现节点的感知和自动切换——可以自动实现主从节点的切换
下面,我们可以通过一个 demo 来实现 RedisTemplate 集成哨兵机制
导入Demo工程
引入依赖
在项目的pom文件中引入 Redis的 starter 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置 Redis 地址
然后在项目的配置文件 application.yml 中指定的 redis 的 sentinel 相关信息
spring:
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群地址
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003
配置读写分离
在项目的启动类中,添加一个新的bean:
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
LettuceClientConfigurationBuilderCustomizer:
LettuceClientConfigurationBuilderCustomizer
是一个接口,通常用于定制和修改 Lettuce 客户端的配置。- Lettuce 是一个流行的 Redis 客户端库,在 Spring 中用来与 Redis 交互。
LettuceClientConfigurationBuilderCustomizer
允许你定制 Lettuce 客户端的配置,例如设置连接池、超时、读写策略等。clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED)
- 这段代码传递给
LettuceClientConfigurationBuilderCustomizer
接口的实现是一个 Lambda 表达式。clientConfigurationBuilder
是一个配置构建器对象,允许你通过它来设置 Lettuce 客户端的各种配置选项。readFrom(ReadFrom.REPLICA_PREFERRED)
是配置客户端的读策略。ReadFrom
是一个枚举,表示读取 Redis 数据的策略:
REPLICA_PREFERRED
表示客户端会优先从 Redis 的从节点(Replica)读取数据,而不是直接从主节点(Master)读取。这样可以减少主节点的负载,但仍然保证读取到的数据是可用的(主节点和从节点的数据是同步的,尽管可能会有轻微延迟)。整体作用:
- 这段代码的作用是定义一个
@Bean
方法,返回一个LettuceClientConfigurationBuilderCustomizer
实例,该实例通过 Lambda 配置了 Lettuce 客户端的读取策略,使其优先从 Redis 从节点读取数据。这种配置可以在 Redis 主从结构下分担主节点的负载,提升系统性能。总结:
- 这段代码配置了 Lettuce 客户端的读策略,指定优先从 Redis 从节点读取数据。通过将这个定制器注册为 Spring Bean,可以在应用中轻松配置 Lettuce 客户端行为。
这个bean中配置的就是读写策略,ReadFrom是配置Redis的读取策略,是一个枚举,包括四种:
-
MASTER:从主节点读取
-
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
-
REPLICA:从slave(replica)节点读取
-
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
4.Redis分片集群
4.1.搭建分片集群
主从和哨兵可以解决高并发读、高可用的问题。但是目前依然存在两个问题没有解决:
- 海量数据的存储问题
- 高并发写的问题
使用分片集群可以解决上述的两个问题,分片集群的特征如下:
- 集群中有多个 master 每个 master 保存不同的数据。
使用分片集群可以解决上述的问题,如图所示:
分片集群的特征:
- 集群中有多个 master , 每个 master 保存不同的数据
- 每个 master 都可以有多个 slave 节点
- master 之间通过 ping 检测彼此健康状态
- 客户端请求可以访问集群中的任意节点,但是最终都会被转发到正确节点(最终都会被路由到正确的结点上)
4.2.散列插槽
4.2.1.插槽原理
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
-
7001——分配的是 0 —5460这些插槽
-
7002——分配的是5461—10922这些插槽
-
7003——分配的是10923—16383这些插槽
set key value时,数据存储到哪个master??其实是与插槽值有关的。。
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
-
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
-
key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到7003节点。
到了7003后,执行get num
时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点
-
将数据与插槽绑定,如果节点宕机,那么将插槽再与一个活着的节点绑定即可,插槽上的数据又不会丢失
-
数据跟着插槽走
4.2.2.小结
Redis如何判断某个key应该在哪个实例?
-
将16384个插槽分配到不同的实例
-
根据key的有效部分计算哈希值,对16384取余
-
余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
-
这一类数据使用相同的有效部分,例如key都以{typeId}为前缀
4.3.集群伸缩
-
集群伸缩 —— 集群能够动态地 增加节点或者是移除节点
redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:
比如,添加节点的命令:
4.3.1.需求分析
需求:向集群中添加一个新的master节点,并向其中存储 num = 10
-
启动一个新的redis实例,端口为7004
-
添加7004到之前的集群,并作为一个master节点
-
给7004节点分配插槽,使得num这个key可以存储到7004实例
这里需要两个新的功能:
-
添加一个节点到集群中
-
将部分插槽分配到新节点
4.3.2.创建新的 Redis 实例
4.3.3.添加新节点到redis中
4.3.4.转移插槽
我们要将num存储到7004节点,因此需要先看看num的插槽是多少:
如上图所示,num的插槽为2765.
我们可以将0~3000的插槽从7001转移到7004,命令格式如下:
具体命令如下:
建立连接:
得到下面的反馈:
询问要移动多少个插槽,我们计划是3000个:
新的问题来了:
哪个node来接收这些插槽??
显然是7004,那么7004节点的id是多少呢?
复制这个id,然后拷贝到刚才的控制台后:
这里询问,你的插槽是从哪里移动过来的?
-
all:代表全部,也就是三个节点各转移一部分
-
具体的id:目标节点的id
-
done:没有了
这里我们要从7001获取,因此填写7001的id:
填完后,点击done,这样插槽转移就准备好了:
确认要转移吗?输入yes:
然后,通过命令查看结果:
可以看到:
目的达成。
4.4.故障转移
集群初始状态是这样的:
其中7001、7002、7003都是master,我们计划让7002宕机。
4.4.1.自动故障转移
当集群中有一个master宕机会发生什么呢?
直接停止一个redis实例,例如7002:
redis-cli -p 7002 shutdown
1)首先是该实例与其它实例失去连接
2)然后是疑似宕机:
3)最后是确定下线,自动提升一个slave为新的master:
4)当7002再次启动,就会变为一个slave节点了:
4.4.2.手动故障转移
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
这种failover命令可以指定三种模式:
-
缺省:默认的流程,如图1~6歩
-
force:省略了对offset的一致性校验
-
takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
案例需求:在7002这个slave节点执行手动故障转移,重新夺回master地位
步骤如下:
1)利用redis-cli连接7002这个节点
2)执行cluster failover命令
如图:
效果:
4.5.RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
1)引入redis的starter依赖
2)配置分片集群地址
3)配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下
spring:
redis:
cluster:
nodes:
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003