注:本篇部分知识点来源于【Redis深度历险-核心原理和应用实践】书籍当中!
在了解Redis集群之前尽量先了解清楚Redis的主从复制和哨兵机制。
https://blog.csdn.net/weixin_43888891/article/details/131039418
Redis的主从复制主要有两个作用:
哨兵机制:
虽然主从+哨兵采用了多节点,但是他们存在的目的主要是解决容灾问题,而并非性能问题。
那目前Redis性能存在什么问题?
互联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。
垂直扩展:提升单机处理能力。增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G;
水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计,以及互联网公司架构各层常见的水平扩展实践,是本文重点讨论的内容。
在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。
单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。
Redis性能问题如何解决?
正是在这样的大数据高并发的需求之下,Redis 集群方案应运而生。redis集群是对redis的水平扩容,即启动N个redis节点,将整个数据分布存储在这个N个节点中,每个节点存储总数据的1/N。
如下图:由3台master和3台slave组成的redis集群,每台master承接客户端三分之一请求和写入的数据,当master挂掉后,slave会自动替代master,做到高可用。
提到集群必然离不开数据分片,数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。那么我们如何实现数据分片?
注意:数据分片不仅仅涉及到数据的存储,还涉及到数据的提取,假设有三台redis节点集群,分别是a、b、c,然后我set test value 将test的key value存储到了三个节点其中一个节点,当我get的时候我还要保证他能找到当时存储的节点,只有找到节点才能拿到当时存储的数据。
关于分片有下面三种方案:
在早期搭建集群很多都是采用代理分片,因为当时redis cluster发布得比较晚(2015年才发布正式版 ),各大厂等不及了,陆陆续续开发了自己的redis数据分片集群模式,比如:Twemproxy、Codis等。都属于当时的产物,现在codis已经彻底不更新了,而Twemproxy更新速度非常缓慢。所以基本上现在搭建集群都是使用官方的Redis Cluster!
twemproxy源码:https://github.com/twitter/twemproxy
Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis或memcached服务器,再原路返回。
Twemproxy 搭建 redis 集群有以下的优势:
但它的痛点就是无法在线扩容、缩容,这就导致运维非常不方便,而且也没有友好的运维UI可以使用。Codis就是因为在这种背景下才衍生出来的。
cocdis源码:https://github.com/CodisLabs/codis
中文使用教程:https://github.com/CodisLabs/codis/blob/master/doc/tutorial_zh.md
cocdis已经近五年没有过更新迭代了,但是在早期是相当的火的,就好比当时的Hibernate!
Codis 是 Redis 集群方案之一,令我们感到骄傲的是,它是中国人开发并开源的,来自前豌豆荚中间件团队。绝大多数国内的开源项目都不怎么靠谱,但是 Codis 非常靠谱。有了Codis 技术积累之后,项目「突头人」刘奇又开发出来中国人自己的开源分布式数据库 ——TiDB,可以说 6 到飞起。
Codis 使用 Go 语言
开发,它是一个代理中间件,它和 Redis 一样也使用 Redis 协议对外提供服务,当客户端向 Codis 发送指令时,Codis 负责将指令转发到后面的 Redis 实例来执行,并将返回结果再转回给客户端
。客户端操纵 Codis 同操纵 Redis 几乎没有区别,还是可以使用相同的客户端 SDK,不需要任何变化。
Codis 上挂接的所有 Redis 实例构成一个 Redis 集群,当集群空间不足时,可以通过动态增加 Redis 实例来实现扩容需求
。
因为 Codis 是无状态的
,它只是一个转发代理中间件,这意味着我们可以启动多个Codis 实例,供客户端使用,每个 Codis 节点都是对等的。因为单个 Codis 代理能支撑的QPS 比较有限,通过启动多个 Codis 代理可以显著增加整体的 QPS 需求,还能起到容灾功能,挂掉一个 Codis 代理没关系,还有很多 Codis 代理可以继续服务。
无状态:无状态指的是对于请求方的每个请求,接收方都当这次请求是第一次请求。为什么叫做无状态呢?因为对于请求方而言,每次请求时,接收方就像是失忆了一样,并不会依赖请求方以往的请求所生成的数据作回应。也就是说,就像是接收方没有保存请求方的状态(数据)一样,所以叫无状态。常用的HTTP就是无状态协议!
Codis 要负责将特定的 key 转发到特定的 Redis 实例,那么这种对应关系 Codis 是如何管理的呢?
Codis 将所有的 key 默认划分为 1024 个槽位(slot)
,它首先对客户端传过来的 key 进行 crc32 运算计算哈希值,再将 hash 后的整数值对 1024 这个整数进行取模得到一个余数,这个余数就是对应 key 的槽位。
每个槽位都会唯一映射到后面的多个 Redis 实例之一,Codis 会在内存维护槽位和Redis 实例的映射关系。这样有了上面 key 对应的槽位,那么它应该转发到哪个 Redis 实例就很明确了。
槽位数量默认是 1024,它是可以配置的,如果集群节点比较多,建议将这个数值配置大一些,比如 2048、4096。
如果 Codis 的槽位映射关系只存储在内存里,那么不同的 Codis 实例之间的槽位关系就无法得到同步。所以 Codis 还需要一个分布式配置存储数据库专门用来持久化槽位关系。Codis 开始使用 ZooKeeper,后来连 etcd 也一块支持了。
Codis 将槽位关系存储在 zk 中,并且提供了一个 Dashboard 可以用来观察和修改槽位关系
,当槽位关系变化时,Codis Proxy 会监听到变化并重新同步槽位关系,从而实现多个Codis Proxy 之间共享相同的槽位关系配置。
刚开始 Codis 后端只有一个 Redis 实例,1024 个槽位全部指向同一个 Redis。然后一个 Redis 实例内存不够了,所以又加了一个 Redis 实例。这时候需要对槽位关系进行调整,将一半的槽位划分到新的节点。这意味着需要对这一半的槽位对应的所有 key 进行迁移,迁移到新的 Redis 实例。
那 Codis 如果找到槽位对应的所有 key 呢?
Codis 对 Redis 进行了改造,增加了 SLOTSSCAN
指令,可以遍历指定 slot(槽位) 下所有的key。Codis 通过 SLOTSSCAN
扫描出待迁移槽位的所有的 key,然后挨个迁移每个 key 到新的 Redis 节点。
在迁移过程中,Codis 还是会接收到新的请求打在当前正在迁移的槽位上,因为当前槽位的数据同时存在于新旧两个槽位中,Codis 如何判断该将请求转发到后面的哪个具体实例呢?
Codis 无法判定迁移过程中的 key 究竟在哪个实例中,所以它采用了另一种完全不同的思路。当 Codis 接收到位于正在迁移槽位中的 key 后,会立即强制对当前的单个 key 进行迁移,迁移完成后,再将请求转发到新的 Redis 实例。
我们知道 Redis 支持的所有 Scan 指令都是无法避免重复的,同样 Codis 自定义的SLOTSSCAN 也是一样,但是这并不会影响迁移。因为单个 key 被迁移一次后,在旧实例中它就彻底被删除了,也就不可能会再次被扫描出来了。
自动均衡
Redis 新增实例,手工均衡 slots 太繁琐,所以 Codis 提供了自动均衡功能。自动均衡会在系统比较空闲的时候观察每个 Redis 实例对应的 Slots 数量,如果不平衡,就会自动进行迁移。
缺点:
优点:
后台管理的界面非常友好,使用了最新的 BootStrap 前端框架。比较酷炫的是可以看到实时的 QPS 波动曲线。
同时还支持服务器集群管理功能,可以增加分组、增加节点、执行自动均衡等指令,还可以直接查看所有 slot 的状态,每个 slot 被分配到哪个 Redis 实例。
mget 指令用于批量获取多个 key 的值,这些 key 可能会分布在多个 Redis 实例中。Codis 的策略是将 key 按照所分配的实例打散分组,然后依次对每个实例调用 mget 方法,最后将结果汇总为一个,再返回给客户端。
官网教程:https://redis.com.cn/topics/cluster-tutorial.html
RedisCluster 是 Redis 的亲儿子,它是 Redis 作者自己提供的 Redis 集群化方案。
相对于 Codis 的不同,它是去中心化的,如图所示,该集群有三个 Redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。这三个节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议相互交互集群信息。
Redis Cluster 将所有数据划分为 16384 的 slots,它比 Codis 的 1024 个槽划分的更为精细,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中,它不像 Codis,它不需要另外的分布式存储来存储节点槽位信息。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息。这样当客户端要查找某个 key 时,可以直接定位到目标节点。这点不同于 Codis,Codis 需要通过 Proxy 来定位目标节点,RedisCluster 是直接定位
。客户端为了可以直接定位某个具体的 key 所在的节点,它就需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
另外,RedisCluster 的每个节点会将集群的配置信息持久化到配置文件中,所以必须确保配置文件是可写的,而且尽量不要依靠人工修改配置文件。
调整槽位
Redis Cluster 提供了工具 redis-trib
可以让运维人员手动调整槽位的分配情况,它使用Ruby 语言进行开发,通过组合各种原生的 Redis Cluster 指令来实现。
这点 Codis 做的更加人性化,它不但提供了 UI 界面可以让我们方便的迁移,还提供了自动化平衡槽位工具,无需人工干预就可以均衡集群负载。不过 Redis 官方向来的策略就是提供最小可用的工具,其它都交由社区完成。
端口
每个Redis集群节点需要打开两个TCP连接。端口6379提供给客户端连接,外加上一个端口16379,记起来也比较容易,在6379的基础上加10000。
端口16379提供给集群总线使用,总线用来集群节点间通信,使用的是二进制协议。集群总线的作用:失败检测、配置升级、故障转移授权等。客户端只能连接6379端口,不能连接端口16379。防火墙需要确保打开这两个端口,否则集群节点之间不能通信。
Cluster 默认会对 key 值使用 crc32 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
Cluster 还允许用户强制某个 key 挂在特定槽位上,通过在 key 字符串里面嵌入 tag 标记,这就可以强制 key 所挂在的槽位等于 tag 所在的槽位。
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。关于这一点在后面会重点进行演示!
Redis集群不能保证强一致性。一些已经向客户端确认写成功的操作,会在某些不确定的情况下丢失。
产生写操作丢失的第一个原因,是因为主从节点之间使用了异步的方式来同步数据。
一个写操作是这样一个流程:
从上面的流程可以看出来,主节点B并没有等从节点B1,B2,B3写完之后再回复客户端这次操作的结果。所以,如果主节点B在通知客户端写操作成功之后,但同步给从节点之前,主节点B故障了,其中一个没有收到该写操作的从节点会晋升成主节点,该写操作就这样永远丢失了。
如果真的需要,Redis集群支持同步复制的方式,通过WAIT 指令来实现,这可以让丢失写操作的可能性降到很低。但就算使用了同步复制的方式,Redis集群依然不是强一致性的,在某些复杂的情况下,比如从节点在与主节点失去连接之后被选为主节点,不一致性还是会发生。
下面我们来配置一个3主3从的集群,每个主下面挂一个slave,master挂掉后,slave会被提升为master。
为了方便,我们在一台Linux机器上进行模拟,通过端口来区分6个不同的节点,配置信息如下
(1)停止redis
假如linux当中安装的有redis,可以先全部停掉,停止所有的redis命令:
ps -ef | grep redis | awk -F" " '{print $2;}' | xargs kill -9
(2)下载redis,然后将redis上传到opt目录
如果没有安装wget请先安装:
cd /opt
yum -y install wget
wget https://download.redis.io/redis-stable.tar.gz
(3)安装redis
要编译 Redis,首先是 tar解压,切换到根目录,然后运行make:
tar -xzvf redis-stable.tar.gz
cd redis-stable
make install
假如make install报如下异常,直接使用make install MALLOC=libc
安装过后你会在src目录中找到几个 Redis 二进制文件,包括:
执行下面命令创建 /opt/cluster 目录,本次所有操作,均在 cluster 目录进行。
(1)创建cluster目录并且将redis.conf复制到cluster目录
log目录专门来存储节点产生的日志
mkdir /opt/redis-stable/cluster
mkdir /opt/redis-stable/cluster/log
cd /opt/redis-stable/cluster
cp /opt/redis-stable/redis.conf /opt/redis-stable/cluster/
现在我们需要创建6个配置文件,6个配置文件只有如下几行是不一样的:
port 6379
# 持久化文件
dbfilename dump_6379.rdb
pidfile /var/run/redis_6379.pid
logfile "/opt/redis-stable/cluster/log/6379.log"
# 设置节点配置文件
cluster-config-file node-6379.conf
(2)创建master1的配置文件:redis-6379.conf
touch redis-6379.conf
vi redis-6379.conf
编辑为如下内容:
bind 0.0.0.0:表示允许所有ip地址访问,默认是127.0.0.1,只允许当前主机连接,假如这里要是配置成当前机器的ip或者是127.0.0.1,那么可能会导致在windows当中无法连接Redis。
在/opt/redis-stable/cluster/目录创建 redis-6379.conf 文件,内容如下
include /opt/redis-stable/cluster/redis.conf
# 当我们采用yes时,redis会在后台运行,此时redis将一直运行,除非手动kill该进程。
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6379
dbfilename dump_6379.rdb
pidfile /var/run/redis_6379.pid
logfile "/opt/redis-stable/cluster/log/6379.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6379.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(3)创建master2的配置文件:redis-6380.conf
include /opt/redis-stable/cluster/redis.conf
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6380
dbfilename dump_6380.rdb
pidfile /var/run/redis_6380.pid
logfile "/opt/redis-stable/cluster/log/6380.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6380.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(4)创建master3的配置文件:redis-6381.conf
include /opt/redis-stable/cluster/redis.conf
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6381
dbfilename dump_6381.rdb
pidfile /var/run/redis_6381.pid
logfile "/opt/redis-stable/cluster/log/6381.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6381.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(5)创建slave1的配置文件:redis-6389.conf
include /opt/redis-stable/cluster/redis.conf
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6389
dbfilename dump_6389.rdb
pidfile /var/run/redis_6389.pid
logfile "/opt/redis-stable/cluster/log/6389.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6389.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(6)创建slave2的配置文件:redis-6390.conf
include /opt/redis-stable/cluster/redis.conf
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6390
dbfilename dump_6390.rdb
pidfile /var/run/redis_6390.pid
logfile "/opt/redis-stable/cluster/log/6390.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6390.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(7)创建slave3的配置文件:redis-6391.conf
include /opt/redis-stable/cluster/redis.conf
daemonize yes
bind 0.0.0.0
dir /opt/redis-stable/cluster/
port 6391
dbfilename dump_6391.rdb
pidfile /var/run/redis_6391.pid
logfile "/opt/redis-stable/cluster/log/6391.log"
# 开启集群设置
cluster-enabled yes
# 设置节点配置文件
cluster-config-file node-6391.conf
# 设置节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
(8)启动master、slave1、slave2
# 方便演示,停止所有的redis
ps -ef | grep redis | awk -F" " '{print $2;}' | xargs kill -9
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6379.conf
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6380.conf
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6381.conf
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6389.conf
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6390.conf
/opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6391.conf
(9)查看6个redis的启动情况
ps -ef | grep redis
稍后我们会将6个实例合并到一个集群,在组合之前,我们要确保6个redis实例启动后,nodes-xxxx.conf
文件都生成正常,如下, /opt/redis-stable/config 目录中确实都生成成功了
这时候node*.conf文件只存储了一个节点id。
(10)关闭防火墙
假如不关闭防火墙,可能会出现windows无法连接我们刚刚在虚拟机安装的redis的情况
设置开机启用防火墙:systemctl enable firewalld.service
设置开机禁用防火墙:systemctl disable firewalld.service
启动防火墙:systemctl start firewalld
关闭防火墙:systemctl stop firewalld
检查防火墙状态:systemctl status firewalld
(1)将6个redis合体
执行下面命令,将6个redis合体
/opt/redis-stable/src/redis-cli --cluster create --cluster-replicas 1 192.168.0.101:6379 192.168.0.101:6380 192.168.0.101:6381 192.168.0.101:6389 192.168.0.101:6390 192.168.0.101:6391
遇到如下报错,redis认为远程连接不安全,所以阻止你对redis进行操作
解决方法:
protected-mode
默认为yes,设置为norequirepass
设置密码这里我选择了方式二,设置密码。通过在命令当中-a
来指定密码,执行过程如下,期间会让我们确定是否同样这样的分配方式,输入:yes,然后等几秒,集群合体成功
(2)查看集群节点信息
[root@bogon cluster]# /opt/redis-stable/src/redis-cli -c -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> cluster nodes
如下图,对 cluster nodes 的结果做下解释,先看下红字的注释,集群中的每个节点都会生成一个ID,这个ID信息会被写到node-xxxx.conf文件中,为什么要生成id呢?
因为节点的ip和端口可能会发生变化,但是节点的ID是不会变的,其他节点可以通过其他节点的ID来认识各个节点。
这里是
cluster nodes
命令查出来的其实和生成的node-xxx.conf
文件的配置是一样的!
(3)验证集群数据的读写操作
如下,我们连接 6379 这个节点,然后执行一个set操作,效果如下,写入成功
大家可能注意到了,我们明明在 6379 上操作的,但是请求被转发到了6380这个节点去处理了,这里就是我们后面要说的slot的知识了,先向后看。
一个集群至少有3个主节点,因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
选项--cluster-replicas 1
表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主库运行在不同的ip,每个主库和从库不在一个ip上,这样才能做到高可用。
如下图,咱们再来看看集群合并的过程中输出的一些信息
同样,执行get也会被重定向,效果如下
Redis集群内部划分了16384个slots(插槽),合并的时候,会将每个slots映射到一个master上面,比如上面3个master和slots的关系如下(通过cluster nodes
命令也可以查看):
不在一个slot下面,不能使用mget、mset等多键操作,效果如下
上面set的一个a1在7785槽位,而a2 set后再11786槽位,两个不在一个槽位导致mget和mset命令异常
可以通过{}来定义组的概念,从而使key中{}内相同的键值放到一个slot中去,效果如下
cluster keyslot
:计算key对应的slotcluster coutkeysinslot
:获取slot槽位中key的个数cluster getkeysinslot
返回count个slot槽中的键192.168.0.101:6381> mget a1{g1} a2{g1} a3{g1}
1) "aa1"
2) "aa2"
3) "aa3"
192.168.0.101:6381> cluster countkeysinslot 13519
(integer) 3
192.168.0.101:6381> cluster getkeysinslot 13519 3
1) "a3{g1}"
2) "a2{g1}"
3) "a1{g1}"
AnotherRedisDesktopManager 一款比较稳定简洁的 redis UI 工具。可以使用集群方式连接,如下:
这时候我们set了好多值,其中值都被分到了不同的节点上
虽然我们连的只是一个节点,但是我们使用的是集群方式连接所以这里看到的是所有节点汇聚起来的全量数据!
如果主节点下线,从节点是否能够提升为主节点?注意:要等15秒
下面我们来试试,如下,连接master1,然后将master1停掉
[root@bogon cluster]# /opt/redis-stable/src/redis-cli -c -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> shutdown
这是什么原因?不是按正常来说一个主机挂掉,从机应该故障恢复吗?怎么整个集群都宕机了。。。
查看了一下日志发现如下:出现大量的同步失败的日志。
我们需要在从数据库当中配置主数据库的密码,不然他同步数据的时候会出现失败的情况,这样会导致他会一直在重连,日志文件也会一直在逐渐的变大。
在所有的配置文件下添加:masterauth 123456
然后重启重新测试故障转移。
[root@bogon cluster]# /opt/redis-stable/src/redis-cli -c -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> shutdown
执行下面命令,连接master2,看下集群节点的信息
[root@localhost cluster]# /opt/redis-stable/src/redis-cli -c -h 127.0.0.1 -p 6380 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6380> cluster nodes
下面我们再来启动6379,然后再看看集群变成什么样了,命令如下
[root@localhost cluster]# /opt/redis-stable/src/redis-server /opt/redis-stable/cluster/redis-6379.conf
[root@localhost cluster]# /opt/redis-stable/src/redis-cli -c -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> cluster nodes
如果某一段插槽的主从都宕机了,redis服务是否还能继续?
这个时候要看 cluster-require-full-coverage 参数的值了
我们使用redis的主从同步的时候会发现在从机当中set值会异常,因为从机只可以读,而redis集群当中也有主从,但是他可以通过连接从机执行set,不会异常,只会将值存储到指定槽位的主数据库当中。
优点:
缺点:
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>4.4.2version>
dependency>
import redis.clients.jedis.*;
import java.util.HashSet;
import java.util.Set;
public class ClusterDemo {
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
//最大空闲连接数, 默认8个
config.setMaxIdle(8);
//最大连接数, 默认8个
config.setMaxTotal(8);
//最小空闲连接数, 默认0
config.setMinIdle(0);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(3000);
//在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false
config.setTestOnBorrow(true);
//redis服务器列表
Set<HostAndPort> cluster = new HashSet<>();
// 有多少节点这里就配置多少个
cluster.add(new HostAndPort("192.168.115.239", 6379));
cluster.add(new HostAndPort("192.168.115.239", 6380));
// connectionTimeout连接超时时间,soTimeout读取数据超时,maxAttempts重试次数,password密码
//public JedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig) {
JedisCluster jedisCluster = new JedisCluster(cluster, 2000, 2000, 5, "123456", config);
jedisCluster.set("key", "hello world");
String aa1 = jedisCluster.get("aa1");
System.out.println(aa1);
jedisCluster.close();
}
}
(1)引入redis的maven配置
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
(2)application.properties中配置redis sentinel信息
# 集群节点(host:port),多个之间用逗号隔开
spring.redis.cluster.nodes=192.168.115.239:6379,192.168.115.239:6380,192.168.115.239:6381,192.168.115.239:6389,192.168.115.239:6390,192.168.115.239:6391
# 连接超时时间(毫秒)
spring.redis.timeout=60000
spring.redis.password=123456
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
database: 0
(3)使用RedisTemplate工具类操作redis
springboot中使用RedisTemplate来操作redis,需要在我们的bean中注入这个对象,代码如下:
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 用下面5个对象来操作对应的类型
this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
this.redisTemplate.opsForSet(); //提供了操作set的所有方法
this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
(4)RedisTemplate示例代码
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* string测试
*
* @return
*/
@RequestMapping("/stringTest")
public String stringTest() {
this.redisTemplate.delete("name");
this.redisTemplate.opsForValue().set("name", "路人");
String name = this.redisTemplate.opsForValue().get("name");
return name;
}
/**
* list测试
*
* @return
*/
@RequestMapping("/listTest")
public List<String> listTest() {
this.redisTemplate.delete("names");
this.redisTemplate.opsForList().rightPushAll("names", "刘德华", "张学友",
"郭富城", "黎明");
List<String> courses = this.redisTemplate.opsForList().range("names", 0,
-1);
return courses;
}
/**
* set类型测试
*
* @return
*/
@RequestMapping("setTest")
public Set<String> setTest() {
this.redisTemplate.delete("courses");
this.redisTemplate.opsForSet().add("courses", "java", "spring",
"springboot");
Set<String> courses = this.redisTemplate.opsForSet().members("courses");
return courses;
}
/**
* hash表测试
*
* @return
*/
@RequestMapping("hashTest")
public Map<Object, Object> hashTest() {
this.redisTemplate.delete("userMap");
Map<String, String> map = new HashMap<>();
map.put("name", "路人");
map.put("age", "30");
this.redisTemplate.opsForHash().putAll("userMap", map);
Map<Object, Object> userMap =
this.redisTemplate.opsForHash().entries("userMap");
return userMap;
}
/**
* zset测试
*
* @return
*/
@RequestMapping("zsetTest")
public Set<String> zsetTest() {
this.redisTemplate.delete("languages");
this.redisTemplate.opsForZSet().add("languages", "java", 100d);
this.redisTemplate.opsForZSet().add("languages", "c", 95d);
this.redisTemplate.opsForZSet().add("languages", "php", 70);
Set<String> languages =
this.redisTemplate.opsForZSet().range("languages", 0, -1);
return languages;
}
/**
* 查看redis机器信息
*
* @return
*/
@RequestMapping(value = "/info", produces = MediaType.TEXT_PLAIN_VALUE)
public String info() {
Object obj = this.redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws
DataAccessException {
return connection.execute("info");
}
});
return obj.toString();
}