点赞系统设计

点赞系统介绍

一个评论的点赞数越高,排名也就越靠前,热度也随之更高

用户回答和评论的欲望就会增加,网站的活跃度也会越来越高

点赞功能在电商、社交、游戏等几乎所有的互联网项目中都广泛使用

其中蕴含着很多的技术方案,最重要的就是如何处理点赞的高并发

系统设计规范

一个通用点赞系统需要满足下列特性:

  • 通用:点赞业务在设计的时候不要与业务系统耦合,必须同时支持不同业务的点赞功能

  • 独立:点赞功能是独立系统,并且不依赖其它服务,这样才具备可迁移性

  • 并发:一些热点业务点赞会很多,所以点赞功能必须支持高并发

  • 安全:要做好并发安全控制,避免重复点赞

技术栈

Maven、SpingCloud、RabbitMQ、MybatisPlus、xxl-job、Redis

数据库表设计

点赞的数据结构分两部分

一是点赞记录:谁给什么点赞的记录

二是与业务关联的点赞数:每一个业务都有自己的一个点赞数字段

点赞记录表字段:主键id、用户id、业务id、业务类型、创建时间、更新时间

系统需求接口

点赞按钮有灰色点亮两种状态。也就是说需要实现查询用户点赞状态的接口

这样前端才能根据点赞状态渲染不同效果,要实现的接口包括:

• 点赞/取消点赞

• 查询是否点赞

第一次点击是点赞,按钮会高亮

第二次点击是取消,点赞按钮变灰

返回值设计:

• 方案一:无返回值,200就是成功,页面直接把点赞数+1展示给用户即可

• 方案二:返回点赞数量,页面渲染

这里推荐使用方案一,因为方案二每次统计点赞数量也有很大的性能消耗。

一也有缺陷,两个人同时点赞或取消都会加减一,不会加减二,没有及时的同步数据

点赞业务的几点需求:

  • 用户不能重复点赞

  • 点赞就新增一条点赞记录,取消点赞就删除记录

  • 点赞数由具体的业务方保存,需要通知业务方更新点赞数

由于业务方的类型很多,比如互动问答、笔记、课程等

所以通知方式必须是低耦合的,这里建议使用MQ来实现。

整体实现思路

增删点赞逻辑

点赞系统发送

调用流程

根据前端传入的参数判断走点赞还是取消逻辑

点赞或取消失败直接结束

点赞或取消成功则统计当前业务点赞数

使用rabbitMqHelper将返回结果投递给MQ

rabbitMqHelper.send(交换机,routingKey,业务id,点赞数)

雪花算法

每个业务的业务id是由雪花算法生成的

因为在微服务项目中或者多业务存储场景中

使用雪花算法可以避免不同业务相同id的出现

比如评论业务和笔记业务存储在同一个点赞记录表中

点赞操作

先查询当前用户id是否对当前业务id点过赞,统计存在记录,返回count

如果点了count>0,则直接返回false结束,点赞失败

若没点过则保存一条记录,从ThreadLocal中拿用户id

取消点赞

直接根据业务id和用户id删除记录即可

业务系统监听

在业务微服务中建立一个MQ监听类,使用同样的实体类接收数据,并保存到数据库中

此时数据库两张表被更改

一张多一条记录,一张点赞数加一

查询是否点赞

在点赞和取消操作之前,需要提前查询当前用户是否给当前显示业务点过赞

返回值是所有点过赞的业务id的集合

使用Fegin远程调用remark微服务,接收一批id参数,返回一批点过赞的id

高并发的优化

暴露问题

每一次点赞都伴随4次数据库的读写

点赞操作波动较大,会出现点赞量瞬间激增的情况

对于数据库是一个巨大的压力

改进思路

高并发读优化:1.优化sql和代码 2.添加缓存 3.页面静态化

高并发写优化:1.优化sql和代码 2.同步写变为异步写 3.合并写请求

合并写请求是将60条redis数据1分钟向mysql保存一次

缺点是数据存在1分钟的延迟,非必要场景可以接受

最终优化方案

优化逻辑

提交点赞后,首先去redis中查询是否点过赞

点过直接结束,没有点过赞则向redis新增一条记录

新增后在redis中查询一次点赞数

查询结果再保存在redis中,并通过定时任务和MQ向mysql同步数据

数据存在一定时间的延迟

数据结构

点赞数量缓存的时候,需要存3个参数来适配MQ的参数列表,所以需要redis中能存3个值的数据类型

Hash:key-hashkey-hashvalue 没有排序

Zset:key-value-source 有source的自动排序,有跳表的索引,空间占用大

缓存点赞记录:Set

缓存点赞数量:ZSet

Redis优化实现

redis中有新增覆盖机制,可以不必查询直接保存

如果数据不存在则直接保存成功,并返结果

如果数据存在则保存失败,并返回结果

调用流程

根据前端传入的参数判断走点赞还是取消逻辑

点赞或取消失败直接结束

点赞或取消成功则统计当前业务点赞数

将点赞数保存到redis中

使用ZSet组装key、value、source

使用redisTemplate新增数据

//组装和新增
String likedKey = "likes:biz:" + recordDTO.getBizId();
Long source = redisTemplate.opsForSet().size(likedKey);
String value = recordDTO.getBizId().toString();
String key = "likes:times:type:" + recordDTO.getBizType();
redisTemplate.opsForZSet().add(key, value, source);

新增点赞记录

组装参数 key---likes​:biz:业务id​

组装参数 value---ThreadLocal中拿到用户id

使用redisTemplate新增记录add

Long num = redisTemplate.opsForSet().add(key,value)

获取返回值num判断不为空不为0则成功

删除点赞记录

组装参数 key---likes​:biz:业务id​

组装参数 value---ThreadLocal中拿到用户id

使用redisTemplate新增记录remove

Long num = redisTemplate.opsForSet().remove(key,value)

获取返回值num判断不为空不为0则成功

查询是否点赞

从redis查询用户点过赞的业务,查询点赞记录

判断当前业务id为key所对应的集合中是否有当前用户

使用redisTemplate的isMember()方法判断

redisTemplate.opsForSet().isMember(likedKey, UserContext.getUser().toString()

定时同步数据

XXL-JOB定时任务:配置文件中xxl-job的配置

在点赞微服务中新增一个定时任务的类,同步所有的点赞数的缓存记录

准备一个集合装载所有的Key,遍历这个集合

使用redisTemplate的popMin()方法得到每个key对应的集合对象tuples

遍历tuples集合进行数据转换

循环内准备一个实体类集合装载数据转换后的对象集合

将当前这个实体类集合通过RabbitMQ投递到队列中

业务微服务监听队列,准备一个集合参数接收数据

定义并开启一个定时任务执行器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值