redis作为一种高效的缓存框架,使用是非常广泛的,在数据存储上,在运行时其将数据存储在内存中,以实现数据的高效读写,并且根据定制的持久化规则不同,其会不定期的将数据持久化到硬盘中。另外相较于其他的NoSql数据库,redis提供了非常丰富的数据结构。在这些存储结构的基础上,redis为用户提供了非常丰富的操作选择。
下载地址
配置文件采用默认
即可。将redis.conf放到指定的目录,比如:/home/dc2-user/docker_data/myredis/redis.conf
尤其注意
daemonize no
保持原来的no,不要修改为yes,否则在docker中启动redis后会立即停止。因为docker机制所致,启动的容器中必须有一个前台进程,否则容器会立即停止,避免占用资源。
不使用密码,直接外网连接,redis.conf修改如下
bind 0.0.0.0
protected-mode no
补充
使用密码进行外网连接,redis.conf修改如下
bind 0.0.0.0
protected-mode yes
requirepass 123456
该文件的路径放在/home/dc2-user/docker_data/myredis/
下,会默认docker-compose.yml所在目录的目录名为项目名。
version: '3'
services:
myredis:
image: redis
ports:
- 6379:6379
volumes:
- ./conf/redis.conf:/etc/redis/redis.conf
- ./data:/data:rw
- ./logs:/usr/local/redis/logs
container_name: myredis
networks:
- mynet
command: redis-server /etc/redis/redis.conf
networks:
mynet:
driver: bridge
进入/home/dc2-user/docker_data/myredis/
目录下,执行以下命令
docker-compose up -d
查看容器是否启动成功
docker-compose ps
没有设置密码
[root@zhangsan myredis]# echo -en 'ping\n' | nc 127.0.0.1 6379
+PONG # 表示通过nc访问docker中的redis成功
设置了密码
[root@zhangsan myredis]# echo -en 'auth 123456\nping\n' | nc 127.0.0.1 6379
+PONG # 表示通过nc访问docker中的redis成功
没有设置密码
[root@zhangsan myredis]# docker-compose exec myredis bash
root@a0d9b9632791:/data# echo -en 'ping\n' | redis-cli
+PONG
设置了密码
[root@zhangsan myredis]# docker-compose exec myredis bash
root@a0d9b9632791:/data# echo -en 'ping\n' | redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG
redis单例提供了一种数据缓存方式和丰富的数据操作api,但是将数据完全存储在单个redis中主要存在两个问题:数据备份和数据体量较大造成的性能降低
。这里redis的主从模式为这两个问题提供了一个较好的解决方案。主从模式指的是使用一个redis实例作为主机,其余的实例作为备份机。主机和从机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取
,也就是说,客户端可以将数据写入到主机,由主机自动将数据的写入操作同步到从机。主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离
的目的。如下所示主机redis-A分别有redis-B、redis-C、redis-D、redis-E四个从机:
前面第1点中我们已经介绍了redis单例的配置方式,而上面我们也介绍了主从模式其实也是多个redis实例组成的,因而redis主从模式的配置可以理解为多个不同的redis实例通过一定的配置告知其相互之间的主从关系。
总共3个容器
目录结构如下:
— redis-m_s
— docker-compose.yml
— master_1_7000
— conf
— redis.conf
— data
— logs
— slave_1_7001
— conf
— redis.conf
— data
— logs
— slave_2_7002
— conf
— redis.conf #redis.conf
结点 | IP |
---|---|
master_7000 | 172.16.21.2 |
slave_1_7001 | 172.16.21.3 |
slave_2_7002 | 172.16.21.4 |
docker-compose.yml
version: '3'
services:
master_7000:
image: redis
ports:
- 7000:6379
volumes:
- ./master_7000/conf/redis.conf:/etc/redis/redis.conf
- ./master_7000/data:/data:rw
- ./master_7000/logs:/usr/local/redis/logs
container_name: master_7000
networks:
mynet:
ipv4_address: 172.16.21.2
command: redis-server /etc/redis/redis.conf
restart: always
slave_1_7001:
image: redis
ports:
- 7001:6379
volumes:
- ./slave_1_7001/conf/redis.conf:/etc/redis/redis.conf
- ./slave_1_7001/data:/data:rw
- ./slave_1_7001/logs:/usr/local/redis/logs
container_name: slave_1_7001
depends_on:
- master_7000
networks:
mynet:
ipv4_address: 172.16.21.3
command: redis-server /etc/redis/redis.conf
restart: always
slave_2_7002:
image: redis
ports:
- 7002:6379
volumes:
- ./slave_2_7002/conf/redis.conf:/etc/redis/redis.conf
- ./slave_2_7002/data:/data:rw
- ./slave_2_7002/logs:/usr/local/redis/logs
container_name: slave_2_7002
depends_on:
- master_7000
networks:
mynet:
ipv4_address: 172.16.21.4
command: redis-server /etc/redis/redis.conf
restart: always
networks:
mynet:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.16.21.0/24
gateway: 172.16.21.1
redis.conf下载
不设置密码
m_1_7000/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode no # 不设置密码外网访问时需要设置为no
appendonly yes #开启持久化
s_1_7001/conf/redis.conf和s_2_7002/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode no # 不设置密码外网访问时需要设置为no
appendonly yes #开启持久化
slaveof 172.16.21.2 6379
补充
protected-mode yes时,不设置密码只能进行本地连接,设置密码以后可以外网连接。
protected-mode no时,不设置密码也可以外网连接。
设置密码
m_1_7000/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode yes # 外网用密码访问时,最后设置为yes
requirepass 123456 # 作用于主节点,设置主节点认证密码
masterauth 123456 # 作用于从节点,当主结点设置了密码后,从结点复制主节点的数据需要通过该设置,从节点的masterauther设置成于reauirepass相同即可。
appendonly yes #开启持久化
s_1_7001/conf/redis.conf和s_2_7002/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode yes # 外网用密码访问时,最后设置为yes
requirepass 123456 # 作用于主节点,设置主节点认证密码
masterauth 123456 # 作用于从节点,当主结点设置了密码后,从结点复制主节点的数据需要通过该设置,从节点的masterauther设置成于reauirepass相同即可。
appendonly yes #开启持久化
slaveof 172.16.21.2 6379
此处部署使用了密码
进入redis-m_s目录,执行命令
[root@zhangsan redis-m_s]# docker-compose up -d
Creating network "redis-m_s_mynet" with driver "bridge"
Creating master_7000 ... done
Creating slave_2_7002 ... done
Creating slave_1_7001 ... done
[root@zhangsan redis-m_s]# docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------
master_7000 docker-entrypoint.sh redis ... Up 0.0.0.0:7000->6379/tcp
slave_1_7001 docker-entrypoint.sh redis ... Up 0.0.0.0:7001->6379/tcp
slave_2_7002 docker-entrypoint.sh redis ... Up 0.0.0.0:7002->6379/tcp
[root@zhangsan redis-m_s]#
打开一终端进入m_7000 容器
[root@zhangsan redis-m_s]# docker-compose exec master_7000 bash
root@8178035070cb:/data# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> info replication
# Replication
role:master # 表明成功
connected_slaves:2 # 表明成功
slave0:ip=172.16.21.4,port=6379,state=online,offset=70,lag=0
slave1:ip=172.16.21.3,port=6379,state=online,offset=70,lag=0
master_replid:7462bff8b1273d8030c45de8d98b8eb0776223fa
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
127.0.0.1:6379>
redis主从模式解决了数据备份和单例可能存在的性能问题,但是其也引入了新的问题。由于主从模式配置了三个redis实例,并且每个实例都使用不同的ip(如果在不同的机器上)和端口号,根据前面所述,(1) 主从模式
下可以将读写操作分配给不同的实例进行从而达到提高系统吞吐量的目的,但也正是因为这种方式造成了使用上的不便,因为每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线
了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接
。另外,(2) 主从模式下,如果主节点由于故障下线
了,那么从节点因为没有主节点而同步中断,因而需要人工
进行故障转移
工作。
为了解决这两个问题,在2.8版本之后redis正式提供了sentinel(哨兵)架构
。关于sentinel,这里需要说明几个概念:
名词z | 逻辑结构 | 物理结构 |
---|---|---|
主节点 | redis主服务/数据库 | 一个独立的redis进程 |
从节点 | redis从服务/数据库 | 一个独立的redis进程 |
sentinel节点 | 监控redis数据节点 | 一个独立的sentinel进程 |
sentinel节点集合 | 若干sentinel节点的抽象集合 | 若干sentinel节点进程 |
应用方 | 泛指一个或多个客户端 | 一个或多个客户端线程或进程 |
每个sentinel节点其实就是一个redis实例,与主从节点不同的是sentinel节点作用是用于监控redis数据节点的,而sentinel节点集合则表示监控一组主从redis实例多个sentinel监控节点的集合,比如有主节点master和从节点slave-1、slave-2,为了监控这三个主从节点,这里配置N个sentinel节点sentinel-1,sentinel-2,…,sentinel-N。如下图是sentinel监控主从节点的示例图:
从图中可以看出,对于一组主从节点,sentinel只是在其外部额外添加的一组用于监控作用的redis实例。在主从节点和sentinel节点集合配置好之后,sentinel节点之间会相互发送消息,以检测其余sentinel节点是否正常工作,并且sentinel节点也会向主从节点发送消息,以检测监控的主从节点是否正常工作。
前面讲到,sentinel架构的主要作用是解决主从模式下主节点的故障转移工作的
。这里如果主节点因为故障下线,那么某个sentinel节点发送检测消息给主节点时,如果在指定时间内收不到回复,那么该sentinel就会主观的判断该主节点已经下线,那么其会发送消息给其余的sentinel节点,询问其是否“认为”该主节点已下线,其余的sentinel收到消息后也会发送检测消息给主节点,如果其认为该主节点已经下线,那么其会回复向其询问的sentinel节点,告知其也认为主节点已经下线,当该sentinel节点最先收到超过指定数目(配置文件中配置的数目和当前sentinel节点集合数的一半,这里两个数目的较大值
)的sentinel节点回复说当前主节点已下线,那么其就会对主节点进行故障转移工作。
故障转移
的基本思路是在从节点中选取某个从节点向其发送slaveof no one(假设选取的从节点为127.0.0.1:6380),使其称为独立的节点(也就是新的主节点),然后sentinel向其余的从节点发送slaveof 127.0.0.1 6380命令使它们重新成为新的主节点的从节点。重新分配之后sentinel节点集合还会继续监控已经下线的主节点(假设为127.0.0.1:6379),如果其重新上线,那么sentinel会向其发送slaveof命令,使其成为新的主机点的从节点,如此故障转移工作完成。
上面我们讲到了,每个sentinel节点在本质上还是一个redis实例,只不过和redis数据节点不同的是,其主要作用是监控redis数据节点。需要用到sentinl.conf
。
目前没有成功,搭建过程中遇到问题为解决。
总共6个容器
目录结构如下:
— redis-m_s
— docker-compose.yml
— master_7000
— conf
— redis.conf
— data
— logs
— slave_1_7001
— conf
— redis.conf
— data
— logs
— slave_2_7002
— conf
— redis.conf
— data
— logs
— sentinel_1_7003
— conf
— sentinel.conf
— data
— logs
— sentinel_2_7004
— conf
— sentinel.conf
— data
— logs
— sentinel_3_7005
— conf
— sentinel.conf
— data
— logs
version: '3'
services:
master_7000:
image: redis
ports:
- 7000:6379
volumes:
- ./master_7000/conf/redis.conf:/etc/redis/redis.conf
- ./master_7000/data:/data:rw
- ./master_7000/logs:/usr/local/redis/logs
container_name: master_7000
networks:
mynet:
ipv4_address: 172.16.21.2
command: redis-server /etc/redis/redis.conf
# restart: always
# 注意yaml文件的空行中不能有任何字符包括空格这类空白字符,否则会报错
slave_1_7001:
image: redis
ports:
- 7001:6379
volumes:
- ./slave_1_7001/conf/redis.conf:/etc/redis/redis.conf
- ./slave_1_7001/data:/data:rw
- ./slave_1_7001/logs:/usr/local/redis/logs
container_name: slave_1_7001
depends_on:
- master_7000
networks:
mynet:
ipv4_address: 172.16.21.3
command: redis-server /etc/redis/redis.conf
# restart: always
slave_2_7002:
image: redis
ports:
- 7002:6379
volumes:
- ./slave_2_7002/conf/redis.conf:/etc/redis/redis.conf
- ./slave_2_7002/data:/data:rw
- ./slave_2_7002/logs:/usr/local/redis/logs
container_name: slave_2_7002
depends_on:
- master_7000
networks:
mynet:
ipv4_address: 172.16.21.4
command: redis-server /etc/redis/redis.conf
# restart: always
sentinel_1_7003:
image: redis
ports:
- 7003:26379
volumes:
- /sentinel_1_7003/conf/sentinel.conf:/etc/redis/sentinel.conf
- /sentinel_1_7003/data:/data:rw
- /sentinel_1_7003/logs:/usr/local/redis/logs
container_name: sentinel_1_7003
depends_on:
- master_7000
- slave_1_7001
- slave_2_7002
networks:
mynet:
ipv4_address: 172.16.21.5
command: redis-sentinel /etc/redis/sentinel.conf
sysctls:
net.core.somaxconn: 511
# restart: always
sentinel_2_7004:
image: redis
ports:
- 7004:26379
volumes:
- /sentinel_2_7004/conf/sentinel.conf:/etc/redis/sentinel.conf
- /sentinel_2_7004/data:/data:rw
- /sentinel_2_7004/logs:/usr/local/redis/logs
container_name: sentinel_2_7004
depends_on:
- master_7000
- slave_1_7001
- slave_2_7002
networks:
mynet:
ipv4_address: 172.16.21.6
command: redis-sentinel /etc/redis/sentinel.conf
sysctls:
net.core.somaxconn: 511
# restart: always
sentinel_3_7005:
image: redis
ports:
- 7005:26379
volumes:
- /sentinel_3_7005/conf/sentinel.conf:/etc/redis/sentinel.conf
- /sentinel_3_7005/data:/data:rw
- /sentinel_3_7005/logs:/usr/local/redis/logs
container_name: sentinel_3_7005
depends_on:
- master_7000
- slave_1_7001
- slave_2_7002
networks:
mynet:
ipv4_address: 172.16.21.7
command: redis-sentinel /etc/redis/sentinel.conf
sysctls:
net.core.somaxconn: 511
# restart:always
networks:
mynet:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.16.21.0/24
gateway: 172.16.21.1
此处部署使用了密码
master_7000/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode yes # 外网用密码访问时,最后设置为yes
requirepass 123456 # 作用于主节点,设置主节点认证密码
masterauth 123456 # 作用于从节点,当主结点设置了密码后,从结点复制主节点的数据需要通过该设置,从节点的masterauther设置成于reauirepass相同即可。
appendonly yes #开启持久化
slave_1_7001/conf/redis.conf和slave_2_7002/conf/redis.conf做如下修改
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode yes # 外网用密码访问时,最后设置为yes
requirepass 123456 # 作用于主节点,设置主节点认证密码
masterauth 123456 # 作用于从节点,当主结点设置了密码后,从结点复制主节点的数据需要通过该设置,从节点的masterauther设置成于reauirepass相同即可。
appendonly yes #开启持久化
slaveof 172.16.21.2 6379
sentinel下载
sentinel_1_7003/conf/snetinel.conf
sentinel_2_7004/conf/snetinel.conf
sentinel_3_7005/conf/snetinel.conf
做如下修改
protected-mode yes
bind 0.0.0.0
port 26379
dir "/tmp"
sentinel monitor mymaster 172.16.21.2 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
# 由于主从机都设置了密码,此处也需要设置
sentinel auth-pass mymaster 123456
配置项 | 参数类型 | 作用 |
---|---|---|
port | 整数 | 启动哨兵进程端口 |
dir | 文件夹目录 | 哨兵进程服务临时文件夹,默认为/tmp,要保证有可写入的权限 |
sentinel | down-after-milliseconds <服务名称><毫秒数(整数)> | 指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒) |
sentinel | parallel-syncs <服务名称><服务器数(整数)> | 指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高 |
sentinel | failover-timeout <服务名称><毫秒数(整数)> | 指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟 |
sentinel | notification-script <服务名称><脚本路径> | 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用 |
sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。
docker-compose up
之后sentinel_1_7003/sentinel_2_7004/sentinel_3_7005报出如下错误,目前没解决。
sentinel_1_7003 | 1:X 09 Dec 2020 02:11:47.048 # Could not rename tmp config file (Is a directory)
sentinel_1_7003 | 1:X 09 Dec 2020 02:11:47.049 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Is a directory
sentinel_2_7004 | 1:X 09 Dec 2020 02:11:47.333 # Could not rename tmp config file (Is a directory)
sentinel_2_7004 | 1:X 09 Dec 2020 02:11:47.334 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Is a directory
sentinel_3_7005 | 1:X 09 Dec 2020 02:11:47.277 # Could not rename tmp config file (Is a directory)
sentinel_3_7005 | 1:X 09 Dec 2020 02:11:47.277 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Is a directory
目前没有解决该问题
redis集群是在redis 3.0版本推出的一个功能,其有效的解决了redis在分布式方面的需求。(1) 当遇到单机内存,并发和流量瓶颈等问题时,可采用Cluster方案达到负载均衡的目的
。(2) redis中sentinel有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题,但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点
,而这些问题在Cluster中都得到了有效的解决。
redis集群中数据是和槽(slot)挂钩
的,其总共定义了6384
个槽,所有的数据根据一致哈希算法会被映射到这16384个槽中的某个槽中;另一方面,这16384个槽是按照设置被分配到不同的redis节点上的,比如启动了三个redis实例:cluster-A,cluster-B和cluster-C,这里将0-5460号槽分配给cluster-A,将5461-10922号槽分配给cluster-B,将10923-16383号槽分配给cluster-C(总共有16384个槽,但是其标号类似数组下标,是从0到16383)。也就是说数据的存储只和槽有关,并且槽的数量是一定的,由于一致hash算法是一定的,因而将这16384个槽分配给无论多少个redis实例,对于确认的数据其都将被分配到确定的槽位上。redis集群通过这种方式来达到redis的高效和高可用性目的。
这里需要进行说明的一点是,一致哈希算法根据数据的key值计算映射位置时和所使用的节点数量有非常大的关系。一致哈希分区的实现思路是为系统中每个节点分配一个token,范围一般在0~2^32,这些token构成一个哈希环,数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该hash值的token节点,需要操作的数据就保存在该节点上。通过分析可以发现,一致哈希分区存在如下问题:
正是由于一致哈希分区的这些问题,redis使用了虚拟槽来处理分区时节点变化的问题,也即将所有的数据映射到16384个虚拟槽位上,当redis节点变化时数据映射的槽位将不会变化,并且这也是redis进行节点扩张的基础。
Redis集群设计包括2部分:哈希Slot和节点主从,本篇博文通过3张图来搞明白Redis的集群设计。
主从设计不算什么新鲜玩意,在数据库中我们也经常用主从来做读写分离,直接上图:
图上能看得到的信息:
当一个新的Slaver加入到这个集群时,会主动找Master来拜码头,Master发现新的小弟后将全量数据发送给新的Slaver,数据量越大性能消耗也就越大,所以尽量避免在运行时做Slaver的扩容。
简单总结下主从模式的设计:
这个艺名看起来很文艺,但也不是什么新技术,他的真名就叫分表分库,再上一个图:
图上能看到的信息:
简单总结下哈希Slot的优缺点:
看到这里大家也就发现了,主从和哈希的设计优缺点正好是相互弥补的,将图一每一套主从对应到图二中的每一个Node,就是Redis集群的终极形态,先Hash分逻辑节点,然后每个逻辑节点内部是主从,如图:
想扩展并发读就添加Slaver,想扩展并发写就添加Master,想扩容也就是添加Master,任何一个Slaver或者几个Master挂了都不会是灾难性的故障。
三张图秒懂Redis集群设计原理
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value
时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。
使用哈希槽的好处就在于可以方便的添加或移除节点。
当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。
"用了哈希槽的概念,而没有用一致性哈希算法,不都是哈希么?这样做的原因是为什么呢?"
Redis Cluster是自己做的crc16的简单hash算法,没有用一致性hash。Redis的作者认为它的crc16(key) mod 16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单,节点增删时处理起来也很方便。
"为了动态增删节点的时候,不至于丢失数据么?"
节点增删时不丢失数据和hash算法没什么关系,不丢失数据要求的是一份数据有多个副本。
“还有集群总共有2的14次方,16384个哈希槽,那么每一个哈希槽中存的key 和 value是什么?”
当你往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。
redis slot 槽点
总共6个容器
目录结构如下:
— redis-cluster
— docker-compose.yml
— m_1_7000
— conf
— redis.conf
— data
— logs
— m_2_7001
— conf
— redis.conf
— data
— logs
— m_3_7002
— conf
— redis.conf
— data
— logs
— s_11_7003
— conf
— redis.conf
— data
— logs
— s_21_7004
— conf
— redis.conf
— data
— logs
— s_31_7005
— conf
— redis.conf
— data
— logs
此处没有配指定的网络,没有为每个容器配指定的ip,当然你也可以指定。此处docker为redis-cluster_mynet自动分配一个网络号,为每个容器自动分配ip。
version: '3'
services:
m_1_7000:
image: redis
ports:
- 7000:6379
volumes:
- ./m_1_7000/conf/redis.conf:/etc/redis/redis.conf
- ./m_1_7000/data:/data:rw
- ./m_1_7000/logs:/usr/local/redis/logs
container_name: m_1_7000
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
m_2_7001:
image: redis
ports:
- 7001:6379
volumes:
- ./m_2_7001/conf/redis.conf:/etc/redis/redis.conf
- ./m_2_7001/data:/data:rw
- ./m_2_7001/logs:/usr/local/redis/logs
container_name: m_2_7001
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
m_3_7002:
image: redis
ports:
- 7002:6379
volumes:
- ./m_3_7002/conf/redis.conf:/etc/redis/redis.conf
- ./m_3_7002/data:/data:rw
- ./m_3_7002/logs:/usr/local/redis/logs
container_name: m_3_7002
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
s_11_7003:
image: redis
ports:
- 7003:6379
volumes:
- ./s_11_7003/conf/redis.conf:/etc/redis/redis.conf
- ./s_11_7003/data:/data:rw
- ./s_11_7003/logs:/usr/local/redis/logs
container_name: s_11_7003
depends_on:
- m_1_7000
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
s_21_7004:
image: redis
ports:
- 7004:6379
volumes:
- ./s_21_7004/conf/redis.conf:/etc/redis/redis.conf
- ./s_21_7004/data:/data:rw
- ./s_21_7004/logs:/usr/local/redis/logs
container_name: s_21_7004
depends_on:
- m_2_7001
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
s_31_7005:
image: redis
ports:
- 7005:6379
volumes:
- ./s_31_7005/conf/redis.conf:/etc/redis/redis.conf
- ./s_31_7005/data:/data:rw
- ./s_31_7005/logs:/usr/local/redis/logs
container_name: s_31_7005
depends_on:
- m_3_7002
networks:
- mynet
command: redis-server /etc/redis/redis.conf
restart: always
networks:
mynet: # 启动容器后会生成一个redis-cluster_mynet的网络
driver: bridge
redis.conf下载
不设置密码
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode no # 不设置密码外网访问时需要设置为no
appendonly yes #开启持久化
cluster-enabled yes #开启集群
cluster-config-file nodes.conf #集群配置文件(这个在后面搭建的时候,会自动生成,不用管,这样写就对了)
cluster-node-timeout 15000 #节点超时时间
补充
protected-mode yes时,不设置密码只能进行本地连接,设置密码以后可以外网连接。
protected-mode no时,不设置密码也可以外网连接。
设置密码
bind 0.0.0.0 #将绑定ip设置为 0.0.0.0 ,即不绑定ip
protected-mode yes # 外网用密码访问时,最后设置为yes
requirepass 123456 # 设置连接认证密码
masterauth 123456 # 主从复制的验证密码
appendonly yes #开启持久化
cluster-enabled yes #开启集群
cluster-config-file nodes.conf #集群配置文件(这个在后面搭建的时候,会自动生成,不用管,这样写就对了)
cluster-node-timeout 15000 #节点超时时间
进入redis-cluster目录,执行命令
[root@zhangsan redis-cluster]# docker-compose up -d
Creating network "redis-cluster_mynet" with driver "bridge"
Creating m_1_7000 ... done
Creating m_3_7002 ... done
Creating m_2_7001 ... done
Creating s_21_7004 ... done
Creating s_11_7003 ... done
Creating s_31_7005 ... done
(base) [root@zhansan redis-cluster]# docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------
m_1_7000 docker-entrypoint.sh redis ... Up 0.0.0.0:7000->6379/tcp
m_2_7001 docker-entrypoint.sh redis ... Up 0.0.0.0:7001->6379/tcp
m_3_7002 docker-entrypoint.sh redis ... Up 0.0.0.0:7002->6379/tcp
s_11_7003 docker-entrypoint.sh redis ... Up 0.0.0.0:7003->6379/tcp
s_21_7004 docker-entrypoint.sh redis ... Up 0.0.0.0:7004->6379/tcp
s_31_7005 docker-entrypoint.sh redis ... Up 0.0.0.0:7005->6379/tcp
[root@zhansan redis-cluster]# docker network ls
NETWORK ID NAME DRIVER SCOPE
7fcfe5724a97 bridge bridge local
6f4636b11f66 host host local
8d9534bd24c4 none null local
a2e10a970796 redis-cluster_mynet bridge local # NAME=项目名+'_'+docker-compose.yml中为每个容器定义的networks名
[root@zhangsan redis-cluster]# docker network inspect redis-cluster_mynet
[
{
"Name": "redis-cluster_mynet",
"Id": "a2e10a970796d79be7a15b42338afeac0f925edbe2f2f8d3d716bb4cb6d3573d",
"Created": "2020-12-06T22:27:43.387866792+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.22.0.0/16",
"Gateway": "172.22.0.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"59bd72b1505255fa41b23fcf590a0dc44273efc07ceaaf9eba2ea7a6f97ca403": {
"Name": "m_1_7000",
"EndpointID": "e3bf5f8259bd7630fec1fa1dfa0b06406f2857bacca3d6a92f90e9be358e41ec",
"MacAddress": "02:42:ac:16:00:04",
"IPv4Address": "172.22.0.4/16",
"IPv6Address": ""
},
"6c32877857f75fca90287a8ae157f7fd209c03d532d0b1627eaa8e9d4dd11fbd": {
"Name": "m_2_7001",
"EndpointID": "38a5b3d0208a54bea19863bea64d7e3cf015fc4b2adfa1ccf75830ce7e321b75",
"MacAddress": "02:42:ac:16:00:03",
"IPv4Address": "172.22.0.3/16",
"IPv6Address": ""
},
"80b638f43deb4f0fce6b5ed81250486cd86b0828ff4e6d581c367fbcbce8559f": {
"Name": "m_3_7002",
"EndpointID": "e3a93b36d70d578b50e82b68332930686e6050f1be30bd349f00411b9bc1edba",
"MacAddress": "02:42:ac:16:00:02",
"IPv4Address": "172.22.0.2/16",
"IPv6Address": ""
},
"9624fc9a8cef195b1eb36e0ee23f01a3662598d165fae8062fe812060ebb08d3": {
"Name": "s_11_7003",
"EndpointID": "c7a2655c7c325e9c32dcfb81a890be258a5cec4a3cd77f6232d7d51b753099d2",
"MacAddress": "02:42:ac:16:00:07",
"IPv4Address": "172.22.0.7/16",
"IPv6Address": ""
},
"b6eb9047f15416169e550e9093401a899d36203d8fed27253a048579a5208d12": {
"Name": "s_31_7005",
"EndpointID": "c98652209a1c3493793aab5640570f56410cbe1922c118686ff40f8aa7147ea0",
"MacAddress": "02:42:ac:16:00:05",
"IPv4Address": "172.22.0.5/16",
"IPv6Address": ""
},
"ca55bcde0d54759cc1e68fb10ccc69e1ada33ae69511e50647d949798a2a5433": {
"Name": "s_21_7004",
"EndpointID": "82d708dd294ef1bfc18869b0fa45b7567c22329a91a0056a3c3485d980c7d001",
"MacAddress": "02:42:ac:16:00:06",
"IPv4Address": "172.22.0.6/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "mynet",
"com.docker.compose.project": "redis-cluster",
"com.docker.compose.version": "1.27.4"
}
}
]
把Containers 里面内容的 Name 和 IPv4Address 保存下来,并分配好主从关系,后面要用到
主(master) | 从(slave) |
---|---|
m_1_7000 : 172.22.0.4 | s_11_7003 : 172.22.0.7 |
m_2_7001 : 172.22.0.3 | s_21_7004 : 172.22.0.6 |
m_3_7002 : 172.22.0.2 | s_31_7005 : 172.22.0.5 |
随意进入一个redis容器,此处进的时m_1_7000
容器
(base) [root@zhangsan redis-cluster]# docker-compose exec m_1_7000 bash
root@caa2e951b5a3:/data# redis-cli
127.0.0.1:6379> auth 123456 #若没有设置密码则忽略该步
OK
127.0.0.1:6379> cluster meet 172.22.0.4 6379
OK
127.0.0.1:6379> cluster meet 172.22.0.3 6379
OK
127.0.0.1:6379> cluster meet 172.22.0.2 6379
OK
127.0.0.1:6379> cluster meet 172.22.0.7 6379
OK
127.0.0.1:6379> cluster meet 172.22.0.6 6379
OK
127.0.0.1:6379> cluster meet 172.22.0.5 6379
OK
127.0.0.1:6379> cluster nodes
3bcab93b86f8c342c6529f2ca743ded1622adfaf 172.22.0.4:6379@16379 myself,master - 0 1607265252000 1 connected
476a42c2d8dd674edf4944d230168bbaa792bdc2 172.22.0.5:6379@16379 master - 0 1607265253834 5 connected
15ecd4b97008980f9078a062b3be469ab45f14ef 172.22.0.2:6379@16379 master - 0 1607265251829 2 connected
859d7fda69c450ea74d9aba3eddc01e8be196e4d 172.22.0.3:6379@16379 master - 0 1607265250000 0 connected
1641f92a3d805b9c46e19fe1607b54d9a1b5a225 172.22.0.6:6379@16379 master - 0 1607265251000 4 connected
3045226eb57977e2d242eaff7d8ce965890d2624 172.22.0.7:6379@16379 master - 0 1607265252831 3 connected
127.0.0.1:6379>
现在,我们要记录 3 个 主 master 的 节点标识,配置主从结构时要用到
主(mashter) | 标识 |
---|---|
m_1_7000 : 172.22.0.4 | 3bcab93b86f8c342c6529f2ca743ded1622adfaf |
m_2_7001 : 172.22.0.3 | 859d7fda69c450ea74d9aba3eddc01e8be196e4d |
m_3_7002 : 172.22.0.2 | 15ecd4b97008980f9078a062b3be469ab45f14ef |
编写脚本
[root@zhangsan redis-cluster]# vim addslots.sh
[root@zhangsan redis-cluster]# mv addslots.sh > m_1_7000/data/addslots.sh
[root@zhangsan redis-cluster]# chmod 755 m_1_7000/data/addslots.sh
addslots.sh文件内容
#!/bin/bash
# 将0-5461的槽点配置在 m_1_7000 上
n=0
for ((i=n;i<=5461;i++))
do
# (1) 若没有设置密码需要去掉'-a 123465'
# (2) 因为本地没有安装redis,也就没有redis-cli客户端,因此需要到某个redis容器中使用redis-cli,所以相当于在redis-cluster_mynet中一个容器访问其他容器,所以-h设置为不同容器ip,而-p设置为6379,当然也可以不用设置-p,因为-p默认就是6379。
# 如果本地安装了reids,有redis-cli,也可以在本地执行该脚本,不过-h需要设置为127.0.0.1,-p 设置为 7000、7001、7002.
# (3) 2>/dev/null。当设置了密码时,没有2>/dev/null,执行脚本会输出'Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.'这样的警告信息,导致脚本不能继续执行下去。因此需要添加 2>/dev/null ,去掉警告。
redis-cli -h 172.22.0.4 -p 6379 -a 123456 cluster addslots ${i} 2>/dev/null
done
# 将5462-10922的槽点配置在 m_2_7001 上
n=5462
for ((i=n;i<=10922;i++))
do
# 若没有设置密码需要去掉'-a 123465'
redis-cli -h 172.22.0.2 -p 6379 -a 123456 cluster addslots ${i} 2>/dev/null
done
# 将10923-16383的槽点配置在 m_3_7002 上
n=10923
for ((i=n;i<=16383;i++))
do
# 若没有设置密码需要去掉'-a 123465'
redis-cli -h 172.22.0.3 -p 6379 -a 123456 cluster addslots ${i} 2>/dev/null
done
再次进入m_1_7000
容器,然后执行 addslots.sh
文件,分配槽点
[root@zhangsan redis-cluster]#docker-compose exec m_1_7000 bash
root@59bd72b15052:/data# ./addslots.sh
OK
OK
.
.
.
root@59bd72b15052:/data#
编写脚本
[root@zhangsan redis-cluster]# vim slaves.sh
[root@zhangsan redis-cluster]# mv slaves.sh > m_1_7000/data/slaves.sh
[root@zhangsan redis-cluster]# chmod 755 m_1_7000/data/slaves.sh
slaves.sh文件内容
# 需要用到 从redis 的IP 与 主redis 的节点标识
# 主 redis1 从 redis4
# 对于 -h -p -a 2>/dev/null 的设置与addslots.sh的情况相同。
redis-cli -h 172.22.0.7 -p 6379 -a 123456 CLUSTER REPLICATE 3bcab93b86f8c342c6529f2ca743ded1622adfaf 2>/dev/null
# 主 redis2 从 redis5
redis-cli -h 172.22.0.6 -p 6379 -a 123456 CLUSTER REPLICATE 859d7fda69c450ea74d9aba3eddc01e8be196e4d 2>/dev/null
# 主 redis3 从 redis6
redis-cli -h 172.22.0.5 -p 6379 -a 123456 CLUSTER REPLICATE 15ecd4b97008980f9078a062b3be469ab45f14ef 2>/dev/null
再次进入m_1_7000
容器,然后执行 addslots.sh
文件,配置主从结构
[root@zhangsan redis-cluster]#docker-compose exec m_1_7000 bash
root@59bd72b15052:/data# ./slaves.sh
OK
OK
OK
root@59bd72b15052:/data# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> cluster nodes
3bcab93b86f8c342c6529f2ca743ded1622adfaf 172.22.0.4:6379@16379 myself,master - 0 1607308632000 1 connected 0-5461
476a42c2d8dd674edf4944d230168bbaa792bdc2 172.22.0.5:6379@16379 slave 15ecd4b97008980f9078a062b3be469ab45f14ef 0 1607308633922 2 connected
15ecd4b97008980f9078a062b3be469ab45f14ef 172.22.0.2:6379@16379 master - 0 1607308633000 2 connected 10923-16383
859d7fda69c450ea74d9aba3eddc01e8be196e4d 172.22.0.3:6379@16379 master - 0 1607308631913 0 connected 5462-10922
1641f92a3d805b9c46e19fe1607b54d9a1b5a225 172.22.0.6:6379@16379 slave 859d7fda69c450ea74d9aba3eddc01e8be196e4d 0 1607308632916 0 connected
3045226eb57977e2d242eaff7d8ce965890d2624 172.22.0.7:6379@16379 slave 3bcab93b86f8c342c6529f2ca743ded1622adfaf 0 1607308634925 1 connected
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_ping_sent:43474
cluster_stats_messages_pong_sent:44261
cluster_stats_messages_meet_sent:6
cluster_stats_messages_sent:87741
cluster_stats_messages_ping_received:44260
cluster_stats_messages_pong_received:43480
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:87741
127.0.0.1:6379>
配置完以后主从关系关联表
(左边为主,对应右边为从)
主redis(容器) | 主redis(宿主) | 从redis(容器) | 从redis(宿主) |
---|---|---|---|
m_1_7000 - 172.22.0.4:6379 | 127.0.0.1:7000 | s_11_7003 - 172.22.0.7:6379 | 127.0.0.1:7003 |
m_2_7001 - 172.22.0.3:6379 | 127.0.0.1: 7001 | s_21_7004 -172.22.0.6:6379 | 127.0.0.1:7004 |
m_3_7002 - 172.22.0.2:6379 | 127.0.0.1:7002 | s_31_7005 - 172.22.0.5:6379 | 127.0.0.1:7005 |
当某个主节点崩掉时,它的从节点可以替补转为主节点。并且当崩掉的主节点恢复时,它只能变为从节点,也就是主从地位发生了反转。
打开一个终端进入m_1_7000 容器
[root@zhangsan redis-cluster]# docker-compose exec m_1_7000 bash
root@59bd72b15052:/data# redis-cli -h 172.22.0.3 -p 6379 -c # 以集群方式进入m_2_7001
172.22.0.3:6379> auth 123456
OK
172.22.0.3:6379> set name zhangsan
OK
172.22.0.3:6379> get name
"zhangsan"
172.22.0.3:6379>
如果不以集群的方式(-c)
进入 redis 会出现错误
打开另一个终端进入s_11_7003 容器
[root@zhangsan redis-cluster]# docker-compose exec s_11_7003 bash
root@9624fc9a8cef:/data# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> get name
(error) MOVED 5798 172.22.0.3:6379 # 没有以集群方式进入会报该错
127.0.0.1:6379> exit
root@9624fc9a8cef:/data# redis-cli -c # 重新以集群方式进入
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> get name
-> Redirected to slot [5798] located at 172.22.0.3:6379
(error) NOAUTH Authentication required. # 目前不知原因,重试了一次成功
172.22.0.3:6379> auth 123456
OK
172.22.0.3:6379> get name
"zhangsan"
172.22.0.3:6379>
以下命令是Redis Cluster集群所独有的,执行下面命令需要先登录redis:
集群
cluster info :打印集群的信息
cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。
节点
cluster meet
:将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget
:从集群中移除 node_id 指定的节点。
cluster replicate
:将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作。
cluster saveconfig
:将节点的配置文件保存到硬盘里面。
槽(slot)
cluster addslots
:将一个或多个槽( slot)指派( assign)给当前节点。
cluster delslots
:移除一个或多个槽对当前节点的指派。
cluster flushslots
:移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot
:将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给
另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot
:将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot
:从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot
:取消对槽 slot 的导入( import)或者迁移( migrate)。
键
cluster keyslot
:计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot
:返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot
:返回 count 个 slot 槽中的键 。
如果需要更高的性能的redis集群,需要使用跨机的docker集群,也就是要用到docker-machine和docker-swarm。
[参考博客]
docker配置Redis哨兵Sentinel模式
redis单例、主从模式、sentinel以及集群的配置方式及优缺点对比
Redis Cluster日常操作命令梳理