高性能点赞功能模块设计的5种解决方案

首先,设计一个点赞模块有哪些要求?

  1. 保存点赞计数
  2. 保存点赞状态/关系,用来判断是否点赞
  3. 保证不会出现重复点赞(接口幂等性问题)

分析一下点赞中的一些行为

  1. 点赞/取消点赞:这是一个读写的操作,需要先通过查询状态,再写入状态,最后对计数++,如果接口没有做幂等性处理,那么这里会有线程安全问题导致的重复点赞,需要保证查询状态到写入状态这两个命令之间的隔离性(通过lua脚本或者排它锁)。
  2. 判断是否点赞:读操作

 下面列举一些方案:

1.直接落库

这种方法简单粗暴,但是对数据库的读写压力过高,通过读写分离/分库分表,在数据量不是特别大的场景下都可以解决,是个不错的方案,但是当表中数据量超过千万级别时,即使做了优化,数据库也开始吃不消了。 

优点:简单

缺点:不适用于数据量过大 

2.redis缓存+mysql定时持久化(不推荐)

这种方法将部分的点赞计数和状态暂时存于redis缓存中,之后再进行定时持久化落库,解决了将全部点赞数据存在redis中出现的内存占用过大的问题,而且因为是直接操作redis,降低了数据库写操作的压力。但是因为数据同时存在于redis和数据库中,在判断点赞状态时,需要同时查询redis和数据库,并且在持久化过程中,需要对持久化成功的hash key进行回删,此时就会出现数据不一致的问题,导致用脏数据删除了新数据。

优点:写操作快 

缺点:需要同时查询redis+数据库,并且有数据不一致问题,实现起来特别麻烦

3.redis位图

 这种方法利用redis的bitmap(位图)来保存,通过uid作为位图的索引来记录点赞状态,无论是查询还是写入,速度都非常快,但是有一个致命的缺点,位图的大小随着uid的大小成倍增长,一个8位数的uid就要让一个bitmap占用几m的内存空间,这对于用户量较多的系统来说显然是不行的,但对于只有几万用户的系统却是个不错的选择。那么我不把bitmap存在内存中不就行了?数据库中有没有类似的bitmap结构?很遗憾,即使是mysql中的varbinary或blob类型也是无法做到的。

优点:读写操作快,实现最为简单,不需要锁就可以解决重复点赞问题

缺点:不适用于注册用户量达到10万级别的系统,否则导致内存占用过大

4.可压缩位图

顾名思义,就相当于是一个可压缩的bitmap实现

 在压缩 bitmap 的实现下,类似于setbit key 1 1, setbit key 5000000 1的操作就不会使压缩Bitmap 内存占用暴涨,而是只会使用 2组 word,即 64 bytes。但是它在Java中毕竟只是一个对象,不像redis中有AOF和RDB的来保证数据不丢失,我们需要自己将这个bitmap对象保存在redis或数据库中,同时要注意线程安全问题。

具体的实现有RoaringBitmap和 EWAHCompressedBitmap,RoaringBitmap自称比EWAH更好,导入项目即可使用

RoaringBitmap github地址:https://github.com/RoaringBitmap/RoaringBitmap

EWAH github地址:https://github.com/lemire/javaewah

优点:读写较快,解决了bitmap空间利用率低,占用内存的问题

缺点:需要做持久化保障数据不丢失

5.redis的Hash+布隆过滤器

使用到redis的hash+布隆过滤器

先介绍一下布隆过滤器,布隆过滤器主要有两个命令,一个是往过滤器里面添加元素的命令。另一个命令是用来判断某个元素是否在过滤器里面,但是他判断元素存在的结果不是绝对准确的,误差基本在0.1%以下,这个误差对点赞数据来说基本可以接受,并且只影响查询操作。当布隆过滤器说某个元素存在的时候,可能是错的(假阳性),但布隆过滤器说某个元素不存在的时候,就肯定不存在(真阴性)。他的内存占用非常低,插入和判断的速度都是常数,原理就不多说了。

用hash来存储点赞计数,例如,hash的key保存文章id,value保存点赞数,而且redis的incrby指令是原子性的,这点不需要担心。而布隆过滤器主要用来保存文章点赞者的uid,用来判断点赞状态,但是布隆过滤器有个缺点,不能进行删除操作,那么我们如何解决取消点赞的情况?因此,我们还要再对取消点赞的记录进行保存,只有当布隆过滤器判断为已点赞状态下,我们才需要再去查询取消点赞的记录,不过因为点赞和取消点赞的频率相差往往是很大的,可能是5:1也可能是10:1,这部分内存占用不会特别大,频率也不高,因此即可以选择保存在redis的set中,也可以保存在关系数据库中。

我们用点赞为例子来具体分析一下流程(接口未做其他幂等性处理的情况下):

1.点赞:

最后为了保证redis数据不丢失,开启RDB+AOF是必要的。

优点:比第二种方案好

缺点:实现起来较难

另外还有一种布谷鸟过滤器,类似于一种支持删除元素的布隆过滤器,但是情况比较复杂,坑比较多。

发布了13 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_40908734/article/details/105168244