Docker化高可用redis集群

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 最近遇到部分系统因为redis服务挂掉,导致部分服务不可用。所以希望搭建一个redis集群镜像,把原先散落各处的redis服务器统一管理起来,并且保障高可用和故障自动迁移。 一:redis集群分类 大家都知道redis集群有两种,一种是redis sentinel,高可用集群,同时只有一

最近遇到部分系统因为redis服务挂掉,导致部分服务不可用。所以希望搭建一个redis集群镜像,把原先散落各处的redis服务器统一管理起来,并且保障高可用和故障自动迁移。
screenshot

一:redis集群分类

大家都知道redis集群有两种,一种是redis sentinel,高可用集群,同时只有一个master,各实例数据保持一致;一种是redis cluster,分布式集群,同时有多个master,数据分片部署在各个master上。基于我们的需求和redis本身技术的成熟度,本次要搭建的是redis sentinel。

关于它的介绍:
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:

  • 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

二:制作镜像

整个集群可以分为一个master,N个slave,M个sentinel,本次以2个slave和3个sentinel为例:
screenshot
首先增加redis.conf

##redis.conf
##redis-0,默认为master
port $redis_port
##授权密码,请各个配置保持一致
##暂且禁用指令重命名
##rename-command
##开启AOF,禁用snapshot
appendonly yes
#slaveof redis-master $master_port
slave-read-only yes
AI 代码解读

默认为master,#slaveof 注释去掉后变为slave,这里固化了master的域名 redis-master
增加sentinel.conf

port $sentinel_port
dir "/tmp"
##sentinel监控的redis的名字、IP和端口,最后一个数字是sentinel做决策的时候需要投赞同票的最少的sentinel的数量。
sentinel monitor mymaster redis-master $master_port 2
##选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel current-epoch 1
AI 代码解读

增加启动脚本,根据入参判断启动master,slave,sentinel

cd /data
redis_role=$1
echo $redis_role
if [ $redis_role = "master" ] ; then
    echo "master" 
    sed -i "s/\$redis_port/$redis_port/g" redis.conf
    redis-server /data/redis.conf
elif [ $redis_role = "slave" ] ; then   
    echo "slave" 
    sed -i "s/\$redis_port/$redis_port/g" redis.conf
    sed -i "s/#slaveof/slaveof/g" redis.conf
    sed -i "s/\$master_port/$master_port/g" redis.conf
    redis-server /data/redis.conf
elif [ $redis_role = "sentinel" ] ; then 
    echo "sentinel" 
    sed -i "s/\$sentinel_port/$sentinel_port/g" sentinel.conf
    sed -i "s/\$master_port/$master_port/g" sentinel.conf
    redis-sentinel /data/sentinel.conf
else 
    echo "unknow role!" 
fi     #ifend
AI 代码解读

其中redisportmaster_port,$sentinel_port都是取自环境变量,通过Docker启动时候传入。
编写Dockerfile

FROM redis:3-alpine
MAINTAINER voidman <voidman>

