先赞后看,南哥助你Java进阶一大半
geeksforgeeks.org
官网给出了Facebook评论系统的高级设计图,Facebook的评论竟然是支持实时刷新的。也就是说用户不用刷新帖子,只要帖子有新的评论就会自动推送到用户端,这里Facebook使用的便是每天在全球有超过20亿设备在使用的WebSocket
技术。
我是南哥,一个Java学习与进阶的领路人。
相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
⭐⭐⭐本文收录在《Java学习/进阶/面试指南》:https://github/JavaSouth
1. 评论系统设计
1.1 评论表如何设计
评论系统的表要这么设计,每条评论的id标识要么是根评论id、要么是回复评论id。如果是根评论,那parent_comment_id字段就为空;而回复别人的评论,parent_comment_id字段指向根评论id。
CREATE TABLE `comments` (
`comment_id` INT AUTO_INCREMENT PRIMARY KEY, -- 评论唯一ID
`user_id` INT NOT NULL, -- 用户ID
`content` TEXT NOT NULL, -- 评论内容
`parent_comment_id` INT DEFAULT NULL, -- 如果是回复,则指向原始评论ID
`post_id` INT NOT NULL, -- 被评论的帖子或内容ID
`like_count` INT DEFAULT 0, -- 点赞数量
`create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 评论创建时间
);
我们还要给评论加上点赞数,南哥给大家看看抖音的评论设计。
用户可以给每条评论打上点赞,所以我们应该再设计一个点赞表。其实抖音这种评论模式叫嵌套式评论结构
,嵌套式评论注重用户对话交流,用户可以很方便地查看一个对话里的所有回复,我们看下抖音评论里有着展开10条回复
的按钮。
其他评论模式设计还有平铺式评论结构
,像微信朋友圈,或者Github的issue都是平铺式评论结构。这种设计更适合用户关注重点在发布的内容本身,而不是对话。大家有没发现微信朋友圈的特点是对话比较少点,点赞反而更多。
来看看点赞表设计。
CREATE TABLE `comment_likes` (
`user_id` INT NOT NULL, -- 点赞用户ID
`comment_id` INT NOT NULL, -- 被点赞的评论ID
`liked_timeMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 点赞时间
);
1.2 评论数据存储
抖音每天产生视频几百万、上千万,每个视频的评论高的甚至有上万条评论,要怎么样的数据查询设计才能支持每天亿级的评论?
南哥先假设我们用MySQL作为实际的数据存储,这么高的并发肯定不能让查询直接冲击数据库 。再分库分表也是没用。
Elasticsearch官网这么宣传它的产品:
Elasticsearch 极其快速,快到不可思议
当用户发表评论时,我们首先把评论写入MySQL数据库,再使用异步机制把评论同步到Elasticsearch中。当在用户请求查询评论时,优先从 Elasticsearch 中进行查询。
// 评论存储到MySQL、Elasticsearch
public void storeComment(Comment comment) {
// 将评论存入 MySQL
commentRepository.save(comment);
// 异步将评论同步到 Elasticsearch
CompletableFuture.runAsync(() -> {
elasticsearchService.indexComment(comment);
});
}
1.3 事务控制
大家想一想以上设计,有哪些需要进行事务控制?
例如comment_likes点赞表的插入和comment评论表的更新,用户为某一个评论点赞,会在comment_likes表插入一条新记录,同时会更新comment表的点赞数量。
但是,从用户需求的角度来看,用户并不在意点赞数的强一致性和实时性,这点不使用事务也可以接受。
我曾经和老外程序员在论坛聊过,他说他们的点赞后端分布式服务用的本地缓存,即使每一个服务的本地缓存相对不太一致,对系统完全没有影响。
// 事务控制
@Transactional
public void likeComment(int commentId, int userId) {
// 插入一条点赞记录
commentLikesRepository.insert(userId, commentId);
// 更新评论表中的点赞数量,假设有一个专门的方法来处理这个更新
commentRepository.incrementLikeCount(commentId);
}
1.4 点赞数加入Redis
点赞数相比评论来说,量更加巨大,用户点赞时直接落到MySQL数据库肯定不合理,服务器扛不住也没必要扛。
假如点赞数没有进行事务控制。南哥打算这样处理,用户点赞后,后端服务接受到点赞请求,把用户内容、点赞数放到Redis里,这里采用Redis五大基本类型之一:Map。
// Map结构
comment_like_key = [comment_id_6:like_count = 66, comment_id_7:like_count = 77]
我们需要查询点赞数时直接从高性能内存数据库Redis查询。
当然这还没完,MySQL数据库和Elasticsearch的点赞量需要去同步更新,我们设置定时任务每个一段时间完成数据同步任务。上文的comment_likes点赞记录表同样需要记录,把点赞放到Redis时进行异步添加点赞记录即可。
// 定时任务数据同步任务
@Scheduled(fixedRate = 10000)
public void syncLikes() {
// 从 Redis 中读取最新的点赞数据
Map<Integer, Integer> likes = redisService.fetchAllLikes();
// 同步到 MySQL 和 Elasticsearch
likes.forEach((commentId, likeCount) -> {
commentRepository.updateLikeCount(commentId, likeCount);
elasticsearchService.updateLikeCount(commentId, likeCount);
});
}
戳这,《JavaSouth》作为一份涵盖Java程序员所需掌握核心知识、面试重点的神秘文档。
我是南哥,南就南在Get到你的点赞点赞。
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️