用最少的机器支持万亿级访问,微博提醒6年Redis优化历史等,目前Redis集群存储超过100亿条记录,每天有万亿次读取访问。随着业务的快速发展,我们将与您分享我们在使用过程中遇到的问题和解决方法。主要包括以下几个方面:实现机制的高可用、业务的高度定制化和服务化。Redis2.0时代(2010-2011)实现机制高可用优化微博首先使用Redis2.0版本。在初期业务规模较小的时候,Redis服务比较稳定。但是随着业务数据量和访问量的增加,一些问题也逐渐暴露出来:持久化问题在我们大部分的业务场景中,都是使用Redis作为存储,会开启持久化机制。如果线上采用单机多实例部署结构,服务器的内存占用会比较高。由于正式版触发bgsave和bgrewriteaof操作的时机不可控,依赖于相关配置项和业务写入模型,单机部署的多个Redis实例可能会同时触发bgsave或bgrewriteaof操作。这些操作都是通过fork一个子进程来完成的。由于copy-on-write机制,可能会导致服务器内存快速耗尽,导致Redis服务崩溃。另外,当磁盘压力大时(rdb生成,aof重写),aof的写入和fsync操作可能会被阻塞。虽然从2.4版本开始将fsync操作调整到了bio线程,但是主线程aof的写入阻塞仍然会导致服务阻塞。主从同步问题为了提高服务可用性,避免单点问题,我们线上业务Redis大多采用主从结构部署。正式版的主从同步机制会导致主从在网络出现问题(比如瞬间断线)时重新做一次全量复制。对于单个端口,如果数据量小,影响不大,但如果数据量比较大,就会造成网络流量激增,slave在加载rdb时无法响应任何请求。当然2.8正式版支持psync增量复制机制,一定程度上解决了主从连接断开导致的全量复制问题,但是这种机制受限于复制积压缓冲区大小,并且需要在主库出现故障时执行在master-severing运行场景下,master-slave仍然需要进行全量复制。版本升级和管理问题早期的Redis版本不够稳定,经常需要修复bug,需要新的运维需求和版本优化,导致版本迭代频繁。正式版在执行升级操作时需要重启服务。我们线上业务大部分都开启了持久化机制,重启操作时间比较长。另外,使用Redis的业务线较多,版本升级操作非常复杂。由于版本统一带来的运维工作量过大,线上的Redis版本一度增加到十几个,这也给版本管理带来了很大的困难。为了解决以上问题,我们对Redis原生的实现机制做了如下优化:1、在持久化机制上,采用了rdb+aof的持久化方式。aof文件按照固定大小滚动,在生成rdb文件时记录当前aof的位置。全量数据包括rdb之后的aof文件和记录的位置点,丢弃aof重写机制,在rdb生成后删除无效的aof文件;定时持久化操作的配置项cronsave,分散了单机部署的多个Redis实例在不同时间点的持久化操作,错开业务高峰;aof的写操作也放在bio线程中解决问题。磁盘压力大时Redis阻塞的问题。2、对于主从同步机制,借鉴了MySQL的复制机制,进行了简化。使用rdb+aof支持基于aofpositon的增量复制。从库只需要与主库进行全量同步即可。主从连接断开或主库切换后,从库会与主库进行增量复制。针对版本升级和管理级别问题,将Redis的核心处理逻辑封装在一个动态库中,将内存中的数据存储在全局变量中,通过外部程序调用动态库中的相应函数来读写数据。版本升级时,只需更换新的动态库文件即可,无需重新加载数据。这样,版本升级只需要执行一条指令,代码升级可以毫秒级完成,对客户端请求没有任何影响。???除了以上几点之外,还做了很多其他的优化,比如主从延迟时间检测、危险命令鉴权等。通过逐步优化,Redis内部版本也进入了稳定期,应用规模不断扩大。业务极致定制化时代(2012-2013)RedisCounter/LongSet在一些特定业务场景下,随着业务规模的不断扩大,Redis的使用暴露出一些问题,尤其是服务成本问题(小编:很省服务器是什么意思?)。为此,我们结合具体的业务场景对Redis做了一些定制化的优化。这里主要介绍关系型和计数型两个业务场景下的定制化优化。关系微博关系业务包括添加、取消关注、判断关注关系等相关业务逻辑。引入Redis后,采用hash数据结构,作为存储。但随着用户规模的快速增长,关系型服务Redis的容量已经达到十几TB,并且还在快速增长。如何应对成本压力?为了解决服务成本问题,我们将Redis的角色从存储调整为缓存。这是因为随着用户数量的增加,商业模式已经从最初的热点数据不集中,转变为冷热区分明确。hash数据结构是最好的跟随关系变化和判断跟随关系的数据结构,但是存在以下问题:cachemiss后回写followinglist的性能较差。达到10ms,对于单线程Redis来说是致命的;Redishash结构的内存使用率并不高,保证cahce命中率所需的缓存容量还是很大的。因此,我们定制了longset数据结构,即“定长开放寻址哈希数组”。通过选择合适的哈希算法和数组填充率,可以实现改变和判断注意力关系的性能与原始Redis哈希相媲美。缓存未命中后,通过客户端重建longset结构,实现O(1)复杂度的回写。通过自定义longset数据结构,在保证服务性能的同时,将关系型Redis的内存占用降低了一个数量级(小编:这要节省多少台服务器……有红利吗?)。微博统计有很多统计场景,比如用户维度的关注和粉丝数,微博维度的转发和评论数等等。计数作为微博中非常重要的数据,在微博业务中扮演着非常重要的角色。为了更好的满足盘点业务需求,我们定制了基于Redis的内部盘点服务。为了支持多种数据类型,原生Redis需要维护大量的指针信息。存储一个业务计数大约需要80个字节,内存利用率很低。为此,我们定制了第一个版本的计数器Redis计数器,通过预先分配内存数组来存储计数,并使用doublehash解决冲突,减少了原生Redis中的大量指针开销。通过以上优化,内存开销降低到原来的1/4以下。(小编:又省了3/4服务器。。。)随着微博的发展,微博的纬度越来越高。在原有转发量和评论量的基础上,增加了评论量。2013年还是在线阅读统计。Redis计数器已经不能很好的解决这种扩容问题:存储单条微博相关的计数,需要重复存储微博的mid信息,而且所有数据都存储在内存中,服务成本高;获取单条微博的所有计数,需要多次调用计数接口,服务器压力很大。为此,我们设计了计数器CounterService的改进版本,增加了以下特性:Schema支持多列:支持动态添加列,内存占用降低到位冷热数据分离:存储频繁访问的热数据在内存中,将较少访问的冷数据存储在磁盘上,以降低服务成本。LRU缓存冷数据:增加LRU模块对访问的冷数据进行缓存,保证冷数据的访问性能。异步IO线程访问冷数据:避免冷数据访问影响服务的整体性能??通过以上定制和优化,我们从根本上解决了计数业务的成本和性能问题。除了以上关系和计数业务场景的定制和优化外,还定制了BloomFilter服务来满足判断业务场景的需要;定制VerctorService服务,满足feed聚合业务场景需求;定制SSDCache服务,降低服务成本。(小编:老板感动落泪)服务时代(2014-)CacheService与SSDCache随着微博业务的快速增长,Redis集群的规模也在不断增大。目前微博Redis集群内存占用几十TB。服务于数百个行业,Redis集群的管理仍然面临着很多问题。数据迁移问题随着时间的推移,由于数据量的增加,越来越多的业务已经达到了单端口内存占用的上限。微博内部建议单个端口的内存不要超过20GB,所以端口需要再次拆分,这涉及到数据迁移,目前的迁移操作是通过自研的迁移工具完成的,成本迁移操作比较高。数据路由问题?目前的使用方式需要在业务代码中实现数据路由规则,路由规则的变更需要重新启动代码,业务变更复杂度高。同时,节点配置采用DNS方式,存在实时性和负载不均问题。在使用过程中虽然有相应的解决方案,但需要一定的运维介入,运维复杂度高。HA系统不成熟。目前的HA系统多采用自动发现问题,人工确认处理的策略。没有实现真正的自动化,运维成本仍然很高。为了解决以上问题,我们实现了基于Redis的服务框架CacheService。CacheService最初是为了解决memcached内部使用中遇到的问题而开发的服务框架。主要包括以下几个模块:配置中心ConfigServer微博内部的配置服务中心主要是管理静态配置和动态命名服务的远程服务,当配置发生变化时可以实时通知监听的ConfigClient。资源层实际的数据存储引擎一开始就支持memcached,之后扩展了Redis和SSDCache组件。SSDCache是??内部开发的基于SSD的存储组件,以降低服务成本,用于缓存内存和DB数据之间的warm。代理层作为业务方请求的代理,根据设定的路由规则转发到后端的缓存资源,本身是无状态的。代理启动后,会从ConfigServer加载后端缓存资源的配置列表进行初始化,并实时接收来自ConfigServer的配置变更通知。客户端提供给业务方的SDK包不需要在业务代码中实现数据路由规则,业务方不需要关心后端缓存的资源。只需配置服务池名称组和业务标识符命名空间即可使用缓存资源。客户端从ConfigServer获取代理节点列表,选择合适的代理节点发送请求,支持多种负载均衡策略,自动检测代理节点变化。集群管理系统ClusterManager管理集群中各组件的运行状态,保证业务的SLA指标,并在异常发生时自动进行运维处理。同时,它还负责配置变更、数据迁移等集群操作。为了支持Redis作为服务,在服务框架中支持Redis代理。同时,为实现在线数据迁移,参考Redis集群的设计思路,修改了内部Redis存储,支持slot数据分片。数据迁移操作由ClusterManager组件Execute执行,完成slot重新规划和数据迁移。此外,它还支持Redis的故障转移机制,当主节点或从节点发生故障时会自动进行容错处理。我们的Redis服务项目部落从2015年底开始上线,正在逐步完善中。小结从Redis的优化过程可以看出,技术进步是由业务需求驱动的,我们需要拥抱需求。同时,对于一个服务,我们需要不断优化,保证服务的运维友好性,以保证服务的生命力。一些后续计划将完善服务系统冷热数据分级存储机制,降低服务成本;引入新组件更好地满足业务需求,进一步完善集群管理组件,降低运维复杂度。作者:刘东辉刘东辉,新浪微博基础设施组研发工程师。2013年加入微博,先后参与微博Redis、CounterService、SSDCache、CacheService等基础组件的设计与开发。目前专注于分布式缓存和存储。
