在我们构建 NovaTube 在线视频分享平台过程中,点赞、评论、弹幕、播放记录等用户操作频繁,尤其在用户量上升后,出现了以下问题:
这些操作共同的特点是:
于是,我们决定引入 Kafka 消息队列对这些热点写操作进行解耦和异步化处理,优化系统性能、增强可扩展性与容错能力。
以下是 NovaTube 在点赞场景下的 Kafka 解耦架构图:
[Client]
↓ 点赞请求
[网关层] + [Sentinel限流]
↓
[主站服务 Controller]
↓
KafkaTemplate.send() 发送到 topic: like-events
↓
[Kafka Broker]
↓
[点赞消费者服务]
↓
MySQL/Redis 数据持久化 + 缓存更新
说明:
@Data
public class LikeEvent {
private Long userId;
private String videoId;
private Long timestamp;
}
@RestController
public class LikeController {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@PostMapping("/like")
public ResponseEntity<?> like(@RequestParam String videoId, HttpServletRequest request) {
Long userId = getUserIdFromRequest(request);
LikeEvent event = new LikeEvent(userId, videoId, System.currentTimeMillis());
kafkaTemplate.send("like-events", JSON.toJSONString(event));
return ResponseEntity.ok("点赞成功");
}
}
特点:
@Component
public class LikeConsumer {
@Autowired
private LikeService likeService;
@KafkaListener(topics = "like-events", groupId = "nova-like-group")
public void consumeLikeEvent(String message) {
LikeEvent event = JSON.parseObject(message, LikeEvent.class);
likeService.saveUserLike(event.getUserId(), event.getVideoId());
}
}
@Service
public class LikeService {
@Autowired
private LikeRepository likeRepository;
public void saveUserLike(Long userId, String videoId) {
if (!likeRepository.existsByUserIdAndVideoId(userId, videoId)) {
likeRepository.save(new UserLike(userId, videoId));
}
}
}
可进一步优化:
优势 | 说明 |
---|---|
⚡ 提升响应速度 | 请求快速返回,主链路无阻塞 |
解耦架构 | 点赞、弹幕、评论等模块独立演化 |
削峰填谷 | 高并发场景下缓冲请求,防止写库瓶颈 |
容错增强 | Kafka 持久化保障消息不丢失,可重试 |
可扩展性好 | 易于接入日志分析、反作弊、通知等模块 |
除了点赞,我们还将 Kafka 运用到以下场景中:
功能模块 | Kafka Topic | 说明 |
---|---|---|
弹幕发送 | danmu-events |
异步写入 + 敏感词审核 |
评论发布 | comment-events |
异步发布 + 通知推送 |
播放记录 | play-record-events |
异步记录观看历史,减少实时写库压力 |
引入 Kafka 解耦点赞等热点操作,是 NovaTube 性能优化的重要一环。
它实现了:
在现代高并发系统中,这是极具实用价值的一种架构模式,适用于所有频繁、可延迟、可容错的非核心路径操作。
基于 Kafka 构建点赞、评论、弹幕等热点操作的异步处理链路,实现系统解耦与削峰填谷,提升响应性能与高并发下的稳定性。
作者:NovaTube 后端开发者
时间:2025 年 6 月
标签:Kafka、异步架构、弹幕、评论、播放记录、解耦设计
在 NovaTube 视频平台中,我们不仅为视频提供播放服务,还支持用户实时发送弹幕、评论互动、记录观看历史。这些功能虽然核心链路简单,但在高并发场景下却会对系统产生明显负载:
为了应对这些压力,我们选择将这三类操作使用 Kafka 进行异步解耦处理,目标是:主流程快响应、后台异步处理、系统更加稳定与可扩展。
场景 | 是否频繁 | 实时性要求 | 可异步化 | 备注 |
---|---|---|---|---|
弹幕发送 | 非常频繁 | 中等 | ✅ | 需要实时展示但写入可延迟 |
评论发布 | 频繁 | 一般 | ✅ | 可异步入库+后台审核+通知 |
播放记录 | 高频 | 低 | ✅ | 无需强一致性,延迟可接受 |
✅ 这些操作天然适合用 Kafka 异步处理,具有良好的“削峰填谷”特性。
┌────────────┐
│ 客户端 │
└────┬───────┘
│
┌──────────▼──────────┐
│ SpringBoot 控制层 │
└──────────┬──────────┘
▼
┌─────────────────────┐
│ KafkaTemplate 发送 │
└─────────────────────┘
▼
┌────────────┐
│ Kafka Broker│
└────┬───────┘
▼
┌──────────────────────────────┐
│ 弹幕/评论/播放记录消费者服务 │
└────┬──────────────┬──────────┘
▼ ▼
持久化入库 审核/通知等
@PostMapping("/danmu/send")
public ResponseEntity<?> sendDanmu(@RequestBody DanmuRequest req) {
DanmuEvent event = new DanmuEvent(req.getUserId(), req.getVideoId(), req.getContent());
kafkaTemplate.send("danmu-events", JSON.toJSONString(event));
return ResponseEntity.ok("发送成功");
}
@KafkaListener(topics = "danmu-events", groupId = "danmu-consumers")
public void consumeDanmu(String msg) {
DanmuEvent event = JSON.parseObject(msg, DanmuEvent.class);
if (!containsSensitiveWord(event.getContent())) {
danmuRepository.save(event.toEntity());
} else {
log.warn("屏蔽敏感弹幕:{}", event);
}
}
@PostMapping("/comment/publish")
public ResponseEntity<?> publish(@RequestBody CommentRequest req) {
CommentEvent event = new CommentEvent(req.getUserId(), req.getVideoId(), req.getContent());
kafkaTemplate.send("comment-events", JSON.toJSONString(event));
return ResponseEntity.ok("评论成功");
}
@KafkaListener(topics = "comment-events", groupId = "comment-group")
public void consumeComment(String msg) {
CommentEvent event = JSON.parseObject(msg, CommentEvent.class);
commentRepository.save(event.toEntity()); // 持久化
auditService.asyncAudit(event); // 审核
notifyService.notifyAuthor(event); // 通知作者
}
好处:写入、审核、通知等子任务可独立扩展,互不影响。
@PostMapping("/video/record")
public ResponseEntity<?> recordPlay(@RequestBody PlayLogRequest req) {
PlayRecordEvent event = new PlayRecordEvent(req.getUserId(), req.getVideoId(), req.getTimestamp());
kafkaTemplate.send("play-record-events", JSON.toJSONString(event));
return ResponseEntity.ok().build();
}
@KafkaListener(topics = "play-record-events", groupId = "record-group")
public void consumePlayRecord(String msg) {
PlayRecordEvent event = JSON.parseObject(msg, PlayRecordEvent.class);
playRecordService.saveRecord(event);
}
可根据需要定向入库至 MySQL、Elasticsearch、ClickHouse 等。
优势 | 说明 |
---|---|
⚡ 响应更快 | 控制层快速返回,异步处理耗时任务 |
系统解耦 | 各模块职责清晰,便于扩展、测试、部署 |
削峰填谷 | 高并发写操作统一缓冲,防止系统过载 |
易于链路拓展 | 后续可接入 AI 审核、内容推荐、数据分析等服务 |
提高容错性 | 消息存储于 Kafka,失败可重试 |
利用 Kafka 构建弹幕发送、评论发布、播放记录等高频操作的异步处理链路,实现写操作解耦、响应提速与高并发场景下系统稳定性提升。
本篇博客分享了如何利用 Kafka 在一个中大型视频平台中实现多类高频操作的异步解耦,帮助系统在高并发下保持高可用与高性能。这一架构思想同样适用于电商(下单、支付)、社交(私信、点赞)、游戏(日志、成就)等场景。
非常好,以下是关于 Kafka 在 Spring Boot 项目中的结构位置、如何调用和使用 的详细讲解,包括 组件职责划分、调用流程、配置方法以及关键代码解析。本讲解既适合入门者了解项目结构,也适合用于博客/面试准备。
在一个典型的 Spring Boot 项目中,Kafka 主要扮演 消息解耦中间件 的角色,负责“接收请求 -> 异步发送消息 -> 消费处理任务”的链路。结构如下:
src
└── main
├── controller // 控制层:接收请求,调用KafkaProducer
├── service // 业务层:处理逻辑或封装消费者回调
├── kafka
│ ├── KafkaProducer // 封装 KafkaTemplate 发送逻辑
│ ├── KafkaConsumer // 监听 Kafka 消费消息
│ └── KafkaConfig // Kafka 连接配置
├── dto // 数据传输对象(Event/Request)
└── repository // 数据持久化
用户请求
↓
@Controller -> /comment/publish
↓
KafkaProducer.send("comment-events", msg)
↓
Kafka Broker 消息队列
↓
@KafkaListener(topic="comment-events") -> KafkaConsumer
↓
处理逻辑(如持久化、通知等)
@Configuration
public class KafkaConfig {
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(config);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: my-consumer-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
@Component
public class KafkaProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void send(String topic, String message) {
kafkaTemplate.send(topic, message);
}
}
@RestController
@RequestMapping("/comment")
public class CommentController {
@Autowired
private KafkaProducer kafkaProducer;
@PostMapping("/publish")
public ResponseEntity<?> publishComment(@RequestBody CommentRequest req) {
CommentEvent event = new CommentEvent(req.getUserId(), req.getVideoId(), req.getContent());
String json = JSON.toJSONString(event);
kafkaProducer.send("comment-events", json); // 调用 Kafka
return ResponseEntity.ok("评论成功,正在后台处理");
}
}
@Component
public class KafkaCommentConsumer {
@Autowired
private CommentService commentService;
@KafkaListener(topics = "comment-events", groupId = "comment-group")
public void consumeComment(String message) {
CommentEvent event = JSON.parseObject(message, CommentEvent.class);
commentService.processComment(event); // 处理评论逻辑(写库、审核、通知)
}
}
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
public void processComment(CommentEvent event) {
Comment comment = new Comment();
comment.setUserId(event.getUserId());
comment.setVideoId(event.getVideoId());
comment.setContent(event.getContent());
comment.setCreateTime(LocalDateTime.now());
commentRepository.save(comment);
}
}
POST /comment/publish
↓
[Controller] 收到请求
↓
KafkaProducer.send("comment-events", json)
↓
Kafka 中转存储(解耦主流程)
↓
KafkaConsumer 监听到消息
↓
调用 CommentService.processComment()
↓
MySQL 写入评论表
模块 | 作用 |
---|---|
KafkaTemplate | 生产者 API,负责消息发送 |
@KafkaListener | 消费者注解,监听 Kafka Topic |
KafkaConfig | 统一配置 Kafka 客户端参数 |
Producer 封装类 | 隔离业务与发送逻辑 |
Consumer 封装类 | 解耦消费逻辑,便于扩展与重试 |
DTO/Event | 标准化传输格式(如 JSON) |
在 Spring Boot 项目中集成 Kafka,用于实现评论、弹幕、播放记录等模块的异步消息处理。封装 Kafka 生产者与消费者逻辑,通过
KafkaTemplate
发送消息、@KafkaListener
异步消费,解耦主流程、提升系统吞吐能力。
Kafka Broker 是 Kafka 的核心组件之一,它是 Kafka 服务端的实例,负责:
一个 Kafka 集群由多个 Broker 组成,每个 Broker 负责维护一部分数据(即部分 Topic 的分区)。
可以简单理解:一个 Broker 就是一个 Kafka 节点,它是 Kafka 消息的存储与转发中心。
例如:
Producer -----> Kafka Broker(1) ----> Consumer
↑
存储的是某个 Topic 的某个分区
Kafka 并不是传统意义上“链表结构”的消息队列,而是基于 “Topic + Partition + Offset” 组成的 持久化分布式消息队列。
概念 | 含义 |
---|---|
Topic | 类似一个消息主题,比如 "comment-events" |
Partition | 每个 Topic 可以被划分成多个分区,每个分区是一个有序队列 |
Offset | 消息在分区中的位置编号(偏移量) |
队列特性的体现方式:
以“用户发表评论”场景为例,Kafka 在系统中经历以下流程:
kafkaTemplate.send("comment-events", messageJson);
@KafkaListener(topics = "comment-events")
public void consume(String message) { ... }
[业务代码]
↓ 调用 KafkaProducer.send()
Producer
─────────────→ Kafka Broker
↑
分区日志队列(消息队列本质)
↓
@KafkaListener 拉取消息
Consumer
↓
持久化 or 业务处理
概念 | 解释 |
---|---|
Kafka Broker | Kafka 的核心服务节点,负责接收/存储/分发消息 |
消息队列特性 | Topic → Partition → Offset 结构形成有序、可回溯、高并发的消息通道 |
调用流程 | 发送(Producer)→ 存储(Broker)→ 消费(Consumer) |
技术优势 | 高性能、可持久化、异步解耦、支持海量并发与分布式部署 |
虽然 Kafka Broker 是运行在服务器端的后台服务,不是直接在代码中“写出来”的一个类或对象,但在代码中配置连接 Kafka Broker 的地址,就等于是告诉应用程序:
“Kafka Broker 在这里,所有消息收发请连接这个地址。”
application.yml
或 application.properties
中体现 Broker 地址spring:
kafka:
bootstrap-servers: localhost:9092
这句配置就告诉了 Kafka 客户端(Producer、Consumer):
Kafka Broker 正在
localhost:9092
上运行。
所有消息都要发送/接收给这个地址的 Kafka 服务。
如果是连接一个集群,可能会是:
spring:
kafka:
bootstrap-servers: broker1:9092,broker2:9092,broker3:9092
KafkaTemplate
和 @KafkaListener
背后被使用当用如下代码发送消息:
kafkaTemplate.send("comment-events", "your message");
或者监听消费:
@KafkaListener(topics = "comment-events")
public void consume(String msg) {
...
}
这些 Kafka 客户端组件背后,Spring Boot 会使用在配置文件中定义的 bootstrap-servers
,自动构建连接到 Kafka Broker 的连接客户端。
Kafka 功能 | 代码体现 | 实际作用 |
---|---|---|
Broker 地址配置 | bootstrap-servers: localhost:9092 |
Kafka 客户端通过这个地址连接 Broker |
Producer 发送消息 | kafkaTemplate.send(...) |
使用配置好的 Kafka 客户端发送消息到 Broker |
Consumer 消费消息 | @KafkaListener(...) |
Spring Boot 通过配置的地址监听 Kafka Broker 的消息 |
Kafka Broker 本身不在业务代码中显式出现,它通过配置文件中的 bootstrap-servers
地址体现,Producer 和 Consumer 都基于这个地址与 Kafka Broker 建立连接、收发消息。