UDN-企业互联网技术人气社区

板块导航

浏览  : 832
回复  : 1

[技术交流] 【干货】Docker化高可用redis集群

[复制链接]
瞌睡虫的头像 楼主
发表于 2016-8-2 09:38:27 | 显示全部楼层 |阅读模式
  最近遇到部分系统因为redis服务挂掉,导致部分服务不可用。所以希望搭建一个redis集群镜像,把原先散落各处的redis服务器统一管理起来,并且保障高可用和故障自动迁移。

  
1.webp.jpg


  1redis集群分类

  大家都知道redis集群有两种:

  一种是redissentinel,高可用集群,同时只有一个master,各实例数据保持一致;

  一种是rediscluster,分布式集群,同时有多个master,数据分片部署在各个master上。基于我们的需求和redis本身技术的成熟度,本次要搭建的是redissentinel。

  关于它的介绍:

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

  监控(Monitoring):Sentinel会不断地检查你的主服务器和从服务器是否运作正常。

  提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。

  自动故障迁移(Automaticfailover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。

  2制作镜像

  整个集群可以分为一个master,N个slave,M个sentinel,本次以2个slave和3个sentinel为例:

  
2.webp.jpg


  首先增加redis.conf

  ##redis.conf##redis-0,默认为masterport$redis_port##授权密码,请各个配置保持一致##暂且禁用指令重命名##rename-command##开启AOF,禁用snapshotappendonlyyes#slaveofredis-master$master_portslave-read-onlyyes

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

  增加sentinel.conf

  port$sentinel_port

  dir"/tmp"##sentinel监控的redis的名字、IP和端口,最后一个数字是sentinel做决策的时候需要投赞同票的最少的sentinel的数量。sentinelmonitormymasterredis-master$master_port2##选项指定了在执行故障转移时,最多可以有多少个从服务器同时对新的主服务器进行同步,这个数字越小,完成故障转移所需的时间就越长。sentinelconfig-epochmymaster1

  sentinelleader-epochmymaster1

  sentinelcurrent-epoch1

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

  cd/data

  redis_role=$1echo$redis_roleif[$redis_role="master"];then

  echo"master"

  sed-i"s/\$redis_port/$redis_port/g"redis.conf

  redis-server/data/redis.confelif[$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.confelif[$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.confelse

  echo"unknowrole!"fi#ifend

  其中$redis_port和$master_port,$sentinel_port都是取自环境变量,通过Docker启动时候传入。

  编写Dockerfile

  FROMredis:3-alpineMAINTAINERvoidman<voidman>COPYShanghai/etc/localtimeCOPYredis.conf/data/redis.confCOPYsentinel.conf/data/sentinel.confCOPYstart.sh/data/start.shRUNchmod+x/data/start.shRUNchownredis:redis/data/*ENTRYPOINT["sh","/data/start.sh"]

  CMD["master"]

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

  build完成后,镜像只有15M。

  3启动

  采用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

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

  4测试

  java客户端测试(片段):

  //初始化

  Set<String>sentinels=newHashSet<String>(16);

  sentinels.add("redis-sentinel1.aliyun.com:16381");

  sentinels.add("redis-sentinel2.aliyun.com:16381");

  sentinels.add("redis-sentinel3.aliyun.com:16381");

  GenericObjectPoolConfigconfig=newGenericObjectPoolConfig();

  config.setBlockWhenExhausted(true);

  config.setMaxTotal(10);

  config.setMaxWaitMillis(1000l);

  config.setMaxIdle(25);

  config.setMaxTotal(32);

  jedisPool=newJedisSentinelPool("mymaster",sentinels,config);

  //不停读写

  while(true){

  AegisRedis.set("testSentinel","ok");

  System.err.println(AegisRedis.get("testSentinel"));

  Thread.sleep(3000);

  }

  sentinel挂掉测试

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

  严重:LostconnectiontoSentinelatredis-sentinel2.aliyun.com:16381.Sleeping5000msandretrying.

  数据正常读写,当把所有sentinel都kill掉后,任然能够正常读写,并且不断在重连sentinel,说明sentinel只是重新选取master和failover时才顶用,一旦选好后,及时全挂了,redis也能照常运行。

  而如果这是重新去初始化redisPool的时候,会报错:

  Causedby:redis.clients.jedis.exceptions.JedisConnectionException:Allsentinelsdown,cannotdeterminewhereismymastermasterisrunning...

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

  slave挂掉测试

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

  2016/4/14下午4:31:336:M14Apr16:31:33.698#Connectionwithslaveip_address:16380lost.

  sentinel也有日志:

  2016/4/14下午4:30:397:X14Apr16:30:39.852#-sdownslaveip_address:16380ip_address16380@mymasterip_address163792016/4/14下午4:32:037:X14Apr16:32:03.786#+sdownslaveip_address:16380ip_address16380@mymasterip_address16379

  此时恢复那台slave

  2016/4/14下午4:36:579:S14Apr16:36:57.441*ConnectingtoMASTERredis-master:163792016/4/14下午4:36:579:S14Apr16:36:57.449*MASTER<->SLAVEsyncstarted2016/4/14下午4:36:579:S14Apr16:36:57.449*NonblockingconnectforSYNCfiredtheevent.2016/4/14下午4:36:579:S14Apr16:36:57.449*MasterrepliedtoPING,replicationcancontinue...2016/4/14下午4:36:579:S14Apr16:36:57.449*Partialresynchronizationnotpossible(nocachedmaster)2016/4/14下午4:36:579:S14Apr16:36:57.450*Fullresyncfrommaster:0505a8e1049095ce597a137ae1161ed4727533d3:845582016/4/14下午4:36:579:S14Apr16:36:57.462*SLAVEOFip_address:16379enabled(userrequestfrom'id=3addr=ip_address2:57122fd=10name=sentinel-11d82028-cmdage=0idle=0flags=xdb=0sub=0psub=0multi=3qbuf=0qbuf-free=32768obl=36oll=0omem=0events=rwcmd=exec')2016/4/14下午4:36:579:S14Apr16:36:57.462#CONFIGREWRITEexecutedwithsuccess.2016/4/14下午4:36:589:S14Apr16:36:58.451*ConnectingtoMASTERip_address:163792016/4/14下午4:36:589:S14Apr16:36:58.451*MASTER<->SLAVEsyncstarted2016/4/14下午4:36:589:S14Apr16:36:58.451*NonblockingconnectforSYNCfiredtheevent.2016/4/14下午4:36:589:S14Apr16:36:58.451*MasterrepliedtoPING,replicationcancontinue...2016/4/14下午4:36:589:S14Apr16:36:58.451*Partialresynchronizationnotpossible(nocachedmaster)2016/4/14下午4:36:589:S14Apr16:36:58.453*Fullresyncfrommaster:0505a8e1049095ce597a137ae1161ed4727533d3:847212016/4/14下午4:36:589:S14Apr16:36:58.532*MASTER<->SLAVEsync:receiving487bytesfrommaster2016/4/14下午4:36:589:S14Apr16:36:58.532*MASTER<->SLAVEsync:Flushingolddata2016/4/14下午4:36:589:S14Apr16:36:58.532*MASTER<->SLAVEsync:LoadingDBinmemory2016/4/14下午4:36:589:S14Apr16:36:58.532*MASTER<->SLAVEsync:Finishedwithsuccess2016/4/14下午4:36:589:S14Apr16:36:58.537*Backgroundappendonlyfilerewritingstartedbypid122016/4/14下午4:36:589:S14Apr16:36:58.563*AOFrewritechildaskstostopsendingdiffs.2016/4/14下午4:36:5812:C14Apr16:36:58.563*Parentagreedtostopsendingdiffs.FinalizingAOF...2016/4/14下午4:36:5812:C14Apr16:36:58.563*Concatenating0.00MBofAOFdiffreceivedfromparent.2016/4/14下午4:36:5812:C14Apr16:36:58.563*SYNCappendonlyfilerewriteperformed2016/4/14下午4:36:5812:C14Apr16:36:58.564*AOFrewrite:0MBofmemoryusedbycopy-on-write2016/4/14下午4:36:589:S14Apr16:36:58.652*BackgroundAOFrewriteterminatedwithsuccess2016/4/14下午4:36:589:S14Apr16:36:58.653*ResidualparentdiffsuccessfullyflushedtotherewrittenAOF(0.00MB)2016/4/14下午4:36:589:S14Apr16:36:58.653*BackgroundAOFrewritefinishedsuccessfully

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

  挂掉master

  此时客户端出现异常:

  Causedby:redis.clients.jedis.exceptions.JedisConnectionException:java.net.ConnectException:Connectionrefused

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

  2016/4/14下午4:40:3613:X14Apr16:40:36.162#+sdownmastermymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.233#+odownmastermymasterip_address16379#quorum2/22016/4/14下午4:40:3613:X14Apr16:40:36.233#+new-epoch102016/4/14下午4:40:3613:X14Apr16:40:36.233#+try-failovermastermymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.238#+vote-for-leader0a632ec0550401e66486846b521ad2de8c345695102016/4/14下午4:40:3613:X14Apr16:40:36.249#ip_address2:16381votedfor0a632ec0550401e66486846b521ad2de8c345695102016/4/14下午4:40:3613:X14Apr16:40:36.261#ip_address3:16381votedfor4e590c09819a793faf1abf185a0d0db07dc89f6a102016/4/14下午4:40:3613:X14Apr16:40:36.309#+elected-leadermastermymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.309#+failover-state-select-slavemastermymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.376#+selected-slaveslaveip_address:16380ip_address16380@mymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.376*+failover-state-send-slaveof-nooneslaveip_address:16380ip_address16380@mymasterip_address163792016/4/14下午4:40:3613:X14Apr16:40:36.459*+failover-state-wait-promotionslaveip_address:16380ip_address16380@mymasterip_address163792016/4/14下午4:40:3713:X14Apr16:40:37.256#+promoted-slaveslaveip_address:16380ip_address16380@mymasterip_address163792016/4/14下午4:40:3713:X14Apr16:40:37.256#+failover-state-reconf-slavesmastermymasterip_address163792016/4/14下午4:40:3713:X14Apr16:40:37.303*+slave-reconf-sentslaveip_address3:16380ip_address316380@mymasterip_address163792016/4/14下午4:40:3813:X14Apr16:40:38.288*+slave-reconf-inprogslaveip_address3:16380ip_address316380@mymasterip_address163792016/4/14下午4:40:3813:X14Apr16:40:38.289*+slave-reconf-doneslaveip_address3:16380ip_address316380@mymasterip_address163792016/4/14下午4:40:3813:X14Apr16:40:38.378*+slave-reconf-sentslaveip_address2:16380ip_address216380@mymasterip_address163792016/4/14下午4:40:3813:X14Apr16:40:38.436#-odownmastermymasterip_address163792016/4/14下午4:40:3913:X14Apr16:40:39.368*+slave-reconf-inprogslaveip_address2:16380ip_address216380@mymasterip_address163792016/4/14下午4:40:3913:X14Apr16:40:39.368*+slave-reconf-doneslaveip_address2:16380ip_address216380@mymasterip_address163792016/4/14下午4:40:3913:X14Apr16:40:39.424#+failover-endmastermymasterip_address163792016/4/14下午4:40:3913:X14Apr16:40:39.424#+switch-mastermymasterip_address16379ip_address163802016/4/14下午4:40:3913:X14Apr16:40:39.425*+slaveslaveip_address3:16380ip_address316380@mymasterip_address163802016/4/14下午4:40:3913:X14Apr16:40:39.425*+slaveslaveip_address2:16380ip_address216380@mymasterip_address163802016/4/14下午4:40:3913:X14Apr16:40:39.425*+slaveslaveip_address:16379ip_address16379@mymasterip_address16380

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

  5总结

  总的来说,只要集群中有一台redis实例存活,集群就能对外提供服务,而sentinel只会在master或slave挂掉才会有实际的作用。

  这次的镜像大小只有15M,非常小。采用启动时配置角色和端口,包括master,slave,和sentinel3个角色,通过服务编排启动一个redis集群。

文章来源:云栖社区
文章作者:yunnotes

发表于 2016-8-2 10:59:06 | 显示全部楼层
赞,帖子很棒!
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部