• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Redis内存和key为什么不易过大?

Redis 彭东稳 7年前 (2017-03-30) 35511次浏览 已收录 0个评论

近几年来,随着Redis的发展壮大,被越来越多的人所熟知,越来越多的企业也使用了Redis。首先说一下为什么Key不易过大,然后来分享下Redis单实例内存过大遇到的问题以及解决方案。

为什么Key不易过大?

由于Redis是单进程工作只能使用单核CPU,所以在Redis中不易单key过大,这个单key指的是string类型的。我曾经碰到过单key大小为5G的,开发存储的是日志信息,由于当时没有定期解析key大小,所以直到出现问题后才发现这个key过大。当单key过大时,每一次访问都会造成redis阻塞,其他请求只能等待了,如果应用中设置了1秒超时等,那么用户就会得到一个错误信息。最后删除的时候也会造成redis阻塞。

为什么内存不易过大?

近两年我们HULK云平台承载的Redis日访问量从800+亿增加到了2100+亿,Redis实例数也增长到了5000+。

Redis内存和key为什么不易过大?

在这几年的线上业务的使用中表明,Redis这个内存数据库,它的高性能、稳定性都是不用怀疑的。但在运维过程中,我们走过的路,踩过的坑也不少。今天来讨论一下,如果单实例内存过大,如果一旦出问题,那它可能会带给我们的就是灾难性(我想很多公司都遇到过), 这里列举一下单实例内存过大可能会遇到的一些问题:

  • 主库切换

先来看一下主库宕机容灾过程,在主库宕机的时候,我们最常见的容灾策略为“切主”。具体为从该集群剩余从库中选出一个从库并将其升级为主库,该从库升级为主库后再将剩余从库挂载至其下成为其从库,最终恢复整个主从集群结构。以上是一个完整的容灾过程,而代价最大的过程为从库的重新挂载,而非主库的切换。

这是因为Redis无法像MySQL、MongoDB那样基于同步的点位在主库发生变化后从新的主库继续同步数据。 在Redis集群中一旦从库换主,Redis的做法是将更换主库的从库清空然后从新主库完整同步一份数据再进行续传。

从库重做流程是这样的:

1. 主库bgsave自身数据到磁盘。

2. 主库发送rdb文件到从库。

3. 从库开始加载。

4. 加载完毕开始续传,同时开始提供服务。

很明显,在这个过程中Redis的内存体积越大以上每一个步骤的时间都会被拉长,实际测试的数据如下(我们自认我们的机器性能比较好):

Redis内存和key为什么不易过大?

可以看到,当数据达到20G的时候,一个从库的恢复时间已经被拉长到了将近20分钟,如果有10个从库那么如果依次恢复则共需200分钟,而如果此时该从库承担着大量的读取请求你能够忍受这么长的恢复时间吗?

看到这里你肯定会问:为什么不能同时重做所有从库?这是因为所有从库如果同时向主库请求rdb文件那么主库的网卡则立即跑满从而进入一个无法正常提供服务的状态,此时主库又死了,简直是雪上加霜。

当然,我们可以批量恢复从库,例如两两一组,那么全部从库的恢复时间也仅仅从200分钟降低到了100分钟,这不是五十步笑百步吗?

另一个重要问题在于第四点中的标红位置,续传可以理解为一个简化的MongoDB的oplog,它是一个体积固定的内存空间,我们称之为“同步缓冲区”。

Redis主库的写入操作都会在该区域存放一份然后发送给从库,而如果在上文中1,2,3步耗时太久那么很可能这个同步缓冲区就被重写,此时从库无法找到对应的续传位置它会怎么办?答案是重做1,2,3步!但因为我们无法解决1,2,3步的耗时因此该从库会永远的进入恶性循环:不停的向主库请求完整数据,结果对主库的网卡造成严重影响。

当然,曾经也看到有公司放弃了Redis原生的主从同步机制,采用实时读取aof持久化文件来实现主从同步。这样做的好处就是主从关系的变动,Redis不需要重新从新主全量同步数据,而是增量的读取aof文件。但是,伴随而来的问题是,主库aof刷盘的频率为always时对性能有一定影响,而刷盘频率太慢,会造成主从同步延迟在秒级别。对于做了读写分离的业务,这种延迟也许是不能忍受的。

  • 从库扩容

