微服务拆分治理最佳实践( 二 )


 

  • 问题1:如上图,事务的是配置在Service层,当事务开启时,数据源的连接并没有获取到,因为真正的数据源配置在Mapper上 。所以会报错,这个错误可以通过多数据源组件的默认数据源功能解决 。
  • 问题2:MyBatis的事务实现会缓存数据库链接 。当第一次缓存了数据库链接后,后续配置在mapper上的数据源注解并不会重新获取数据库链接,而是直接使用缓存起来的数据库链接 。如果后续的mapper要操作其余数据库,会出现找不到表的情况 。鉴于以上问题,我们开发了一个自定义的事务实现类,用来解决这个问题 。
下面将对方案中出现的两个组件进行简要说明原理 。
多数据源组件 
【微服务拆分治理最佳实践】多数据源组件是单个应用连接多个数据源时使用的工具,其核心原理是通过配置文件将数据库链接在程序启动时初始化好,在执行到存在注解的方法时,通过切面获取当前的数据源名称来切换数据源,当一次调用涉及多个数据源时,会利用栈的特性解决数据源嵌套的问题 。
/** * 切面方法 */ public Object switchdataSourceAroundAdvice(ProceedingJoinPoint pjp) throws Throwable { //获取数据源的名字 String dsName = getDataSourceName(pjp); boolean dataSourceSwitched = false; if (StringUtils.isNotEmpty(dsName) && !StringUtils.equals(dsName, StackRoutingDataSource.getCurrentTargetKey())) { // 见下一段代码 StackRoutingDataSource.setTargetDs(dsName); dataSourceSwitched = true; } try { // 执行切面方法 return pjp.proceed(); } catch (Throwable e) { throw e; } finally { if (dataSourceSwitched) { StackRoutingDataSource.clear(); } } }public static void setTargetDs(String dbName) { if (dbName == null) { throw new NullPointerException(); } if (contextHolder.get() == null) { contextHolder.set(new Stack()); } contextHolder.get().push(dbName); log.debug("set current datasource is " + dbName); }
StackRoutingDataSource继承 AbstractRoutingDataSource类,AbstractRoutingDataSource是Spring-jdbc包提供的一个了AbstractDataSource的抽象类,它实现了DataSource接口的用于获取数据库链接的方法 。
自定义事务实现
从方案二的图中可以看到默认的事务实现使用的是mybatis的SpringManagedTransaction 。
微服务拆分治理最佳实践

文章插图
 
如上图,Transaction和SpringManagedTransaction都是mybatis提供的类,他提供了接口供SQLSession使用,处理事务操作 。通过下边的一段代码可以看到,事务对象中存在connection变量,首次获得数据库链接后,后续当前事务内的所有数据库操作都不会重新获取数据库链接,而是会使用现有的数据库链接,从而无法支持跨库操作 。
public class SpringManagedTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class); private final DataSource dataSource; private Connection connection; private boolean isConnectionTransactional; private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } // 下略 }
MultiDataSourceManagedTransaction是我们自定义的事务实现,继承自SpringManagedTransaction类,并在内部支持维护多个数据库链接 。每次执行数据库操作时,会根据数据源名称判断,如果当前数据源没有缓存的链接则重新获取链接 。这样,service上的事务注解其实控制了多个单库事务,且作用域范围相同,一起进行提交或回滚 。
代码如下:
public class MultiDataSourceManagedTransaction extends SpringManagedTransaction { private DataSource dataSource; public ConcurrentHashMap CON_MAP = new ConcurrentHashMap<>(); public MultiDataSourceManagedTransaction(DataSource dataSource) { super(dataSource); this.dataSource = dataSource; } @Override public Connection getConnection() throws SQLException { Method getCurrentTargetKey; String dataSourceKey; try { getCurrentTargetKey = dataSource.getClass().getDeclaredMethod("getCurrentTargetKey"); getCurrentTargetKey.setAccessible(true); dataSourceKey = (String) getCurrentTargetKey.invoke(dataSource); } catch (Exception e) { log.error("MultiDataSourceManagedTransaction invoke getCurrentTargetKey 异常", e); return null; } if (CON_MAP.get(dataSourceKey) == null) { Connection connection = dataSource.getConnection(); if (!TransactionSynchronizationManager.isActualTransactionActive()) { connection.setAutoCommit(true); } else { connection.setAutoCommit(false); } CON_MAP.put(dataSourceKey, connection); return connection; } return CON_MAP.get(dataSourceKey); } @Override public void commit() throws SQLException { if (CON_MAP == null || CON_MAP.size() == 0) { return; } Set> entries = CON_MAP.entrySet(); for (Map.Entry entry : entries) { Connection value = https://www.isolves.com/it/cxkf/jiagou/2022-12-20/entry.getValue(); if (!value.isClosed() && !value.getAutoCommit()) { value.commit(); } } } @Override public void rollback() throws SQLException { if (CON_MAP == null || CON_MAP.size() == 0) { return; } Set> entries = CON_MAP.entrySet(); for (Map.Entry entry : entries) { Connection value = entry.getValue(); if (value == null) { continue; } if (!value.isClosed() && !value.getAutoCommit()) { entry.getValue().rollback(); } } } @Override public void close() throws SQLException { if (CON_MAP == null || CON_MAP.size() == 0) { return; } Set> entries = CON_MAP.entrySet(); for (Map.Entry entry : entries) { DataSourceUtils.releaseConnection(entry.getValue(), this.dataSource); } CON_MAP.clear(); } }


推荐阅读