MySQL和MongoDB事務同步的一種嘗試
現象
最近線上的一條數據狀態不對,但是日志又記錄上了。 查看了這條數據的更新邏輯
public Boolean autoReject(AutoRejectParam param) {
OperationLog log = createOperationLog(param);
// 保存操作日志到mysql
operationLogMapper.insertSelective(log);
Query query = new Query();
Criteria criteria = new Criteria();
criteria.and("requestId").is(param.getRequestId());
query.addCriteria(criteria);
Update update = new Update();
update.set("status", CvBusinessStatusEnum.Rejected.getCode())
.set("updateTime", new Date())
.set("taskId", "");
mongoTemplate.updateFirst(query, update, JSONObject.class, collectionName);
return true;
}
從代碼可以看出這里分別保存了日志到mysql,然后更新了mongodb中的數據狀態。
很明顯保存mysql成功了,但是更新mongodb的數據失敗了,那為什么保存mongodb的數據失敗了呢? 然后根據日志發現,當時服務器和mongodb連接出現了問題,于是就導致了保存mysql成功,保存到mongodb失敗了。
如何解決?
問題既然產生了,那么有什么辦法能夠保證要成功就都成功呢? 第一個想到的是事務,我們需要保證兩個數據庫操作的事務一致性就可以避免這個問題了。使用單一的事務管理器肯定是不行的,需要使用鏈式事務。
我們可以使用spring中的ChainedTransactionManager
來實現鏈式調用
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager mongoTransactionManager(MongoTemplate mongoTemplate) {
return new MongoTransactionManager(mongoTemplate.getMongoDbFactory());
}
@Bean
public PlatformTransactionManager jpaTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public ChainedTransactionManager chainedTransactionManager(
PlatformTransactionManager mongoTransactionManager,
PlatformTransactionManager jpaTransactionManager) {
return new ChainedTransactionManager(mongoTransactionManager, jpaTransactionManager);
}
}
@Transactional("chainedTransactionManager")
public Boolean autoReject(AutoRejectParam param) {
//省略其他代碼
// 保存操作日志到mysql
operationLogMapper.insertSelective(log);
// 更新mongodb
mongoTemplate.updateFirst(query, update, JSONObject.class, collectionName);
return true;
}
這種方法使用 ChainedTransactionManager 來管理多個事務管理器。當方法執行時,它會按順序開啟所有事務,如果在執行過程中出現異常,它會按相反的順序回滾所有事務。
需要注意的是,這種方法并不能保證 100% 的事務一致性,因為它實際上是在應用層面模擬的分布式事務。在某些極端情況下(比如網絡故障或服務器崩潰),可能會出現部分提交的情況。
比如我們是現在這樣的執行流程
transaction1 begin
transaction2 begin
transaction2 commit -> error rollbacks, rollbacks transction1 too
transaction1 commit -> error, only rollbacks transaction1
比如上面這種情況,在最后提交transaction1的時候如果由于網絡原因提交失敗了,就會導致事務2成功,事務1失敗,還是部分提交了。
當然如果業務要求對于這種不一致是可以接受的,或者說我們可以進行手動補償方式達到最終一致性,那這種方案也是可以接受的。
對于要求更高事務一致性的場景,可能需要考慮使用專門的分布式事務解決方案,如 XA 協議或 TCC (Try-Confirm-Cancel) 模式。 比如JTA就屬于XA協議, 我們可以使用開源實現atomikos。