vault backup: 2026-05-24 20:51:06
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQ 与流处理
|
||||
|
||||
## 概述
|
||||
|
||||
消息队列处理的是离散的消息,消费即删;流处理平台处理的是连续的数据流,持久保留、可回溯。本文对比两者的差异,深入流处理的核心概念(事件时间、窗口、Watermark),并介绍 Kafka Streams、Flink、Spark Streaming 三大流处理方案的特点与适用场景。
|
||||
|
||||
## 正文
|
||||
|
||||
### 消息队列 vs 流处理平台
|
||||
|
||||
很多人把 Kafka 叫"消息队列",但它其实更接近一个流处理平台。两者的本质区别在于:
|
||||
|
||||
| 维度 | 消息队列 | 流处理平台 |
|
||||
|------|---------|-----------|
|
||||
| 数据模型 | 离散消息,消费即删 | 连续流,持久保留 |
|
||||
| 消费语义 | 一条消息只被消费一次 | 同一数据可被多次回溯 |
|
||||
| 处理方式 | 单条处理 | 窗口聚合、流式计算 |
|
||||
| 典型代表 | RabbitMQ, RocketMQ | Kafka, Pulsar, Flink |
|
||||
|
||||
> [!question]
|
||||
> Kafka 到底是消息队列还是流处理平台?这个争论有意义吗?
|
||||
|
||||
其实没有意义。Kafka 是一个分布式日志系统,你可以把它当消息队列用,也可以当流处理平台用。关键不在于它"是什么",而在于你怎么用它。
|
||||
|
||||
### 流处理的核心概念
|
||||
|
||||
#### 事件时间 vs 处理时间
|
||||
|
||||
这是流处理中最容易混淆的概念:
|
||||
|
||||
- **事件时间(Event Time)**:事件实际发生的时间,由消息自身携带
|
||||
- **处理时间(Processing Time)**:事件被流处理引擎处理的时间
|
||||
|
||||
两者可能相差毫秒,也可能相差小时(网络延迟、积压、重放)。正确使用事件时间是保证结果准确性的前提。
|
||||
|
||||
#### 窗口(Window)
|
||||
|
||||
流数据是无限的,但业务计算需要有限的数据集。窗口把无限流切分成有限的"片段":
|
||||
|
||||
- **滚动窗口(Tumbling)**:固定大小,不重叠。每 5 分钟一个窗口
|
||||
- **滑动窗口(Sliding)**:固定大小,可重叠。窗口大小 10 分钟,每 1 分钟滑动一次
|
||||
- **会话窗口(Session)**:按活跃度切分,超时即关闭窗口
|
||||
|
||||
#### Watermark
|
||||
|
||||
Watermark 解决的是"迟到数据"问题。它是一个时间戳,表示"在这个时间之前的数据,我认为已经到齐了"。
|
||||
|
||||
当 Watermark 推过窗口的结束时间,窗口就会触发计算并关闭。但如果数据迟到超过 Watermark,就只能靠 **允许迟到(Allowed Lateness)** 或 **侧输出(Side Output)** 来补救。
|
||||
|
||||
### Kafka Streams
|
||||
|
||||
Kafka Streams 是一个**轻量级流处理库**,不需要独立集群,直接嵌入应用中运行。
|
||||
|
||||
核心抽象:
|
||||
|
||||
- **KStream**:无界的、追加式的记录流(类似日志)
|
||||
- **KTable**:可更新的 changelog 流(类似数据库表)
|
||||
|
||||
```go
|
||||
// Kafka Streams 的 DSL 思路(伪代码)
|
||||
// 从 topic 读取流 -> 按 key 分组 -> 聚合 -> 写回 topic
|
||||
stream := builder.Stream("input-topic")
|
||||
stream.GroupByKey().
|
||||
WindowedBy(TimeWindows.OfSize(5 * time.Minute)).
|
||||
Count().
|
||||
ToStream().
|
||||
To("output-topic")
|
||||
```
|
||||
|
||||
Kafka Streams 的优势在于运维简单——不需要 Flink 那样的独立集群,应用启动就自动处理,应用停止就自动释放资源。
|
||||
|
||||
### Flink + Kafka
|
||||
|
||||
Flink 是真正的分布式流处理引擎,与 Kafka 配合是最常见的组合:
|
||||
|
||||
- **Source/Sink Connector**:Flink 原生支持 Kafka 作为数据源和输出目标
|
||||
- **Exactly-Once 保障**:Flink 的 checkpoint 机制 + Kafka 的事务消费,端到端 exactly-once
|
||||
- **状态后端**:RocksDB 状态后端支持超大状态(TB 级别),适合复杂聚合
|
||||
|
||||
Flink 的核心优势是 **低延迟 + 高吞吐 + 强一致性**。适合对延迟和正确性要求极高的场景。
|
||||
|
||||
### Spark Streaming + Kafka
|
||||
|
||||
Spark Streaming 使用**微批处理模型**:把流数据切成小批次(通常秒级),每个批次用 Spark 引擎处理。
|
||||
|
||||
严格来说这不是"真正的流处理",但在很多场景下足够用。优势是复用了 Spark 生态(ML、SQL、GraphX),适合批流一体的需求。Structured Streaming 在此基础上提供了更接近真正流处理的 API。
|
||||
|
||||
### 流处理适用场景
|
||||
|
||||
- **实时聚合**:实时 PV/UV 统计、实时 GMV 计算
|
||||
- **实时风控**:检测异常登录、欺诈交易,毫秒级响应
|
||||
- **实时推荐**:根据用户实时行为更新推荐模型
|
||||
- **IoT 数据处理**:传感器数据实时清洗、聚合、告警
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
MQ["消息队列"] -->|"数据流"| SP["流处理器"]
|
||||
SP -->|"聚合结果"| DB["数据库"]
|
||||
SP -->|"实时告警"| A["告警系统"]
|
||||
SP -->|"写回"| MQ
|
||||
style MQ fill:#4CAF50,color:#fff
|
||||
style SP fill:#2196F3,color:#fff
|
||||
style DB fill:#FF9800,color:#fff
|
||||
style A fill:#F44336,color:#fff
|
||||
```
|
||||
|
||||
### Go 代码:简化版窗口聚合
|
||||
|
||||
```go
|
||||
// 简化版窗口聚合逻辑
|
||||
type Event struct {
|
||||
Timestamp time.Time
|
||||
Value float64
|
||||
}
|
||||
|
||||
func windowAggregate(events <-chan Event, windowSize time.Duration) {
|
||||
window := make([]Event, 0)
|
||||
ticker := time.NewTicker(windowSize)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-events:
|
||||
window = append(window, e)
|
||||
case <-ticker.C:
|
||||
// 窗口关闭,触发聚合计算
|
||||
sum := 0.0
|
||||
for _, e := range window {
|
||||
sum += e.Value
|
||||
}
|
||||
avg := sum / float64(len(window))
|
||||
fmt.Printf("Window closed: count=%d, avg=%.2f\n", len(window), avg)
|
||||
window = window[:0] // 清空窗口
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码展示了窗口聚合的核心思想:在一个时间窗口内收集事件,窗口关闭时触发计算。真实场景中还需要处理迟到数据、状态持久化、checkpoint 等问题,但基本思路是一样的。
|
||||
|
||||
> [!question]
|
||||
> Kafka Streams 和 Flink 都能做流处理,什么时候选哪个?
|
||||
|
||||
简单说:**Kafka Streams 适合轻量级场景**(日均百万级、逻辑简单、不想运维额外集群),**Flink 适合重量级场景**(日均十亿级、复杂窗口逻辑、需要强一致性保证)。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[31-MQ-背压与流控]]
|
||||
- [[32-MQ-请求-回复模式]]
|
||||
- [[34-事件驱动架构-EDA]]
|
||||
@@ -0,0 +1,216 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# 事件驱动架构(EDA)
|
||||
|
||||
## 概述
|
||||
|
||||
事件驱动架构(Event-Driven Architecture, EDA)是系统间通过事件进行通信的架构风格。服务不直接调用对方,而是发布事件、订阅事件,实现松耦合和可扩展性。本文涵盖事件风暴、编排 vs 协同、Saga 模式,以及 EDA 的优势与挑战。
|
||||
|
||||
## 正文
|
||||
|
||||
### EDA 定义
|
||||
|
||||
在传统架构中,服务 A 需要调用服务 B,直接发起 RPC 就行。但这也意味着 A 必须知道 B 的存在、地址和接口——**紧耦合**。
|
||||
|
||||
EDA 换了一种思路:服务 A 只发布一个事件 "订单已创建",至于谁关心这个事件、怎么处理,A 完全不关心。服务 B、C、D 各自订阅感兴趣的事件,独立响应。
|
||||
|
||||
```go
|
||||
// 事件:描述"发生了什么"
|
||||
type OrderCreatedEvent struct {
|
||||
OrderID string
|
||||
UserID string
|
||||
Amount float64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// 发布事件的服务不需要知道谁会处理它
|
||||
func (s *OrderService) CreateOrder(req CreateOrderReq) error {
|
||||
order := s.repo.Save(req)
|
||||
// 发布事件,不关心谁订阅
|
||||
s.eventBus.Publish(OrderCreatedEvent{
|
||||
OrderID: order.ID,
|
||||
UserID: order.UserID,
|
||||
Amount: order.Amount,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 事件风暴(Event Storming)
|
||||
|
||||
事件风暴是一种协作式的需求分析方法论,由 Alberto Brandolini 提出。核心操作很简单:
|
||||
|
||||
1. 找一面大墙,用橙色便利贴写出所有**领域事件**(过去式的动词,如"订单已创建"、"库存已扣减")
|
||||
2. 按时间线排列事件
|
||||
3. 识别触发事件的**命令**(蓝色便利贴)和负责处理的**聚合**(黄色便利贴)
|
||||
4. 发现**策略**(当某事件发生时自动触发的命令)和**读模型**
|
||||
|
||||
事件风暴的价值不在于产出完美的设计文档,而在于**让业务和技术人员用同一种语言对话**。一面墙上的便利贴比任何 UML 图都直观。
|
||||
|
||||
### 编排 vs 协同
|
||||
|
||||
EDA 中服务间的协作方式有两种模式:
|
||||
|
||||
#### 编排(Orchestration)
|
||||
|
||||
中心协调者指挥各服务做事,像乐队指挥。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
OC["编排器"] -->|"1 创建订单"| OS["订单服务"]
|
||||
OC -->|"2 扣减库存"| IS["库存服务"]
|
||||
OC -->|"3 发起支付"| PS["支付服务"]
|
||||
OC -->|"4 安排发货"| SS["发货服务"]
|
||||
style OC fill:#F44336,color:#fff
|
||||
style OS fill:#2196F3,color:#fff
|
||||
style IS fill:#2196F3,color:#fff
|
||||
style PS fill:#2196F3,color:#fff
|
||||
style SS fill:#2196F3,color:#fff
|
||||
```
|
||||
|
||||
优点:流程清晰可见,容易调试和监控。缺点:编排器是单点,可能成为瓶颈,服务间仍然有一定耦合。
|
||||
|
||||
#### 协同(Choreography)
|
||||
|
||||
各服务自主响应事件,像舞蹈演员各自跳舞但配合默契。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
OS["订单服务"] -->|"OrderCreated"| IS["库存服务"]
|
||||
IS -->|"StockReserved"| PS["支付服务"]
|
||||
PS -->|"PaymentDone"| SS["发货服务"]
|
||||
style OS fill:#4CAF50,color:#fff
|
||||
style IS fill:#2196F3,color:#fff
|
||||
style PS fill:#FF9800,color:#fff
|
||||
style SS fill:#9C27B0,color:#fff
|
||||
```
|
||||
|
||||
优点:真正松耦合,没有单点瓶颈,各服务可独立演进。缺点:流程分散在各服务中,难以全局理解,调试困难。
|
||||
|
||||
> [!question]
|
||||
> 编排式 Saga 和协同式 Saga 各有什么优缺点?你在项目中会如何选择?
|
||||
|
||||
### Saga 模式
|
||||
|
||||
分布式事务的经典问题是:如何跨多个服务保证数据一致性?两阶段提交(2PC)性能差、可用性低。Saga 模式提供了一种更实用的方案:**把长事务拆成多个本地事务,每个本地事务有对应的补偿操作**。
|
||||
|
||||
举例:创建订单涉及三个步骤——
|
||||
|
||||
1. 订单服务:创建订单(补偿:取消订单)
|
||||
2. 库存服务:扣减库存(补偿:恢复库存)
|
||||
3. 支付服务:发起扣款(补偿:退款)
|
||||
|
||||
如果步骤 2 失败,执行步骤 1 的补偿操作(取消订单)。如果步骤 3 失败,执行步骤 2 和 1 的补偿操作。
|
||||
|
||||
**编排式 Saga**:由一个 Saga 协调者集中管理流程和补偿逻辑。协调者知道每一步该做什么、失败了怎么回滚。
|
||||
|
||||
**协同式 Saga**:没有中心协调者,每个服务监听事件,完成后发布新事件。失败时发布补偿事件,上游服务监听到后自行回滚。
|
||||
|
||||
### 事件驱动的优势
|
||||
|
||||
**松耦合**:生产者不依赖消费者,服务可独立开发、部署、扩展。新增一个消费者不需要修改生产者。
|
||||
|
||||
**可扩展**:消费者可以水平扩展,消息队列天然支持分区和并行消费。
|
||||
|
||||
**天然支持审计**:事件就是操作日志。任何时候都可以回放事件流,重建系统状态。这对金融、医疗等合规要求高的场景非常有价值。
|
||||
|
||||
### 事件驱动的挑战
|
||||
|
||||
**最终一致性**:事件驱动天然是异步的,不同服务看到的状态可能有短暂的不一致。需要在业务上接受"最终一致性",而不是强一致。
|
||||
|
||||
**调试困难**:请求链路被事件拆散,排查问题时需要在多个服务间追踪 CorrelationID。分布式链路追踪(Jaeger、Zipkin)是必备工具。
|
||||
|
||||
**事件风暴(过多事件)**:如果事件粒度太细、订阅关系太复杂,系统会变成"事件风暴"——满天飞的事件让人难以理解业务流程。需要在事件设计上有纪律。
|
||||
|
||||
### Go 代码:协同式 Saga 实现
|
||||
|
||||
```go
|
||||
// 事件定义
|
||||
type Event struct {
|
||||
Type string // 事件类型
|
||||
Payload interface{} // 事件数据
|
||||
TraceID string // 链路追踪ID
|
||||
}
|
||||
|
||||
// 事件总线
|
||||
type EventBus struct {
|
||||
subscribers map[string][]func(Event)
|
||||
}
|
||||
|
||||
func (eb *EventBus) Subscribe(eventType string, handler func(Event)) {
|
||||
eb.subscribers[eventType] = append(eb.subscribers[eventType], handler)
|
||||
}
|
||||
|
||||
func (eb *EventBus) Publish(event Event) {
|
||||
for _, handler := range eb.subscribers[event.Type] {
|
||||
go handler(event) // 异步处理
|
||||
}
|
||||
}
|
||||
|
||||
// 库存服务:监听订单创建事件,扣减库存
|
||||
func InventoryService(bus *EventBus) {
|
||||
bus.Subscribe("OrderCreated", func(e Event) {
|
||||
order := e.Payload.(OrderCreatedEvent)
|
||||
err := deductStock(order.Items)
|
||||
if err != nil {
|
||||
// 扣减失败,发布补偿事件
|
||||
bus.Publish(Event{
|
||||
Type: "StockDeductFailed",
|
||||
Payload: order,
|
||||
TraceID: e.TraceID,
|
||||
})
|
||||
return
|
||||
}
|
||||
// 扣减成功,发布下一步事件
|
||||
bus.Publish(Event{
|
||||
Type: "StockReserved",
|
||||
Payload: order,
|
||||
TraceID: e.TraceID,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 支付服务:监听库存预留事件
|
||||
func PaymentService(bus *EventBus) {
|
||||
bus.Subscribe("StockReserved", func(e Event) {
|
||||
order := e.Payload.(OrderCreatedEvent)
|
||||
err := chargePayment(order.UserID, order.Amount)
|
||||
if err != nil {
|
||||
// 支付失败,触发库存恢复
|
||||
bus.Publish(Event{
|
||||
Type: "PaymentFailed",
|
||||
Payload: order,
|
||||
TraceID: e.TraceID,
|
||||
})
|
||||
return
|
||||
}
|
||||
bus.Publish(Event{
|
||||
Type: "PaymentDone",
|
||||
Payload: order,
|
||||
TraceID: e.TraceID,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 订单服务:监听支付失败事件,取消订单(补偿)
|
||||
func OrderCompensation(bus *EventBus) {
|
||||
bus.Subscribe("PaymentFailed", func(e Event) {
|
||||
order := e.Payload.(OrderCreatedEvent)
|
||||
cancelOrder(order.OrderID)
|
||||
fmt.Printf("Order %s compensated\n", order.OrderID)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
这个简化实现展示了协同式 Saga 的核心:每个服务只关心自己监听的事件和需要发布的事件。失败时通过补偿事件触发回滚链。`TraceID` 贯穿整个链路,方便追踪和调试。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[31-MQ-背压与流控]]
|
||||
- [[32-MQ-请求-回复模式]]
|
||||
- [[33-MQ-与流处理]]
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
- CDC
|
||||
- Kafka
|
||||
- 数据同步
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQ 与 CDC
|
||||
|
||||
## 概述
|
||||
|
||||
CDC(Change Data Capture)是一种捕获数据库变更事件并将其发布到消息队列的技术模式。它解耦了数据变更的感知与消费,是构建实时数据管道的基石。
|
||||
|
||||
## 正文
|
||||
|
||||
### 什么是 CDC
|
||||
|
||||
传统架构中,当一个服务写入数据库后,其他系统想拿到这份数据,往往依赖定时轮询或者应用层双写。这两种方式要么延迟高,要么耦合重。CDC 的思路完全不同——它直接从数据库的变更日志中"读"出发生了什么变化,然后以事件的形式推送到消息队列。
|
||||
|
||||
简单来说,CDC 让数据库的每一次 INSERT、UPDATE、DELETE 都变成一条消息,下游系统按需消费即可。
|
||||
|
||||
### CDC 的三种实现方式
|
||||
|
||||
**基于触发器(Trigger-based)**:在数据库上创建触发器,每次数据变更时触发逻辑将变更写入一张中间表,再由外部程序读取。优点是实现简单,缺点是触发器会增加写操作的延迟,对高写入量的系统不太友好。
|
||||
|
||||
**基于日志解析(Log-based)**:直接读取数据库的 WAL(Write-Ahead Log)或 binlog。这是目前最主流的方式,对源库几乎没有侵入性。MySQL 的 binlog、PostgreSQL 的 WAL、Oracle 的 redo log 都可以被解析。缺点是需要处理日志格式的兼容性问题,不同数据库版本可能有差异。
|
||||
|
||||
**基于时间戳(Timestamp-based)**:每张表维护一个 `updated_at` 字段,定期查询变更数据。这种方式最简单,但只能捕获新增和更新,无法捕获删除操作,而且存在时间窗口内的延迟。
|
||||
|
||||
> [!question]
|
||||
> 这三种方式各有取舍。如果你的系统对延迟要求在秒级,而且不能接受对源库有任何性能影响,你会选择哪种?如果还需要捕获 DELETE 操作呢?
|
||||
|
||||
### Debezium + Kafka Connect
|
||||
|
||||
**Debezium** 是目前最流行的开源 CDC 框架,底层基于日志解析。它以 Kafka Connect 插件的形式运行,支持 MySQL、PostgreSQL、MongoDB、SQL Server、Oracle 等主流数据库。
|
||||
|
||||
Kafka Connect 的架构分为两种角色:
|
||||
|
||||
- **Source Connector**:负责从外部系统(如数据库)读取数据,写入 Kafka Topic。Debezium 就是一个 Source Connector。
|
||||
- **Sink Connector**:负责从 Kafka Topic 读取数据,写入目标系统(如 Elasticsearch、Redis、另一个数据库)。
|
||||
|
||||
两者配合,就形成了一条完整的数据管道:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
MySQL["MySQL"] -->|"binlog"| Debezium["Debezium Source Connector"]
|
||||
PG["PostgreSQL"] -->|"WAL"| Debezium
|
||||
Debezium -->|"变更事件"| Kafka["Kafka"]
|
||||
Kafka -->|"消费"| ES["Elasticsearch"]
|
||||
Kafka -->|"消费"| DW["Data Warehouse"]
|
||||
Kafka -->|"消费"| Cache["Redis Cache"]
|
||||
```
|
||||
|
||||
这里有个关键设计:Debezium 将每张表的变更发布到独立的 Topic(默认命名规则为 `serverName.databaseName.tableName`),下游消费者可以按表订阅,互不干扰。
|
||||
|
||||
### 典型应用场景
|
||||
|
||||
**数据库实时同步到 ES**:MySQL 作为主存储,ES 作为搜索索引。通过 CDC 将 MySQL 的变更实时同步到 ES,避免了双写带来的数据一致性问题。
|
||||
|
||||
**数据仓库实时 ETL**:传统 ETL 是 T+1 的批处理模式,引入 CDC 后可以做到分钟级甚至秒级的数据入仓,大幅缩短数据时效性。
|
||||
|
||||
**缓存一致性**:数据库变更时,通过 CDC 事件通知缓存层失效或更新,比应用层主动删除缓存更可靠,因为它不依赖业务代码的正确性。
|
||||
|
||||
### CDC vs 应用层双写
|
||||
|
||||
很多团队在做数据同步时,第一反应是在应用代码里"写完 A 库再写 B 库"。这种双写看似简单,实则问题重重:
|
||||
|
||||
1. **一致性难保证**:如果第一个写成功、第二个写失败,数据就不一致了。引入分布式事务又太重。
|
||||
2. **代码侵入**:每个写操作都要额外写同步逻辑,业务代码和数据同步耦合在一起。
|
||||
3. **维护成本**:新增一个下游系统,就要改一次业务代码。
|
||||
|
||||
CDC 完全没有这些问题。它在数据库层面捕获变更,业务代码完全无感知。新增下游系统只需要加一个消费者,不改一行业务代码。
|
||||
|
||||
> [!question]
|
||||
> CDC 虽然解决了双写的一致性问题,但 CDC 本身的事件投递是"至少一次"(at-least-once)语义,下游消费者如何保证幂等?
|
||||
|
||||
### Go 代码示例:消费 CDC 事件
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/segmentio/kafka-go"
|
||||
)
|
||||
|
||||
// CDCEvent Debezium 产出的 CDC 事件结构
|
||||
type CDCEvent struct {
|
||||
Before map[string]interface{} `json:"before"` // 变更前的数据
|
||||
After map[string]interface{} `json:"after"` // 变更后的数据
|
||||
Source struct {
|
||||
Table string `json:"table"` // 源表名
|
||||
} `json:"source"`
|
||||
Op string `json:"op"` // 操作类型: c=create, u=update, d=delete
|
||||
}
|
||||
|
||||
func main() {
|
||||
reader := kafka.NewReader(kafka.ReaderConfig{
|
||||
Brokers: []string{"localhost:9092"},
|
||||
Topic: "myserver.mydb.orders", // Debezium 默认 Topic 命名
|
||||
GroupID: "cdc-consumer",
|
||||
})
|
||||
defer reader.Close()
|
||||
|
||||
for {
|
||||
msg, err := reader.ReadMessage(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var event CDCEvent
|
||||
if err := json.Unmarshal(msg.Value, &event); err != nil {
|
||||
log.Printf("unmarshal error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据操作类型分发处理
|
||||
switch event.Op {
|
||||
case "c", "u": // 创建或更新 → 同步到 ES
|
||||
log.Printf("sync to ES: table=%s, id=%v", event.Source.Table, event.After["id"])
|
||||
case "d": // 删除 → 清理缓存
|
||||
log.Printf("invalidate cache: table=%s, id=%v", event.Source.Table, event.Before["id"])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码消费 Debezium 产出的 CDC 事件,根据操作类型(create/update/delete)分发到不同的处理逻辑。实际生产中,你还需要处理 Schema Registry 的反序列化、错误重试、死信队列等问题。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[30-MQ-核心概念与选型]]
|
||||
- [[33-MQ-Kafka-架构与核心机制]]
|
||||
- [[36-MQ-监控指标与告警]]
|
||||
- [[38-MQ-消息轨迹与链路追踪]]
|
||||
Reference in New Issue
Block a user