【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版
准备阶段
prepare
请求,参与者执行事务但不提交Yes/No
响应提交阶段
commit
命令rollback
命令-- 事务状态表
CREATE TABLE transaction_log (
tx_id VARCHAR(64) PRIMARY KEY,
status ENUM('PREPARED', 'COMMITTED', 'ROLLBACKED'),
create_time DATETIME
);
// 协调者伪代码
public class Coordinator {
public boolean execute(TransactionContext ctx) {
// 阶段1:准备
for (Participant p : participants) {
if (!p.prepare(ctx)) return false;
}
// 阶段2:提交
try {
for (Participant p : participants) {
p.commit(ctx);
}
return true;
} catch (Exception e) {
for (Participant p : participants) {
p.rollback(ctx);
}
return false;
}
}
}
✅ 强一致性保证
❌ 同步阻塞、协调者单点故障
❌ 网络分区可能导致资源锁定
阶段 | 操作说明 |
---|---|
Try | 资源预留(如冻结库存) |
Confirm | 确认执行(实际扣减) |
Cancel | 补偿回滚(释放冻结资源) |
public interface PaymentServiceTCC {
@Transactional
boolean tryPayment(Long orderId, BigDecimal amount);
@Transactional
boolean confirmPayment(Long orderId);
@Transactional
boolean cancelPayment(Long orderId);
}
[业务事务] → [消息表记录] → [定时任务] → [MQ] → [消费者]
CREATE TABLE local_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
biz_id VARCHAR(64) NOT NULL,
content TEXT NOT NULL,
status TINYINT DEFAULT 0 COMMENT '0-待发送,1-已发送',
retry_count INT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
@Transactional
public void createOrder(Order order) {
// 1. 业务操作
orderMapper.insert(order);
// 2. 记录消息(同事务)
LocalMessage msg = new LocalMessage();
msg.setBizId(order.getOrderNo());
msg.setContent(JSON.toJSONString(order));
messageMapper.insert(msg);
}
类型 | 特点 |
---|---|
协同式 | 通过事件驱动协调 |
编排式 | 中央协调器控制流程 |
public void placeOrder() {
try {
// 正向操作
inventoryService.reserveStock();
paymentService.charge();
shippingService.createShipment();
} catch (Exception e) {
// 逆向补偿
shippingService.cancelShipment();
paymentService.refund();
inventoryService.releaseStock();
}
}
CREATE TABLE saga_log (
saga_id VARCHAR(64) NOT NULL,
step_name VARCHAR(32) NOT NULL,
status VARCHAR(16) NOT NULL,
params TEXT,
create_time TIMESTAMP,
PRIMARY KEY (saga_id, step_name)
);
方案 | 一致性 | 复杂度 | 性能 | 适用场景 |
---|---|---|---|---|
2PC | 强一致 | 高 | 低 | 银行转账等强一致场景 |
TCC | 最终一致 | 中高 | 中 | 电商交易、支付系统 |
本地消息表 | 最终一致 | 低 | 高 | 物流通知、积分系统 |
Saga | 最终一致 | 中 | 中高 | 长流程业务(保险理赔) |
注:所有方案都需要配合幂等控制、重试机制和日志追踪才能保证可靠性
本地消息表是一种基于最终一致性的分布式事务解决方案,通过异步消息传递+事务日志实现跨服务数据同步:
组件 | 作用 | 技术实现示例 |
---|---|---|
业务事务表 | 记录主业务数据(如订单表) | MySQL order 表 |
本地消息表 | 存储待分发的消息(状态机模式) | MySQL local_message 表 |
定时任务调度器 | 扫描未处理消息,触发下游服务调用 | Spring @Scheduled + 线程池 |
幂等处理器 | 防止下游服务重复消费 | Redis唯一ID/数据库唯一约束 |
监控告警模块 | 捕获长期失败消息,触发人工干预 | Prometheus + Grafana + 企业微信报警 |
@Transactional
public void createOrder(Order order) {
orderDao.insert(order); // 业务数据
messageDao.insert(toMessage(order)); // 事务消息
}
@PostMapping("/api/process")
public Response process(@RequestBody Message msg) {
if (redis.setnx(msg.getId(), "1", 24h)) { // 分布式锁
realProcess(msg); // 真实业务逻辑
}
return Response.success();
}
long delay = Math.min(1000 * Math.pow(2, retryCount), 3600000);
CREATE TABLE local_message_${hash(id)%16} (...);
@Scheduled(fixedDelay = 5000)
public void batchProcess() {
List<Message> batch = messageDao.scan(100);
CompletableFuture[] futures = batch.stream()
.map(msg -> asyncProcess(msg))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
}
if (msg.getRetryCount() > MAX_RETRY) {
deadLetterQueue.add(msg); // 转入死信队列
alarmService.notifyAdmin(msg);
}
时效性缺陷
依赖定时任务扫描,消息处理延迟通常在秒级
架构约束
要求业务消息必须可序列化存储
维护成本
需额外维护消息表、定时任务等组件
场景 | 适用性 | 理由 |
---|---|---|
订单创建→库存扣减 | ★★★★★ | 允许短暂延迟,业务容忍最终一致 |
支付成功→短信通知 | ★★★★☆ | 通知类操作对实时性要求较低 |
金融账户转账 | ★★☆☆☆ | 需要强一致性,建议使用TCC或Saga |
日志数据同步 | ★★★★★ | 天然适合异步处理 |
该方案在电商、物流等互联网业务中广泛应用,是平衡实现复杂度与可靠性的典型折中方案
餐馆运营问题 | 分布式系统问题 | 解决方案 |
---|---|---|
前台接单记录 | 业务数据存储 | MySQL订单表 |
厨房小票 | 事务消息 | local_message表 |
服务员送小票 | 消息投递 | 定时任务扫描 |
厨房小黑板 | 幂等控制 | Redis唯一标识 |
店长监督 | 监控告警 | Prometheus+钉钉 |
@Transactional // 原子操作保证
public void 接单(订单 order) {
// 记录主订单(账本)
订单库.save(order);
// 生成厨房小票(消息)
小票机.save(new 小票(
order.id,
"新订单",
LocalDateTime.now()
));
}
就像收银机:按一次按钮同时打印顾客账单和厨房小票
def 做菜(订单号):
if redis.get(订单号) == "处理中":
return "已在制作"
redis.set(订单号, "处理中", ex=3600)
实际做菜操作()
return "开始制作"
相当于厨师长看到相同订单号会说:“这份已经在炒了!”
// 1. 指数退避重试
Thread.sleep(1000 * Math.pow(2, 重试次数));
// 2. 死信队列监控
if(小票.重试次数 > 3){
钉钉报警("请店长处理订单:"+小票.订单号);
}
// 3. 人工补偿入口
@PostMapping("/手动重试")
public String 人工重试(String 订单号){
消息 msg = 小票机.find(订单号);
厨房服务.做菜(msg.getContent());
}
就像优秀的外卖系统:订单可能稍有延迟,但绝不会丢失或重复!
用这种模式可以处理:订单→库存、支付→通知等大多数最终一致性场景。