4

每日一面 - Redis程序设计中,上百万的新闻,如何实时展示最热点的top10条呢

 3 years ago
source link: https://my.oschina.net/u/3747772/blog/4874897
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
每日一面 - Redis程序设计中,上百万的新闻,如何实时展示最热点的top10条呢 - zhxhash的个人空间 - OSCHINA - 中文开源技术交流社区

假设可以使用 MySQLredis本地缓存以及MQ

用户量级千万,新闻数据百万用户数比新闻数还多。用户的操作包括:

  • 关注某个新闻
  • 获取某个新闻的关注数量
  • 获取 top10 热点新闻
  • 查询自己关注的新闻。

可以推测,获取 top10 热点新闻请求会远大于关注某个新闻的请求。这些请求都不能直接压入数据库,数据库受不了。

首先想到的是 Redis 中的 Zset,所有的新闻id作为key放入同一个zset中,用户关注某个新闻,使用 zincrby 给这个新闻分数 +1。读取 top 10的时候,用zrevrange.

并且,在实际业务上(例如微博热点话题,知乎热点话题,都是每过一段时间才更新的),top10 热点新闻并不是实时更新的,可以接受一点延迟,可以通过客户端实例的本地缓存,将读取到的 top 10 存在本地缓存一段时间,过了这段时间自动失效。

但是这样也会很快遇到性能瓶颈

1.zset 在很大时可能不满足我们对于性能的要求: Redis 的 Zset 在数量够大的时候底层基于 skiplist:

image

skiplist 实现简单,插入、删除、查找的复杂度均为O(logN)。zincrby 实际上是一个查找+删除+插入(当然由于score只加了1,所以删除插入只修改相邻节点,这个有优化)

我们的场景是首先插入的新闻分数都是0,之后增长这个分数,在新闻很多,并且并不能确定某些新闻是热点的时候,zincrby 导致的节点变动很频繁。这个通过业务设计可以优化,例如新闻分级,不同级别的新闻初始分数不同

2.放入同一个 zset,对于单实例 redis 性能瓶颈时,扩展不友好

用户千万量级,更新很频繁,如果都更新同一个 zset,很快会遇到性能瓶颈。读取还好说,可以通过本地缓存,因为展示最热点的top10的实时性要求并没有那么高。这时考虑 redis 集群,redis分片,但是如果放在同一个 zset,无法分摊压力。

那么我们可以换一种思路:redis 中,每个新闻id作为key,关注数作为value,存储简单的键值对。用户关注了某个新闻:

同步事务更新数据库中的用户关注新闻表,这个每个用户会均摊行锁压力 缓存新闻id key + 1(注意catch住缓存不可用的异常) 写入 MQ,之后返回 MQ 消费更新数据库这个新闻的关注数量,这样不会有性能瓶颈,同时针对新闻id做queue以及线程分区(就是同一个新闻总是对应特定的queue以及线程,尽量每一个行锁一个线程更新,避免数据库 lock wait timeout)

怎样获取 top 10:定时任务扫描数据库,按照新闻关注数量排序获取top10,直接放入缓存。用户请求都是读取这个缓存。虽然实时性差,但是能满足需求。

读取某个新闻的关注数量:这个就读缓存,缓存不可用,读取数据库。

获取某个用户关注的新闻列表:这个读取数据库,如果感觉也有性能瓶颈,对于每个用户id添加缓存保存关注的新闻列表即可。这个很简单


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK