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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 每日一面 - Redis程序设计中,上百万的新闻,如何实时展示最热点的top10条呢

假设与要实现的功能


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

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

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

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


基于 Zset 的解决方案


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


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


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

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


微信图片_20220624200434.jpg


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

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


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

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


换一种思路(按照评论区的各位兄弟建议,修改了,之前的太意识流了,不好意思)


数据库中存储每个用户关注的新闻,也就是用户关注新闻表,表结构包括:

用户id
新闻id


还存储 每个新闻的关注数量表

新闻id(主键)
关注数量

用户 id 和 新闻 id 组成联合主键。


redis 中,每个新闻id作为key,关注数作为value,存储简单的键值对。

用户关注了某个新闻,发来请求:

  1. 同步更新数据库中的用户关注新闻表,由于用户关注新闻表每个用户会均摊行锁压力
  2. 更新 Redis 缓存,INCR 新闻id (注意catch住缓存不可用的异常)
  3. 将新闻 id 与 用户 id 写入 MQ,请求返回。


之后 MQ 消费更新数据库这个新闻 id 的关注数量,根据消息中的新闻 id 将每个新闻的关注数量表的关注数量加一:

  • 考虑 MQ 重复消费,则可以将 MQ 新闻id与用户 id组合作为 key 存储进入 redis 并设置超时时间(setnx ex),如果不存在则没放入,如果存在则不放入。这里还是有一种极限情况,就是放入 redis 但是没更新数据库进程重启,并且在 key 过期前又消费了这个消息,那么就会认为这个消息已经消费了。
  • 同时还需要针对新闻id做queue以及线程分区(就是同一个新闻总是对应特定的queue以及线程,尽量每一个行锁一个线程更新,也就是尽量避免多线程更新数据库表的某一行,避免数据库 lock wait timeout)
  • 另外,分区大小最好是 2 的 n 次方,因为对于 2 的 n 次方取余相当于对 2 的 n 次方 - 1 取与运算,比取余快很多很多。


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


读取某个新闻的关注数量:这个就读 redis 缓存,缓存不可用,读取数据库 用户关注新闻表 (使用 select count(1) ... group by),注意如果读取数据库那么最好加上本地缓存防止压垮数据库。

获取某个用户关注的新闻列表:这个读取数据库 用户关注新闻表,如果感觉也有性能瓶颈,对于每个用户id添加缓存保存关注的新闻列表即可,用户发来关注新闻请求的时候同步更新。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
缓存 NoSQL 算法
17- 数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?
保证Redis中的20w数据为热点数据,可以通过设置Redis的LFU(Least Frequently Used)淘汰策略。这样,当数据库有1000万数据而Redis仅能缓存20w时,LFU会自动移除使用频率最低的项,确保缓存中的数据是最常使用的。
150 8
|
4月前
|
机器学习/深度学习 缓存 监控
Redis经典问题:热点key问题
本文介绍了Redis中的热点key问题及其对系统稳定性的影响。作者提出了多种提前发现热点key的方法,包括历史数据分析、业务分析、实时监控、用户行为分析和机器学习预测。同时,文章列举了应对热点key的解决方案,如分布式存储、主从复制、前置缓存、定时刷新、限制逃逸流量和兜底逻辑。通过这些策略,可以有效管理和预防热点key带来的挑战,保证系统性能和可用性。
657 5
|
4月前
|
缓存 NoSQL Java
【九】springboot整合redis实现启动服务时热点数据保存在全局和缓存
【九】springboot整合redis实现启动服务时热点数据保存在全局和缓存
125 0
|
4月前
|
存储 缓存 负载均衡
Redis 热点key
Redis 热点key
62 0
|
10月前
|
存储 缓存 NoSQL
分布式系列教程(07) -分布式Redis缓存 (缓存雪崩&穿透&热点key)
分布式系列教程(07) -分布式Redis缓存 (缓存雪崩&穿透&热点key)
169 0
|
缓存 NoSQL Redis
数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据
通过合理的缓存淘汰策略、数据预加载和缓存保护机制等手段,可以确保Redis中的数据都是热点数据,提高缓存的效率和命中率。
495 0
|
存储 缓存 NoSQL
Redis热点大Key的优化过程
Redis热点大Key的优化过程
241 0
|
存储 SQL 缓存
【Redis热点数据缓存】
【Redis热点数据缓存】
400 0
【Redis热点数据缓存】
|
存储 NoSQL 算法
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
189 0
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
|
存储 缓存 NoSQL
【Redis实战】面试热点:缓存穿透、缓存击穿、缓存雪崩的区别和解决方案
面试热点:缓存穿透、缓存击穿、缓存雪崩的区别和解决方案
【Redis实战】面试热点:缓存穿透、缓存击穿、缓存雪崩的区别和解决方案