90. 说一下使用 Redis 实现大规模的帖子浏览计数的思路

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 90. 说一下使用 Redis 实现大规模的帖子浏览计数的思路

90. 说一下使用 Redis 实现大规模的帖子浏览计数的思路


本文我们就来聊一聊,Reddit 是如何在大规模下统计帖子浏览量的。

统计方法

我们对统计浏览量有四个基本的要求

计数必须达到实时或者接近实时。

每个用户在一个时间窗口内仅被记录一次。

帖子显示的统计数量的误差不能超过百分之几。

整个系统必须能在生成环境下,数秒内完成阅读计数的处理。

满足上面四个条件,其实比想象中要复杂。为了在实时统计的情况下保持精准度,我们需要知道某一个用户之前是否浏览过一篇文章,所以我们需要为每一篇文章存储浏览过它的用户的集合,并且在每次新增浏览时检查该集合进行去重复操作。

一个比较简单的解决方案是,为每篇文章维护一个哈希表,用文章ID作为key,去重的userid的集合(set数据结构)作为value。

这种方案在文章数量和阅读数比较小的情况下,还能很好的运行,但当数据量到达大规模时,它就不适用了。尤其是该文章变成了热门文章,阅读数迅速增长,有些受欢迎的文章的阅读者数量超过百万级别,想象一下维护一个超过百万的unqine userId的集合在内存中的,还有经受住不断的查询,集合中的用户是否存在。

自从我们决定不提供100%精准的数据后,我们开始考虑使用几种不同的基数估计算法。我们综合考虑下选出量两个可以满足需求的算法:

线性概率计算方法,它非常精确,但是需要的内存数量是根据用户数线性增长的。

基于HyperLogLog (HLL)的计算方法,HLL的内存增长是非线性的,但是统计的精准度和线性概率就不是同一级别的了。

为了更好的理解基于HLL的计算方法,究竟能够节省多少内存,我们这里使用一个例子。

考虑到r/pics文章,在本文开头提及,该文章收到了超过一百万用户的浏览过,如果我们存储一百万个唯一的用户ID,每一个id占用8个字节,那么仅仅一篇文章就需要8mb的空间存储!对照着HLL所需要的存储空间就非常少了,在这个例子中使用HLL计算方法仅需要 12kb的空间也就是第一种方法的0.15%。

(This article on High Scalability 这篇文章讲解了上面的两种算法.)

有很多的HLL实现是基于上面两种算法的结合而成的,也就是一开始统计数量少的情况下使用线性概率方法,当数量达到一定阈值时,切换为HLL方法。这种混合方法非常有用,不但能够为小量数据集提供精准性,也能为大量数据节省存储空间。该种实现方式的细节请参阅论文(Google’s HyperLogLog++ paper)

HLL算法的实现是相当标准的,这里有三种不同的实现方式,要注意的是,基于内存存储方案的HLL,这里我们只考虑Java和Scale两种实现

Twitter的Algebird库,Scala实现,Algebird的文档撰写非常好,但是关于它是如何实现HLL的,不是很容易理解。

stream-lib库中的**HyperLogLog++**实现,Java编写。stream-lib代码的文档化做的很好,但我们对如何适当调优它,还是有些困惑的。

Redis的HLL实现(我们最终的选择),我们觉得Redis的实现不管从文档完善程度还是配置和提供的API接口,来说做的都非常好。另外的加分点是,使用Redis可以减少我们对CPU和内存性能的担忧。

Reddit的数据管道,主要都是使用Apache Kafka的。每当一个用户浏览一篇文章时,就会触发一个事件并且被发送到事件收集服务器,然后批量的将这些事件发送打kafka中进行持久化。

Reddit的浏览统计系统,分为两个顺序执行的组成部分,其中的第一部分是,被称为Nazar的kafka队列『消费者』(consumer) ,它会从kafka中读取事件,然后将这些事件通过特定的条件进行过滤,判断改事件是否应该被算作一次文章阅读计数,它被称为『NAZAR』是因为在系统中它有作为『眼镜』的用处,识别出哪些事件是不应该被加入到统计中的。

