事务模式扩展
TX-LCN不仅仅支持LCN TXC TCC模式,也可以由开发者自定义符合TX-LCN控制原理的请求事务模型。
事务模式的接口定义
- 增加一种新的事务模式名称,不能与已有的模式重名,例如test模式。
在使用新的模式时,只需要在业务上标准类型即可。如下:
@TxTransaction(type = "test")
@Transactional
public void test(){
}
- 实现
TransactionResourceExecutor
接口,处理db资源。
public interface TransactionResourceProxy {
/**
* 获取资源连接
*
* @param connectionCallback Connection提供者
* @return Connection Connection
* @throws Throwable Throwable
*/
Connection proxyConnection(ConnectionCallback connectionCallback) throws Throwable;
}
- 实现不同状态下的事务控制 实现
DTXLocalControl
接口处理业务。
public interface DTXLocalControl {
/**
* 业务代码执行前
*
* @param info info
* @throws TransactionException TransactionException
*/
default void preBusinessCode(TxTransactionInfo info) throws TransactionException {
}
/**
* 执行业务代码
*
* @param info info
* @return Object Object
* @throws Throwable Throwable
*/
default Object doBusinessCode(TxTransactionInfo info) throws Throwable {
return info.getBusinessCallback().call();
}
/**
* 业务代码执行失败
*
* @param info info
* @param throwable throwable
*/
default void onBusinessCodeError(TxTransactionInfo info, Throwable throwable) throws TransactionException {
}
/**
* 业务代码执行成功
*
* @param info info
* @param result result
* @throws TransactionException TransactionException
*/
default void onBusinessCodeSuccess(TxTransactionInfo info, Object result) throws TransactionException {
}
/**
* 清场
*
* @param info info
*/
default void postBusinessCode(TxTransactionInfo info) {
}
}
例如 LCN starting状态下的处理实现,bean name control_lcn_starting
是标准规范,control_+模式名称+状态名称:
@Service(value = "control_lcn_starting")
@Slf4j
public class LcnStartingTransaction implements DTXLocalControl {
private final TransactionControlTemplate transactionControlTemplate;
@Autowired
public LcnStartingTransaction(TransactionControlTemplate transactionControlTemplate) {
this.transactionControlTemplate = transactionControlTemplate;
}
@Override
public void preBusinessCode(TxTransactionInfo info) throws TransactionException {
// create DTX group
transactionControlTemplate.createGroup(
info.getGroupId(), info.getUnitId(), info.getTransactionInfo(), info.getTransactionType());
// lcn type need connection proxy
DTXLocalContext.makeProxy();
}
@Override
public void onBusinessCodeError(TxTransactionInfo info, Throwable throwable) {
DTXLocalContext.cur().setSysTransactionState(0);
}
@Override
public void onBusinessCodeSuccess(TxTransactionInfo info, Object result) {
DTXLocalContext.cur().setSysTransactionState(1);
}
@Override
public void postBusinessCode(TxTransactionInfo info) {
// RPC close DTX group
transactionControlTemplate.notifyGroup(
info.getGroupId(), info.getUnitId(), info.getTransactionType(), DTXLocalContext.transactionState());
}
}
说明:
若增加的新的模式最好创建一个新的模块,然后调整pom增加该模块的支持即可。
通讯协议扩展
通讯协议扩展是指txclient与txmanager通讯的协议扩展。
目前TX-LCN默认采用了netty方式通讯。关于拓展也以netty方式来说明如何拓展。
拓展txlcn-txmsg
主要实现6个接口,其中下面4个是由txlcn-txmsg的实现方提供:
- 发起请求调用客户端
RpcClient
public abstract class RpcClient {
@Autowired
private RpcLoadBalance rpcLoadBalance;
/**
* 发送指令不需要返回数据,需要知道返回的状态
*
* @param rpcCmd 指令内容
* @return 指令状态
* @throws RpcException 远程调用请求异常
*/
public abstract RpcResponseState send(RpcCmd rpcCmd) throws RpcException;
/**
* 发送指令不需要返回数据,需要知道返回的状态
*
* @param remoteKey 远程标识关键字
* @param msg 指令内容
* @return 指令状态
* @throws RpcException 远程调用请求异常
*/
public abstract RpcResponseState send(String remoteKey, MessageDto msg) throws RpcException;
/**
* 发送请求并获取响应
*
* @param rpcCmd 指令内容
* @return 响应指令数据
* @throws RpcException 远程调用请求异常
*/
public abstract MessageDto request(RpcCmd rpcCmd) throws RpcException;
/**
* 发送请求并响应
*
* @param remoteKey 远程标识关键字
* @param msg 请求内容
* @return 相应指令数据
* @throws RpcException 远程调用请求异常
*/
public abstract MessageDto request(String remoteKey, MessageDto msg) throws RpcException;
/**
* 发送请求并获取响应
*
* @param remoteKey 远程标识关键字
* @param msg 请求内容
* @param timeout 超时时间
* @return 响应消息
* @throws RpcException 远程调用请求异常
*/
public abstract MessageDto request(String remoteKey, MessageDto msg, long timeout) throws RpcException;
/**
* 获取一个远程标识关键字
*
* @return 远程标识关键字
* @throws RpcException 远程调用请求异常
*/
public String loadRemoteKey() throws RpcException {
return rpcLoadBalance.getRemoteKey();
}
/**
* 获取所有的远程连接对象
*
* @return 远程连接对象数组.
*/
public abstract List<String> loadAllRemoteKey();
/**
* 获取模块远程标识
*
* @param moduleName 模块名称
* @return 远程标识
*/
public abstract List<String> remoteKeys(String moduleName);
/**
* 绑定模块名称
*
* @param remoteKey 远程标识
* @param appName 应用名称
*/
public abstract void bindAppName(String remoteKey, String appName);
/**
* 获取模块名称
*
* @param remoteKey 远程标识
* @return 应用名称
*/
public abstract String getAppName(String remoteKey);
/**
* 获取所有的模块信息
*
* @return 应用名称
*/
public abstract List<AppInfo> apps();
}
- 发起请求调用客户端初始化接口
RpcClientInitializer
public interface RpcClientInitializer {
/**
* message client init
* @param hosts
*/
void init(List<TxManagerHost> hosts);
/**
* 建立连接
* @param socketAddress
*/
void connect(SocketAddress socketAddress);
}
- TxManager message初始化接口
RpcServerInitializer
public interface RpcServerInitializer {
/**
* support server init
*
* @param managerProperties 配置信息
*/
void init(ManagerProperties managerProperties);
}
- 客户端请求TxManager的负载策略
RpcLoadBalance
public interface RpcLoadBalance {
/**
* 获取一个远程标识关键字
* @return
* @throws RpcException
*/
String getRemoteKey()throws RpcException;
}
下面两个用于Tx-Manager与Tx-Client的回调业务
RpcAnswer
接口 Tx-Manager与Tx-Client都会实现用于接受响应数据。
public interface RpcAnswer {
/**
* 业务处理
* @param rpcCmd message 曾业务回调函数
*
*/
void callback(RpcCmd rpcCmd);
}
ClientInitCallBack
接口,用于Tx-Manager下需要处理客户端与TxManager建立连接的初始化回调业务。
public interface ClientInitCallBack {
/**
* 初始化连接成功回调
* @param remoteKey 远程调用唯一key
*/
void connected(String remoteKey);
}
实现细节可借鉴 txlcn-txmsg-netty 模块源码
RPC框架扩展
RPC扩展主要是指在分布式事务框架下对传递控制参数的支持、与负载均衡的扩展控制。
下面以dubbo框架为例讲解扩展的过程。
- 传递控制参数的支持
dubbo参数传递可以通过隐形传参的方式来完成。参数传递分为传出与接受两块。下面分别展示代码说明。
dubbo传出参数的filter:
@Activate(group = Constants.CONSUMER)
public class DubboRequestInterceptor implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//判断是否存在事务
if (TracingContext.tracing().hasGroup()) {
//设置传递的参数信息
RpcContext.getContext().setAttachment(TracingConstants.HEADER_KEY_GROUP_ID, TracingContext.tracing().groupId());
RpcContext.getContext().setAttachment(TracingConstants.HEADER_KEY_APP_MAP, TracingContext.tracing().appMapBase64String());
}
return invoker.invoke(invocation);
}
}
dubbo传入参数的filter:
@Activate(group = {Constants.PROVIDER})
public class TracingHandlerInterceptor implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//接受参数
String groupId = invocation.getAttachment(TracingConstants.HEADER_KEY_GROUP_ID, "");
String appList = invocation.getAttachment(TracingConstants.HEADER_KEY_APP_MAP, "");
//设置参数
TracingContext.tracing().init(Maps.newHashMap(TracingConstants.GROUP_ID, groupId, TracingConstants.APP_MAP, appList));
return invoker.invoke(invocation);
}
}
- 负载均衡的扩展控制(仅限于LCN模式下)
控制的效果:负载均衡扩展主要为了做到在同一次分布式事务中相同的模块重复调用在同一个模块下。
为什么仅限于LCN模式?
当存在这样的请求链,A模块先调用了B模块的one方法,然后在调用了two方法,如下所示:
A ->B.one(); A ->B.two(); 假如one与two方法的业务都是在修改同一条数据,假如两个方法的id相同,伪代码如下:
void one(id){
execute => update demo set state = 1 where id = {id} ;
}
void two(id){
execute => update demo set state = 2 where id = {id} ;
}
若B模块做了集群存在B1、B2两个模块。那么就可能出现A分别调用了B1 B2模块,如下:
A ->B1.one(); A ->B2.two(); 在这样的情况下业务方将在LCN下会因为资源占用而导致执行失败而回滚事务。为了支持这样的场景,框架提供了重写了rpc的负载模式。
控制在同一次事务下同一个被负载的模块被重复调用时将只会请求到第一次被选中的模块。在采用这样的方案的时候也会提高Connection的连接使用率,会提高在负载情况下的性能。
dubbo框架默认提供了四种负载策略,这里仅仅展示random的实现。
public class TxlcnRandomLoadBalance extends RandomLoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
return DubboTxlcnLoadBalance.chooseInvoker(invokers, url, invocation, super::select);
}
}
@Slf4j
class DubboTxlcnLoadBalance {
private static final String empty = "";
static <T> Invoker<T> chooseInvoker(List<Invoker<T>> invokers, URL url, Invocation invocation, TxLcnLoadBalance loadBalance) {
//非分布式事务直接执行默认业务.
if(!TracingContext.tracing().hasGroup()){
return loadBalance.select(invokers, url, invocation);
}
TracingContext.tracing()
.addApp(RpcContext.getContext().getLocalAddressString(), empty);
assert invokers.size() > 0;
JSONObject appMap = TracingContext.tracing().appMap();
log.debug("invokers: {}", invokers);
Invoker<T> chooseInvoker = null;
outline:
for (Invoker<T> tInvoker : invokers) {
for (String address : appMap.keySet()) {
if (address.equals(tInvoker.getUrl().getAddress())) {
chooseInvoker = tInvoker;
log.debug("txlcn choosed server [{}] in txGroup: {}", tInvoker, TracingContext.tracing().groupId());
break outline;
}
}
}
if (chooseInvoker == null) {
Invoker<T> invoker = loadBalance.select(invokers, url, invocation);
TracingContext.tracing().addApp(invoker.getUrl().getAddress(), empty);
return invoker;
}
return chooseInvoker;
}
@FunctionalInterface
public interface TxLcnLoadBalance {
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation);
}
}