Redis探索之路

1. 写在前面

对于Redis相信各位程序猿童鞋都是不陌生的。这款使用ANSI C编写的,基于内存亦可持久化的数据库已经成为后端数据缓存炙手可热的首选。但Redis自身的短板也被大家所诟病,例如:在现今动不动就24核32核的服务器,Redis单线程的设计就无法利用好CPU资源;基于内存Redis在内存使用量巨大时,管理和维护显得十分困难和低效等。相信聪明的童鞋们一定会想到通过单机多实例Redis然后采用客户端分片的模式的方式解决上面的问题,确实这也是常见的方案。今天将给大家讲讲我们在这方面上的心路历程~

2. 最开始的Redis

Redis最早在我们线上的正式应用是一款儿童游戏。当时的游戏数据存储采用MySQL。数据库做了20个分库,存储也做了RAID阵列,游戏在线量较大时,数据库的IO压力依然非常大。为了减轻MySQL的压力,我们采用Redis对数据做了缓存,这也是我们在线上的第一次尝试这款nosql的新贵。

作为早期的架构方案设计也比较简单,就是四台服务器构成两组Redis主从的模型:

  • 单机内存32G,开20个Redis实例。
  • 主库不做AOF,也不做快照,每天凌晨定时做快照并异地迁移,从库开启AOF并1秒落地。
  • 主库每5分钟插入一个时间点,方便从库AOF记录时间点,用于定点还原。

虽然架构简单不过确实极大地缓解了MySQL的压力,但弊端也开始显露:如果主Redis挂了,则借助从库手动做定点还原,还有程序层面涉及的一系列操作。而且故障无法自动转移,在线高时故障会给用户带来相当不好的体验,更不提运维攻城狮自己的苦逼了。

如果能实现故障自动转移,那也是极好的。那时年少,对未来充满了期待。

3.  sentinel的出现

业务高可用、故障自动转移一直是咱们运维攻城狮极力追求的。我们也是一步一个脚印地前进,努力往这目标迈进,大家有兴趣有阅读本公众号之前的文章《双机房灾备架构搭建实践》。

随着业务的发展,Redis开始越来越多地被我们所使用。尤其是在公共平台Redis几乎就是缓存的不二之选,同时对Redis故障自动转移的需求也越来越迫切。于是我们一开始采用官方推荐的Redis实例的监控管理工具:Redis-sentinel。

3.1 Redis sentinel剖析

Sentinel是多个Redis实例的监控管理工具,主要实现以下三个功能:

  • Monitoring: Sentinel 会不断地检查主Redis和从Redis是否运作正常。
  • Notification: 被监控的某个 Redis出现问题时,Sentinel 可以通过 API发送通知。
  • Automatic failover: 当主Redis不能正常工作时, Sentinel 会自动进行故障迁移操作,将Redis主从的角色进行互换。

3.2 Redis sentinel应用

Sentinel第一次在线上的应用是一款公共社交平台。

Redis用于DB缓存,通过同内网服务器搭建Redis主从模型,实现主Redis读写、从Redis落地。借助Sentinel的Automatic failover,主Redis故障时,自动切换Redis主从。我们的设计方案如下:

从上图可以看出,程序读写Redis都是通过VIP实现的。Keepalive借助vrrp_script检测当前Redis master是否异常以决定是否转移VIP,Sentinel实现故障时主从自动切换。Redis master不备份,定时脚本每晚检查当前Redis是否为slave,然后进行bgsave。由于设计为互备,主从故障切换后可以不进行回切。

Sentinel + keepalive在实现Redis故障转移这块确实帮了我们很大的忙,但在实际应用时也需要遇到了以下问题:

  • Sentinel存在网络分区的问题,会受到网络影响造成误判。
  • 当面对业务规模较大,甚至需要进行扩容时,Sentinel的功能就显得有些捉襟见肘。
  • Keepalive的VIP切换存在约40ms延迟,程序设计前期需要考虑网络链接的异常处理。

4. Redis cluster践行

4.1 Redis cluster窥视

Redis cluster是一个去中心化、多实例Redis间进行数据共享的集群。由于被设计为无中心节点和无代理节点,Redis cluster可以实现集群节点的在线线性伸缩,并通过主从复制模型来提供一定程度的高可用行,在实际环境中某个节点故障不可用时,集群其他节点可以持续提供服务。

关于数据分片,Redis cluster并没有采用一致性hash,而是引入了hash slot。整个集群共有16384个slot,这些slot被全部分配在各个节点中,并且可以在不同节点间迁移。而每个slot存放哪些数据是由key通过CRC16校验后对16384取模来决定的。

为了使集群中部分节点故障或者失去联系的情况下集群其他节点可以提供持续服务,Redis cluster 采用主从复制模型。简单的说,就是每个对外提供服务的节点,我们称之为master节点,并为每个master节点提供一个slave节点。当其中一个master节点故障时,其slave节点替代原有的master节点,以保证不会因为找不到slot而使整个集群不可用。

4.2 Redis cluster应用

Redis cluster所具备的水平伸缩能力和高可用性开始逐渐被我们业务所青睐。

Redis cluster目前主要应用于后端大数据平台,常见配置都是借助高配机部署4个8G内存的Redis实例组建4个master节点的集群,必要时直接通过同内网物理机扩容节点即可,非常方便。在需求量比较大的场景,比如我们的云推送平台,更是借助96G内存的超配机部署单实例10G的集群。下图是其中一个实例内存使用情况:

