分布式事物解决方法(零配置 零耦合 零依赖)

分布式事物解决方法:零配置 零耦合 零依赖

应用场景

  • 非分布式架构系统下
serviceA TransactionalA serviceB serviceC 开启一个新的事物 本地调用B方法 本地调用C的方法 本地C方法结束 本地B方法结束 事务提交 serviceA TransactionalA serviceB serviceC

这是本地事务处理,默认情况下,serviceA中开启事务之后,serviceA方法中调用serviceB、serviceC方法,均会保证serviceA、serviceB、serviceC三者业务处理的事务一致性与原子性;

  • 分布式架构系统下
serviceA TransactionalA serviceB TransactionalB serviceC TransactionalC 开启本地A新的事物 远程调用B方法 开启本地B新的事物 远程调用C的方法 开启本地C新的事物 远程C方法结束 远程B方法结束 A本地法结束 A本地 事务提交 B本地 事务提交 C本地 事务提交 serviceA TransactionalA serviceB TransactionalB serviceC TransactionalC

serviceA、serviceB、serviceC不同节点下,默认情况下,serviceA、serviceB、serviceC三节点服务均会拥有自己的事务且相互不受影响,同时serviceA方法中远程调用其他两个节点方法具有依次性,假如serviceB、serviceC业务方法均处理完且节点本地事务均提交,但在serviceA方法最后异步异常了,事务需要回滚,这就出现严重的问题,因为不同节点上,serviceB、serviceC事务已经处理完成无法回滚,所以如何保证serviceA、serviceB、serviceC三者业务处理的事务一致性与原子性就是我们需要解决的问题!也就是如何实现分布式事务?

目前主流解决方案

  1. 两阶段提交(2PC)
    优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域(其实也不能100%保证强一致)
    缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景,如果分布式系统跨接口调用,目前 .NET 界还没有实现方案

  2. 补偿事务(TCC)
    优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
    缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理

  3. 本地消息表(异步确保)
    优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性
    缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理

  4. MQ 事务消息
    优点: 实现了最终一致性,不需要依赖本地数据库事务。
    缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源

本项目解决思路(mything.jar)

其思路本质:事务预处理+事务异步提交+事务同步回滚

我们分布式系统中,每一个节点均有自己的本地事务,也就是可以再各自本地节点上建立一个各自的事务管理器;通过用户请求traceId识别一次处理业务的一致性;让每一个节点serviceB、serviceC事务开启之后,会先处理自己本地业务方法,但处理完不会立即提交事务,只会对事务进行一个预处理,判断是否是否可以正确提交;当调用者serviceA对serviceB、serviceC调用完成且本地业务方法正常处理完成时,就会同步通过HTPP通知serviceB、serviceC节点事务同时提交,同时serviceA本地事务也会提交;反正,假如任何一段有异常或者事务预处理失败最后都会通知到serviceA中,serviceA就会通过HTTP通知所有节点对事物进行回滚;这是理想情况下,99.99%的均能正常处理,但这有一个缺点!假如任何一台节点停止服务了时,假如是调用者(消费节点)服务器停掉了,那么被调用者(服务节点)事务就会迟迟未提交,处于等待通知提交状态,导致出现锁表现象;针对该特殊情况,本项目解决方法就是被调用者(服务节点)会每5S对调用者(根消费节点)请求主动咨询,主要是判断本地等待事务是否还需要继续等待通知,如果不需要或者主动咨询失败(2次),本地事务管理器会让其进行回滚;

调用者(根消费节点):serviceA(首先建立事务的)
调用者(消费节点):serviceA、serviceB(相对serviceC来说,两者均是消费)
被调用者(服务节点):serviceB、serviceC(相对serviceA来说,两者均是服务)

应用方法

