Redis集群(一)
- 产生的背景 一句话单台机器承受不了大数据量了
假设我们在一台主从机器上配置了200G内存,但是业务需求是需要500G的时候,主从结构+哨兵可以实现高可用故障切换+冗余备份,但是并不能解决数据容量 的 问题,用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。
为了最大化利用内存,可以采用cluster群集,就是分布式存储。即每台redis存储不同的内容。 对于redis的集群其实就和MySQL的分库分表的切分是有很大的相似性,把数据根据规则切分在不同的节点上
Redis 分布式方案一般有两种:
2. 客户端分区方案,优点是分区逻辑可控,缺点是需要自己处理数据路由、高可用、故障转移等问题,比如在redis2.8之前通常的做法是获取某个key的 hashcode ,然后取余分布到不同节点,不过这种做法无法很好的支持动态伸缩性需求,一旦节点的增或者删操作,都会导致key无法在redis中命中。
- 代理方案,优点是简化客户端分布式逻辑和升级维护便利,缺点是加重架构部署复杂度和性能损耗,比如 twemproxy/Codis
- 官方为我们提供了专有的集群方案:Redis Cluster,它非常优雅地解决了 Redis 集群方面的问题,部署方便简单,因此理解应用好 Redis Cluster 将极大地解放 我们使用分布式 Redis 的工作量。
redis cluster 2.1 简绍
Redis Cluster 是 Redis 的分布式解决方案,在3.0版本正式推出,有效地解决了 Redis 分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用 Cluster 架构方案达到负载均衡的目的。
架构图:
在这个图中,每一个蓝色的圈都代表着一个redis的服务器节点。它们任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群 中的任何一个节点,对其进行存取和其他操作。
Redis 集群提供了以下两个好处:
1、将数据自动切分到多个节点的能力。
2、当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力,拥有自动故障转移的能力。
redis cluster vs. replication + sentinal如何选择
如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,单机足够了。
- Replication:一个mater,多个slave,要几个slave跟你的要求的读吞吐量有关系,结合sentinal集群,去保证redis主从架构的高可用性,就可以了。
- redis cluster:主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,那么建议就用redis cluster。
数据分布理论
分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集. 常见的分区规则有哈希分区和顺序分区两种
顺序分布一般都是平均分配的。
哈希分区:如下图所示,1~100这整块数字,通过 hash 的函数,取余产生的数。这样可以保证这串数字充分的打散,也保证了均匀的分配到各台机器上。
对比
哈希分布和顺序分布只是场景上的适用。哈希分布不能顺序访问,比如你想访问1~100,哈希分布只能遍历全部数据,同时哈希分布因为做了 hash 后导致与业务 数据无关了。
数据倾斜与数据迁移跟节点伸缩
顺序分布是会导致数据倾斜的,主要是访问的倾斜。每次点击会重点访问某台机器,这就导致最后数据都到这台机器上了,这就是顺序分布最大的缺点。
但哈希分布其实是有个问题的,当我们要扩容机器的时候,专业上称之为“节点伸缩”,这个时候,因为是哈希算法,会导致数据迁移。
哈希分区方式
- 节点取余分区
使用特定的数据(包括redis的键或用户ID),再根据节点数量N,使用公式:hash(key)%N计算出一个0~(N-1)值,用来决定数据映射到哪一个节点上。即哈希值 对节点总数取余。
缺点:当节点数量N变化时(扩容或者收缩),数据和节点之间的映射关系需要重新计算,这样的话,按照新的规则映射,要么之前存储的数据找不到,要么之前 数据被重新映射到新的节点(导致以前存储的数据发生数据迁移)
实践:常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为512或1024张表,保证可支撑未来一段时间的数据量, 再根据负载情况将表迁移到其他数据库中。 - 一致性哈希
一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个 token,范围一般在0~232,这些 token 构成一个哈希环。数据读写执行节点查 找操作时,先根据 key 计算 hash 值,然后顺时针找到第一个大于等于该哈希值的 token 节点
假设我们有 n1~n4 这四台机器,我们对每一台机器分配一个唯一 token,每次有数据(图中黄色代表数据),一致性哈希算法规定每次都顺时针漂移数据,也就是图中黄色的数据都指向 n3。
这个时候我们需要增加一个节点 n5,在 n2 和 n3 之间,数据还是会发生漂移(会偏移到大于等于的节点),但是这个时候你是否注意到,其实只有 n2~n3 这部分的数据被漂移,其他的数据都是不会变的,这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。
- 缺点:每个节点的负载不相同,因为每个节点的hash是根据key计算出来的,换句话说就是假设key足够多,被hash算法打散得非常均匀,但是节点过少,导致
每个节点处理的key个数不太一样,甚至相差很大,这就会导致某些节点压力很大 - 实践:加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
- 虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节 点数,比如 Redis Cluster 槽范围是 0~16383 (也就是说有16383个槽)。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,下图所示。
当前集群有5个节点,每个节点平均大约负责3276个槽。由于采用高质量的哈希算法,每个槽所映射的数据通常比较均匀,将数据平均划分到5个节点进行数据分 区。 Redis Cluster 就是采用虚拟槽分区,下面就介绍 Redis 数据分区方法。
每当 key 访问过来,Redis Cluster 会计算哈希值是否在这个区间里。它们彼此都知道对应的槽在哪台机器上,这样就能做到平均分配了。
集群限制: Key批量操作,mget()
搭建集群
- 使用redis5搭建建群的方式
首先只需要配置好配置文件
bind 0.0.0.0
cluster-enabled yes
cluster-config-file "/redis/log/nodes.conf" cluster-node-timeout 5000
protected-mode no
port 6379
daemonize no
dir "/redis/data"
logfile "/redis/log/redis.log"
然后配置好,docker-compose及目录结构
cluster
- redis5
- 200
- conf
- redis.conf
- nodes.conf
- data
- log
- 201
- 202
- 203
- 204
- 205
redis集群中的每个节点都会单独开辟一个tcp通道,用于节点之间彼此通信,通信端口号在基础端口上加10000
version: "3.6" # 确定docker-composer文件的版本
services: # 代表就是一组服务 - 简单来说一组容器
redis_200: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.200 #设置ip地址
container_name: redis5_cluster_200 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6320:6379" # php java python 语言连接
- "16320:16379" # 对节点 6379 + 10000 = 端口 对节点进行通信
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/200:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
redis_201: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.201 #设置ip地址
container_name: redis5_cluster_201 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6321:6379"
- "16321:16379"
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/201:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
redis_202: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.202 #设置ip地址
container_name: redis5_cluster_202 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6322:6379"
- "16322:16379"
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/202:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
redis_203: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.203 #设置ip地址
container_name: redis5_cluster_203 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6323:6379"
- "16323:16379"
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/203:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
redis_204: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.204 #设置ip地址
container_name: redis5_cluster_204 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6324:6379"
- "16324:16379"
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/204:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
redis_205: # 这个表示服务的名称,课自定义; 注意不是容器名称
image: redis5asm # 指定容器的镜像文件
networks: ## 引入外部预先定义的网段
redis5sm:
ipv4_address: 192.160.1.205 #设置ip地址
container_name: redis5_cluster_205 # 这是容器的名称
ports: # 配置容器与宿主机的端口
- "6325:6379"
- "16325:16379"
volumes: # 配置数据挂载
- /redis_2004/cluster/redis5/205:/redis
command: /usr/local/bin/redis-server /redis/conf/redis.conf
# 网段设置
networks:
#引用外部预先定义好的网段
redis5sm:
external:
name: redis5sm
执行操作
[root@localhost redis5]# docker stop $(docker ps -a -q) | xargs docker rm 88f40be37831
a90c22d381d2
5658f05e5b2f
3f61e251b86d
817c7e012482
b2d4ee6d2c13
[root@localhost redis5]# docker-compose up -d Creating redis5_cluster_204 ... done
Creating redis5_cluster_202 ... done Creating redis5_cluster_200 ... done Creating redis5_cluster_203 ... done Creating redis5_cluster_205 ... done Creating redis5_cluster_201 ... done [root@localhost redis5]# docker ps CONTAINER ID IMAGE
NAMES
4308ac04cf14 redis5asm
0.0.0.0:16321->16379/tcp redis5_cluster_201
f9333d9d3670 redis5asm "/usr/local/bin/redi..." 0.0.0.0:16325->16379/tcp redis5_cluster_205
2b17b80eed86 redis5asm "/usr/local/bin/redi..." 0.0.0.0:16320->16379/tcp redis5_cluster_200
7f6cb7237118 redis5asm "/usr/local/bin/redi..." 0.0.0.0:16323->16379/tcp redis5_cluster_203
10e18214da8a redis5asm "/usr/local/bin/redi..." 0.0.0.0:16322->16379/tcp redis5_cluster_202
62b1fe18609e redis5asm "/usr/local/bin/redi..." 3 seconds ago Up 2 seconds 0.0.0.0:6324->6379/tcp, 0.0.0.0:16324->16379/tcp redis5_cluster_204
[root@localhost redis5]# docker exec -it redis5_cluster_200 sh
/ # cat /redis/log/nodes.conf
a163aee6c9a5695f8d98828a850ca851afd73ea6 :0@0 myself,master - 0 0 0 connected vars currentEpoch 0 lastVoteEpoch 0
/ # redis-cli --cluster create 192.160.1.200:6379 192.160.1.201:6379 192.160.1.2 02:6379 192.160.1.203:6379 192.160.1.204:6379 192.160.1.205:6379 --cluster-repli cas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.160.1.204:6379 to 192.160.1.200:6379
Adding replica 192.160.1.205:6379 to 192.160.1.201:6379
Adding replica 192.160.1.203:6379 to 192.160.1.202:6379
M: a163aee6c9a5695f8d98828a850ca851afd73ea6 192.160.1.200:6379
slots:[0-5460] (5461 slots) master
M: 3e992bd376701b9f58dcb77082606d4796e4ba0a 192.160.1.201:6379
slots:[5461-10922] (5462 slots) master
M: 8480eccf92eb45d66d9344982460678444618ea9 192.160.1.202:6379
slots:[10923-16383] (5461 slots) master
S: a68508720542496d71cc675ebedf2ebaed956726 192.160.1.203:6379
replicates 8480eccf92eb45d66d9344982460678444618ea9
S: bc8175b99a6030e3029a8fddf3acb0ac550ed07d 192.160.1.204:6379
replicates a163aee6c9a5695f8d98828a850ca851afd73ea6
S: 8057c014c56c1f0bbf7db66bb5bca81744dce85a 192.160.1.205:6379
replicates 3e992bd376701b9f58dcb77082606d4796e4ba0a
Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
>>> Performing Cluster Check (using node 192.160.1.200:6379) M: a163aee6c9a5695f8d98828a850ca851afd73ea6 192.160.1.200:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 8057c014c56c1f0bbf7db66bb5bca81744dce85a 192.160.1.205:6379
slots: (0 slots) slave
replicates 3e992bd376701b9f58dcb77082606d4796e4ba0a
S: bc8175b99a6030e3029a8fddf3acb0ac550ed07d 192.160.1.204:6379
slots: (0 slots) slave
replicates a163aee6c9a5695f8d98828a850ca851afd73ea6
M: 8480eccf92eb45d66d9344982460678444618ea9 192.160.1.202:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: a68508720542496d71cc675ebedf2ebaed956726 192.160.1.203:6379
slots: (0 slots) slave
replicates 8480eccf92eb45d66d9344982460678444618ea9
M: 3e992bd376701b9f58dcb77082606d4796e4ba0a 192.160.1.201:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration. >>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
/#
注意:
1、每个Redis Cluster节点会占用两个TCP端口,一个监听客户端的请求,默认是6379,另外一个在前一个端口加上10000, 比如16379,来监听数据的请求,节点和 节点之间会监听第二个端口,用一套二进制协议来通信。
节点之间会通过套协议来进行失败检测,配置更新,```failover```认证等等。 为了保证节点之间正常的访问,需要注意防火墙的配置。
2、节点建立握手之后集群还不能正常工作,这时集群处于下线状态,所有的数据读写都被禁止
3、设置从节点作为一个完整的集群,需要主从节点,保证当它出现故障时可以自动进行故障转移。集群模式下,Redis 节点角色分为主节点和从节点。
首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。
使用 ``cluster replicate {nodeId}``命令让一个节点成为从节点。其中命令执行必须在对应的从节点上执行,将当前节点设置为 ``node_id`` 指定的节点的从节点
操作集群 -c
如果没有指定集群模式,那么会出现如下错误
/ # redis-cli
127.0.0.1:6379> set ma k
(error) MOVED 497 192.160.1.100:6379
/ # redis-cli -c
127.0.0.1:6379> set ma l
-> Redirected to slot [497] located at 192.160.1.100:6379 OK
所有命令
时间紧任务重,今天到这,乖乖的去搬砖了!