【IT168 评论】本文根据【2016 第七届中国数据库技术大会】(微信搜索DTCC2014,关注关注中国数据库技术大会公众号)现场演讲嘉宾代晓磊老师分享内容整理而成。录音整理及文字编辑IT168@ZYY@老鱼
讲师简介
大街网数据库 & Cache负责人,曾就职于人人网,主要负责人人无线数据库维护、统计部门Inforbright数据仓库库维护。2013年-至今,大街网数据库Cache负责人。
正文
大家好,我是大街网的代晓磊,今天我演讲的主题是《Redis集群在大街网的应用实践》,我的分享主要分为以下几部分:
第一部分与大家分享“大街网Redis缓存架构之路”,主要经过了三个阶段,第一个阶段是单节点主从阶段,第二阶段是我们自建hash集群,最后一个也是最主要的阶段是Redis Cluster的应用。
早期的Redis使用是单节点的主从,并且所有的主节点都部署在同一台服务器,导致主服务器压力比较大,而从服务器没有任何请求。高可用是通过服务器级别的LVS+KeepAlive来实现,这种架构只有当主服务器宕机的时候,所有redis节点才能切换到备用服务器。因为高可用是基于服务器级别的,任何单事例的redis宕机都不会切换到备事例,会对业务造成较大的影响,并且Redis服务器的负载也不均衡。还有一个问题就是单Redis的容量和扩展性都没有。
基于单Redis主从的各种问题,我们就搭建了第二个架构——自建hash集群。优点是解决了单主从的性能或者扩展问题,但依然没有解决高可用问题,因为该hash集群没有挂载slave,也没有Redis实例宕机的切换逻辑。该架构主要有几个特点:引入ZooKeeper配置中心,程序访问的是配置中心的数据源,目的方便以后的迁移;引入封装好的中间层,该架构的优点是通过多个hash节点解决了单Redis带来的性能瓶颈, 封装好的中间层proxy做hash,通过一致性hash算法,使一个节点扩展到多个节点,这样的话它的并发,包括扩展性上都有一定的提升,最关键的是:依然没有解决高可用问题,任何一个hash节点的宕机,就会丢失1/N的线上请求(N是hash节点数)。
为了提供一个高可用、易维护的方案,我们推出了第三种架构:Redis Cluster。
我们首先对Redis Cluster进行简单的介绍,首先我们先说redis集群的优点:
(1)首先我们最关心的是高可用,Redis集群高可用是指在集群中master节点至少有1个slave节点存活的条件下,当master宕机,salve就会通过集群内部一半以上的节点投票选举为新的master节点。
(2)第二高性能,高性能是指集群不需要其他的中间件(比如proxy),降低了在中间件级别的性能消耗,并且没有了单节点中复杂的merge操作。
(3)扩展性好:当集群的空间以及性能出现瓶颈时,我们可以通过添加新的集群节点来水平扩展。注意集群并不是自动扩展,需要脚本支持。
(4)丰富的集群命令:redis cluster提供了丰富的集群命令,大家可以上官网(redis.io)上去把20个redis命令都熟悉和执行一遍就OK,这些命令足够redis集群的基本运维,涵盖了查看集群信息、hash slots分布、hash slots迁移等等操作。经常看到网上的博客说redis cluster缺点说需要依赖redis-trib.rb这个脚本才能维护,并且需要会ruby。我认为通过了解了20个redis命令,自己写脚本就可以实现redis集群的各种运维。
Redis集群缺点是:
(1)不支持多keys操作,通过查看官网文档可以了解,Redis集群主要担心多个大keys的merge会对性能带来较大的影响,所以最主要的问题是性能问题。但该操作也不能说的那么严格,如果两个keys在同一个Hash桶里,也可以进行多keys操作,但是为了避免问题,还是不要执行多keys操作。
(2)只能使用0号数据库
(3)还有一个缺点是缺乏大规模的线上使用,之前也有一些互联网的公司在用这个东西,但是有很多坑大家都没有测试出来,摸着石头过河,大家都不知道水有多深,不过随着redis cluster版本的更新,目前redis cluster已经在不少大互联网公司的推广使用。
上图是我模拟的主结点被kill掉之后redis集群的自我恢复,我想看内部大概需要多久能够提升。这个failover主要有两个关键点:第一是Cluster node的timeout时间,就是nodes之间在多少微秒后仍然获取不到节点的请求反馈。当一个master结点被kill掉以后,集群中节点在设定的超时时间内获取不到该结点的反馈,这个探测结点就会把该结点标记为fail,当半数以上的集群结点都选举同一个结点为fall down时,就会启动failover过程。第二就是投票选举新master的时间较短,如log中所示,整个切换用时不到一秒钟,这就是集群的高可用性。
接下来看一下大街网Redis Cluster的现状。
以下是大街网目前使用的Redis集群架构。特点如下:
(1)还是基于ZooKeeper做配置管理,并且ZK的配置只在2种情况下会被程序访问,一是程序启动时,初始化集群连接。二是我们迁移或者下线节点,修改了ZK中数据源的配置。目的就是做到集群对开发是透明的,也就是说开发数据库使用Redis集群时,只需要在程序中配置一个数据源就可以了。数据源中“种子节点“的配置也是可选的,比如一个6主6从的Redis集群,可以给它配4个结点也可以配6个结点,在程序启动的时候,它会取里面的数据源配置,检测种子结点。我们能够保证在至少一个种子节点可用的情况下,程序可以启动并正常使用(PS:程序启动时会报一些错误,但不影响使用)。
(2)中间层封装,目的是为了提供通用接口,原因是老的那套hash集群也是依赖接口,能够保证我推广Redis Cluster的时候,老的hash集群可以比较平滑的迁移到Redis Cluster上。二是基于通用的API,假如Jedis这个driver出现重大bug,我们可以对它进行替换,这样替换的成本也比较低。
(3)按业务来划分集群的,避免不同业务的相互影响。如果集群混用就可能出现一个人存的key比较大,或者执行了keys *等危险命令,导致整个集群的速度慢,会影响其它业务,所以我们根据之前的业务划分集群。
关于我们遇到的坑,第一点内存相关的设定
(1)最大内存没有设定,如果再加上keys又没有设定过期时间,这样随着业务的增长,redis就会一直占用内存,直到被linux oom kill掉。
(2)keys的过期时间,在功能上可以分为持久化和缓存集群。持久化集群代表keys只在redis中存,mysql中没有,这种keys不能设定过期时间。另一种就是缓存集群,这种需求的集群占95%以上,这些集群keys需要设定过期时间,就算缓存失效,当用户访问到这个keys的时候还会从mysql中取,同时set到缓存集群中。
(3)keys的过期策略,这个主要是redis使用到最大内存后如何处置的问题,我主要讲3个策略,一是volatile-lru,这个是设定keys过期时间缓存集群默认配置,redis会根据lru算法淘汰数据。二是allkeys-lru,对于一些非核心、并且程序员忘记给keys设定过期时间的redis配置,就是keys在set到redis集群时,redis会给keys标记一个时间(可以使用object idletime命令来查看keys的空转时间),当内存使用到上限时,redis根据lru算法来淘汰keys。三是:no-enviction策略,这个是redis集群的默认策略,就是永不淘汰keys,这样一单redis使用到内存上限,集群就无法写入了。
接下来可以看一下内存碎片率的问题,由于redis没有内存回收的策略。我们可以通过Redis_fragmentation_ratio这个参数可以反映出内存使用情况,该参数是操作系统分配内存(used_memory_rss)除以redis使用内存(used_memory)所得.
首先看内存碎片率大于1的情况。在redis使用过程中,由于一些keys的过期或者被主动清理,导致redis系统分配比实际使用的内存要多的多,呈现大于1的情况,要解决这个问题,可以通过重启Redis实例解决。
当Redis事例跟大量程序共用服务器时,内存碎片经常会出现小于1的情况。比如本来要存10G,但是系统分配的只有3G,此时,内存碎片就小于1,当Redis申请不到足够的内存,这样就会使用swap,导致性能急剧下降。解决方式:删除redis中的一些keys来空出内存,或者停掉一些程序后(停掉程序目的是节省内存,为该实例搭建从库而空出bgsave的内存),对该节点进行迁移。
核心参数主要有两个,
一是redis集群nodes探测的超时时间:系统默认的结点之间的超时时间是十五秒,我们设定的是5秒,当然,这可以根据自己不同的环境来设定,比如在一些虚拟机上,可以把这个参数设大。如果你们的网络足够稳定,并且你想让集群更快的发现出问题的node,并且尽快的执行salve提升,那就将该参数设小。
二是cluster-require-full-coverage参数,默认的值是yes,该参数配置成no的主要目的是在集群某些hash slots不可用的情况下,其他的hash slots仍然能够接受请求。举个例子如果集群里是3主3从,其中一组主从全部宕掉,其它两组主从依然能够接受读写请求,但这之中有个限制,整个集群必须有半数以上的结点配置是可用的,也就是说,如果3主3从的集群挂掉2主2从,这个集群就无法用了,因为不满足半数以上节点可用的情况。
另外,很多年轻的程序员在开发程序时,经常会check一下,查找程序里的keys是否已经set到线上Redis中,他们会连接到redis,执行keys *命令,该命令会阻塞线上请求。我们已经将这些危险命令在配置文件中进行了rename。对于上面的需求可以使用redis-cli的—scan –pattern来实现。
接下来探讨Redis连接周期性异常的问题,问题就是每半小时的大量的连接,并且不是长连接,一般程序连接redis都是长连接,出现这种问题一般可能被攻击或者某些程序的异常,必须及时的解决,我们最后通过tcpflow来抓取问题时间段的连接数据包情况,最终定位到具体的IP,并且找到原因。
如图所示出现了线上官客、www、job同时又大量的5XX报警,时间点发现是下午4:04到4:14左右,一般基础业务的同时抖动肯定跟底层DB或者cache有关。通过查看DB监控发现这几个DB实例性能稳定,然后将问题定位到缓存,因为跟职位相关,所以直接找职位相关的集群,进行问题排查。
通过查看job集群的Redis log,发现在出问题的时间段有2条aof写入异常:disk is busy?,通过查看源码发现,对于aof持久化,如果在2秒内aof文件无法写入,redis就不再接受任何请求,直到写入成功,在高的硬盘IO情况下,aof出现无法写入。通过查看服务器的IO负载情况,发现两个峰值,跟5XX的时间段也能匹配的上。这时问题的关键点就在于:是什么导致硬盘IO的峰值出现的。
第三步进行问题定位,因为该服务是redis专用服务器,能够导致如此高IO的操作,只有2种情况,一是aof rewrite,另一种情况就是bgsave来存rdb快照。进入redis数据目录,发现在出问题的时间段内,集群执行了rdb备份操作,从而导致了这两个时间段内的高IO。
解决以上问题的方法:(1)财大气粗的可以直接上SSD硬盘(2)将aof持久化集群跟缓存集群隔离,避免相互影响。
对于出现的问题,我对Redis持久化比较纠结,到底是用AOF还是rdb?为了解决困惑,我们先来看一下AOF和RDB的区别。
AOF的优点一:从启动起来就有序保存了所有写入操作,而且是在文件尾追加的,aof本身对IO的消耗并不高。
AOF优点二、误操作能及时恢复数据:如果有类似flushdb等操作,只需要修改下aof文件,剔除fulshdb操作,然后重启redis即可。
缺点是aof文件比较大,需要一段时间后进行aof rewrite。
RDB优点:RDB二进制文件非常紧凑,非常适合备份以及快速恢复。
RDB缺点是rdb只是某一时刻的内存快照,适合缓存集群的备份,不适合存储集群。
所以总结来说,根据需求选择合适的备份方式,缓存集群使用RDB备份,存储集群使用AOF。如果适当提高机器配置,因为redis主要是基于内存,aof阻塞问题也是能够解决的。
下面讲一下自动化,要做自动化之前必须有一个规范,目录,文件命名,keys使用,只要有了规范,自动化也不是非常难。二是自动化部署和配置,比如可能经常手动加内存,手误设定内存太小会导致写入问题。三是自动化监控,监控Redis性能,包括内存使用等。四是Redis自动迁移,五是集群扩容,六是自动化备份,七是分析包括slowlog分析、keys分布。
keys命名规范:命名之前应该想好要简单明确。一个好的命名对分析Redis节点的keys分布非常有帮助,比如命名一个job集群,可以用job_invite_*的方式来表示职位邀约的keys。这样做的好处是,当我需要对keys分布分析时,可以通过”_”来截取分析不同业务的keys。
Redis使用规范,是将我们遇到的坑加以规避。比如禁止将大量成员存储到一个hash key中,因为执行hgetall性能非常的差;禁止连接线上redis执行keys *dxl这种方式来过滤keys;keys建议设定过期时间,除非把redis当存储使用;合理使用Redis的数据类型:list、set、hash,因为合适的类型对性能和内存使用都能带来好处。
二是自动化部署Redis Cluster,创建集群的时候,只需要在配置表填写集群节点的相应信息,然后我们程序会调用配置信息自动建立集群。
其次集群配置,比如为集群扩展内存,之前线上Redis集群需添加内存是手工操作,在为最后一个结点设定内存时时,内存值少拷贝了一位,本来是几十G的东西弄成了几个G,redis可以设定成功,并不报错,但是程序访问会出问题,并且redis log中也会提示内存设置过小。解决方式,通过程序来自动设定,程序会检查线上redis已经使用的内存,跟DBA设定的内存进行比对后,只有DB设定的数值大才设定到线上redis。
自动化监控:Redis的命中率是所有人都关心的。可以写一个脚本,每十分钟通过info stats来采集计算,通过这个命中率就会反映出集群的使用情况。
自动化监控之内存使用—基于内存监控统计表,所有基于内存的报警都出自这个表,比如通过采的内存数据看出集群内存增长情况,是否是正常的增长,并且为内存自动添加提供数据支持。
自动化监控之连接监控:每十分钟抓链接请求情况,前面PPT提到的链接异常就是靠下面的统计表而来,对于异常的大量请求都会及时报警,然后DBA跟进分析,看是推广带来的连接还是被攻击。
自动化迁移工具:
在推广redis cluster的过程中,经常有程序员经常会说:这个单redis节点的keys必须全部给我导到集群里,否则我的程序会有各种问题。之前没有迁移工具,只能迁移那些缓存redis节点或者集群(因为这些集群keys在mysql中都有,他们只需要凌晨跑一遍全量数据,新redis集群中就有keys了),Redis集群推进比较缓慢。自从发现Redis-port这个工具之后,就不会产生由于无法迁移旧集群keys所无法迁移了。
Redis-port模拟了redis slave的角色。大致分为如图所示的七步。
1、rsync 2、fork进程 3、更新入buffer 4、dump rdb 5、fork进程exit 6、send rds 7、将buffer更新到集群。
五是集群扩容
集群扩容主要分2种方式:
(1)如果之前集群分配的内存较少,现在集群所在的服务器内存也比较充裕,解决扩容可以直接扩大内存就搞定。
(2)第二种方式是增加新结点,我的建议是直接增加1倍,因为这样避免了hash slots的零散,比如一个3主3从的redis集群扩展到换到6主6从,只需要把原来主节点上一半的hash slots分别分配到新加入的3个master上就OK,迁移程序好控制,slots分布也均匀。
增加新节点的扩容方式需要自己写脚本来实现,具体迁移的细节,redis官网上有,参考下面的连接:http://redis.io/commands/cluster-setslot
六是自动化备份,我们是根据配置表做的,只需要将配置表中:是否备份置为1即可,并且备份过程是自动化的。
性能分析之slow log,slowlog可以发现集群中执行时间长的命令,通过命令行:slowlog get 的输出是比较规整的,但如果需要存到统计表里还是需要花一些心思的,我们会将慢SQL统一整理分析后发给相应的负责人优化。
最后是keys分布,如果一个基础业务集群在1天内内存翻了一倍,谁能告诉我是哪些keys增加导致的?这时就需要keys分布工具来分析每一类keys的数量、占用内存大小等等,通过跟历史数量以及内存使用的对比可以找出问题的答案。我们采用RDB tools实现Keys分布。
今天的分享到此结束,希望我们的经验能给大家一定的帮助,谢谢大家!