调用者方引用:让Spring扫描"com.tc.mything.consume"目录,如下
@EnableTransactionManagement //启用事务
@DubboComponentScan("com.xw.serve.task")
@ComponentScan(basePackages={"com.tc.mything.consume"})
@ImportResource({"classpath*:spring-*.xml"})
public class App {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SpringApplication application = new SpringApplication(App.class);
        application.setRegisterShutdownHook(false);
        application.setBannerMode(Banner.Mode.OFF);
        BeanHeader.setApplicationContext(application.run(args));
	}
}
被调用者引用:让Spring扫描"com.tc.mything.serve"目录,如下
@EnableTransactionManagement //启用事务
@ImportResource({"classpath*:spring-*.xml"})
@DubboComponentScan({"com.xw.serve.common"})
@ComponentScan(basePackages={"com.tc.mything.serve"})
public class App {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SpringApplication application = new SpringApplication(App.class);
        application.setRegisterShutdownHook(false);
        application.setBannerMode(Banner.Mode.OFF);
        BeanHeader.setApplicationContext(application.run(args));
	}
}

只需要这样引入即可,如果不想引用去掉mything.jar或者去掉扫描路径就行了,对系统原有业务代码无半点侵入!真正达到零配置、零耦合、零依赖、且高效率、应用简单;

特别说明下:本项目实现方式默认过滤了get、select 开头的service方法事务处理,且目前只针对TransactionDefinition.PROPAGATION_REQUIRED处理**

应用结果说明

调用者serviceA代码,如下
@Component("tastTest")
public class TastTest {
	@Reference(check=false)
	private SysParameterService sysParameterService;
	@Scheduled(cron = "0/20 * * * * *")
	@Transactional
    public void scheduled(){
    	//第一次调用
		sysParameterService.saveSysParameter(SysParameterType.ThirdParty, "ceshi1", SysParamKey.EMAIL_NICK_NAME, "1234560");
		//第二次调用
		sysParameterService.selectByKey(1);
		// TODO Auto-generated method stub
		//第三次调用
		sysParameterService.saveSysParameter(SysParameterType.ThirdParty, "ceshi", SysParamKey.EMAIL_SENDER, "123456");
        System.out.println("=====>>>>>使用cron  {}");
    }
}
被调用者serviceB代码,如下
@Service(timeout=5000)
@Transactional
public class SysParameterServiceImpl  extends BaseServiceImpl<SysParameterMapper,SysParameter,Integer> implements SysParameterService {
	private static Map<String, Object> sysParam;
	public boolean saveSysParameter(SysParameterType type, String name, SysParamKey key, String val) {
		// TODO Auto-generated method stub
		boolean bool = false;
		SysParameter sp = new SysParameter();
		sp.setCreateTime(new Date());
		sp.setIsDel(IsDel.NO);
		sp.setKey(key.name());
		sp.setName(name);
		sp.setType(type);
		sp.setValType(key.getValType());
		sp.setVal(val);
		int k = super.dao.insert(sp);
		if(k>0) {
			if(sysParam != null) sysParam.put(key.name(), SysParameterValType.getByObject(key.getValType(), val));
			bool = true;
		}
		return bool;
	}
}
结果日志如下:

调用者(消费者)打印日志记录
调用者(消费者)打印日志记录

被调用者(服务者)打印日志记录
被调用者(服务者)打印日志记录

通过日志看的出,调用者(消费者)调用被调用者(服务者)插入数据两次,两次均是同一个事务提交处理;

结束

这是本人花了半个月时间研究的一种实现方式,已经运用过半年多了,中途也修复过许多bug,如果程序猿运用中有发现bug可立即联系我,我会第一时间来和你分享解决方法;无论该轻量级框架是否好坏,只是想分享出来让大家可以一起学习,本项目的源代码会在2019年10月1日在码云上分享出来,到时希望该框架版本能够非常完善。如果有需要引用的程序猿可以点击如下来下载,共同使用发现其缺点;本项目只是展现其解决方法思路,如果针对springcloud或者其他的分布式框架系统均可以采用该思路来解决;

	零配置:无任何配置,使用简单明了
	零耦合:对系统代码性侵入为零
	零依赖:无需依赖类似MQ、数据表等
	高效率:利用HTTP无阻塞通知机制

引用mything.jar 下载

mything-1.1.2.jar @联系my:550110979@qq.com

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值