Seata的架構設計圖如下
TC - 事務協調者(可以簡單理解成一個Seata服務端)
維護全局和分支事務的狀態,驅動全局事務提交或回滾。
TM(GlobalTransaction) - 事務管理器
定義全局事務的範圍:開始全局事務、提交或回滾全局事務。
RM(ResourceManager) - 資源管理器
管理分支事務處理的資源,與TC交談以註冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。
執行Seata服務端
Spring cloud 整合分佈式事務Seata客戶端
Spring cloud 整合分佈式事務Seata可以參考之前文章
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
DataSourceProxy這個類是Seata包下的數據源代理增強對象,在Spring Cloud啓動過程當中是把這個對象放入到Spring容器當中
/**
* The type Data source proxy.
* 數據源代理對象,實現增加,可用於增加原Seata分佈式事務功能
* @author sharajava
*/
public class DataSourceProxy extends AbstractDataSourceProxy implements Resource {
private String resourceGroupId;
private static final String DEFAULT_RESOURCE_GROUP_ID = "DEFAULT";
private String jdbcUrl;
private String dbType;
/**
* Enable the table meta checker
*/
private static boolean ENABLE_TABLE_META_CHECKER_ENABLE = ConfigurationFactory.getInstance().getBoolean(
ConfigurationKeys.CLIENT_TABLE_META_CHECK_ENABLE, false);
/**
* Table meta checker interval
*/
private static final long TABLE_META_CHECKER_INTERVAL = 60000L;
private final ScheduledExecutorService tableMetaExcutor = new ScheduledThreadPoolExecutor(1,
new NamedThreadFactory("tableMetaChecker", 1, true));
/**
* Instantiates a new Data source proxy.
* 調用這個方法
* @param targetDataSource the target data source
*/
public DataSourceProxy(DataSource targetDataSource) {
this(targetDataSource, DEFAULT_RESOURCE_GROUP_ID);
}
/**
* Instantiates a new Data source proxy.
*
* @param targetDataSource the target data source
* @param resourceGroupId the resource group id
*/
public DataSourceProxy(DataSource targetDataSource, String resourceGroupId) {
super(targetDataSource);
init(targetDataSource, resourceGroupId);
}
/**
* RM的具體初始化訪求,主要是這個方法
* @param dataSource
* @param resourceGroupId
*/
private void init(DataSource dataSource, String resourceGroupId) {
this.resourceGroupId = resourceGroupId;
try (Connection connection = dataSource.getConnection()) {
jdbcUrl = connection.getMetaData().getURL();
dbType = JdbcUtils.getDbType(jdbcUrl, null);
} catch (SQLException e) {
throw new IllegalStateException("can not init dataSource", e);
}
//RM主要初始化方法,這裏初始化三種RM對象
DefaultResourceManager.get().registerResource(this);
if (ENABLE_TABLE_META_CHECKER_ENABLE) {
tableMetaExcutor.scheduleAtFixedRate(() -> {
try (Connection connection = dataSource.getConnection()) {
TableMetaCacheFactory.getTableMetaCache(DataSourceProxy.this.getDbType())
.refresh(connection, DataSourceProxy.this.getResourceId());
} catch (Exception e) {
}
}, 0, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS);
}
}
/**
* Gets plain connection.
*
* @return the plain connection
* @throws SQLException the sql exception
*/
public Connection getPlainConnection() throws SQLException {
return targetDataSource.getConnection();
}
/**
* Gets db type.
*
* @return the db type
*/
public String getDbType() {
return dbType;
}
@Override
public ConnectionProxy getConnection() throws SQLException {
Connection targetConnection = targetDataSource.getConnection();
return new ConnectionProxy(this, targetConnection);
}
@Override
public ConnectionProxy getConnection(String username, String password) throws SQLException {
Connection targetConnection = targetDataSource.getConnection(username, password);
return new ConnectionProxy(this, targetConnection);
}
@Override
public String getResourceGroupId() {
return resourceGroupId;
}
@Override
public String getResourceId() {
if (jdbcUrl.contains("?")) {
return jdbcUrl.substring(0, jdbcUrl.indexOf("?"));
} else {
return jdbcUrl;
}
}
@Override
public BranchType getBranchType() {
return BranchType.AT;
}
}
DefaultResourceManager 類封裝了RM的核心業務邏輯代碼,比如初始化三種類型 RM對象,向TC(Seata的服務端)發送相關的請求,如向AT發送註冊RM請求、向AT報告RM的狀態、向AT發送分支事務的提交請求、向AT發送分支事務的回滾請求等
/**
* default resource manager, adapt all resource managers
* 默認的RM
* @author zhangsen
*/
public class DefaultResourceManager implements ResourceManager {
/**
* all resource managers
*/
protected static Map<BranchType, ResourceManager> resourceManagers
= new ConcurrentHashMap<>();
/**
* 初始化相關的分支事務
*/
private DefaultResourceManager() {
initResourceManagers();
}
/**
* Get resource manager.
*
* @return the resource manager
*/
public static DefaultResourceManager get() {
return SingletonHolder.INSTANCE;
}
/**
* only for mock
*
* @param branchType
* @param rm
*/
public static void mockResourceManager(BranchType branchType, ResourceManager rm) {
//維護所有的RM資源
resourceManagers.put(branchType, rm);
}
/**
* 初始公相關的分支事務
*/
protected void initResourceManagers() {
//init all resource managers
//初始化所有的RM管理器,通過報名加相關的文件前綴去讀取這個文件的內容,最新去通過反射來實例化類,最後放入到容器在當中
//有三個 一個DBRM 、TCCRM 、SaGaRM
List<ResourceManager> allResourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class);
if (CollectionUtils.isNotEmpty(allResourceManagers)) {
for (ResourceManager rm : allResourceManagers) {
resourceManagers.put(rm.getBranchType(), rm);
}
}
}
/**
* RM分支事務提交方法
* @param branchType the branch type
* @param xid Transaction id.
* @param branchId Branch id.
* @param resourceId Resource id.
* @param applicationData Application data bind with this branch.
* @return
* @throws TransactionException
*/
@Override
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId,
String resourceId, String applicationData)
throws TransactionException {
return getResourceManager(branchType).branchCommit(branchType, xid, branchId, resourceId, applicationData);
}
/**
* 分支事務回滾方法
* @param branchType the branch type
* @param xid Transaction id.
* @param branchId Branch id.
* @param resourceId Resource id.
* @param applicationData Application data bind with this branch.
* @return
* @throws TransactionException
*/
@Override
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId,
String resourceId, String applicationData)
throws TransactionException {
return getResourceManager(branchType).branchRollback(branchType, xid, branchId, resourceId, applicationData);
}
/**
* 分支註冊方法
* @param branchType the branch type
* @param resourceId the resource id
* @param clientId the client id
* @param xid the xid
* @param applicationData the context
* @param lockKeys the lock keys
* @return
* @throws TransactionException
*/
@Override
public Long branchRegister(BranchType branchType, String resourceId,
String clientId, String xid, String applicationData, String lockKeys)
throws TransactionException {
return getResourceManager(branchType).branchRegister(branchType, resourceId, clientId, xid, applicationData,
lockKeys);
}
/**
* 分支心跳報告
* @param branchType the branch type
* @param xid the xid
* @param branchId the branch id
* @param status the status
* @param applicationData the application data
* @throws TransactionException
*/
@Override
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status,
String applicationData) throws TransactionException {
getResourceManager(branchType).branchReport(branchType, xid, branchId, status, applicationData);
}
@Override
public boolean lockQuery(BranchType branchType, String resourceId,
String xid, String lockKeys) throws TransactionException {
return getResourceManager(branchType).lockQuery(branchType, resourceId, xid, lockKeys);
}
@Override
public void registerResource(Resource resource) {
getResourceManager(resource.getBranchType()).registerResource(resource);
}
/**
* 解除註冊
* @param resource The resource to be removed.
*/
@Override
public void unregisterResource(Resource resource) {
getResourceManager(resource.getBranchType()).unregisterResource(resource);
}
@Override
public Map<String, Resource> getManagedResources() {
Map<String, Resource> allResource = new HashMap<>();
for (ResourceManager rm : resourceManagers.values()) {
Map<String, Resource> tempResources = rm.getManagedResources();
if (tempResources != null) {
allResource.putAll(tempResources);
}
}
return allResource;
}
/**
* get ResourceManager by Resource Type
*
* @param branchType
* @return
*/
public ResourceManager getResourceManager(BranchType branchType) {
ResourceManager rm = resourceManagers.get(branchType);
if (rm == null) {
throw new FrameworkException("No ResourceManager for BranchType:" + branchType.name());
}
return rm;
}
@Override
public BranchType getBranchType() {
throw new FrameworkException("DefaultResourceManager isn't a real ResourceManager");
}
//數據源代理對象初始化的時候,會調用到這個方法
private static class SingletonHolder {
//會初始化三種RM 分別是
/*
AT –> DatasourceRourceManager
TCC->TCCRourceManager
SAGA->SagaResoutceMnager
分別放入到resourceManagers容器當中
*/
private static DefaultResourceManager INSTANCE = new DefaultResourceManager();
}
}
在DefaultResourceManager#registerResource方法當中調用了AbstractResourceManager類當中的方法
AbstractResourceManager類的內容如下,它的registerResource方法當中封裝了向AT發送註冊請求的具體代碼
/**
* abstract ResourceManager
* 抽象的分支事務管理器 RM
* @author zhangsen
*/
public abstract class AbstractResourceManager implements ResourceManager {
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractResourceManager.class);
/**
* 註冊分支信息
* registry branch record
* @param branchType the branch type
* @param resourceId the resource id
* @param clientId the client id
* @param xid the xid
* @param lockKeys the lock keys
* @return
* @throws TransactionException
*/
@Override
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
try {
//封裝RM註冊請求實體類
BranchRegisterRequest request = new BranchRegisterRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
request.setBranchType(branchType);
request.setApplicationData(applicationData);
//向TC發送分支事務註冊請求
BranchRegisterResponse response = (BranchRegisterResponse) RmRpcClient.getInstance().sendMsgWithResponse(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
return response.getBranchId();
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex);
}
}
/**
* 向Seata服務端 報告分支事務的狀態
* report branch status
* @param branchType the branch type
* @param xid the xid
* @param branchId the branch id
* @param status the status
* @param applicationData the application data
* @throws TransactionException
*/
@Override
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException {
try {
//封裝分支事務的狀態請求實體
BranchReportRequest request = new BranchReportRequest();
request.setXid(xid);
request.setBranchId(branchId);
request.setStatus(status);
request.setApplicationData(applicationData);
//向TC發送分支事務狀態請求
BranchReportResponse response = (BranchReportResponse) RmRpcClient.getInstance().sendMsgWithResponse(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.BranchReportFailed, "Runtime", rex);
}
}
@Override
public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys) throws TransactionException {
return false;
}
@Override
public void unregisterResource(Resource resource) {
throw new NotSupportYetException("unregister a resource");
}
/**
* 向TC(Seata 服務端) 發送信息 註冊RM
* @param resource The resource to be managed.
*/
@Override
public void registerResource(Resource resource) {
//Netty的客戶端,發送請求
//RmRpcClient.getInstance() 獲取到Netty客戶的 RmRpcClient
RmRpcClient.getInstance().registerResource(resource.getResourceGroupId(), resource.getResourceId());
}
}
RmRpcClient類的具體內容如下:
這個類主要是封裝了和Netty Server(AT)端通信的相關的業務代碼,這個類還不是最新的請求對象
/**
* The type Rm rpc client.
* Netty客戶端具體的實現實現類
* @author slievrly
* @author zhaojun
*/
@Sharable
public final class RmRpcClient extends AbstractRpcRemotingClient {
private static final Logger LOGGER = LoggerFactory.getLogger(RmRpcClient.class);
private ResourceManager resourceManager;
//客戶端實例
private static volatile RmRpcClient instance;
private final AtomicBoolean initialized = new AtomicBoolean(false);
private static final long KEEP_ALIVE_TIME = Integer.MAX_VALUE;
private static final int MAX_QUEUE_SIZE = 20000;
private String applicationId;
private String transactionServiceGroup;
private RmRpcClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup,
ThreadPoolExecutor messageExecutor) {
super(nettyClientConfig, eventExecutorGroup, messageExecutor, TransactionRole.RMROLE);
}
/**
* Gets instance.
*
* @param applicationId the application id
* @param transactionServiceGroup the transaction service group
* @return the instance
*/
public static RmRpcClient getInstance(String applicationId, String transactionServiceGroup) {
RmRpcClient rmRpcClient = getInstance();
rmRpcClient.setApplicationId(applicationId);
rmRpcClient.setTransactionServiceGroup(transactionServiceGroup);
return rmRpcClient;
}
/**
* Gets instance.
* 獲取Netyy RPC客戶端
* @return the instance
*/
public static RmRpcClient getInstance() {
if (null == instance) {
synchronized (RmRpcClient.class) {
if (null == instance) {
NettyClientConfig nettyClientConfig = new NettyClientConfig();
final ThreadPoolExecutor messageExecutor = new ThreadPoolExecutor(
nettyClientConfig.getClientWorkerThreads(), nettyClientConfig.getClientWorkerThreads(),
KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_QUEUE_SIZE),
new NamedThreadFactory(nettyClientConfig.getRmDispatchThreadPrefix(),
nettyClientConfig.getClientWorkerThreads()), new ThreadPoolExecutor.CallerRunsPolicy());
instance = new RmRpcClient(nettyClientConfig, null, messageExecutor);
}
}
}
return instance;
}
/**
* Sets application id.
*
* @param applicationId the application id
*/
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
/**
* Sets transaction service group.
*
* @param transactionServiceGroup the transaction service group
*/
public void setTransactionServiceGroup(String transactionServiceGroup) {
this.transactionServiceGroup = transactionServiceGroup;
}
/**
* Sets resource manager.
*
* @param resourceManager the resource manager
*/
public void setResourceManager(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
/**
* 這個方法在一啓動的時候會調用到,可以查看Spring boot 客戶端啓動的控制檯,有看到具體的啓動過程,調用的方法
* Netty客戶初始化方法,啓動Netty客戶端,向Netty服務端(AT) 連接
*/
@Override
public void init() {
if (initialized.compareAndSet(false, true)) {
//
super.init();
}
}
@Override
public void destroy() {
super.destroy();
initialized.getAndSet(false);
instance = null;
}
@Override
protected Function<String, NettyPoolKey> getPoolKeyFunction() {
return (serverAddress) -> {
String resourceIds = getMergedResourceKeys();
if (null != resourceIds && LOGGER.isInfoEnabled()) {
LOGGER.info("RM will register :{}", resourceIds);
}
RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup);
message.setResourceIds(resourceIds);
return new NettyPoolKey(NettyPoolKey.TransactionRole.RMROLE, serverAddress, message);
};
}
@Override
protected String getTransactionServiceGroup() {
return transactionServiceGroup;
}
/**
* 註冊成功的回調方法
* @param serverAddress the server address
* @param channel the channel
* @param response the response
* @param requestMessage the request message
*/
@Override
public void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response,
AbstractMessage requestMessage) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("register RM success. server version:{},channel:{}", ((RegisterRMResponse)response).getVersion(), channel);
}
getClientChannelManager().registerChannel(serverAddress, channel);
String dbKey = getMergedResourceKeys();
RegisterRMRequest message = (RegisterRMRequest)requestMessage;
if (message.getResourceIds() != null) {
if (!message.getResourceIds().equals(dbKey)) {
sendRegisterMessage(serverAddress, channel, dbKey);
}
}
}
/**
* 註冊失敗的回調方法
* @param serverAddress the server address
* @param channel the channel
* @param response the response
* @param requestMessage the request message
*/
@Override
public void onRegisterMsgFail(String serverAddress, Channel channel, Object response,
AbstractMessage requestMessage) {
if (response instanceof RegisterRMResponse && LOGGER.isInfoEnabled()) {
LOGGER.info("register RM failed. server version:{}", ((RegisterRMResponse)response).getVersion());
}
throw new FrameworkException("register RM failed, channel:" + channel);
}
/**
* Register new db key.
* 註冊請求
* @param resourceGroupId the resource group id
* @param resourceId the db key
*/
public void registerResource(String resourceGroupId, String resourceId) {
if (getClientChannelManager().getChannels().isEmpty()) {
//重新請求Seata服務端
getClientChannelManager().reconnect(transactionServiceGroup);
return;
}
synchronized (getClientChannelManager().getChannels()) {
for (Map.Entry<String, Channel> entry : getClientChannelManager().getChannels().entrySet()) {
String serverAddress = entry.getKey();
Channel rmChannel = entry.getValue();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("will register resourceId:{}", resourceId);
}
//發送請求
sendRegisterMessage(serverAddress, rmChannel, resourceId);
}
}
}
/**
* 向AT發送註冊消息
* @param serverAddress
* @param channel
* @param resourceId
*/
private void sendRegisterMessage(String serverAddress, Channel channel, String resourceId) {
//註冊消息請求封裝對象
RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup);
message.setResourceIds(resourceId);
try {
super.sendAsyncRequestWithoutResponse(channel, message);
} catch (FrameworkException e) {
if (e.getErrcode() == FrameworkErrorCode.ChannelIsNotWritable && serverAddress != null) {
getClientChannelManager().releaseChannel(channel, serverAddress);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("remove not writable channel:{}", channel);
}
} else {
LOGGER.error("register resource failed, channel:{},resourceId:{}", channel, resourceId, e);
}
} catch (TimeoutException e) {
LOGGER.error(e.getMessage());
}
}
private String getMergedResourceKeys() {
Map<String, Resource> managedResources = resourceManager.getManagedResources();
Set<String> resourceIds = managedResources.keySet();
if (!resourceIds.isEmpty()) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String resourceId : resourceIds) {
if (first) {
first = false;
} else {
sb.append(DBKEYS_SPLIT_CHAR);
}
sb.append(resourceId);
}
return sb.toString();
}
return null;
}
}