Nazar使用Redis 维护状态还有一个事件不被计数的潜在原因,这个原因可能是用户短时间内重复浏览统一文章。Nazar会在事件被发送回kafka时,为事件添加一个标识位,根据该事件是否被加入到计数当中的布尔值。

统计系统的第二部是一个称为Abacus 的kafka『消费者』它会真正的统计浏览量,并且让浏览量数据可以在整站和客户端上显示, 它接收从Nazar发送出来的事件消息,然后根据该消息中包含着标识值(Nazar中处理的)来判断这个事件是否算做一次计数,如果事件被计数,Abacus会首先检查这个事件中文章的HLL计数是否存在于Redis中,如果存在,Abacus会发送一个PFADD请求给Redis,如果不存在,Abacus会发生一个请求到Cassandra集群,Cassandra集群会持久化HLL 计数和真实的原始计数数据,然后再发送一个SET请求到Redis,这个过程通常出现在用户阅读一个已经被Redis剔除的就文章的情况下发送。

为了让维护一个在Redis可能被剔除的旧文章,Abacus会定期的,从Redis中将HLL过滤数据,包括每篇文章的计数,全部写入到Cassandra集群中,当然为了避免集群过载,这个步骤会分为每篇文章10秒一组批次进行写入。下图就是整个过程的流程图。

相关实践学习
基于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
目录
相关文章
BXA
|
存储 数据采集 消息中间件
Redis在大规模分布式系统的应用与优化
在分布式系统中缓存是非常重要的组件。Redis作为一种主流的缓存系统具有高性能、高可用性、高可扩展性等特点,在分布式缓存中得到了广泛的应用
BXA
140 0
|
NoSQL Redis
Redis学习4:List数据类型、拓展操作、实现日志等
注意点:对存储空间的顺序进行分析!
Redis学习4:List数据类型、拓展操作、实现日志等
|
存储 NoSQL Redis
Redis学习3:hash类型操作、拓展操作、实现购物等
首先可以理解成一个redis里面有一个小的redis。同时要注意引入了一个field的名字。
Redis学习3:hash类型操作、拓展操作、实现购物等
|
缓存 NoSQL 安全
2021年你还不会Shiro?----10.使用redis实现Shiro的缓存
上一篇文章已经总结了使用ehCache来实现Shiro的缓存管理,步骤也很简单,引入依赖后,直接开启Realm的缓存管理器即可。如果使用Redis来实现缓存管理其实也是一样的,我们也是需要引入redis的依赖,然后开启缓存传入自定义的redis的缓存管理器就行。区别是我们需要为自定义的redis缓存管理器提供自定义的缓存管理类。这个缓存管理类中需要使用到redisTemplate模板,这个模板我们也是需要自己定义。
234 0
2021年你还不会Shiro?----10.使用redis实现Shiro的缓存
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 NoSQL 关系型数据库
「Redis」事务实现机制
Redis事务实现机制
336 0
|
消息中间件 设计模式 NoSQL
异步结果通知实现——基于Redis实现,我这操作很可以
前段时间,我在内存中实现了一个简单异步通知框架。但由于没有持久化功能,应用重启就会导致数据丢失,且不支持分布式和集群。今天这篇笔记,引入了 Redis 来解决这些问题,以下是几点理由: 数据结构丰富,支持 List、Sorted Set 等 具有持久化功能,消息的可靠性能得到保证 高可用性,支持单机、主从、集群部署 项目中已使用,接入成本更低 基于 Redis 实现延时队列也有几种方法,展开详细讲讲。
|
NoSQL 前端开发 PHP
thinkphp+redis实现秒杀功能
thinkphp+redis实现秒杀功能
192 0
thinkphp+redis实现秒杀功能
|
NoSQL PHP Redis
PHP结合redis实现点赞功能
PHP结合redis实现点赞功能
110 0
|
缓存 移动开发 NoSQL
php结合redis实现高并发下的抢购、秒杀功能的实例
php结合redis实现高并发下的抢购、秒杀功能的实例
217 0