Redis做为当前很是热门的内存型数据结构存储,可用于数据存储,缓存和消息代理等。本文将讲解如何基于docker搭建Redis集群,Redis的集群设计包括两个部分:主从复制和哈希Slot。node
主从复制在数据库中很常见,通常用来作读写分离,Redis中也是如此。要求只有1个Master(主节点),能够有N个slaver(从节点),并且Slaver也能够有本身的Slaver,因为这种主从的关系决定他们是在配置阶段就要指定他们的上下级关系,而不是Zookeeper那种平行关系是自主推优出来的。git
读写分离,Master只负责写和同步数据给Slaver,Slaver承担了被读的任务,因此Slaver的扩容只能提升读效率不能提升写效率。github
Slaver先将Master那边获取到的信息压入磁盘,再load进内存,client端是从内存中读取信息的。当一个新的Slaver加入到这个集群时,会主动找Master来拜码头,Master发现新的小弟后将全量数据发送给新的Slaver,数据量越大性能消耗也就越大,因此尽可能避免在运行时作Slaver的扩容。redis
哈希Slot名字上可能很差理解,其实就是数据库中的“水平划分”。若是你以前有了解过数据库的表分区的话,就会发现下来对于哈希Slot的描述,就和数据库表分区里面的“HASH分区”原理上大体相同。docker
对象保存到Redis以前先通过CRC16哈希到一个指定的Node上,例如图中Object4最终Hash到了Node1上。数据库
每一个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,不然会致使对象重复存储或没法存储。缓存
Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位作数据的迁移。例如Node1若是掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,因为Node2和Node3自己维护的Slot还会在本身身上不会被从新分配,因此迁移过程当中不会影响到 5641-16384 Slot段的使用。数据结构
看到这里你们也就发现了,主从和哈希的设计优缺点正好是相互弥补的,将两者结合在一块儿,就是Redis集群的终极形态,先Hash分逻辑节点,而后每一个逻辑节点内部是主从,如图:并发
默认咱们已经有了docker环境,如今开始基于docker安装Redis集群。redis 5.0 版本以前,网上有不少教程都是经过redis-trib.rb 来建立集群,可是redis 5.0 版本之后,就只能经过 redis-cli 来实现。本文即经过 redis-cli 建立集群,由于redis集群的节点选举方式是须要半数以上的master经过,因此建议建立奇数个节点。本例中建立3个Master节点,并为每一个Master节点各分配1个Slave节点。app
首先须要找一份原始的redis.conf文件,将其重命名为:redis-cluster.tmpl,并配置以下几个参数,此文件的目的是生成每个redis实例的redis.conf:
[root@kerry2 redis]# wget https://raw.githubusercontent.com/antirez/redis/5.0/redis.conf [root@kerry2 redis]# mv redis.conf redis-cluster.tmpl
vi redis-cluster.tmpl
# bind 127.0.0.1 protected-mode no port ${PORT} daemonize no dir /data/redis appendonly yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000
而后执行下列脚本,给3个Master和3个Slave建立各自的挂载卷目录
# 建立 master 和 slave 文件夹 for port in `seq 7700 7705`; do ms="master" if [ $port -ge 7703 ]; then ms="slave" fi mkdir -p ./$ms/$port/ && mkdir -p ./$ms/$port/data \ && PORT=$port envsubst < ./redis-cluster.tmpl > ./$ms/$port/redis.conf; done
当前目录结构为
[root@kerry2 redis]# tree . ├── master │ ├── 7700 │ │ ├── data │ │ └── redis.conf │ ├── 7701 │ │ ├── data │ │ └── redis.conf │ └── 7702 │ ├── data │ └── redis.conf ├── redis-cluster.tmpl ├── slave ├── 7703 │ ├── data │ └── redis.conf ├── 7704 │ ├── data │ └── redis.conf └── 7705 ├── data └── redis.conf
假设咱们只考虑单纯的docker环境,并没有docker-compose和k8s之类的服务编排,每一个redis容器之间必需要保证通信,能够经过建立docker network。(使用微服务编排的状况,后续再讨论)
[root@kerry2 redis]# docker network create redis-cluster-net
如今咱们就能够运行docker redis 的 master 和 slave 实例了
# 运行docker redis 的 master 和 slave 实例 for port in `seq 7700 7705`; do ms="master" if [ $port -ge 7703 ]; then ms="slave" fi docker run -d -p $port:$port -p 1$port:1$port \ -v $PWD/$ms/$port/redis.conf:/data/redis.conf \ -v $PWD/$ms/$port/data:/data/redis \ --restart always --name redis-$ms-$port --net redis-cluster-net \ redis redis-server /data/redis.conf; done
查看已建立的redis容器
[root@kerry2 redis]# docker ps |grep redis 010f295922e3 redis "docker-entrypoint..." 41 seconds ago Up 36 seconds 0.0.0.0:7705->7705/tcp, 6379/tcp, 0.0.0.0:17705->17705/tcp redis-slave-7705 b5d89f0469ee redis "docker-entrypoint..." 45 seconds ago Up 40 seconds 0.0.0.0:7704->7704/tcp, 6379/tcp, 0.0.0.0:17704->17704/tcp redis-slave-7704 f710e805fe96 redis "docker-entrypoint..." 50 seconds ago Up 45 seconds 0.0.0.0:7703->7703/tcp, 6379/tcp, 0.0.0.0:17703->17703/tcp redis-slave-7703 b187603aec65 redis "docker-entrypoint..." 55 seconds ago Up 50 seconds 0.0.0.0:7702->7702/tcp, 6379/tcp, 0.0.0.0:17702->17702/tcp redis-master-7702 ea635bd8b3dc redis "docker-entrypoint..." About a minute ago Up 55 seconds 0.0.0.0:7701->7701/tcp, 6379/tcp, 0.0.0.0:17701->17701/tcp redis-master-7701 f02a468572ca redis "docker-entrypoint..." About a minute ago Up About a minute 0.0.0.0:7700->7700/tcp, 6379/tcp, 0.0.0.0:17700->17700/tcp redis-master-7700
经过redis-cli 命令构建集群,随便找一个redis容器,运行redis-cli --cluster create --cluster-replicas 1 ip:port 命令便可
[root@kerry2 redis]# docker exec -it redis-master-7700 redis-cli --cluster create 宿主ip:7700 宿主ip:7701 宿主ip:7702 宿主ip:7703 宿主ip:7704 宿主ip:7705 --cluster-replicas 1 # 提示输入yes后,构建集群成功
记住构建集群时,要保证节点redis数据为空,不然会出现下列错误。
[ERR] Node 172.18.0.2:7700 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
集群搭建完成后,咱们经过 redis-cli 命令链接集群节点验证一下。redis 集群节点的链接命令是经过 redis-cli -c -h ${ip} -p ${port}
[root@kerry2 ~]# docker exec -it redis-master-7700 redis-cli -c -h 宿主机ip -p 7700 宿主机ip :7700> 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:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:23838 cluster_stats_messages_pong_sent:24283 cluster_stats_messages_sent:48121 cluster_stats_messages_ping_received:24278 cluster_stats_messages_pong_received:23838 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:48121 宿主机ip :7700> cluster nodes 056b99fe5993510c264e3e9a1fd1a04144da6a7b 172.18.0.2:7700@17700 myself,master - 0 1558578383000 1 connected 0-5460 73376f00b2837309d77b82d98984715f44eb2dcf 宿主机ip:7704@17704 slave 056b99fe5993510c264e3e9a1fd1a04144da6a7b 0 1558578388562 5 connected 20e4b509a54fb17ed8d0f6c21bbc8693ab715ee7 宿主机ip:7705@17705 slave 1bcb0a6ac770e261c5b0de21cfe26b0bd614590e 0 1558578386658 6 connected 1bcb0a6ac770e261c5b0de21cfe26b0bd614590e 宿主机ip:7701@17701 master - 0 1558578386579 2 connected 5461-10922 07a4c19848d578ac339bfaf741e1edfd0b010b08 宿主机ip:7702@17702 master - 0 1558578388661 3 connected 10923-16383 506271ed3f0657f05f439108d9372b638d2c4571 宿主机ip:7703@17703 slave 07a4c19848d578ac339bfaf741e1edfd0b010b08 0 1558578386000 4 connected
能够看到经过 "cluster info"命令看到集群的基本信息,全部的slot (16384) 都分配完毕。而后经过 "cluster nodes" 命令查看到每一个master节点的slot分配的区域。至此,redis集群基本安装成功。
集群
cluster info :打印集群的信息 cluster nodes :列出集群当前已知的全部节点( node),以及这些节点的相关信息。
节点
cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。 cluster forget <node_id> :从集群中移除 node_id 指定的节点。 cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点。 cluster saveconfig :将节点的配置文件保存到硬盘里面。
槽(slot)
cluster addslots <slot> [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。 cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派。 cluster flushslots :移除指派给当前节点的全部槽,让当前节点变成一个没有指派任何槽的节点。 cluster setslot <slot> node <node_id> :将槽 slot 指派给 node_id 指定的节点,若是槽已经指派给 另外一个节点,那么先让另外一个节点删除该槽>,而后再进行指派。 cluster setslot <slot> migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。 cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。 cluster setslot <slot> stable :取消对槽 slot 的导入( import)或者迁移( migrate)。 键 cluster keyslot <key> :计算键 key 应该被放置在哪一个槽上。 cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量。 cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键
(1)redis-cluster 把全部的物理节点映射到[ 0 ~ 16383 ]个slot(哈希槽)上,cluster负责维护 node<->slot<->value。
(2)集群任意一个节点中,若是master挂掉,可是还有slaver,slave将自动升为 master,系统正常。
(3)集群任意一个节点中,若是master挂掉,而且没有slaver,集群将进入fail状态。
(4)若是集群超过半数以上节点的master挂掉,不论是否有slaver,集群都将进入fail状态。
(5)节点判断是否失效的选举,是集群中全部的master参与的,若是半数以上的master节点与当前被检测的master节点通信检测超时(cluster-node-timerout),就认为当前master节点挂掉了。