COPY Shanghai /etc/localtime
COPY redis.conf /data/redis.conf
COPY sentinel.conf /data/sentinel.conf
COPY start.sh /data/start.sh
RUN chmod +x /data/start.sh
RUN chown redis:redis /data/*
ENTRYPOINT ["sh","/data/start.sh"] 
CMD ["master"]
AI 代码解读

选取redis-alpine镜像作为基础镜像,因为它非常小,只有9M,修改时区和把一些配置拷贝进去后,变更下权限和用户组,因为基础镜像是redis用户组。ENTRYPOINTCMD组合,默认以master方式启动。
build完成后,镜像只有15M。

三:启动

采用docker-compose格式:

redis-master-host:
  environment:
    redis_port: '16379'
  labels:
    io.rancher.container.pull_image: always
  tty: true
  image: xxx.aliyun.com:5000/aegis-redis-ha:1.0
  stdin_open: true
  net: host
redis-slaves:
  environment:
    master_port: '16379'
    redis_port: '16380'
  labels:
    io.rancher.scheduler.affinity:container_label_soft_ne: name=slaves
    io.rancher.container.pull_image: always
    name: slaves
  tty: true
  command:
  - slave
  image: xxx.aliyun.com:5000/aegis-redis-cluster:1.0
  stdin_open: true
  net: host
redis-sentinels:
  environment:
    master_port: '16379'
    sentinel_port: '16381'
  labels:
    io.rancher.container.pull_image: always
    name: sentinels
    io.rancher.scheduler.affinity:container_label_ne: name=sentinels
  tty: true
  command:
  - sentinel
  image: xxx.aliyun.com:5000/aegis-redis-cluster:1.0
  stdin_open: true
  net: host

AI 代码解读

首先启动master,传入端口16379,host模式,在启动slave,成为16379 master 的slave,并且设置调度策略为尽可能分散的方式,sentinels也类似。

四:测试

java客户端测试(片段):

//初始化
 Set<String> sentinels = new HashSet<String>(16);
            sentinels.add("redis-sentinel1.aliyun.com:16381");
            sentinels.add("redis-sentinel2.aliyun.com:16381");
            sentinels.add("redis-sentinel3.aliyun.com:16381");
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            config.setBlockWhenExhausted(true);
            config.setMaxTotal(10);
            config.setMaxWaitMillis(1000l);
            config.setMaxIdle(25);
            config.setMaxTotal(32);
            jedisPool = new JedisSentinelPool("mymaster", sentinels, config);
AI 代码解读
//不停读写
 while (true) {
                AegisRedis.set("testSentinel", "ok");
                System.err.println(AegisRedis.get("testSentinel"));
                Thread.sleep(3000);
        }
AI 代码解读

sentinel挂掉测试

此时kill掉一台sentinel,会提示:

严重: Lost connection to Sentinel at redis-sentinel2.aliyun.com:16381. Sleeping 5000ms and retrying.
AI 代码解读

数据正常读写,当把所有sentinel都kill掉后,任然能够正常读写,并且不断在重连sentinel,说明sentinel只是重新选取master和failover时才顶用,一旦选好后,及时全挂了,redis也能照常运行。
而如果这是重新去初始化redisPool的时候,会报错:

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: All sentinels down, cannot determine where is mymaster master is running...
AI 代码解读

sentinel之间不需要相互配置,大家都通过订阅master和slave的sentinel:hello 频道,上报自己的ip,port等信息,然后每个sentinel就都维护了一份已知的sentinel列表。

slave 挂掉测试

此时kill掉一台slave,对客户端没有任何影响,也不会有感知,master会有失联日志:

2016/4/14 下午4:31:336:M 14 Apr 16:31:33.698 # Connection with slave ip_address:16380 lost.
AI 代码解读

sentinel也有日志:

2016/4/14 下午4:30:397:X 14 Apr 16:30:39.852 # -sdown slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
2016/4/14 下午4:32:037:X 14 Apr 16:32:03.786 # +sdown slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
AI 代码解读

此时恢复那台slave

2016/4/14 下午4:36:579:S 14 Apr 16:36:57.441 * Connecting to MASTER redis-master:16379
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.449 * MASTER <-> SLAVE sync started
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.449 * Non blocking connect for SYNC fired the event.
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.449 * Master replied to PING, replication can continue...
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.449 * Partial resynchronization not possible (no cached master)
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.450 * Full resync from master: 0505a8e1049095ce597a137ae1161ed4727533d3:84558
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.462 * SLAVE OF ip_address:16379 enabled (user request from 'id=3 addr=ip_address2:57122 fd=10 name=sentinel-11d82028-cmd age=0 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0 omem=0 events=rw cmd=exec')
2016/4/14 下午4:36:579:S 14 Apr 16:36:57.462 # CONFIG REWRITE executed with success.
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.451 * Connecting to MASTER ip_address:16379
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.451 * MASTER <-> SLAVE sync started
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.451 * Non blocking connect for SYNC fired the event.
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.451 * Master replied to PING, replication can continue...
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.451 * Partial resynchronization not possible (no cached master)
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.453 * Full resync from master: 0505a8e1049095ce597a137ae1161ed4727533d3:84721
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.532 * MASTER <-> SLAVE sync: receiving 487 bytes from master
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.532 * MASTER <-> SLAVE sync: Flushing old data
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.532 * MASTER <-> SLAVE sync: Loading DB in memory
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.532 * MASTER <-> SLAVE sync: Finished with success
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.537 * Background append only file rewriting started by pid 12
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.563 * AOF rewrite child asks to stop sending diffs.
2016/4/14 下午4:36:5812:C 14 Apr 16:36:58.563 * Parent agreed to stop sending diffs. Finalizing AOF...
2016/4/14 下午4:36:5812:C 14 Apr 16:36:58.563 * Concatenating 0.00 MB of AOF diff received from parent.
2016/4/14 下午4:36:5812:C 14 Apr 16:36:58.563 * SYNC append only file rewrite performed
2016/4/14 下午4:36:5812:C 14 Apr 16:36:58.564 * AOF rewrite: 0 MB of memory used by copy-on-write
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.652 * Background AOF rewrite terminated with success
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.653 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
2016/4/14 下午4:36:589:S 14 Apr 16:36:58.653 * Background AOF rewrite finished successfully
AI 代码解读

马上从master恢复数据,最终保持一致。

挂掉master

此时客户端出现异常:

Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused
AI 代码解读

并且sentinel开始发现这个情况,首先主观判断master(ip_address 16379)已经挂了,然后通过询问其他sentinel,是否master挂了,判断得到2个sentinel都认为master挂了(这里的2个为之前sentinel.conf中配置,一般建议选择多余一半的sentinel的个数),此时客观判断master挂了。开始新的一轮master投票,投票给了ip_address:16380,进行failover,完成后切换至新主。并且通知其余slave,有了新主。以下是详细日志:注意的是,再选取过程中,出现了短暂的客户端不可用。

2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.162 # +sdown master mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.233 # +odown master mymaster ip_address 16379 #quorum 2/2
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.233 # +new-epoch 10
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.233 # +try-failover master mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.238 # +vote-for-leader 0a632ec0550401e66486846b521ad2de8c345695 10
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.249 # ip_address2:16381 voted for 0a632ec0550401e66486846b521ad2de8c345695 10
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.261 # ip_address3:16381 voted for 4e590c09819a793faf1abf185a0d0db07dc89f6a 10
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.309 # +elected-leader master mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.309 # +failover-state-select-slave master mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.376 # +selected-slave slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.376 * +failover-state-send-slaveof-noone slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3613:X 14 Apr 16:40:36.459 * +failover-state-wait-promotion slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3713:X 14 Apr 16:40:37.256 # +promoted-slave slave ip_address:16380 ip_address 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3713:X 14 Apr 16:40:37.256 # +failover-state-reconf-slaves master mymaster ip_address 16379
2016/4/14 下午4:40:3713:X 14 Apr 16:40:37.303 * +slave-reconf-sent slave ip_address3:16380 ip_address3 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3813:X 14 Apr 16:40:38.288 * +slave-reconf-inprog slave ip_address3:16380 ip_address3 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3813:X 14 Apr 16:40:38.289 * +slave-reconf-done slave ip_address3:16380 ip_address3 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3813:X 14 Apr 16:40:38.378 * +slave-reconf-sent slave ip_address2:16380 ip_address2 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3813:X 14 Apr 16:40:38.436 # -odown master mymaster ip_address 16379
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.368 * +slave-reconf-inprog slave ip_address2:16380 ip_address2 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.368 * +slave-reconf-done slave ip_address2:16380 ip_address2 16380 @ mymaster ip_address 16379
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.424 # +failover-end master mymaster ip_address 16379
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.424 # +switch-master mymaster ip_address 16379 ip_address 16380
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.425 * +slave slave ip_address3:16380 ip_address3 16380 @ mymaster ip_address 16380
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.425 * +slave slave ip_address2:16380 ip_address2 16380 @ mymaster ip_address 16380
2016/4/14 下午4:40:3913:X 14 Apr 16:40:39.425 * +slave slave ip_address:16379 ip_address 16379 @ mymaster ip_address 16380
AI 代码解读

此时若老master恢复后,发现自己被sentinel定义为新master的slave,所以只能乖乖的变成slave,从master同步一下数据,保证数据一致性。

五:总结

总的来说,只要集群中有一台redis实例存活,集群就能对外提供服务,而sentinel只会在master或slave挂掉才会有实际的作用。
这次的镜像大小只有15M,非常小。采用启动时配置角色和端口,包括master,slave,和sentinel3个角色,通过服务编排启动一个redis集群。

参考资料:http://www.redis.cn/topics/sentinel.html

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
打赏
0
0
0
5
16
分享
相关文章
Docker部署RocketMQ5.2.0集群
本文详细介绍了如何使用Docker和Docker Compose部署RocketMQ 5.2.0集群。通过创建配置文件、启动集群和验证容器状态,您可以快速搭建起一个RocketMQ集群环境。希望本文能够帮助您更好地理解和应用RocketMQ,提高消息中间件的部署和管理效率。
288 91
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 +  无锁架构 +  EDA架构  + 异步日志 + 集群架构
Docker 部署 Redis
在使用 Docker 部署 Redis 时,为实现数据持久化,需正确挂载容器内的数据目录到宿主机。推荐命令如下: ``` docker run -d --name redis -v /mnt/data/redis:/data -p 6379:6379 redis ``` 该命令将宿主机的 `/mnt/data/redis` 目录挂载到容器的 `/data` 目录,确保 Redis 数据持久化。此路径更通用,适合大多数场景。避免使用不匹配的挂载路径,如 `/var/lib/redis` 或 `/mnt/data/redis` 到非默认目录,以防止数据无法正确持久化。
Docker——阿里云服务器利用docker搭建redis集群
本文详细记录了使用Docker搭建Redis集群的过程,包括检查Docker和Docker Compose的安装、创建Redis配置文件、编写`docker-compose.yml`文件、启动Redis节点、创建Redis集群的具体步骤,以及最终的验证方法。文章还提供了在多服务器环境下搭建Redis集群的注意事项,帮助读者全面了解 Redis 集群的部署流程。
111 12
Redis分片集群中数据是怎么存储和读取的 ?
Redis集群采用的算法是哈希槽分区算法。Redis集群中有16384个哈希槽(槽的范围是 0 -16383,哈希槽),将不同的哈希槽分布在不同的Redis节点上面进行管理,也就是说每个Redis节点只负责一部分的哈希槽。在对数据进行操作的时候,集群会对使用CRC16算法对key进行计算并对16384取模(slot = CRC16(key)%16383),得到的结果就是 Key-Value 所放入的槽,通过这个值,去找到对应的槽所对应的Redis节点,然后直接到这个对应的节点上进行存取操作
Redis原理—3.复制、哨兵和集群
详细介绍了Redis的复制原理、哨兵原理和集群原理。
《docker高级篇(大厂进阶):1.Docker复杂安装详说》包括:安装mysql主从复制、安装redis集群
《docker高级篇(大厂进阶):1.Docker复杂安装详说》包括:安装mysql主从复制、安装redis集群
160 14
搭建高可用及负载均衡的Redis
通过本文介绍的高可用及负载均衡Redis架构,可以有效提升Redis服务的可靠性和性能。主从复制、哨兵模式、Redis集群以及负载均衡技术的结合,使得Redis系统在应对高并发和数据一致性方面表现出色。这些配置和技术不仅适用于小型应用,也能够支持大规模企业级应用的需求。希望本文能够为您的Redis部署提供实用指导和参考。
278 9
《docker基础篇:8.Docker常规安装简介》包括:docker常规安装总体步骤、安装tomcat、安装mysql、安装redis
《docker基础篇:8.Docker常规安装简介》包括:docker常规安装总体步骤、安装tomcat、安装mysql、安装redis
182 7
docker高级篇(大厂进阶):安装redis集群
docker高级篇(大厂进阶):安装redis集群
313 24

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等