在后端业务的架构中,由于Redis处于缓存层,借助cluster-require-full-coverage参数,允许我们的集群一个节点故障而业务依然可以正常运行,因此在设计上,我们取消了slave节点,将所有实例都成为master。

运维、开发以及业务方积极的沟通协作,加之集群灵活的伸缩能力,让我们可以在业务高峰来临前及时扩容集群,无缝平滑对业务提供支持,在业务量减小时,可以做必要的集群收缩。及时的扩容、必要的收缩既是为了保障业务持续稳定,也是为了合理高效地利用资源。

5. Redis cluster实践

说了这么多,相信大家并不满足于理论性的介绍。笔者就给大家讲解Redis cluster的实践方式。

我们通过四个Redis实例构成集群。注意:Redis cluster需要3.0以上的版本。限于边幅Redis部署过程就略去了,下面是集群每个节点需要的配置:

# cluster cluster-enabled yes

#集群配置文件,由集群自动维护,不可人工编辑
cluster-config-file nodes${port}.conf

#节点失联时间,超过这个时间,该节点就被认为是故障
cluster-node-timeout 15000                

#只有一个master节点失效时仍然提供服务
cluster-require-full-coverage no

5.1 创建集群

使用redis-trib.rb命令的create选项来创建四个master节点的集群,命令如下:

redis-trib.rb create 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003  127.0.0.1:6004

使用redis-trib.rb命令的check选项来瞄一眼刚刚创建的集群:

可以看到四个节点在集群中的角色都是master,并且每个节点都持有4096个slot。

5.2 伸缩集群

现在我们模拟一个情景:线上业务压力剧增,现有的集群压力巨大,我们需要对集群进行扩容并且不中断业务。

在开始之前,我们在后台跑个小脚本,让它不间断对集群写入数据:

for i in `seq 6000`  do      redis-cli  -c  -h 127.0.0.1 -p 6001  set key"$i" value"$i"      sleep 1s  done

新增节点可以使用redis-trib.rb 命令的add-node选项,命令如下:

redis-trib.rb add-node 127.0.0.1:6005 127.0.0.1:6001

目前Redis cluster还无法自动为新加入的加点分配slot,需要管理员手动分配,算是一个短板。使用redis-trib.rb 命令的reshard选项,给新节点分配100个slot吧,命令如下:

redis-trib.rb reshard --from ${src_node_id} --to${dist_node_id} --slots 100 --yes 127.0.0.1:6001

上面这条命令很easy,从${src_node_id} 节点迁移100个slot到${dist_node_id}节点的。我们来看看命令的执行结果:

如上图,新增的master节点不仅拥有了100个slot,同时还有数据。这也就说明了,slot的迁移其实也就是数据的迁移。

也许童鞋们觉得笔者很抠门,新的master节点才给100个slot。没关系redis-trib.rb命令的rebalance选项可实现slot在所有节点间均衡分配,这里就不做赘述了,就留给大家当做课后作业。

现在让我们来试试将集群收缩吧。假设业务已经平稳度过高峰期,4个节点完全能扛得住压力,需要去掉6005这个节点。

将一个节点从集群中剔除,首先要做的是将该节点的数据迁移至集群其他节点,也就是slot全部转移到集群其他节点。Slot迁移通过上面的介绍大家都知道通过redis-trib.rb命令的reshard选项来实现,就不再复述了。6005节点的slot都迁移到6004节点,得到的结果应该是:6005节点“0 keys 0 slots”。

其次使用redis-trib.rb命令的del-node选项来将节点从集群中去除,命令如下:

redis-trib.rb del-node 127.0.0.1:6005 ${del_node_id}

看看效果:

至此我们就完成了一次集群的收缩工作。

童鞋们注意:对集群进行扩容收缩的工作,实际就是对slot的迁移,意味着进行数据迁移,过程挺消耗性能的,因此建议大家及时根据业务趋势提前做好评估和规划工作,避免在高峰时段给服务器增加额外的压力。

5.3 健壮集群

如前面描述Redis cluster采用主从复制模型来避免个别节点故障失联而导致集群不可用。相信这会是各位童鞋最期待的。

依旧是启用一个新的Redis6005实例,为6004节点增加一个slave节点,命令如下:

redis-trib.rb add-node --slave --master-id ${master_node_id}  127.0.0.1:6005 127.0.0.1:6001

我们看看此时的集群状态:

看图中红框框的部分,这里表明了他俩是一对master/slave 。

现在failover掉6004节点(ps:进行failover节点必须是slave节点):

将6004节点failover后,6005应该提升为master节点替代6004,来看看现在的集群节点是不是如我们预期的:

从上面的信息可以看出,原有的master 6004已经降级为slave,而6005则升级为master替代6004,并且集群可以持续正常服务:

同理如6004节点恢复了,可在6004节点上执行cluster failover命令,让6005节点假故障,实现主从切换,恢复原有的集群状态。

线上的配置规划,如果服务器资源充裕的话,还是建议为每个master节点都配置slave,防止节点意外故障而导致集群无法使用。当然要是资源吃紧,也是可以在某些敏感时段进行临时配置slave,毕竟Redis cluster的伸缩都是很灵活的。

6. 结束语

以上就是我们在Redis上走过的路,相信会对大家对使用Redis上有帮助。技术选型其实并不是运维一言拍板,而是结合业务需求协调开发的基础上确定方案,并在业务程序开发前与开发者沟通注意事项。任何技术都有其优劣的方面,详细了解、提前规避风险、针对自身业务做好规划才是正确使用技术的方式。

END

赞(1)
未经允许不得转载:运维军团 » Redis探索之路

评论 抢沙发

*

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址