一种解决NOSQL和SQL数据分布式事务的技术方案
废话不多说。直奔主题!
这是目前遇见的一个业务场景:
1、为了支撑一套产品生产线,打造了一套中间件框架。这套框架集成了各种通用性功能,大大缩短了开发成本。
2、你可以理解,是一套业务中台。
3、我该次讲的是数据中台部分,一套支撑上层应用的数据访问层DAO。
也不废话,直接说设计思路:
继续说重点:
这里说的是multdao如何实现整合mysql和mongodb和ES数据库.
如果要整合,俩个目标:读和写。
写:同步syn写,一次写记录,分别在mysql、mongodb、es里存在一条,数据一致性,并且出现事务问题保证数据多库数据一致。
Mongodb.syn=true //开启同步
Mysql.syn=true //开启同步
读:可以切换,从mongodb取数据,从mysql取数据库,从ES里取数据。
DAO.name=组件名
Master.query=mongodb//查询来源库
Master.query.whitelist=项目包路径 //哪些包(精细到类名)下的查询走上述库
如何设计同步?
public String saveSyn(Object po){
try{
mysqlsession.save(po);
mongodbsession.save(po);
}catch(RuntimeException e){
mysqlsession.rollback();
mongodbsession.rollback();
}
}
这看起来不错,不过我告诉你这是错的。为啥?你这可以解决单层垮库本地事务,解决的了多层嵌套事务吗?
也就是说这样写,没有完全解决分布式事务。
不废话直接告诉你一种正确的分布式事务实现方案:
1、思想:选一款DAO中间件,比如HIBERNATE。为啥选?他统一了规范,你写一套PO映射关系就行,他对mongodb和mysql都不需要变代码。(PO就是@Entity注解的类)然后你可以extends一个父类,这个父类@MappedSuperclass注解一些公用字段。
2、你选一个支持XA本地事务的数据库,比如这里选了mysql,因为我发现hibernate 的mysql提供了拦截器和监听器接口。PostInsertEventListener,PostDeleteEventListener,PostUpdateEventListener
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;
说明一下,hibernate拦截器是个大杂烩,执行前执行后啥都能拦截。但是我们要的是,mysql执行成功之后,拦截进行写mongodb的操作,所以选择了过滤器。
MYSQL是支持XA事务的,因此我们可以不用自己实现事务。用MYSQL成熟的事务,我们只需要在事务回滚的时候得到触发告诉我这个语句写MYSQL成功了,那么我就可以继续去写MONGODB。如果MYSQL执行事务失败发生回滚了,告诉我不能写mongodb。
3、写三个过滤器实现。
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;
import gboat3.mult.dao.util.MongoUtil;
public class MongoUpdateEventListener implements PostUpdateEventListener {
private static final long serialVersionUID = -7776598890958220332L;
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onPostUpdate(PostUpdateEvent event) {
// TODO Auto-generated method stub
System.out.println("PostUpdateEventListener同步到mongodb:"+event.getEntity().getClass().getSimpleName());
MongoUtil cc=SpringContextUtil.getApplicationContext().getBean(MongoUtil.class);
cc.updateMog(event.getEntity());
}
}
插一句,有一种情况:写的过程中,MYSQL正常,突然mongodb挂了。这样mysql事务还正常执行,正常触发过滤器是吧,mongdo不断的报错,是不是就造成mysql一直写,mongodb一些写不进去。我告诉你不会出现这种情况,因为hibernate给你控制了这种情况,当mongodb连接异常的时候,mysql也不正常工作。为什么?@Configuration 注解是在上一层,它出问题了初始化不过,MYSQL也暂停写。但是数据库挂这期间的写操作丢失。(任何一个库挂了,其他库都停止写,这样多库数据一致。挂了一个,其他的都不工作。挂的期间丢失操作,日志里人工处理,我们讲的分布式事务不包含因为宕机造成的数据丢失,但是保证了宕机时不产生一致性问题)