背景
- 在现代互联网应用中,用户之间的互动是提升用户体验和应用活跃度的关键因素。点赞功能是一种简单而有效的用户互动方式。例如,在社交媒体平台(如微博、抖音)上,用户可以通过点赞来表达对他人发布的内容(如文字动态、视频)的喜爱。这种互动行为能够让内容创作者得到反馈,知道自己的作品受到了关注,同时也让点赞的用户能够参与到内容传播中。
- 在内容分享平台(如知乎、今日头条),用户可以点赞有价值的文章、回答等内容。这有助于优质内容在众多信息中脱颖而出,引导其他用户发现高质量的信息资源。
业务需求
首先我们来分析整理一下点赞业务的需求,一个通用点赞
系统需要满足下列特性:
-
通用:点赞业务在设计的时候不要与业务系统耦合,必须同时支持不同业务的点赞功能
-
独立:点赞功能是独立系统,并且不依赖其它服务,这样才具备可迁移性
-
并发:一些热点业务点赞会很多,所以点赞功能必须支持高并发
-
安全:要做好并发安全控制,避免重复点赞
技术选型
mysql:保存点赞记录的信息。
rabbitmq:当用户点赞或者取消点赞之后,异步的投递点赞消息给具体的业务,目的是解耦合。
实现思路
为了保证安全性,避免重复点赞我们需要拆功能键一个保存点赞信息的数据表用于存储当前点赞用户的基本信息;只有这样在下次用户点赞的时候我们就可以查询并判断该用户是不是已经点赞过该评论。初次之外,因为点赞功能一般都是根据点赞的数量来进行排序的,所以我们还需要将点赞的数量记录下来。
业务流程
点赞有两种状态分别是新增和取消点赞,当请求过来的时候需要判断一下是否是新增点赞,如果是新增点赞的话,那么就需要修点赞记录是否存在进行判断,如果存在该用户的点赞记录那么就直接结束请求也不需要对其他业务微服务发送点赞数,如果不存在该用户的点赞记录那么就对点赞记录的数据表进行新增操作并统计点赞数,通过MQ向其他的业务微服务发送点赞数的通知;如果是取消点赞的话,那么也会对点赞记录进行判断如果不存在说明数据表中一开始就没有点赞消息所以不用取消直接结束,如果存在的话就删除点在记录统计点赞数,也是通过MQ来通知给其他业务微服务。
由于用户点赞的每条记录都存储在点赞微服务中,因此点赞微服务还需要对外暴露出一个Feign的接口,供其他微服务调用,以判断某用户是否对某条业务数据已经进行过点赞操作,用户id我们可以从ThreadLocal中获取,因此只需要设置参数需要查询的业务数据的id,接收到后到用户点赞表中进行查询即可。
性能优化
问题分析
从下图的流程可以看出对于点赞功能存在一个较大的隐患,就是在这一个流程当中点赞功能的这一个业务包含了多次对数据库的读写操作,如果一旦点赞的请求在一段时间能突然暴增,可能会对数据库造成巨大的压力。
针对点赞业务高并发的这种问题可以看出点赞业务既有高并发读操作,也存在高并发写操作我们可以分别对这两部分进行优化,对于并发读问题因为我们选用的mysql数据库所以我们就需要考虑对sql的优化,除此之外,还可以通过添加缓存的方式来减少对数据的查询操作,这两种方式都可以很好的改善高并发环境下的性能,要添加缓存我们首选的就是redis数据库。对于处理高并发写操作就需要我们将同步操作变为异步操作,以及将多次请求合并到一起进行提交等操作。
修改后的流程图如下:
对于使用redis缓存来保存点赞记录和点赞数量我们还需要挑选一下数据类型。对于保存点赞记录我们应该使用set这种数据结构进行存储,通过将业务的id设置为key,将用户的id设置为value来将每一个用户的点赞数据保存到redis中这要保存的好处是set这种类型的数据结构是基于HashSet的所以不允许数据库出现重复的数据这样就满足了我们同一个用户不能重复点赞的需求。对于点赞数量这个功能的保存我们应该在hash和zset中来进行挑选,主要是因为需要根据点赞数量是否存在并且对点赞数量的排序这两个方向考虑的;如果没有的话就可以选用hash来存储这样可以减少存储的空间,如果有的话就可以考虑使用zset,将点赞数量存储到score字段中,这样的话zset会根据score来进行自动排序。
最终的改进流程图如下: