Redis计数在新浪微博的应用

jopen 10年前

  微博业务的迅速发展,对基础架构层面的要求也越来越高。新浪作为国内最早使用 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 的过程中能有所帮助。

来自: InfoQ