标签:
微博业务的迅速发展,对基础架构层面的要求也越来越高。新浪作为国内最早使用redis,并且是国内最大的redis使用者,在redis的使用上,也在逐步优化和提高。
作为微博中一项重要的数据,计数类业务在微博业务中占的比重和重要性逐步提高。计数结果的准确度直接影响用户体验,并且很容易引起用户的投诉。在计数业务上,在不断的优化和改进中,我们主要经历了以下三个阶段:
从2010年开始,使用redis-2.0版本。在最初业务数据比较少的时候,表现相当不错。但随着数据量和请求量的不断增加,一些问题逐渐暴露出来。
主从同步问题
首先遇到的是主从的同步问题。它的原理是当master接收到slave的同步请求后,把内存的数据fork出一个子进程dump出来,形成rdb文件,然后传到slave,slave再把这个文件加载到内存,之后的增量更新由master在执行完每条修改命令后立即同步给slave。 在网络出现问题时,比如瞬断,会导致slave里的数据全部重传。对单个端口来说,如果数据量小,那么这个影响不大,而如果数据量比较大的话,就会导致网络流量暴增,同时slave在加载rdb时无法响应任何请求。
持久化问题
计数业务中多数使用redis作为存储,因此都开启了aof,并配置为每秒做一次fsync操作将写操作刷新到磁盘。随着aof的增长,需要定期rewrite。Rewrite的机制和生成rdb的过程类似,都是fork出一个子进程来完成的,子进程对于磁盘的持续写入会导致父进程的 fsync操作阻塞,造成大量请求超时。
版本升级问题
由于redis在使用初期bug较多,版本迭代频繁,而版本升级需要关闭redis进程并重新加载aof。对于大量使用redis的微博业务来讲,这样的升级成本也越来越难以承受。
内存使用问题
2.0版本的redis,在内存使用上相对比较粗放,对于计数这样一个简单的key-value,占用的内存达到100字节以上,存在比较多的优化空间。
针对redis使用初期存在的问题,我们逐个进行了改进。主从复制参考mysql的同步方式,使用rdb+aof结合的方式,解决了网络瞬断引起的重传问题,同时限制子进程做后台dump时对磁盘的写入,期间暂停主进程的fsync操作,解决了慢请求的问题。
针对计数业务,我们开发了专用的版本redisscounter,单个key-value占用的内存key的长度加4个字节的value,将内存的使用量降低到原来的1/4以下。通过预先分配内存数组和double hash技术,消除了redis中hash表的大量指针开销。
对于版本升级的问题,我们将redis的核心处理逻辑封装到动态库,内存中的数据保存在全局变量里,通过外部程序来调用动态库里的相应函数来读写数据。版本升级时只需要替换成新的动态库文件即可,无须重新载入数据。通过这样的方式,版本升级只需执行一条指令,即可在毫秒级别完成代码的升级,同时对客户端请求无任何影响。
有了上面的改进后,新版本开始大量应用,多数业务都可以作为完整的存储替代以前的mysql+memcached组合。对于微博的评论数和转发数,由于微博条目不断增加,无法保存全量数据,因此采用mysql+redisscounter组合的方式,mysql保存全量数据,使用两组 redisscounter保存最近几个月的热数据,通过定期滚动两组redisscounter里的数据来清理冷数据。
随着微博的发展,针对单条微博的计数也不断增加,从原来的评论数、转发数,又增加了表态数,2013年还上线了阅读数。Redisscounter 不能很好的解决这类扩展问题,同时上面的mysql+redisscounter的滚动方式也过于复杂,定期的滚动操作很容易出现问题。针对这类问题,我们再度做出改进,将key由原先的字符串改成微博id,同时对于每条微博的评论转发等计数,我们统计发现,绝大多数微博的计数都可以用10~15个bit 来保存,因此可以将多个计数保存到一个4字节的value里,过大的计数值在内存中另外开辟一块空间来保存。这样通过一条get命令即可获取该微博的所有计数。同时针对微博业务的特点,越老的微博被访问的次数就会越少,在内存使用多个数组保存不同范围的微博,内存不足时将最老的一组微博dump到ssd 上,内部自动实现的滚动可以保证热微博全部在内存里。对于落到ssd上的老数据的访问,通过异步的io线程来读写,经过这样的改进后,去掉了原先的 mysql存储,降低了业务开发成本和运维成本。
从redis在计数业务上的发展经历可以看出,技术的进步是由业务的需求推动的。随着业务的发展,还会遇到更多新的挑战。希望我们走过的这些改进之路对于读者在使用redis的过程中能有所帮助。
标签:
原文地址:http://my.oschina.net/wanghhao/blog/502282