很多时候会出现流量的突发性增长,通常在找到原因之前我们的应急做法就是扩容了。 一个20G的Redis扩容一个从库需要将近20分钟,在这个紧急的时刻20分钟业务能够容忍吗?可能还没扩好就死翘翘了。

  • 网络缓慢导致从库重做引发雪崩

该场景的最大问题是主库与从库的同步中断,而此时很可能从库仍然在接受写入请求,那么一旦中断时间过长同步缓冲区就很可能被复写。此时从库上一次的同步位置已丢失,在网络恢复后虽然主库没有发生变化但由于从库的同步位置丢失了从库必须进行重做,也就是问题一中的1,2,3,4步。如果此时主库内存体积过大那么从库重做速度就会很慢,而发送到从库的读请求就会受到严重影响,同时由于传输的rdb文件的体积过大,主库的网卡在相当长的一段时间内都会受到严重影响。

  • 内存越大,触发持久化阻塞Redis主线程时间越长

Redis是单线程的内存数据库,在Redis需要执行耗时的操作时,会fork一个新进程来做,比如bgsave,bgrewriteaof。另外,当Redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建子进程。Fork新进程时,虽然可共享的数据内容不需要复制,但会复制之前进程空间的内存页表,这个复制是主线程来做的,会阻塞所有的读写操作,并且随着内存使用量越大耗时越长。可以在info stats统计中查latest_fork_usec指标获取最近一次fork操作耗时,单位(微秒)。例如:内存20G的Redis,bgsave复制内存页表耗时约为750ms,Redis主线程也会因为它阻塞750ms。

改善措施 1)优先使用物理机或者高效支持fork操作的虚拟化技术。

2)控制redis单实例的内存大小。fork耗时跟内存量成正比,线上建议每个Redis实例内存控制在10GB以内。

3)适度放宽AOF rewrite触发时机,目前线上配置:auto-aof-rewrite-percentage增长100%

子进程开销监控与优化 cpu

不要和其他CPU密集型服务部署在一起,造成CPU过度竞争

如果部署多个Redis实例,尽量保证同一时刻只有一个子进程执行重写工作

1G内存fork时间约20ms

内存

子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但Linux有写时复制机制(copy-on-write)。父子进程会共享相同的物理内存页,当父进程处理写请求时会把要修改的页创建副本,而子进程在fork操作过程中共享整个父进程内存快照。

Fork耗费的内存相关日志:AOF rewrite: 53 MB of memory used by copy-on-write,RDB: 5 MB of memory used by copy-on-write

关闭巨页,开启之后,复制页单位从原来4KB变为2MB,增加fork的负担,会拖慢写操作的执行时间,导致大量写操作慢查询

“sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled

硬盘

不要和其他高硬盘负载的服务部署在一起。如:存储服务、消息队列。

那Pika是什么?

Pika是360 DBA团队和基础架构组联合开发的大容量、高性能、多线程、持久化的类Redis存储系统。Pika中的数据使用磁盘而非内存,多线程的结构设计,保证了在使用磁盘的同时还拥有强劲的性能。它支持多数据结构,完全支持Redis协议。用户无需换驱动,无需改代码,支持从Redis实时同步数据的无缝迁移。如果把业务迁移到新开源的Pika上面,这样就不用太关注内存了,Redis内存太大引发的问题,那也都不是问题了。

  • Pika的单线程的性能肯定不如 Redis,Pika 是多线程的结构,因此在线程数比较多的情况下,某些数据结构的性能可以优于 Redis。
  • Pika肯定不是完全优于 Redis 的方案,只是在某些场景下面更适合。所以目前公司内部 Redis,Pika 是共同存在的方案。DBA 会根据业务的场景挑选合适的方案。

感兴趣的同学快来试试吧!

Githup:https://github.com/Qihoo360/pika

学习文档:http://www.360doc.com/content/16/0531/14/13247663_563808424.shtml


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (3)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!