redis

1 背景

        Redis作为内存级别的存储或缓存,为一淘玩客(http://wanke.etao.com) 提供了Feed流,计数器,精细粒度缓存功能。2013年10月份上线,逐渐成为系统中重要的组成部分,我们已经将Redis集群抽象成为了一淘Redis平台级别的服务。

       一淘Redis平台上线2年多的时间,我们遇到了很多问题。例如网络抖动,主从同步,机器宕机,节点迁移,内存不足等等。最近我们刚刚遇到了由于清理任务被意外终止后,Redis内存险些爆掉的情况。本文将过去两年中遇到的一些问题总结提炼出来,希望能够为有同样在使用Redis的同学能够有一些借鉴意义,可以少走一些弯路。

2 基本结构

   在总结我们遇到的问题之前,我先简单介绍一下在玩客中使用的Redis集群的基本结构:


在下面问题的总结中会提到上图中的几个基本组件的名字,我简单介绍下图中涉及到的几个基本组件:

  • Nutcracker(twemproxy)
    • 整体集群的唯一入口,负责请求的分发以及集群存储的分片。客户端请求唯一入口。
  • Redis实例:
    • 提供数据存储,数据真正存储的地方。
  • 集群哨兵:
    • 提供Redis实例的主从监控,并且在必要时,进行主从切换。


熟悉了上面的概念后,我们进入本文正题。

 

3 问题与经验总结

3.1 网络抖动导致主动切换

      在我们的Redis集群中,每个主实例至少会配备一个从实例。我们有一些Redis的主实例处于ET2机房,哨兵处于CM6机房,我们当时采用了单哨兵方式(多哨兵的方式虽然可以使用投票的方式来判断实例的客观与主观宕机情况,但依然无法解决网络分区的问题,因此我们使用了单哨兵的工作方式),单哨兵方式由于网络抖动会导致主从切换发生。

 

解决方案:

修改哨兵配置:

增加节点宕机判断时间。我们目前cm6,et2机房同时存在节点的情况,使用的判断时间是2500ms,即2.5s。

通过修改哨兵配置文件中的 down-after-milliseconds 参数来实现,例如

sentinel down-after-milliseconds server1 2500

标识server1的主从实例如果主实例超过2500ms无法通信,则会认为主实例宕机。则会启动主从实例切换指令。

3.2 参数设置导致主从切换无法完成

      问题还是在CM6机房和ET2机房之间,我们将Redis实例部署到ET2机房之后,发现有些时候主从切换无法完成,哨兵总是出现failover切换失败的情况,这是由于哨兵参数设置不当导致。哨兵配置中有一项failover-timeout 设置表示主从切换最长可以执行多长时间,这个时间表示整个主从节点从切换到完全可用的时间,不仅仅是主实例可用,从实例也必须可以用,因此当节点实例内存比较大,而且从实例有比较多的情况下,需要将这个值适当设大(因为所有从实例必须slave of 到新的主实例上,这个过程是需要拉取rdb并且将rdb加载到内存中)。我们在实践中,实例最大的10G,设置的failover-timeout时间是900000,即15分钟。

sentinel failover-timeout server1 900000

但是,修改了 failover-timeout 参数后,哨兵不再抱怨主从切换时间超时了,但我们发现了另外的一个问题,从实例总是不断的重新slave of 主实例,有时要重试2-3次才能成功。起初以为是网络丢包造成,后来查看从实例的日志,发现

 

从日志中可以看到,从实例都已经将所有的rdb数据加载成功了,结果报与主实例连接断开,然后再次重新slave of 主实例。

解决方案:

这个问题只出现在那些实例大小比较打的情况下,问题出在Redis实例的配置中,redis有一项参数默认配置如下:

client-output-buffer-limit slave 256mb 64mb 60

这表示如果有从实例连接到主实例,如果client-output-buffer超过256MB,或者持续60秒超过64MB,主实例会立刻断开从实例的连接,这也是为什么我们刚刚看到从实例在loading完rdb镜像后,出现了Connection with master lost的日志。

我们使用了如下配置

client-output-buffer-limit slave 512mb 256mb 300

使用后,我们最大的单实例10G的节点在做slave of时没有出现过上述现象。

 

 3.3 一条命令导致主从切换

      我们的集群中使用了twemproxy做为透明代理,负责Redis集群的分片工作,并且在twemproxy处设置了超时时间500ms,但这个500ms表示后端redis的实例执行时间超过500ms后会直接向client返回false,虽然不会导致客户端请求被hang住,但是指令依然会发送到Redis的实例上,于是有了如下的一次问题:

      我们当时在zcount一个key下的元素个数,恰好这个key下的元素个数非常多,超过了80w条,结果导致了这条指令在redis上执行了将近1分钟,当然哨兵毫不留情的将主从进行了切换(这个切换是符合预期的),因此我们必须避免导致redis执行过慢的语句在线执行,虽然twemproxy天生屏蔽掉了一些比较危险的指令,例如keys *这样的指令,但是zcount,zrange 这样的指令如果使用不当,依然很危险。

 3.4 Redis中的节点迁移

      玩客的Redis集群中,我们将3个实例放在了同一台机器上,随着数据量增长,单实例大小涨到了10G左右,我们的物理机只有32G的大小,因此我们需要将这些实例迁移出去。这种操作过程基本上利用Redis的slaveof功能,当然这个功能要建立在参数配置合理的情况下进行,例如上面提到的 client-output-buffer-limit。如果希望切换master,则在哨兵中制定failover server1即可,如果希望某个redis的从实例成为主实例,可以将该的priority值降低为一个大于0的值。

 

 3.5 哨兵状态

      对于上面实例迁移的场景中,如果关闭了迁移掉的slave ,如果哨兵没有重启,虽然节点已经关闭,哨兵依然会记录这个节点的状态,如果端口被用作其他的用途,例如想分配给另外的集群使用,这时哨兵依然会将这个实例切换会当前集群的从实例,因此这种情况一定要小心。 目前哨兵没有cli命令可以remove slave。因此需要重启哨兵。另外重启哨兵要注意当前的配置,因为在redis的2.8.4版本后,在redis执行过程中,会定期的将当前配置回写到配置文件中(通过cli 的config方式执行的配置也会被回写)、

 

  3.6 内存不足导致无法bgsave ,bgsave的机制。

      最近我们刚刚遇到了由于清理任务被意外终止后,Redis内存险些爆掉的情况。 因此我们采用了问题4中的方式进行了节点迁移工作,但是当时迁移的过程中发现,slave节点一直认为master节点处于down的状态,但我们通过cli方式访问master节点,各种命令执行都是正常的情况。后来查看master节点的日志,发现如下内容:

 

究其原因是由于 bgsave 的时候会fork。我们的服务器当时内核配置的vm.overcommit_memory =0 。关于这个配置有如下说明:

 

  • 设成0的时候,内核会评估系统所剩内存是否足够本次请求,如果不够,则不进行分配。
  • 设成1的时候,内核不会评估剩余内存,而是直接分配,这是存在OOM风险的。
  • 设成2的时候,当系统分配的内存超过swap+N%*物理RAM(N%由vm.overcommit_ratio决定)时,会拒绝commit。

redis官方,对于日志中出现的这种问题,给出的建议是将overcommit_memory=1。理由如下:

Redis background saving schema relies on the copy-on-write semantic of fork in modern operating systems: Redis forks (creates a child process) that is an exact copy of the parent. The child process dumps the DB on disk and finally exits. In theory the child should use as much memory as the parent being a copy, but actually thanks to the copy-on-write semantic implemented by most modern operating systems the parent and child process will sharethe common memory pages. A page will be duplicated only when it changes in the child or in the parent. Since in theory all the pages may change while the child process is saving, Linux can't tell in advance how much memory the child will take, so if the overcommit_memory setting is set to zero fork will fail unless there is as much free RAM as required to really duplicate all the parent memory pages, with the result that if you have a Redis dataset of 3 GB and just 2 GB of free memory it will fail.

      大概的意思是,fork的时候如果overcommit设置为0,那么操作系统会认为剩余内存不足,而实际上fork的时候是使用COW的方式,并不会真正占用2倍内存的大小。当然如果这个期间如果大量内存发生修改,会有OOM的风险。我们当时面临这个问题的时候,是通过overcommit_memory=1的方式解决。当时10G的实例,fork后,大概多用了1G左右的内存。

 3.7 RSS内存 和 User Used 内存。

      最近我们刚刚遇到了由于清理任务被意外终止后,Redis实例中出现了很多垃圾数据,导致Redis内存使用飚升,随后我们开启了清理程序,通过Redis的info可以看到Used memroy降低了,但RSS依然很高。

原因分析:

      这是因为redis的内存分配使用了jemalloc,jemalloc的分配策略使用arena来分配内存,会根据请求分配的大小来选择分配去,每个分配区维护一系列的分页。而对于回收的内存,会放到dirty区。分配内存的时候会优先使用dirty区进行分配,其次使用clean(未分配过的)区域进行分配。对于比较大的内存分配请求,jemalloc会通过mmap和munmap进行申请和释放,而对于比较小的内存请求(例如4M以内),则会使用arena中的run进行分配,而这部分内存再进行回收的时候,不会立刻归还给操作系统,jemalloc会根据dirty page的大小来决定是否将回收的内存归还给操作系统

3.8 twemproxy的CPU占用率过高

      这篇文章在我之前写过的一篇文章中有介绍,大概的问题就是如果像twemproxy一次发送了包含了大量命令的pipeline指令(大概每次10000条循环发),会导致twemproxy的CPU使用率过高,这是由于twemproxy的mbuf的内存分配和命令分割机制造成的,目前我们在玩客的集群中,通过改进twemproxy的工作方式来降低这种内存分配方式带来的影响。


4 关于一淘Redis平台的后续计划

      Redis在一淘玩客已经使用了近2年的时间,我们将我们的经验打包,启动了一淘Redis平台项目。在后续有计划在公司内部开放给对Redis有着同样需求的小伙伴们。一淘Redis平台项目中采用了业界使用比较多的twemproxy(nutcracker) + redis哨兵 + redis实例的工作方式。一淘Redis平台项目的目标有如下几点:

  • 对用户透明。
  • 快速建立集群。
  • 自动failover。
  • 集群节点异常报警。
  • Redis实例监控。
  • 节点迁移,节点扩容等等。


      最后,想借这篇文章想了解一下大家的项目中对Redis使用的需求强度到底有多大?对Redis有使用需求的同学可以直接在下面回帖,回帖中大概说一下自己项目中使用Redis的应用场景(比如计数器),存储方式(内存存储,内存缓存),以及大概可能会使用的内存大小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值