事务是什么
如果我们需要对若干数据进行更新操作,为了保证这些数据的完整性和一致性,我们希望这些更新操作要么都成功,要么都失败
事务的特性(ACID)
- 原子性,是指一个事务操作不可分割,要么成功,要么失败,不能有一半成功一半失败的情况。
- 一致性,是指这些数据在事务执行完成这个时间点之前,读到的一定是更新前的数据,之后读到的一定是更新后的数据,不应该存在一个时刻,让用户读到更新过程中的数据。
- 隔离性,是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对正在进行的其他事务是隔离的,并发执行的各个事务之间不能互相干扰.
- 持久性,是指一个事务一旦完成提交,后续的其他操作和故障都不会对事务的结果产生任何影响。
分布式事务
对于分布式事务而言几乎满足不了 ACID
分布式事务的实现
事务消息
- 我们在做数据迁移时经常使用的方式
- 历史数据线下迁移
- 增量数据双写(写库+写队列)
- 移出双写
第二步中如何保证写库和写队列是在一个事务中呢
具体实现
- 创建事务消息listener,实现TransactionListener接口
type Listener struct{}
func (l *Listener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
onion_log.Infof("ExecuteLocalTransaction message: %+v", msg)
// TODO: 根据msg执行本地入库操作, 根据事务结果返回对应状态 CommitMessageState or RollbackMessageState
return primitive.CommitMessageState
}
func (l *Listener) CheckLocalTransaction(msgExt *primitive.MessageExt) primitive.LocalTransactionState {
onion_log.Infof("CheckLocalTransaction message: %+v", msgExt)
// TODO: 根据msgExt查询是否入库成功 返回对应状态:CommitMessageState or RollbackMessageState
return primitive.CommitMessageState
}
- 定义事务消息生产者
transactionClient, err := rocketmq.NewTransactionProducer(
listener,
producer.WithNameServer(c.config.Endpoint),
producer.WithCreateTopicKey(c.config.Topic),
producer.WithGroupName(c.config.Group),
producer.WithNamespace(c.config.InstanceID),
producer.WithCredentials(primitive.Credentials{
AccessKey: c.config.Access,
SecretKey: c.config.Secret,
}),
producer.WithRetry(c.config.Retries),
)
- 发送消息
msg := primitive.NewMessage("study_text", []byte("Hello RocketMQ again "+strconv.Itoa(i))
// msg增加属性,标志为事务消息
msg.WithProperty(primitive.PropertyTransactionPrepared, "true")
msg.WithProperty(primitive.PropertyProducerGroup, tp.producer.options.GroupName)
result, err := transactionClient.SendMessageInTransaction(ctx, msg)
if err != nil {
return err
}
logger.Infof("SendMessageInTransaction result %+v", result)
2PC(Two-phase Commit,二阶段提交)
引入一个事务协调者的角色来协调管理各参与者的提交和回滚,将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase)
- 准备阶段协调者会给各参与者发送准备命令
- 假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。
- 假如在第一阶段有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。
实现
dtm: xa事务模式
问题
- 准备阶段请求无响应或者某个参与者挂了?
- 设置超时机制,超时后所有事务回滚
- 提交阶段有请求失败了?
- 无论是提交事务还是回滚事务,都是不断重试
- 协调者单点故障问题?
- 通过选举选出新的协调者
- 极端状态下的数据不一致问题
协调者发送了提交命令,此时第一个参与者收到了并执行,然后协调者和第一个参与者都挂了,其它参与者没收到请求。新的参与者不知道第一个参与者有没有提交事务,所以就不知道接下来应该发送提交命令还是回滚命令
TCC (Try-Confirm-Cancel)
- Try 操作做业务检查及资源预留
- Confirm 做业务确认操作
- Cancel 实现一个与 Try 相反的操作即回滚操作
特点
- 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作
- 撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等
- 适用的范围更大,可以跨数据库、跨不同的业务系统来实现事务
实现
dtm:TCC事务模式
总结
2PC一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面
TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的Try、Confirm、Cancel三个方法
事务消息其实都是最终一致性事务,因此适用于一些对时间不敏感的业务
事务消息 | 2PC | TCC | |
---|---|---|---|
一致性 | 最终一致性 | 强一致性 | 最终一致性 |
吞吐量 | 高 | 低 | 中 |
实现复杂度 | 中 | 易 | 难 |
参考资料: