仿牛客社区项目笔记-构建TB级异步消息系统(引入Kafka)
1. 构建TB级异步消息系统
分为引入Kafka,Spring 整合 Kafka,发送系统通知和显示系统通知。
1.1 引入Kafka
- 在 Kafka.apach.org 安装 kafka 压缩包,解压缩。
- 更改 config 目录下 zookeeper.properties 和 server.properties 下的 /temp 路径,改为硬盘下路径。
- Kafka简介:其中broker是kafka的集群服务器,而zookeeper是管理集群的。topic是存放消息的空间,partition是topic的分区。
1.2 Spring 整合 Kafka
- 导入 Spring-kafka 依赖。
- 在 application.properties 中设置配置。
- 在使用 kafka 前要使用命令行打开 zookeeper 和 kafka,命令为:
cd idea/kafka/kafka_2.12-3.1.0
E:\IDEA\kafka\kafka_2.12-3.1.0>bin\windows\zookeeper-server-start.bat config\zookeeper.properties
E:\IDEA\kafka\kafka_2.12-3.1.0>bin\windows\kafka-server-start.bat config\server.properties
1.3 发送系统通知
- 构建事件 Event 实体类。包含字段:
private String topic; // 存放消息空间
private int userId; // 触发事件用户id
private int entityType; // 事件目标类型
private int entityId; // 事件目标id
private int entityUserId ;// 事件目标所有者id
private Map<String, Object> data = new HashMap<>(); // 其他
- 在 event 文件夹下构建 EventProducer 类,调用 KafkaTemplate 将事件发布到指定的主题。
@Component
public class EventProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
// 处理事件
public void fireEvent(Event event) {
// 将事件发布到指定的主题
kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
}
}
- 在 event 文件夹下构建 EventConsumer 类,监听三种类型topic,即:评论,点赞,关注。根据收到的 event 构建 message 对象,使用 messageService 插入数据库。
@Component
public class EventConsumer implements CommunityConstant {
private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
@Autowired
private MessageService messageService;
@KafkaListener(topics = {TOPIC_COMMENT, TOPIC_LIKE, TOPIC_FOLLOW})
public void handleCommentMessage(ConsumerRecord record) {
if (record == null || record.value() == null) {
logger.error("消息的内容为空!");
return;
}
Event event = JSONObject.parseObject(record.value().toString(), Event.class);
if (event == null) {
logger.error("消息格式错误!");
return;
}
// 发送站内通知
Message message = new Message();
// 系统发送 设为 1
message.setFromId(SYSTEM_USER_ID);
message.setToId(event.getEntityUserId());
// ConversationId 设为 topic。
message.setConversationId(event.getTopic());
message.setCreateTime(new Date());
// message 的 content
Map<String, Object> content = new HashMap<>();
content.put("userId", event.getUserId());
content.put("entityType", event.getEntityType());
content.put("entityId", event.getEntityId());
if (!event.getData().isEmpty()) {
for (Map.Entry<String, Object> entry : event.getData().entrySet()) {
content.put(entry.getKey(), entry.getValue());
}
}
// 保存为 json 字符串
message.setContent(JSONObject.toJSONString(content));
messageService.addMessage(message);
}
}
- 在评论,点赞,关注时加入触发事件代码。在 帖子模块(核心)1.5小节:CommentController,点赞关注模块(引入Redis)1.2小节:LikeController,点赞关注模块(引入Redis)1.4.1小节:FollowController中加入相应代码,即构建event并且调用 eventProducer 发布事件。
1.4 显示系统通知
1.4.1 点击系统通知
- 点击消息内的系统通知,映射到 MessageController 中的 /notice/list。会分别显示评论,赞,关注三类通知的一个最新消息。
- 以评论为例:会调用 messageService.findLatestNotice 方法查询到最近的评论通知,将 message 中的 content 反html转义后从json转为map对象,然后将各种信息传入返回给浏览器的map,同时在该map中装入评论通知总数和未读评论通知数。然后将map装入model返回给浏览器。
- 赞和关注模块同样如上。最后再将所有的未读数量查询出,装入model。
1.4.2 系统通知详情页面
- 点击评论,赞,关注三类通知的某一类通知,会映射到 MessageController 层的 /notice/detail/{topic}。设置分页信息。通过 messageService.findNotices 方法查询通知列表。
- 将列表中每个message装入map,并且将每个message中的content反转义并且从json转换为map,将该map中的信息装入返回给浏览器的map。将每个map装入list,将list装入model。
- 使用 messageService.readMessage 方法将列表中的 message 设置为已读。
- 返回 /site/notice-detail。
最后使用 MessageInterceptor 拦截器查询出私信未读数量和系统通知未读数量,将其加和放入modelAndView,在顶部栏的消息上显示总的未读数量。