问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

微服务架构下的点赞系统设计

创作时间:
作者:
@小白创作中心

微服务架构下的点赞系统设计

引用
CSDN
1.
https://blog.csdn.net/Dongdong20021203/article/details/145022417

点赞系统是社交应用中常见的功能,如何设计一个既通用又独立、同时支持高并发且安全的点赞系统?本文将从需求分析、技术架构、数据库设计、微服务实现等多个维度,详细阐述一个基于微服务架构的点赞系统设计方案。

一、业务需求

点赞业务需要满足以下特性:

  1. 通用:点赞业务在设计的时候不要与业务系统耦合,必须同时支持不同业务的点赞功能。
  2. 独立:点赞功能是独立系统,并且不依赖其它服务,这样才具备可迁移性。
  3. 并发:一些热点业务点赞会很多,所以点赞功能必须支持高并发。
  4. 安全:要做好并发安全控制,避免重复点赞。

二、实现思路

为了保证安全,避免重复点赞,我们需要保存每一次点赞记录。同时,因为业务方经常需要根据点赞数量排序,因此每个业务的点赞数量也需要记录下来。

三、技术架构

在实现点赞业务时,主要用到了以下技术和工具:

  • 该点赞业务实现通过微服务架构、数据库存储、Redis 缓存、消息队列和定时任务等技术,实现了通用、独立、高并发和安全的点赞功能。
  • 通过 Nacos 进行服务的配置管理和服务发现,确保服务的可扩展性和配置的灵活性。
  • 采用 RabbitMQ 进行消息的异步传递,实现了点赞操作和点赞数更新的解耦,同时使用 Feign 实现服务间通信,方便不同服务调用点赞服务。
  • 利用 Redis 优化高并发读写操作,通过定时任务定期将 Redis 中的点赞数同步到数据库,确保数据的最终一致性。

四、数据库表设计

点赞的数据结构分两部分,一是点赞记录,二是与业务关联的点赞数。点赞数与具体业务表关联在一起记录,比如互动问答的点赞,就在问答表中记录点赞数。学员笔记点赞,自然是在笔记表中记录点赞数。

点赞表设计如下:

create database tj_remark;
use tj_remark;
CREATE TABLE IF NOT EXISTS `liked_record` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
    `user_id` bigint NOT NULL COMMENT '用户id',
    `biz_id` bigint NOT NULL COMMENT '点赞的业务id',
    `biz_type` VARCHAR(16) NOT NULL COMMENT '点赞的业务类型: qa-回答 note-笔记',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_biz_user` (`biz_id`,`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='点赞记录表';

五、微服务设计

模块创建:创建一个独立的微服务来处理点赞业务。

依赖管理:添加必要的依赖,如数据库驱动、Redis、MQ等。

配置文件:配置服务的端口、数据库连接、缓存配置、消息队列配置等。

启动类:创建启动类,配置服务的基本信息和扫描路径。

代码生成:使用代码生成工具生成基本的代码框架和数据库操作类。

六、接口设计

  1. 点赞/取消点赞接口
  • 接口路径:/like
  • 请求方法:POST
  • 请求参数
  • bizId:业务ID,标识被点赞的对象
  • bizType:业务类型,如qa表示问答,note表示笔记等
  • 响应:无返回值,200表示成功
  1. 查询是否点赞接口
  • 接口路径:/isLiked
  • 请求方法:GET
  • 请求参数
  • bizId:业务ID,标识被查询的对象
  • bizType:业务类型,如qa表示问答,note表示笔记等
  • 响应
  • liked:布尔值,表示用户是否已经点赞该对象

这些接口将满足用户点赞和查询点赞状态的需求。

七、业务流程

我们先梳理一下点赞业务的几点需求:

  • 用户不能重复点赞
  • 点赞就新增一条点赞记录,取消点赞就删除记录
  • 点赞数由具体的业务方保存,需要通知业务方更新点赞数

由于业务方的类型很多,比如互动问答、笔记、课程等,所以通知方式必须是低耦合的,这里建议使用MQ来实现。当点赞或取消点赞后,点赞数发生变化,我们就发送MQ通知。整体业务流程如图:

新增点赞功能实现

● 逻辑说明:

○ 接收 LikeRecordFormDTO 作为请求参数,包含业务信息。

○ addLikeRecord 方法根据 recordDTO 的 liked 属性判断是点赞还是取消点赞操作,调用 liked 或 unliked 方法。

○ liked 方法先检查用户是否已点赞,若未点赞则保存点赞记录;unliked 方法则删除用户的点赞记录。

○ 操作成功后,通过 RabbitMqHelper 发送 MQ 消息,通知其他服务点赞数发生了变化,消息发送到指定的交换器和路由键,内容包含业务 id 和点赞次数。

监听点赞数变更

● 在相关业务服务(如 tj-learning)中添加 MQ 监听器,监听点赞数变更的消息并更新数据库。

● 逻辑说明:

○ 使用 @RabbitListener 监听特定交换器和路由键的消息。

○ 收到消息后,将消息中的点赞数更新到相应业务表中,如 InteractionReply 表。

查询点赞状态功能实现

● 逻辑说明:

○ 接收一个业务 id 列表作为参数。

○ 通过 lambdaQuery 查询用户对这些业务是否已点赞。

○ 最终将用户已点赞的业务 id 以集合形式返回。

暴露 Feign 接口

在 tj-api 模块中定义 RemarkClient 作为 Feign 客户端,用于其他微服务调用点赞服务:

@FeignClient(value = "remark-service", fallbackFactory = RemarkClientFallback.class)
public interface RemarkClient {
    //批量查询我的点赞状态
    @GetMapping("/likes/list")
    Set<Long> getLikedIds(@RequestParam("bizIds") List<Long> bizIds);
}

同时定义 RemarkClientFallback 作为服务降级处理:

@Slf4j
public class RemarkClientFallback implements FallbackFactory<RemarkClient> {
    @Override
    public RemarkClient create(Throwable cause) {
        log.error("查询点赞服务异常", cause);
        return new RemarkClient() {
            @Override
            public Set<Long> getLikedIds(List<Long> bizIds) {
                return null;
            }
        };
    }
}

调用

在查询回复或者评论的时候远程调用点赞微服务查询是否点过赞

八、优化思路

高并发读的优化:1. 优化SQL和代码 2. 添加缓存

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

  • 引入 Redis 缓存
    ○ 使用 Redis 存储点赞记录和点赞数,以应对高并发问题。
    ○ 存储点赞记录使用 Set 数据结构,存储点赞数使用 ZSet 数据结构。
    ○ 点赞操作(addLikeRecord)通过 Redis 的 SADD 或 SREM 命令操作点赞记录,统计点赞数使用 SCARD 命令。

  • 使用定时任务
    ○ 创建定时任务类 LikedTimesTask,使用 @XxlJob 注解,调用 readLikedTimesAndSendMQ 方法。
    ○ 该方法从 Redis 中读取点赞数,转换数据后发送 MQ 消息,更新其他服务的点赞数。

@Component
@Slf4j
public class LikedTimesTask {
    @Autowired
    private ILikedRecordService likedRecordService;
    @XxlJob("checkLikedTimes")
    public ReturnT<String> checkLikedTime(String param) {
        log.info("开始同步点赞次数");
        likedRecordService.readLikedTimesAndSendMQ();
        return ReturnT.SUCCESS;
    }
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号