本文根据4月12日UCloud结构化存储研发部经理吴斌炜于〖KVM社区&UCloud技术微信群〗线上分享内容整理而成。欢迎关注〖KVM社区 & UCloud〗线上系列分享。
讲师介绍
吴斌炜
UCloud结构化存储研发部经理负责UCloud内存存储UMem和云数据库UDB等存储产品的设计和研发工作。设计并研发了国内第一个服务化的分布式Redis产品和第一个MongoDB云数据库产品UDB,UDB是首次在国内云服务的Paas服务中大规模应用了Docker技术的产品。
演讲提纲
-
Redis简单介绍;
-
服务化分布式Redis的优势;
-
服务化分布式Redis的挑战;
-
服务化分布式Redis的设计要点。
Redis简单介绍
在介绍服务化Redis设计前,我先简单介绍下Redis,以便那些先前对Redis了解较少的朋友对Redis有个更直观的认识。
Redis是REmote DIctionary Server的首字母的缩写,是一种以内存为存储介质的Key-Value型存储系统,主要有以下3个特点:
-
拥有超高性能,读写性能可达10万以上;
-
支持string、list、hash、set、sorted set等丰富的数据结构;
-
支持排序、集合类运算、位运算、过期淘汰等复杂运算。
Redis应用场景较为丰富,我总结了Redis常见的四种应用场景:
-
在读多写少的场景下作为mysql/mongodb等数据库的缓存,cache后端数据库的热点数据,可以有效的提升系统的读性能,降低后端数据库的压力。
-
Redis支持数据的持久化,若机器重启,Redis会从磁盘文件中重建内存数据,所以越来越多的公司为获取更高的读和写性能,将Redis作为持久化的数据库,后端不接其他数据库,这种场景相比做缓存的场景的优势是除了能提升系统的读性能外,还能提升系统的写性能。
-
支持list、hash、set、sorted set等复杂数据结构,支持排序、集合类运算、位运算、过期淘汰等复杂运算,将这些数据结构和算法放置在Redis上进行,可以降低应用程序的复杂度。在即将推出的Redis3.2版本还将支持经纬度的数据结构和相关的运算。
-
替换私有内存,利用私有内存提升读写性能的应用场景在游戏业务中较为多见,使用私有内存的弊端是机器宕机、程序升级、修改配置重启进程等会导致部分数据的丢失。若使用Redis替换私有内存就无需担心程序或机器的重启导致数据丢失的问题。
服务化分布式Redis的优势
简单介绍了Redis,接下来本文将介绍服务化Redis的一些设计思想。主要结合我们公司的服务化Redis系统—UMEM在设计、研发和运营过程中遇到的挑战来谈谈我们的应对策略,以供读者做个参考。
服务化Redis的设计优势是为开发人员屏蔽Redis的底层运维细节,只需通过可视化界面或API就可获取Redis服务,无需关注资源、部署、迁移、扩容、监控及宕机处理等细节。开发人员兼任运维人员的现象普遍存在于国内中小互联网公司中。在业务快速发展时期,很多开发人员都有过这种既要负责研发任务又要处理运维工作的痛苦经历。
服务化Redis的另一个优势是资源的统一控制和管理,确保环境的一致性,为运维自动化奠定基础,从而有效降低硬件和人力资源。
UCloud 服务化Redis系统分为管理系统及Redis存储系统。管理系统包括可视化页面、API、集群管理模块、Agent等。本文不重点介绍管理系统的实现细节,也不介绍使用何种语言和框架进行开发。本文的重点是介绍Redis存储系统在架构和运营上的挑战以及我们的应对策略。
为确保数据安全和更高的读写性能,Redis存储系统一般采用集群的模式,目前常见的Redis集群实现方案有以下五种:
-
采用主从模式。该模式优点是命令全兼容,直连redis性能无损耗;缺点是容量受单机限制,扩容较麻烦。
-
Twemproxy代理模式。该模式优点是支持sharding和自动容灾;缺点是不支持在线扩容,比较适合缓存的场景。
-
Redis cluster模式。该模式优点是直连Redis性能无损耗;缺点是Redis节点既要处理业务数据又要负责集群管理,逻辑比较重,给升级和运维都造成了困难。Redis cluster还需要smart client客户端的支持,而目前稳定版的smart client还很少。
-
Codis集群。codis模式和Twemproxy都属于代理模式,相比Twemproxy他的优点是支持简单的管理页面和在线扩容。缺点是模块较多,搭建和维护比较复杂,而且一个集群只支持一个实例,如果需要使用多个实例需要重新部署多套一样的codis集群,大大提升了运维的复杂度。
-
如果觉得以上几种模式都不适合自己的业务场景,可以自行研发一套适合自己业务场景的Redis集群系统。
UCloud采用了第5种方案。Redis官方的cluster正式版本是2015年推出,Codis是在2014年11月开源的,而UCloud是在2013年开始服务化Redis的设计和研发工作,2013年11月正式对用户开放申请,正式上线比codis开源还早一年。所以在我们设计之初市场上还没有一个较为成熟的Redis集群产品,唯一可以借鉴的是Twemproxy,但是因为Twemproxy不支持在线扩容,而且Twemproxy支持的命令也较少,加上Twemproxy采用的是单进程的模式,单IP的性能上限只有10万左右。基于这些原因,我们选择了一条完全自研的道路。而目前国内较多自研的分布式Redis集群很多是基于Twemproxy开发的,比如网易和搜狐的分布式Redis集群。
UCloud 的集群方案采用了类似Twemproxy的代理层模式,采用这方案的原因主要有以下3点:
-
代理层的设计便于原先Redis或者Twemproxy的用户无缝切换。
-
代理层的设计相对简单,而且无需大量修改Redis源码,Redis的数据是完全加载在内存中的,对Redis进行升级需要重新将磁盘中的数据加载到内存,加载过程中服务不可用,所以对Redis的升级成本非常高,我们尽量能够减少对Redis的修动。
-
代理层的设计将分布式逻辑和存储逻辑完全隔离,若分布式逻辑需要升级只需要升级代理层,这给运维和升级都带来了方便。
服务化的环境存在多实例共享,多实例之间会存在资源竞争和数据安全的问题。服务化的设计目的希望对使用者屏蔽后端的运维细节。所以要求用户停止服务做离线迁移或者升级变得不可行;后端的异常情况对用户的影响范围尽可能小,影响时间尽可能短。
不然对用户黑盒的服务化设计很容易让用户对系统产生不信任感;后端的操作需要考虑到用户的各种使用场景,比如用户如果使用长连接但是重连机制有问题,那么我们后端正常一个升级重启进程的操作都会影响到用户的业务。服务化的设计的挑战主要来自系统的架构和运营能力上。
挑战及应对策略
下面谈谈UCloud的服务化Redis系统——UMEM在设计和运营中遇到的挑战及应对的策略。
数据安全
服务化的环境多实例之间首要保证的数据的安全。UMEM的应对策略:
-
底层隔离。
UMEM只能通过内网访问,且IAAS层网络基于SDN实现,按用户粒度对网络做了隔离,不同用户之间无法访问彼此的实例。对不具备类似网络隔离技术的公司可以考虑采用现在比较热门的Docker技术,另外通过为Redis添加复杂密码和增加单位时间内密码错误数来防止暴力破解,从而来提高安全性。
-
过滤危险命令。
去年Redis爆发了Redis-crackit的漏洞,黑客利用config等命令入侵了全球几万台Redis服务器,对Redis用户造成了不小的损失。UMEM在代理层过滤了config等命令,避免了出现类似的问题。
资源竞争
为了提升资源利用率,服务化设计中不可避免会存在多个实例共享一台机器甚至一组进程的情况,多个实例会对机器的IO、CPU、带宽等存在竞争。如何合理的分配彼此的资源?如何防止某个业务突增影响到其他业务呢?UMEM采用了以下几个策略。
-
过载保护
设置资源的阈值,达到阈值后就对使用率较高的实例进行限制,比如为了提升代理层单进程的性能,我们采用了多进程的模式,同时多个实例会共享该进程组,如果我们程序检测到代理层负载较高的话,会限制当前负载最高实例的读事件的处理频率,从而降低代理层的负载。
-
设置资源隔离池
比直接限制业务请求频率更加优雅的方式是设置资源隔离池,UMEM尝试过将代理层的进程组分为共享池和隔离池,一开始实例默认运行在共享进程池中,如果该实例业务突增,导致代理层负载超过阈值,进程会动态将该实例从共享池迁移到隔离池,等业务正常后再动态加回到共享池。
-
快慢分离
过载保护和资源隔离池都没有从更本上解决问题,实际还会对业务突增的实例产生一定的影响。为提升代理层的实际处理能力,UMEM采用了快慢分离的策略,该策略借鉴自高速公路快慢车道分离的设计思想,快慢车道的分离是为使行驶较快的汽车不受行驶较慢汽车的影响,从而提升道路的车流量。Redis存在一些聚合类的命令,比如sunionstore,还有一些返回数据量较大的命令,如keys,这些命令都需要在代理层做聚合操作,特别是keys命令,因为数据量较大会对代理层负载造成不小的压力,将这些复杂命令交由一个独立的模块专门处理可以降低对原有模块的影响。将复杂命令和简单命令交由不同的模块处理就类似高速公路的快慢车道分离的思想。
-
在线迁移
如果说机器资源已经无法满足实例正常运行的话,还可以通过在线迁移的方式,UMEM的代理层和存储层都具备在线迁移的能力。
扩容问题
如果说用户对性能和容量的需求超过单台机器的上限,那么以上的方法将不可行,为了解决该问题最好的方法是分布式的架构设计。
支持在线扩容的分布式架构设计
分布式设计可以突破单机的性能和容量的瓶颈问题,但是若用户的性能和容量达到了初始申请的上限后还需要进行扩容,在服务化的设计下若所有用户的扩容都是通过停服离线进行的话,将变的不可行。所以支持在线扩容的分布式架构设计是很有必要的。不仅存储层需要支持在线扩容,代理层也需要能够支持在线扩容。因为代理层为无状态设计,所以代理层的扩容可以通过增加IP的方式进行。
升级问题
单实例的升级出现异常或者引入了程序bug只会影响一个业务,如果服务化系统的共享模块中引入bug,那么就有可能影响所有业务,所以在服务化系统中升级必须更加的小心,即便如此我们还是无法保证线上不会出现误操作,或者程序没有bug,我们能做的就是即便异常发生我们的影响范围也是可控的,同时尽可能少的对业务产生影响。除此之外我们还要考虑到用户的各种使用场景,比如我们的正常升级进程可能导致长连接的业务断开连接,如果业务的重连机制有问题那么就会影响到业务。为解决这些问题我们采用了以下几个策略:
-
分SET部署
将一个机房划分为多个SET,业务按照SET进行部署,将影响范围从机房缩减到SET。
-
灰度升级
SET的影响范围还是不小,如果系统具备按照业务或者实例,甚至更小的命令粒度的灰度能力的话,那么影响范围就会更小,UMEM早先的架构是未进行快慢分离的,在对架构做快慢分离的升级中就采用了按实例粒度灰度的策略。
如果采用按业务粒度的灰度策略的话,可按业务的重要程度分为若干等级,按照业务重要程度逐渐递增的原则进行灰度升级,UMEM的管理模块的升级中主要采用这种方式。
-
大系统小做
虽然通过set部署和灰度我们已经能够将影响范围变得很小,但我们还希望做的更好。将大的系统按照功能模块拆分成一些小系统和模块,不仅可以加快研发的速度,同时更易维护和升级源码。对系统的升级影响范围也会更小,一些核心模块尽可能简单、稳定。以做到对他的升级频率尽可能的低,一些重要的核心模块甚至几年都不需要做升级。
Redis cluster将数据处理和集群处理合在一起的设计并不是好设计,后期任何的bug修复都需要升级Redis,而Redis数据是完全保存在内存中的,1G的文件加载至少需要数秒时间,也就是1G数据的Redis升级至少会导致数秒时间服务不可用。
-
在线升级
我们一个正常的升级重启操作也可能导致业务异常,比如用户采用的是长连接的操作,重启后如果重连机制有问题,那么就会对业务产生影响。解决这个问题的方法是在线热升级,Linux内核有热升级的技术,可以不停机升级内核补丁,应用层的热升级技术会更加麻烦,而且不同的应用层架构方法不一样。如果采用该种技术,那么升级程序就不用重启进程,那样才可能做到对用户真正的无损升级。
异常情况处理
我们的目标是希望在故障发生前就是发现问题并解决问题,次之是在问题发生的时候系统能自动解决问题。最差的方案是故障发生后系统能及时发现并通知到对应的处理人处理。
-
自动容灾
在主机宕机后,服务能够自动切换到从库.
-
数据多份备份及确保数据强一致
在一台机器的磁盘损坏时,多备份数据能够确保数据不丢失,Redis的数据复制采用异步的方式进行,异步同步的方式无法确保主从数据的完全一致,如果网络的抖动导致Redis从库重新同步主库的数据,那么数据的不一致性将会更大。为了确保数据的一致性UMEM优化了Redis的同步方法,将异步改为了同步,确保数据的强一致性。目前所有开源的Redis集群系统都不支持数据的强一致。所以使用这些集群系统在用作持久化数据库时理论上都有数据丢失的风险。
-
主动监控
主动监控能帮我及时发现问题,如在机器负载快达到阈值前,将负载较高业务做迁移可以提前避免问题发生。在升级后对升级的业务的连接数和QPS做监控,可以及时发现那些因为升级受到影响的业务。Redis里有个aof重写的特性,当aof文件较大时重写可能会导致磁盘io负载较高,如果正好处于业务高峰期,那么aof文件重写会影响自身和其他业务的性能,如果主动监控aof文件的大小,在达到一定阈值但未自动触发重写前我们主动在业务低峰的时候触发aof重写的话,那样就可以大大缓解重写对业务的影响。
-
自动化运维
在异常发生时,人肉的处理效率较低而且容易出错,最好的方式是采用工具或者系统自动化的处理方式,既可以提升处理效率又可以降低出错的风险。
服务化分布式Redis的设计
谈好了系统设计的思想,下面来讲讲我们系统的架构设计。
该架构图只包含了系统的主要模块,部分细节并没有体现,比如路由模块包含快慢两个模块。
系统的数据分布采用了一致性hash,一致性hash的好处是系统扩容和缩容的时候只会影响到扩容缩容数据的重新分布。
同步采用了强一致的同步方式,只有主从同时写成功才返回成功。这个解决了Redis异步同步延时导致的数据不一致的问题,同时也解决网络抖动导致主库全量同步数据到从库导致主库负载较高的问题。
自动容灾采用的主从节点上报心跳到管理模块,有管理模块决策并更改路由实现,自动容灾的原理和影响如下图。
这里的T秒是指机器宕机后到管理节点决策成功并修改路由的时间。
UMEM的数据分布基于虚拟节点的一致性hash原理,如下图集群中先前有node1和node2两个节点,每个节点各有3个虚拟节点组成。
在集群中增加node3节点后只会有一小部分数据需求迁移。在线迁移可以利用Redis的Migrate命令,Redis单进程单线程的模式以及migrate原子化的操作为在线迁移带来了不少便利。
Q&A
由于提问的内容太多,无法一一在这里统一发出,本文截取部分QA问题与大家进行分享,稍后会将完整QA发送至提问的同学邮箱,感谢大家的支持:)
Q1:问个问题,Redis主从配置我们遇到过脑裂的问题,sentinal无法判断哪个是主哪个是从,你们遇到过没有?
答:我们的Redis的容灾模块是自己研发的,没有使用sentinal。
Q2:另外,Redis cluster是没有一致性协议的保证的吧?
答:3.0集群的可以保证,我们UMem也可以。
Q3:如何解决Redis的多CPU利用率不高的问题?
答:可以考虑使用分布式架构,或者主从读写分离的方式。
Q4:这样的服务,比较适合哪些公司呢?
答:有3类场景比较适合:1、对性能要求较高,如几十万QPS以上;2、对数据量要求比较大,如数百G甚至数T以上的业务;3、业务增长较快业务,可以支持在线扩容。
Q5:怎么做到高可用和集群兼顾的?
答:集群和高可用并不冲突,集群中每个节点都可以有主从备份,比如可以同时有多个代理层,主从存储节点,多个管理模块,代理层的高可用可以通过HAProxy或者LVS,存储层可以使用管理模块监听心跳做存活监控和容灾。
Q6:有没有测试过百T量级甚至更高的规模?
答:UMEM最大有超过数T的实例,数百T量级暂时没有测试,但是UMEM的存储层和代理层都支持水平扩展,所以理论可以支持数百T的量。
Q7:如果把Redis直接作为应用的DB使用,有什么限制或者问题吗?
答:Redis支持aof模式的持久化,aof模式下Redis的写请求消息会被记录到磁盘,aof刷新磁盘的频率可以设置,建议将aof刷新磁盘的频率设置的小一些,比如1s刷新一次,甚至每次都刷新,但是aof模式在Redis重启后更新数据较慢,建议将aof文件重写的阈值设置的低些,或者采用主动监控的方式在aof文件增长到一定阈值的时候且在业务比较低峰的时候主动触发aof文件重写。
Q8:Redis数据量(内存不够)过大导致意外宕机或者hang起,有什么好的技巧或者方案?
答:一般建议Reids内存使用量不要超过机器的一半,另外单实例的Redis也不要太大,一般建议不要超过20g。如果超过该容量建议使用集群的模式。
Q9:路由节点 到 管理节点 获取路由表,是一个什么策略?频率?
答:路由节点从管理节点获取路由可以在请求第一次到达的时候获取,然后可以进行缓存,等到路由有变更的时候再获取即可。
10: Codis不是可以么?应用场景多嘛?
答:codis模块较多,需要除了proxy和codis server节点外还需要部署zk集群,dashborad ,维护较麻烦,而且一个codis集群只支持一个实例,如果多个实例的话还需部署多个集群,管理维护都比较麻烦。另外codis只支持异步同步,无法确保数据完全一致,比较适合应用在缓存的场景下。
下期预告
参与方式详见: