高性能计数服务方案
# 背景
目前市面上很多媒体平台都有播放数、观看数、下单数的这么些概念。
对于这个计数根据业务又有区分,有风控的、没风控的,没风控的就是允许刷,点击一次计数就+1,就能无限制的去刷,这样自然会给产品带来一些问题的,比如电商,不做风控的话就可能百万销量的商品质量却很差,而且退货退款是不会把数字减回去的,对于平台和商家来说,高销量也更好看一些,这样自然会有一些灰产诞生,有些平台也是睁一只眼闭一只眼,亦或是像b站这种媒体平台,就会产生视频质量与播放数不符的情况。
所以一般来讲都会对这个数量做一个风控,比如直接按人头数算,一个人不会产生两次播放数累加,或者是以IP为粒度,又或者是一定时间内不会产生重复的播放数累加,而且目前这些风控更多是基于账号去做的,因为现在账号基本上都是实名制,如果风控算法检测到这个账号短时间有大量的点赞评论或者是播放视频,而且播放时长都很短,那大概率是机器人,可能就会直接把这类账号加到风控名单中,如果发现在一个IP产生大量的这类账号可能就是工作室了,那也会对IP做一个风控。
然后电商那边呢有个实际的例子,我朋友的抖音店,喊我去刷单,我们刷单的策略是对0销量的商品加入购物车一起下单,然后跟客服闲聊几句,做戏嘛,结果给抖音的风控检测到直接把销量清零了,可以看出抖音的风控还是比较严格的,所以根据风控策略我们的技术实现也会有很大的不同。
本文讨论一下关于计数业务的一些技术实现思路,比如文章阅读数、b站视频播放数等等。
# 需求分析
以播放数为例,播放数就是一个视频的播放次数,也代表着这个视频的热度,当然代表热度的可能还有其他数字在背后,比如展示次数,点击次数,点击次数就比较接近播放数,也是一些推荐算法最关注的一个数字。
# 技术实现思路
技术实现思路我们一步步来想,首先是最基本的功能实现,直接在视频表里加一个字段去计数,点一次计数就+1
这样就满足了最基本的点击次数+1和播放数展示的功能,但单纯这样去设计显然是不太合理的,首先这个字段是一个更新非常活跃的字段,频繁的IO写入更新无非是加大数据库连接池的压力,也会影响其他链路的接口响应速度。那么该怎么去做优化呢?
首先得明确一个点,这个计数服务应该是非常通用的,而且肯定不能是核心链路,那么就需要单独抽象出来一个服务去做集群部署,在这个前提下我们先拟几点要求:
- 性能要高,至少单机上万的QPS得扛得住
- 尽量做到高可用,服务理论上可以挂掉,但是需要有默认值兜底
- 不要求数据的绝对准确,可以允许一定的丢失
- 对数据实时性要求不高,几分钟同步一次这样
针对这四点逐个分析。
# 1.高性能
首先要做到高性能肯定是不能直接读磁盘,只能是内存读写,我们读写分开讨论。
针对读,我们的架构就可以是这样:
应用节点直接读取redis,如果压力大的话redis也需要做主从,这里可能会想那在应用节点也做一级缓存是不是可以进一步提高读的性能?可以是可以,但是多节点的一致性就很麻烦了,负载均衡就可能会刷新一会加一会减,而且这样做的话应用节点相当于是有状态了,不利于扩展,所以建议还是直接读中央缓存比较好,甚至可以不用去设置过期时间,就不用担心雪崩、穿透的问题了。
针对写,我们的架构可以是这样:
这样应用节点就不需要关心写入Redis后与MySQL的同步问题,本身这也是一个异步的操作,至于同步的落地方案可以是使用中间件亦或是使用分布式定时任务去调度服务执行同步程序。在这个基础上我认为还可以进一步优化,就是在应用节点在做一级缓存,就好比把请求都兜住,攒一波再写到Redis里面,这样就能保证写性能的绝对高,减少RPC远端内存,架构就变成了这样:
因为我们要做到计数服务的通用性,所以数据模型可以参考这样一个结构:
- 业务类型ID(Biz Type):区分不同业务分类,如视频、商品、文章。
- 业务实体ID (Entity ID):唯一标识每个被计数的实体。
- 计数类型 (Counter Type):区分不同计数的类型,如播放数、点赞数、评论数等。
- 数量 (Count):记录对应计数项的具体数量。
- 时间戳 (Timestamp):记录计数的时间,可用于分析趋势和历史数据。
- ...
{
"bizType": 1,
"entityID": "video123",
"counterType": "video_play_count",
"count": 1000,
"timestamp": "2022-01-01T12:00:00Z"
}
2
3
4
5
6
7
存入Redis的时候key:bizType,value:数据模型json。
# 2.高可用
高可用就是在调用计数服务的时候做好熔断降级,并且返回默认值来兜底。
# 3.数据非绝对准确
不追求数据的绝对准确性,首先数据的绝对准确性很难保证,因为要考虑极端的情况,比如本地内存写入Redis的时候,应用节点挂了,自然应用节点本地缓存的计数就丢失了,就算不用本地缓存的方案,直接写入Redis的时候应用节点宕机一样是会丢失当次数据,以及Redis宕机的时候,也会出现数据丢失,如果用本地缓存还可以在本地做个兜底,写入Redis失败的时候先在本地兜着,然后等服务恢复的时候再写入,如果Redis迟迟没恢复,本地节点就需要选择性的丢弃数据了,也同样会造成数据丢失。
# 4.数据实时性
我认为数据的实时性达到分钟级就可以了,没必要每秒都去更新,带来的价值不高,反而对性能的影响更大,具体的实时性就可以根据定时任务的周期、节点的可靠性去调整。
# 优缺点
优点: 这个架构方案可以做到DB的压力非常低,能够保证高性能
缺点: Redis集群可能会比较大,压力都在Redis这里
# 关于风控策略
关于风控策略,以一定时间内同一账号对同一实体不产生多次技术为例,我们可以增加一个缓存,用于存储用户查看的实体id,过期时间就设为需要管控的时间段,如一天、一周、一个月,通过判断这个key是否存在来判断计数是否+1。