一、背景:同步调用的"多米诺骨牌"困境
电商订单系统,用户下单后调用库存扣减、积分增加、优惠券核销、物流创建------四个服务串行同步调用,总耗时 = 库存200ms + 积分150ms + 优惠券180ms + 物流100ms = 630ms。
更可怕的是,积分服务挂了,整个下单流程失败,用户付款成功但订单回滚------客诉爆炸。
这就是同步调用架构的三大痛点:
- 耦合度高:下单流程强依赖四个服务的可用性,任何一个挂了全链路失败
- 响应慢:串行调用累加延迟,用户体验差
- 扩展性差:新增一个"发送短信通知"的服务,需要修改下单核心逻辑
事件驱动架构(EDA)应运而生。
但EDA不是银弹,它引入了新的复杂度:最终一致性如何保证?事件丢失怎么办?消息重复消费怎么处理?本文用真实踩坑经验,从原理到代码实现,带你落地事件驱动架构。
二、事件驱动架构核心原理
2.1 什么是事件驱动架构
定义:事件驱动架构是一种软件架构模式,系统通过"事件"进行通信,事件的产生者不直接调用消费者,而是发布事件到事件总线,消费者订阅并处理事件。
核心三要素:
- 事件 :状态变化的事实,如
OrderCreated、PaymentCompleted - 事件生产者:产生事件的服务,只负责发布事件,不关心谁消费
- 事件消费者:订阅事件并执行业务逻辑,可以多个消费者并行处理
架构对比:
【同步调用架构】
订单服务 ──HTTP调用──> 库存服务 ──HTTP调用──> 积分服务
│ │
└──────────HTTP调用──> 优惠券服务 ────────┘
问题:串行调用,耦合度高,任何一个服务失败全链路失败
【事件驱动架构】
订单服务 ──发布事件──> 消息队列 ──订阅──> 库存服务
├──订阅──> 积分服务
├──订阅──> 优惠券服务
└──订阅──> 物流服务
优势:异步解耦,并行处理,单个服务失败不影响其他服务
2.2 事件驱动 vs 消息队列
很多人把事件驱动等同于消息队列,这是一个常见的误解。
| 维度 | 消息队列(MQ) | 事件驱动架构(EDA) |
|---|---|---|
| 核心概念 | 消息 | 事件 |
| 语义 | "做某件事"(命令) | "某件事发生了"(事实) |
| 消费方式 | 点对点,一条消息只被一个消费者处理 | 发布-订阅,一个事件可被多个消费者处理 |
| 典型场景 | 任务队列、RPC异步调用 | 状态同步、业务解耦、审计日志 |
| 技术选型 | RabbitMQ、RocketMQ | Kafka、EventStore、云事件总线 |
关键区别:消息是"命令",事件是"事实"。
- 消息(命令) :
CreateOrder(创建订单)------生产者期望消费者执行一个动作 - 事件(事实) :
OrderCreated(订单已创建)------生产者只是通知一个事实,消费者自行决定如何处理
事件驱动架构更强调"通知"而非"命令",消费者有更大的自主权。
2.3 最终一致性的理论基础
CAP定理告诉我们:分布式系统无法同时满足一致性、可用性、分区容错性。
事件驱动架构选择AP(可用性+分区容错) ,牺牲强一致性,采用最终一致性。
最终一致性定义:系统保证在"足够长的时间后"所有副本最终达到一致状态,但在此期间,不同副本可能看到不同的数据。
关键公式:
强一致性:读操作立即看到最新的写操作结果
最终一致性:读操作可能暂时看不到最新结果,但最终会看到
业务场景选择:
- 银行转账:强一致性(余额必须准确)
- 电商下单:最终一致性(订单创建后,库存异步扣减可接受短暂延迟)
- 社交点赞:最终一致性(点赞数延迟几秒无伤大雅)
2.4 事件驱动架构的典型模式
模式一:简单事件通知
最基础的模式,生产者发布事件,消费者订阅处理。适用于业务解耦场景。
订单服务 ──发布 OrderCreated──> 消息队列 ──消费──> 短信服务
└──消费──> 推送服务
模式二:事件溯源(Event Sourcing)
将系统状态变化以事件序列的形式持久化存储,通过重放事件恢复状态。适用于审计日志、状态回溯场景。
事件存储:
[OrderCreated] → [PaymentCompleted] → [OrderShipped] → [OrderDelivered]
重建订单状态:重放所有事件 → 得到最终状态
模式三:CQRS
命令查询职责分离,写操作通过事件同步到读模型,读写分离提升性能。适用于读写比例悬殊的场景。
写模型(订单服务)──事件──> 读模型(订单查询服务)
│ │
复杂业务逻辑 简单查询
事务一致性 最终一致性
三、实战案例:电商订单系统的事件驱动改造
3.1 场景描述
原有同步架构:
用户下单 → 创建订单(DB)→ 同步调用库存扣减 → 同步调用积分增加 → 同步调用优惠券核销
改造后事件驱动架构:
用户下单 → 创建订单(DB)→ 发布 OrderCreated 事件 → 异步处理库存/积分/优惠券
3.2 事件定义
java
// 基础事件接口
public interface DomainEvent {
String getEventId(); // 事件唯一ID
String getEventType(); // 事件类型
Long getTimestamp(); // 事件时间戳
String getAggregateId(); // 聚合根ID(如订单ID)
}
// 订单创建事件
@Data
@Builder
public class OrderCreatedEvent implements DomainEvent {
private String eventId;
private String eventType = "OrderCreated";
private Long timestamp;
private String orderId; // 订单ID
private String userId; // 用户ID
private BigDecimal amount; // 订单金额
private List<OrderItem> items; // 订单商品列表
private String couponId; // 优惠券ID
@Override
public String getAggregateId() {
return orderId;
}
}
// 支付完成事件
@Data
@Builder
public class PaymentCompletedEvent implements DomainEvent {
private String eventId;
private String eventType = "PaymentCompleted";
private Long timestamp;
private String orderId;
private String paymentId;
private BigDecimal paidAmount;
private String paymentMethod;
@Override
public String getAggregateId() {
return orderId;
}
}
3.3 事件发布者实现
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private EventPublisher eventPublisher;
@Transactional
public Order createOrder(CreateOrderRequest request) {
// 1. 创建订单(本地事务)
Order order = Order.builder()
.orderId(UUID.randomUUID().toString())
.userId(request.getUserId())
.amount(request.getAmount())
.items(request.getItems())
.status("CREATED")
.build();
orderRepository.save(order);
// 2. 发布事件(事务提交后)
OrderCreatedEvent event = OrderCreatedEvent.builder()
.eventId(UUID.randomUUID().toString())
.timestamp(System.currentTimeMillis())
.orderId(order.getOrderId())
.userId(order.getUserId())
.amount(order.getAmount())
.items(order.getItems())
.couponId(request.getCouponId())
.build();
// 关键:事务提交后发布,避免事务回滚但事件已发出的不一致
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
eventPublisher.publish("order-events", event);
}
}
);
return order;
}
}
// 事件发布器(基于RocketMQ)
@Component
public class RocketMQEventPublisher implements EventPublisher {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Override
public void publish(String topic, DomainEvent event) {
String eventJson = JsonUtils.toJson(event);
// 同步发送,确保消息发送成功
SendResult result = rocketMQTemplate.syncSend(topic, eventJson);
if (result.getSendStatus() != SendStatus.SEND_OK) {
throw new RuntimeException("事件发送失败: " + event.getEventType());
}
log.info("事件发送成功: eventId={}, topic={}, msgId={}",
event.getEventId(), topic, result.getMsgId());
}
}
3.4 事件消费者实现
java
// 库存服务消费者
@Service
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "inventory-consumer-group",
selectorExpression = "OrderCreated || PaymentCompleted" // 订阅多种事件
)
public class InventoryConsumer implements RocketMQListener<String> {
@Autowired
private InventoryService inventoryService;
@Override
public void onMessage(String message) {
DomainEvent event = JsonUtils.fromJson(message, DomainEvent.class);
if ("OrderCreated".equals(event.getEventType())) {
OrderCreatedEvent orderEvent = JsonUtils.fromJson(message, OrderCreatedEvent.class);
handleOrderCreated(orderEvent);
} else if ("PaymentCompleted".equals(event.getEventType())) {
PaymentCompletedEvent paymentEvent = JsonUtils.fromJson(message, PaymentCompletedEvent.class);
handlePaymentCompleted(paymentEvent);
}
}
private void handleOrderCreated(OrderCreatedEvent event) {
// 预扣库存(下单时)
for (OrderItem item : event.getItems()) {
inventoryService.preDeduct(item.getProductId(), item.getQuantity(), event.getOrderId());
}
}
private void handlePaymentCompleted(PaymentCompletedEvent event) {
// 确认扣减库存(支付成功后)
inventoryService.confirmDeduct(event.getOrderId());
}
}
// 积分服务消费者
@Service
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "points-consumer-group",
selectorExpression = "PaymentCompleted" // 只订阅支付完成事件
)
public class PointsConsumer implements RocketMQListener<String> {
@Autowired
private PointsService pointsService;
@Override
public void onMessage(String message) {
PaymentCompletedEvent event = JsonUtils.fromJson(message, PaymentCompletedEvent.class);
// 增加积分(支付金额的1%)
int points = event.getPaidAmount().multiply(new BigDecimal("0.01")).intValue();
pointsService.addPoints(event.getUserId(), points, "订单支付: " + event.getOrderId());
}
}
// 优惠券服务消费者
@Service
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "coupon-consumer-group",
selectorExpression = "OrderCreated"
)
public class CouponConsumer implements RocketMQListener<String> {
@Autowired
private CouponService couponService;
@Override
public void onMessage(String message) {
OrderCreatedEvent event = JsonUtils.fromJson(message, OrderCreatedEvent.class);
if (event.getCouponId() != null) {
// 核销优惠券
couponService.useCoupon(event.getCouponId(), event.getOrderId(), event.getUserId());
}
}
}
四、关键问题与解决方案
4.1 问题1:事件丢失怎么办?
场景:订单创建成功,事件发送失败(消息队列宕机),库存未扣减,导致超卖。
根因:事件发布与本地事务不在同一个事务中,无法保证原子性。
解决方案:事务消息 + 本地消息表
java
// 方案1:RocketMQ事务消息
@Service
public class OrderServiceWithTransactionMessage {
@Autowired
private OrderRepository orderRepository;
@Autowired
private TransactionMQProducer transactionProducer;
public Order createOrder(CreateOrderRequest request) {
// 1. 发送半消息(消息暂不可消费)
Message message = new Message("order-events", "OrderCreated",
JsonUtils.toJson(request).getBytes());
TransactionSendResult result = transactionProducer.sendMessageInTransaction(
message,
null // 本地事务参数
);
if (result.getLocalTransactionState() == LocalTransactionState.COMMIT_MESSAGE) {
// 2. 本地事务执行成功,消息可消费
return orderRepository.findByOrderId(request.getOrderId());
} else {
// 3. 本地事务失败,消息回滚
throw new RuntimeException("创建订单失败");
}
}
}
// 事务监听器
@Component
public class OrderTransactionListener implements TransactionListener {
@Autowired
private OrderRepository orderRepository;
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 执行本地事务:创建订单
CreateOrderRequest request = JsonUtils.fromJson(
new String(msg.getBody()), CreateOrderRequest.class);
Order order = Order.builder()
.orderId(UUID.randomUUID().toString())
.userId(request.getUserId())
.amount(request.getAmount())
.status("CREATED")
.build();
orderRepository.save(order);
return LocalTransactionState.COMMIT_MESSAGE; // 提交消息
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE; // 回滚消息
}
}
@Override
public LocalTransactionState checkLocalTransaction(Message msg) {
// 事务回查:检查本地事务是否成功
String orderId = msg.getKeys();
Order order = orderRepository.findByOrderId(orderId);
if (order != null) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
// 方案2:本地消息表(数据库事务保证一致性)
@Service
public class OrderServiceWithLocalMessageTable {
@Autowired
private OrderRepository orderRepository;
@Autowired
private EventRepository eventRepository;
@Autowired
private EventPublisher eventPublisher;
@Transactional
public Order createOrder(CreateOrderRequest request) {
// 1. 创建订单
Order order = Order.builder()
.orderId(UUID.randomUUID().toString())
.userId(request.getUserId())
.amount(request.getAmount())
.status("CREATED")
.build();
orderRepository.save(order);
// 2. 写入本地事件表(同一事务)
EventRecord event = EventRecord.builder()
.eventId(UUID.randomUUID().toString())
.eventType("OrderCreated")
.aggregateId(order.getOrderId())
.payload(JsonUtils.toJson(order))
.status("PENDING")
.build();
eventRepository.save(event);
return order;
}
}
// 定时任务:扫描待发送事件并发布
@Scheduled(fixedDelay = 5000)
public void publishPendingEvents() {
List<EventRecord> pendingEvents = eventRepository.findByStatus("PENDING");
for (EventRecord event : pendingEvents) {
try {
eventPublisher.publish("order-events", event.getPayload());
event.setStatus("PUBLISHED");
eventRepository.save(event);
} catch (Exception e) {
log.error("事件发送失败: eventId={}", event.getEventId(), e);
// 下次重试
}
}
}
4.2 问题2:消息重复消费怎么办?
场景:库存服务消费事件后宕机,消息未ACK,消息队列重新投递,库存被重复扣减。
解决方案:幂等性设计
java
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private DeductionRecordRepository deductionRecordRepository;
public void preDeduct(String productId, int quantity, String orderId) {
// 幂等检查:是否已处理过
DeductionRecord existing = deductionRecordRepository.findByOrderId(orderId);
if (existing != null) {
log.info("订单已处理过,跳过: orderId={}", orderId);
return;
}
// 扣减库存
Inventory inventory = inventoryRepository.findByProductId(productId);
if (inventory.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryRepository.save(inventory);
// 记录扣减记录(幂等键)
DeductionRecord record = DeductionRecord.builder()
.orderId(orderId)
.productId(productId)
.quantity(quantity)
.build();
deductionRecordRepository.save(record);
}
}
// 数据库唯一索引保证幂等
CREATE UNIQUE INDEX uk_order_id ON deduction_record(order_id);
幂等性设计的三种方式:
- 唯一索引:数据库唯一索引保证不会重复插入
- 状态机:订单状态只能单向流转,避免重复处理
- Token机制:消费前检查Token是否已消费
4.3 问题3:消费失败如何重试?
解决方案:重试机制 + 死信队列
java
@Service
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "inventory-consumer-group",
maxReconsumeTimes = 3 // 最大重试次数
)
public class InventoryConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
try {
// 业务处理
processMessage(message);
} catch (Exception e) {
log.error("消息处理失败,将重试: {}", message, e);
throw new RuntimeException("处理失败,触发重试"); // 抛异常触发重试
}
}
}
// 死信队列消费者(超过重试次数后进入)
@Service
@RocketMQMessageListener(
topic = "%DLQ%inventory-consumer-group", // 死信队列
consumerGroup = "dead-letter-consumer"
)
public class DeadLetterConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 人工介入或告警
log.error("消息进入死信队列,需人工处理: {}", message);
alertService.sendAlert("死信队列消息需处理", message);
}
}
4.4 问题4:事件顺序如何保证?
场景:订单状态变更(创建→支付→发货→完成),但消费者处理顺序错乱。
解决方案:顺序消息
java
// RocketMQ顺序消息:同一订单的事件发送到同一Queue
@Service
public class OrderEventPublisher {
@Autowired
private DefaultMQProducer producer;
public void publishOrderEvent(DomainEvent event) {
Message message = new Message("order-events",
event.getEventType(),
JsonUtils.toJson(event).getBytes());
// 关键:以orderId为key,保证同一订单的事件进入同一Queue
message.setKeys(event.getAggregateId());
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
String orderId = (String) arg;
int index = Math.abs(orderId.hashCode()) % mqs.size();
return mqs.get(index); // 同一orderId路由到同一Queue
}
}, event.getAggregateId());
}
}
// 消费端:单线程串行消费
@Service
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "order-status-consumer",
consumeMode = ConsumeMode.ORDERLY // 顺序消费模式
)
public class OrderStatusConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
// 单线程串行处理,禁止在方法内启动异步线程
DomainEvent event = JsonUtils.fromJson(message, DomainEvent.class);
orderService.processStatusChange(event);
}
}
五、事件驱动架构最佳实践
5.1 事件命名规范
推荐格式 :<聚合根><过去式动词>
✅ 好的命名:
- OrderCreated(订单已创建)
- PaymentCompleted(支付已完成)
- InventoryDeducted(库存已扣减)
- UserRegistered(用户已注册)
❌ 不好的命名:
- CreateOrder(命令式,不是事件)
- OrderEvent(太泛,不明确)
- OrderSuccess(语义不清晰)
5.2 事件版本管理
java
// 事件升级时,保留旧版本兼容
@Data
public class OrderCreatedEventV2 implements DomainEvent {
private String eventId;
private String eventType = "OrderCreatedV2";
private Long timestamp;
private String orderId;
private String userId;
private BigDecimal amount;
private List<OrderItem> items;
private String couponId;
private String promotionId; // V2新增字段
// 兼容V1消费者的转换方法
public OrderCreatedEventV1 toV1() {
return OrderCreatedEventV1.builder()
.eventId(eventId)
.timestamp(timestamp)
.orderId(orderId)
.userId(userId)
.amount(amount)
.items(items)
.couponId(couponId)
.build();
}
}
5.3 监控与追踪
java
// 使用 Sleuth + Zipkin 进行分布式追踪
@Service
public class OrderService {
@Autowired
private Tracer tracer;
public Order createOrder(CreateOrderRequest request) {
Span span = tracer.nextSpan().name("create-order");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 业务逻辑
Order order = doCreateOrder(request);
// 发布事件时携带 TraceId
OrderCreatedEvent event = OrderCreatedEvent.builder()
.eventId(UUID.randomUUID().toString())
.traceId(span.context().traceIdString()) // 链路追踪
.orderId(order.getOrderId())
.build();
eventPublisher.publish("order-events", event);
return order;
} finally {
span.end();
}
}
}
5.4 事件存储与回溯
对于需要审计或状态回溯的场景,建议使用事件存储:
java
// 事件存储表设计
CREATE TABLE event_store (
event_id VARCHAR(64) PRIMARY KEY,
event_type VARCHAR(100) NOT NULL,
aggregate_id VARCHAR(64) NOT NULL,
aggregate_type VARCHAR(50) NOT NULL,
event_data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
version INT NOT NULL,
INDEX idx_aggregate (aggregate_type, aggregate_id, version)
);
// 事件溯源:通过重放事件恢复状态
public Order rebuildOrder(String orderId) {
List<EventRecord> events = eventStore.findByAggregateId(orderId);
Order order = new Order(); // 空对象
for (EventRecord event : events) {
order.apply(event); // 应用每个事件
}
return order;
}
六、选型建议:什么时候用事件驱动
适合事件驱动的场景
- 业务流程可异步:下单后库存扣减可延迟几秒
- 一对多通知:一个订单创建,多个服务需要响应
- 高并发削峰:秒杀场景,先接受请求,异步处理
- 业务解耦:新增业务逻辑不影响核心流程
- 审计追踪:需要记录完整的业务变更历史
不适合事件驱动的场景
- 强一致性要求:银行转账、库存核心扣减
- 实时性要求高:即时通讯、在线游戏
- 业务简单:CRUD操作,无需复杂架构
- 团队经验不足:事件驱动调试困难,需要成熟团队
引入事件驱动的决策流程
1. 评估业务场景
├── 是否需要异步处理?
├── 是否能接受最终一致性?
└── 是否有多个消费者?
2. 评估团队能力
├── 是否有消息队列运维经验?
├── 是否有能力处理分布式事务?
└── 是否有完善的监控体系?
3. 选择技术方案
├── 轻量级场景 → RabbitMQ
├── 高吞吐场景 → Kafka
└── 金融级事务 → RocketMQ
4. 逐步迁移
├── 先从非核心业务开始
├── 建立完善的监控告警
└── 积累经验后再推广
七、踩坑总结
踩坑1:事务回滚但事件已发出
问题 :@Transactional 方法中直接发送事件,事务回滚了但消息已发出。
解决方案:
- 使用
TransactionSynchronizationManager.registerSynchronization在事务提交后发送 - 使用事务消息或本地消息表
踩坑2:消费者处理超时导致重复消费
问题:消费者处理时间过长,超过消息队列的等待时间,消息被重新投递。
解决方案:
- 优化消费者处理逻辑,减少单条消息处理时间
- 增加消息队列的等待超时时间
- 实现幂等性,重复消费不影响结果
踩坑3:事件类型过多导致消费者臃肿
问题:一个消费者订阅几十种事件,代码臃肿难以维护。
解决方案:
- 按业务领域拆分消费者
- 使用策略模式,每种事件类型对应一个处理器
踩坑4:消息积压导致消费者OOM
问题:大促期间消息积压严重,消费者内存溢出崩溃。
根因:消费者拉取消息速度大于处理速度,消息堆积在内存中。
解决方案:
java
// RocketMQ消费者配置
@RocketMQMessageListener(
topic = "order-events",
consumerGroup = "order-consumer",
consumeMode = ConsumeMode.CONCURRENTLY,
maxReconsumeTimes = 3,
consumeThreadMax = 20, // 限制消费线程数
pullBatchSize = 10 // 每次拉取消息数量
)
踩坑5:事件丢失后无法追踪
问题:线上出现数据不一致,但无法定位是哪个事件丢失了。
解决方案:
- 建立事件日志表,记录所有事件的生命周期
- 使用分布式追踪工具(Zipkin/Jaeger)
- 关键事件加监控告警
java
// 事件生命周期日志
CREATE TABLE event_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
event_id VARCHAR(64) NOT NULL,
event_type VARCHAR(100) NOT NULL,
status VARCHAR(20) NOT NULL, -- PUBLISHED/CONSUMED/FAILED
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT,
INDEX idx_event_id (event_id),
INDEX idx_status (status, created_at)
);
九、技术选型对比
消息中间件选型
| 中间件 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| Kafka | 大数据、日志收集、流处理 | 高吞吐、持久化、分区有序 | 延迟高、事务支持弱 |
| RocketMQ | 金融、电商、分布式事务 | 事务消息、延迟消息、顺序消息 | 社区活跃度低 |
| RabbitMQ | 轻量级异步、简单解耦 | 开箱即用、路由灵活 | 吞吐量有限、内存优先 |
| Pulsar | 多租户、跨地域复制 | 存算分离、多协议支持 | 生态不如Kafka成熟 |
事件总线选型
对于云原生环境,可以考虑使用云厂商提供的事件总线服务:
- 阿里云事件总线:集成阿里云生态,支持事件规则过滤
- 腾讯云事件总线:支持跨地域事件路由
- AWS EventBridge:Serverless架构首选
十、总结
事件驱动架构的核心价值:
- 解耦:生产者不关心消费者,消费者自主决定如何处理
- 异步:提高响应速度,削峰填谷
- 扩展:新增消费者无需修改生产者
- 审计:事件存储提供完整的业务变更历史
落地关键点:
- 事件可靠性:事务消息或本地消息表保证不丢
- 幂等性:消费端必须支持重复消费
- 重试机制:失败自动重试,超过阈值进入死信队列
- 监控追踪:全链路追踪,快速定位问题
- 顺序保证:顺序消息保证同一聚合根的事件有序
- 背压控制:消费者拉取速度要匹配处理能力,避免OOM
性能优化建议:
- 生产者:异步发送提升吞吐,批量发送减少网络开销
- 消费者:批量消费提升效率,并发消费提升并行度
- 存储:消息压缩减少磁盘占用,过期清理避免无限增长
- 监控:消费延迟告警,死信队列监控,消费者健康检查
个人观点 :
事件驱动架构不是银弹,它用复杂度换了解耦和异步。在引入事件驱动之前,先问自己:真的需要吗?业务能接受最终一致性吗?团队有能力运维消息队列吗?架构选型永远是在权衡,没有最优解,只有最适解。
今日思考 :
你们系统中有哪些场景适合用事件驱动架构?有没有遇到过事件丢失或重复消费的问题?欢迎评论区分享你的踩坑经验!
个人观点,仅供参考