chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
348
server/pom.xml
Normal file
348
server/pom.xml
Normal file
@@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-server ${project.version}</name>
|
||||
|
||||
<properties>
|
||||
<logstash-logback-encoder.version>6.5</logstash-logback-encoder.version>
|
||||
<kafka-appender.version>0.2.0-RC2</kafka-appender.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-discovery-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-serializer-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-compressor-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-metrics-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- for database -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.beust</groupId>
|
||||
<artifactId>jcommander</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- only for event bus -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- logback -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- jedis -->
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.fppt</groupId>
|
||||
<artifactId>jedis-mock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
<version>${logstash-logback-encoder.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.danielwegener</groupId>
|
||||
<artifactId>logback-kafka-appender</artifactId>
|
||||
<version>${kafka-appender.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-seata</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<excludeDefaultDirectories>false</excludeDefaultDirectories>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>../distribution</directory>
|
||||
<includes>
|
||||
<include>bin/**</include>
|
||||
<include>conf/**</include>
|
||||
<include>logs/**</include>
|
||||
<include>lib/**</include>
|
||||
<include>seata-server-${revision}/**</include>
|
||||
</includes>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>appassembler-maven-plugin</artifactId>
|
||||
<version>1.10</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>assemble</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<configurationDirectory>conf</configurationDirectory>
|
||||
<configurationSourceDirectory>src/main/resources</configurationSourceDirectory>
|
||||
<copyConfigurationDirectory>true</copyConfigurationDirectory>
|
||||
<includeConfigurationDirectoryInClasspath>true</includeConfigurationDirectoryInClasspath>
|
||||
<repositoryLayout>flat</repositoryLayout>
|
||||
<repositoryName>lib</repositoryName>
|
||||
<encoding>UTF-8</encoding>
|
||||
<useWildcardClassPath>true</useWildcardClassPath>
|
||||
<binFileExtensions>
|
||||
<unix>.sh</unix>
|
||||
<windows>.bat</windows>
|
||||
</binFileExtensions>
|
||||
<assembleDirectory>../distribution/seata-server-${revision}</assembleDirectory>
|
||||
<logsDirectory>logs</logsDirectory>
|
||||
<programs>
|
||||
<program>
|
||||
<mainClass>io.seata.server.Server</mainClass>
|
||||
<name>seata-server</name>
|
||||
<jvmSettings>
|
||||
<extraArguments>
|
||||
<extraArgument>-server</extraArgument>
|
||||
<extraArgument>-Xmx2048m</extraArgument>
|
||||
<extraArgument>-Xms2048m</extraArgument>
|
||||
<extraArgument>-Xmn1024m</extraArgument>
|
||||
<extraArgument>-Xss512k</extraArgument>
|
||||
<extraArgument>-XX:SurvivorRatio=10</extraArgument>
|
||||
<extraArgument>-XX:MetaspaceSize=128m</extraArgument>
|
||||
<extraArgument>-XX:MaxMetaspaceSize=256m</extraArgument>
|
||||
<extraArgument>-XX:MaxDirectMemorySize=1024m</extraArgument>
|
||||
<extraArgument>-XX:-OmitStackTraceInFastThrow</extraArgument>
|
||||
<extraArgument>-XX:-UseAdaptiveSizePolicy</extraArgument>
|
||||
<extraArgument>-XX:+HeapDumpOnOutOfMemoryError</extraArgument>
|
||||
<extraArgument>-XX:HeapDumpPath=@BASEDIR@/logs/java_heapdump.hprof</extraArgument>
|
||||
<!--gc-->
|
||||
<extraArgument>-XX:+DisableExplicitGC</extraArgument>
|
||||
<extraArgument>-XX:+CMSParallelRemarkEnabled</extraArgument>
|
||||
<extraArgument>-XX:+UseCMSInitiatingOccupancyOnly</extraArgument>
|
||||
<extraArgument>-XX:CMSInitiatingOccupancyFraction=75</extraArgument>
|
||||
<extraArgument>-Xloggc:@BASEDIR@/logs/seata_gc.log</extraArgument>
|
||||
<extraArgument>-verbose:gc</extraArgument>
|
||||
<!--netty-->
|
||||
<extraArgument>-Dio.netty.leakDetectionLevel=advanced</extraArgument>
|
||||
<!--log-->
|
||||
<extraArgument>-Dlogback.color.disable-for-bat=true</extraArgument>
|
||||
</extraArguments>
|
||||
</jvmSettings>
|
||||
<platforms>
|
||||
<platform>windows</platform>
|
||||
<platform>unix</platform>
|
||||
</platforms>
|
||||
</program>
|
||||
</programs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-mysql</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.jdbc.version}</version>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql8.jdbc.version}</version>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>
|
||||
${basedir}/target/lib/jdbc
|
||||
</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.google.cloud.tools</groupId>
|
||||
<artifactId>jib-maven-plugin</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<configuration>
|
||||
<from>
|
||||
<image>${IMAGE_NAME}</image>
|
||||
</from>
|
||||
<to>
|
||||
<image>docker.io/seataio/seata-server</image>
|
||||
<tags>
|
||||
${image.tags}
|
||||
</tags>
|
||||
<auth>
|
||||
<username>${REGISTRY_USERNAME}</username>
|
||||
<password>${REGISTRY_PASSWORD}</password>
|
||||
</auth>
|
||||
</to>
|
||||
<container>
|
||||
<appRoot>/seata-server</appRoot>
|
||||
<workingDirectory>/seata-server</workingDirectory>
|
||||
<mainClass>io.seata.server.Server</mainClass>
|
||||
<ports>
|
||||
<port>8091</port>
|
||||
</ports>
|
||||
<jvmFlags>
|
||||
<jvmFlag>-Djava.security.egd=file:/dev/./urandom</jvmFlag>
|
||||
<jvmFlag>-server</jvmFlag>
|
||||
<jvmFlag>-Xss512k</jvmFlag>
|
||||
<jvmFlag>-XX:+UnlockExperimentalVMOptions</jvmFlag>
|
||||
<jvmFlag>-XX:+UseContainerSupport</jvmFlag>
|
||||
<jvmFlag>-XX:SurvivorRatio=10</jvmFlag>
|
||||
<jvmFlag>-XX:MetaspaceSize=128m</jvmFlag>
|
||||
<jvmFlag>-XX:MaxMetaspaceSize=256m</jvmFlag>
|
||||
<jvmFlag>-XX:MaxDirectMemorySize=1024m</jvmFlag>
|
||||
<jvmFlag>-XX:-OmitStackTraceInFastThrow</jvmFlag>
|
||||
<jvmFlag>-XX:-UseAdaptiveSizePolicy</jvmFlag>
|
||||
<jvmFlag>-XX:+HeapDumpOnOutOfMemoryError</jvmFlag>
|
||||
<jvmFlag>-XX:HeapDumpPath=/var/log/seata_heapdump.hprof</jvmFlag>
|
||||
<!--gc-->
|
||||
<jvmFlag>-XX:+DisableExplicitGC</jvmFlag>
|
||||
<jvmFlag>-XX:+CMSParallelRemarkEnabled</jvmFlag>
|
||||
<jvmFlag>-XX:+UseCMSInitiatingOccupancyOnly</jvmFlag>
|
||||
<jvmFlag>-XX:CMSInitiatingOccupancyFraction=75</jvmFlag>
|
||||
<jvmFlag>-Xloggc:/var/log/seata_gc.log</jvmFlag>
|
||||
<jvmFlag>-verbose:gc</jvmFlag>
|
||||
<!--netty-->
|
||||
<jvmFlag>-Dio.netty.leakDetectionLevel=advanced</jvmFlag>
|
||||
<!--log-->
|
||||
<jvmFlag>-Dlogback.color.disable-for-bat=true</jvmFlag>
|
||||
</jvmFlags>
|
||||
<labels>
|
||||
<name>seata-server</name>
|
||||
</labels>
|
||||
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
|
||||
</container>
|
||||
<extraDirectories>
|
||||
<paths>
|
||||
<path>
|
||||
<from>target/lib/jdbc</from>
|
||||
<into>/seata-server/libs/jdbc</into>
|
||||
</path>
|
||||
</paths>
|
||||
</extraDirectories>
|
||||
<skip>${image.publish.skip}</skip>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.core.exception.AbstractExceptionHandler;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.exception.TransactionExceptionCode;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.protocol.transaction.AbstractGlobalEndRequest;
|
||||
import io.seata.core.protocol.transaction.AbstractGlobalEndResponse;
|
||||
import io.seata.core.protocol.transaction.BranchRegisterRequest;
|
||||
import io.seata.core.protocol.transaction.BranchRegisterResponse;
|
||||
import io.seata.core.protocol.transaction.BranchReportRequest;
|
||||
import io.seata.core.protocol.transaction.BranchReportResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalLockQueryRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalLockQueryResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalReportRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalReportResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusResponse;
|
||||
import io.seata.core.protocol.transaction.TCInboundHandler;
|
||||
import io.seata.core.rpc.RpcContext;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Abstract tc inbound handler.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public abstract class AbstractTCInboundHandler extends AbstractExceptionHandler implements TCInboundHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTCInboundHandler.class);
|
||||
|
||||
@Override
|
||||
public GlobalBeginResponse handle(GlobalBeginRequest request, final RpcContext rpcContext) {
|
||||
GlobalBeginResponse response = new GlobalBeginResponse();
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalBeginRequest, GlobalBeginResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalBeginRequest request, GlobalBeginResponse response) throws TransactionException {
|
||||
try {
|
||||
doGlobalBegin(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore,
|
||||
String.format("begin global request failed. xid=%s, msg=%s", response.getXid(), e.getMessage()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global begin.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public GlobalCommitResponse handle(GlobalCommitRequest request, final RpcContext rpcContext) {
|
||||
GlobalCommitResponse response = new GlobalCommitResponse();
|
||||
response.setGlobalStatus(GlobalStatus.Committing);
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalCommitRequest, GlobalCommitResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalCommitRequest request, GlobalCommitResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doGlobalCommit(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore,
|
||||
String.format("global commit request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onTransactionException(GlobalCommitRequest request, GlobalCommitResponse response,
|
||||
TransactionException tex) {
|
||||
super.onTransactionException(request, response, tex);
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(GlobalCommitRequest request, GlobalCommitResponse response, Exception rex) {
|
||||
super.onException(request, response, rex);
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
|
||||
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global commit.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public GlobalRollbackResponse handle(GlobalRollbackRequest request, final RpcContext rpcContext) {
|
||||
GlobalRollbackResponse response = new GlobalRollbackResponse();
|
||||
response.setGlobalStatus(GlobalStatus.Rollbacking);
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalRollbackRequest, GlobalRollbackResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalRollbackRequest request, GlobalRollbackResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doGlobalRollback(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore, String
|
||||
.format("global rollback request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionException(GlobalRollbackRequest request, GlobalRollbackResponse response,
|
||||
TransactionException tex) {
|
||||
super.onTransactionException(request, response, tex);
|
||||
// may be appears StoreException outer layer method catch
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(GlobalRollbackRequest request, GlobalRollbackResponse response, Exception rex) {
|
||||
super.onException(request, response, rex);
|
||||
// may be appears StoreException outer layer method catch
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global rollback.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public BranchRegisterResponse handle(BranchRegisterRequest request, final RpcContext rpcContext) {
|
||||
BranchRegisterResponse response = new BranchRegisterResponse();
|
||||
exceptionHandleTemplate(new AbstractCallback<BranchRegisterRequest, BranchRegisterResponse>() {
|
||||
@Override
|
||||
public void execute(BranchRegisterRequest request, BranchRegisterResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doBranchRegister(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore, String
|
||||
.format("branch register request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do branch register.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public BranchReportResponse handle(BranchReportRequest request, final RpcContext rpcContext) {
|
||||
BranchReportResponse response = new BranchReportResponse();
|
||||
exceptionHandleTemplate(new AbstractCallback<BranchReportRequest, BranchReportResponse>() {
|
||||
@Override
|
||||
public void execute(BranchReportRequest request, BranchReportResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doBranchReport(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore, String
|
||||
.format("branch report request failed. xid=%s, branchId=%s, msg=%s", request.getXid(),
|
||||
request.getBranchId(), e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do branch report.
|
||||
*
|
||||
* @param request the request
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doBranchReport(BranchReportRequest request, BranchReportResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public GlobalLockQueryResponse handle(GlobalLockQueryRequest request, final RpcContext rpcContext) {
|
||||
GlobalLockQueryResponse response = new GlobalLockQueryResponse();
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalLockQueryRequest, GlobalLockQueryResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalLockQueryRequest request, GlobalLockQueryResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doLockCheck(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore, String
|
||||
.format("global lock query request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do lock check.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doLockCheck(GlobalLockQueryRequest request, GlobalLockQueryResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public GlobalStatusResponse handle(GlobalStatusRequest request, final RpcContext rpcContext) {
|
||||
GlobalStatusResponse response = new GlobalStatusResponse();
|
||||
response.setGlobalStatus(GlobalStatus.UnKnown);
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalStatusRequest, GlobalStatusResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalStatusRequest request, GlobalStatusResponse response)
|
||||
throws TransactionException {
|
||||
try {
|
||||
doGlobalStatus(request, response, rpcContext);
|
||||
} catch (StoreException e) {
|
||||
throw new TransactionException(TransactionExceptionCode.FailedStore,
|
||||
String.format("global status request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionException(GlobalStatusRequest request, GlobalStatusResponse response,
|
||||
TransactionException tex) {
|
||||
super.onTransactionException(request, response, tex);
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(GlobalStatusRequest request, GlobalStatusResponse response, Exception rex) {
|
||||
super.onException(request, response, rex);
|
||||
checkTransactionStatus(request, response);
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global status.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doGlobalStatus(GlobalStatusRequest request, GlobalStatusResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
@Override
|
||||
public GlobalReportResponse handle(GlobalReportRequest request, final RpcContext rpcContext) {
|
||||
GlobalReportResponse response = new GlobalReportResponse();
|
||||
response.setGlobalStatus(request.getGlobalStatus());
|
||||
exceptionHandleTemplate(new AbstractCallback<GlobalReportRequest, GlobalReportResponse>() {
|
||||
@Override
|
||||
public void execute(GlobalReportRequest request, GlobalReportResponse response)
|
||||
throws TransactionException {
|
||||
doGlobalReport(request, response, rpcContext);
|
||||
}
|
||||
}, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global report.
|
||||
*
|
||||
* @param request the request
|
||||
* @param response the response
|
||||
* @param rpcContext the rpc context
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected abstract void doGlobalReport(GlobalReportRequest request, GlobalReportResponse response,
|
||||
RpcContext rpcContext) throws TransactionException;
|
||||
|
||||
private void checkTransactionStatus(AbstractGlobalEndRequest request, AbstractGlobalEndResponse response) {
|
||||
try {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(request.getXid(), false);
|
||||
if (globalSession != null) {
|
||||
response.setGlobalStatus(globalSession.getStatus());
|
||||
} else {
|
||||
response.setGlobalStatus(GlobalStatus.Finished);
|
||||
}
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("check transaction status error,{}]", exx.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
163
server/src/main/java/io/seata/server/ParameterParser.java
Normal file
163
server/src/main/java/io/seata/server/ParameterParser.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.server.env.ContainerHelper;
|
||||
|
||||
import static io.seata.common.DefaultValues.SERVER_DEFAULT_PORT;
|
||||
import static io.seata.common.DefaultValues.SERVER_DEFAULT_STORE_MODE;
|
||||
import static io.seata.config.ConfigurationFactory.ENV_PROPERTY_KEY;
|
||||
|
||||
/**
|
||||
* The type Parameter parser.
|
||||
*
|
||||
* @author xingfudeshi @gmail.com
|
||||
*/
|
||||
public class ParameterParser {
|
||||
|
||||
private static final String PROGRAM_NAME
|
||||
= "sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for windows)";
|
||||
|
||||
@Parameter(names = "--help", help = true)
|
||||
private boolean help;
|
||||
@Parameter(names = {"--host", "-h"}, description = "The ip to register to registry center.", order = 1)
|
||||
private String host;
|
||||
@Parameter(names = {"--port", "-p"}, description = "The port to listen.", order = 2)
|
||||
private int port = SERVER_DEFAULT_PORT;
|
||||
@Parameter(names = {"--storeMode", "-m"}, description = "log store mode : file, db", order = 3)
|
||||
private String storeMode;
|
||||
@Parameter(names = {"--serverNode", "-n"}, description = "server node id, such as 1, 2, 3.it will be generated according to the snowflake by default", order = 4)
|
||||
private Long serverNode;
|
||||
@Parameter(names = {"--seataEnv", "-e"}, description = "The name used for multi-configuration isolation.",
|
||||
order = 5)
|
||||
private String seataEnv;
|
||||
|
||||
/**
|
||||
* Instantiates a new Parameter parser.
|
||||
*
|
||||
* @param args the args
|
||||
*/
|
||||
public ParameterParser(String[] args) {
|
||||
this.init(args);
|
||||
}
|
||||
|
||||
private void init(String[] args) {
|
||||
try {
|
||||
if (ContainerHelper.isRunningInContainer()) {
|
||||
this.seataEnv = ContainerHelper.getEnv();
|
||||
this.host = ContainerHelper.getHost();
|
||||
this.port = ContainerHelper.getPort();
|
||||
this.serverNode = ContainerHelper.getServerNode();
|
||||
this.storeMode = ContainerHelper.getStoreMode();
|
||||
} else {
|
||||
JCommander jCommander = JCommander.newBuilder().addObject(this).build();
|
||||
jCommander.parse(args);
|
||||
if (help) {
|
||||
jCommander.setProgramName(PROGRAM_NAME);
|
||||
jCommander.usage();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(seataEnv)) {
|
||||
System.setProperty(ENV_PROPERTY_KEY, seataEnv);
|
||||
}
|
||||
if (StringUtils.isBlank(storeMode)) {
|
||||
storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE,
|
||||
SERVER_DEFAULT_STORE_MODE);
|
||||
}
|
||||
} catch (ParameterException e) {
|
||||
printError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void printError(ParameterException e) {
|
||||
System.err.println("Option error " + e.getMessage());
|
||||
e.getJCommander().setProgramName(PROGRAM_NAME);
|
||||
e.usage();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets host.
|
||||
*
|
||||
* @return the host
|
||||
*/
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets port.
|
||||
*
|
||||
* @return the port
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets store mode.
|
||||
*
|
||||
* @return the store mode
|
||||
*/
|
||||
public String getStoreMode() {
|
||||
return storeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is help boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean isHelp() {
|
||||
return help;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets server node.
|
||||
*
|
||||
* @return the server node
|
||||
*/
|
||||
public Long getServerNode() {
|
||||
return serverNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets seata env
|
||||
*
|
||||
* @return the name used for multi-configuration isolation.
|
||||
*/
|
||||
public String getSeataEnv() {
|
||||
return seataEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up.
|
||||
*/
|
||||
public void cleanUp() {
|
||||
if (null != System.getProperty(ENV_PROPERTY_KEY)) {
|
||||
System.clearProperty(ENV_PROPERTY_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
106
server/src/main/java/io/seata/server/Server.java
Normal file
106
server/src/main/java/io/seata/server/Server.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import io.seata.common.XID;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.NetUtil;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.rpc.ShutdownHook;
|
||||
import io.seata.core.rpc.netty.NettyRemotingServer;
|
||||
import io.seata.core.rpc.netty.NettyServerConfig;
|
||||
import io.seata.server.coordinator.DefaultCoordinator;
|
||||
import io.seata.server.env.ContainerHelper;
|
||||
import io.seata.server.env.PortHelper;
|
||||
import io.seata.server.metrics.MetricsManager;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Server.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class Server {
|
||||
/**
|
||||
* The entry point of application.
|
||||
*
|
||||
* @param args the input arguments
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
// get port first, use to logback.xml
|
||||
int port = PortHelper.getPort(args);
|
||||
System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port));
|
||||
|
||||
// create logger
|
||||
final Logger logger = LoggerFactory.getLogger(Server.class);
|
||||
if (ContainerHelper.isRunningInContainer()) {
|
||||
logger.info("The server is running in container.");
|
||||
}
|
||||
|
||||
//initialize the parameter parser
|
||||
//Note that the parameter parser should always be the first line to execute.
|
||||
//Because, here we need to parse the parameters needed for startup.
|
||||
ParameterParser parameterParser = new ParameterParser(args);
|
||||
|
||||
//initialize the metrics
|
||||
MetricsManager.get().init();
|
||||
|
||||
System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode());
|
||||
|
||||
ThreadPoolExecutor workingThreads = new ThreadPoolExecutor(NettyServerConfig.getMinServerPoolSize(),
|
||||
NettyServerConfig.getMaxServerPoolSize(), NettyServerConfig.getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(NettyServerConfig.getMaxTaskQueueSize()),
|
||||
new NamedThreadFactory("ServerHandlerThread", NettyServerConfig.getMaxServerPoolSize()), new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
|
||||
NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads);
|
||||
//server port
|
||||
nettyRemotingServer.setListenPort(parameterParser.getPort());
|
||||
UUIDGenerator.init(parameterParser.getServerNode());
|
||||
//log store mode : file, db, redis
|
||||
SessionHolder.init(parameterParser.getStoreMode());
|
||||
|
||||
DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);
|
||||
coordinator.init();
|
||||
nettyRemotingServer.setHandler(coordinator);
|
||||
// register ShutdownHook
|
||||
ShutdownHook.getInstance().addDisposable(coordinator);
|
||||
ShutdownHook.getInstance().addDisposable(nettyRemotingServer);
|
||||
|
||||
//127.0.0.1 and 0.0.0.0 are not valid here.
|
||||
if (NetUtil.isValidIp(parameterParser.getHost(), false)) {
|
||||
XID.setIpAddress(parameterParser.getHost());
|
||||
} else {
|
||||
XID.setIpAddress(NetUtil.getLocalIp());
|
||||
}
|
||||
XID.setPort(nettyRemotingServer.getListenPort());
|
||||
|
||||
try {
|
||||
nettyRemotingServer.init();
|
||||
} catch (Throwable e) {
|
||||
logger.error("nettyServer init error:{}", e.getMessage(), e);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
51
server/src/main/java/io/seata/server/UUIDGenerator.java
Normal file
51
server/src/main/java/io/seata/server/UUIDGenerator.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import io.seata.common.util.IdWorker;
|
||||
|
||||
/**
|
||||
* The type Uuid generator.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class UUIDGenerator {
|
||||
|
||||
private static volatile IdWorker idWorker;
|
||||
|
||||
/**
|
||||
* generate UUID using snowflake algorithm
|
||||
* @return UUID
|
||||
*/
|
||||
public static long generateUUID() {
|
||||
if (idWorker == null) {
|
||||
synchronized (UUIDGenerator.class) {
|
||||
if (idWorker == null) {
|
||||
init(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return idWorker.nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* init IdWorker
|
||||
* @param serverNode the server node id, consider as machine id in snowflake
|
||||
*/
|
||||
public static void init(Long serverNode) {
|
||||
idWorker = new IdWorker(serverNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.seata.server.auth;
|
||||
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.protocol.RegisterRMRequest;
|
||||
import io.seata.core.protocol.RegisterTMRequest;
|
||||
import io.seata.core.rpc.RegisterCheckAuthHandler;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_SERVER_ENABLE_CHECK_AUTH;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
public abstract class AbstractCheckAuthHandler implements RegisterCheckAuthHandler {
|
||||
|
||||
private static final Boolean ENABLE_CHECK_AUTH = ConfigurationFactory.getInstance().getBoolean(
|
||||
ConfigurationKeys.SERVER_ENABLE_CHECK_AUTH, DEFAULT_SERVER_ENABLE_CHECK_AUTH);
|
||||
|
||||
@Override
|
||||
public boolean regTransactionManagerCheckAuth(RegisterTMRequest request) {
|
||||
if (!ENABLE_CHECK_AUTH) {
|
||||
return true;
|
||||
}
|
||||
return doRegTransactionManagerCheck(request);
|
||||
}
|
||||
|
||||
public abstract boolean doRegTransactionManagerCheck(RegisterTMRequest request);
|
||||
|
||||
@Override
|
||||
public boolean regResourceManagerCheckAuth(RegisterRMRequest request) {
|
||||
if (!ENABLE_CHECK_AUTH) {
|
||||
return true;
|
||||
}
|
||||
return doRegResourceManagerCheck(request);
|
||||
}
|
||||
|
||||
public abstract boolean doRegResourceManagerCheck(RegisterRMRequest request);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.seata.server.auth;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.core.protocol.RegisterRMRequest;
|
||||
import io.seata.core.protocol.RegisterTMRequest;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
@LoadLevel(name = "defaultCheckAuthHandler", order = 100)
|
||||
public class DefaultCheckAuthHandler extends AbstractCheckAuthHandler {
|
||||
|
||||
@Override
|
||||
public boolean doRegTransactionManagerCheck(RegisterTMRequest request) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doRegResourceManagerCheck(RegisterRMRequest request) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.BranchTransactionException;
|
||||
import io.seata.core.exception.GlobalTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.exception.TransactionExceptionCode;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.protocol.transaction.BranchCommitRequest;
|
||||
import io.seata.core.protocol.transaction.BranchCommitResponse;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackResponse;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.lock.LockManager;
|
||||
import io.seata.server.lock.LockerManagerFactory;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHelper;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static io.seata.core.exception.TransactionExceptionCode.BranchTransactionNotExist;
|
||||
import static io.seata.core.exception.TransactionExceptionCode.FailedToAddBranch;
|
||||
import static io.seata.core.exception.TransactionExceptionCode.GlobalTransactionNotActive;
|
||||
import static io.seata.core.exception.TransactionExceptionCode.GlobalTransactionStatusInvalid;
|
||||
import static io.seata.core.exception.TransactionExceptionCode.FailedToSendBranchCommitRequest;
|
||||
import static io.seata.core.exception.TransactionExceptionCode.FailedToSendBranchRollbackRequest;
|
||||
|
||||
/**
|
||||
* The type abstract core.
|
||||
*
|
||||
* @author ph3636
|
||||
*/
|
||||
public abstract class AbstractCore implements Core {
|
||||
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractCore.class);
|
||||
|
||||
protected LockManager lockManager = LockerManagerFactory.getLockManager();
|
||||
|
||||
protected RemotingServer remotingServer;
|
||||
|
||||
public AbstractCore(RemotingServer remotingServer) {
|
||||
if (remotingServer == null) {
|
||||
throw new IllegalArgumentException("remotingServer must be not null");
|
||||
}
|
||||
this.remotingServer = remotingServer;
|
||||
}
|
||||
|
||||
public abstract BranchType getHandleBranchType();
|
||||
|
||||
@Override
|
||||
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
|
||||
String applicationData, String lockKeys) throws TransactionException {
|
||||
GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
|
||||
return SessionHolder.lockAndExecute(globalSession, () -> {
|
||||
globalSessionStatusCheck(globalSession);
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
|
||||
applicationData, lockKeys, clientId);
|
||||
MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
|
||||
branchSessionLock(globalSession, branchSession);
|
||||
try {
|
||||
globalSession.addBranch(branchSession);
|
||||
} catch (RuntimeException ex) {
|
||||
branchSessionUnlock(branchSession);
|
||||
throw new BranchTransactionException(FailedToAddBranch, String
|
||||
.format("Failed to store branch xid = %s branchId = %s", globalSession.getXid(),
|
||||
branchSession.getBranchId()), ex);
|
||||
}
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Register branch successfully, xid = {}, branchId = {}, resourceId = {} ,lockKeys = {}",
|
||||
globalSession.getXid(), branchSession.getBranchId(), resourceId, lockKeys);
|
||||
}
|
||||
return branchSession.getBranchId();
|
||||
});
|
||||
}
|
||||
|
||||
protected void globalSessionStatusCheck(GlobalSession globalSession) throws GlobalTransactionException {
|
||||
if (!globalSession.isActive()) {
|
||||
throw new GlobalTransactionException(GlobalTransactionNotActive, String.format(
|
||||
"Could not register branch into global session xid = %s status = %s, cause by globalSession not active",
|
||||
globalSession.getXid(), globalSession.getStatus()));
|
||||
}
|
||||
if (globalSession.getStatus() != GlobalStatus.Begin) {
|
||||
throw new GlobalTransactionException(GlobalTransactionStatusInvalid, String
|
||||
.format("Could not register branch into global session xid = %s status = %s while expecting %s",
|
||||
globalSession.getXid(), globalSession.getStatus(), GlobalStatus.Begin));
|
||||
}
|
||||
}
|
||||
|
||||
protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
protected void branchSessionUnlock(BranchSession branchSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
private GlobalSession assertGlobalSessionNotNull(String xid, boolean withBranchSessions)
|
||||
throws TransactionException {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid, withBranchSessions);
|
||||
if (globalSession == null) {
|
||||
throw new GlobalTransactionException(TransactionExceptionCode.GlobalTransactionNotExist,
|
||||
String.format("Could not found global transaction xid = %s, may be has finished.", xid));
|
||||
}
|
||||
return globalSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status,
|
||||
String applicationData) throws TransactionException {
|
||||
GlobalSession globalSession = assertGlobalSessionNotNull(xid, true);
|
||||
BranchSession branchSession = globalSession.getBranch(branchId);
|
||||
if (branchSession == null) {
|
||||
throw new BranchTransactionException(BranchTransactionNotExist,
|
||||
String.format("Could not found branch session xid = %s branchId = %s", xid, branchId));
|
||||
}
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
globalSession.changeBranchStatus(branchSession, status);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Report branch status successfully, xid = {}, branchId = {}", globalSession.getXid(),
|
||||
branchSession.getBranchId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys)
|
||||
throws TransactionException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
try {
|
||||
BranchCommitRequest request = new BranchCommitRequest();
|
||||
request.setXid(branchSession.getXid());
|
||||
request.setBranchId(branchSession.getBranchId());
|
||||
request.setResourceId(branchSession.getResourceId());
|
||||
request.setApplicationData(branchSession.getApplicationData());
|
||||
request.setBranchType(branchSession.getBranchType());
|
||||
return branchCommitSend(request, globalSession, branchSession);
|
||||
} catch (IOException | TimeoutException e) {
|
||||
throw new BranchTransactionException(FailedToSendBranchCommitRequest,
|
||||
String.format("Send branch commit failed, xid = %s branchId = %s", branchSession.getXid(),
|
||||
branchSession.getBranchId()), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected BranchStatus branchCommitSend(BranchCommitRequest request, GlobalSession globalSession,
|
||||
BranchSession branchSession) throws IOException, TimeoutException {
|
||||
BranchCommitResponse response = (BranchCommitResponse) remotingServer.sendSyncRequest(
|
||||
branchSession.getResourceId(), branchSession.getClientId(), request);
|
||||
return response.getBranchStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
try {
|
||||
BranchRollbackRequest request = new BranchRollbackRequest();
|
||||
request.setXid(branchSession.getXid());
|
||||
request.setBranchId(branchSession.getBranchId());
|
||||
request.setResourceId(branchSession.getResourceId());
|
||||
request.setApplicationData(branchSession.getApplicationData());
|
||||
request.setBranchType(branchSession.getBranchType());
|
||||
return branchRollbackSend(request, globalSession, branchSession);
|
||||
} catch (IOException | TimeoutException e) {
|
||||
throw new BranchTransactionException(FailedToSendBranchRollbackRequest,
|
||||
String.format("Send branch rollback failed, xid = %s branchId = %s",
|
||||
branchSession.getXid(), branchSession.getBranchId()), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected BranchStatus branchRollbackSend(BranchRollbackRequest request, GlobalSession globalSession,
|
||||
BranchSession branchSession) throws IOException, TimeoutException {
|
||||
BranchRollbackResponse response = (BranchRollbackResponse) remotingServer.sendSyncRequest(
|
||||
branchSession.getResourceId(), branchSession.getClientId(), request);
|
||||
return response.getBranchStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus(String xid) throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
|
||||
}
|
||||
}
|
||||
59
server/src/main/java/io/seata/server/coordinator/Core.java
Normal file
59
server/src/main/java/io/seata/server/coordinator/Core.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
/**
|
||||
* The interface Core.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface Core extends TransactionCoordinatorInbound, TransactionCoordinatorOutbound {
|
||||
|
||||
/**
|
||||
* Do global commit.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param retrying the retrying
|
||||
* @return is global commit.
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Do global rollback.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param retrying the retrying
|
||||
* @return is global rollback.
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Do global report.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param xid Transaction id.
|
||||
* @param param the global status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus param) throws TransactionException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import io.netty.channel.Channel;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.DurationUtil;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.event.EventBus;
|
||||
import io.seata.core.event.GlobalTransactionEvent;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.protocol.AbstractMessage;
|
||||
import io.seata.core.protocol.AbstractResultMessage;
|
||||
import io.seata.core.protocol.transaction.AbstractTransactionRequestToTC;
|
||||
import io.seata.core.protocol.transaction.AbstractTransactionResponse;
|
||||
import io.seata.core.protocol.transaction.BranchRegisterRequest;
|
||||
import io.seata.core.protocol.transaction.BranchRegisterResponse;
|
||||
import io.seata.core.protocol.transaction.BranchReportRequest;
|
||||
import io.seata.core.protocol.transaction.BranchReportResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalLockQueryRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalLockQueryResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalReportRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalReportResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalStatusResponse;
|
||||
import io.seata.core.protocol.transaction.UndoLogDeleteRequest;
|
||||
import io.seata.core.rpc.Disposable;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.core.rpc.RpcContext;
|
||||
import io.seata.core.rpc.TransactionMessageHandler;
|
||||
import io.seata.core.rpc.netty.ChannelManager;
|
||||
import io.seata.core.rpc.netty.NettyRemotingServer;
|
||||
import io.seata.server.AbstractTCInboundHandler;
|
||||
import io.seata.server.event.EventBusManager;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHelper;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
/**
|
||||
* The type Default coordinator.
|
||||
*/
|
||||
public class DefaultCoordinator extends AbstractTCInboundHandler implements TransactionMessageHandler, Disposable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCoordinator.class);
|
||||
|
||||
private static final int TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS = 5000;
|
||||
|
||||
/**
|
||||
* The constant COMMITTING_RETRY_PERIOD.
|
||||
*/
|
||||
protected static final long COMMITTING_RETRY_PERIOD = CONFIG.getLong(ConfigurationKeys.COMMITING_RETRY_PERIOD,
|
||||
1000L);
|
||||
|
||||
/**
|
||||
* The constant ASYNC_COMMITTING_RETRY_PERIOD.
|
||||
*/
|
||||
protected static final long ASYNC_COMMITTING_RETRY_PERIOD = CONFIG.getLong(
|
||||
ConfigurationKeys.ASYN_COMMITING_RETRY_PERIOD, 1000L);
|
||||
|
||||
/**
|
||||
* The constant ROLLBACKING_RETRY_PERIOD.
|
||||
*/
|
||||
protected static final long ROLLBACKING_RETRY_PERIOD = CONFIG.getLong(ConfigurationKeys.ROLLBACKING_RETRY_PERIOD,
|
||||
1000L);
|
||||
|
||||
/**
|
||||
* The constant TIMEOUT_RETRY_PERIOD.
|
||||
*/
|
||||
protected static final long TIMEOUT_RETRY_PERIOD = CONFIG.getLong(ConfigurationKeys.TIMEOUT_RETRY_PERIOD, 1000L);
|
||||
|
||||
/**
|
||||
* The Transaction undo log delete period.
|
||||
*/
|
||||
protected static final long UNDO_LOG_DELETE_PERIOD = CONFIG.getLong(
|
||||
ConfigurationKeys.TRANSACTION_UNDO_LOG_DELETE_PERIOD, 24 * 60 * 60 * 1000);
|
||||
|
||||
/**
|
||||
* The Transaction undo log delay delete period
|
||||
*/
|
||||
protected static final long UNDO_LOG_DELAY_DELETE_PERIOD = 3 * 60 * 1000;
|
||||
|
||||
private static final int ALWAYS_RETRY_BOUNDARY = 0;
|
||||
|
||||
private static final Duration MAX_COMMIT_RETRY_TIMEOUT = ConfigurationFactory.getInstance().getDuration(
|
||||
ConfigurationKeys.MAX_COMMIT_RETRY_TIMEOUT, DurationUtil.DEFAULT_DURATION, 100);
|
||||
|
||||
private static final Duration MAX_ROLLBACK_RETRY_TIMEOUT = ConfigurationFactory.getInstance().getDuration(
|
||||
ConfigurationKeys.MAX_ROLLBACK_RETRY_TIMEOUT, DurationUtil.DEFAULT_DURATION, 100);
|
||||
|
||||
private static final boolean ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE = ConfigurationFactory.getInstance().getBoolean(
|
||||
ConfigurationKeys.ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE, false);
|
||||
|
||||
private ScheduledThreadPoolExecutor retryRollbacking = new ScheduledThreadPoolExecutor(1,
|
||||
new NamedThreadFactory("RetryRollbacking", 1));
|
||||
|
||||
private ScheduledThreadPoolExecutor retryCommitting = new ScheduledThreadPoolExecutor(1,
|
||||
new NamedThreadFactory("RetryCommitting", 1));
|
||||
|
||||
private ScheduledThreadPoolExecutor asyncCommitting = new ScheduledThreadPoolExecutor(1,
|
||||
new NamedThreadFactory("AsyncCommitting", 1));
|
||||
|
||||
private ScheduledThreadPoolExecutor timeoutCheck = new ScheduledThreadPoolExecutor(1,
|
||||
new NamedThreadFactory("TxTimeoutCheck", 1));
|
||||
|
||||
private ScheduledThreadPoolExecutor undoLogDelete = new ScheduledThreadPoolExecutor(1,
|
||||
new NamedThreadFactory("UndoLogDelete", 1));
|
||||
|
||||
private RemotingServer remotingServer;
|
||||
|
||||
private DefaultCore core;
|
||||
|
||||
private EventBus eventBus = EventBusManager.get();
|
||||
|
||||
/**
|
||||
* Instantiates a new Default coordinator.
|
||||
*
|
||||
* @param remotingServer the remoting server
|
||||
*/
|
||||
public DefaultCoordinator(RemotingServer remotingServer) {
|
||||
this.remotingServer = remotingServer;
|
||||
this.core = new DefaultCore(remotingServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),
|
||||
request.getTransactionName(), request.getTimeout()));
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}",
|
||||
rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setGlobalStatus(core.commit(request.getXid()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response,
|
||||
RpcContext rpcContext) throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setGlobalStatus(core.rollback(request.getXid()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGlobalStatus(GlobalStatusRequest request, GlobalStatusResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setGlobalStatus(core.getStatus(request.getXid()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGlobalReport(GlobalReportRequest request, GlobalReportResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setGlobalStatus(core.globalReport(request.getXid(), request.getGlobalStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response,
|
||||
RpcContext rpcContext) throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setBranchId(
|
||||
core.branchRegister(request.getBranchType(), request.getResourceId(), rpcContext.getClientId(),
|
||||
request.getXid(), request.getApplicationData(), request.getLockKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBranchReport(BranchReportRequest request, BranchReportResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(request.getBranchId()));
|
||||
core.branchReport(request.getBranchType(), request.getXid(), request.getBranchId(), request.getStatus(),
|
||||
request.getApplicationData());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLockCheck(GlobalLockQueryRequest request, GlobalLockQueryResponse response, RpcContext rpcContext)
|
||||
throws TransactionException {
|
||||
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
|
||||
response.setLockable(
|
||||
core.lockQuery(request.getBranchType(), request.getResourceId(), request.getXid(), request.getLockKey()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout check.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
protected void timeoutCheck() throws TransactionException {
|
||||
Collection<GlobalSession> allSessions = SessionHolder.getRootSessionManager().allSessions();
|
||||
if (CollectionUtils.isEmpty(allSessions)) {
|
||||
return;
|
||||
}
|
||||
if (allSessions.size() > 0 && LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Global transaction timeout check begin, size: {}", allSessions.size());
|
||||
}
|
||||
SessionHelper.forEach(allSessions, globalSession -> {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
globalSession.getXid() + " " + globalSession.getStatus() + " " + globalSession.getBeginTime() + " "
|
||||
+ globalSession.getTimeout());
|
||||
}
|
||||
boolean shouldTimeout = SessionHolder.lockAndExecute(globalSession, () -> {
|
||||
if (globalSession.getStatus() != GlobalStatus.Begin || !globalSession.isTimeout()) {
|
||||
return false;
|
||||
}
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
globalSession.close();
|
||||
globalSession.changeStatus(GlobalStatus.TimeoutRollbacking);
|
||||
|
||||
// transaction timeout and start rollbacking event
|
||||
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(),
|
||||
GlobalTransactionEvent.ROLE_TC,
|
||||
globalSession.getTransactionName(),
|
||||
globalSession.getApplicationId(),
|
||||
globalSession.getTransactionServiceGroup(),
|
||||
globalSession.getBeginTime(), null, globalSession.getStatus()));
|
||||
|
||||
return true;
|
||||
});
|
||||
if (!shouldTimeout) {
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Global transaction[{}] is timeout and will be rollback.", globalSession.getXid());
|
||||
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager());
|
||||
SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(globalSession);
|
||||
});
|
||||
if (allSessions.size() > 0 && LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Global transaction timeout check end. ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle retry rollbacking.
|
||||
*/
|
||||
protected void handleRetryRollbacking() {
|
||||
Collection<GlobalSession> rollbackingSessions = SessionHolder.getRetryRollbackingSessionManager().allSessions();
|
||||
if (CollectionUtils.isEmpty(rollbackingSessions)) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
SessionHelper.forEach(rollbackingSessions, rollbackingSession -> {
|
||||
try {
|
||||
// prevent repeated rollback
|
||||
if (rollbackingSession.getStatus().equals(GlobalStatus.Rollbacking) && !rollbackingSession.isDeadSession()) {
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
if (isRetryTimeout(now, MAX_ROLLBACK_RETRY_TIMEOUT.toMillis(), rollbackingSession.getBeginTime())) {
|
||||
if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE) {
|
||||
rollbackingSession.clean();
|
||||
}
|
||||
/**
|
||||
* Prevent thread safety issues
|
||||
*/
|
||||
SessionHolder.getRetryRollbackingSessionManager().removeGlobalSession(rollbackingSession);
|
||||
LOGGER.info("Global transaction rollback retry timeout and has removed [{}]", rollbackingSession.getXid());
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
rollbackingSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
core.doGlobalRollback(rollbackingSession, true);
|
||||
} catch (TransactionException ex) {
|
||||
LOGGER.info("Failed to retry rollbacking [{}] {} {}", rollbackingSession.getXid(), ex.getCode(), ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle retry committing.
|
||||
*/
|
||||
protected void handleRetryCommitting() {
|
||||
Collection<GlobalSession> committingSessions = SessionHolder.getRetryCommittingSessionManager().allSessions();
|
||||
if (CollectionUtils.isEmpty(committingSessions)) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
SessionHelper.forEach(committingSessions, committingSession -> {
|
||||
try {
|
||||
// prevent repeated commit
|
||||
if (committingSession.getStatus().equals(GlobalStatus.Committing) && !committingSession.isDeadSession()) {
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
if (isRetryTimeout(now, MAX_COMMIT_RETRY_TIMEOUT.toMillis(), committingSession.getBeginTime())) {
|
||||
/**
|
||||
* Prevent thread safety issues
|
||||
*/
|
||||
SessionHolder.getRetryCommittingSessionManager().removeGlobalSession(committingSession);
|
||||
LOGGER.error("Global transaction commit retry timeout and has removed [{}]", committingSession.getXid());
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
committingSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
core.doGlobalCommit(committingSession, true);
|
||||
} catch (TransactionException ex) {
|
||||
LOGGER.info("Failed to retry committing [{}] {} {}", committingSession.getXid(), ex.getCode(), ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isRetryTimeout(long now, long timeout, long beginTime) {
|
||||
return timeout >= ALWAYS_RETRY_BOUNDARY && now - beginTime > timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle async committing.
|
||||
*/
|
||||
protected void handleAsyncCommitting() {
|
||||
Collection<GlobalSession> asyncCommittingSessions = SessionHolder.getAsyncCommittingSessionManager()
|
||||
.allSessions();
|
||||
if (CollectionUtils.isEmpty(asyncCommittingSessions)) {
|
||||
return;
|
||||
}
|
||||
SessionHelper.forEach(asyncCommittingSessions, asyncCommittingSession -> {
|
||||
try {
|
||||
// Instruction reordering in DefaultCore#asyncCommit may cause this situation
|
||||
if (GlobalStatus.AsyncCommitting != asyncCommittingSession.getStatus()) {
|
||||
//The function of this 'return' is 'continue'.
|
||||
return;
|
||||
}
|
||||
asyncCommittingSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
core.doGlobalCommit(asyncCommittingSession, true);
|
||||
} catch (TransactionException ex) {
|
||||
LOGGER.error("Failed to async committing [{}] {} {}", asyncCommittingSession.getXid(), ex.getCode(), ex.getMessage(), ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo log delete.
|
||||
*/
|
||||
protected void undoLogDelete() {
|
||||
Map<String, Channel> rmChannels = ChannelManager.getRmChannels();
|
||||
if (rmChannels == null || rmChannels.isEmpty()) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("no active rm channels to delete undo log");
|
||||
}
|
||||
return;
|
||||
}
|
||||
short saveDays = CONFIG.getShort(ConfigurationKeys.TRANSACTION_UNDO_LOG_SAVE_DAYS,
|
||||
UndoLogDeleteRequest.DEFAULT_SAVE_DAYS);
|
||||
for (Map.Entry<String, Channel> channelEntry : rmChannels.entrySet()) {
|
||||
String resourceId = channelEntry.getKey();
|
||||
UndoLogDeleteRequest deleteRequest = new UndoLogDeleteRequest();
|
||||
deleteRequest.setResourceId(resourceId);
|
||||
deleteRequest.setSaveDays(saveDays > 0 ? saveDays : UndoLogDeleteRequest.DEFAULT_SAVE_DAYS);
|
||||
try {
|
||||
remotingServer.sendAsyncRequest(channelEntry.getValue(), deleteRequest);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to async delete undo log resourceId = {}, exception: {}", resourceId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public void init() {
|
||||
retryRollbacking.scheduleAtFixedRate(() -> {
|
||||
boolean lock = SessionHolder.retryRollbackingLock();
|
||||
if (lock) {
|
||||
try {
|
||||
handleRetryRollbacking();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("Exception retry rollbacking ... ", e);
|
||||
} finally {
|
||||
SessionHolder.unRetryRollbackingLock();
|
||||
}
|
||||
}
|
||||
}, 0, ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
|
||||
|
||||
retryCommitting.scheduleAtFixedRate(() -> {
|
||||
boolean lock = SessionHolder.retryCommittingLock();
|
||||
if (lock) {
|
||||
try {
|
||||
handleRetryCommitting();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("Exception retry committing ... ", e);
|
||||
} finally {
|
||||
SessionHolder.unRetryCommittingLock();
|
||||
}
|
||||
}
|
||||
}, 0, COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
|
||||
|
||||
asyncCommitting.scheduleAtFixedRate(() -> {
|
||||
boolean lock = SessionHolder.asyncCommittingLock();
|
||||
if (lock) {
|
||||
try {
|
||||
handleAsyncCommitting();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("Exception async committing ... ", e);
|
||||
} finally {
|
||||
SessionHolder.unAsyncCommittingLock();
|
||||
}
|
||||
}
|
||||
}, 0, ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);
|
||||
|
||||
timeoutCheck.scheduleAtFixedRate(() -> {
|
||||
boolean lock = SessionHolder.txTimeoutCheckLock();
|
||||
if (lock) {
|
||||
try {
|
||||
timeoutCheck();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("Exception timeout checking ... ", e);
|
||||
} finally {
|
||||
SessionHolder.unTxTimeoutCheckLock();
|
||||
}
|
||||
}
|
||||
}, 0, TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS);
|
||||
|
||||
undoLogDelete.scheduleAtFixedRate(() -> {
|
||||
boolean lock = SessionHolder.undoLogDeleteLock();
|
||||
if (lock) {
|
||||
try {
|
||||
undoLogDelete();
|
||||
} catch (Exception e) {
|
||||
LOGGER.info("Exception undoLog deleting ... ", e);
|
||||
} finally {
|
||||
SessionHolder.unUndoLogDeleteLock();
|
||||
}
|
||||
}
|
||||
}, UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) {
|
||||
if (!(request instanceof AbstractTransactionRequestToTC)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request;
|
||||
transactionRequest.setTCInboundHandler(this);
|
||||
|
||||
return transactionRequest.handle(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(AbstractResultMessage response, RpcContext context) {
|
||||
if (!(response instanceof AbstractTransactionResponse)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 1. first shutdown timed task
|
||||
retryRollbacking.shutdown();
|
||||
retryCommitting.shutdown();
|
||||
asyncCommitting.shutdown();
|
||||
timeoutCheck.shutdown();
|
||||
try {
|
||||
retryRollbacking.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS);
|
||||
retryCommitting.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS);
|
||||
asyncCommitting.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS);
|
||||
timeoutCheck.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException ignore) {
|
||||
|
||||
}
|
||||
// 2. second close netty flow
|
||||
if (remotingServer instanceof NettyRemotingServer) {
|
||||
((NettyRemotingServer) remotingServer).destroy();
|
||||
}
|
||||
// 3. last destroy SessionHolder
|
||||
SessionHolder.destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.seata.common.exception.NotSupportYetException;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.event.EventBus;
|
||||
import io.seata.core.event.GlobalTransactionEvent;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.logger.StackTraceLogger;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.event.EventBusManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHelper;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static io.seata.server.session.BranchSessionHandler.CONTINUE;
|
||||
|
||||
/**
|
||||
* The type Default core.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class DefaultCore implements Core {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCore.class);
|
||||
|
||||
private EventBus eventBus = EventBusManager.get();
|
||||
|
||||
private static Map<BranchType, AbstractCore> coreMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* get the Default core.
|
||||
*
|
||||
* @param remotingServer the remoting server
|
||||
*/
|
||||
public DefaultCore(RemotingServer remotingServer) {
|
||||
List<AbstractCore> allCore = EnhancedServiceLoader.loadAll(AbstractCore.class,
|
||||
new Class[]{RemotingServer.class}, new Object[]{remotingServer});
|
||||
if (CollectionUtils.isNotEmpty(allCore)) {
|
||||
for (AbstractCore core : allCore) {
|
||||
coreMap.put(core.getHandleBranchType(), core);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get core
|
||||
*
|
||||
* @param branchType the branchType
|
||||
* @return the core
|
||||
*/
|
||||
public AbstractCore getCore(BranchType branchType) {
|
||||
AbstractCore core = coreMap.get(branchType);
|
||||
if (core == null) {
|
||||
throw new NotSupportYetException("unsupported type:" + branchType.name());
|
||||
}
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* only for mock
|
||||
*
|
||||
* @param branchType the branchType
|
||||
* @param core the core
|
||||
*/
|
||||
public void mockCore(BranchType branchType, AbstractCore core) {
|
||||
coreMap.put(branchType, core);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
|
||||
String applicationData, String lockKeys) throws TransactionException {
|
||||
return getCore(branchType).branchRegister(branchType, resourceId, clientId, xid,
|
||||
applicationData, lockKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status,
|
||||
String applicationData) throws TransactionException {
|
||||
getCore(branchType).branchReport(branchType, xid, branchId, status, applicationData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys)
|
||||
throws TransactionException {
|
||||
return getCore(branchType).lockQuery(branchType, resourceId, xid, lockKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
return getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
return getCore(branchSession.getBranchType()).branchRollback(globalSession, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
|
||||
throws TransactionException {
|
||||
GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
|
||||
timeout);
|
||||
MDC.put(RootContext.MDC_KEY_XID, session.getXid());
|
||||
session.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
|
||||
session.begin();
|
||||
|
||||
// transaction start event
|
||||
eventBus.post(new GlobalTransactionEvent(session.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
|
||||
session.getTransactionName(), applicationId, transactionServiceGroup, session.getBeginTime(), null, session.getStatus()));
|
||||
|
||||
return session.getXid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus commit(String xid) throws TransactionException {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
if (globalSession == null) {
|
||||
return GlobalStatus.Finished;
|
||||
}
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
// just lock changeStatus
|
||||
|
||||
boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {
|
||||
// Highlight: Firstly, close the session, then no more branch can be registered.
|
||||
globalSession.closeAndClean();
|
||||
if (globalSession.getStatus() == GlobalStatus.Begin) {
|
||||
if (globalSession.canBeCommittedAsync()) {
|
||||
globalSession.asyncCommit();
|
||||
return false;
|
||||
} else {
|
||||
globalSession.changeStatus(GlobalStatus.Committing);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (shouldCommit) {
|
||||
boolean success = doGlobalCommit(globalSession, false);
|
||||
//If successful and all remaining branches can be committed asynchronously, do async commit.
|
||||
if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {
|
||||
globalSession.asyncCommit();
|
||||
return GlobalStatus.Committed;
|
||||
} else {
|
||||
return globalSession.getStatus();
|
||||
}
|
||||
} else {
|
||||
return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
boolean success = true;
|
||||
// start committing event
|
||||
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
|
||||
globalSession.getTransactionName(), globalSession.getApplicationId(), globalSession.getTransactionServiceGroup(),
|
||||
globalSession.getBeginTime(), null, globalSession.getStatus()));
|
||||
|
||||
if (globalSession.isSaga()) {
|
||||
success = getCore(BranchType.SAGA).doGlobalCommit(globalSession, retrying);
|
||||
} else {
|
||||
Boolean result = SessionHelper.forEach(globalSession.getSortedBranches(), branchSession -> {
|
||||
// if not retrying, skip the canBeCommittedAsync branches
|
||||
if (!retrying && branchSession.canBeCommittedAsync()) {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
BranchStatus currentStatus = branchSession.getStatus();
|
||||
if (currentStatus == BranchStatus.PhaseOne_Failed) {
|
||||
globalSession.removeBranch(branchSession);
|
||||
return CONTINUE;
|
||||
}
|
||||
try {
|
||||
BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession);
|
||||
|
||||
switch (branchStatus) {
|
||||
case PhaseTwo_Committed:
|
||||
globalSession.removeBranch(branchSession);
|
||||
return CONTINUE;
|
||||
case PhaseTwo_CommitFailed_Unretryable:
|
||||
if (globalSession.canBeCommittedAsync()) {
|
||||
LOGGER.error(
|
||||
"Committing branch transaction[{}], status: PhaseTwo_CommitFailed_Unretryable, please check the business log.", branchSession.getBranchId());
|
||||
return CONTINUE;
|
||||
} else {
|
||||
SessionHelper.endCommitFailed(globalSession);
|
||||
LOGGER.error("Committing global transaction[{}] finally failed, caused by branch transaction[{}] commit failed.", globalSession.getXid(), branchSession.getBranchId());
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryCommit();
|
||||
return false;
|
||||
}
|
||||
if (globalSession.canBeCommittedAsync()) {
|
||||
LOGGER.error("Committing branch transaction[{}], status:{} and will retry later",
|
||||
branchSession.getBranchId(), branchStatus);
|
||||
return CONTINUE;
|
||||
} else {
|
||||
LOGGER.error(
|
||||
"Committing global transaction[{}] failed, caused by branch transaction[{}] commit failed, will retry later.", globalSession.getXid(), branchSession.getBranchId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
StackTraceLogger.error(LOGGER, ex, "Committing branch transaction exception: {}",
|
||||
new String[] {branchSession.toString()});
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryCommit();
|
||||
throw new TransactionException(ex);
|
||||
}
|
||||
}
|
||||
return CONTINUE;
|
||||
});
|
||||
// Return if the result is not null
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
//If has branch and not all remaining branches can be committed asynchronously,
|
||||
//do print log and return false
|
||||
if (globalSession.hasBranch() && !globalSession.canBeCommittedAsync()) {
|
||||
LOGGER.info("Committing global transaction is NOT done, xid = {}.", globalSession.getXid());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//If success and there is no branch, end the global transaction.
|
||||
if (success && globalSession.getBranchSessions().isEmpty()) {
|
||||
SessionHelper.endCommitted(globalSession);
|
||||
|
||||
// committed event
|
||||
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC,
|
||||
globalSession.getTransactionName(), globalSession.getApplicationId(), globalSession.getTransactionServiceGroup(),
|
||||
globalSession.getBeginTime(), System.currentTimeMillis(), globalSession.getStatus()));
|
||||
|
||||
LOGGER.info("Committing global transaction is successfully done, xid = {}.", globalSession.getXid());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus rollback(String xid) throws TransactionException {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
if (globalSession == null) {
|
||||
return GlobalStatus.Finished;
|
||||
}
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
// just lock changeStatus
|
||||
boolean shouldRollBack = SessionHolder.lockAndExecute(globalSession, () -> {
|
||||
globalSession.close(); // Highlight: Firstly, close the session, then no more branch can be registered.
|
||||
if (globalSession.getStatus() == GlobalStatus.Begin) {
|
||||
globalSession.changeStatus(GlobalStatus.Rollbacking);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (!shouldRollBack) {
|
||||
return globalSession.getStatus();
|
||||
}
|
||||
|
||||
doGlobalRollback(globalSession, false);
|
||||
return globalSession.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
boolean success = true;
|
||||
// start rollback event
|
||||
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(),
|
||||
GlobalTransactionEvent.ROLE_TC, globalSession.getTransactionName(),
|
||||
globalSession.getApplicationId(),
|
||||
globalSession.getTransactionServiceGroup(), globalSession.getBeginTime(),
|
||||
null, globalSession.getStatus()));
|
||||
|
||||
if (globalSession.isSaga()) {
|
||||
success = getCore(BranchType.SAGA).doGlobalRollback(globalSession, retrying);
|
||||
} else {
|
||||
Boolean result = SessionHelper.forEach(globalSession.getReverseSortedBranches(), branchSession -> {
|
||||
BranchStatus currentBranchStatus = branchSession.getStatus();
|
||||
if (currentBranchStatus == BranchStatus.PhaseOne_Failed) {
|
||||
globalSession.removeBranch(branchSession);
|
||||
return CONTINUE;
|
||||
}
|
||||
try {
|
||||
BranchStatus branchStatus = branchRollback(globalSession, branchSession);
|
||||
switch (branchStatus) {
|
||||
case PhaseTwo_Rollbacked:
|
||||
globalSession.removeBranch(branchSession);
|
||||
LOGGER.info("Rollback branch transaction successfully, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
|
||||
return CONTINUE;
|
||||
case PhaseTwo_RollbackFailed_Unretryable:
|
||||
SessionHelper.endRollbackFailed(globalSession);
|
||||
LOGGER.info("Rollback branch transaction fail and stop retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
|
||||
return false;
|
||||
default:
|
||||
LOGGER.info("Rollback branch transaction fail and will retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryRollback();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
StackTraceLogger.error(LOGGER, ex,
|
||||
"Rollback branch transaction exception, xid = {} branchId = {} exception = {}",
|
||||
new String[] {globalSession.getXid(), String.valueOf(branchSession.getBranchId()), ex.getMessage()});
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryRollback();
|
||||
}
|
||||
throw new TransactionException(ex);
|
||||
}
|
||||
});
|
||||
// Return if the result is not null
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// In db mode, there is a problem of inconsistent data in multiple copies, resulting in new branch
|
||||
// transaction registration when rolling back.
|
||||
// 1. New branch transaction and rollback branch transaction have no data association
|
||||
// 2. New branch transaction has data association with rollback branch transaction
|
||||
// The second query can solve the first problem, and if it is the second problem, it may cause a rollback
|
||||
// failure due to data changes.
|
||||
GlobalSession globalSessionTwice = SessionHolder.findGlobalSession(globalSession.getXid());
|
||||
if (globalSessionTwice != null && globalSessionTwice.hasBranch()) {
|
||||
LOGGER.info("Rollbacking global transaction is NOT done, xid = {}.", globalSession.getXid());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
SessionHelper.endRollbacked(globalSession);
|
||||
|
||||
// rollbacked event
|
||||
eventBus.post(new GlobalTransactionEvent(globalSession.getTransactionId(),
|
||||
GlobalTransactionEvent.ROLE_TC, globalSession.getTransactionName(),
|
||||
globalSession.getApplicationId(),
|
||||
globalSession.getTransactionServiceGroup(),
|
||||
globalSession.getBeginTime(), System.currentTimeMillis(),
|
||||
globalSession.getStatus()));
|
||||
|
||||
LOGGER.info("Rollback global transaction successfully, xid = {}.", globalSession.getXid());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus getStatus(String xid) throws TransactionException {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid, false);
|
||||
if (globalSession == null) {
|
||||
return GlobalStatus.Finished;
|
||||
} else {
|
||||
return globalSession.getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
if (globalSession == null) {
|
||||
return globalStatus;
|
||||
}
|
||||
globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
|
||||
doGlobalReport(globalSession, xid, globalStatus);
|
||||
return globalSession.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
if (globalSession.isSaga()) {
|
||||
getCore(BranchType.SAGA).doGlobalReport(globalSession, xid, globalStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import io.seata.core.model.ResourceManagerOutbound;
|
||||
import io.seata.core.model.TransactionManager;
|
||||
|
||||
/**
|
||||
* receive inbound request from RM or TM.
|
||||
*
|
||||
* @author zhangchenghui.dev@gmail.com
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface TransactionCoordinatorInbound extends ResourceManagerOutbound, TransactionManager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
/**
|
||||
* send outbound request to RM.
|
||||
*
|
||||
* @author zhangchenghui.dev@gmail.com
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface TransactionCoordinatorOutbound {
|
||||
|
||||
/**
|
||||
* Commit a branch transaction.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchSession the branch session
|
||||
* @return Status of the branch after committing.
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Rollback a branch transaction.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchSession the branch session
|
||||
* @return Status of the branch after rollbacking.
|
||||
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
|
||||
* out.
|
||||
*/
|
||||
BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException;
|
||||
|
||||
|
||||
}
|
||||
107
server/src/main/java/io/seata/server/env/ContainerHelper.java
vendored
Normal file
107
server/src/main/java/io/seata/server/env/ContainerHelper.java
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.env;
|
||||
|
||||
import io.seata.common.util.NumberUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.seata.common.DefaultValues.SERVER_DEFAULT_PORT;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class ContainerHelper {
|
||||
|
||||
private static final String C_GROUP_PATH = "/proc/1/cgroup";
|
||||
private static final String DOCKER_PATH = "/docker";
|
||||
private static final String KUBEPODS_PATH = "/kubepods";
|
||||
|
||||
private static final String ENV_SYSTEM_KEY = "SEATA_ENV";
|
||||
private static final String ENV_SEATA_IP_KEY = "SEATA_IP";
|
||||
private static final String ENV_SERVER_NODE_KEY = "SERVER_NODE";
|
||||
private static final String ENV_SEATA_PORT_KEY = "SEATA_PORT";
|
||||
private static final String ENV_STORE_MODE_KEY = "STORE_MODE";
|
||||
|
||||
/**
|
||||
* Judge if application is run in container.
|
||||
*
|
||||
* @return If application is run in container
|
||||
*/
|
||||
public static boolean isRunningInContainer() {
|
||||
Path path = Paths.get(C_GROUP_PATH);
|
||||
if (Files.exists(path)) {
|
||||
try (Stream<String> stream = Files.lines(path)) {
|
||||
return stream.anyMatch(line -> line.contains(DOCKER_PATH) || line.contains(KUBEPODS_PATH));
|
||||
} catch (IOException e) {
|
||||
System.err.println("Judge if running in container failed: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets env from container.
|
||||
*
|
||||
* @return the env
|
||||
*/
|
||||
public static String getEnv() {
|
||||
return StringUtils.trimToNull(System.getenv(ENV_SYSTEM_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets host from container.
|
||||
*
|
||||
* @return the env
|
||||
*/
|
||||
public static String getHost() {
|
||||
return StringUtils.trimToNull(System.getenv(ENV_SEATA_IP_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets port from container.
|
||||
*
|
||||
* @return the env
|
||||
*/
|
||||
public static int getPort() {
|
||||
return NumberUtils.toInt(System.getenv(ENV_SEATA_PORT_KEY), SERVER_DEFAULT_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets server node from container.
|
||||
*
|
||||
* @return the env
|
||||
*/
|
||||
public static Long getServerNode() {
|
||||
return NumberUtils.toLong(System.getenv(ENV_SERVER_NODE_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets store mode from container.
|
||||
*
|
||||
* @return the env
|
||||
*/
|
||||
public static String getStoreMode() {
|
||||
return StringUtils.trimToNull(System.getenv(ENV_STORE_MODE_KEY));
|
||||
}
|
||||
}
|
||||
41
server/src/main/java/io/seata/server/env/PortHelper.java
vendored
Normal file
41
server/src/main/java/io/seata/server/env/PortHelper.java
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.env;
|
||||
|
||||
import io.seata.common.util.NumberUtils;
|
||||
|
||||
import static io.seata.common.DefaultValues.SERVER_DEFAULT_PORT;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class PortHelper {
|
||||
|
||||
public static int getPort(String[] args) {
|
||||
if (ContainerHelper.isRunningInContainer()) {
|
||||
return ContainerHelper.getPort();
|
||||
} else if (args != null && args.length >= 2) {
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
if ("-p".equalsIgnoreCase(args[i]) && i < args.length - 1) {
|
||||
return NumberUtils.toInt(args[i + 1], SERVER_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SERVER_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.event;
|
||||
|
||||
import io.seata.core.event.EventBus;
|
||||
import io.seata.core.event.GuavaEventBus;
|
||||
|
||||
/**
|
||||
* Manager hold the singleton event bus instance.
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public class EventBusManager {
|
||||
private static class SingletonHolder {
|
||||
private static EventBus INSTANCE = new GuavaEventBus("tc");
|
||||
}
|
||||
|
||||
public static EventBus get() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.lock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.seata.common.XID;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.Locker;
|
||||
import io.seata.core.lock.RowLock;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Abstract lock manager.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public abstract class AbstractLockManager implements LockManager {
|
||||
|
||||
/**
|
||||
* The constant LOGGER.
|
||||
*/
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractLockManager.class);
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(BranchSession branchSession) throws TransactionException {
|
||||
if (branchSession == null) {
|
||||
throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
|
||||
}
|
||||
String lockKey = branchSession.getLockKey();
|
||||
if (StringUtils.isNullOrEmpty(lockKey)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
// get locks of branch
|
||||
List<RowLock> locks = collectRowLocks(branchSession);
|
||||
if (CollectionUtils.isEmpty(locks)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
return getLocker(branchSession).acquireLock(locks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(BranchSession branchSession) throws TransactionException {
|
||||
if (branchSession == null) {
|
||||
throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
|
||||
}
|
||||
List<RowLock> locks = collectRowLocks(branchSession);
|
||||
try {
|
||||
return getLocker(branchSession).releaseLock(locks);
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock error, branchSession:{}", branchSession, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(String xid, String resourceId, String lockKey) throws TransactionException {
|
||||
if (StringUtils.isBlank(lockKey)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
List<RowLock> locks = collectRowLocks(lockKey, resourceId, xid);
|
||||
try {
|
||||
return getLocker().isLockable(locks);
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("isLockable error, xid:{} resourceId:{}, lockKey:{}", xid, resourceId, lockKey, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cleanAllLocks() throws TransactionException {
|
||||
getLocker().cleanAllLocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets locker.
|
||||
*
|
||||
* @return the locker
|
||||
*/
|
||||
protected Locker getLocker() {
|
||||
return getLocker(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets locker.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the locker
|
||||
*/
|
||||
protected abstract Locker getLocker(BranchSession branchSession);
|
||||
|
||||
/**
|
||||
* Collect row locks list.`
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the list
|
||||
*/
|
||||
protected List<RowLock> collectRowLocks(BranchSession branchSession) {
|
||||
List<RowLock> locks = new ArrayList<>();
|
||||
if (branchSession == null || StringUtils.isBlank(branchSession.getLockKey())) {
|
||||
return locks;
|
||||
}
|
||||
String xid = branchSession.getXid();
|
||||
String resourceId = branchSession.getResourceId();
|
||||
long transactionId = branchSession.getTransactionId();
|
||||
|
||||
String lockKey = branchSession.getLockKey();
|
||||
|
||||
return collectRowLocks(lockKey, resourceId, xid, transactionId, branchSession.getBranchId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect row locks list.
|
||||
*
|
||||
* @param lockKey the lock key
|
||||
* @param resourceId the resource id
|
||||
* @param xid the xid
|
||||
* @return the list
|
||||
*/
|
||||
protected List<RowLock> collectRowLocks(String lockKey, String resourceId, String xid) {
|
||||
return collectRowLocks(lockKey, resourceId, xid, XID.getTransactionId(xid), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect row locks list.
|
||||
*
|
||||
* @param lockKey the lock key
|
||||
* @param resourceId the resource id
|
||||
* @param xid the xid
|
||||
* @param transactionId the transaction id
|
||||
* @param branchID the branch id
|
||||
* @return the list
|
||||
*/
|
||||
protected List<RowLock> collectRowLocks(String lockKey, String resourceId, String xid, Long transactionId,
|
||||
Long branchID) {
|
||||
List<RowLock> locks = new ArrayList<RowLock>();
|
||||
|
||||
String[] tableGroupedLockKeys = lockKey.split(";");
|
||||
for (String tableGroupedLockKey : tableGroupedLockKeys) {
|
||||
int idx = tableGroupedLockKey.indexOf(":");
|
||||
if (idx < 0) {
|
||||
return locks;
|
||||
}
|
||||
String tableName = tableGroupedLockKey.substring(0, idx);
|
||||
String mergedPKs = tableGroupedLockKey.substring(idx + 1);
|
||||
if (StringUtils.isBlank(mergedPKs)) {
|
||||
return locks;
|
||||
}
|
||||
String[] pks = mergedPKs.split(",");
|
||||
if (pks == null || pks.length == 0) {
|
||||
return locks;
|
||||
}
|
||||
for (String pk : pks) {
|
||||
if (StringUtils.isNotBlank(pk)) {
|
||||
RowLock rowLock = new RowLock();
|
||||
rowLock.setXid(xid);
|
||||
rowLock.setTransactionId(transactionId);
|
||||
rowLock.setBranchId(branchID);
|
||||
rowLock.setTableName(tableName);
|
||||
rowLock.setPk(pk);
|
||||
rowLock.setResourceId(resourceId);
|
||||
locks.add(rowLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
return locks;
|
||||
}
|
||||
|
||||
}
|
||||
74
server/src/main/java/io/seata/server/lock/LockManager.java
Normal file
74
server/src/main/java/io/seata/server/lock/LockManager.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.lock;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
/**
|
||||
* The interface Lock manager.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface LockManager {
|
||||
|
||||
/**
|
||||
* Acquire lock boolean.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean acquireLock(BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Un lock boolean.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean releaseLock(BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Un lock boolean.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean releaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Is lockable boolean.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param resourceId the resource id
|
||||
* @param lockKey the lock key
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean isLockable(String xid, String resourceId, String lockKey) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Clean all locks.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void cleanAllLocks() throws TransactionException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.lock;
|
||||
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
|
||||
/**
|
||||
* The type Lock manager factory.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class LockerManagerFactory {
|
||||
|
||||
/**
|
||||
* the lock manager
|
||||
*/
|
||||
private static final LockManager LOCK_MANAGER = EnhancedServiceLoader.load(LockManager.class,
|
||||
ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE));
|
||||
|
||||
/**
|
||||
* Get lock manager.
|
||||
*
|
||||
* @return the lock manager
|
||||
*/
|
||||
public static LockManager getLockManager() {
|
||||
return LOCK_MANAGER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.listener;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.LoggerContextListener;
|
||||
import ch.qos.logback.core.Context;
|
||||
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||
import ch.qos.logback.core.spi.LifeCycle;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class SystemPropertyLoggerContextListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {
|
||||
|
||||
private boolean started = false;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
context.putProperty("PORT", System.getProperty(ConfigurationKeys.SERVER_PORT));
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResetResistant() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(LoggerContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(LoggerContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(LoggerContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLevelChange(Logger logger, Level level) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.pattern.CompositeConverter;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.seata.server.logging.logback.ansi.AnsiColor;
|
||||
import io.seata.server.logging.logback.ansi.AnsiElement;
|
||||
import io.seata.server.logging.logback.ansi.AnsiOutput;
|
||||
import io.seata.server.logging.logback.ansi.AnsiStyle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Logback {@link CompositeConverter} colors output using the {@link AnsiOutput} class. A
|
||||
* single 'color' option can be provided to the converter, or if not specified color will
|
||||
* be picked based on the logging level.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public class ColorConverter extends CompositeConverter<ILoggingEvent> {
|
||||
|
||||
private static final Map<String, AnsiElement> ELEMENTS;
|
||||
|
||||
private static final String DISABLE_PROPERTY_NAME = "logback.color.disable-for-bat";
|
||||
|
||||
static {
|
||||
Map<String, AnsiElement> ansiElements = new HashMap<>();
|
||||
ansiElements.put("faint", AnsiStyle.FAINT);
|
||||
ansiElements.put("red", AnsiColor.RED);
|
||||
ansiElements.put("green", AnsiColor.GREEN);
|
||||
ansiElements.put("yellow", AnsiColor.YELLOW);
|
||||
ansiElements.put("blue", AnsiColor.BLUE);
|
||||
ansiElements.put("magenta", AnsiColor.MAGENTA);
|
||||
ansiElements.put("cyan", AnsiColor.CYAN);
|
||||
ELEMENTS = Collections.unmodifiableMap(ansiElements);
|
||||
}
|
||||
|
||||
private static final Map<Integer, AnsiElement> LEVELS;
|
||||
|
||||
static {
|
||||
Map<Integer, AnsiElement> ansiLevels = new HashMap<>();
|
||||
ansiLevels.put(Level.ERROR_INTEGER, AnsiColor.RED);
|
||||
ansiLevels.put(Level.WARN_INTEGER, AnsiColor.YELLOW);
|
||||
LEVELS = Collections.unmodifiableMap(ansiLevels);
|
||||
}
|
||||
|
||||
private final boolean disable;
|
||||
|
||||
public ColorConverter() {
|
||||
//If is windows and run by seata-server.bat, then disable the color log.
|
||||
disable = PlatformDependent.isWindows() && Boolean.parseBoolean(System.getProperty(DISABLE_PROPERTY_NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String transform(ILoggingEvent event, String in) {
|
||||
if (disable) {
|
||||
//return the original log
|
||||
return in;
|
||||
}
|
||||
|
||||
AnsiElement element = ELEMENTS.get(getFirstOption());
|
||||
if (element == null) {
|
||||
// Assume highlighting
|
||||
element = LEVELS.get(event.getLevel().toInteger());
|
||||
element = (element != null) ? element : AnsiColor.GREEN;
|
||||
}
|
||||
return toAnsiString(in, element);
|
||||
}
|
||||
|
||||
protected String toAnsiString(String in, AnsiElement element) {
|
||||
return AnsiOutput.toString(element, in);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback;
|
||||
|
||||
import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
|
||||
import ch.qos.logback.classic.spi.IThrowableProxy;
|
||||
import ch.qos.logback.core.CoreConstants;
|
||||
|
||||
/**
|
||||
* {@link ExtendedThrowableProxyConverter} that adds some additional whitespace around the
|
||||
* stack trace.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public class ExtendedWhitespaceThrowableProxyConverter extends ExtendedThrowableProxyConverter {
|
||||
|
||||
@Override
|
||||
protected String throwableProxyToString(IThrowableProxy tp) {
|
||||
return "==>" + CoreConstants.LINE_SEPARATOR + super.throwableProxyToString(tp)
|
||||
+ "<==" + CoreConstants.LINE_SEPARATOR + CoreConstants.LINE_SEPARATOR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback;
|
||||
|
||||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
||||
import ch.qos.logback.classic.spi.IThrowableProxy;
|
||||
import ch.qos.logback.core.CoreConstants;
|
||||
|
||||
/**
|
||||
* {@link ThrowableProxyConverter} that adds some additional whitespace around the stacktrace.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public class WhitespaceThrowableProxyConverter extends ThrowableProxyConverter {
|
||||
|
||||
@Override
|
||||
protected String throwableProxyToString(IThrowableProxy tp) {
|
||||
return CoreConstants.LINE_SEPARATOR + super.throwableProxyToString(tp)
|
||||
+ CoreConstants.LINE_SEPARATOR + CoreConstants.LINE_SEPARATOR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback.ansi;
|
||||
|
||||
/**
|
||||
* {@link AnsiElement Ansi} colors.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Geoffrey Chandler
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public enum AnsiColor implements AnsiElement {
|
||||
|
||||
/**
|
||||
* default is light-grey
|
||||
*/
|
||||
DEFAULT("39"),
|
||||
|
||||
/**
|
||||
* black
|
||||
*/
|
||||
BLACK("30"),
|
||||
|
||||
/**
|
||||
* red
|
||||
*/
|
||||
RED("31"),
|
||||
|
||||
/**
|
||||
* green
|
||||
*/
|
||||
GREEN("32"),
|
||||
|
||||
/**
|
||||
* yellow
|
||||
*/
|
||||
YELLOW("33"),
|
||||
|
||||
/**
|
||||
* blue
|
||||
*/
|
||||
BLUE("34"),
|
||||
|
||||
/**
|
||||
* magenta
|
||||
*/
|
||||
MAGENTA("35"),
|
||||
|
||||
/**
|
||||
* cyan
|
||||
*/
|
||||
CYAN("36"),
|
||||
|
||||
/**
|
||||
* white
|
||||
*/
|
||||
WHITE("37"),
|
||||
|
||||
/**
|
||||
* bright black
|
||||
*/
|
||||
BRIGHT_BLACK("90"),
|
||||
|
||||
/**
|
||||
* bright red
|
||||
*/
|
||||
BRIGHT_RED("91"),
|
||||
|
||||
/**
|
||||
* bright green
|
||||
*/
|
||||
BRIGHT_GREEN("92"),
|
||||
|
||||
/**
|
||||
* bright yellow
|
||||
*/
|
||||
BRIGHT_YELLOW("93"),
|
||||
|
||||
/**
|
||||
* bright blue
|
||||
*/
|
||||
BRIGHT_BLUE("94"),
|
||||
|
||||
/**
|
||||
* bright magenta
|
||||
*/
|
||||
BRIGHT_MAGENTA("95"),
|
||||
|
||||
/**
|
||||
* bright cyan
|
||||
*/
|
||||
BRIGHT_CYAN("96"),
|
||||
|
||||
/**
|
||||
* bright white
|
||||
*/
|
||||
BRIGHT_WHITE("97");
|
||||
|
||||
/**
|
||||
* code of color
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
AnsiColor(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback.ansi;
|
||||
|
||||
/**
|
||||
* An ANSI encodable element.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public interface AnsiElement {
|
||||
|
||||
/**
|
||||
* @return the ANSI escape code
|
||||
*/
|
||||
@Override
|
||||
String toString();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback.ansi;
|
||||
|
||||
import com.alipay.lookout.common.Assert;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Generates ANSI encoded output, automatically attempting to detect if the terminal
|
||||
* supports ANSI.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public class AnsiOutput {
|
||||
|
||||
private static final String ENCODE_JOIN = ";";
|
||||
|
||||
private static Enabled enabled = Enabled.ALWAYS;
|
||||
|
||||
private static Boolean consoleAvailable;
|
||||
|
||||
private static Boolean ansiCapable;
|
||||
|
||||
private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name")
|
||||
.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
private static final String ENCODE_START = "\033[";
|
||||
|
||||
private static final String ENCODE_END = "m";
|
||||
|
||||
private static final String RESET = "0;" + AnsiColor.DEFAULT;
|
||||
|
||||
/**
|
||||
* Sets if ANSI output is enabled.
|
||||
* @param enabled if ANSI is enabled, disabled or detected
|
||||
*/
|
||||
public static void setEnabled(Enabled enabled) {
|
||||
Assert.notNull(enabled, "Enabled must not be null");
|
||||
AnsiOutput.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the System.console() is known to be available.
|
||||
* @param consoleAvailable if the console is known to be available or {@code null} to
|
||||
* use standard detection logic.
|
||||
*/
|
||||
public static void setConsoleAvailable(Boolean consoleAvailable) {
|
||||
AnsiOutput.consoleAvailable = consoleAvailable;
|
||||
}
|
||||
|
||||
static Enabled getEnabled() {
|
||||
return AnsiOutput.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a single {@link AnsiElement} if output is enabled.
|
||||
* @param element the element to encode
|
||||
* @return the encoded element or an empty string
|
||||
*/
|
||||
public static String encode(AnsiElement element) {
|
||||
if (isEnabled()) {
|
||||
return ENCODE_START + element + ENCODE_END;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ANSI string from the specified elements. Any {@link AnsiElement}s will
|
||||
* be encoded as required.
|
||||
* @param elements the elements to encode
|
||||
* @return a string of the encoded elements
|
||||
*/
|
||||
public static String toString(Object... elements) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isEnabled()) {
|
||||
buildEnabled(sb, elements);
|
||||
}
|
||||
else {
|
||||
buildDisabled(sb, elements);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void buildEnabled(StringBuilder sb, Object[] elements) {
|
||||
boolean writingAnsi = false;
|
||||
boolean containsEncoding = false;
|
||||
for (Object element : elements) {
|
||||
if (element instanceof AnsiElement) {
|
||||
containsEncoding = true;
|
||||
if (!writingAnsi) {
|
||||
sb.append(ENCODE_START);
|
||||
writingAnsi = true;
|
||||
}
|
||||
else {
|
||||
sb.append(ENCODE_JOIN);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (writingAnsi) {
|
||||
sb.append(ENCODE_END);
|
||||
writingAnsi = false;
|
||||
}
|
||||
}
|
||||
sb.append(element);
|
||||
}
|
||||
if (containsEncoding) {
|
||||
sb.append(writingAnsi ? ENCODE_JOIN : ENCODE_START);
|
||||
sb.append(RESET);
|
||||
sb.append(ENCODE_END);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildDisabled(StringBuilder sb, Object[] elements) {
|
||||
for (Object element : elements) {
|
||||
if (!(element instanceof AnsiElement) && element != null) {
|
||||
sb.append(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEnabled() {
|
||||
if (enabled == Enabled.DETECT) {
|
||||
if (ansiCapable == null) {
|
||||
ansiCapable = detectIfAnsiCapable();
|
||||
}
|
||||
return ansiCapable;
|
||||
}
|
||||
return enabled == Enabled.ALWAYS;
|
||||
}
|
||||
|
||||
private static boolean detectIfAnsiCapable() {
|
||||
try {
|
||||
if (Boolean.FALSE.equals(consoleAvailable)) {
|
||||
return false;
|
||||
}
|
||||
if ((consoleAvailable == null) && (System.console() == null)) {
|
||||
return false;
|
||||
}
|
||||
return !(OPERATING_SYSTEM_NAME.indexOf("win") >= 0);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible values to pass to {@link AnsiOutput#setEnabled}. Determines when to output
|
||||
* ANSI escape sequences for coloring application output.
|
||||
*/
|
||||
public enum Enabled {
|
||||
|
||||
/**
|
||||
* Try to detect whether ANSI coloring capabilities are available. The default
|
||||
* value for {@link AnsiOutput}.
|
||||
*/
|
||||
DETECT,
|
||||
|
||||
/**
|
||||
* Enable ANSI-colored output.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Disable ANSI-colored output.
|
||||
*/
|
||||
NEVER
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback.ansi;
|
||||
|
||||
/**
|
||||
* {@link AnsiElement Ansi} styles.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @origin Copied from spring-boot.jar by wang.liang
|
||||
*/
|
||||
public enum AnsiStyle implements AnsiElement {
|
||||
|
||||
/**
|
||||
* normal
|
||||
*/
|
||||
NORMAL("0"),
|
||||
|
||||
/**
|
||||
* bold
|
||||
*/
|
||||
BOLD("1"),
|
||||
|
||||
/**
|
||||
* faint
|
||||
*/
|
||||
FAINT("2"),
|
||||
|
||||
/**
|
||||
* italic
|
||||
*/
|
||||
ITALIC("3"),
|
||||
|
||||
/**
|
||||
* underline
|
||||
*/
|
||||
UNDERLINE("4");
|
||||
|
||||
/**
|
||||
* code of style
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
AnsiStyle(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.logging.logback.appender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.logstash.logback.composite.JsonProvider;
|
||||
import net.logstash.logback.composite.JsonProviders;
|
||||
import net.logstash.logback.encoder.LogstashEncoder;
|
||||
|
||||
/**
|
||||
* The type Enhanced logstash encoder
|
||||
*
|
||||
* @author wang.liang
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public class EnhancedLogstashEncoder extends LogstashEncoder {
|
||||
|
||||
/**
|
||||
* set exclude provider
|
||||
*
|
||||
* @param excludedProviderClassName the excluded provider class name
|
||||
*/
|
||||
public void setExcludeProvider(String excludedProviderClassName) {
|
||||
JsonProviders<?> providers = getFormatter().getProviders();
|
||||
for (JsonProvider<?> provider : new ArrayList<>(providers.getProviders())) {
|
||||
if (provider.getClass().getName().equals(excludedProviderClassName)) {
|
||||
providers.removeProvider((JsonProvider) provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.metrics;
|
||||
|
||||
import io.seata.metrics.IdConstants;
|
||||
import io.seata.metrics.Id;
|
||||
|
||||
/**
|
||||
* Constants for meter id in tc
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public interface MeterIdConstants {
|
||||
Id COUNTER_ACTIVE = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_COUNTER)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_ACTIVE);
|
||||
|
||||
Id COUNTER_COMMITTED = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_COUNTER)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_COMMITTED);
|
||||
|
||||
Id COUNTER_ROLLBACKED = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_COUNTER)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_ROLLBACKED);
|
||||
|
||||
Id SUMMARY_COMMITTED = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_SUMMARY)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_COMMITTED);
|
||||
|
||||
Id SUMMARY_ROLLBACKED = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_SUMMARY)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_ROLLBACKED);
|
||||
|
||||
Id TIMER_COMMITTED = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_TIMER)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_COMMITTED);
|
||||
|
||||
Id TIMER_ROLLBACK = new Id(IdConstants.SEATA_TRANSACTION)
|
||||
.withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC)
|
||||
.withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_TIMER)
|
||||
.withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_ROLLBACKED);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.metrics;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.metrics.exporter.Exporter;
|
||||
import io.seata.metrics.exporter.ExporterFactory;
|
||||
import io.seata.metrics.registry.Registry;
|
||||
import io.seata.metrics.registry.RegistryFactory;
|
||||
import io.seata.server.event.EventBusManager;
|
||||
|
||||
/**
|
||||
* Metrics manager for init
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public class MetricsManager {
|
||||
private static class SingletonHolder {
|
||||
private static MetricsManager INSTANCE = new MetricsManager();
|
||||
}
|
||||
|
||||
public static final MetricsManager get() {
|
||||
return MetricsManager.SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private Registry registry;
|
||||
|
||||
public Registry getRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
boolean enabled = ConfigurationFactory.getInstance().getBoolean(
|
||||
ConfigurationKeys.METRICS_PREFIX + ConfigurationKeys.METRICS_ENABLED, false);
|
||||
if (enabled) {
|
||||
registry = RegistryFactory.getInstance();
|
||||
if (registry != null) {
|
||||
List<Exporter> exporters = ExporterFactory.getInstanceList();
|
||||
//only at least one metrics exporter implement had imported in pom then need register MetricsSubscriber
|
||||
if (exporters.size() != 0) {
|
||||
exporters.forEach(exporter -> exporter.setRegistry(registry));
|
||||
EventBusManager.get().register(new MetricsSubscriber(registry));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.metrics;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.seata.core.event.GlobalTransactionEvent;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.metrics.registry.Registry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static io.seata.metrics.IdConstants.APP_ID_KEY;
|
||||
import static io.seata.metrics.IdConstants.GROUP_KEY;
|
||||
|
||||
/**
|
||||
* Event subscriber for metrics
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public class MetricsSubscriber {
|
||||
private final Registry registry;
|
||||
|
||||
private final Map<GlobalStatus, Consumer<GlobalTransactionEvent>> consumers;
|
||||
|
||||
public MetricsSubscriber(Registry registry) {
|
||||
this.registry = registry;
|
||||
consumers = new HashMap<>();
|
||||
consumers.put(GlobalStatus.Begin, this::processGlobalStatusBegin);
|
||||
consumers.put(GlobalStatus.Committed, this::processGlobalStatusCommitted);
|
||||
consumers.put(GlobalStatus.Rollbacked, this::processGlobalStatusRollbacked);
|
||||
|
||||
consumers.put(GlobalStatus.CommitFailed, this::processGlobalStatusCommitFailed);
|
||||
consumers.put(GlobalStatus.RollbackFailed, this::processGlobalStatusRollbackFailed);
|
||||
consumers.put(GlobalStatus.TimeoutRollbacked, this::processGlobalStatusTimeoutRollbacked);
|
||||
consumers.put(GlobalStatus.TimeoutRollbackFailed, this::processGlobalStatusTimeoutRollbackFailed);
|
||||
}
|
||||
|
||||
private void processGlobalStatusBegin(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).increase(1);
|
||||
}
|
||||
|
||||
private void processGlobalStatusCommitted(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
registry.getCounter(MeterIdConstants.COUNTER_COMMITTED
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).increase(1);
|
||||
registry.getSummary(MeterIdConstants.SUMMARY_COMMITTED
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).increase(1);
|
||||
registry.getTimer(MeterIdConstants.TIMER_COMMITTED
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup()))
|
||||
.record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void processGlobalStatusRollbacked(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ROLLBACKED
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).increase(1);
|
||||
registry.getSummary(MeterIdConstants.SUMMARY_ROLLBACKED
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).increase(1);
|
||||
registry.getTimer(MeterIdConstants.TIMER_ROLLBACK
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup()))
|
||||
.record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void processGlobalStatusCommitFailed(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
}
|
||||
|
||||
private void processGlobalStatusRollbackFailed(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
}
|
||||
|
||||
private void processGlobalStatusTimeoutRollbacked(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
}
|
||||
|
||||
private void processGlobalStatusTimeoutRollbackFailed(GlobalTransactionEvent event) {
|
||||
registry.getCounter(MeterIdConstants.COUNTER_ACTIVE
|
||||
.withTag(APP_ID_KEY, event.getApplicationId())
|
||||
.withTag(GROUP_KEY, event.getGroup())).decrease(1);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void recordGlobalTransactionEventForMetrics(GlobalTransactionEvent event) {
|
||||
if (registry != null && consumers.containsKey(event.getStatus())) {
|
||||
consumers.get(event.getStatus()).accept(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.BranchTransactionException;
|
||||
import io.seata.core.exception.GlobalTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.exception.TransactionExceptionCode;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
import io.seata.server.store.TransactionStoreManager.LogOperation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Abstract session manager.
|
||||
*/
|
||||
public abstract class AbstractSessionManager implements SessionManager, SessionLifecycleListener {
|
||||
|
||||
/**
|
||||
* The constant LOGGER.
|
||||
*/
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractSessionManager.class);
|
||||
|
||||
/**
|
||||
* The Transaction store manager.
|
||||
*/
|
||||
protected TransactionStoreManager transactionStoreManager;
|
||||
|
||||
/**
|
||||
* The Name.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Instantiates a new Abstract session manager.
|
||||
*/
|
||||
public AbstractSessionManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Abstract session manager.
|
||||
*
|
||||
* @param name the name
|
||||
*/
|
||||
public AbstractSessionManager(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + session + "] " + LogOperation.GLOBAL_ADD);
|
||||
}
|
||||
writeSession(LogOperation.GLOBAL_ADD, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status) throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + session + "] " + LogOperation.GLOBAL_UPDATE);
|
||||
}
|
||||
writeSession(LogOperation.GLOBAL_UPDATE, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + session + "] " + LogOperation.GLOBAL_REMOVE);
|
||||
}
|
||||
writeSession(LogOperation.GLOBAL_REMOVE, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranchSession(GlobalSession session, BranchSession branchSession) throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + branchSession + "] " + LogOperation.BRANCH_ADD);
|
||||
}
|
||||
writeSession(LogOperation.BRANCH_ADD, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBranchSessionStatus(BranchSession branchSession, BranchStatus status)
|
||||
throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + branchSession + "] " + LogOperation.BRANCH_UPDATE);
|
||||
}
|
||||
writeSession(LogOperation.BRANCH_UPDATE, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranchSession(GlobalSession globalSession, BranchSession branchSession)
|
||||
throws TransactionException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("MANAGER[" + name + "] SESSION[" + branchSession + "] " + LogOperation.BRANCH_REMOVE);
|
||||
}
|
||||
writeSession(LogOperation.BRANCH_REMOVE, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(GlobalSession globalSession) throws TransactionException {
|
||||
addGlobalSession(globalSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChange(GlobalSession globalSession, GlobalStatus status) throws TransactionException {
|
||||
updateGlobalSessionStatus(globalSession, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSession, BranchStatus status)
|
||||
throws TransactionException {
|
||||
updateBranchSessionStatus(branchSession, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
addBranchSession(globalSession, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
removeBranchSession(globalSession, branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(GlobalSession globalSession) throws TransactionException {
|
||||
globalSession.setActive(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(GlobalSession globalSession) throws TransactionException {
|
||||
removeGlobalSession(globalSession);
|
||||
}
|
||||
|
||||
private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {
|
||||
if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {
|
||||
if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
|
||||
throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to store global session");
|
||||
} else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
|
||||
throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to update global session");
|
||||
} else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
|
||||
throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to remove global session");
|
||||
} else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
|
||||
throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to store branch session");
|
||||
} else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
|
||||
throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to update branch session");
|
||||
} else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
|
||||
throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Fail to remove branch session");
|
||||
} else {
|
||||
throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
|
||||
"Unknown LogOperation:" + logOperation.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transaction store manager.
|
||||
*
|
||||
* @param transactionStoreManager the transaction store manager
|
||||
*/
|
||||
public void setTransactionStoreManager(TransactionStoreManager transactionStoreManager) {
|
||||
this.transactionStoreManager = transactionStoreManager;
|
||||
}
|
||||
}
|
||||
452
server/src/main/java/io/seata/server/session/BranchSession.java
Normal file
452
server/src/main/java/io/seata/server/session/BranchSession.java
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import io.seata.server.storage.file.lock.FileLocker;
|
||||
import io.seata.common.util.CompressUtil;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.server.lock.LockerManagerFactory;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.StoreConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Branch session.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class BranchSession implements Lockable, Comparable<BranchSession>, SessionStorable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BranchSession.class);
|
||||
|
||||
private static final int MAX_BRANCH_SESSION_SIZE = StoreConfig.getMaxBranchSessionSize();
|
||||
|
||||
private static ThreadLocal<ByteBuffer> byteBufferThreadLocal = ThreadLocal.withInitial(() -> ByteBuffer.allocate(
|
||||
MAX_BRANCH_SESSION_SIZE));
|
||||
|
||||
private String xid;
|
||||
|
||||
private long transactionId;
|
||||
|
||||
private long branchId;
|
||||
|
||||
private String resourceGroupId;
|
||||
|
||||
private String resourceId;
|
||||
|
||||
private String lockKey;
|
||||
|
||||
private BranchType branchType;
|
||||
|
||||
private BranchStatus status = BranchStatus.Unknown;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String applicationData;
|
||||
|
||||
private ConcurrentMap<FileLocker.BucketLockMap, Set<String>> lockHolder
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Gets application data.
|
||||
*
|
||||
* @return the application data
|
||||
*/
|
||||
public String getApplicationData() {
|
||||
return applicationData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets application data.
|
||||
*
|
||||
* @param applicationData the application data
|
||||
*/
|
||||
public void setApplicationData(String applicationData) {
|
||||
this.applicationData = applicationData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets resource group id.
|
||||
*
|
||||
* @return the resource group id
|
||||
*/
|
||||
public String getResourceGroupId() {
|
||||
return resourceGroupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource group id.
|
||||
*
|
||||
* @param resourceGroupId the resource group id
|
||||
*/
|
||||
public void setResourceGroupId(String resourceGroupId) {
|
||||
this.resourceGroupId = resourceGroupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets client id.
|
||||
*
|
||||
* @return the client id
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets client id.
|
||||
*
|
||||
* @param clientId the client id
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets resource id.
|
||||
*
|
||||
* @return the resource id
|
||||
*/
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource id.
|
||||
*
|
||||
* @param resourceId the resource id
|
||||
*/
|
||||
public void setResourceId(String resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets lock key.
|
||||
*
|
||||
* @return the lock key
|
||||
*/
|
||||
public String getLockKey() {
|
||||
return lockKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets lock key.
|
||||
*
|
||||
* @param lockKey the lock key
|
||||
*/
|
||||
public void setLockKey(String lockKey) {
|
||||
this.lockKey = lockKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets branch type.
|
||||
*
|
||||
* @return the branch type
|
||||
*/
|
||||
public BranchType getBranchType() {
|
||||
return branchType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets branch type.
|
||||
*
|
||||
* @param branchType the branch type
|
||||
*/
|
||||
public void setBranchType(BranchType branchType) {
|
||||
this.branchType = branchType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status.
|
||||
*
|
||||
* @return the status
|
||||
*/
|
||||
public BranchStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets status.
|
||||
*
|
||||
* @param status the status
|
||||
*/
|
||||
public void setStatus(BranchStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction id.
|
||||
*
|
||||
* @return the transaction id
|
||||
*/
|
||||
public long getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transaction id.
|
||||
*
|
||||
* @param transactionId the transaction id
|
||||
*/
|
||||
public void setTransactionId(long transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets branch id.
|
||||
*
|
||||
* @return the branch id
|
||||
*/
|
||||
public long getBranchId() {
|
||||
return branchId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets branch id.
|
||||
*
|
||||
* @param branchId the branch id
|
||||
*/
|
||||
public void setBranchId(long branchId) {
|
||||
this.branchId = branchId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets xid.
|
||||
*
|
||||
* @return the xid
|
||||
*/
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets xid.
|
||||
*
|
||||
* @param xid the xid
|
||||
*/
|
||||
public void setXid(String xid) {
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BR:" + branchId + "/" + transactionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(BranchSession o) {
|
||||
return Long.compare(this.branchId, o.branchId);
|
||||
}
|
||||
|
||||
public boolean canBeCommittedAsync() {
|
||||
return branchType == BranchType.AT || status == BranchStatus.PhaseOne_Failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets lock holder.
|
||||
*
|
||||
* @return the lock holder
|
||||
*/
|
||||
public ConcurrentMap<FileLocker.BucketLockMap, Set<String>> getLockHolder() {
|
||||
return lockHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lock() throws TransactionException {
|
||||
if (this.getBranchType().equals(BranchType.AT)) {
|
||||
return LockerManagerFactory.getLockManager().acquireLock(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlock() throws TransactionException {
|
||||
if (this.getBranchType() == BranchType.AT) {
|
||||
return LockerManagerFactory.getLockManager().releaseLock(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
|
||||
byte[] resourceIdBytes = resourceId != null ? resourceId.getBytes() : null;
|
||||
|
||||
byte[] lockKeyBytes = lockKey != null ? lockKey.getBytes() : null;
|
||||
|
||||
byte[] clientIdBytes = clientId != null ? clientId.getBytes() : null;
|
||||
|
||||
byte[] applicationDataBytes = applicationData != null ? applicationData.getBytes() : null;
|
||||
|
||||
byte[] xidBytes = xid != null ? xid.getBytes() : null;
|
||||
|
||||
byte branchTypeByte = branchType != null ? (byte) branchType.ordinal() : -1;
|
||||
|
||||
int size = calBranchSessionSize(resourceIdBytes, lockKeyBytes, clientIdBytes, applicationDataBytes, xidBytes);
|
||||
|
||||
if (size > MAX_BRANCH_SESSION_SIZE) {
|
||||
if (lockKeyBytes == null) {
|
||||
throw new RuntimeException("branch session size exceeded, size : " + size + " maxBranchSessionSize : "
|
||||
+ MAX_BRANCH_SESSION_SIZE);
|
||||
}
|
||||
// try compress lockkey
|
||||
try {
|
||||
size -= lockKeyBytes.length;
|
||||
lockKeyBytes = CompressUtil.compress(lockKeyBytes);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("compress lockKey error", e);
|
||||
} finally {
|
||||
size += lockKeyBytes.length;
|
||||
}
|
||||
|
||||
if (size > MAX_BRANCH_SESSION_SIZE) {
|
||||
throw new RuntimeException(
|
||||
"compress branch session size exceeded, compressSize : " + size + " maxBranchSessionSize : "
|
||||
+ MAX_BRANCH_SESSION_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer byteBuffer = byteBufferThreadLocal.get();
|
||||
//recycle
|
||||
byteBuffer.clear();
|
||||
|
||||
byteBuffer.putLong(transactionId);
|
||||
byteBuffer.putLong(branchId);
|
||||
|
||||
if (resourceIdBytes != null) {
|
||||
byteBuffer.putInt(resourceIdBytes.length);
|
||||
byteBuffer.put(resourceIdBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
|
||||
if (lockKeyBytes != null) {
|
||||
byteBuffer.putInt(lockKeyBytes.length);
|
||||
byteBuffer.put(lockKeyBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
|
||||
if (clientIdBytes != null) {
|
||||
byteBuffer.putShort((short)clientIdBytes.length);
|
||||
byteBuffer.put(clientIdBytes);
|
||||
} else {
|
||||
byteBuffer.putShort((short)0);
|
||||
}
|
||||
|
||||
if (applicationDataBytes != null) {
|
||||
byteBuffer.putInt(applicationDataBytes.length);
|
||||
byteBuffer.put(applicationDataBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
|
||||
if (xidBytes != null) {
|
||||
byteBuffer.putInt(xidBytes.length);
|
||||
byteBuffer.put(xidBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
|
||||
byteBuffer.put(branchTypeByte);
|
||||
|
||||
byteBuffer.put((byte)status.getCode());
|
||||
byteBuffer.flip();
|
||||
byte[] result = new byte[byteBuffer.limit()];
|
||||
byteBuffer.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int calBranchSessionSize(byte[] resourceIdBytes, byte[] lockKeyBytes, byte[] clientIdBytes,
|
||||
byte[] applicationDataBytes, byte[] xidBytes) {
|
||||
final int size = 8 // trascationId
|
||||
+ 8 // branchId
|
||||
+ 4 // resourceIdBytes.length
|
||||
+ 4 // lockKeyBytes.length
|
||||
+ 2 // clientIdBytes.length
|
||||
+ 4 // applicationDataBytes.length
|
||||
+ 4 // xidBytes.size
|
||||
+ 1 // statusCode
|
||||
+ (resourceIdBytes == null ? 0 : resourceIdBytes.length)
|
||||
+ (lockKeyBytes == null ? 0 : lockKeyBytes.length)
|
||||
+ (clientIdBytes == null ? 0 : clientIdBytes.length)
|
||||
+ (applicationDataBytes == null ? 0 : applicationDataBytes.length)
|
||||
+ (xidBytes == null ? 0 : xidBytes.length)
|
||||
+ 1; //branchType
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(byte[] a) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(a);
|
||||
this.transactionId = byteBuffer.getLong();
|
||||
this.branchId = byteBuffer.getLong();
|
||||
int resourceLen = byteBuffer.getInt();
|
||||
if (resourceLen > 0) {
|
||||
byte[] byResource = new byte[resourceLen];
|
||||
byteBuffer.get(byResource);
|
||||
this.resourceId = new String(byResource);
|
||||
}
|
||||
int lockKeyLen = byteBuffer.getInt();
|
||||
if (lockKeyLen > 0) {
|
||||
byte[] byLockKey = new byte[lockKeyLen];
|
||||
byteBuffer.get(byLockKey);
|
||||
if (CompressUtil.isCompressData(byLockKey)) {
|
||||
try {
|
||||
this.lockKey = new String(CompressUtil.uncompress(byLockKey));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("decompress lockKey error", e);
|
||||
}
|
||||
} else {
|
||||
this.lockKey = new String(byLockKey);
|
||||
}
|
||||
|
||||
}
|
||||
short clientIdLen = byteBuffer.getShort();
|
||||
if (clientIdLen > 0) {
|
||||
byte[] byClientId = new byte[clientIdLen];
|
||||
byteBuffer.get(byClientId);
|
||||
this.clientId = new String(byClientId);
|
||||
}
|
||||
int applicationDataLen = byteBuffer.getInt();
|
||||
if (applicationDataLen > 0) {
|
||||
byte[] byApplicationData = new byte[applicationDataLen];
|
||||
byteBuffer.get(byApplicationData);
|
||||
this.applicationData = new String(byApplicationData);
|
||||
}
|
||||
int xidLen = byteBuffer.getInt();
|
||||
if (xidLen > 0) {
|
||||
byte[] xidBytes = new byte[xidLen];
|
||||
byteBuffer.get(xidBytes);
|
||||
this.xid = new String(xidBytes);
|
||||
}
|
||||
int branchTypeId = byteBuffer.get();
|
||||
if (branchTypeId >= 0) {
|
||||
this.branchType = BranchType.values()[branchTypeId];
|
||||
}
|
||||
this.status = BranchStatus.get(byteBuffer.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
|
||||
/**
|
||||
* The Functional Interface Branch session handler
|
||||
*
|
||||
* @author wang.liang
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BranchSessionHandler {
|
||||
|
||||
Boolean CONTINUE = null;
|
||||
|
||||
/**
|
||||
* Handle branch session.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the handle result
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
Boolean handle(BranchSession branchSession) throws TransactionException;
|
||||
}
|
||||
700
server/src/main/java/io/seata/server/session/GlobalSession.java
Normal file
700
server/src/main/java/io/seata/server/session/GlobalSession.java
Normal file
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import io.seata.common.Constants;
|
||||
import io.seata.common.DefaultValues;
|
||||
import io.seata.common.XID;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.exception.GlobalTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.exception.TransactionExceptionCode;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.UUIDGenerator;
|
||||
import io.seata.server.lock.LockerManagerFactory;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.StoreConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.seata.core.model.GlobalStatus.AsyncCommitting;
|
||||
import static io.seata.core.model.GlobalStatus.CommitRetrying;
|
||||
import static io.seata.core.model.GlobalStatus.Committing;
|
||||
|
||||
/**
|
||||
* The type Global session.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class GlobalSession implements SessionLifecycle, SessionStorable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSession.class);
|
||||
|
||||
private static final int MAX_GLOBAL_SESSION_SIZE = StoreConfig.getMaxGlobalSessionSize();
|
||||
|
||||
private static ThreadLocal<ByteBuffer> byteBufferThreadLocal = ThreadLocal.withInitial(() -> ByteBuffer.allocate(
|
||||
MAX_GLOBAL_SESSION_SIZE));
|
||||
|
||||
/**
|
||||
* If the global session's status is (Rollbacking or Committing) and currentTime - createTime >= RETRY_DEAD_THRESHOLD
|
||||
* then the tx will be remand as need to retry rollback
|
||||
*/
|
||||
private static final int RETRY_DEAD_THRESHOLD = ConfigurationFactory.getInstance()
|
||||
.getInt(ConfigurationKeys.RETRY_DEAD_THRESHOLD, DefaultValues.DEFAULT_RETRY_DEAD_THRESHOLD);
|
||||
|
||||
private String xid;
|
||||
|
||||
private long transactionId;
|
||||
|
||||
private volatile GlobalStatus status;
|
||||
|
||||
private String applicationId;
|
||||
|
||||
private String transactionServiceGroup;
|
||||
|
||||
private String transactionName;
|
||||
|
||||
private int timeout;
|
||||
|
||||
private long beginTime;
|
||||
|
||||
private String applicationData;
|
||||
|
||||
private volatile boolean active = true;
|
||||
|
||||
private final ArrayList<BranchSession> branchSessions = new ArrayList<>();
|
||||
|
||||
private GlobalSessionLock globalSessionLock = new GlobalSessionLock();
|
||||
|
||||
|
||||
/**
|
||||
* Add boolean.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean add(BranchSession branchSession) {
|
||||
return branchSessions.add(branchSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove boolean.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean remove(BranchSession branchSession) {
|
||||
return branchSessions.remove(branchSession);
|
||||
}
|
||||
|
||||
private Set<SessionLifecycleListener> lifecycleListeners = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Can be committed async boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean canBeCommittedAsync() {
|
||||
for (BranchSession branchSession : branchSessions) {
|
||||
if (!branchSession.canBeCommittedAsync()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has AT branch
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean hasATBranch() {
|
||||
for (BranchSession branchSession : branchSessions) {
|
||||
if (branchSession.getBranchType() == BranchType.AT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is saga type transaction
|
||||
*
|
||||
* @return is saga
|
||||
*/
|
||||
public boolean isSaga() {
|
||||
if (branchSessions.size() > 0) {
|
||||
return BranchType.SAGA == branchSessions.get(0).getBranchType();
|
||||
} else {
|
||||
return StringUtils.isNotBlank(transactionName)
|
||||
&& transactionName.startsWith(Constants.SAGA_TRANS_NAME_PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is timeout boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean isTimeout() {
|
||||
return (System.currentTimeMillis() - beginTime) > timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* prevent could not handle committing and rollbacking transaction
|
||||
* @return if true retry commit or roll back
|
||||
*/
|
||||
public boolean isDeadSession() {
|
||||
return (System.currentTimeMillis() - beginTime) > RETRY_DEAD_THRESHOLD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin() throws TransactionException {
|
||||
this.status = GlobalStatus.Begin;
|
||||
this.beginTime = System.currentTimeMillis();
|
||||
this.active = true;
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onBegin(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStatus(GlobalStatus status) throws TransactionException {
|
||||
this.status = status;
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onStatusChange(this, status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeBranchStatus(BranchSession branchSession, BranchStatus status)
|
||||
throws TransactionException {
|
||||
branchSession.setStatus(status);
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onBranchStatusChange(this, branchSession, status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws TransactionException {
|
||||
if (active) {
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onClose(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() throws TransactionException {
|
||||
// Clean locks first
|
||||
clean();
|
||||
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void clean() throws TransactionException {
|
||||
if (this.hasATBranch()) {
|
||||
if (!LockerManagerFactory.getLockManager().releaseGlobalSessionLock(this)) {
|
||||
throw new TransactionException("UnLock globalSession error, xid = " + this.xid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and clean.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public void closeAndClean() throws TransactionException {
|
||||
close();
|
||||
clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add session lifecycle listener.
|
||||
*
|
||||
* @param sessionLifecycleListener the session lifecycle listener
|
||||
*/
|
||||
public void addSessionLifecycleListener(SessionLifecycleListener sessionLifecycleListener) {
|
||||
lifecycleListeners.add(sessionLifecycleListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove session lifecycle listener.
|
||||
*
|
||||
* @param sessionLifecycleListener the session lifecycle listener
|
||||
*/
|
||||
public void removeSessionLifecycleListener(SessionLifecycleListener sessionLifecycleListener) {
|
||||
lifecycleListeners.remove(sessionLifecycleListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranch(BranchSession branchSession) throws TransactionException {
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onAddBranch(this, branchSession);
|
||||
}
|
||||
branchSession.setStatus(BranchStatus.Registered);
|
||||
add(branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranch(BranchSession branchSession) throws TransactionException {
|
||||
// do not unlock if global status in (Committing, CommitRetrying, AsyncCommitting),
|
||||
// because it's already unlocked in 'DefaultCore.commit()'
|
||||
if (status != Committing && status != CommitRetrying && status != AsyncCommitting) {
|
||||
if (!branchSession.unlock()) {
|
||||
throw new TransactionException("Unlock branch lock failed, xid = " + this.xid + ", branchId = " + branchSession.getBranchId());
|
||||
}
|
||||
}
|
||||
for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
|
||||
lifecycleListener.onRemoveBranch(this, branchSession);
|
||||
}
|
||||
remove(branchSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets branch.
|
||||
*
|
||||
* @param branchId the branch id
|
||||
* @return the branch
|
||||
*/
|
||||
public BranchSession getBranch(long branchId) {
|
||||
synchronized (branchSessions) {
|
||||
for (BranchSession branchSession : branchSessions) {
|
||||
if (branchSession.getBranchId() == branchId) {
|
||||
return branchSession;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets sorted branches.
|
||||
*
|
||||
* @return the sorted branches
|
||||
*/
|
||||
public ArrayList<BranchSession> getSortedBranches() {
|
||||
return new ArrayList<>(branchSessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets reverse sorted branches.
|
||||
*
|
||||
* @return the reverse sorted branches
|
||||
*/
|
||||
public ArrayList<BranchSession> getReverseSortedBranches() {
|
||||
ArrayList<BranchSession> reversed = new ArrayList<>(branchSessions);
|
||||
Collections.reverse(reversed);
|
||||
return reversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global session.
|
||||
*/
|
||||
public GlobalSession() {}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global session.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param transactionServiceGroup the transaction service group
|
||||
* @param transactionName the transaction name
|
||||
* @param timeout the timeout
|
||||
*/
|
||||
public GlobalSession(String applicationId, String transactionServiceGroup, String transactionName, int timeout) {
|
||||
this.transactionId = UUIDGenerator.generateUUID();
|
||||
this.status = GlobalStatus.Begin;
|
||||
|
||||
this.applicationId = applicationId;
|
||||
this.transactionServiceGroup = transactionServiceGroup;
|
||||
this.transactionName = transactionName;
|
||||
this.timeout = timeout;
|
||||
this.xid = XID.generateXID(transactionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction id.
|
||||
*
|
||||
* @return the transaction id
|
||||
*/
|
||||
public long getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transaction id.
|
||||
*
|
||||
* @param transactionId the transaction id
|
||||
*/
|
||||
public void setTransactionId(long transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status.
|
||||
*
|
||||
* @return the status
|
||||
*/
|
||||
public GlobalStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets status.
|
||||
*
|
||||
* @param status the status
|
||||
*/
|
||||
public void setStatus(GlobalStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets xid.
|
||||
*
|
||||
* @return the xid
|
||||
*/
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets xid.
|
||||
*
|
||||
* @param xid the xid
|
||||
*/
|
||||
public void setXid(String xid) {
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets application id.
|
||||
*
|
||||
* @return the application id
|
||||
*/
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction service group.
|
||||
*
|
||||
* @return the transaction service group
|
||||
*/
|
||||
public String getTransactionServiceGroup() {
|
||||
return transactionServiceGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction name.
|
||||
*
|
||||
* @return the transaction name
|
||||
*/
|
||||
public String getTransactionName() {
|
||||
return transactionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets timeout.
|
||||
*
|
||||
* @return the timeout
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets begin time.
|
||||
*
|
||||
* @return the begin time
|
||||
*/
|
||||
public long getBeginTime() {
|
||||
return beginTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets begin time.
|
||||
*
|
||||
* @param beginTime the begin time
|
||||
*/
|
||||
public void setBeginTime(long beginTime) {
|
||||
this.beginTime = beginTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets application data.
|
||||
*
|
||||
* @return the application data
|
||||
*/
|
||||
public String getApplicationData() {
|
||||
return applicationData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets application data.
|
||||
*
|
||||
* @param applicationData the application data
|
||||
*/
|
||||
public void setApplicationData(String applicationData) {
|
||||
this.applicationData = applicationData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create global session global session.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param txServiceGroup the tx service group
|
||||
* @param txName the tx name
|
||||
* @param timeout the timeout
|
||||
* @return the global session
|
||||
*/
|
||||
public static GlobalSession createGlobalSession(String applicationId, String txServiceGroup, String txName,
|
||||
int timeout) {
|
||||
GlobalSession session = new GlobalSession(applicationId, txServiceGroup, txName, timeout);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets active.
|
||||
*
|
||||
* @param active the active
|
||||
*/
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
byte[] byApplicationIdBytes = applicationId != null ? applicationId.getBytes() : null;
|
||||
|
||||
byte[] byServiceGroupBytes = transactionServiceGroup != null ? transactionServiceGroup.getBytes() : null;
|
||||
|
||||
byte[] byTxNameBytes = transactionName != null ? transactionName.getBytes() : null;
|
||||
|
||||
byte[] xidBytes = xid != null ? xid.getBytes() : null;
|
||||
|
||||
byte[] applicationDataBytes = applicationData != null ? applicationData.getBytes() : null;
|
||||
|
||||
int size = calGlobalSessionSize(byApplicationIdBytes, byServiceGroupBytes, byTxNameBytes, xidBytes,
|
||||
applicationDataBytes);
|
||||
|
||||
if (size > MAX_GLOBAL_SESSION_SIZE) {
|
||||
throw new RuntimeException("global session size exceeded, size : " + size + " maxBranchSessionSize : " +
|
||||
MAX_GLOBAL_SESSION_SIZE);
|
||||
}
|
||||
ByteBuffer byteBuffer = byteBufferThreadLocal.get();
|
||||
//recycle
|
||||
byteBuffer.clear();
|
||||
|
||||
byteBuffer.putLong(transactionId);
|
||||
byteBuffer.putInt(timeout);
|
||||
if (byApplicationIdBytes != null) {
|
||||
byteBuffer.putShort((short)byApplicationIdBytes.length);
|
||||
byteBuffer.put(byApplicationIdBytes);
|
||||
} else {
|
||||
byteBuffer.putShort((short)0);
|
||||
}
|
||||
if (byServiceGroupBytes != null) {
|
||||
byteBuffer.putShort((short)byServiceGroupBytes.length);
|
||||
byteBuffer.put(byServiceGroupBytes);
|
||||
} else {
|
||||
byteBuffer.putShort((short)0);
|
||||
}
|
||||
if (byTxNameBytes != null) {
|
||||
byteBuffer.putShort((short)byTxNameBytes.length);
|
||||
byteBuffer.put(byTxNameBytes);
|
||||
} else {
|
||||
byteBuffer.putShort((short)0);
|
||||
}
|
||||
if (xidBytes != null) {
|
||||
byteBuffer.putInt(xidBytes.length);
|
||||
byteBuffer.put(xidBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
if (applicationDataBytes != null) {
|
||||
byteBuffer.putInt(applicationDataBytes.length);
|
||||
byteBuffer.put(applicationDataBytes);
|
||||
} else {
|
||||
byteBuffer.putInt(0);
|
||||
}
|
||||
|
||||
byteBuffer.putLong(beginTime);
|
||||
byteBuffer.put((byte)status.getCode());
|
||||
byteBuffer.flip();
|
||||
byte[] result = new byte[byteBuffer.limit()];
|
||||
byteBuffer.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int calGlobalSessionSize(byte[] byApplicationIdBytes, byte[] byServiceGroupBytes, byte[] byTxNameBytes,
|
||||
byte[] xidBytes, byte[] applicationDataBytes) {
|
||||
final int size = 8 // transactionId
|
||||
+ 4 // timeout
|
||||
+ 2 // byApplicationIdBytes.length
|
||||
+ 2 // byServiceGroupBytes.length
|
||||
+ 2 // byTxNameBytes.length
|
||||
+ 4 // xidBytes.length
|
||||
+ 4 // applicationDataBytes.length
|
||||
+ 8 // beginTime
|
||||
+ 1 // statusCode
|
||||
+ (byApplicationIdBytes == null ? 0 : byApplicationIdBytes.length)
|
||||
+ (byServiceGroupBytes == null ? 0 : byServiceGroupBytes.length)
|
||||
+ (byTxNameBytes == null ? 0 : byTxNameBytes.length)
|
||||
+ (xidBytes == null ? 0 : xidBytes.length)
|
||||
+ (applicationDataBytes == null ? 0 : applicationDataBytes.length);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(byte[] a) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(a);
|
||||
this.transactionId = byteBuffer.getLong();
|
||||
this.timeout = byteBuffer.getInt();
|
||||
short applicationIdLen = byteBuffer.getShort();
|
||||
if (applicationIdLen > 0) {
|
||||
byte[] byApplicationId = new byte[applicationIdLen];
|
||||
byteBuffer.get(byApplicationId);
|
||||
this.applicationId = new String(byApplicationId);
|
||||
}
|
||||
short serviceGroupLen = byteBuffer.getShort();
|
||||
if (serviceGroupLen > 0) {
|
||||
byte[] byServiceGroup = new byte[serviceGroupLen];
|
||||
byteBuffer.get(byServiceGroup);
|
||||
this.transactionServiceGroup = new String(byServiceGroup);
|
||||
}
|
||||
short txNameLen = byteBuffer.getShort();
|
||||
if (txNameLen > 0) {
|
||||
byte[] byTxName = new byte[txNameLen];
|
||||
byteBuffer.get(byTxName);
|
||||
this.transactionName = new String(byTxName);
|
||||
}
|
||||
int xidLen = byteBuffer.getInt();
|
||||
if (xidLen > 0) {
|
||||
byte[] xidBytes = new byte[xidLen];
|
||||
byteBuffer.get(xidBytes);
|
||||
this.xid = new String(xidBytes);
|
||||
}
|
||||
int applicationDataLen = byteBuffer.getInt();
|
||||
if (applicationDataLen > 0) {
|
||||
byte[] applicationDataLenBytes = new byte[applicationDataLen];
|
||||
byteBuffer.get(applicationDataLenBytes);
|
||||
this.applicationData = new String(applicationDataLenBytes);
|
||||
}
|
||||
|
||||
this.beginTime = byteBuffer.getLong();
|
||||
this.status = GlobalStatus.get(byteBuffer.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Has branch boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public boolean hasBranch() {
|
||||
return branchSessions.size() > 0;
|
||||
}
|
||||
|
||||
public void lock() throws TransactionException {
|
||||
globalSessionLock.lock();
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
globalSessionLock.unlock();
|
||||
}
|
||||
|
||||
private static class GlobalSessionLock {
|
||||
|
||||
private Lock globalSessionLock = new ReentrantLock();
|
||||
|
||||
private static final int GLOBAL_SESSION_LOCK_TIME_OUT_MILLS = 2 * 1000;
|
||||
|
||||
public void lock() throws TransactionException {
|
||||
try {
|
||||
if (globalSessionLock.tryLock(GLOBAL_SESSION_LOCK_TIME_OUT_MILLS, TimeUnit.MILLISECONDS)) {
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Interrupted error", e);
|
||||
}
|
||||
throw new GlobalTransactionException(TransactionExceptionCode.FailedLockGlobalTranscation, "Lock global session failed");
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
globalSessionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LockRunnable {
|
||||
|
||||
void run() throws TransactionException;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LockCallable<V> {
|
||||
|
||||
V call() throws TransactionException;
|
||||
}
|
||||
|
||||
public ArrayList<BranchSession> getBranchSessions() {
|
||||
return branchSessions;
|
||||
}
|
||||
|
||||
public void asyncCommit() throws TransactionException {
|
||||
this.addSessionLifecycleListener(SessionHolder.getAsyncCommittingSessionManager());
|
||||
SessionHolder.getAsyncCommittingSessionManager().addGlobalSession(this);
|
||||
this.changeStatus(GlobalStatus.AsyncCommitting);
|
||||
}
|
||||
|
||||
public void queueToRetryCommit() throws TransactionException {
|
||||
this.addSessionLifecycleListener(SessionHolder.getRetryCommittingSessionManager());
|
||||
SessionHolder.getRetryCommittingSessionManager().addGlobalSession(this);
|
||||
this.changeStatus(GlobalStatus.CommitRetrying);
|
||||
}
|
||||
|
||||
public void queueToRetryRollback() throws TransactionException {
|
||||
this.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager());
|
||||
SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(this);
|
||||
GlobalStatus currentStatus = this.getStatus();
|
||||
if (SessionHelper.isTimeoutGlobalStatus(currentStatus)) {
|
||||
this.changeStatus(GlobalStatus.TimeoutRollbackRetrying);
|
||||
} else {
|
||||
this.changeStatus(GlobalStatus.RollbackRetrying);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
|
||||
/**
|
||||
* The Functional Interface Global session handler
|
||||
*
|
||||
* @author wang.liang
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GlobalSessionHandler {
|
||||
|
||||
/**
|
||||
* Handle global session.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void handle(GlobalSession globalSession) throws TransactionException;
|
||||
}
|
||||
42
server/src/main/java/io/seata/server/session/Lockable.java
Normal file
42
server/src/main/java/io/seata/server/session/Lockable.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
|
||||
/**
|
||||
* The interface Lockable.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface Lockable {
|
||||
|
||||
/**
|
||||
* Lock boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean lock() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Unlock boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
boolean unlock() throws TransactionException;
|
||||
}
|
||||
29
server/src/main/java/io/seata/server/session/Reloadable.java
Normal file
29
server/src/main/java/io/seata/server/session/Reloadable.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
/**
|
||||
* Service contains states which can be reloaded.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface Reloadable {
|
||||
|
||||
/**
|
||||
* Reload states.
|
||||
*/
|
||||
void reload();
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
|
||||
/**
|
||||
* The type Session condition.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class SessionCondition {
|
||||
private Long transactionId;
|
||||
private String xid;
|
||||
private GlobalStatus status;
|
||||
private GlobalStatus[] statuses;
|
||||
private long overTimeAliveMills;
|
||||
|
||||
/**
|
||||
* Instantiates a new Session condition.
|
||||
*/
|
||||
public SessionCondition() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Session condition.
|
||||
*
|
||||
* @param xid the xid
|
||||
*/
|
||||
public SessionCondition(String xid) {
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Session condition.
|
||||
*
|
||||
* @param status the status
|
||||
*/
|
||||
public SessionCondition(GlobalStatus status) {
|
||||
this.status = status;
|
||||
statuses = new GlobalStatus[] {status};
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Session condition.
|
||||
*
|
||||
* @param statuses the statuses
|
||||
*/
|
||||
public SessionCondition(GlobalStatus[] statuses) {
|
||||
this.statuses = statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Session condition.
|
||||
*
|
||||
* @param overTimeAliveMills the over time alive mills
|
||||
*/
|
||||
public SessionCondition(long overTimeAliveMills) {
|
||||
this.overTimeAliveMills = overTimeAliveMills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status.
|
||||
*
|
||||
* @return the status
|
||||
*/
|
||||
public GlobalStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets status.
|
||||
*
|
||||
* @param status the status
|
||||
*/
|
||||
public void setStatus(GlobalStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets over time alive mills.
|
||||
*
|
||||
* @return the over time alive mills
|
||||
*/
|
||||
public long getOverTimeAliveMills() {
|
||||
return overTimeAliveMills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets over time alive mills.
|
||||
*
|
||||
* @param overTimeAliveMills the over time alive mills
|
||||
*/
|
||||
public void setOverTimeAliveMills(long overTimeAliveMills) {
|
||||
this.overTimeAliveMills = overTimeAliveMills;
|
||||
}
|
||||
|
||||
public Long getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public void setTransactionId(Long transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
|
||||
public void setXid(String xid) {
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
public GlobalStatus[] getStatuses() {
|
||||
return statuses;
|
||||
}
|
||||
|
||||
public void setStatuses(GlobalStatus[] statuses) {
|
||||
this.statuses = statuses;
|
||||
}
|
||||
}
|
||||
193
server/src/main/java/io/seata/server/session/SessionHelper.java
Normal file
193
server/src/main/java/io/seata/server/session/SessionHelper.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.UUIDGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
/**
|
||||
* The type Session helper.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class SessionHelper {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionHelper.class);
|
||||
|
||||
private SessionHelper() {}
|
||||
|
||||
public static BranchSession newBranchByGlobal(GlobalSession globalSession, BranchType branchType, String resourceId, String lockKeys, String clientId) {
|
||||
return newBranchByGlobal(globalSession, branchType, resourceId, null, lockKeys, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* New branch by global branch session.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchType the branch type
|
||||
* @param resourceId the resource id
|
||||
* @param lockKeys the lock keys
|
||||
* @param clientId the client id
|
||||
* @return the branch session
|
||||
*/
|
||||
public static BranchSession newBranchByGlobal(GlobalSession globalSession, BranchType branchType, String resourceId,
|
||||
String applicationData, String lockKeys, String clientId) {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
|
||||
branchSession.setXid(globalSession.getXid());
|
||||
branchSession.setTransactionId(globalSession.getTransactionId());
|
||||
branchSession.setBranchId(UUIDGenerator.generateUUID());
|
||||
branchSession.setBranchType(branchType);
|
||||
branchSession.setResourceId(resourceId);
|
||||
branchSession.setLockKey(lockKeys);
|
||||
branchSession.setClientId(clientId);
|
||||
branchSession.setApplicationData(applicationData);
|
||||
|
||||
return branchSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* New branch
|
||||
*
|
||||
* @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 the branch session
|
||||
*/
|
||||
public static BranchSession newBranch(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid(xid);
|
||||
branchSession.setBranchId(branchId);
|
||||
branchSession.setBranchType(branchType);
|
||||
branchSession.setResourceId(resourceId);
|
||||
branchSession.setApplicationData(applicationData);
|
||||
return branchSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* End committed.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public static void endCommitted(GlobalSession globalSession) throws TransactionException {
|
||||
globalSession.changeStatus(GlobalStatus.Committed);
|
||||
globalSession.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* End commit failed.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public static void endCommitFailed(GlobalSession globalSession) throws TransactionException {
|
||||
globalSession.changeStatus(GlobalStatus.CommitFailed);
|
||||
globalSession.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* End rollbacked.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public static void endRollbacked(GlobalSession globalSession) throws TransactionException {
|
||||
GlobalStatus currentStatus = globalSession.getStatus();
|
||||
if (isTimeoutGlobalStatus(currentStatus)) {
|
||||
globalSession.changeStatus(GlobalStatus.TimeoutRollbacked);
|
||||
} else {
|
||||
globalSession.changeStatus(GlobalStatus.Rollbacked);
|
||||
}
|
||||
globalSession.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* End rollback failed.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
public static void endRollbackFailed(GlobalSession globalSession) throws TransactionException {
|
||||
GlobalStatus currentStatus = globalSession.getStatus();
|
||||
if (isTimeoutGlobalStatus(currentStatus)) {
|
||||
globalSession.changeStatus(GlobalStatus.TimeoutRollbackFailed);
|
||||
} else {
|
||||
globalSession.changeStatus(GlobalStatus.RollbackFailed);
|
||||
}
|
||||
globalSession.end();
|
||||
}
|
||||
|
||||
public static boolean isTimeoutGlobalStatus(GlobalStatus status) {
|
||||
return status == GlobalStatus.TimeoutRollbacked
|
||||
|| status == GlobalStatus.TimeoutRollbackFailed
|
||||
|| status == GlobalStatus.TimeoutRollbacking
|
||||
|| status == GlobalStatus.TimeoutRollbackRetrying;
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreach global sessions.
|
||||
*
|
||||
* @param sessions the global sessions
|
||||
* @param handler the handler
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public static void forEach(Collection<GlobalSession> sessions, GlobalSessionHandler handler) {
|
||||
for (GlobalSession globalSession : sessions) {
|
||||
try {
|
||||
MDC.put(RootContext.MDC_KEY_XID, globalSession.getXid());
|
||||
handler.handle(globalSession);
|
||||
} catch (Throwable th) {
|
||||
LOGGER.error("handle global session failed: {}", globalSession.getXid(), th);
|
||||
} finally {
|
||||
MDC.remove(RootContext.MDC_KEY_XID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreach branch sessions.
|
||||
*
|
||||
* @param sessions the branch session
|
||||
* @param handler the handler
|
||||
* @since 1.5.0
|
||||
*/
|
||||
public static Boolean forEach(Collection<BranchSession> sessions, BranchSessionHandler handler) throws TransactionException {
|
||||
Boolean result;
|
||||
for (BranchSession branchSession : sessions) {
|
||||
try {
|
||||
MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
|
||||
result = handler.handle(branchSession);
|
||||
if (result == null) {
|
||||
continue;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
MDC.remove(RootContext.MDC_KEY_BRANCH_ID);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
438
server/src/main/java/io/seata/server/session/SessionHolder.java
Normal file
438
server/src/main/java/io/seata/server/session/SessionHolder.java
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.store.StoreMode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
import static io.seata.common.Constants.ASYNC_COMMITTING;
|
||||
import static io.seata.common.Constants.RETRY_COMMITTING;
|
||||
import static io.seata.common.Constants.RETRY_ROLLBACKING;
|
||||
import static io.seata.common.Constants.TX_TIMEOUT_CHECK;
|
||||
import static io.seata.common.Constants.UNDOLOG_DELETE;
|
||||
|
||||
/**
|
||||
* The type Session holder.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class SessionHolder {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionHolder.class);
|
||||
|
||||
/**
|
||||
* The constant CONFIG.
|
||||
*/
|
||||
protected static final Configuration CONFIG = ConfigurationFactory.getInstance();
|
||||
/**
|
||||
* The constant ROOT_SESSION_MANAGER_NAME.
|
||||
*/
|
||||
public static final String ROOT_SESSION_MANAGER_NAME = "root.data";
|
||||
/**
|
||||
* The constant ASYNC_COMMITTING_SESSION_MANAGER_NAME.
|
||||
*/
|
||||
public static final String ASYNC_COMMITTING_SESSION_MANAGER_NAME = "async.commit.data";
|
||||
/**
|
||||
* The constant RETRY_COMMITTING_SESSION_MANAGER_NAME.
|
||||
*/
|
||||
public static final String RETRY_COMMITTING_SESSION_MANAGER_NAME = "retry.commit.data";
|
||||
/**
|
||||
* The constant RETRY_ROLLBACKING_SESSION_MANAGER_NAME.
|
||||
*/
|
||||
public static final String RETRY_ROLLBACKING_SESSION_MANAGER_NAME = "retry.rollback.data";
|
||||
|
||||
/**
|
||||
* The default session store dir
|
||||
*/
|
||||
public static final String DEFAULT_SESSION_STORE_FILE_DIR = "sessionStore";
|
||||
|
||||
private static SessionManager ROOT_SESSION_MANAGER;
|
||||
private static SessionManager ASYNC_COMMITTING_SESSION_MANAGER;
|
||||
private static SessionManager RETRY_COMMITTING_SESSION_MANAGER;
|
||||
private static SessionManager RETRY_ROLLBACKING_SESSION_MANAGER;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param mode the store mode: file, db
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public static void init(String mode) {
|
||||
if (StringUtils.isBlank(mode)) {
|
||||
mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
|
||||
}
|
||||
StoreMode storeMode = StoreMode.get(mode);
|
||||
if (StoreMode.DB.equals(storeMode)) {
|
||||
ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
|
||||
ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
|
||||
new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
|
||||
RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
|
||||
new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
|
||||
RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
|
||||
new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
|
||||
} else if (StoreMode.FILE.equals(storeMode)) {
|
||||
String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR,
|
||||
DEFAULT_SESSION_STORE_FILE_DIR);
|
||||
if (StringUtils.isBlank(sessionStorePath)) {
|
||||
throw new StoreException("the {store.file.dir} is empty.");
|
||||
}
|
||||
ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
|
||||
new Object[] {ROOT_SESSION_MANAGER_NAME, sessionStorePath});
|
||||
ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
|
||||
new Class[] {String.class, String.class}, new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME, null});
|
||||
RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
|
||||
new Class[] {String.class, String.class}, new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME, null});
|
||||
RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
|
||||
new Class[] {String.class, String.class}, new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME, null});
|
||||
} else if (StoreMode.REDIS.equals(storeMode)) {
|
||||
ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.REDIS.getName());
|
||||
ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
|
||||
StoreMode.REDIS.getName(), new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
|
||||
RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
|
||||
StoreMode.REDIS.getName(), new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
|
||||
RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
|
||||
StoreMode.REDIS.getName(), new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
|
||||
} else {
|
||||
// unknown store
|
||||
throw new IllegalArgumentException("unknown store mode:" + mode);
|
||||
}
|
||||
reload(storeMode);
|
||||
}
|
||||
|
||||
//region reload
|
||||
|
||||
/**
|
||||
* Reload.
|
||||
*/
|
||||
protected static void reload(StoreMode storeMode) {
|
||||
if (ROOT_SESSION_MANAGER instanceof Reloadable) {
|
||||
((Reloadable) ROOT_SESSION_MANAGER).reload();
|
||||
}
|
||||
|
||||
Collection<GlobalSession> allSessions = ROOT_SESSION_MANAGER.allSessions();
|
||||
if (CollectionUtils.isNotEmpty(allSessions)) {
|
||||
List<GlobalSession> removeGlobalSessions = new ArrayList<>();
|
||||
Iterator<GlobalSession> iterator = allSessions.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
GlobalSession globalSession = iterator.next();
|
||||
GlobalStatus globalStatus = globalSession.getStatus();
|
||||
switch (globalStatus) {
|
||||
case UnKnown:
|
||||
case Committed:
|
||||
case CommitFailed:
|
||||
case Rollbacked:
|
||||
case RollbackFailed:
|
||||
case TimeoutRollbacked:
|
||||
case TimeoutRollbackFailed:
|
||||
case Finished:
|
||||
removeGlobalSessions.add(globalSession);
|
||||
break;
|
||||
case AsyncCommitting:
|
||||
if (storeMode == StoreMode.FILE) {
|
||||
queueToAsyncCommitting(globalSession);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
if (storeMode == StoreMode.FILE) {
|
||||
lockBranchSessions(globalSession.getSortedBranches());
|
||||
|
||||
switch (globalStatus) {
|
||||
case Committing:
|
||||
case CommitRetrying:
|
||||
queueToRetryCommit(globalSession);
|
||||
break;
|
||||
case Rollbacking:
|
||||
case RollbackRetrying:
|
||||
case TimeoutRollbacking:
|
||||
case TimeoutRollbackRetrying:
|
||||
queueToRetryRollback(globalSession);
|
||||
break;
|
||||
case Begin:
|
||||
globalSession.setActive(true);
|
||||
break;
|
||||
default:
|
||||
throw new ShouldNeverHappenException("NOT properly handled " + globalStatus);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (GlobalSession globalSession : removeGlobalSessions) {
|
||||
removeInErrorState(globalSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeInErrorState(GlobalSession globalSession) {
|
||||
try {
|
||||
LOGGER.warn("The global session should NOT be {}, remove it. xid = {}", globalSession.getStatus(), globalSession.getXid());
|
||||
ROOT_SESSION_MANAGER.removeGlobalSession(globalSession);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Remove global session succeed, xid = {}, status = {}", globalSession.getXid(), globalSession.getStatus());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Remove global session failed, xid = {}, status = {}", globalSession.getXid(), globalSession.getStatus(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void queueToAsyncCommitting(GlobalSession globalSession) {
|
||||
try {
|
||||
globalSession.addSessionLifecycleListener(getAsyncCommittingSessionManager());
|
||||
getAsyncCommittingSessionManager().addGlobalSession(globalSession);
|
||||
} catch (TransactionException e) {
|
||||
throw new ShouldNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void lockBranchSessions(ArrayList<BranchSession> branchSessions) {
|
||||
branchSessions.forEach(branchSession -> {
|
||||
try {
|
||||
branchSession.lock();
|
||||
} catch (TransactionException e) {
|
||||
throw new ShouldNeverHappenException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void queueToRetryCommit(GlobalSession globalSession) {
|
||||
try {
|
||||
globalSession.addSessionLifecycleListener(getRetryCommittingSessionManager());
|
||||
getRetryCommittingSessionManager().addGlobalSession(globalSession);
|
||||
} catch (TransactionException e) {
|
||||
throw new ShouldNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void queueToRetryRollback(GlobalSession globalSession) {
|
||||
try {
|
||||
globalSession.addSessionLifecycleListener(getRetryRollbackingSessionManager());
|
||||
getRetryRollbackingSessionManager().addGlobalSession(globalSession);
|
||||
} catch (TransactionException e) {
|
||||
throw new ShouldNeverHappenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region get session manager
|
||||
|
||||
/**
|
||||
* Gets root session manager.
|
||||
*
|
||||
* @return the root session manager
|
||||
*/
|
||||
public static SessionManager getRootSessionManager() {
|
||||
if (ROOT_SESSION_MANAGER == null) {
|
||||
throw new ShouldNeverHappenException("SessionManager is NOT init!");
|
||||
}
|
||||
return ROOT_SESSION_MANAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets async committing session manager.
|
||||
*
|
||||
* @return the async committing session manager
|
||||
*/
|
||||
public static SessionManager getAsyncCommittingSessionManager() {
|
||||
if (ASYNC_COMMITTING_SESSION_MANAGER == null) {
|
||||
throw new ShouldNeverHappenException("SessionManager is NOT init!");
|
||||
}
|
||||
return ASYNC_COMMITTING_SESSION_MANAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets retry committing session manager.
|
||||
*
|
||||
* @return the retry committing session manager
|
||||
*/
|
||||
public static SessionManager getRetryCommittingSessionManager() {
|
||||
if (RETRY_COMMITTING_SESSION_MANAGER == null) {
|
||||
throw new ShouldNeverHappenException("SessionManager is NOT init!");
|
||||
}
|
||||
return RETRY_COMMITTING_SESSION_MANAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets retry rollbacking session manager.
|
||||
*
|
||||
* @return the retry rollbacking session manager
|
||||
*/
|
||||
public static SessionManager getRetryRollbackingSessionManager() {
|
||||
if (RETRY_ROLLBACKING_SESSION_MANAGER == null) {
|
||||
throw new ShouldNeverHappenException("SessionManager is NOT init!");
|
||||
}
|
||||
return RETRY_ROLLBACKING_SESSION_MANAGER;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* Find global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @return the global session
|
||||
*/
|
||||
public static GlobalSession findGlobalSession(String xid) {
|
||||
return findGlobalSession(xid, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param withBranchSessions the withBranchSessions
|
||||
* @return the global session
|
||||
*/
|
||||
public static GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
return getRootSessionManager().findGlobalSession(xid, withBranchSessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* lock and execute
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param lockCallable the lock Callable
|
||||
* @return the value
|
||||
*/
|
||||
public static <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
return getRootSessionManager().lockAndExecute(globalSession, lockCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* retry rollbacking lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean retryRollbackingLock() {
|
||||
return getRootSessionManager().scheduledLock(RETRY_ROLLBACKING);
|
||||
}
|
||||
|
||||
/**
|
||||
* retry committing lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean retryCommittingLock() {
|
||||
return getRootSessionManager().scheduledLock(RETRY_COMMITTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* async committing lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean asyncCommittingLock() {
|
||||
return getRootSessionManager().scheduledLock(ASYNC_COMMITTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* tx timeout check lOck
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean txTimeoutCheckLock() {
|
||||
return getRootSessionManager().scheduledLock(TX_TIMEOUT_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* undolog delete lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean undoLogDeleteLock() {
|
||||
return getRootSessionManager().scheduledLock(UNDOLOG_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* un retry rollbacking lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean unRetryRollbackingLock() {
|
||||
return getRootSessionManager().unScheduledLock(RETRY_ROLLBACKING);
|
||||
}
|
||||
|
||||
/**
|
||||
* un retry committing lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean unRetryCommittingLock() {
|
||||
return getRootSessionManager().unScheduledLock(RETRY_COMMITTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* un async committing lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean unAsyncCommittingLock() {
|
||||
return getRootSessionManager().unScheduledLock(ASYNC_COMMITTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* un tx timeout check lOck
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean unTxTimeoutCheckLock() {
|
||||
return getRootSessionManager().unScheduledLock(TX_TIMEOUT_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* un undolog delete lock
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean unUndoLogDeleteLock() {
|
||||
return getRootSessionManager().unScheduledLock(UNDOLOG_DELETE);
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
if (ROOT_SESSION_MANAGER != null) {
|
||||
ROOT_SESSION_MANAGER.destroy();
|
||||
}
|
||||
if (ASYNC_COMMITTING_SESSION_MANAGER != null) {
|
||||
ASYNC_COMMITTING_SESSION_MANAGER.destroy();
|
||||
}
|
||||
if (RETRY_COMMITTING_SESSION_MANAGER != null) {
|
||||
RETRY_COMMITTING_SESSION_MANAGER.destroy();
|
||||
}
|
||||
if (RETRY_ROLLBACKING_SESSION_MANAGER != null) {
|
||||
RETRY_ROLLBACKING_SESSION_MANAGER.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
|
||||
/**
|
||||
* The interface Session lifecycle.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface SessionLifecycle {
|
||||
|
||||
/**
|
||||
* Begin.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void begin() throws TransactionException;
|
||||
|
||||
/**
|
||||
* Change status.
|
||||
*
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void changeStatus(GlobalStatus status) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Change branch status.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void changeBranchStatus(BranchSession branchSession, BranchStatus status) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Add branch.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void addBranch(BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Remove branch.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void removeBranch(BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Is active boolean.
|
||||
*
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Close.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void close() throws TransactionException;
|
||||
|
||||
/**
|
||||
* End.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void end() throws TransactionException;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
|
||||
/**
|
||||
* The interface Session lifecycle listener.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface SessionLifecycleListener {
|
||||
|
||||
/**
|
||||
* On begin.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onBegin(GlobalSession globalSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* On status change.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onStatusChange(GlobalSession globalSession, GlobalStatus status) throws TransactionException;
|
||||
|
||||
/**
|
||||
* On branch status change.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchSession the branch session
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSession, BranchStatus status)
|
||||
throws TransactionException;
|
||||
|
||||
/**
|
||||
* On add branch.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchSession the branch session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onAddBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* On remove branch.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param branchSession the branch session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onRemoveBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* On close.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onClose(GlobalSession globalSession) throws TransactionException;
|
||||
|
||||
/**
|
||||
* On end.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void onEnd(GlobalSession globalSession) throws TransactionException;
|
||||
}
|
||||
146
server/src/main/java/io/seata/server/session/SessionManager.java
Normal file
146
server/src/main/java/io/seata/server/session/SessionManager.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.rpc.Disposable;
|
||||
|
||||
/**
|
||||
* The interface Session manager.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public interface SessionManager extends SessionLifecycleListener, Disposable {
|
||||
|
||||
/**
|
||||
* Add global session.
|
||||
*
|
||||
* @param session the session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void addGlobalSession(GlobalSession session) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Find global session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @return the global session
|
||||
*/
|
||||
GlobalSession findGlobalSession(String xid) ;
|
||||
|
||||
/**
|
||||
* Find global session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param withBranchSessions the withBranchSessions
|
||||
* @return the global session
|
||||
*/
|
||||
GlobalSession findGlobalSession(String xid, boolean withBranchSessions);
|
||||
|
||||
/**
|
||||
* Update global session status.
|
||||
*
|
||||
* @param session the session
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Remove global session.
|
||||
*
|
||||
* @param session the session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void removeGlobalSession(GlobalSession session) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Add branch session.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param session the session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void addBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Update branch session status.
|
||||
*
|
||||
* @param session the session
|
||||
* @param status the status
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void updateBranchSessionStatus(BranchSession session, BranchStatus status) throws TransactionException;
|
||||
|
||||
/**
|
||||
* Remove branch session.
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param session the session
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
void removeBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException;
|
||||
|
||||
/**
|
||||
* All sessions collection.
|
||||
*
|
||||
* @return the collection
|
||||
*/
|
||||
Collection<GlobalSession> allSessions();
|
||||
|
||||
/**
|
||||
* Find global sessions list.
|
||||
*
|
||||
* @param condition the condition
|
||||
* @return the list
|
||||
*/
|
||||
List<GlobalSession> findGlobalSessions(SessionCondition condition);
|
||||
|
||||
/**
|
||||
* lock and execute
|
||||
*
|
||||
* @param globalSession the global session
|
||||
* @param lockCallable the lock Callable
|
||||
* @return the value
|
||||
*/
|
||||
<T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException;
|
||||
|
||||
/**
|
||||
* scheduled lock
|
||||
*
|
||||
* @param key the lock key
|
||||
* @return the boolean
|
||||
*/
|
||||
default boolean scheduledLock(String key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* un scheduled lock
|
||||
*
|
||||
* @param key the lock key
|
||||
* @return the boolean
|
||||
*/
|
||||
default boolean unScheduledLock(String key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage;
|
||||
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.store.BranchTransactionDO;
|
||||
import io.seata.core.store.GlobalTransactionDO;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
|
||||
/**
|
||||
* The session converter
|
||||
*
|
||||
* @author wangzhongxiang
|
||||
*/
|
||||
public class SessionConverter {
|
||||
|
||||
public static GlobalSession convertGlobalSession(GlobalTransactionDO globalTransactionDO) {
|
||||
if (globalTransactionDO == null) {
|
||||
return null;
|
||||
}
|
||||
GlobalSession session = new GlobalSession(globalTransactionDO.getApplicationId(),
|
||||
globalTransactionDO.getTransactionServiceGroup(),
|
||||
globalTransactionDO.getTransactionName(),
|
||||
globalTransactionDO.getTimeout());
|
||||
session.setXid(globalTransactionDO.getXid());
|
||||
session.setTransactionId(globalTransactionDO.getTransactionId());
|
||||
session.setStatus(GlobalStatus.get(globalTransactionDO.getStatus()));
|
||||
session.setApplicationData(globalTransactionDO.getApplicationData());
|
||||
session.setBeginTime(globalTransactionDO.getBeginTime());
|
||||
return session;
|
||||
}
|
||||
|
||||
public static BranchSession convertBranchSession(BranchTransactionDO branchTransactionDO) {
|
||||
if (branchTransactionDO == null) {
|
||||
return null;
|
||||
}
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid(branchTransactionDO.getXid());
|
||||
branchSession.setTransactionId(branchTransactionDO.getTransactionId());
|
||||
branchSession.setApplicationData(branchTransactionDO.getApplicationData());
|
||||
branchSession.setBranchId(branchTransactionDO.getBranchId());
|
||||
branchSession.setBranchType(BranchType.valueOf(branchTransactionDO.getBranchType()));
|
||||
branchSession.setResourceId(branchTransactionDO.getResourceId());
|
||||
branchSession.setClientId(branchTransactionDO.getClientId());
|
||||
branchSession.setResourceGroupId(branchTransactionDO.getResourceGroupId());
|
||||
branchSession.setStatus(BranchStatus.get(branchTransactionDO.getStatus()));
|
||||
return branchSession;
|
||||
}
|
||||
|
||||
public static GlobalTransactionDO convertGlobalTransactionDO(SessionStorable session) {
|
||||
if (session == null || !(session instanceof GlobalSession)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session));
|
||||
}
|
||||
GlobalSession globalSession = (GlobalSession)session;
|
||||
|
||||
GlobalTransactionDO globalTransactionDO = new GlobalTransactionDO();
|
||||
globalTransactionDO.setXid(globalSession.getXid());
|
||||
globalTransactionDO.setStatus(globalSession.getStatus().getCode());
|
||||
globalTransactionDO.setApplicationId(globalSession.getApplicationId());
|
||||
globalTransactionDO.setBeginTime(globalSession.getBeginTime());
|
||||
globalTransactionDO.setTimeout(globalSession.getTimeout());
|
||||
globalTransactionDO.setTransactionId(globalSession.getTransactionId());
|
||||
globalTransactionDO.setTransactionName(globalSession.getTransactionName());
|
||||
globalTransactionDO.setTransactionServiceGroup(globalSession.getTransactionServiceGroup());
|
||||
globalTransactionDO.setApplicationData(globalSession.getApplicationData());
|
||||
return globalTransactionDO;
|
||||
}
|
||||
|
||||
public static BranchTransactionDO convertBranchTransactionDO(SessionStorable session) {
|
||||
if (session == null || !(session instanceof BranchSession)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session));
|
||||
}
|
||||
BranchSession branchSession = (BranchSession)session;
|
||||
BranchTransactionDO branchTransactionDO = new BranchTransactionDO();
|
||||
branchTransactionDO.setXid(branchSession.getXid());
|
||||
branchTransactionDO.setBranchId(branchSession.getBranchId());
|
||||
branchTransactionDO.setBranchType(branchSession.getBranchType().name());
|
||||
branchTransactionDO.setClientId(branchSession.getClientId());
|
||||
branchTransactionDO.setResourceGroupId(branchSession.getResourceGroupId());
|
||||
branchTransactionDO.setTransactionId(branchSession.getTransactionId());
|
||||
branchTransactionDO.setApplicationData(branchSession.getApplicationData());
|
||||
branchTransactionDO.setResourceId(branchSession.getResourceId());
|
||||
branchTransactionDO.setStatus(branchSession.getStatus().getCode());
|
||||
return branchTransactionDO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.lock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.seata.common.executor.Initialize;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.Locker;
|
||||
import io.seata.core.store.db.DataSourceProvider;
|
||||
import io.seata.server.lock.AbstractLockManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
/**
|
||||
* The type db lock manager.
|
||||
*
|
||||
* @author zjinlei
|
||||
*/
|
||||
@LoadLevel(name = "db")
|
||||
public class DataBaseLockManager extends AbstractLockManager implements Initialize {
|
||||
|
||||
/**
|
||||
* The locker.
|
||||
*/
|
||||
private Locker locker;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// init dataSource
|
||||
String datasourceType = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE);
|
||||
DataSource lockStoreDataSource = EnhancedServiceLoader.load(DataSourceProvider.class, datasourceType).provide();
|
||||
locker = new DataBaseLocker(lockStoreDataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(BranchSession branchSession) throws TransactionException {
|
||||
try {
|
||||
return getLocker().releaseLock(branchSession.getXid(), branchSession.getBranchId());
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock error, xid {}, branchId:{}", branchSession.getXid(), branchSession.getBranchId(), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locker getLocker(BranchSession branchSession) {
|
||||
return locker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException {
|
||||
List<BranchSession> branchSessions = globalSession.getBranchSessions();
|
||||
if (CollectionUtils.isEmpty(branchSessions)) {
|
||||
return true;
|
||||
}
|
||||
List<Long> branchIds = branchSessions.stream().map(BranchSession::getBranchId).collect(Collectors.toList());
|
||||
try {
|
||||
return getLocker().releaseLock(globalSession.getXid(), branchIds);
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock globalSession error, xid:{} branchIds:{}", globalSession.getXid(),
|
||||
CollectionUtils.toString(branchIds), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.lock;
|
||||
|
||||
import java.util.List;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.seata.common.exception.DataAccessException;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.lock.AbstractLocker;
|
||||
import io.seata.core.lock.RowLock;
|
||||
import io.seata.core.store.LockStore;
|
||||
|
||||
/**
|
||||
* The type Data base locker.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class DataBaseLocker extends AbstractLocker {
|
||||
|
||||
private LockStore lockStore;
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base locker.
|
||||
*/
|
||||
public DataBaseLocker() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base locker.
|
||||
*
|
||||
* @param logStoreDataSource the log store data source
|
||||
*/
|
||||
public DataBaseLocker(DataSource logStoreDataSource) {
|
||||
lockStore = new LockStoreDataBaseDAO(logStoreDataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(List<RowLock> locks) {
|
||||
if (CollectionUtils.isEmpty(locks)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return lockStore.acquireLock(convertToLockDO(locks));
|
||||
} catch (StoreException e) {
|
||||
throw e;
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("AcquireLock error, locks:{}", CollectionUtils.toString(locks), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(List<RowLock> locks) {
|
||||
if (CollectionUtils.isEmpty(locks)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return lockStore.unLock(convertToLockDO(locks));
|
||||
} catch (StoreException e) {
|
||||
throw e;
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock error, locks:{}", CollectionUtils.toString(locks), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(String xid, Long branchId) {
|
||||
try {
|
||||
return lockStore.unLock(xid, branchId);
|
||||
} catch (StoreException e) {
|
||||
throw e;
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock by branchId error, xid {}, branchId:{}", xid, branchId, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(String xid, List<Long> branchIds) {
|
||||
if (CollectionUtils.isEmpty(branchIds)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return lockStore.unLock(xid, branchIds);
|
||||
} catch (StoreException e) {
|
||||
throw e;
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock by branchIds error, xid {}, branchIds:{}", xid, CollectionUtils.toString(branchIds), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(List<RowLock> locks) {
|
||||
if (CollectionUtils.isEmpty(locks)) {
|
||||
// no lock
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return lockStore.isLockable(convertToLockDO(locks));
|
||||
} catch (DataAccessException e) {
|
||||
throw e;
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("isLockable error, locks:{}", CollectionUtils.toString(locks), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets lock store.
|
||||
*
|
||||
* @param lockStore the lock store
|
||||
*/
|
||||
public void setLockStore(LockStore lockStore) {
|
||||
this.lockStore = lockStore;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.lock;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.seata.common.exception.DataAccessException;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.IOUtil;
|
||||
import io.seata.common.util.LambdaUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.constants.ServerTableColumnsName;
|
||||
import io.seata.core.store.LockDO;
|
||||
import io.seata.core.store.LockStore;
|
||||
import io.seata.core.store.db.sql.lock.LockStoreSqlFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_LOCK_DB_TABLE;
|
||||
|
||||
/**
|
||||
* The type Data base lock store.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class LockStoreDataBaseDAO implements LockStore {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LockStoreDataBaseDAO.class);
|
||||
|
||||
/**
|
||||
* The constant CONFIG.
|
||||
*/
|
||||
protected static final Configuration CONFIG = ConfigurationFactory.getInstance();
|
||||
|
||||
/**
|
||||
* The Lock store data source.
|
||||
*/
|
||||
protected DataSource lockStoreDataSource;
|
||||
|
||||
/**
|
||||
* The Lock table.
|
||||
*/
|
||||
protected String lockTable;
|
||||
|
||||
/**
|
||||
* The Db type.
|
||||
*/
|
||||
protected String dbType;
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base lock store dao.
|
||||
*
|
||||
* @param lockStoreDataSource the log store data source
|
||||
*/
|
||||
public LockStoreDataBaseDAO(DataSource lockStoreDataSource) {
|
||||
this.lockStoreDataSource = lockStoreDataSource;
|
||||
lockTable = CONFIG.getConfig(ConfigurationKeys.LOCK_DB_TABLE, DEFAULT_LOCK_DB_TABLE);
|
||||
dbType = CONFIG.getConfig(ConfigurationKeys.STORE_DB_TYPE);
|
||||
if (StringUtils.isBlank(dbType)) {
|
||||
throw new StoreException("there must be db type.");
|
||||
}
|
||||
if (lockStoreDataSource == null) {
|
||||
throw new StoreException("there must be lockStoreDataSource.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(LockDO lockDO) {
|
||||
return acquireLock(Collections.singletonList(lockDO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(List<LockDO> lockDOs) {
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
Set<String> dbExistedRowKeys = new HashSet<>();
|
||||
boolean originalAutoCommit = true;
|
||||
if (lockDOs.size() > 1) {
|
||||
lockDOs = lockDOs.stream().filter(LambdaUtils.distinctByKey(LockDO::getRowKey)).collect(Collectors.toList());
|
||||
}
|
||||
try {
|
||||
conn = lockStoreDataSource.getConnection();
|
||||
if (originalAutoCommit = conn.getAutoCommit()) {
|
||||
conn.setAutoCommit(false);
|
||||
}
|
||||
//check lock
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
sj.add("?");
|
||||
}
|
||||
boolean canLock = true;
|
||||
//query
|
||||
String checkLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getCheckLockableSql(lockTable, sj.toString());
|
||||
ps = conn.prepareStatement(checkLockSQL);
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
ps.setString(i + 1, lockDOs.get(i).getRowKey());
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
String currentXID = lockDOs.get(0).getXid();
|
||||
while (rs.next()) {
|
||||
String dbXID = rs.getString(ServerTableColumnsName.LOCK_TABLE_XID);
|
||||
if (!StringUtils.equals(dbXID, currentXID)) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
String dbPk = rs.getString(ServerTableColumnsName.LOCK_TABLE_PK);
|
||||
String dbTableName = rs.getString(ServerTableColumnsName.LOCK_TABLE_TABLE_NAME);
|
||||
Long dbBranchId = rs.getLong(ServerTableColumnsName.LOCK_TABLE_BRANCH_ID);
|
||||
LOGGER.info("Global lock on [{}:{}] is holding by xid {} branchId {}", dbTableName, dbPk, dbXID,
|
||||
dbBranchId);
|
||||
}
|
||||
canLock &= false;
|
||||
break;
|
||||
}
|
||||
dbExistedRowKeys.add(rs.getString(ServerTableColumnsName.LOCK_TABLE_ROW_KEY));
|
||||
}
|
||||
|
||||
if (!canLock) {
|
||||
conn.rollback();
|
||||
return false;
|
||||
}
|
||||
List<LockDO> unrepeatedLockDOs = null;
|
||||
if (CollectionUtils.isNotEmpty(dbExistedRowKeys)) {
|
||||
unrepeatedLockDOs = lockDOs.stream().filter(lockDO -> !dbExistedRowKeys.contains(lockDO.getRowKey()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
unrepeatedLockDOs = lockDOs;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(unrepeatedLockDOs)) {
|
||||
conn.rollback();
|
||||
return true;
|
||||
}
|
||||
//lock
|
||||
if (unrepeatedLockDOs.size() == 1) {
|
||||
LockDO lockDO = unrepeatedLockDOs.get(0);
|
||||
if (!doAcquireLock(conn, lockDO)) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Global lock acquire failed, xid {} branchId {} pk {}", lockDO.getXid(), lockDO.getBranchId(), lockDO.getPk());
|
||||
}
|
||||
conn.rollback();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!doAcquireLocks(conn, unrepeatedLockDOs)) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Global lock batch acquire failed, xid {} branchId {} pks {}", unrepeatedLockDOs.get(0).getXid(),
|
||||
unrepeatedLockDOs.get(0).getBranchId(), unrepeatedLockDOs.stream().map(lockDO -> lockDO.getPk()).collect(Collectors.toList()));
|
||||
}
|
||||
conn.rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
conn.commit();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps);
|
||||
if (conn != null) {
|
||||
try {
|
||||
if (originalAutoCommit) {
|
||||
conn.setAutoCommit(true);
|
||||
}
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unLock(LockDO lockDO) {
|
||||
return unLock(Collections.singletonList(lockDO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unLock(List<LockDO> lockDOs) {
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = lockStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
sj.add("?");
|
||||
}
|
||||
//batch release lock
|
||||
String batchDeleteSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getBatchDeleteLockSql(lockTable, sj.toString());
|
||||
ps = conn.prepareStatement(batchDeleteSQL);
|
||||
ps.setString(1, lockDOs.get(0).getXid());
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
ps.setString(i + 2, lockDOs.get(i).getRowKey());
|
||||
}
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unLock(String xid, Long branchId) {
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = lockStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
//batch release lock by branch
|
||||
String batchDeleteSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getBatchDeleteLockSqlByBranch(lockTable);
|
||||
ps = conn.prepareStatement(batchDeleteSQL);
|
||||
ps.setString(1, xid);
|
||||
ps.setLong(2, branchId);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unLock(String xid, List<Long> branchIds) {
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = lockStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
branchIds.forEach(branchId -> sj.add("?"));
|
||||
//batch release lock by branch list
|
||||
String batchDeleteSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getBatchDeleteLockSqlByBranchs(lockTable, sj.toString());
|
||||
ps = conn.prepareStatement(batchDeleteSQL);
|
||||
ps.setString(1, xid);
|
||||
for (int i = 0; i < branchIds.size(); i++) {
|
||||
ps.setLong(i + 2, branchIds.get(i));
|
||||
}
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(List<LockDO> lockDOs) {
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = lockStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
if (!checkLockable(conn, lockDOs)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do acquire lock boolean.
|
||||
*
|
||||
* @param conn the conn
|
||||
* @param lockDO the lock do
|
||||
* @return the boolean
|
||||
*/
|
||||
protected boolean doAcquireLock(Connection conn, LockDO lockDO) {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
//insert
|
||||
String insertLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getInsertLockSQL(lockTable);
|
||||
ps = conn.prepareStatement(insertLockSQL);
|
||||
ps.setString(1, lockDO.getXid());
|
||||
ps.setLong(2, lockDO.getTransactionId());
|
||||
ps.setLong(3, lockDO.getBranchId());
|
||||
ps.setString(4, lockDO.getResourceId());
|
||||
ps.setString(5, lockDO.getTableName());
|
||||
ps.setString(6, lockDO.getPk());
|
||||
ps.setString(7, lockDO.getRowKey());
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do acquire lock boolean.
|
||||
*
|
||||
* @param conn the conn
|
||||
* @param lockDOs the lock do list
|
||||
* @return the boolean
|
||||
*/
|
||||
protected boolean doAcquireLocks(Connection conn, List<LockDO> lockDOs) {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
//insert
|
||||
String insertLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getInsertLockSQL(lockTable);
|
||||
ps = conn.prepareStatement(insertLockSQL);
|
||||
for (LockDO lockDO : lockDOs) {
|
||||
ps.setString(1, lockDO.getXid());
|
||||
ps.setLong(2, lockDO.getTransactionId());
|
||||
ps.setLong(3, lockDO.getBranchId());
|
||||
ps.setString(4, lockDO.getResourceId());
|
||||
ps.setString(5, lockDO.getTableName());
|
||||
ps.setString(6, lockDO.getPk());
|
||||
ps.setString(7, lockDO.getRowKey());
|
||||
ps.addBatch();
|
||||
}
|
||||
return ps.executeBatch().length == lockDOs.size();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Global lock batch acquire error: {}", e.getMessage(), e);
|
||||
//return false,let the caller go to conn.rollabck()
|
||||
return false;
|
||||
} finally {
|
||||
IOUtil.close(ps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check lock boolean.
|
||||
*
|
||||
* @param conn the conn
|
||||
* @param lockDOs the lock do
|
||||
* @return the boolean
|
||||
*/
|
||||
protected boolean checkLockable(Connection conn, List<LockDO> lockDOs) {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
sj.add("?");
|
||||
}
|
||||
|
||||
//query
|
||||
String checkLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getCheckLockableSql(lockTable, sj.toString());
|
||||
ps = conn.prepareStatement(checkLockSQL);
|
||||
for (int i = 0; i < lockDOs.size(); i++) {
|
||||
ps.setString(i + 1, lockDOs.get(i).getRowKey());
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
String xid = rs.getString("xid");
|
||||
if (!StringUtils.equals(xid, lockDOs.get(0).getXid())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets lock table.
|
||||
*
|
||||
* @param lockTable the lock table
|
||||
*/
|
||||
public void setLockTable(String lockTable) {
|
||||
this.lockTable = lockTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets db type.
|
||||
*
|
||||
* @param dbType the db type
|
||||
*/
|
||||
public void setDbType(String dbType) {
|
||||
this.dbType = dbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets log store data source.
|
||||
*
|
||||
* @param lockStoreDataSource the log store data source
|
||||
*/
|
||||
public void setLogStoreDataSource(DataSource lockStoreDataSource) {
|
||||
this.lockStoreDataSource = lockStoreDataSource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.executor.Initialize;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.AbstractSessionManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import io.seata.server.storage.db.store.DataBaseTransactionStoreManager;
|
||||
import io.seata.server.store.TransactionStoreManager.LogOperation;
|
||||
import io.seata.common.loader.Scope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The Data base session manager.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
@LoadLevel(name = "db", scope = Scope.PROTOTYPE)
|
||||
public class DataBaseSessionManager extends AbstractSessionManager
|
||||
implements Initialize {
|
||||
|
||||
/**
|
||||
* The constant LOGGER.
|
||||
*/
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(DataBaseSessionManager.class);
|
||||
|
||||
/**
|
||||
* The Task name.
|
||||
*/
|
||||
protected String taskName;
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base session manager.
|
||||
*/
|
||||
public DataBaseSessionManager() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base session manager.
|
||||
*
|
||||
* @param name the name
|
||||
*/
|
||||
public DataBaseSessionManager(String name) {
|
||||
super();
|
||||
this.taskName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
transactionStoreManager = DataBaseTransactionStoreManager.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
if (StringUtils.isBlank(taskName)) {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_ADD, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addGlobalSession failed.");
|
||||
}
|
||||
} else {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addGlobalSession failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status) throws TransactionException {
|
||||
if (StringUtils.isNotBlank(taskName)) {
|
||||
return;
|
||||
}
|
||||
session.setStatus(status);
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("updateGlobalSessionStatus failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove globalSession
|
||||
* 1. rootSessionManager remove normal globalSession
|
||||
* 2. retryCommitSessionManager and retryRollbackSessionManager remove retry expired globalSession
|
||||
* @param session the session
|
||||
* @throws TransactionException
|
||||
*/
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_REMOVE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("removeGlobalSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException {
|
||||
if (StringUtils.isNotBlank(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_ADD, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addBranchSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBranchSessionStatus(BranchSession session, BranchStatus status) throws TransactionException {
|
||||
if (StringUtils.isNotBlank(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("updateBranchSessionStatus failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException {
|
||||
if (StringUtils.isNotBlank(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_REMOVE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("removeBranchSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid) {
|
||||
return this.findGlobalSession(xid, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
return transactionStoreManager.readSession(xid, withBranchSessions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GlobalSession> allSessions() {
|
||||
// get by taskName
|
||||
if (SessionHolder.ASYNC_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(GlobalStatus.AsyncCommitting));
|
||||
} else if (SessionHolder.RETRY_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.CommitRetrying, GlobalStatus.Committing}));
|
||||
} else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.RollbackRetrying,
|
||||
GlobalStatus.Rollbacking, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying}));
|
||||
} else {
|
||||
// all data
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {
|
||||
GlobalStatus.UnKnown, GlobalStatus.Begin,
|
||||
GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking,
|
||||
GlobalStatus.RollbackRetrying,
|
||||
GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> findGlobalSessions(SessionCondition condition) {
|
||||
// nothing need to do
|
||||
return transactionStoreManager.readSession(condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
return lockCallable.call();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.store;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.store.BranchTransactionDO;
|
||||
import io.seata.core.store.GlobalTransactionDO;
|
||||
import io.seata.core.store.LogStore;
|
||||
import io.seata.core.store.db.DataSourceProvider;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.store.AbstractTransactionStoreManager;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
import io.seata.server.storage.SessionConverter;
|
||||
|
||||
/**
|
||||
* The type Database transaction store manager.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class DataBaseTransactionStoreManager extends AbstractTransactionStoreManager
|
||||
implements TransactionStoreManager {
|
||||
|
||||
private static volatile DataBaseTransactionStoreManager instance;
|
||||
|
||||
/**
|
||||
* The constant CONFIG.
|
||||
*/
|
||||
protected static final Configuration CONFIG = ConfigurationFactory.getInstance();
|
||||
|
||||
/**
|
||||
* The constant DEFAULT_LOG_QUERY_LIMIT.
|
||||
*/
|
||||
protected static final int DEFAULT_LOG_QUERY_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* The Log store.
|
||||
*/
|
||||
protected LogStore logStore;
|
||||
|
||||
/**
|
||||
* The Log query limit.
|
||||
*/
|
||||
protected int logQueryLimit;
|
||||
|
||||
/**
|
||||
* Get the instance.
|
||||
*/
|
||||
public static DataBaseTransactionStoreManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (DataBaseTransactionStoreManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new DataBaseTransactionStoreManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Database transaction store manager.
|
||||
*/
|
||||
private DataBaseTransactionStoreManager() {
|
||||
logQueryLimit = CONFIG.getInt(ConfigurationKeys.STORE_DB_LOG_QUERY_LIMIT, DEFAULT_LOG_QUERY_LIMIT);
|
||||
String datasourceType = CONFIG.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE);
|
||||
//init dataSource
|
||||
DataSource logStoreDataSource = EnhancedServiceLoader.load(DataSourceProvider.class, datasourceType).provide();
|
||||
logStore = new LogStoreDataBaseDAO(logStoreDataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeSession(LogOperation logOperation, SessionStorable session) {
|
||||
if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
|
||||
return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
|
||||
return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
|
||||
return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
|
||||
return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
|
||||
return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
|
||||
return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else {
|
||||
throw new StoreException("Unknown LogOperation:" + logOperation.name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param transactionId the transaction id
|
||||
* @return the global session
|
||||
*/
|
||||
public GlobalSession readSession(Long transactionId) {
|
||||
//global transaction
|
||||
GlobalTransactionDO globalTransactionDO = logStore.queryGlobalTransactionDO(transactionId);
|
||||
if (globalTransactionDO == null) {
|
||||
return null;
|
||||
}
|
||||
//branch transactions
|
||||
List<BranchTransactionDO> branchTransactionDOs = logStore.queryBranchTransactionDO(
|
||||
globalTransactionDO.getXid());
|
||||
return getGlobalSession(globalTransactionDO, branchTransactionDOs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @return the global session
|
||||
*/
|
||||
@Override
|
||||
public GlobalSession readSession(String xid) {
|
||||
return this.readSession(xid, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param withBranchSessions the withBranchSessions
|
||||
* @return the global session
|
||||
*/
|
||||
@Override
|
||||
public GlobalSession readSession(String xid, boolean withBranchSessions) {
|
||||
//global transaction
|
||||
GlobalTransactionDO globalTransactionDO = logStore.queryGlobalTransactionDO(xid);
|
||||
if (globalTransactionDO == null) {
|
||||
return null;
|
||||
}
|
||||
//branch transactions
|
||||
List<BranchTransactionDO> branchTransactionDOs = null;
|
||||
//reduce rpc with db when branchRegister and getGlobalStatus
|
||||
if (withBranchSessions) {
|
||||
branchTransactionDOs = logStore.queryBranchTransactionDO(globalTransactionDO.getXid());
|
||||
}
|
||||
return getGlobalSession(globalTransactionDO, branchTransactionDOs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session list.
|
||||
*
|
||||
* @param statuses the statuses
|
||||
* @return the list
|
||||
*/
|
||||
public List<GlobalSession> readSession(GlobalStatus[] statuses) {
|
||||
int[] states = new int[statuses.length];
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
states[i] = statuses[i].getCode();
|
||||
}
|
||||
//global transaction
|
||||
List<GlobalTransactionDO> globalTransactionDOs = logStore.queryGlobalTransactionDO(states, logQueryLimit);
|
||||
if (CollectionUtils.isEmpty(globalTransactionDOs)) {
|
||||
return null;
|
||||
}
|
||||
List<String> xids = globalTransactionDOs.stream().map(GlobalTransactionDO::getXid).collect(Collectors.toList());
|
||||
List<BranchTransactionDO> branchTransactionDOs = logStore.queryBranchTransactionDO(xids);
|
||||
Map<String, List<BranchTransactionDO>> branchTransactionDOsMap = branchTransactionDOs.stream()
|
||||
.collect(Collectors.groupingBy(BranchTransactionDO::getXid, LinkedHashMap::new, Collectors.toList()));
|
||||
return globalTransactionDOs.stream().map(globalTransactionDO ->
|
||||
getGlobalSession(globalTransactionDO, branchTransactionDOsMap.get(globalTransactionDO.getXid())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> readSession(SessionCondition sessionCondition) {
|
||||
if (StringUtils.isNotBlank(sessionCondition.getXid())) {
|
||||
GlobalSession globalSession = readSession(sessionCondition.getXid());
|
||||
if (globalSession != null) {
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
globalSessions.add(globalSession);
|
||||
return globalSessions;
|
||||
}
|
||||
} else if (sessionCondition.getTransactionId() != null) {
|
||||
GlobalSession globalSession = readSession(sessionCondition.getTransactionId());
|
||||
if (globalSession != null) {
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
globalSessions.add(globalSession);
|
||||
return globalSessions;
|
||||
}
|
||||
} else if (CollectionUtils.isNotEmpty(sessionCondition.getStatuses())) {
|
||||
return readSession(sessionCondition.getStatuses());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private GlobalSession getGlobalSession(GlobalTransactionDO globalTransactionDO,
|
||||
List<BranchTransactionDO> branchTransactionDOs) {
|
||||
GlobalSession globalSession = SessionConverter.convertGlobalSession(globalTransactionDO);
|
||||
//branch transactions
|
||||
if (CollectionUtils.isNotEmpty(branchTransactionDOs)) {
|
||||
for (BranchTransactionDO branchTransactionDO : branchTransactionDOs) {
|
||||
globalSession.add(SessionConverter.convertBranchSession(branchTransactionDO));
|
||||
}
|
||||
}
|
||||
return globalSession;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets log store.
|
||||
*
|
||||
* @param logStore the log store
|
||||
*/
|
||||
public void setLogStore(LogStore logStore) {
|
||||
this.logStore = logStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets log query limit.
|
||||
*
|
||||
* @param logQueryLimit the log query limit
|
||||
*/
|
||||
public void setLogQueryLimit(int logQueryLimit) {
|
||||
this.logQueryLimit = logQueryLimit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.db.store;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.seata.common.exception.DataAccessException;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.util.IOUtil;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.constants.ServerTableColumnsName;
|
||||
import io.seata.core.store.BranchTransactionDO;
|
||||
import io.seata.core.store.GlobalTransactionDO;
|
||||
import io.seata.core.store.LogStore;
|
||||
import io.seata.core.store.db.sql.log.LogStoreSqlsFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_STORE_DB_BRANCH_TABLE;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_STORE_DB_GLOBAL_TABLE;
|
||||
|
||||
/**
|
||||
* The type Log store data base dao.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class LogStoreDataBaseDAO implements LogStore {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LogStoreDataBaseDAO.class);
|
||||
|
||||
/**
|
||||
* The transaction name key
|
||||
*/
|
||||
private static final String TRANSACTION_NAME_KEY = "TRANSACTION_NAME";
|
||||
/**
|
||||
* The transaction name default size is 128
|
||||
*/
|
||||
private static final int TRANSACTION_NAME_DEFAULT_SIZE = 128;
|
||||
|
||||
/**
|
||||
* The constant CONFIG.
|
||||
*/
|
||||
protected static final Configuration CONFIG = ConfigurationFactory.getInstance();
|
||||
|
||||
/**
|
||||
* The Log store data source.
|
||||
*/
|
||||
protected DataSource logStoreDataSource = null;
|
||||
|
||||
/**
|
||||
* The Global table.
|
||||
*/
|
||||
protected String globalTable;
|
||||
|
||||
/**
|
||||
* The Branch table.
|
||||
*/
|
||||
protected String branchTable;
|
||||
|
||||
private String dbType;
|
||||
|
||||
private int transactionNameColumnSize = TRANSACTION_NAME_DEFAULT_SIZE;
|
||||
|
||||
/**
|
||||
* Instantiates a new Log store data base dao.
|
||||
*
|
||||
* @param logStoreDataSource the log store data source
|
||||
*/
|
||||
public LogStoreDataBaseDAO(DataSource logStoreDataSource) {
|
||||
this.logStoreDataSource = logStoreDataSource;
|
||||
globalTable = CONFIG.getConfig(ConfigurationKeys.STORE_DB_GLOBAL_TABLE,
|
||||
DEFAULT_STORE_DB_GLOBAL_TABLE);
|
||||
branchTable = CONFIG.getConfig(ConfigurationKeys.STORE_DB_BRANCH_TABLE,
|
||||
DEFAULT_STORE_DB_BRANCH_TABLE);
|
||||
dbType = CONFIG.getConfig(ConfigurationKeys.STORE_DB_TYPE);
|
||||
if (StringUtils.isBlank(dbType)) {
|
||||
throw new StoreException("there must be db type.");
|
||||
}
|
||||
if (logStoreDataSource == null) {
|
||||
throw new StoreException("there must be logStoreDataSource.");
|
||||
}
|
||||
// init transaction_name size
|
||||
initTransactionNameSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalTransactionDO queryGlobalTransactionDO(String xid) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryGlobalTransactionSQL(globalTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, xid);
|
||||
rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return convertGlobalTransactionDO(rs);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalTransactionDO queryGlobalTransactionDO(long transactionId) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryGlobalTransactionSQLByTransactionId(globalTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setLong(1, transactionId);
|
||||
rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return convertGlobalTransactionDO(rs);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalTransactionDO> queryGlobalTransactionDO(int[] statuses, int limit) {
|
||||
List<GlobalTransactionDO> ret = new ArrayList<>();
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
|
||||
String paramsPlaceHolder = org.apache.commons.lang.StringUtils.repeat("?", ",", statuses.length);
|
||||
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryGlobalTransactionSQLByStatus(globalTable, paramsPlaceHolder);
|
||||
ps = conn.prepareStatement(sql);
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
int status = statuses[i];
|
||||
ps.setInt(i + 1, status);
|
||||
}
|
||||
ps.setInt(statuses.length + 1, limit);
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
ret.add(convertGlobalTransactionDO(rs));
|
||||
}
|
||||
return ret;
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getInsertGlobalTransactionSQL(globalTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, globalTransactionDO.getXid());
|
||||
ps.setLong(2, globalTransactionDO.getTransactionId());
|
||||
ps.setInt(3, globalTransactionDO.getStatus());
|
||||
ps.setString(4, globalTransactionDO.getApplicationId());
|
||||
ps.setString(5, globalTransactionDO.getTransactionServiceGroup());
|
||||
String transactionName = globalTransactionDO.getTransactionName();
|
||||
transactionName = transactionName.length() > transactionNameColumnSize ? transactionName.substring(0,
|
||||
transactionNameColumnSize) : transactionName;
|
||||
ps.setString(6, transactionName);
|
||||
ps.setInt(7, globalTransactionDO.getTimeout());
|
||||
ps.setLong(8, globalTransactionDO.getBeginTime());
|
||||
ps.setString(9, globalTransactionDO.getApplicationData());
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getUpdateGlobalTransactionStatusSQL(globalTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setInt(1, globalTransactionDO.getStatus());
|
||||
ps.setString(2, globalTransactionDO.getXid());
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getDeleteGlobalTransactionSQL(globalTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, globalTransactionDO.getXid());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BranchTransactionDO> queryBranchTransactionDO(String xid) {
|
||||
List<BranchTransactionDO> rets = new ArrayList<>();
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryBranchTransaction(branchTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, xid);
|
||||
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
rets.add(convertBranchTransactionDO(rs));
|
||||
}
|
||||
return rets;
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BranchTransactionDO> queryBranchTransactionDO(List<String> xids) {
|
||||
int length = xids.size();
|
||||
List<BranchTransactionDO> rets = new ArrayList<>(length * 3);
|
||||
String paramsPlaceHolder = org.apache.commons.lang.StringUtils.repeat("?", ",", length);
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryBranchTransaction(branchTable, paramsPlaceHolder);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
for (int i = 0; i < length; i++) {
|
||||
ps.setString(i + 1, xids.get(i));
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
rets.add(convertBranchTransactionDO(rs));
|
||||
}
|
||||
return rets;
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insertBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getInsertBranchTransactionSQL(branchTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, branchTransactionDO.getXid());
|
||||
ps.setLong(2, branchTransactionDO.getTransactionId());
|
||||
ps.setLong(3, branchTransactionDO.getBranchId());
|
||||
ps.setString(4, branchTransactionDO.getResourceGroupId());
|
||||
ps.setString(5, branchTransactionDO.getResourceId());
|
||||
ps.setString(6, branchTransactionDO.getBranchType());
|
||||
ps.setInt(7, branchTransactionDO.getStatus());
|
||||
ps.setString(8, branchTransactionDO.getClientId());
|
||||
ps.setString(9, branchTransactionDO.getApplicationData());
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getUpdateBranchTransactionStatusSQL(branchTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setInt(1, branchTransactionDO.getStatus());
|
||||
ps.setString(2, branchTransactionDO.getXid());
|
||||
ps.setLong(3, branchTransactionDO.getBranchId());
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getDeleteBranchTransactionByBranchIdSQL(branchTable);
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, branchTransactionDO.getXid());
|
||||
ps.setLong(2, branchTransactionDO.getBranchId());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
} finally {
|
||||
IOUtil.close(ps, conn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentMaxSessionId(long high, long low) {
|
||||
String transMaxSql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryGlobalMax(globalTable);
|
||||
String branchMaxSql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getQueryBranchMax(branchTable);
|
||||
long maxTransId = getCurrentMaxSessionId(transMaxSql, high, low);
|
||||
long maxBranchId = getCurrentMaxSessionId(branchMaxSql, high, low);
|
||||
return maxBranchId > maxTransId ? maxBranchId : maxTransId;
|
||||
}
|
||||
|
||||
private long getCurrentMaxSessionId(String sql, long high, long low) {
|
||||
long max = 0;
|
||||
Connection conn = null;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
conn = logStoreDataSource.getConnection();
|
||||
conn.setAutoCommit(true);
|
||||
ps = conn.prepareStatement(sql);
|
||||
ps.setLong(1, high);
|
||||
ps.setLong(2, low);
|
||||
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
max = rs.getLong(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DataAccessException(e);
|
||||
} finally {
|
||||
IOUtil.close(rs, ps, conn);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private GlobalTransactionDO convertGlobalTransactionDO(ResultSet rs) throws SQLException {
|
||||
GlobalTransactionDO globalTransactionDO = new GlobalTransactionDO();
|
||||
globalTransactionDO.setXid(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_XID));
|
||||
globalTransactionDO.setStatus(rs.getInt(ServerTableColumnsName.GLOBAL_TABLE_STATUS));
|
||||
globalTransactionDO.setApplicationId(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_ID));
|
||||
globalTransactionDO.setBeginTime(rs.getLong(ServerTableColumnsName.GLOBAL_TABLE_BEGIN_TIME));
|
||||
globalTransactionDO.setTimeout(rs.getInt(ServerTableColumnsName.GLOBAL_TABLE_TIMEOUT));
|
||||
globalTransactionDO.setTransactionId(rs.getLong(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID));
|
||||
globalTransactionDO.setTransactionName(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_NAME));
|
||||
globalTransactionDO.setTransactionServiceGroup(
|
||||
rs.getString(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_SERVICE_GROUP));
|
||||
globalTransactionDO.setApplicationData(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_DATA));
|
||||
globalTransactionDO.setGmtCreate(rs.getTimestamp(ServerTableColumnsName.GLOBAL_TABLE_GMT_CREATE));
|
||||
globalTransactionDO.setGmtModified(rs.getTimestamp(ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED));
|
||||
return globalTransactionDO;
|
||||
}
|
||||
|
||||
private BranchTransactionDO convertBranchTransactionDO(ResultSet rs) throws SQLException {
|
||||
BranchTransactionDO branchTransactionDO = new BranchTransactionDO();
|
||||
branchTransactionDO.setResourceGroupId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_RESOURCE_GROUP_ID));
|
||||
branchTransactionDO.setStatus(rs.getInt(ServerTableColumnsName.BRANCH_TABLE_STATUS));
|
||||
branchTransactionDO.setApplicationData(rs.getString(ServerTableColumnsName.BRANCH_TABLE_APPLICATION_DATA));
|
||||
branchTransactionDO.setClientId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_CLIENT_ID));
|
||||
branchTransactionDO.setXid(rs.getString(ServerTableColumnsName.BRANCH_TABLE_XID));
|
||||
branchTransactionDO.setResourceId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_RESOURCE_ID));
|
||||
branchTransactionDO.setBranchId(rs.getLong(ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID));
|
||||
branchTransactionDO.setBranchType(rs.getString(ServerTableColumnsName.BRANCH_TABLE_BRANCH_TYPE));
|
||||
branchTransactionDO.setTransactionId(rs.getLong(ServerTableColumnsName.BRANCH_TABLE_TRANSACTION_ID));
|
||||
branchTransactionDO.setGmtCreate(rs.getTimestamp(ServerTableColumnsName.BRANCH_TABLE_GMT_CREATE));
|
||||
branchTransactionDO.setGmtModified(rs.getTimestamp(ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED));
|
||||
return branchTransactionDO;
|
||||
}
|
||||
|
||||
/**
|
||||
* the public modifier only for test
|
||||
*/
|
||||
public void initTransactionNameSize() {
|
||||
ColumnInfo columnInfo = queryTableStructure(globalTable, TRANSACTION_NAME_KEY);
|
||||
if (columnInfo == null) {
|
||||
LOGGER.warn("{} table or {} column not found", globalTable, TRANSACTION_NAME_KEY);
|
||||
return;
|
||||
}
|
||||
this.transactionNameColumnSize = columnInfo.getColumnSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* query column info from table
|
||||
*
|
||||
* @param tableName the table name
|
||||
* @param colName the column name
|
||||
* @return the column info
|
||||
*/
|
||||
private ColumnInfo queryTableStructure(final String tableName, String colName) {
|
||||
try (Connection conn = logStoreDataSource.getConnection()) {
|
||||
DatabaseMetaData dbmd = conn.getMetaData();
|
||||
String schema = getSchema(conn);
|
||||
ResultSet tableRs = dbmd.getTables(null, schema, "%", new String[]{"TABLE"});
|
||||
while (tableRs.next()) {
|
||||
String table = tableRs.getString("TABLE_NAME");
|
||||
if (StringUtils.equalsIgnoreCase(table, tableName)) {
|
||||
ResultSet columnRs = conn.getMetaData().getColumns(null, schema, table, null);
|
||||
while (columnRs.next()) {
|
||||
ColumnInfo info = new ColumnInfo();
|
||||
String columnName = columnRs.getString("COLUMN_NAME");
|
||||
info.setColumnName(columnName);
|
||||
String typeName = columnRs.getString("TYPE_NAME");
|
||||
info.setTypeName(typeName);
|
||||
int columnSize = columnRs.getInt("COLUMN_SIZE");
|
||||
info.setColumnSize(columnSize);
|
||||
String remarks = columnRs.getString("REMARKS");
|
||||
info.setRemarks(remarks);
|
||||
if (StringUtils.equalsIgnoreCase(columnName, colName)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("query transaction_name size fail, {}", e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getSchema(Connection conn) throws SQLException {
|
||||
if ("h2".equalsIgnoreCase(dbType)) {
|
||||
return null;
|
||||
} else if ("postgresql".equalsIgnoreCase(dbType)) {
|
||||
String sql = "select current_schema";
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
String schema = null;
|
||||
if (rs.next()) {
|
||||
schema = rs.getString(1);
|
||||
}
|
||||
return schema;
|
||||
} catch (SQLException e) {
|
||||
throw new StoreException(e);
|
||||
}
|
||||
} else {
|
||||
return conn.getMetaData().getUserName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets log store data source.
|
||||
*
|
||||
* @param logStoreDataSource the log store data source
|
||||
*/
|
||||
public void setLogStoreDataSource(DataSource logStoreDataSource) {
|
||||
this.logStoreDataSource = logStoreDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets global table.
|
||||
*
|
||||
* @param globalTable the global table
|
||||
*/
|
||||
public void setGlobalTable(String globalTable) {
|
||||
this.globalTable = globalTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets branch table.
|
||||
*
|
||||
* @param branchTable the branch table
|
||||
*/
|
||||
public void setBranchTable(String branchTable) {
|
||||
this.branchTable = branchTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets db type.
|
||||
*
|
||||
* @param dbType the db type
|
||||
*/
|
||||
public void setDbType(String dbType) {
|
||||
this.dbType = dbType;
|
||||
}
|
||||
|
||||
public int getTransactionNameColumnSize() {
|
||||
return transactionNameColumnSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* column info
|
||||
*/
|
||||
private static class ColumnInfo {
|
||||
private String columnName;
|
||||
private String typeName;
|
||||
private int columnSize;
|
||||
private String remarks;
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public void setColumnName(String columnName) {
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public void setTypeName(String typeName) {
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
public int getColumnSize() {
|
||||
return columnSize;
|
||||
}
|
||||
|
||||
public void setColumnSize(int columnSize) {
|
||||
this.columnSize = columnSize;
|
||||
}
|
||||
|
||||
public String getRemarks() {
|
||||
return remarks;
|
||||
}
|
||||
|
||||
public void setRemarks(String remarks) {
|
||||
this.remarks = remarks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file;
|
||||
|
||||
/**
|
||||
* @author lizhao
|
||||
*/
|
||||
public enum FlushDiskMode {
|
||||
/**
|
||||
* sync flush disk
|
||||
*/
|
||||
SYNC_MODEL("sync"),
|
||||
/**
|
||||
* async flush disk
|
||||
*/
|
||||
ASYNC_MODEL("async");
|
||||
|
||||
private String modeStr;
|
||||
|
||||
FlushDiskMode(String modeStr) {
|
||||
this.modeStr = modeStr;
|
||||
}
|
||||
|
||||
public static FlushDiskMode findDiskMode(String modeStr) {
|
||||
if (SYNC_MODEL.modeStr.equals(modeStr)) {
|
||||
return SYNC_MODEL;
|
||||
}
|
||||
return ASYNC_MODEL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The interface Reloadable store.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public interface ReloadableStore {
|
||||
|
||||
/**
|
||||
* Read write store.
|
||||
*
|
||||
* @param readSize the read size
|
||||
* @param isHistory the is history
|
||||
* @return the list
|
||||
*/
|
||||
List<TransactionWriteStore> readWriteStore(int readSize, boolean isHistory);
|
||||
|
||||
/**
|
||||
* Has remaining boolean.
|
||||
*
|
||||
* @param isHistory the is history
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean hasRemaining(boolean isHistory);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager.LogOperation;
|
||||
|
||||
/**
|
||||
* The type Transaction write store.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class TransactionWriteStore implements SessionStorable {
|
||||
private SessionStorable sessionRequest;
|
||||
private LogOperation operate;
|
||||
|
||||
/**
|
||||
* Instantiates a new Transaction write store.
|
||||
*
|
||||
* @param sessionRequest the session request
|
||||
* @param operate the operate
|
||||
*/
|
||||
public TransactionWriteStore(SessionStorable sessionRequest, LogOperation operate) {
|
||||
this.sessionRequest = sessionRequest;
|
||||
this.operate = operate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Transaction write store.
|
||||
*/
|
||||
public TransactionWriteStore() {}
|
||||
|
||||
/**
|
||||
* Gets session request.
|
||||
*
|
||||
* @return the session request
|
||||
*/
|
||||
public SessionStorable getSessionRequest() {
|
||||
return sessionRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets session request.
|
||||
*
|
||||
* @param sessionRequest the session request
|
||||
*/
|
||||
public void setSessionRequest(SessionStorable sessionRequest) {
|
||||
this.sessionRequest = sessionRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets operate.
|
||||
*
|
||||
* @return the operate
|
||||
*/
|
||||
public LogOperation getOperate() {
|
||||
return operate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets operate.
|
||||
*
|
||||
* @param operate the operate
|
||||
*/
|
||||
public void setOperate(LogOperation operate) {
|
||||
this.operate = operate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode() {
|
||||
byte[] bySessionRequest = this.sessionRequest.encode();
|
||||
byte byOpCode = this.getOperate().getCode();
|
||||
int len = bySessionRequest.length + 1;
|
||||
byte[] byResult = new byte[len];
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(byResult);
|
||||
byteBuffer.put(bySessionRequest);
|
||||
byteBuffer.put(byOpCode);
|
||||
return byResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(byte[] src) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(src);
|
||||
byte[] bySessionRequest = new byte[src.length - 1];
|
||||
byteBuffer.get(bySessionRequest);
|
||||
byte byOpCode = byteBuffer.get();
|
||||
this.operate = LogOperation.getLogOperationByCode(byOpCode);
|
||||
SessionStorable tmpSessionStorable = getSessionInstanceByOperation(this.operate);
|
||||
tmpSessionStorable.decode(bySessionRequest);
|
||||
this.sessionRequest = tmpSessionStorable;
|
||||
}
|
||||
|
||||
private SessionStorable getSessionInstanceByOperation(LogOperation logOperation) {
|
||||
SessionStorable sessionStorable = null;
|
||||
switch (logOperation) {
|
||||
case GLOBAL_ADD:
|
||||
case GLOBAL_UPDATE:
|
||||
case GLOBAL_REMOVE:
|
||||
sessionStorable = new GlobalSession();
|
||||
break;
|
||||
case BRANCH_ADD:
|
||||
case BRANCH_UPDATE:
|
||||
case BRANCH_REMOVE:
|
||||
sessionStorable = new BranchSession();
|
||||
break;
|
||||
default:
|
||||
throw new ShouldNeverHappenException("incorrect logOperation");
|
||||
}
|
||||
return sessionStorable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file.lock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.Locker;
|
||||
import io.seata.server.lock.AbstractLockManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static io.seata.core.context.RootContext.MDC_KEY_BRANCH_ID;
|
||||
|
||||
/**
|
||||
* The type file lock manager.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
@LoadLevel(name = "file")
|
||||
public class FileLockManager extends AbstractLockManager {
|
||||
|
||||
@Override
|
||||
public Locker getLocker(BranchSession branchSession) {
|
||||
return new FileLocker(branchSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException {
|
||||
ArrayList<BranchSession> branchSessions = globalSession.getBranchSessions();
|
||||
boolean releaseLockResult = true;
|
||||
for (BranchSession branchSession : branchSessions) {
|
||||
try {
|
||||
MDC.put(MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
|
||||
if (!this.releaseLock(branchSession)) {
|
||||
releaseLockResult = false;
|
||||
}
|
||||
} finally {
|
||||
MDC.remove(MDC_KEY_BRANCH_ID);
|
||||
}
|
||||
}
|
||||
return releaseLockResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file.lock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import io.netty.util.internal.ConcurrentSet;
|
||||
import io.seata.common.exception.FrameworkException;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.AbstractLocker;
|
||||
import io.seata.core.lock.RowLock;
|
||||
import io.seata.server.session.BranchSession;
|
||||
|
||||
/**
|
||||
* The type Memory locker.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class FileLocker extends AbstractLocker {
|
||||
|
||||
private static final int BUCKET_PER_TABLE = 128;
|
||||
|
||||
private static final ConcurrentMap<String/* resourceId */, ConcurrentMap<String/* tableName */,
|
||||
ConcurrentMap<Integer/* bucketId */, BucketLockMap>>>
|
||||
LOCK_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* The Branch session.
|
||||
*/
|
||||
protected BranchSession branchSession = null;
|
||||
|
||||
/**
|
||||
* Instantiates a new Memory locker.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
*/
|
||||
public FileLocker(BranchSession branchSession) {
|
||||
this.branchSession = branchSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(List<RowLock> rowLocks) {
|
||||
if (CollectionUtils.isEmpty(rowLocks)) {
|
||||
//no lock
|
||||
return true;
|
||||
}
|
||||
String resourceId = branchSession.getResourceId();
|
||||
long transactionId = branchSession.getTransactionId();
|
||||
|
||||
ConcurrentMap<BucketLockMap, Set<String>> bucketHolder = branchSession.getLockHolder();
|
||||
ConcurrentMap<String, ConcurrentMap<Integer, BucketLockMap>> dbLockMap = CollectionUtils.computeIfAbsent(
|
||||
LOCK_MAP, resourceId, key -> new ConcurrentHashMap<>());
|
||||
|
||||
for (RowLock lock : rowLocks) {
|
||||
String tableName = lock.getTableName();
|
||||
String pk = lock.getPk();
|
||||
ConcurrentMap<Integer, BucketLockMap> tableLockMap = CollectionUtils.computeIfAbsent(dbLockMap, tableName,
|
||||
key -> new ConcurrentHashMap<>());
|
||||
|
||||
int bucketId = pk.hashCode() % BUCKET_PER_TABLE;
|
||||
BucketLockMap bucketLockMap = CollectionUtils.computeIfAbsent(tableLockMap, bucketId,
|
||||
key -> new BucketLockMap());
|
||||
Long previousLockTransactionId = bucketLockMap.get().putIfAbsent(pk, transactionId);
|
||||
if (previousLockTransactionId == null) {
|
||||
//No existing lock, and now locked by myself
|
||||
Set<String> keysInHolder = CollectionUtils.computeIfAbsent(bucketHolder, bucketLockMap,
|
||||
key -> new ConcurrentSet<>());
|
||||
keysInHolder.add(pk);
|
||||
} else if (previousLockTransactionId == transactionId) {
|
||||
// Locked by me before
|
||||
continue;
|
||||
} else {
|
||||
LOGGER.info("Global lock on [" + tableName + ":" + pk + "] is holding by " + previousLockTransactionId);
|
||||
try {
|
||||
// Release all acquired locks.
|
||||
branchSession.unlock();
|
||||
} catch (TransactionException e) {
|
||||
throw new FrameworkException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(List<RowLock> rowLock) {
|
||||
if (CollectionUtils.isEmpty(rowLock)) {
|
||||
//no lock
|
||||
return true;
|
||||
}
|
||||
ConcurrentMap<BucketLockMap, Set<String>> lockHolder = branchSession.getLockHolder();
|
||||
if (CollectionUtils.isEmpty(lockHolder)) {
|
||||
return true;
|
||||
}
|
||||
for (Map.Entry<BucketLockMap, Set<String>> entry : lockHolder.entrySet()) {
|
||||
BucketLockMap bucket = entry.getKey();
|
||||
Set<String> keys = entry.getValue();
|
||||
for (String key : keys) {
|
||||
// remove lock only if it locked by myself
|
||||
bucket.get().remove(key, branchSession.getTransactionId());
|
||||
}
|
||||
}
|
||||
lockHolder.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(List<RowLock> rowLocks) {
|
||||
if (CollectionUtils.isEmpty(rowLocks)) {
|
||||
//no lock
|
||||
return true;
|
||||
}
|
||||
Long transactionId = rowLocks.get(0).getTransactionId();
|
||||
String resourceId = rowLocks.get(0).getResourceId();
|
||||
ConcurrentMap<String, ConcurrentMap<Integer, BucketLockMap>> dbLockMap = LOCK_MAP.get(resourceId);
|
||||
if (dbLockMap == null) {
|
||||
return true;
|
||||
}
|
||||
for (RowLock rowLock : rowLocks) {
|
||||
String tableName = rowLock.getTableName();
|
||||
String pk = rowLock.getPk();
|
||||
|
||||
ConcurrentMap<Integer, BucketLockMap> tableLockMap = dbLockMap.get(tableName);
|
||||
if (tableLockMap == null) {
|
||||
continue;
|
||||
}
|
||||
int bucketId = pk.hashCode() % BUCKET_PER_TABLE;
|
||||
BucketLockMap bucketLockMap = tableLockMap.get(bucketId);
|
||||
if (bucketLockMap == null) {
|
||||
continue;
|
||||
}
|
||||
Long lockingTransactionId = bucketLockMap.get().get(pk);
|
||||
if (lockingTransactionId == null || lockingTransactionId.longValue() == transactionId) {
|
||||
// Locked by me
|
||||
continue;
|
||||
} else {
|
||||
LOGGER.info("Global lock on [" + tableName + ":" + pk + "] is holding by " + lockingTransactionId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanAllLocks() {
|
||||
LOCK_MAP.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Because bucket lock map will be key of HashMap(lockHolder), however {@link ConcurrentHashMap} overwrites
|
||||
* {@link Object##hashCode()} and {@link Object##equals(Object)}, that leads to hash key conflict in lockHolder.
|
||||
* We define a {@link BucketLockMap} to hold the ConcurrentHashMap(bucketLockMap) and replace it as key of
|
||||
* HashMap(lockHolder).
|
||||
*/
|
||||
public static class BucketLockMap {
|
||||
private final ConcurrentHashMap<String/* pk */, Long/* transactionId */> bucketLockMap
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
ConcurrentHashMap<String, Long> get() {
|
||||
return bucketLockMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file.session;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.loader.Scope;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.AbstractSessionManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.Reloadable;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.storage.file.ReloadableStore;
|
||||
import io.seata.server.storage.file.TransactionWriteStore;
|
||||
import io.seata.server.storage.file.store.FileTransactionStoreManager;
|
||||
import io.seata.server.store.AbstractTransactionStoreManager;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
|
||||
|
||||
/**
|
||||
* The type File based session manager.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
@LoadLevel(name = "file", scope = Scope.PROTOTYPE)
|
||||
public class FileSessionManager extends AbstractSessionManager implements Reloadable {
|
||||
|
||||
private static final int READ_SIZE = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.SERVICE_SESSION_RELOAD_READ_SIZE, 100);
|
||||
/**
|
||||
* The Session map.
|
||||
*/
|
||||
private Map<String, GlobalSession> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new File based session manager.
|
||||
*
|
||||
* @param name the name
|
||||
* @param sessionStoreFilePath the session store file path
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public FileSessionManager(String name, String sessionStoreFilePath) throws IOException {
|
||||
super(name);
|
||||
if (StringUtils.isNotBlank(sessionStoreFilePath)) {
|
||||
transactionStoreManager = new FileTransactionStoreManager(
|
||||
sessionStoreFilePath + File.separator + name, this);
|
||||
} else {
|
||||
transactionStoreManager = new AbstractTransactionStoreManager() {
|
||||
@Override
|
||||
public boolean writeSession(LogOperation logOperation, SessionStorable session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
restoreSessions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
super.addGlobalSession(session);
|
||||
sessionMap.put(session.getXid(), session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid) {
|
||||
return sessionMap.get(xid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
// withBranchSessions without process in memory
|
||||
return sessionMap.get(xid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
super.removeGlobalSession(session);
|
||||
sessionMap.remove(session.getXid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GlobalSession> allSessions() {
|
||||
return sessionMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> findGlobalSessions(SessionCondition condition) {
|
||||
List<GlobalSession> found = new ArrayList<>();
|
||||
for (GlobalSession globalSession : sessionMap.values()) {
|
||||
if (System.currentTimeMillis() - globalSession.getBeginTime() > condition.getOverTimeAliveMills()) {
|
||||
found.add(globalSession);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
globalSession.lock();
|
||||
try {
|
||||
return lockCallable.call();
|
||||
} finally {
|
||||
globalSession.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreSessions() {
|
||||
final Set<String> removedGlobalBuffer = new HashSet<>();
|
||||
final Map<String, Map<Long, BranchSession>> unhandledBranchBuffer = new HashMap<>();
|
||||
|
||||
restoreSessions(true, removedGlobalBuffer, unhandledBranchBuffer);
|
||||
restoreSessions(false, removedGlobalBuffer, unhandledBranchBuffer);
|
||||
|
||||
if (!unhandledBranchBuffer.isEmpty()) {
|
||||
unhandledBranchBuffer.values().forEach(unhandledBranchSessions -> {
|
||||
unhandledBranchSessions.values().forEach(branchSession -> {
|
||||
String xid = branchSession.getXid();
|
||||
if (removedGlobalBuffer.contains(xid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long bid = branchSession.getBranchId();
|
||||
GlobalSession found = sessionMap.get(xid);
|
||||
if (found == null) {
|
||||
// Ignore
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("GlobalSession Does Not Exists For BranchSession [" + bid + "/" + xid + "]");
|
||||
}
|
||||
} else {
|
||||
BranchSession existingBranch = found.getBranch(branchSession.getBranchId());
|
||||
if (existingBranch == null) {
|
||||
found.add(branchSession);
|
||||
} else {
|
||||
existingBranch.setStatus(branchSession.getStatus());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSessionStatus(GlobalSession globalSession) {
|
||||
GlobalStatus globalStatus = globalSession.getStatus();
|
||||
switch (globalStatus) {
|
||||
case UnKnown:
|
||||
case Committed:
|
||||
case CommitFailed:
|
||||
case Rollbacked:
|
||||
case RollbackFailed:
|
||||
case TimeoutRollbacked:
|
||||
case TimeoutRollbackFailed:
|
||||
case Finished:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreSessions(boolean isHistory, Set<String> removedGlobalBuffer, Map<String,
|
||||
Map<Long, BranchSession>> unhandledBranchBuffer) {
|
||||
if (!(transactionStoreManager instanceof ReloadableStore)) {
|
||||
return;
|
||||
}
|
||||
while (((ReloadableStore)transactionStoreManager).hasRemaining(isHistory)) {
|
||||
List<TransactionWriteStore> stores = ((ReloadableStore)transactionStoreManager).readWriteStore(READ_SIZE,
|
||||
isHistory);
|
||||
restore(stores, removedGlobalBuffer, unhandledBranchBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void restore(List<TransactionWriteStore> stores, Set<String> removedGlobalBuffer,
|
||||
Map<String, Map<Long, BranchSession>> unhandledBranchBuffer) {
|
||||
for (TransactionWriteStore store : stores) {
|
||||
TransactionStoreManager.LogOperation logOperation = store.getOperate();
|
||||
SessionStorable sessionStorable = store.getSessionRequest();
|
||||
switch (logOperation) {
|
||||
case GLOBAL_ADD:
|
||||
case GLOBAL_UPDATE: {
|
||||
GlobalSession globalSession = (GlobalSession)sessionStorable;
|
||||
if (globalSession.getTransactionId() == 0) {
|
||||
LOGGER.error(
|
||||
"Restore globalSession from file failed, the transactionId is zero , xid:" + globalSession
|
||||
.getXid());
|
||||
break;
|
||||
}
|
||||
if (removedGlobalBuffer.contains(globalSession.getXid())) {
|
||||
break;
|
||||
}
|
||||
GlobalSession foundGlobalSession = sessionMap.get(globalSession.getXid());
|
||||
if (foundGlobalSession == null) {
|
||||
if (this.checkSessionStatus(globalSession)) {
|
||||
sessionMap.put(globalSession.getXid(), globalSession);
|
||||
} else {
|
||||
removedGlobalBuffer.add(globalSession.getXid());
|
||||
unhandledBranchBuffer.remove(globalSession.getXid());
|
||||
}
|
||||
} else {
|
||||
if (this.checkSessionStatus(globalSession)) {
|
||||
foundGlobalSession.setStatus(globalSession.getStatus());
|
||||
} else {
|
||||
sessionMap.remove(globalSession.getXid());
|
||||
removedGlobalBuffer.add(globalSession.getXid());
|
||||
unhandledBranchBuffer.remove(globalSession.getXid());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GLOBAL_REMOVE: {
|
||||
GlobalSession globalSession = (GlobalSession)sessionStorable;
|
||||
if (globalSession.getTransactionId() == 0) {
|
||||
LOGGER.error(
|
||||
"Restore globalSession from file failed, the transactionId is zero , xid:" + globalSession
|
||||
.getXid());
|
||||
break;
|
||||
}
|
||||
if (removedGlobalBuffer.contains(globalSession.getXid())) {
|
||||
break;
|
||||
}
|
||||
if (sessionMap.remove(globalSession.getXid()) == null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("GlobalSession To Be Removed Does Not Exists [" + globalSession.getXid() + "]");
|
||||
}
|
||||
}
|
||||
removedGlobalBuffer.add(globalSession.getXid());
|
||||
unhandledBranchBuffer.remove(globalSession.getXid());
|
||||
break;
|
||||
}
|
||||
case BRANCH_ADD:
|
||||
case BRANCH_UPDATE: {
|
||||
BranchSession branchSession = (BranchSession)sessionStorable;
|
||||
if (branchSession.getTransactionId() == 0) {
|
||||
LOGGER.error(
|
||||
"Restore branchSession from file failed, the transactionId is zero , xid:" + branchSession
|
||||
.getXid());
|
||||
break;
|
||||
}
|
||||
if (removedGlobalBuffer.contains(branchSession.getXid())) {
|
||||
break;
|
||||
}
|
||||
GlobalSession foundGlobalSession = sessionMap.get(branchSession.getXid());
|
||||
if (foundGlobalSession == null) {
|
||||
unhandledBranchBuffer.computeIfAbsent(branchSession.getXid(), key -> new HashMap<>())
|
||||
.put(branchSession.getBranchId(), branchSession);
|
||||
} else {
|
||||
BranchSession existingBranch = foundGlobalSession.getBranch(branchSession.getBranchId());
|
||||
if (existingBranch == null) {
|
||||
foundGlobalSession.add(branchSession);
|
||||
} else {
|
||||
existingBranch.setStatus(branchSession.getStatus());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BRANCH_REMOVE: {
|
||||
BranchSession branchSession = (BranchSession)sessionStorable;
|
||||
String xid = branchSession.getXid();
|
||||
if (removedGlobalBuffer.contains(xid)) {
|
||||
break;
|
||||
}
|
||||
long bid = branchSession.getBranchId();
|
||||
if (branchSession.getTransactionId() == 0) {
|
||||
LOGGER.error(
|
||||
"Restore branchSession from file failed, the transactionId is zero , xid:" + branchSession
|
||||
.getXid());
|
||||
break;
|
||||
}
|
||||
GlobalSession found = sessionMap.get(xid);
|
||||
if (found == null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info(
|
||||
"GlobalSession To Be Updated (Remove Branch) Does Not Exists [" + bid + "/" + xid
|
||||
+ "]");
|
||||
}
|
||||
} else {
|
||||
BranchSession theBranch = found.getBranch(bid);
|
||||
if (theBranch == null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("BranchSession To Be Updated Does Not Exists [" + bid + "/" + xid + "]");
|
||||
}
|
||||
} else {
|
||||
found.remove(theBranch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ShouldNeverHappenException("Unknown Operation: " + logOperation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
transactionStoreManager.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.file.store;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.session.SessionManager;
|
||||
import io.seata.server.store.AbstractTransactionStoreManager;
|
||||
import io.seata.server.storage.file.FlushDiskMode;
|
||||
import io.seata.server.storage.file.ReloadableStore;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.StoreConfig;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
import io.seata.server.storage.file.TransactionWriteStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static io.seata.core.context.RootContext.MDC_KEY_BRANCH_ID;
|
||||
|
||||
/**
|
||||
* The type File transaction store manager.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class FileTransactionStoreManager extends AbstractTransactionStoreManager
|
||||
implements TransactionStoreManager, ReloadableStore {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FileTransactionStoreManager.class);
|
||||
|
||||
private static final int MAX_THREAD_WRITE = 1;
|
||||
|
||||
private ExecutorService fileWriteExecutor;
|
||||
|
||||
private volatile boolean stopping = false;
|
||||
|
||||
private static final int MAX_SHUTDOWN_RETRY = 3;
|
||||
|
||||
private static final int SHUTDOWN_CHECK_INTERNAL = 1 * 1000;
|
||||
|
||||
private static final int MAX_WRITE_RETRY = 5;
|
||||
|
||||
private static final String HIS_DATA_FILENAME_POSTFIX = ".1";
|
||||
|
||||
private static final AtomicLong FILE_TRX_NUM = new AtomicLong(0);
|
||||
|
||||
private static final AtomicLong FILE_FLUSH_NUM = new AtomicLong(0);
|
||||
|
||||
private static final int MARK_SIZE = 4;
|
||||
|
||||
private static final int MAX_WAIT_TIME_MILLS = 2 * 1000;
|
||||
|
||||
private static final int MAX_FLUSH_TIME_MILLS = 2 * 1000;
|
||||
|
||||
private static final int MAX_FLUSH_NUM = 10;
|
||||
|
||||
private static final int PER_FILE_BLOCK_SIZE = 65535 * 8;
|
||||
|
||||
private static final long MAX_TRX_TIMEOUT_MILLS = 30 * 60 * 1000;
|
||||
|
||||
private static volatile long trxStartTimeMills = System.currentTimeMillis();
|
||||
|
||||
private File currDataFile;
|
||||
|
||||
private RandomAccessFile currRaf;
|
||||
|
||||
private FileChannel currFileChannel;
|
||||
|
||||
private long recoverCurrOffset = 0;
|
||||
|
||||
private long recoverHisOffset = 0;
|
||||
|
||||
private SessionManager sessionManager;
|
||||
|
||||
private String currFullFileName;
|
||||
|
||||
private String hisFullFileName;
|
||||
|
||||
private WriteDataFileRunnable writeDataFileRunnable;
|
||||
|
||||
private ReentrantLock writeSessionLock = new ReentrantLock();
|
||||
|
||||
private volatile long lastModifiedTime;
|
||||
|
||||
private static final int MAX_WRITE_BUFFER_SIZE = StoreConfig.getFileWriteBufferCacheSize();
|
||||
|
||||
private final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(MAX_WRITE_BUFFER_SIZE);
|
||||
|
||||
private static final FlushDiskMode FLUSH_DISK_MODE = StoreConfig.getFlushDiskMode();
|
||||
|
||||
private static final int MAX_WAIT_FOR_FLUSH_TIME_MILLS = 2 * 1000;
|
||||
|
||||
private static final int MAX_WAIT_FOR_CLOSE_TIME_MILLS = 2 * 1000;
|
||||
|
||||
private static final int INT_BYTE_SIZE = 4;
|
||||
|
||||
/**
|
||||
* Instantiates a new File transaction store manager.
|
||||
*
|
||||
* @param fullFileName the dir path
|
||||
* @param sessionManager the session manager
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public FileTransactionStoreManager(String fullFileName, SessionManager sessionManager) throws IOException {
|
||||
initFile(fullFileName);
|
||||
fileWriteExecutor = new ThreadPoolExecutor(MAX_THREAD_WRITE, MAX_THREAD_WRITE, Integer.MAX_VALUE,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
|
||||
new NamedThreadFactory("fileTransactionStore", MAX_THREAD_WRITE, true));
|
||||
writeDataFileRunnable = new WriteDataFileRunnable();
|
||||
fileWriteExecutor.submit(writeDataFileRunnable);
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
private void initFile(String fullFileName) throws IOException {
|
||||
this.currFullFileName = fullFileName;
|
||||
this.hisFullFileName = fullFileName + HIS_DATA_FILENAME_POSTFIX;
|
||||
try {
|
||||
currDataFile = new File(currFullFileName);
|
||||
if (!currDataFile.exists()) {
|
||||
// create parent dir first
|
||||
if (currDataFile.getParentFile() != null && !currDataFile.getParentFile().exists()) {
|
||||
currDataFile.getParentFile().mkdirs();
|
||||
}
|
||||
currDataFile.createNewFile();
|
||||
trxStartTimeMills = System.currentTimeMillis();
|
||||
} else {
|
||||
trxStartTimeMills = currDataFile.lastModified();
|
||||
}
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
currRaf = new RandomAccessFile(currDataFile, "rw");
|
||||
currRaf.seek(currDataFile.length());
|
||||
currFileChannel = currRaf.getChannel();
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("init file error,{}", exx.getMessage(), exx);
|
||||
throw exx;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeSession(LogOperation logOperation, SessionStorable session) {
|
||||
writeSessionLock.lock();
|
||||
long curFileTrxNum;
|
||||
try {
|
||||
if (!writeDataFile(new TransactionWriteStore(session, logOperation).encode())) {
|
||||
return false;
|
||||
}
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
curFileTrxNum = FILE_TRX_NUM.incrementAndGet();
|
||||
if (curFileTrxNum % PER_FILE_BLOCK_SIZE == 0
|
||||
&& (System.currentTimeMillis() - trxStartTimeMills) > MAX_TRX_TIMEOUT_MILLS) {
|
||||
return saveHistory();
|
||||
}
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("writeSession error, {}", exx.getMessage(), exx);
|
||||
return false;
|
||||
} finally {
|
||||
writeSessionLock.unlock();
|
||||
}
|
||||
flushDisk(curFileTrxNum, currFileChannel);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void flushDisk(long curFileNum, FileChannel currFileChannel) {
|
||||
|
||||
if (FLUSH_DISK_MODE == FlushDiskMode.SYNC_MODEL) {
|
||||
SyncFlushRequest syncFlushRequest = new SyncFlushRequest(curFileNum, currFileChannel);
|
||||
writeDataFileRunnable.putRequest(syncFlushRequest);
|
||||
syncFlushRequest.waitForFlush(MAX_WAIT_FOR_FLUSH_TIME_MILLS);
|
||||
} else {
|
||||
writeDataFileRunnable.putRequest(new AsyncFlushRequest(curFileNum, currFileChannel));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all overTimeSessionStorables
|
||||
* merge write file
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean saveHistory() throws IOException {
|
||||
boolean result;
|
||||
try {
|
||||
result = findTimeoutAndSave();
|
||||
CloseFileRequest request = new CloseFileRequest(currFileChannel, currRaf);
|
||||
writeDataFileRunnable.putRequest(request);
|
||||
request.waitForClose(MAX_WAIT_FOR_CLOSE_TIME_MILLS);
|
||||
Files.move(currDataFile.toPath(), new File(hisFullFileName).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("save history data file error, {}", exx.getMessage(), exx);
|
||||
result = false;
|
||||
} finally {
|
||||
initFile(currFullFileName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean writeDataFrame(byte[] data) {
|
||||
if (data == null || data.length <= 0) {
|
||||
return true;
|
||||
}
|
||||
int dataLength = data.length;
|
||||
int bufferRemainingSize = writeBuffer.remaining();
|
||||
if (bufferRemainingSize <= INT_BYTE_SIZE) {
|
||||
if (!flushWriteBuffer(writeBuffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bufferRemainingSize = writeBuffer.remaining();
|
||||
if (bufferRemainingSize <= INT_BYTE_SIZE) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Write buffer remaining size %d was too small", bufferRemainingSize));
|
||||
}
|
||||
writeBuffer.putInt(dataLength);
|
||||
bufferRemainingSize = writeBuffer.remaining();
|
||||
int dataPos = 0;
|
||||
while (dataPos < dataLength) {
|
||||
int dataLengthToWrite = dataLength - dataPos;
|
||||
dataLengthToWrite = Math.min(dataLengthToWrite, bufferRemainingSize);
|
||||
writeBuffer.put(data, dataPos, dataLengthToWrite);
|
||||
bufferRemainingSize = writeBuffer.remaining();
|
||||
if (bufferRemainingSize == 0) {
|
||||
if (!flushWriteBuffer(writeBuffer)) {
|
||||
return false;
|
||||
}
|
||||
bufferRemainingSize = writeBuffer.remaining();
|
||||
}
|
||||
dataPos += dataLengthToWrite;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean flushWriteBuffer(ByteBuffer writeBuffer) {
|
||||
writeBuffer.flip();
|
||||
if (!writeDataFileByBuffer(writeBuffer)) {
|
||||
return false;
|
||||
}
|
||||
writeBuffer.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean findTimeoutAndSave() throws IOException {
|
||||
List<GlobalSession> globalSessionsOverMaxTimeout = sessionManager.findGlobalSessions(
|
||||
new SessionCondition(MAX_TRX_TIMEOUT_MILLS));
|
||||
if (CollectionUtils.isEmpty(globalSessionsOverMaxTimeout)) {
|
||||
return true;
|
||||
}
|
||||
for (GlobalSession globalSession : globalSessionsOverMaxTimeout) {
|
||||
TransactionWriteStore globalWriteStore = new TransactionWriteStore(globalSession, LogOperation.GLOBAL_ADD);
|
||||
byte[] data = globalWriteStore.encode();
|
||||
if (!writeDataFrame(data)) {
|
||||
return false;
|
||||
}
|
||||
List<BranchSession> branchSessIonsOverMaXTimeout = globalSession.getSortedBranches();
|
||||
if (branchSessIonsOverMaXTimeout != null) {
|
||||
for (BranchSession branchSession : branchSessIonsOverMaXTimeout) {
|
||||
try {
|
||||
MDC.put(MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
|
||||
TransactionWriteStore branchWriteStore = new TransactionWriteStore(branchSession,
|
||||
LogOperation.BRANCH_ADD);
|
||||
data = branchWriteStore.encode();
|
||||
if (!writeDataFrame(data)) {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
MDC.remove(MDC_KEY_BRANCH_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flushWriteBuffer(writeBuffer)) {
|
||||
currFileChannel.force(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession readSession(String xid) {
|
||||
throw new StoreException("unsupport for read from file, xid:" + xid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> readSession(SessionCondition sessionCondition) {
|
||||
throw new StoreException("unsupport for read from file");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (fileWriteExecutor != null) {
|
||||
fileWriteExecutor.shutdown();
|
||||
stopping = true;
|
||||
int retry = 0;
|
||||
while (!fileWriteExecutor.isTerminated() && retry < MAX_SHUTDOWN_RETRY) {
|
||||
++retry;
|
||||
try {
|
||||
Thread.sleep(SHUTDOWN_CHECK_INTERNAL);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
if (retry >= MAX_SHUTDOWN_RETRY) {
|
||||
fileWriteExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
try {
|
||||
currFileChannel.force(true);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("fileChannel force error{}", e.getMessage(), e);
|
||||
}
|
||||
closeFile(currRaf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TransactionWriteStore> readWriteStore(int readSize, boolean isHistory) {
|
||||
File file = null;
|
||||
long currentOffset = 0;
|
||||
if (isHistory) {
|
||||
file = new File(hisFullFileName);
|
||||
currentOffset = recoverHisOffset;
|
||||
} else {
|
||||
file = new File(currFullFileName);
|
||||
currentOffset = recoverCurrOffset;
|
||||
}
|
||||
if (file.exists()) {
|
||||
return parseDataFile(file, readSize, currentOffset, isHistory);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRemaining(boolean isHistory) {
|
||||
File file = null;
|
||||
RandomAccessFile raf = null;
|
||||
long currentOffset = 0;
|
||||
if (isHistory) {
|
||||
file = new File(hisFullFileName);
|
||||
currentOffset = recoverHisOffset;
|
||||
} else {
|
||||
file = new File(currFullFileName);
|
||||
currentOffset = recoverCurrOffset;
|
||||
}
|
||||
try {
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
return currentOffset < raf.length();
|
||||
|
||||
} catch (IOException ignore) {
|
||||
} finally {
|
||||
closeFile(raf);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<TransactionWriteStore> parseDataFile(File file, int readSize, long currentOffset, boolean isHistory) {
|
||||
List<TransactionWriteStore> transactionWriteStores = new ArrayList<>(readSize);
|
||||
RandomAccessFile raf = null;
|
||||
FileChannel fileChannel = null;
|
||||
try {
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
raf.seek(currentOffset);
|
||||
fileChannel = raf.getChannel();
|
||||
fileChannel.position(currentOffset);
|
||||
long size = raf.length();
|
||||
ByteBuffer buffSize = ByteBuffer.allocate(MARK_SIZE);
|
||||
while (fileChannel.position() < size) {
|
||||
try {
|
||||
buffSize.clear();
|
||||
int avilReadSize = fileChannel.read(buffSize);
|
||||
if (avilReadSize != MARK_SIZE) {
|
||||
break;
|
||||
}
|
||||
buffSize.flip();
|
||||
int bodySize = buffSize.getInt();
|
||||
byte[] byBody = new byte[bodySize];
|
||||
ByteBuffer buffBody = ByteBuffer.wrap(byBody);
|
||||
avilReadSize = fileChannel.read(buffBody);
|
||||
if (avilReadSize != bodySize) {
|
||||
break;
|
||||
}
|
||||
TransactionWriteStore writeStore = new TransactionWriteStore();
|
||||
writeStore.decode(byBody);
|
||||
transactionWriteStores.add(writeStore);
|
||||
if (transactionWriteStores.size() == readSize) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("decode data file error:{}", ex.getMessage(), ex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return transactionWriteStores;
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("parse data file error:{},file:{}", exx.getMessage(), file.getName(), exx);
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
if (fileChannel != null) {
|
||||
if (isHistory) {
|
||||
recoverHisOffset = fileChannel.position();
|
||||
} else {
|
||||
recoverCurrOffset = fileChannel.position();
|
||||
}
|
||||
}
|
||||
closeFile(raf);
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("file close error{}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void closeFile(RandomAccessFile raf) {
|
||||
try {
|
||||
if (raf != null) {
|
||||
raf.close();
|
||||
raf = null;
|
||||
}
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("file close error,{}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeDataFile(byte[] bs) {
|
||||
if (bs == null || bs.length >= Integer.MAX_VALUE - 3) {
|
||||
return false;
|
||||
}
|
||||
if (!writeDataFrame(bs)) {
|
||||
return false;
|
||||
}
|
||||
return flushWriteBuffer(writeBuffer);
|
||||
}
|
||||
|
||||
private boolean writeDataFileByBuffer(ByteBuffer byteBuffer) {
|
||||
for (int retry = 0; retry < MAX_WRITE_RETRY; retry++) {
|
||||
try {
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
currFileChannel.write(byteBuffer);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("write data file error:{}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
LOGGER.error("write dataFile failed,retry more than :{}", MAX_WRITE_RETRY);
|
||||
return false;
|
||||
}
|
||||
|
||||
interface StoreRequest {
|
||||
|
||||
}
|
||||
|
||||
abstract static class AbstractFlushRequest implements StoreRequest {
|
||||
private final long curFileTrxNum;
|
||||
|
||||
private final FileChannel curFileChannel;
|
||||
|
||||
protected AbstractFlushRequest(long curFileTrxNum, FileChannel curFileChannel) {
|
||||
this.curFileTrxNum = curFileTrxNum;
|
||||
this.curFileChannel = curFileChannel;
|
||||
}
|
||||
|
||||
public long getCurFileTrxNum() {
|
||||
return curFileTrxNum;
|
||||
}
|
||||
|
||||
public FileChannel getCurFileChannel() {
|
||||
return curFileChannel;
|
||||
}
|
||||
}
|
||||
|
||||
class SyncFlushRequest extends AbstractFlushRequest {
|
||||
|
||||
private final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
public SyncFlushRequest(long curFileTrxNum, FileChannel curFileChannel) {
|
||||
super(curFileTrxNum, curFileChannel);
|
||||
}
|
||||
|
||||
public void wakeup() {
|
||||
this.countDownLatch.countDown();
|
||||
}
|
||||
|
||||
public void waitForFlush(long timeout) {
|
||||
try {
|
||||
this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncFlushRequest extends AbstractFlushRequest {
|
||||
|
||||
public AsyncFlushRequest(long curFileTrxNum, FileChannel curFileChannel) {
|
||||
super(curFileTrxNum, curFileChannel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CloseFileRequest implements StoreRequest {
|
||||
private final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
private FileChannel fileChannel;
|
||||
|
||||
private RandomAccessFile file;
|
||||
|
||||
public CloseFileRequest(FileChannel fileChannel, RandomAccessFile file) {
|
||||
this.fileChannel = fileChannel;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public FileChannel getFileChannel() {
|
||||
return fileChannel;
|
||||
}
|
||||
|
||||
public RandomAccessFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void wakeup() {
|
||||
this.countDownLatch.countDown();
|
||||
}
|
||||
|
||||
public void waitForClose(long timeout) {
|
||||
try {
|
||||
this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Interrupted", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Write data file runnable.
|
||||
*/
|
||||
class WriteDataFileRunnable implements Runnable {
|
||||
|
||||
private LinkedBlockingQueue<StoreRequest> storeRequests = new LinkedBlockingQueue<>();
|
||||
|
||||
public void putRequest(final StoreRequest request) {
|
||||
storeRequests.add(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!stopping) {
|
||||
try {
|
||||
StoreRequest storeRequest = storeRequests.poll(MAX_WAIT_TIME_MILLS, TimeUnit.MILLISECONDS);
|
||||
handleStoreRequest(storeRequest);
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("write file error: {}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
handleRestRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the rest requests when stopping is true
|
||||
*/
|
||||
private void handleRestRequest() {
|
||||
int remainNums = storeRequests.size();
|
||||
for (int i = 0; i < remainNums; i++) {
|
||||
handleStoreRequest(storeRequests.poll());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStoreRequest(StoreRequest storeRequest) {
|
||||
if (storeRequest == null) {
|
||||
flushOnCondition(currFileChannel);
|
||||
}
|
||||
if (storeRequest instanceof SyncFlushRequest) {
|
||||
syncFlush((SyncFlushRequest)storeRequest);
|
||||
} else if (storeRequest instanceof AsyncFlushRequest) {
|
||||
async((AsyncFlushRequest)storeRequest);
|
||||
} else if (storeRequest instanceof CloseFileRequest) {
|
||||
closeAndFlush((CloseFileRequest)storeRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAndFlush(CloseFileRequest req) {
|
||||
long diff = FILE_TRX_NUM.get() - FILE_FLUSH_NUM.get();
|
||||
flush(req.getFileChannel());
|
||||
FILE_FLUSH_NUM.addAndGet(diff);
|
||||
closeFile(req.getFile());
|
||||
req.wakeup();
|
||||
}
|
||||
|
||||
private void async(AsyncFlushRequest req) {
|
||||
flushOnCondition(req.getCurFileChannel());
|
||||
}
|
||||
|
||||
private void syncFlush(SyncFlushRequest req) {
|
||||
if (req.getCurFileTrxNum() > FILE_FLUSH_NUM.get()) {
|
||||
long diff = FILE_TRX_NUM.get() - FILE_FLUSH_NUM.get();
|
||||
flush(req.getCurFileChannel());
|
||||
FILE_FLUSH_NUM.addAndGet(diff);
|
||||
}
|
||||
// notify
|
||||
req.wakeup();
|
||||
}
|
||||
|
||||
private void flushOnCondition(FileChannel fileChannel) {
|
||||
if (FLUSH_DISK_MODE == FlushDiskMode.SYNC_MODEL) {
|
||||
return;
|
||||
}
|
||||
long diff = FILE_TRX_NUM.get() - FILE_FLUSH_NUM.get();
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
if (diff % MAX_FLUSH_NUM == 0 || System.currentTimeMillis() - lastModifiedTime > MAX_FLUSH_TIME_MILLS) {
|
||||
flush(fileChannel);
|
||||
FILE_FLUSH_NUM.addAndGet(diff);
|
||||
}
|
||||
}
|
||||
|
||||
private void flush(FileChannel fileChannel) {
|
||||
try {
|
||||
fileChannel.force(false);
|
||||
} catch (IOException exx) {
|
||||
LOGGER.error("flush error: {}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.redis;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.seata.common.util.ConfigTools;
|
||||
import io.seata.common.exception.RedisException;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolAbstract;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.JedisSentinelPool;
|
||||
|
||||
/**
|
||||
* @author funkye
|
||||
*/
|
||||
public class JedisPooledFactory {
|
||||
/**
|
||||
* The constant LOGGER.
|
||||
*/
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(JedisPooledFactory.class);
|
||||
|
||||
private static volatile JedisPoolAbstract jedisPool = null;
|
||||
|
||||
private static final String HOST = "127.0.0.1";
|
||||
|
||||
private static final int PORT = 6379;
|
||||
|
||||
private static final int MINCONN = 1;
|
||||
|
||||
private static final int MAXCONN = 10;
|
||||
|
||||
private static final int MAXTOTAL = 100;
|
||||
|
||||
private static final int DATABASE = 0;
|
||||
|
||||
private static final int SENTINEL_HOST_NUMBER = 3;
|
||||
|
||||
private static final Configuration CONFIGURATION = ConfigurationFactory.getInstance();
|
||||
|
||||
/**
|
||||
* get the RedisPool instance (singleton)
|
||||
*
|
||||
* @return redisPool
|
||||
*/
|
||||
public static JedisPoolAbstract getJedisPoolInstance(JedisPoolAbstract... jedisPools) {
|
||||
if (jedisPool == null) {
|
||||
synchronized (JedisPooledFactory.class) {
|
||||
if (jedisPool == null) {
|
||||
if (jedisPools != null && jedisPools.length > 0) {
|
||||
jedisPool = jedisPools[0];
|
||||
} else {
|
||||
String password = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_PASSWORD);
|
||||
if (StringUtils.isBlank(password)) {
|
||||
password = null;
|
||||
} else {
|
||||
String publicKey = CONFIGURATION.getConfig(ConfigurationKeys.STORE_PUBLIC_KEY);
|
||||
if (StringUtils.isNotBlank(publicKey)) {
|
||||
try {
|
||||
password = ConfigTools.publicDecrypt(password, publicKey);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(
|
||||
"decryption failed,please confirm whether the ciphertext and secret key are correct! error msg: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
poolConfig.setMinIdle(CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_MIN_CONN, MINCONN));
|
||||
poolConfig.setMaxIdle(CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_MAX_CONN, MAXCONN));
|
||||
poolConfig.setMaxTotal(CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_MAX_TOTAL, MAXTOTAL));
|
||||
String mode = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_MODE,ConfigurationKeys.REDIS_SINGLE_MODE);
|
||||
if (mode.equals(ConfigurationKeys.REDIS_SENTINEL_MODE)) {
|
||||
String masterName = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_SENTINEL_MASTERNAME);
|
||||
if (StringUtils.isBlank(masterName)) {
|
||||
throw new RedisException("The masterName is null in redis sentinel mode");
|
||||
}
|
||||
Set<String> sentinels = new HashSet<>(SENTINEL_HOST_NUMBER);
|
||||
String[] sentinelHosts = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_SENTINEL_HOST).split(",");
|
||||
Arrays.asList(sentinelHosts).forEach(sentinelHost -> sentinels.add(sentinelHost));
|
||||
jedisPool = new JedisSentinelPool(masterName, sentinels, poolConfig, 60000, password,
|
||||
CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_DATABASE, DATABASE));
|
||||
} else if (mode.equals(ConfigurationKeys.REDIS_SINGLE_MODE)) {
|
||||
String host = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_SINGLE_HOST);
|
||||
host = StringUtils.isBlank(host) ? CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_HOST, HOST) : host;
|
||||
int port = CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_SINGLE_PORT);
|
||||
port = port == 0 ? CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_PORT, PORT) : port;
|
||||
jedisPool =
|
||||
new JedisPool(poolConfig, host, port, 60000, password,
|
||||
CONFIGURATION.getInt(ConfigurationKeys.STORE_REDIS_DATABASE, DATABASE));
|
||||
} else {
|
||||
throw new RedisException("Configuration error of redis cluster mode");
|
||||
}
|
||||
}
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("initialization of the build redis connection pool is complete");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return jedisPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* get an instance of Jedis (connection) from the connection pool
|
||||
*
|
||||
* @return jedis
|
||||
*/
|
||||
public static Jedis getJedisInstance() {
|
||||
return getJedisPoolInstance().getResource();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.redis.lock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.seata.common.executor.Initialize;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.Locker;
|
||||
import io.seata.server.lock.AbstractLockManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
/**
|
||||
* @author funkye
|
||||
*/
|
||||
@LoadLevel(name = "redis")
|
||||
public class RedisLockManager extends AbstractLockManager implements Initialize {
|
||||
|
||||
/**
|
||||
* The locker.
|
||||
*/
|
||||
private Locker locker;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
locker = new RedisLocker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locker getLocker(BranchSession branchSession) {
|
||||
return locker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(BranchSession branchSession) throws TransactionException {
|
||||
try {
|
||||
return getLocker().releaseLock(branchSession.getXid(), branchSession.getBranchId());
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock error, xid {}, branchId:{}", branchSession.getXid(), branchSession.getBranchId(), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException {
|
||||
List<BranchSession> branchSessions = globalSession.getBranchSessions();
|
||||
if (CollectionUtils.isEmpty(branchSessions)) {
|
||||
return true;
|
||||
}
|
||||
List<Long> branchIds = branchSessions.stream().map(BranchSession::getBranchId).collect(Collectors.toList());
|
||||
try {
|
||||
return getLocker().releaseLock(globalSession.getXid(), branchIds);
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("unLock globalSession error, xid:{} branchIds:{}", globalSession.getXid(),
|
||||
CollectionUtils.toString(branchIds), t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.redis.lock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
import com.google.common.collect.Lists;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.LambdaUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.lock.AbstractLocker;
|
||||
import io.seata.core.lock.RowLock;
|
||||
import io.seata.core.store.LockDO;
|
||||
import io.seata.server.storage.redis.JedisPooledFactory;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
|
||||
|
||||
import static io.seata.common.Constants.ROW_LOCK_KEY_SPLIT_CHAR;
|
||||
|
||||
/**
|
||||
* The redis lock store operation
|
||||
*
|
||||
* @author funkye
|
||||
* @author wangzhongxiang
|
||||
*/
|
||||
public class RedisLocker extends AbstractLocker {
|
||||
|
||||
private static final Integer SUCCEED = 1;
|
||||
|
||||
private static final Integer FAILED = 0;
|
||||
|
||||
private static final String DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX = "SEATA_ROW_LOCK_";
|
||||
|
||||
private static final String DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX = "SEATA_GLOBAL_LOCK";
|
||||
|
||||
private static final String XID = "xid";
|
||||
|
||||
private static final String TRANSACTION_ID = "transactionId";
|
||||
|
||||
private static final String BRANCH_ID = "branchId";
|
||||
|
||||
private static final String RESOURCE_ID = "resourceId";
|
||||
|
||||
private static final String TABLE_NAME = "tableName";
|
||||
|
||||
private static final String PK = "pk";
|
||||
|
||||
private static final String ROW_KEY = "rowKey";
|
||||
|
||||
/**
|
||||
* Instantiates a new Redis locker.
|
||||
*/
|
||||
public RedisLocker() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acquireLock(List<RowLock> rowLocks) {
|
||||
if (CollectionUtils.isEmpty(rowLocks)) {
|
||||
return true;
|
||||
}
|
||||
String needLockXid = rowLocks.get(0).getXid();
|
||||
Long branchId = rowLocks.get(0).getBranchId();
|
||||
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
List<LockDO> needLockDOS = convertToLockDO(rowLocks);
|
||||
if (needLockDOS.size() > 1) {
|
||||
needLockDOS = needLockDOS.stream().
|
||||
filter(LambdaUtils.distinctByKey(LockDO::getRowKey))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
List<String> needLockKeys = new ArrayList<>();
|
||||
needLockDOS.forEach(lockDO -> needLockKeys.add(buildLockKey(lockDO.getRowKey())));
|
||||
|
||||
Pipeline pipeline1 = jedis.pipelined();
|
||||
needLockKeys.stream().forEachOrdered(needLockKey -> pipeline1.hget(needLockKey, XID));
|
||||
List<String> existedLockInfos = (List<String>) (List) pipeline1.syncAndReturnAll();
|
||||
Map<String, LockDO> needAddLock = new HashMap<>(needLockKeys.size(), 1);
|
||||
|
||||
for (int i = 0; i < needLockKeys.size(); i++) {
|
||||
String existedLockXid = existedLockInfos.get(i);
|
||||
if (StringUtils.isEmpty(existedLockXid)) {
|
||||
//If empty,we need to lock this row
|
||||
needAddLock.put(needLockKeys.get(i), needLockDOS.get(i));
|
||||
} else {
|
||||
if (!StringUtils.equals(existedLockXid, needLockXid)) {
|
||||
//If not equals,means the rowkey is holding by another global transaction
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needAddLock.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
List<String> readyKeys = new ArrayList<>();
|
||||
needAddLock.forEach((key, value) -> {
|
||||
pipeline.hsetnx(key, XID, value.getXid());
|
||||
pipeline.hsetnx(key, TRANSACTION_ID, value.getTransactionId().toString());
|
||||
pipeline.hsetnx(key, BRANCH_ID, value.getBranchId().toString());
|
||||
pipeline.hset(key, ROW_KEY, value.getRowKey());
|
||||
pipeline.hset(key, RESOURCE_ID, value.getResourceId());
|
||||
pipeline.hset(key, TABLE_NAME, value.getTableName());
|
||||
pipeline.hset(key, PK, value.getPk());
|
||||
readyKeys.add(key);
|
||||
});
|
||||
List<Integer> results = (List<Integer>) (List) pipeline.syncAndReturnAll();
|
||||
List<List<Integer>> partitions = Lists.partition(results, 7);
|
||||
|
||||
ArrayList<String> success = new ArrayList<>(partitions.size());
|
||||
Integer status = SUCCEED;
|
||||
for (int i = 0; i < partitions.size(); i++) {
|
||||
if (Objects.equals(partitions.get(i).get(0),FAILED)) {
|
||||
status = FAILED;
|
||||
} else {
|
||||
success.add(readyKeys.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
//If someone has failed,all the lockkey which has been added need to be delete.
|
||||
if (FAILED.equals(status)) {
|
||||
if (success.size() > 0) {
|
||||
jedis.del(success.toArray(new String[0]));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
String xidLockKey = buildXidLockKey(needLockXid);
|
||||
StringJoiner lockKeysString = new StringJoiner(ROW_LOCK_KEY_SPLIT_CHAR);
|
||||
needLockKeys.forEach(lockKeysString::add);
|
||||
jedis.hset(xidLockKey, branchId.toString(), lockKeysString.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(List<RowLock> rowLocks) {
|
||||
if (CollectionUtils.isEmpty(rowLocks)) {
|
||||
return true;
|
||||
}
|
||||
String currentXid = rowLocks.get(0).getXid();
|
||||
Long branchId = rowLocks.get(0).getBranchId();
|
||||
List<LockDO> needReleaseLocks = convertToLockDO(rowLocks);
|
||||
String[] needReleaseKeys = new String[needReleaseLocks.size()];
|
||||
for (int i = 0; i < needReleaseLocks.size(); i ++) {
|
||||
needReleaseKeys[i] = buildLockKey(needReleaseLocks.get(i).getRowKey());
|
||||
}
|
||||
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.del(needReleaseKeys);
|
||||
pipelined.hdel(buildXidLockKey(currentXid), branchId.toString());
|
||||
pipelined.sync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(String xid, List<Long> branchIds) {
|
||||
if (CollectionUtils.isEmpty(branchIds)) {
|
||||
return true;
|
||||
}
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
String xidLockKey = buildXidLockKey(xid);
|
||||
String[] branchIdsArray = new String[branchIds.size()];
|
||||
for (int i = 0; i < branchIds.size(); i++) {
|
||||
branchIdsArray[i] = branchIds.get(i).toString();
|
||||
}
|
||||
List<String> rowKeys = jedis.hmget(xidLockKey, branchIdsArray);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(rowKeys)) {
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.hdel(xidLockKey, branchIdsArray);
|
||||
rowKeys.forEach(rowKeyStr -> {
|
||||
if (StringUtils.isNotEmpty(rowKeyStr)) {
|
||||
if (rowKeyStr.contains(ROW_LOCK_KEY_SPLIT_CHAR)) {
|
||||
String[] keys = rowKeyStr.split(ROW_LOCK_KEY_SPLIT_CHAR);
|
||||
pipelined.del(keys);
|
||||
} else {
|
||||
pipelined.del(rowKeyStr);
|
||||
}
|
||||
}
|
||||
});
|
||||
pipelined.sync();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean releaseLock(String xid, Long branchId) {
|
||||
List<Long> branchIds = new ArrayList<>();
|
||||
branchIds.add(branchId);
|
||||
return releaseLock(xid, branchIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(List<RowLock> rowLocks) {
|
||||
if (CollectionUtils.isEmpty(rowLocks)) {
|
||||
return true;
|
||||
}
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
List<LockDO> locks = convertToLockDO(rowLocks);
|
||||
Set<String> lockKeys = new HashSet<>();
|
||||
for (LockDO rowlock : locks) {
|
||||
lockKeys.add(buildLockKey(rowlock.getRowKey()));
|
||||
}
|
||||
|
||||
String xid = rowLocks.get(0).getXid();
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
lockKeys.forEach(key -> pipeline.hget(key, XID));
|
||||
List<String> existedXids = (List<String>) (List) pipeline.syncAndReturnAll();
|
||||
return existedXids.stream().allMatch(existedXid -> existedXid == null || xid.equals(existedXid));
|
||||
}
|
||||
}
|
||||
|
||||
private String buildXidLockKey(String xid) {
|
||||
return DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX + xid;
|
||||
}
|
||||
|
||||
private String buildLockKey(String rowKey) {
|
||||
return DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX + rowKey;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.redis.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.executor.Initialize;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.loader.Scope;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.AbstractSessionManager;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import io.seata.server.storage.redis.store.RedisTransactionStoreManager;
|
||||
import io.seata.server.store.TransactionStoreManager.LogOperation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author funkye
|
||||
*/
|
||||
@LoadLevel(name = "redis", scope = Scope.PROTOTYPE)
|
||||
public class RedisSessionManager extends AbstractSessionManager
|
||||
implements Initialize {
|
||||
/**
|
||||
* The constant LOGGER.
|
||||
*/
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(RedisSessionManager.class);
|
||||
|
||||
/**
|
||||
* The Task name.
|
||||
*/
|
||||
protected String taskName;
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base session manager.
|
||||
*/
|
||||
public RedisSessionManager() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Data base session manager.
|
||||
*
|
||||
* @param name
|
||||
* the name
|
||||
*/
|
||||
public RedisSessionManager(String name) {
|
||||
super();
|
||||
this.taskName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
transactionStoreManager = RedisTransactionStoreManager.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
if (StringUtils.isBlank(taskName)) {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_ADD, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addGlobalSession failed.");
|
||||
}
|
||||
} else {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addGlobalSession failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status) throws TransactionException {
|
||||
if (!StringUtils.isEmpty(taskName)) {
|
||||
return;
|
||||
}
|
||||
session.setStatus(status);
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("updateGlobalSessionStatus failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove globalSession 1. rootSessionManager remove normal globalSession 2. retryCommitSessionManager and
|
||||
* retryRollbackSessionManager remove retry expired globalSession
|
||||
*
|
||||
* @param session
|
||||
* the session
|
||||
* @throws TransactionException
|
||||
*/
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.GLOBAL_REMOVE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("removeGlobalSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException {
|
||||
if (!StringUtils.isEmpty(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_ADD, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("addBranchSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBranchSessionStatus(BranchSession session, BranchStatus status) throws TransactionException {
|
||||
if (!StringUtils.isEmpty(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_UPDATE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("updateBranchSessionStatus failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranchSession(GlobalSession globalSession, BranchSession session) throws TransactionException {
|
||||
if (!StringUtils.isEmpty(taskName)) {
|
||||
return;
|
||||
}
|
||||
boolean ret = transactionStoreManager.writeSession(LogOperation.BRANCH_REMOVE, session);
|
||||
if (!ret) {
|
||||
throw new StoreException("removeBranchSession failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid) {
|
||||
return this.findGlobalSession(xid, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
return transactionStoreManager.readSession(xid, withBranchSessions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GlobalSession> allSessions() {
|
||||
// get by taskName
|
||||
if (SessionHolder.ASYNC_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(GlobalStatus.AsyncCommitting));
|
||||
} else if (SessionHolder.RETRY_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.CommitRetrying, GlobalStatus.Committing}));
|
||||
} else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.RollbackRetrying,
|
||||
GlobalStatus.Rollbacking, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying}));
|
||||
} else {
|
||||
// all data
|
||||
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.UnKnown, GlobalStatus.Begin,
|
||||
GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking,
|
||||
GlobalStatus.RollbackRetrying, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying,
|
||||
GlobalStatus.AsyncCommitting}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> findGlobalSessions(SessionCondition condition) {
|
||||
// nothing need to do
|
||||
return transactionStoreManager.readSession(condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
return lockCallable.call();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.storage.redis.store;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Transaction;
|
||||
import io.seata.common.exception.StoreException;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.common.exception.RedisException;
|
||||
import io.seata.common.util.BeanUtils;
|
||||
import io.seata.common.XID;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.store.BranchTransactionDO;
|
||||
import io.seata.core.store.GlobalTransactionDO;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.storage.SessionConverter;
|
||||
import io.seata.server.storage.redis.JedisPooledFactory;
|
||||
import io.seata.server.store.AbstractTransactionStoreManager;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_GMT_MODIFIED;
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_STATUS;
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_XID;
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_GMT_MODIFIED;
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_STATUS;
|
||||
import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_XID;
|
||||
|
||||
/**
|
||||
* The redis transaction store manager
|
||||
*
|
||||
* @author funkye
|
||||
* @author wangzhongxiang
|
||||
*/
|
||||
public class RedisTransactionStoreManager extends AbstractTransactionStoreManager implements TransactionStoreManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RedisTransactionStoreManager.class);
|
||||
|
||||
/**the prefix of the branch transactions*/
|
||||
private static final String REDIS_SEATA_BRANCHES_PREFIX = "SEATA_BRANCHES_";
|
||||
|
||||
/**the prefix of the branch transaction*/
|
||||
private static final String REDIS_SEATA_BRANCH_PREFIX = "SEATA_BRANCH_";
|
||||
|
||||
/**the prefix of the global transaction*/
|
||||
private static final String REDIS_SEATA_GLOBAL_PREFIX = "SEATA_GLOBAL_";
|
||||
|
||||
/**the prefix of the global transaction status*/
|
||||
private static final String REDIS_SEATA_STATUS_PREFIX = "SEATA_STATUS_";
|
||||
|
||||
private static volatile RedisTransactionStoreManager instance;
|
||||
|
||||
private static final String OK = "OK";
|
||||
|
||||
/**
|
||||
* Get the instance.
|
||||
*/
|
||||
public static RedisTransactionStoreManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (RedisTransactionStoreManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new RedisTransactionStoreManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeSession(LogOperation logOperation, SessionStorable session) {
|
||||
if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
|
||||
return insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
|
||||
return updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
|
||||
return deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
|
||||
return insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
|
||||
return updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
|
||||
return deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
|
||||
} else {
|
||||
throw new StoreException("Unknown LogOperation:" + logOperation.name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert branch transaction
|
||||
* @param branchTransactionDO
|
||||
* @return the boolean
|
||||
*/
|
||||
private boolean insertBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String branchKey = buildBranchKey(branchTransactionDO.getBranchId());
|
||||
String branchListKey = buildBranchListKeyByXid(branchTransactionDO.getXid());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Date now = new Date();
|
||||
branchTransactionDO.setGmtCreate(now);
|
||||
branchTransactionDO.setGmtModified(now);
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.hmset(branchKey, BeanUtils.objectToMap(branchTransactionDO));
|
||||
pipelined.rpush(branchListKey, branchKey);
|
||||
pipelined.sync();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the branch transaction
|
||||
* @param branchTransactionDO
|
||||
* @return
|
||||
*/
|
||||
private boolean deleteBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String branchKey = buildBranchKey(branchTransactionDO.getBranchId());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Map<String, String> branchTransactionDOMap = jedis.hgetAll(branchKey);
|
||||
String xid = branchTransactionDOMap.get(REDIS_KEY_BRANCH_XID);
|
||||
if (StringUtils.isEmpty(xid)) {
|
||||
return true;
|
||||
}
|
||||
String branchListKey = buildBranchListKeyByXid(branchTransactionDO.getXid());
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.lrem(branchListKey, 0, branchKey);
|
||||
pipelined.del(branchKey);
|
||||
pipelined.sync();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the branch transaction
|
||||
* @param branchTransactionDO
|
||||
* @return
|
||||
*/
|
||||
private boolean updateBranchTransactionDO(BranchTransactionDO branchTransactionDO) {
|
||||
String branchKey = buildBranchKey(branchTransactionDO.getBranchId());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
String previousBranchStatus = jedis.hget(branchKey, REDIS_KEY_BRANCH_STATUS);
|
||||
if (StringUtils.isEmpty(previousBranchStatus)) {
|
||||
throw new StoreException("Branch transaction is not exist, update branch transaction failed.");
|
||||
}
|
||||
Map<String,String> map = new HashMap<>(2,1);
|
||||
map.put(REDIS_KEY_BRANCH_STATUS,String.valueOf(branchTransactionDO.getStatus()));
|
||||
map.put(REDIS_KEY_BRANCH_GMT_MODIFIED,String.valueOf((new Date()).getTime()));
|
||||
jedis.hmset(branchKey, map);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the global transaction.
|
||||
* @param globalTransactionDO
|
||||
* @return
|
||||
*/
|
||||
private boolean insertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String globalKey = buildGlobalKeyByTransactionId(globalTransactionDO.getTransactionId());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Date now = new Date();
|
||||
globalTransactionDO.setGmtCreate(now);
|
||||
globalTransactionDO.setGmtModified(now);
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.hmset(globalKey, BeanUtils.objectToMap(globalTransactionDO));
|
||||
pipelined.rpush(buildGlobalStatus(globalTransactionDO.getStatus()), globalTransactionDO.getXid());
|
||||
pipelined.sync();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the global transaction.
|
||||
* It will operate two parts:
|
||||
* 1.delete the global session map
|
||||
* 2.remove the xid from the global status list
|
||||
* If the operate failed,the succeed operates will rollback
|
||||
* @param globalTransactionDO
|
||||
* @return
|
||||
*/
|
||||
private boolean deleteGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String globalKey = buildGlobalKeyByTransactionId(globalTransactionDO.getTransactionId());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Map<String, String> globalTransactionDoMap = jedis.hgetAll(globalKey);
|
||||
String xid = globalTransactionDoMap.get(REDIS_KEY_GLOBAL_XID);
|
||||
if (StringUtils.isEmpty(xid)) {
|
||||
LOGGER.warn("Global transaction is not exist,xid = {}.Maybe has been deleted by another tc server",
|
||||
globalTransactionDO.getXid());
|
||||
return true;
|
||||
}
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
pipelined.lrem(buildGlobalStatus(globalTransactionDO.getStatus()), 0, globalTransactionDO.getXid());
|
||||
pipelined.del(globalKey);
|
||||
pipelined.sync();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the global transaction.
|
||||
* It will update two parts:
|
||||
* 1.the global session map
|
||||
* 2.the global status list
|
||||
* If the update failed,the succeed operates will rollback
|
||||
* @param globalTransactionDO
|
||||
* @return
|
||||
*/
|
||||
private boolean updateGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
|
||||
String xid = globalTransactionDO.getXid();
|
||||
String globalKey = buildGlobalKeyByTransactionId(globalTransactionDO.getTransactionId());
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
// Defensive watch to prevent other TC server operating concurrently,Fail fast
|
||||
jedis.watch(globalKey);
|
||||
List<String> statusAndGmtModified = jedis.hmget(globalKey, REDIS_KEY_GLOBAL_STATUS, REDIS_KEY_GLOBAL_GMT_MODIFIED);
|
||||
String previousStatus = statusAndGmtModified.get(0);
|
||||
if (StringUtils.isEmpty(previousStatus)) {
|
||||
jedis.unwatch();
|
||||
throw new StoreException("Global transaction is not exist, update global transaction failed.");
|
||||
}
|
||||
if (previousStatus.equals(String.valueOf(globalTransactionDO.getStatus()))) {
|
||||
jedis.unwatch();
|
||||
return true;
|
||||
}
|
||||
|
||||
String previousGmtModified = statusAndGmtModified.get(1);
|
||||
Transaction multi = jedis.multi();
|
||||
Map<String,String> map = new HashMap<>(2);
|
||||
map.put(REDIS_KEY_GLOBAL_STATUS,String.valueOf(globalTransactionDO.getStatus()));
|
||||
map.put(REDIS_KEY_GLOBAL_GMT_MODIFIED,String.valueOf((new Date()).getTime()));
|
||||
multi.hmset(globalKey,map);
|
||||
multi.lrem(buildGlobalStatus(Integer.valueOf(previousStatus)),0, xid);
|
||||
multi.rpush(buildGlobalStatus(globalTransactionDO.getStatus()), xid);
|
||||
List<Object> exec = multi.exec();
|
||||
String hmset = exec.get(0).toString();
|
||||
long lrem = (long)exec.get(1);
|
||||
long rpush = (long)exec.get(2);
|
||||
if (OK.equalsIgnoreCase(hmset) && lrem > 0 && rpush > 0) {
|
||||
return true;
|
||||
} else {
|
||||
// If someone failed, the succeed operations need rollback
|
||||
if (OK.equalsIgnoreCase(hmset)) {
|
||||
// Defensive watch to prevent other TC server operating concurrently,give up this operate
|
||||
jedis.watch(globalKey);
|
||||
String xid2 = jedis.hget(globalKey, REDIS_KEY_GLOBAL_XID);
|
||||
if (StringUtils.isNotEmpty(xid2)) {
|
||||
Map<String,String> mapPrevious = new HashMap<>(2,1);
|
||||
mapPrevious.put(REDIS_KEY_GLOBAL_STATUS,previousStatus);
|
||||
mapPrevious.put(REDIS_KEY_GLOBAL_GMT_MODIFIED,previousGmtModified);
|
||||
Transaction multi2 = jedis.multi();
|
||||
multi2.hmset(globalKey,mapPrevious);
|
||||
multi2.exec();
|
||||
}
|
||||
}
|
||||
if (lrem > 0) {
|
||||
jedis.rpush(buildGlobalStatus(Integer.valueOf(previousStatus)),xid);
|
||||
}
|
||||
if (rpush > 0) {
|
||||
jedis.lrem(buildGlobalStatus(globalTransactionDO.getStatus()),0,xid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RedisException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param withBranchSessions the withBranchSessions
|
||||
* @return the global session
|
||||
*/
|
||||
@Override
|
||||
public GlobalSession readSession(String xid, boolean withBranchSessions) {
|
||||
String transactionId = String.valueOf(XID.getTransactionId(xid));
|
||||
String globalKey = buildGlobalKeyByTransactionId(transactionId);
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Map<String, String> map = jedis.hgetAll(globalKey);
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
return null;
|
||||
}
|
||||
GlobalTransactionDO globalTransactionDO = (GlobalTransactionDO)BeanUtils.mapToObject(map, GlobalTransactionDO.class);
|
||||
List<BranchTransactionDO> branchTransactionDOs = null;
|
||||
if (withBranchSessions) {
|
||||
branchTransactionDOs = this.readBranchSessionByXid(jedis,xid);
|
||||
}
|
||||
return getGlobalSession(globalTransactionDO,branchTransactionDOs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param xid
|
||||
* the xid
|
||||
* @return the global session
|
||||
*/
|
||||
@Override
|
||||
public GlobalSession readSession(String xid) {
|
||||
return this.readSession(xid, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read globalSession list by global status
|
||||
*
|
||||
* @param statuses the statuses
|
||||
* @return the list
|
||||
*/
|
||||
public List<GlobalSession> readSession(GlobalStatus[] statuses) {
|
||||
List<String> statusKeys = new ArrayList<>();
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
statusKeys.add(buildGlobalStatus(statuses[i].getCode()));
|
||||
}
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Pipeline pipelined = jedis.pipelined();
|
||||
statusKeys.stream().forEach(statusKey -> pipelined.lrange(statusKey,0,-1));
|
||||
List<List<String>> list = (List<List<String>>)(List)pipelined.syncAndReturnAll();
|
||||
List<String> xids = new ArrayList<>();
|
||||
if (CollectionUtils.isNotEmpty(list)) {
|
||||
xids = list.stream().flatMap(ll -> ll.stream()).collect(Collectors.toList());
|
||||
}
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
xids.parallelStream().forEach(xid -> {
|
||||
GlobalSession globalSession = this.readSession(xid, true);
|
||||
if (globalSession != null) {
|
||||
globalSessions.add(globalSession);
|
||||
}
|
||||
});
|
||||
return globalSessions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the global session list by different condition
|
||||
* @param sessionCondition the session condition
|
||||
* @return the global sessions
|
||||
*/
|
||||
@Override
|
||||
public List<GlobalSession> readSession(SessionCondition sessionCondition) {
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
if (StringUtils.isNotEmpty(sessionCondition.getXid())) {
|
||||
GlobalSession globalSession = this.readSession(sessionCondition.getXid(), true);
|
||||
if (globalSession != null) {
|
||||
globalSessions.add(globalSession);
|
||||
}
|
||||
return globalSessions;
|
||||
} else if (sessionCondition.getTransactionId() != null) {
|
||||
GlobalSession globalSession = this
|
||||
.readSessionByTransactionId(sessionCondition.getTransactionId().toString(), true);
|
||||
if (globalSession != null) {
|
||||
globalSessions.add(globalSession);
|
||||
}
|
||||
return globalSessions;
|
||||
} else if (CollectionUtils.isNotEmpty(sessionCondition.getStatuses())) {
|
||||
return readSession(sessionCondition.getStatuses());
|
||||
} else if (sessionCondition.getStatus() != null) {
|
||||
return readSession(new GlobalStatus[]{sessionCondition.getStatus()});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* assemble the global session and branch session
|
||||
* @param globalTransactionDO the global transactionDo
|
||||
* @param branchTransactionDOs the branch transactionDos
|
||||
* @return the global session with branch session
|
||||
*/
|
||||
private GlobalSession getGlobalSession(GlobalTransactionDO globalTransactionDO,
|
||||
List<BranchTransactionDO> branchTransactionDOs) {
|
||||
GlobalSession globalSession = SessionConverter.convertGlobalSession(globalTransactionDO);
|
||||
if (CollectionUtils.isNotEmpty(branchTransactionDOs)) {
|
||||
for (BranchTransactionDO branchTransactionDO : branchTransactionDOs) {
|
||||
globalSession.add(SessionConverter.convertBranchSession(branchTransactionDO));
|
||||
}
|
||||
}
|
||||
return globalSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* read the global session by transactionId
|
||||
* @param transactionId the transaction id
|
||||
* @param withBranchSessions if read branch sessions
|
||||
* @return the global session
|
||||
*/
|
||||
private GlobalSession readSessionByTransactionId(String transactionId, boolean withBranchSessions) {
|
||||
String globalKey = buildGlobalKeyByTransactionId(transactionId);
|
||||
String xid = null;
|
||||
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
|
||||
Map<String, String> map = jedis.hgetAll(globalKey);
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
return null;
|
||||
}
|
||||
GlobalTransactionDO globalTransactionDO = (GlobalTransactionDO)BeanUtils.mapToObject(map, GlobalTransactionDO.class);
|
||||
if (globalTransactionDO != null) {
|
||||
xid = globalTransactionDO.getXid();
|
||||
}
|
||||
List<BranchTransactionDO> branchTransactionDOs = new ArrayList<>();
|
||||
if (withBranchSessions) {
|
||||
branchTransactionDOs = this.readBranchSessionByXid(jedis,xid);
|
||||
}
|
||||
return getGlobalSession(globalTransactionDO,branchTransactionDOs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the branch session list by xid
|
||||
* @param jedis
|
||||
* @param xid the xid
|
||||
* @return the branch transactionDo list
|
||||
*/
|
||||
private List<BranchTransactionDO> readBranchSessionByXid(Jedis jedis,String xid) {
|
||||
List<BranchTransactionDO> branchTransactionDOs = new ArrayList<>();
|
||||
String branchListKey = buildBranchListKeyByXid(xid);
|
||||
List<String> branchKeys = jedis.lrange(branchListKey, 0, -1);
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
if (CollectionUtils.isNotEmpty(branchKeys)) {
|
||||
branchKeys.stream().forEachOrdered(branchKey -> pipeline.hgetAll(branchKey));
|
||||
List<Object> branchInfos = pipeline.syncAndReturnAll();
|
||||
for (Object branchInfo : branchInfos) {
|
||||
if (branchInfo != null) {
|
||||
Map<String, String> branchInfoMap = (Map<String, String>) branchInfo;
|
||||
Optional<BranchTransactionDO> branchTransactionDO =
|
||||
Optional.ofNullable((BranchTransactionDO) BeanUtils.mapToObject(branchInfoMap, BranchTransactionDO.class));
|
||||
branchTransactionDO.ifPresent(branchTransactionDOs::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(branchTransactionDOs)) {
|
||||
branchTransactionDOs = branchTransactionDOs.stream().sorted(Comparator.comparing(BranchTransactionDO::getGmtCreate))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return branchTransactionDOs;
|
||||
}
|
||||
|
||||
private String buildBranchListKeyByXid(String xid) {
|
||||
return REDIS_SEATA_BRANCHES_PREFIX + xid;
|
||||
}
|
||||
|
||||
private String buildGlobalKeyByTransactionId(Object transactionId) {
|
||||
return REDIS_SEATA_GLOBAL_PREFIX + transactionId;
|
||||
}
|
||||
|
||||
private String buildBranchKey(Long branchId) {
|
||||
return REDIS_SEATA_BRANCH_PREFIX + branchId;
|
||||
}
|
||||
|
||||
private String buildGlobalStatus(Integer status) {
|
||||
return REDIS_SEATA_STATUS_PREFIX + status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The type Abstract transaction store manager.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public abstract class AbstractTransactionStoreManager implements TransactionStoreManager {
|
||||
|
||||
@Override
|
||||
public GlobalSession readSession(String xid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession readSession(String xid, boolean withBranchSessions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> readSession(SessionCondition sessionCondition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.core.store.db.AbstractDataSourceProvider;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* The dbcp datasource provider
|
||||
* @author zhangsen
|
||||
* @author ggndnn
|
||||
* @author will
|
||||
*/
|
||||
@LoadLevel(name = "dbcp")
|
||||
public class DbcpDataSourceProvider extends AbstractDataSourceProvider {
|
||||
|
||||
@Override
|
||||
public DataSource generate() {
|
||||
BasicDataSource ds = new BasicDataSource();
|
||||
ds.setDriverClassName(getDriverClassName());
|
||||
// DriverClassLoader works if upgrade commons-dbcp to at least 1.3.1.
|
||||
// https://issues.apache.org/jira/browse/DBCP-333
|
||||
ds.setDriverClassLoader(getDriverClassLoader());
|
||||
ds.setUrl(getUrl());
|
||||
ds.setUsername(getUser());
|
||||
|
||||
ds.setPassword(getPassword());
|
||||
ds.setInitialSize(getMinConn());
|
||||
ds.setMaxTotal(getMaxConn());
|
||||
ds.setMinIdle(getMinConn());
|
||||
ds.setMaxIdle(getMinConn());
|
||||
ds.setMaxWaitMillis(getMaxWait());
|
||||
ds.setTimeBetweenEvictionRunsMillis(120000);
|
||||
ds.setNumTestsPerEvictionRun(1);
|
||||
ds.setTestWhileIdle(true);
|
||||
ds.setValidationQuery(getValidationQuery(getDBType()));
|
||||
ds.setConnectionProperties("useUnicode=yes;characterEncoding=utf8;socketTimeout=5000;connectTimeout=500");
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.core.store.db.AbstractDataSourceProvider;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* The druid datasource provider
|
||||
* @author zhangsen
|
||||
* @author ggndnn
|
||||
* @author will
|
||||
*/
|
||||
@LoadLevel(name = "druid")
|
||||
public class DruidDataSourceProvider extends AbstractDataSourceProvider {
|
||||
|
||||
@Override
|
||||
public DataSource generate() {
|
||||
DruidDataSource ds = new DruidDataSource();
|
||||
ds.setDriverClassName(getDriverClassName());
|
||||
ds.setDriverClassLoader(getDriverClassLoader());
|
||||
ds.setUrl(getUrl());
|
||||
ds.setUsername(getUser());
|
||||
ds.setPassword(getPassword());
|
||||
ds.setInitialSize(getMinConn());
|
||||
ds.setMaxActive(getMaxConn());
|
||||
ds.setMinIdle(getMinConn());
|
||||
ds.setMaxWait(getMaxWait());
|
||||
ds.setTimeBetweenEvictionRunsMillis(120000);
|
||||
ds.setMinEvictableIdleTimeMillis(300000);
|
||||
ds.setTestWhileIdle(true);
|
||||
ds.setTestOnBorrow(false);
|
||||
ds.setPoolPreparedStatements(true);
|
||||
ds.setMaxPoolPreparedStatementPerConnectionSize(20);
|
||||
ds.setValidationQuery(getValidationQuery(getDBType()));
|
||||
ds.setDefaultAutoCommit(true);
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.core.store.db.AbstractDataSourceProvider;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* The hikari datasource provider
|
||||
* @author diguage
|
||||
* @author will
|
||||
*/
|
||||
@LoadLevel(name = "hikari")
|
||||
public class HikariDataSourceProvider extends AbstractDataSourceProvider {
|
||||
|
||||
@Override
|
||||
public DataSource generate() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("dataSource.cachePrepStmts", "true");
|
||||
properties.setProperty("dataSource.prepStmtCacheSize", "250");
|
||||
properties.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
|
||||
properties.setProperty("dataSource.useServerPrepStmts", "true");
|
||||
properties.setProperty("dataSource.useLocalSessionState", "true");
|
||||
properties.setProperty("dataSource.rewriteBatchedStatements", "true");
|
||||
properties.setProperty("dataSource.cacheResultSetMetadata", "true");
|
||||
properties.setProperty("dataSource.cacheServerConfiguration", "true");
|
||||
properties.setProperty("dataSource.elideSetAutoCommits", "true");
|
||||
properties.setProperty("dataSource.maintainTimeStats", "false");
|
||||
|
||||
HikariConfig config = new HikariConfig(properties);
|
||||
config.setDriverClassName(getDriverClassName());
|
||||
config.setJdbcUrl(getUrl());
|
||||
config.setUsername(getUser());
|
||||
config.setPassword(getPassword());
|
||||
config.setMaximumPoolSize(getMaxConn());
|
||||
config.setMinimumIdle(getMinConn());
|
||||
config.setAutoCommit(true);
|
||||
config.setConnectionTimeout(getMaxWait());
|
||||
config.setInitializationFailTimeout(-1);
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
/**
|
||||
* The interface Session storable.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface SessionStorable {
|
||||
|
||||
/**
|
||||
* Encode byte [ ].
|
||||
*
|
||||
* @return the byte [ ]
|
||||
*/
|
||||
byte[] encode();
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param src the src
|
||||
*/
|
||||
void decode(byte[] src);
|
||||
}
|
||||
63
server/src/main/java/io/seata/server/store/StoreConfig.java
Normal file
63
server/src/main/java/io/seata/server/store/StoreConfig.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.server.storage.file.FlushDiskMode;
|
||||
|
||||
import static io.seata.core.constants.ConfigurationKeys.STORE_FILE_PREFIX;
|
||||
|
||||
|
||||
/**
|
||||
* @author lizhao
|
||||
*/
|
||||
public class StoreConfig {
|
||||
|
||||
private static final Configuration CONFIGURATION = ConfigurationFactory.getInstance();
|
||||
|
||||
|
||||
/**
|
||||
* Default 16kb.
|
||||
*/
|
||||
private static final int DEFAULT_MAX_BRANCH_SESSION_SIZE = 1024 * 16;
|
||||
|
||||
/**
|
||||
* Default 512b.
|
||||
*/
|
||||
private static final int DEFAULT_MAX_GLOBAL_SESSION_SIZE = 512;
|
||||
|
||||
/**
|
||||
* Default 16kb.
|
||||
*/
|
||||
private static final int DEFAULT_WRITE_BUFFER_SIZE = 1024 * 16;
|
||||
|
||||
public static int getMaxBranchSessionSize() {
|
||||
return CONFIGURATION.getInt(STORE_FILE_PREFIX + "maxBranchSessionSize", DEFAULT_MAX_BRANCH_SESSION_SIZE);
|
||||
}
|
||||
|
||||
public static int getMaxGlobalSessionSize() {
|
||||
return CONFIGURATION.getInt(STORE_FILE_PREFIX + "maxGlobalSessionSize", DEFAULT_MAX_GLOBAL_SESSION_SIZE);
|
||||
}
|
||||
|
||||
public static int getFileWriteBufferCacheSize() {
|
||||
return CONFIGURATION.getInt(STORE_FILE_PREFIX + "fileWriteBufferCacheSize", DEFAULT_WRITE_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public static FlushDiskMode getFlushDiskMode() {
|
||||
return FlushDiskMode.findDiskMode(CONFIGURATION.getConfig(STORE_FILE_PREFIX + "flushDiskMode"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.store;
|
||||
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The interface Transaction store manager.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface TransactionStoreManager {
|
||||
|
||||
/**
|
||||
* Write session boolean.
|
||||
*
|
||||
* @param logOperation the log operation
|
||||
* @param session the session
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean writeSession(LogOperation logOperation, SessionStorable session);
|
||||
|
||||
|
||||
/**
|
||||
* Read global session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @return the global session
|
||||
*/
|
||||
GlobalSession readSession(String xid);
|
||||
|
||||
/**
|
||||
* Read session global session.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param withBranchSessions the withBranchSessions
|
||||
* @return the global session
|
||||
*/
|
||||
GlobalSession readSession(String xid, boolean withBranchSessions);
|
||||
|
||||
/**
|
||||
* Read session by status list.
|
||||
*
|
||||
* @param sessionCondition the session condition
|
||||
* @return the list
|
||||
*/
|
||||
List<GlobalSession> readSession(SessionCondition sessionCondition);
|
||||
|
||||
/**
|
||||
* Shutdown.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
|
||||
/**
|
||||
* The enum Log operation.
|
||||
*/
|
||||
enum LogOperation {
|
||||
|
||||
/**
|
||||
* Global add log operation.
|
||||
*/
|
||||
GLOBAL_ADD((byte)1),
|
||||
/**
|
||||
* Global update log operation.
|
||||
*/
|
||||
GLOBAL_UPDATE((byte)2),
|
||||
/**
|
||||
* Global remove log operation.
|
||||
*/
|
||||
GLOBAL_REMOVE((byte)3),
|
||||
/**
|
||||
* Branch add log operation.
|
||||
*/
|
||||
BRANCH_ADD((byte)4),
|
||||
/**
|
||||
* Branch update log operation.
|
||||
*/
|
||||
BRANCH_UPDATE((byte)5),
|
||||
/**
|
||||
* Branch remove log operation.
|
||||
*/
|
||||
BRANCH_REMOVE((byte)6);
|
||||
|
||||
private byte code;
|
||||
|
||||
LogOperation(byte code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets code.
|
||||
*
|
||||
* @return the code
|
||||
*/
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets log operation by code.
|
||||
*
|
||||
* @param code the code
|
||||
* @return the log operation by code
|
||||
*/
|
||||
public static LogOperation getLogOperationByCode(byte code) {
|
||||
for (LogOperation temp : values()) {
|
||||
if (temp.getCode() == code) {
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown LogOperation[" + code + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.transaction.at;
|
||||
|
||||
import io.seata.core.exception.BranchTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.coordinator.AbstractCore;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
|
||||
import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflict;
|
||||
|
||||
/**
|
||||
* The type at core.
|
||||
*
|
||||
* @author ph3636
|
||||
*/
|
||||
public class ATCore extends AbstractCore {
|
||||
|
||||
public ATCore(RemotingServer remotingServer) {
|
||||
super(remotingServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchType getHandleBranchType() {
|
||||
return BranchType.AT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
if (!branchSession.lock()) {
|
||||
throw new BranchTransactionException(LockKeyConflict, String
|
||||
.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(),
|
||||
branchSession.getBranchId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void branchSessionUnlock(BranchSession branchSession) throws TransactionException {
|
||||
branchSession.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys)
|
||||
throws TransactionException {
|
||||
return lockManager.isLockable(xid, resourceId, lockKeys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.transaction.saga;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.exception.GlobalTransactionException;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.protocol.transaction.BranchCommitRequest;
|
||||
import io.seata.core.protocol.transaction.BranchCommitResponse;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackResponse;
|
||||
import io.seata.core.rpc.netty.ChannelManager;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.coordinator.AbstractCore;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHelper;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
|
||||
/**
|
||||
* The type saga core.
|
||||
*
|
||||
* @author ph3636
|
||||
*/
|
||||
public class SagaCore extends AbstractCore {
|
||||
|
||||
public SagaCore(RemotingServer remotingServer) {
|
||||
super(remotingServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchType getHandleBranchType() {
|
||||
return BranchType.SAGA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void globalSessionStatusCheck(GlobalSession globalSession) throws GlobalTransactionException {
|
||||
// SAGA type accept forward(retry) operation on timeout or commit fail, forward operation will register remaining branches
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchCommitSend(BranchCommitRequest request, GlobalSession globalSession,
|
||||
BranchSession branchSession) throws IOException, TimeoutException {
|
||||
Map<String, Channel> channels = ChannelManager.getRmChannels();
|
||||
if (CollectionUtils.isEmpty(channels)) {
|
||||
LOGGER.error("Failed to commit SAGA global[" + globalSession.getXid() + ", RM channels is empty.");
|
||||
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
|
||||
}
|
||||
String sagaResourceId = getSagaResourceId(globalSession);
|
||||
Channel sagaChannel = channels.get(sagaResourceId);
|
||||
if (sagaChannel == null) {
|
||||
LOGGER.error("Failed to commit SAGA global[" + globalSession.getXid()
|
||||
+ ", cannot find channel by resourceId[" + sagaResourceId + "]");
|
||||
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
|
||||
}
|
||||
BranchCommitResponse response = (BranchCommitResponse) remotingServer.sendSyncRequest(sagaChannel, request);
|
||||
return response.getBranchStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchRollbackSend(BranchRollbackRequest request, GlobalSession globalSession,
|
||||
BranchSession branchSession) throws IOException, TimeoutException {
|
||||
Map<String, Channel> channels = ChannelManager.getRmChannels();
|
||||
if (CollectionUtils.isEmpty(channels)) {
|
||||
LOGGER.error("Failed to rollback SAGA global[" + globalSession.getXid() + ", RM channels is empty.");
|
||||
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
|
||||
}
|
||||
String sagaResourceId = getSagaResourceId(globalSession);
|
||||
Channel sagaChannel = channels.get(sagaResourceId);
|
||||
if (sagaChannel == null) {
|
||||
LOGGER.error("Failed to rollback SAGA global[" + globalSession.getXid()
|
||||
+ ", cannot find channel by resourceId[" + sagaResourceId + "]");
|
||||
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
|
||||
}
|
||||
BranchRollbackResponse response = (BranchRollbackResponse) remotingServer.sendSyncRequest(sagaChannel, request);
|
||||
return response.getBranchStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
try {
|
||||
BranchStatus branchStatus = branchCommit(globalSession, SessionHelper.newBranch(BranchType.SAGA,
|
||||
globalSession.getXid(), -1, getSagaResourceId(globalSession), globalSession.getStatus().name()));
|
||||
|
||||
switch (branchStatus) {
|
||||
case PhaseTwo_Committed:
|
||||
removeAllBranches(globalSession);
|
||||
LOGGER.info("Successfully committed SAGA global[" + globalSession.getXid() + "]");
|
||||
break;
|
||||
case PhaseTwo_Rollbacked:
|
||||
LOGGER.info("Successfully rollbacked SAGA global[" + globalSession.getXid() + "]");
|
||||
removeAllBranches(globalSession);
|
||||
SessionHelper.endRollbacked(globalSession);
|
||||
return false;
|
||||
case PhaseTwo_RollbackFailed_Retryable:
|
||||
LOGGER.error("By [{}], failed to rollback SAGA global [{}], will retry later.", branchStatus,
|
||||
globalSession.getXid());
|
||||
SessionHolder.getRetryCommittingSessionManager().removeGlobalSession(globalSession);
|
||||
globalSession.queueToRetryRollback();
|
||||
return false;
|
||||
case PhaseOne_Failed:
|
||||
LOGGER.error("By [{}], finish SAGA global [{}]", branchStatus, globalSession.getXid());
|
||||
removeAllBranches(globalSession);
|
||||
globalSession.changeStatus(GlobalStatus.Finished);
|
||||
globalSession.end();
|
||||
return false;
|
||||
case PhaseTwo_CommitFailed_Unretryable:
|
||||
if (globalSession.canBeCommittedAsync()) {
|
||||
LOGGER.error("By [{}], failed to commit SAGA global [{}]", branchStatus,
|
||||
globalSession.getXid());
|
||||
break;
|
||||
} else {
|
||||
SessionHelper.endCommitFailed(globalSession);
|
||||
LOGGER.error("Finally, failed to commit SAGA global[{}]", globalSession.getXid());
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryCommit();
|
||||
return false;
|
||||
} else {
|
||||
LOGGER.error("Failed to commit SAGA global[{}], will retry later.", globalSession.getXid());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("Failed to commit global[" + globalSession.getXid() + "]", ex);
|
||||
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryRollback();
|
||||
}
|
||||
throw new TransactionException(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
|
||||
try {
|
||||
BranchStatus branchStatus = branchRollback(globalSession, SessionHelper.newBranch(BranchType.SAGA,
|
||||
globalSession.getXid(), -1, getSagaResourceId(globalSession), globalSession.getStatus().name()));
|
||||
|
||||
switch (branchStatus) {
|
||||
case PhaseTwo_Rollbacked:
|
||||
removeAllBranches(globalSession);
|
||||
LOGGER.info("Successfully rollbacked SAGA global[{}]",globalSession.getXid());
|
||||
break;
|
||||
case PhaseTwo_RollbackFailed_Unretryable:
|
||||
SessionHelper.endRollbackFailed(globalSession);
|
||||
LOGGER.error("Failed to rollback SAGA global[{}]", globalSession.getXid());
|
||||
return false;
|
||||
case PhaseTwo_CommitFailed_Retryable:
|
||||
SessionHolder.getRetryRollbackingSessionManager().removeGlobalSession(globalSession);
|
||||
globalSession.queueToRetryCommit();
|
||||
LOGGER.warn("Retry by custom recover strategy [Forward] on timeout, SAGA global[{}]", globalSession.getXid());
|
||||
return false;
|
||||
default:
|
||||
LOGGER.error("Failed to rollback SAGA global[{}]", globalSession.getXid());
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryRollback();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("Failed to rollback global[{}]", globalSession.getXid(), ex);
|
||||
if (!retrying) {
|
||||
globalSession.queueToRetryRollback();
|
||||
}
|
||||
throw new TransactionException(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGlobalReport(GlobalSession globalSession, String xid, GlobalStatus globalStatus) throws TransactionException {
|
||||
if (GlobalStatus.Committed.equals(globalStatus)) {
|
||||
removeAllBranches(globalSession);
|
||||
SessionHelper.endCommitted(globalSession);
|
||||
LOGGER.info("Global[{}] committed", globalSession.getXid());
|
||||
} else if (GlobalStatus.Rollbacked.equals(globalStatus)
|
||||
|| GlobalStatus.Finished.equals(globalStatus)) {
|
||||
removeAllBranches(globalSession);
|
||||
SessionHelper.endRollbacked(globalSession);
|
||||
LOGGER.info("Global[{}] rollbacked", globalSession.getXid());
|
||||
} else {
|
||||
globalSession.changeStatus(globalStatus);
|
||||
LOGGER.info("Global[{}] reporting is successfully done. status[{}]", globalSession.getXid(), globalSession.getStatus());
|
||||
|
||||
if (GlobalStatus.RollbackRetrying.equals(globalStatus)
|
||||
|| GlobalStatus.TimeoutRollbackRetrying.equals(globalStatus)
|
||||
|| GlobalStatus.UnKnown.equals(globalStatus)) {
|
||||
globalSession.queueToRetryRollback();
|
||||
LOGGER.info("Global[{}] will retry rollback", globalSession.getXid());
|
||||
} else if (GlobalStatus.CommitRetrying.equals(globalStatus)) {
|
||||
globalSession.queueToRetryCommit();
|
||||
LOGGER.info("Global[{}] will retry commit", globalSession.getXid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all branches
|
||||
*
|
||||
* @param globalSession the globalSession
|
||||
* @throws TransactionException the TransactionException
|
||||
*/
|
||||
private void removeAllBranches(GlobalSession globalSession) throws TransactionException {
|
||||
ArrayList<BranchSession> branchSessions = globalSession.getSortedBranches();
|
||||
for (BranchSession branchSession : branchSessions) {
|
||||
globalSession.removeBranch(branchSession);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get saga ResourceId
|
||||
*
|
||||
* @param globalSession the globalSession
|
||||
* @return sagaResourceId
|
||||
*/
|
||||
private String getSagaResourceId(GlobalSession globalSession) {
|
||||
return globalSession.getApplicationId() + "#" + globalSession.getTransactionServiceGroup();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.transaction.tcc;
|
||||
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.coordinator.AbstractCore;
|
||||
|
||||
/**
|
||||
* The type tcc core.
|
||||
*
|
||||
* @author ph3636
|
||||
*/
|
||||
public class TccCore extends AbstractCore {
|
||||
|
||||
public TccCore(RemotingServer remotingServer) {
|
||||
super(remotingServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchType getHandleBranchType() {
|
||||
return BranchType.TCC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.transaction.xa;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.coordinator.AbstractCore;
|
||||
|
||||
/**
|
||||
* The type XA core.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
public class XACore extends AbstractCore {
|
||||
|
||||
public XACore(RemotingServer remotingServer) {
|
||||
super(remotingServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchType getHandleBranchType() {
|
||||
return BranchType.XA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status,
|
||||
String applicationData) throws TransactionException {
|
||||
super.branchReport(branchType, xid, branchId, status, applicationData);
|
||||
if (BranchStatus.PhaseOne_Failed == status) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.server.auth.DefaultCheckAuthHandler
|
||||
@@ -0,0 +1,3 @@
|
||||
io.seata.server.store.DbcpDataSourceProvider
|
||||
io.seata.server.store.DruidDataSourceProvider
|
||||
io.seata.server.store.HikariDataSourceProvider
|
||||
@@ -0,0 +1,4 @@
|
||||
io.seata.server.transaction.at.ATCore
|
||||
io.seata.server.transaction.tcc.TccCore
|
||||
io.seata.server.transaction.saga.SagaCore
|
||||
io.seata.server.transaction.xa.XACore
|
||||
@@ -0,0 +1,3 @@
|
||||
io.seata.server.storage.db.lock.DataBaseLockManager
|
||||
io.seata.server.storage.file.lock.FileLockManager
|
||||
io.seata.server.storage.redis.lock.RedisLockManager
|
||||
@@ -0,0 +1,3 @@
|
||||
io.seata.server.storage.file.session.FileSessionManager
|
||||
io.seata.server.storage.db.session.DataBaseSessionManager
|
||||
io.seata.server.storage.redis.session.RedisSessionManager
|
||||
30
server/src/main/resources/README-zh.md
Normal file
30
server/src/main/resources/README-zh.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 脚本说明
|
||||
|
||||
## [client](https://github.com/seata/seata/tree/develop/script/client)
|
||||
|
||||
> 存放用于客户端的配置和SQL
|
||||
|
||||
- at: AT模式下的 `undo_log` 建表语句
|
||||
- conf: 客户端的配置文件
|
||||
- saga: SAGA 模式下所需表的建表语句
|
||||
- spring: SpringBoot 应用支持的配置文件
|
||||
|
||||
## [server](https://github.com/seata/seata/tree/develop/script/server)
|
||||
|
||||
> 存放server侧所需SQL和部署脚本
|
||||
|
||||
- db: server 侧的保存模式为 `db` 时所需表的建表语句
|
||||
- docker-compose: server 侧通过 docker-compose 部署的脚本
|
||||
- helm: server 侧通过 Helm 部署的脚本
|
||||
- kubernetes: server 侧通过 Kubernetes 部署的脚本
|
||||
|
||||
## [config-center](https://github.com/seata/seata/tree/develop/script/config-center)
|
||||
|
||||
> 用于存放各种配置中心的初始化脚本,执行时都会读取 `config.txt`配置文件,并写入配置中心
|
||||
|
||||
- nacos: 用于向 Nacos 中添加配置
|
||||
- zk: 用于向 Zookeeper 中添加配置,脚本依赖 Zookeeper 的相关脚本,需要手动下载;ZooKeeper相关的配置可以写在 `zk-params.txt` 中,也可以在执行的时候输入
|
||||
- apollo: 向 Apollo 中添加配置,Apollo 的地址端口等可以写在 `apollo-params.txt`,也可以在执行的时候输入
|
||||
- etcd3: 用于向 Etcd3 中添加配置
|
||||
- consul: 用于向 consul 中添加配置
|
||||
|
||||
30
server/src/main/resources/README.md
Normal file
30
server/src/main/resources/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Script Description
|
||||
|
||||
## [client](https://github.com/seata/seata/tree/develop/script/client)
|
||||
|
||||
> Store configuration and SQL for client side
|
||||
|
||||
- at: Script of create table `undo_log` for AT mode.
|
||||
- conf: Configuration which client need.
|
||||
- saga: Script of create table in SAGA mode
|
||||
- spring: Configuration for Spring Boot
|
||||
|
||||
## [server](https://github.com/seata/seata/tree/develop/script/server)
|
||||
|
||||
> Store SQL and deploy script for server side
|
||||
|
||||
- db: Create table script for server when store mode is `db`
|
||||
- docker-compose: Script for deploy server by docker-compose
|
||||
- helm: Script for deploy server by Helm
|
||||
- kubernetes: Script for deploy server by Kubernetes
|
||||
|
||||
## [config-center](https://github.com/seata/seata/tree/develop/script/config-center)
|
||||
|
||||
> Store initialize script for configuration center, will use `config.txt` as configuration when initial
|
||||
|
||||
- nacos: Initialize script for Nacos
|
||||
- zk: Initialize script for ZooKeeper, the script need related script in Zookeeper, you need download yourself. You can modify `zk-params.txt` to change the ZooKeeper server configuration, or input when execute also
|
||||
- apollo: Initialize script for Apollo. You can modify `apollo-params.txt` to change the Apollo server configuration, or input when execute also
|
||||
- etcd3: Initialize script for Etcd3
|
||||
- consul: Initialize script for consul
|
||||
|
||||
65
server/src/main/resources/file.conf
Normal file
65
server/src/main/resources/file.conf
Normal file
@@ -0,0 +1,65 @@
|
||||
## transaction log store, only used in seata-server
|
||||
store {
|
||||
## store mode: file、db、redis
|
||||
mode = "file"
|
||||
## rsa decryption public key
|
||||
publicKey = ""
|
||||
## file store property
|
||||
file {
|
||||
## store location dir
|
||||
dir = "sessionStore"
|
||||
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
|
||||
maxBranchSessionSize = 16384
|
||||
# globe session size , if exceeded throws exceptions
|
||||
maxGlobalSessionSize = 512
|
||||
# file buffer size , if exceeded allocate new buffer
|
||||
fileWriteBufferCacheSize = 16384
|
||||
# when recover batch read size
|
||||
sessionReloadReadSize = 100
|
||||
# async, sync
|
||||
flushDiskMode = async
|
||||
}
|
||||
|
||||
## database store property
|
||||
db {
|
||||
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
|
||||
datasource = "druid"
|
||||
## mysql/oracle/postgresql/h2/oceanbase etc.
|
||||
dbType = "mysql"
|
||||
driverClassName = "com.mysql.jdbc.Driver"
|
||||
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
|
||||
url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
|
||||
user = "mysql"
|
||||
password = "mysql"
|
||||
minConn = 5
|
||||
maxConn = 100
|
||||
globalTable = "global_table"
|
||||
branchTable = "branch_table"
|
||||
lockTable = "lock_table"
|
||||
queryLimit = 100
|
||||
maxWait = 5000
|
||||
}
|
||||
|
||||
## redis store property
|
||||
redis {
|
||||
## redis mode: single、sentinel
|
||||
mode = "single"
|
||||
## single mode property
|
||||
single {
|
||||
host = "127.0.0.1"
|
||||
port = "6379"
|
||||
}
|
||||
## sentinel mode property
|
||||
sentinel {
|
||||
masterName = ""
|
||||
## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
|
||||
sentinelHosts = ""
|
||||
}
|
||||
password = ""
|
||||
database = "0"
|
||||
minConn = 1
|
||||
maxConn = 10
|
||||
maxTotal = 100
|
||||
queryLimit = 100
|
||||
}
|
||||
}
|
||||
104
server/src/main/resources/file.conf.example
Normal file
104
server/src/main/resources/file.conf.example
Normal file
@@ -0,0 +1,104 @@
|
||||
transport {
|
||||
# tcp, unix-domain-socket
|
||||
type = "TCP"
|
||||
#NIO, NATIVE
|
||||
server = "NIO"
|
||||
#enable heartbeat
|
||||
heartbeat = true
|
||||
# the client batch send request enable
|
||||
enableClientBatchSendRequest = false
|
||||
#thread factory for netty
|
||||
threadFactory {
|
||||
bossThreadPrefix = "NettyBoss"
|
||||
workerThreadPrefix = "NettyServerNIOWorker"
|
||||
serverExecutorThreadPrefix = "NettyServerBizHandler"
|
||||
shareBossWorker = false
|
||||
clientSelectorThreadPrefix = "NettyClientSelector"
|
||||
clientSelectorThreadSize = 1
|
||||
clientWorkerThreadPrefix = "NettyClientWorkerThread"
|
||||
# netty boss thread size
|
||||
bossThreadSize = 1
|
||||
#auto default pin or 8
|
||||
workerThreadSize = "default"
|
||||
}
|
||||
shutdown {
|
||||
# when destroy server, wait seconds
|
||||
wait = 3
|
||||
}
|
||||
serialization = "seata"
|
||||
compressor = "none"
|
||||
}
|
||||
|
||||
## transaction log store, only used in server side
|
||||
store {
|
||||
## store mode: file、db
|
||||
mode = "file"
|
||||
## file store property
|
||||
file {
|
||||
## store location dir
|
||||
dir = "sessionStore"
|
||||
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
|
||||
maxBranchSessionSize = 16384
|
||||
# globe session size , if exceeded throws exceptions
|
||||
maxGlobalSessionSize = 512
|
||||
# file buffer size , if exceeded allocate new buffer
|
||||
fileWriteBufferCacheSize = 16384
|
||||
# when recover batch read size
|
||||
sessionReloadReadSize = 100
|
||||
# async, sync
|
||||
flushDiskMode = async
|
||||
}
|
||||
|
||||
## database store property
|
||||
db {
|
||||
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
|
||||
datasource = "druid"
|
||||
## mysql/oracle/postgresql/h2/oceanbase etc.
|
||||
dbType = "mysql"
|
||||
driverClassName = "com.mysql.jdbc.Driver"
|
||||
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
|
||||
url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"
|
||||
user = "mysql"
|
||||
password = "mysql"
|
||||
minConn = 5
|
||||
maxConn = 30
|
||||
globalTable = "global_table"
|
||||
branchTable = "branch_table"
|
||||
lockTable = "lock_table"
|
||||
queryLimit = 100
|
||||
}
|
||||
}
|
||||
## server configuration, only used in server side
|
||||
server {
|
||||
recovery {
|
||||
#schedule committing retry period in milliseconds
|
||||
committingRetryPeriod = 1000
|
||||
#schedule asyn committing retry period in milliseconds
|
||||
asynCommittingRetryPeriod = 1000
|
||||
#schedule rollbacking retry period in milliseconds
|
||||
rollbackingRetryPeriod = 1000
|
||||
#schedule timeout retry period in milliseconds
|
||||
timeoutRetryPeriod = 1000
|
||||
}
|
||||
undo {
|
||||
logSaveDays = 7
|
||||
#schedule delete expired undo_log in milliseconds
|
||||
logDeletePeriod = 86400000
|
||||
}
|
||||
#check auth
|
||||
enableCheckAuth = true
|
||||
#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
|
||||
maxCommitRetryTimeout = "-1"
|
||||
maxRollbackRetryTimeout = "-1"
|
||||
rollbackRetryTimeoutUnlockEnable = false
|
||||
retryDeadThreshold = 130000
|
||||
}
|
||||
|
||||
## metrics configuration, only used in server side
|
||||
metrics {
|
||||
enabled = false
|
||||
registryType = "compact"
|
||||
# multi exporters use comma divided
|
||||
exporterList = "prometheus"
|
||||
exporterPrometheusPort = 9898
|
||||
}
|
||||
58
server/src/main/resources/logback.xml
Normal file
58
server/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<!-- Context listeners -->
|
||||
<contextListener class="io.seata.server.logging.listener.SystemPropertyLoggerContextListener"/>
|
||||
|
||||
<!-- Copied from spring-boot.jar -->
|
||||
<conversionRule conversionWord="clr" converterClass="io.seata.server.logging.logback.ColorConverter"/>
|
||||
<conversionRule conversionWord="wex" converterClass="io.seata.server.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||
<conversionRule conversionWord="wEx" converterClass="io.seata.server.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||
|
||||
<!-- common properties -->
|
||||
<property name="APPLICATION_NAME" value="seata-server"/>
|
||||
|
||||
|
||||
<!-- console-appender -->
|
||||
<include resource="logback/console-appender.xml"/>
|
||||
|
||||
<!-- file-appender -->
|
||||
<include resource="logback/file-appender.xml"/>
|
||||
|
||||
<!-- logstash-appender: off by default -->
|
||||
<!--<include resource="logback/logstash-appender.xml"/>-->
|
||||
|
||||
<!-- kafka-appender: off by default -->
|
||||
<!--<include resource="logback/kafka-appender.xml"/>-->
|
||||
|
||||
<root level="INFO">
|
||||
<!-- console-appender -->
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
|
||||
<!-- file-appender -->
|
||||
<appender-ref ref="FILE_ALL"/>
|
||||
<appender-ref ref="FILE_WARN"/>
|
||||
<appender-ref ref="FILE_ERROR"/>
|
||||
|
||||
<!-- logstash-appender: off by default -->
|
||||
<!--<appender-ref ref="LOGSTASH"/>-->
|
||||
|
||||
<!-- kafka-appender: off by default -->
|
||||
<!--<appender-ref ref="KAFKA"/>-->
|
||||
</root>
|
||||
</configuration>
|
||||
12
server/src/main/resources/logback/console-appender.xml
Normal file
12
server/src/main/resources/logback/console-appender.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<!-- console-appender properties -->
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%25.25t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"/>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
</included>
|
||||
67
server/src/main/resources/logback/file-appender.xml
Normal file
67
server/src/main/resources/logback/file-appender.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<!-- file-appender properties -->
|
||||
<property name="LOG_HOME" value="${user.home}/logs/seata"/>
|
||||
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p --- [%30.30t] %-40.40logger{39} : %m%n%wEx"/>
|
||||
|
||||
<!--ALL-->
|
||||
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/${APPLICATION_NAME:-}.${PORT}.all.log</file>
|
||||
<append>true</append>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/history/${APPLICATION_NAME:-}.${PORT}.all.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>2GB</maxFileSize>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
<totalSizeCap>7GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<Pattern>${FILE_LOG_PATTERN}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--WARN-->
|
||||
<appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<file>${LOG_HOME}/${APPLICATION_NAME:-}.${PORT}.warn.log</file>
|
||||
<append>true</append>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/history/${APPLICATION_NAME:-}.${PORT}.warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>2GB</maxFileSize>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
<totalSizeCap>7GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<Pattern>${FILE_LOG_PATTERN}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--ERROR-->
|
||||
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
<file>${LOG_HOME}/${APPLICATION_NAME:-}.${PORT}.error.log</file>
|
||||
<append>true</append>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/history/${APPLICATION_NAME:-}.${PORT}.error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>2GB</maxFileSize>
|
||||
<MaxHistory>7</MaxHistory>
|
||||
<totalSizeCap>7GB</totalSizeCap>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<Pattern>${FILE_LOG_PATTERN}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
</included>
|
||||
32
server/src/main/resources/logback/kafka-appender.xml
Normal file
32
server/src/main/resources/logback/kafka-appender.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<!-- kafka-appender properties -->
|
||||
<property name="KAFKA_BOOTSTRAP_SERVERS" value="localhost:9092"/>
|
||||
<property name="KAFKA_TOPIC" value="logback_to_logstash"/>
|
||||
|
||||
<appender name="KAFKA" class="com.github.danielwegener.logback.kafka.KafkaAppender">
|
||||
<encoder>
|
||||
<!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|%p|seata-server|${PORT:-0}|%t|%logger|%X{X-TX-XID:-}|%X{X-TX-BRANCH-ID:-}|%m|%wex</pattern>-->
|
||||
<pattern>{
|
||||
"@timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
|
||||
"level":"%p",
|
||||
"app_name":"${APPLICATION_NAME:-}",
|
||||
"PORT": ${PORT:-0},
|
||||
"thread_name": "%t",
|
||||
"logger_name": "%logger",
|
||||
"X-TX-XID": "%X{X-TX-XID:-}",
|
||||
"X-TX-BRANCH-ID": "%X{X-TX-BRANCH-ID:-}",
|
||||
"message": "%m",
|
||||
"stack_trace": "%wex"
|
||||
}
|
||||
</pattern>
|
||||
</encoder>
|
||||
<topic>${KAFKA_TOPIC}</topic>
|
||||
<keyingStrategy class="com.github.danielwegener.logback.kafka.keying.NoKeyKeyingStrategy"/>
|
||||
<deliveryStrategy class="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy"/>
|
||||
<producerConfig>bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS}</producerConfig>
|
||||
<producerConfig>acks=0</producerConfig>
|
||||
<producerConfig>linger.ms=1000</producerConfig>
|
||||
<producerConfig>max.block.ms=0</producerConfig>
|
||||
</appender>
|
||||
</included>
|
||||
28
server/src/main/resources/logback/logstash-appender.xml
Normal file
28
server/src/main/resources/logback/logstash-appender.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<!-- logstash-appender properties -->
|
||||
<property name="LOGSTASH_DESTINATION" value="localhost:4560"/>
|
||||
|
||||
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
|
||||
<!-- the TCP address of the logstash -->
|
||||
<destination>${LOGSTASH_DESTINATION}</destination>
|
||||
|
||||
<!--<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">-->
|
||||
<encoder charset="UTF-8" class="io.seata.server.logging.logback.appender.EnhancedLogstashEncoder">
|
||||
<!-- the global custom fields -->
|
||||
<customFields>
|
||||
{
|
||||
"app_name": "${APPLICATION_NAME:-}"
|
||||
}
|
||||
</customFields>
|
||||
|
||||
<!-- exclude the provider of data `@version` -->
|
||||
<excludeProvider>net.logstash.logback.composite.LogstashVersionJsonProvider</excludeProvider>
|
||||
<!-- exclude providers not currently needed, reduce some performance loss. -->
|
||||
<excludeProvider>net.logstash.logback.composite.loggingevent.JsonMessageJsonProvider</excludeProvider>
|
||||
<excludeProvider>net.logstash.logback.composite.loggingevent.TagsJsonProvider</excludeProvider>
|
||||
<excludeProvider>net.logstash.logback.composite.loggingevent.LogstashMarkersJsonProvider</excludeProvider>
|
||||
<excludeProvider>net.logstash.logback.composite.loggingevent.ArgumentsJsonProvider</excludeProvider>
|
||||
</encoder>
|
||||
</appender>
|
||||
</included>
|
||||
96
server/src/main/resources/registry.conf
Normal file
96
server/src/main/resources/registry.conf
Normal file
@@ -0,0 +1,96 @@
|
||||
registry {
|
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
application = "seata-server"
|
||||
serverAddr = "127.0.0.1:8848"
|
||||
group = "SEATA_GROUP"
|
||||
namespace = ""
|
||||
cluster = "default"
|
||||
username = ""
|
||||
password = ""
|
||||
}
|
||||
eureka {
|
||||
serviceUrl = "http://localhost:8761/eureka"
|
||||
application = "default"
|
||||
weight = "1"
|
||||
}
|
||||
redis {
|
||||
serverAddr = "localhost:6379"
|
||||
db = 0
|
||||
password = ""
|
||||
cluster = "default"
|
||||
timeout = 0
|
||||
}
|
||||
zk {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
username = ""
|
||||
password = ""
|
||||
}
|
||||
consul {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
aclToken = ""
|
||||
}
|
||||
etcd3 {
|
||||
cluster = "default"
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
sofa {
|
||||
serverAddr = "127.0.0.1:9603"
|
||||
application = "default"
|
||||
region = "DEFAULT_ZONE"
|
||||
datacenter = "DefaultDataCenter"
|
||||
cluster = "default"
|
||||
group = "SEATA_GROUP"
|
||||
addressWaitTime = "3000"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
|
||||
config {
|
||||
# file、nacos 、apollo、zk、consul、etcd3
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "127.0.0.1:8848"
|
||||
namespace = ""
|
||||
group = "SEATA_GROUP"
|
||||
username = ""
|
||||
password = ""
|
||||
dataId = "seataServer.properties"
|
||||
}
|
||||
consul {
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
aclToken = ""
|
||||
}
|
||||
apollo {
|
||||
appId = "seata-server"
|
||||
## apolloConfigService will cover apolloMeta
|
||||
apolloMeta = "http://192.168.1.204:8801"
|
||||
apolloConfigService = "http://192.168.1.204:8080"
|
||||
namespace = "application"
|
||||
apolloAccesskeySecret = ""
|
||||
cluster = "seata"
|
||||
}
|
||||
zk {
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
username = ""
|
||||
password = ""
|
||||
nodePath = "/seata/seata.properties"
|
||||
}
|
||||
etcd3 {
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
53
server/src/test/java/ServerTest.java
Normal file
53
server/src/test/java/ServerTest.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import io.seata.common.XID;
|
||||
import io.seata.common.util.NetUtil;
|
||||
import io.seata.core.rpc.netty.NettyRemotingServer;
|
||||
import io.seata.server.UUIDGenerator;
|
||||
import io.seata.server.coordinator.DefaultCoordinator;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The type Server test.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class ServerTest {
|
||||
|
||||
private static final ThreadPoolExecutor workingThreads = new ThreadPoolExecutor(100, 500, 500, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue(20000), new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
|
||||
|
||||
/**
|
||||
* The entry point of application.
|
||||
*
|
||||
* @param args the input arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
NettyRemotingServer nettyServer = new NettyRemotingServer(workingThreads);
|
||||
nettyServer.setHandler(new DefaultCoordinator(nettyServer));
|
||||
UUIDGenerator.init(1L);
|
||||
XID.setIpAddress(NetUtil.getLocalIp());
|
||||
XID.setPort(nettyServer.getListenPort());
|
||||
nettyServer.init();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
212
server/src/test/java/WriteStoreMultithreadTest.java
Normal file
212
server/src/test/java/WriteStoreMultithreadTest.java
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.session.SessionManager;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
import io.seata.server.storage.file.store.FileTransactionStoreManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* The interface Session storable.
|
||||
*
|
||||
* @author lizhao
|
||||
*/
|
||||
public class WriteStoreMultithreadTest {
|
||||
private static String vgroup = "vgroupMock";
|
||||
private static String appname = "appnameMock";
|
||||
private static String instname = "seataMock";
|
||||
private static int per_thread_trx_num = 65535;
|
||||
private static int threadNum = 16;
|
||||
private static CountDownLatch countDownLatch = new CountDownLatch(threadNum);
|
||||
public static void main(String[] args) throws Exception {
|
||||
TransactionStoreManager transactionStoreManager = new FileTransactionStoreManager(
|
||||
"data",
|
||||
new SessionManager() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranchSession(GlobalSession globalSession, BranchSession session)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBranchSessionStatus(BranchSession session, BranchStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranchSession(GlobalSession globalSession, BranchSession session)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GlobalSession> allSessions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> findGlobalSessions(SessionCondition condition) {
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
int begin = 10000;
|
||||
int num = 1000;
|
||||
for (int i = begin; i < begin + num; i++) {
|
||||
BranchSession branchSession1 = new BranchSession();
|
||||
branchSession1.setTransactionId(i);
|
||||
branchSession1.setBranchId(begin + num + (i - begin) * 2);
|
||||
branchSession1.setResourceId("mockDbkeY1");
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setTransactionId(i);
|
||||
branchSession2.setBranchId(begin + num + (i - begin) * 2 + 1);
|
||||
branchSession2.setResourceId("mockDbkeY2");
|
||||
|
||||
GlobalSession globalSession = new GlobalSession(appname, vgroup, instname, 60000);
|
||||
try {
|
||||
globalSession.add(branchSession1);
|
||||
globalSession.add(branchSession2);
|
||||
globalSessions.add(globalSession);
|
||||
} catch (Exception exx) {}
|
||||
}
|
||||
return globalSessions;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChange(GlobalSession globalSession, GlobalStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSession,
|
||||
BranchStatus status) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddBranch(GlobalSession globalSession, BranchSession branchSession)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveBranch(GlobalSession globalSession, BranchSession branchSession)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
});
|
||||
long beginWriteMills = System.currentTimeMillis();
|
||||
for (int i = 0; i < threadNum; i++) {
|
||||
final int threadNo = i;
|
||||
Thread thread = new Thread(() -> {write(transactionStoreManager, threadNo);});
|
||||
thread.start();
|
||||
}
|
||||
countDownLatch.await();
|
||||
long endWriteMills = System.currentTimeMillis();
|
||||
System.out.println("thread nums:" + threadNum + ", per_thread_trx_num:" + per_thread_trx_num +" ,cost" + (endWriteMills-beginWriteMills));
|
||||
}
|
||||
|
||||
private static void write(TransactionStoreManager transactionStoreManager, int threadNo) {
|
||||
int trx_begin = threadNo * per_thread_trx_num;
|
||||
for (int i = trx_begin; i < trx_begin + per_thread_trx_num; i++) {
|
||||
GlobalSession globalSession = new GlobalSession(appname, vgroup, instname, 60000);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.GLOBAL_ADD, globalSession);
|
||||
|
||||
BranchSession branchSession1 = new BranchSession();
|
||||
branchSession1.setTransactionId(globalSession.getTransactionId());
|
||||
branchSession1.setBranchId(trx_begin + per_thread_trx_num + (i - trx_begin) * 2);
|
||||
branchSession1.setResourceId("mockDbkeY1");
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_ADD, branchSession1);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_UPDATE, branchSession1);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_REMOVE, branchSession1);
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setTransactionId(globalSession.getTransactionId());
|
||||
branchSession2.setBranchId(trx_begin + (i - trx_begin) + i * 2 + 1);
|
||||
branchSession2.setResourceId("mockDbkeY2");
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_ADD, branchSession2);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_UPDATE, branchSession2);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.BRANCH_REMOVE, branchSession2);
|
||||
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.GLOBAL_UPDATE, globalSession);
|
||||
transactionStoreManager.writeSession(TransactionStoreManager.LogOperation.GLOBAL_REMOVE, globalSession);
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
273
server/src/test/java/WriteStoreTest.java
Normal file
273
server/src/test/java/WriteStoreTest.java
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionCondition;
|
||||
import io.seata.server.session.SessionManager;
|
||||
import io.seata.server.storage.file.ReloadableStore;
|
||||
import io.seata.server.store.SessionStorable;
|
||||
import io.seata.server.store.TransactionStoreManager;
|
||||
import io.seata.server.store.TransactionStoreManager.LogOperation;
|
||||
import io.seata.server.storage.file.TransactionWriteStore;
|
||||
import io.seata.server.storage.file.store.FileTransactionStoreManager;
|
||||
|
||||
|
||||
/**
|
||||
* The type Write store test.
|
||||
*
|
||||
* @author slievrly
|
||||
* write cost:87281,read cost:158922 65535*5 1000 per open init 1024 write cost:86454,read
|
||||
* cost:160541 65535*5 2000 per open init 1024 write cost:82953,read cost:157736 65535*5 2000 per open init
|
||||
* 65535*5*9 write cost:115079,read cost:163664 65535*5 2000 per open init 65535*5*9 schedule flush 10||2s
|
||||
*/
|
||||
public class WriteStoreTest {
|
||||
private static String vgroup = "vgroupMock";
|
||||
private static String appname = "appnameMock";
|
||||
private static String instname = "seataMocK";
|
||||
private static int trx_num = 65535 * 5;
|
||||
private static int trx_begin = 0;
|
||||
|
||||
/**
|
||||
* The entry point of application.
|
||||
*
|
||||
* @param args the input arguments
|
||||
* @throws InterruptedException the interrupted exception
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public static void main(String[] args) throws InterruptedException, IOException {
|
||||
TransactionStoreManager transactionStoreManager = new FileTransactionStoreManager(
|
||||
"~/Documents/test/data",
|
||||
new SessionManager() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalSession(GlobalSession session) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalSession findGlobalSession(String xid, boolean withBranchSessions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGlobalSessionStatus(GlobalSession session, GlobalStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalSession(GlobalSession session) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBranchSession(GlobalSession globalSession, BranchSession session)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBranchSessionStatus(BranchSession session, BranchStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBranchSession(GlobalSession globalSession, BranchSession session)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GlobalSession> allSessions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GlobalSession> findGlobalSessions(SessionCondition condition) {
|
||||
List<GlobalSession> globalSessions = new ArrayList<>();
|
||||
int begin = 10000;
|
||||
int num = 1000;
|
||||
for (int i = begin; i < begin + num; i++) {
|
||||
BranchSession branchSession1 = new BranchSession();
|
||||
branchSession1.setTransactionId(i);
|
||||
branchSession1.setBranchId(begin + num + (i - begin) * 2);
|
||||
branchSession1.setResourceId("mockDbkeY1");
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setTransactionId(i);
|
||||
branchSession2.setBranchId(begin + num + (i - begin) * 2 + 1);
|
||||
branchSession2.setResourceId("mockDbkeY2");
|
||||
|
||||
GlobalSession globalSession = new GlobalSession(appname, vgroup, instname, 60000);
|
||||
try {
|
||||
globalSession.add(branchSession1);
|
||||
globalSession.add(branchSession2);
|
||||
globalSessions.add(globalSession);
|
||||
} catch (Exception exx) {}
|
||||
}
|
||||
return globalSessions;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable<T> lockCallable)
|
||||
throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChange(GlobalSession globalSession, GlobalStatus status)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSession,
|
||||
BranchStatus status) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddBranch(GlobalSession globalSession, BranchSession branchSession)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveBranch(GlobalSession globalSession, BranchSession branchSession)
|
||||
throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(GlobalSession globalSession) throws TransactionException {
|
||||
|
||||
}
|
||||
});
|
||||
long beginWriteMills = System.currentTimeMillis();
|
||||
write(transactionStoreManager);
|
||||
long endWriteMills = System.currentTimeMillis();
|
||||
Thread.sleep(10 * 1000);
|
||||
long beginReadMills = System.currentTimeMillis();
|
||||
Map<SessionStorable, LogOperation> resultMap = readAll(transactionStoreManager);
|
||||
long endReadMills = System.currentTimeMillis();
|
||||
if ((resultMap.size() % (65535)) % 3000 == 0) {
|
||||
System.out.print("check success");
|
||||
} else {
|
||||
System.out.print("check failed");
|
||||
}
|
||||
System.out.print(
|
||||
"write cost:" + (endWriteMills - beginWriteMills) + ",read cost:" + (endReadMills - beginReadMills));
|
||||
|
||||
}
|
||||
|
||||
private static void write(TransactionStoreManager transactionStoreManager) {
|
||||
for (int i = trx_begin; i < trx_begin + trx_num; i++) {
|
||||
GlobalSession globalSession = new GlobalSession(appname, vgroup, instname, 60000);
|
||||
transactionStoreManager.writeSession(LogOperation.GLOBAL_ADD, globalSession);
|
||||
|
||||
BranchSession branchSession1 = new BranchSession();
|
||||
branchSession1.setTransactionId(globalSession.getTransactionId());
|
||||
branchSession1.setBranchId(trx_begin + trx_num + (i - trx_begin) * 2);
|
||||
branchSession1.setResourceId("mockDbkeY1");
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_ADD, branchSession1);
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_UPDATE, branchSession1);
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_REMOVE, branchSession1);
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setTransactionId(globalSession.getTransactionId());
|
||||
branchSession2.setBranchId(trx_begin + (i - trx_begin) + i * 2 + 1);
|
||||
branchSession2.setResourceId("mockDbkeY2");
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_ADD, branchSession2);
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_UPDATE, branchSession2);
|
||||
transactionStoreManager.writeSession(LogOperation.BRANCH_REMOVE, branchSession2);
|
||||
|
||||
transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, globalSession);
|
||||
transactionStoreManager.writeSession(LogOperation.GLOBAL_REMOVE, globalSession);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<SessionStorable, LogOperation> readAll(TransactionStoreManager transactionStoreManager) {
|
||||
Map<SessionStorable, LogOperation> resultMap = new HashMap<>(65535 * 5 * 9);
|
||||
while (((ReloadableStore)transactionStoreManager).hasRemaining(true)) {
|
||||
List<TransactionWriteStore> transactionWriteStores = ((ReloadableStore)transactionStoreManager).readWriteStore(2000,
|
||||
true);
|
||||
if (transactionWriteStores != null) {
|
||||
for (TransactionWriteStore transactionWriteStore : transactionWriteStores) {
|
||||
printLog(transactionWriteStore);
|
||||
resultMap.put(transactionWriteStore.getSessionRequest(), transactionWriteStore.getOperate());
|
||||
}
|
||||
}
|
||||
}
|
||||
while (((ReloadableStore)transactionStoreManager).hasRemaining(false)) {
|
||||
List<TransactionWriteStore> transactionWriteStores = ((ReloadableStore)transactionStoreManager).readWriteStore(2000,
|
||||
false);
|
||||
if (transactionWriteStores != null) {
|
||||
for (TransactionWriteStore transactionWriteStore : transactionWriteStores) {
|
||||
printLog(transactionWriteStore);
|
||||
resultMap.put(transactionWriteStore.getSessionRequest(), transactionWriteStore.getOperate());
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
private static void printLog(TransactionWriteStore transactionWriteStore) {
|
||||
if (transactionWriteStore.getSessionRequest() instanceof GlobalSession) {
|
||||
GlobalSession globalSession = (GlobalSession)transactionWriteStore.getSessionRequest();
|
||||
System.out.print(
|
||||
"xid:" + globalSession.getTransactionId() + "," + globalSession.getApplicationId() + "," + globalSession
|
||||
.getTransactionServiceGroup() + "," + globalSession.getTransactionName() + "," + globalSession
|
||||
.getTimeout());
|
||||
} else {
|
||||
BranchSession branchSession = (BranchSession)transactionWriteStore.getSessionRequest();
|
||||
System.out.print(
|
||||
"xid:" + branchSession.getTransactionId() + ",branchId:" + branchSession.getBranchId() + ","
|
||||
+ branchSession.getResourceId());
|
||||
}
|
||||
System.out.println(",op:" + transactionWriteStore.getOperate().name());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The type parameter parser test
|
||||
*
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public class ParameterParserTest {
|
||||
private static ParameterParser parameterParser = null;
|
||||
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
@BeforeEach
|
||||
private void init() {
|
||||
String[] args = new String[] {"-h", "127.0.0.1", "-p", "8088", "-m", "file","-e","test"};
|
||||
parameterParser = new ParameterParser(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty mode.
|
||||
*/
|
||||
@Test
|
||||
public void testEmptyMode() {
|
||||
String[] args = new String[] {"-h", "127.0.0.1", "-p", "8088"};
|
||||
parameterParser.cleanUp();
|
||||
parameterParser = new ParameterParser(args);
|
||||
//always set store.mode=file in test/resource/file.conf, if not will cause SessionStoreTest's case fail.
|
||||
Assertions.assertEquals("file", parameterParser.getStoreMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* test get host
|
||||
*/
|
||||
@Test
|
||||
public void testGetHost() {
|
||||
Assertions.assertEquals("127.0.0.1", parameterParser.getHost());
|
||||
}
|
||||
|
||||
/**
|
||||
* test get port
|
||||
*/
|
||||
@Test
|
||||
public void testGetPort() {
|
||||
Assertions.assertEquals(8088, parameterParser.getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* test get store mode
|
||||
*/
|
||||
@Test
|
||||
public void testGetStoreMode() {
|
||||
Assertions.assertEquals("file", parameterParser.getStoreMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* test get seata env
|
||||
*/
|
||||
@Test
|
||||
public void testGetSeataEnv() {
|
||||
Assertions.assertEquals("test", parameterParser.getSeataEnv());
|
||||
}
|
||||
|
||||
/**
|
||||
* clean up
|
||||
*/
|
||||
@AfterEach
|
||||
public void cleanUp() {
|
||||
if (null != parameterParser) {
|
||||
parameterParser.cleanUp();
|
||||
parameterParser = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The type Uuid generator overflow test.
|
||||
*/
|
||||
public class UUIDGeneratorOverflowTest {
|
||||
private static final int UUID_GENERATE_COUNT = 5;
|
||||
private static final Long SERVER_NODE_ID = 1023L;
|
||||
|
||||
/**
|
||||
* Test generate uuid.
|
||||
*/
|
||||
@Test
|
||||
public void testGenerateUUID() {
|
||||
UUIDGenerator.init(SERVER_NODE_ID);
|
||||
for (int i = 0; i < UUID_GENERATE_COUNT; i++) {
|
||||
System.out.println("[UUID " + i + "] is: " + UUIDGenerator.generateUUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalBeginResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalCommitResponse;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.GlobalRollbackResponse;
|
||||
import io.seata.core.rpc.RpcContext;
|
||||
import io.seata.metrics.Measurement;
|
||||
import io.seata.server.metrics.MetricsManager;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
|
||||
|
||||
import static io.seata.server.coordinator.DefaultCoordinatorTest.MockServerMessageSender;
|
||||
|
||||
/**
|
||||
* Test Metrics
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public class DefaultCoordinatorMetricsTest {
|
||||
@Test
|
||||
public void test() throws IOException, TransactionException, InterruptedException {
|
||||
SessionHolder.init(null);
|
||||
DefaultCoordinator coordinator = new DefaultCoordinator(new MockServerMessageSender());
|
||||
coordinator.init();
|
||||
try {
|
||||
MetricsManager.get().init();
|
||||
|
||||
//start a transaction
|
||||
GlobalBeginRequest request = new GlobalBeginRequest();
|
||||
request.setTransactionName("test_transaction");
|
||||
GlobalBeginResponse response = new GlobalBeginResponse();
|
||||
coordinator.doGlobalBegin(request, response, new RpcContext());
|
||||
|
||||
Map<String, Measurement> measurements = new HashMap<>();
|
||||
MetricsManager.get().getRegistry().measure().forEach(
|
||||
measurement -> measurements.put(measurement.getId().toString(), measurement));
|
||||
|
||||
Assertions.assertEquals(1, measurements.size());
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=active)")
|
||||
.getValue(), 0);
|
||||
|
||||
//commit this transaction
|
||||
GlobalCommitRequest commitRequest = new GlobalCommitRequest();
|
||||
commitRequest.setXid(response.getXid());
|
||||
coordinator.doGlobalCommit(commitRequest, new GlobalCommitResponse(), new RpcContext());
|
||||
|
||||
//we need sleep for a short while because default canBeCommittedAsync() is true
|
||||
Thread.sleep(200);
|
||||
|
||||
measurements.clear();
|
||||
MetricsManager.get().getRegistry().measure().forEach(
|
||||
measurement -> measurements.put(measurement.getId().toString(), measurement));
|
||||
Assertions.assertEquals(9, measurements.size());
|
||||
Assertions.assertEquals(0, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=active)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=count,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=total,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=timer,role=tc,statistic=count,status=committed)")
|
||||
.getValue(), 0);
|
||||
|
||||
//start another new transaction
|
||||
request = new GlobalBeginRequest();
|
||||
request.setTransactionName("test_transaction_2");
|
||||
response = new GlobalBeginResponse();
|
||||
coordinator.doGlobalBegin(request, response, new RpcContext());
|
||||
|
||||
//rollback this transaction
|
||||
GlobalRollbackRequest rollbackRequest = new GlobalRollbackRequest();
|
||||
rollbackRequest.setXid(response.getXid());
|
||||
coordinator.doGlobalRollback(rollbackRequest, new GlobalRollbackResponse(), new RpcContext());
|
||||
|
||||
Thread.sleep(200);
|
||||
|
||||
measurements.clear();
|
||||
MetricsManager.get().getRegistry().measure().forEach(
|
||||
measurement -> measurements.put(measurement.getId().toString(), measurement));
|
||||
Assertions.assertEquals(17, measurements.size());
|
||||
Assertions.assertEquals(0, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=active)")
|
||||
.getValue(), 0);
|
||||
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(0, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=count,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(0, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=total,status=committed)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(0, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=timer,role=tc,statistic=count,status=committed)")
|
||||
.getValue(), 0);
|
||||
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=counter,role=tc,status=rollbacked)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=count,status=rollbacked)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=summary,role=tc,statistic=total,status=rollbacked)")
|
||||
.getValue(), 0);
|
||||
Assertions.assertEquals(1, measurements.get(
|
||||
"seata.transaction(applicationId=null,group=null,meter=timer,role=tc,statistic=count,status=rollbacked)")
|
||||
.getValue(), 0);
|
||||
} finally {
|
||||
coordinator.destroy();
|
||||
SessionHolder.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.seata.common.XID;
|
||||
import io.seata.common.util.DurationUtil;
|
||||
import io.seata.common.util.NetUtil;
|
||||
import io.seata.common.util.ReflectionUtil;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.protocol.RpcMessage;
|
||||
import io.seata.core.protocol.transaction.BranchCommitRequest;
|
||||
import io.seata.core.protocol.transaction.BranchCommitResponse;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackRequest;
|
||||
import io.seata.core.protocol.transaction.BranchRollbackResponse;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.core.rpc.processor.RemotingProcessor;
|
||||
import io.seata.core.store.StoreMode;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static io.seata.server.session.SessionHolder.DEFAULT_SESSION_STORE_FILE_DIR;
|
||||
|
||||
/**
|
||||
* The type DefaultCoordinator test.
|
||||
*
|
||||
* @author leizhiyuan
|
||||
*/
|
||||
public class DefaultCoordinatorTest {
|
||||
private static DefaultCoordinator defaultCoordinator;
|
||||
|
||||
private static final String applicationId = "demo-child-app";
|
||||
|
||||
private static final String txServiceGroup = "my_test_tx_group";
|
||||
|
||||
private static final String txName = "tx-1";
|
||||
|
||||
private static final int timeout = 3000;
|
||||
|
||||
private static final String resourceId = "tb_1";
|
||||
|
||||
private static final String clientId = "c_1";
|
||||
|
||||
private static final String lockKeys_1 = "tb_1:11";
|
||||
|
||||
private static final String lockKeys_2 = "tb_1:12";
|
||||
|
||||
private static final String applicationData = "{\"data\":\"test\"}";
|
||||
|
||||
private static DefaultCore core;
|
||||
|
||||
private static final Configuration CONFIG = ConfigurationFactory.getInstance();
|
||||
|
||||
private static String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR,
|
||||
DEFAULT_SESSION_STORE_FILE_DIR);
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
XID.setIpAddress(NetUtil.getLocalIp());
|
||||
RemotingServer remotingServer = new MockServerMessageSender();
|
||||
defaultCoordinator = new DefaultCoordinator(remotingServer);
|
||||
core = new DefaultCore(remotingServer);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void tearUp() throws IOException {
|
||||
deleteAndCreateDataFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void branchCommit() throws TransactionException {
|
||||
BranchStatus result = null;
|
||||
String xid = null;
|
||||
GlobalSession globalSession = null;
|
||||
try {
|
||||
xid = core.begin(applicationId, txServiceGroup, txName, timeout);
|
||||
Long branchId = core.branchRegister(BranchType.AT, resourceId, clientId, xid, applicationData, lockKeys_1);
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
result = core.branchCommit(globalSession, globalSession.getBranch(branchId));
|
||||
} catch (TransactionException e) {
|
||||
Assertions.fail(e.getMessage());
|
||||
}
|
||||
Assertions.assertEquals(result, BranchStatus.PhaseTwo_Committed);
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertNotNull(globalSession);
|
||||
globalSession.end();
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidAndBranchIdProviderForRollback")
|
||||
public void branchRollback(String xid, Long branchId) {
|
||||
BranchStatus result = null;
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
try {
|
||||
result = core.branchRollback(globalSession, globalSession.getBranch(branchId));
|
||||
} catch (TransactionException e) {
|
||||
Assertions.fail(e.getMessage());
|
||||
}
|
||||
Assertions.assertEquals(result, BranchStatus.PhaseTwo_Rollbacked);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test_handleRetryRollbacking() throws TransactionException, InterruptedException {
|
||||
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, 10);
|
||||
Long branchId = core.branchRegister(BranchType.AT, "abcd", clientId, xid, applicationData, lockKeys_2);
|
||||
|
||||
Assertions.assertNotNull(branchId);
|
||||
|
||||
Thread.sleep(100);
|
||||
defaultCoordinator.timeoutCheck();
|
||||
defaultCoordinator.handleRetryRollbacking();
|
||||
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertNull(globalSession);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_handleRetryRollbackingTimeOut() throws TransactionException, InterruptedException, NoSuchFieldException, IllegalAccessException {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, 10);
|
||||
Long branchId = core.branchRegister(BranchType.AT, "abcd", clientId, xid, applicationData, lockKeys_2);
|
||||
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertNotNull(globalSession);
|
||||
Assertions.assertNotNull(globalSession.getBranchSessions());
|
||||
Assertions.assertNotNull(branchId);
|
||||
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "MAX_ROLLBACK_RETRY_TIMEOUT", Duration.ofMillis(10));
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE", false);
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
defaultCoordinator.timeoutCheck();
|
||||
defaultCoordinator.handleRetryRollbacking();
|
||||
int lockSize = globalSession.getBranchSessions().get(0).getLockHolder().size();
|
||||
try {
|
||||
Assertions.assertTrue(lockSize > 0);
|
||||
} finally {
|
||||
globalSession.closeAndClean();
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "MAX_ROLLBACK_RETRY_TIMEOUT",
|
||||
ConfigurationFactory.getInstance().getDuration(ConfigurationKeys.MAX_ROLLBACK_RETRY_TIMEOUT, DurationUtil.DEFAULT_DURATION, 100));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_handleRetryRollbackingTimeOut_unlock() throws TransactionException, InterruptedException,
|
||||
NoSuchFieldException, IllegalAccessException {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, 10);
|
||||
Long branchId = core.branchRegister(BranchType.AT, "abcd", clientId, xid, applicationData, lockKeys_2);
|
||||
|
||||
GlobalSession globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertNotNull(globalSession);
|
||||
Assertions.assertNotNull(globalSession.getBranchSessions());
|
||||
Assertions.assertNotNull(branchId);
|
||||
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "MAX_ROLLBACK_RETRY_TIMEOUT", Duration.ofMillis(10));
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE", true);
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
|
||||
defaultCoordinator.timeoutCheck();
|
||||
defaultCoordinator.handleRetryRollbacking();
|
||||
|
||||
int lockSize = globalSession.getBranchSessions().get(0).getLockHolder().size();
|
||||
try {
|
||||
Assertions.assertTrue(lockSize == 0);
|
||||
} finally {
|
||||
globalSession.closeAndClean();
|
||||
ReflectionUtil.modifyStaticFinalField(defaultCoordinator.getClass(), "MAX_ROLLBACK_RETRY_TIMEOUT",
|
||||
ConfigurationFactory.getInstance().getDuration(ConfigurationKeys.MAX_ROLLBACK_RETRY_TIMEOUT, DurationUtil.DEFAULT_DURATION, 100));
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() throws Exception {
|
||||
|
||||
Collection<GlobalSession> globalSessions = SessionHolder.getRootSessionManager().allSessions();
|
||||
Collection<GlobalSession> asyncGlobalSessions = SessionHolder.getAsyncCommittingSessionManager().allSessions();
|
||||
for (GlobalSession asyncGlobalSession : asyncGlobalSessions) {
|
||||
asyncGlobalSession.closeAndClean();
|
||||
}
|
||||
for (GlobalSession globalSession : globalSessions) {
|
||||
globalSession.closeAndClean();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws IOException {
|
||||
SessionHolder.destroy();
|
||||
deleteDataFile();
|
||||
}
|
||||
|
||||
private static void deleteDataFile() throws IOException {
|
||||
File directory = new File(sessionStorePath);
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null && files.length > 0) {
|
||||
for (File file : files) {
|
||||
Files.delete(Paths.get(file.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteAndCreateDataFile() throws IOException {
|
||||
SessionHolder.destroy();
|
||||
deleteDataFile();
|
||||
SessionHolder.init(StoreMode.FILE.name());
|
||||
}
|
||||
|
||||
static Stream<Arguments> xidAndBranchIdProviderForRollback() throws Exception {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, timeout);
|
||||
Long branchId = core.branchRegister(BranchType.AT, resourceId, clientId, xid, applicationData, lockKeys_2);
|
||||
return Stream.of(
|
||||
Arguments.of(xid, branchId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public static class MockServerMessageSender implements RemotingServer {
|
||||
|
||||
@Override
|
||||
public Object sendSyncRequest(String resourceId, String clientId, Object message) throws TimeoutException {
|
||||
if (message instanceof BranchCommitRequest) {
|
||||
final BranchCommitResponse branchCommitResponse = new BranchCommitResponse();
|
||||
branchCommitResponse.setBranchStatus(BranchStatus.PhaseTwo_Committed);
|
||||
return branchCommitResponse;
|
||||
} else if (message instanceof BranchRollbackRequest) {
|
||||
final BranchRollbackResponse branchRollbackResponse = new BranchRollbackResponse();
|
||||
branchRollbackResponse.setBranchStatus(BranchStatus.PhaseTwo_Rollbacked);
|
||||
return branchRollbackResponse;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object sendSyncRequest(Channel clientChannel, Object message) throws TimeoutException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAsyncRequest(Channel channel, Object msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAsyncResponse(RpcMessage request, Channel channel, Object msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerProcessor(int messageType, RemotingProcessor processor, ExecutorService executor) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.coordinator;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchStatus;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import io.seata.server.session.GlobalSession;
|
||||
import io.seata.server.session.SessionHelper;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
/**
|
||||
* The type Default core test.
|
||||
*
|
||||
* @author zhimo.xiao @gmail.com
|
||||
* @since 2019 /1/23
|
||||
*/
|
||||
public class DefaultCoreTest {
|
||||
|
||||
private static DefaultCore core;
|
||||
private static RemotingServer remotingServer;
|
||||
|
||||
private static final String applicationId = "demo-child-app";
|
||||
|
||||
private static final String txServiceGroup = "my_test_tx_group";
|
||||
|
||||
private static final String txName = "tx-1";
|
||||
|
||||
private static final int timeout = 3000;
|
||||
|
||||
private static final String resourceId = "tb_1";
|
||||
|
||||
private static final String clientId = "c_1";
|
||||
|
||||
private static final String lockKeys_1 = "tb_11:11";
|
||||
|
||||
private static final String lockKeys_2 = "tb_12:12";
|
||||
|
||||
private static final String applicationData = "{\"data\":\"test\"}";
|
||||
|
||||
private GlobalSession globalSession;
|
||||
|
||||
/**
|
||||
* Init session manager.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void initSessionManager() throws Exception {
|
||||
SessionHolder.init(null);
|
||||
remotingServer = new DefaultCoordinatorTest.MockServerMessageSender();
|
||||
core = new DefaultCore(remotingServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy session manager.
|
||||
*/
|
||||
@AfterAll
|
||||
public static void destroySessionManager() {
|
||||
SessionHolder.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean.
|
||||
*
|
||||
* @throws TransactionException the transaction exception
|
||||
*/
|
||||
@AfterEach
|
||||
public void clean() throws TransactionException {
|
||||
if (globalSession != null) {
|
||||
globalSession.end();
|
||||
globalSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch register test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void branchRegisterTest(String xid) throws Exception {
|
||||
core.branchRegister(BranchType.AT, resourceId, clientId, xid, "abc", lockKeys_1);
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertEquals(globalSession.getSortedBranches().size(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch report test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @param branchId the branch id
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidAndBranchIdProvider")
|
||||
public void branchReportTest(String xid, Long branchId) throws Exception {
|
||||
core.branchReport(BranchType.AT, xid, branchId, BranchStatus.PhaseOne_Done, applicationData);
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = globalSession.getBranch(branchId);
|
||||
Assertions.assertEquals(branchSession.getStatus(), BranchStatus.PhaseOne_Done);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin test.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@Test
|
||||
public void beginTest() throws Exception {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, timeout);
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
Assertions.assertNotNull(globalSession);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void commitTest(String xid) throws Exception {
|
||||
GlobalStatus globalStatus = core.commit(xid);
|
||||
Assertions.assertNotEquals(globalStatus, GlobalStatus.Begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global commit test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalCommitCommitTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.XA, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.XA,
|
||||
new MockCore(BranchStatus.PhaseTwo_Committed, BranchStatus.PhaseOne_Done));
|
||||
core.doGlobalCommit(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.Committed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global commit test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalCommitUnretryableTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.AT, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.AT,
|
||||
new MockCore(BranchStatus.PhaseTwo_CommitFailed_Unretryable, BranchStatus.PhaseOne_Done));
|
||||
core.doGlobalCommit(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.Begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global commit test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalCommitExpTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.XA, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.XA,
|
||||
new MockCore(BranchStatus.PhaseOne_Timeout, BranchStatus.PhaseOne_Done));
|
||||
core.doGlobalCommit(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.CommitRetrying);
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll back test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void rollBackTest(String xid) throws Exception {
|
||||
GlobalStatus globalStatus = core.rollback(xid);
|
||||
Assertions.assertEquals(globalStatus, GlobalStatus.Rollbacked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global roll back test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalRollBackRollbackedTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.AT, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.AT,
|
||||
new MockCore(BranchStatus.PhaseTwo_Committed, BranchStatus.PhaseTwo_Rollbacked));
|
||||
core.doGlobalRollback(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.Rollbacked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global roll back test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalRollBackUnretryableTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.AT, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.AT, new MockCore(BranchStatus.PhaseTwo_Committed,
|
||||
BranchStatus.PhaseTwo_RollbackFailed_Unretryable));
|
||||
core.doGlobalRollback(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.RollbackFailed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do global roll back test.
|
||||
*
|
||||
* @param xid the xid
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("xidProvider")
|
||||
public void doGlobalRollBackRetryableExpTest(String xid) throws Exception {
|
||||
globalSession = SessionHolder.findGlobalSession(xid);
|
||||
BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, BranchType.AT, resourceId,
|
||||
applicationData, "t1:1", clientId);
|
||||
globalSession.addBranch(branchSession);
|
||||
globalSession.changeBranchStatus(branchSession, BranchStatus.PhaseOne_Done);
|
||||
core.mockCore(BranchType.AT, new MockCore(BranchStatus.PhaseTwo_Committed,
|
||||
BranchStatus.PhaseTwo_RollbackFailed_Retryable));
|
||||
core.doGlobalRollback(globalSession, false);
|
||||
Assertions.assertEquals(globalSession.getStatus(), GlobalStatus.RollbackRetrying);
|
||||
}
|
||||
|
||||
/**
|
||||
* Xid provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
static Stream<Arguments> xidProvider() throws Exception {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, timeout);
|
||||
Assertions.assertNotNull(xid);
|
||||
return Stream.of(
|
||||
Arguments.of(xid)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Xid and branch id provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
static Stream<Arguments> xidAndBranchIdProvider() throws Exception {
|
||||
String xid = core.begin(applicationId, txServiceGroup, txName, timeout);
|
||||
Long branchId = core.branchRegister(BranchType.AT, resourceId, clientId, xid, null, lockKeys_2);
|
||||
Assertions.assertNotNull(xid);
|
||||
Assertions.assertTrue(branchId != 0);
|
||||
return Stream.of(
|
||||
Arguments.of(xid, branchId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release session manager.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@AfterEach
|
||||
public void releaseSessionManager() throws Exception {
|
||||
Collection<GlobalSession> globalSessions = SessionHolder.getRootSessionManager().allSessions();
|
||||
Collection<GlobalSession> asyncGlobalSessions = SessionHolder.getAsyncCommittingSessionManager().allSessions();
|
||||
for (GlobalSession asyncGlobalSession : asyncGlobalSessions) {
|
||||
asyncGlobalSession.closeAndClean();
|
||||
}
|
||||
for (GlobalSession globalSession : globalSessions) {
|
||||
globalSession.closeAndClean();
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockCore extends AbstractCore {
|
||||
|
||||
private BranchStatus commitStatus;
|
||||
private BranchStatus rollbackStatus;
|
||||
|
||||
/**
|
||||
* Instantiates a new Mock resource manager inbound.
|
||||
*
|
||||
* @param commitStatus the commit status
|
||||
* @param rollbackStatus the rollback status
|
||||
*/
|
||||
public MockCore(BranchStatus commitStatus, BranchStatus rollbackStatus) {
|
||||
super(new DefaultCoordinatorTest.MockServerMessageSender());
|
||||
this.commitStatus = commitStatus;
|
||||
this.rollbackStatus = rollbackStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
return commitStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
|
||||
return rollbackStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BranchType getHandleBranchType() {
|
||||
return BranchType.AT;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.event;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.seata.core.event.GlobalTransactionEvent;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.GlobalStatus;
|
||||
import io.seata.core.rpc.RemotingServer;
|
||||
import io.seata.server.coordinator.DefaultCoordinator;
|
||||
import io.seata.server.coordinator.DefaultCoordinatorTest;
|
||||
import io.seata.server.coordinator.DefaultCore;
|
||||
import io.seata.server.session.SessionHolder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test events come from Default Core.
|
||||
*
|
||||
* @author zhengyangyong
|
||||
*/
|
||||
public class DefaultCoreForEventBusTest {
|
||||
@Test
|
||||
public void test() throws IOException, TransactionException, InterruptedException {
|
||||
class GlobalTransactionEventSubscriber {
|
||||
private final Map<GlobalStatus, AtomicInteger> eventCounters;
|
||||
|
||||
public Map<GlobalStatus, AtomicInteger> getEventCounters() {
|
||||
return eventCounters;
|
||||
}
|
||||
|
||||
public GlobalTransactionEventSubscriber() {
|
||||
this.eventCounters = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void processGlobalTransactionEvent(GlobalTransactionEvent event) {
|
||||
AtomicInteger counter = eventCounters.computeIfAbsent(event.getStatus(),
|
||||
status -> new AtomicInteger(0));
|
||||
counter.addAndGet(1);
|
||||
}
|
||||
}
|
||||
SessionHolder.init(null);
|
||||
RemotingServer remotingServer = new DefaultCoordinatorTest.MockServerMessageSender();
|
||||
DefaultCoordinator coordinator = new DefaultCoordinator(remotingServer);
|
||||
coordinator.init();
|
||||
try {
|
||||
DefaultCore core = new DefaultCore(remotingServer);
|
||||
|
||||
GlobalTransactionEventSubscriber subscriber = new GlobalTransactionEventSubscriber();
|
||||
// avoid transactional interference from other unit tests
|
||||
Thread.sleep(1500);
|
||||
EventBusManager.get().register(subscriber);
|
||||
|
||||
//start a transaction
|
||||
String xid = core.begin("test_app_id", "default_group", "test_tran_name", 30000);
|
||||
|
||||
Assertions.assertEquals(1, subscriber.getEventCounters().get(GlobalStatus.Begin).get());
|
||||
|
||||
//commit this transaction
|
||||
core.commit(xid);
|
||||
|
||||
//we need sleep for a short while because default canBeCommittedAsync() is true
|
||||
Thread.sleep(1000);
|
||||
|
||||
//check
|
||||
Assertions.assertEquals(1, subscriber.getEventCounters().get(GlobalStatus.AsyncCommitting).get());
|
||||
Assertions.assertEquals(1, subscriber.getEventCounters().get(GlobalStatus.Committed).get());
|
||||
|
||||
//start another new transaction
|
||||
xid = core.begin("test_app_id", "default_group", "test_tran_name2", 30000);
|
||||
|
||||
Assertions.assertEquals(2, subscriber.getEventCounters().get(GlobalStatus.Begin).get());
|
||||
|
||||
core.rollback(xid);
|
||||
|
||||
//check
|
||||
Assertions.assertEquals(1, subscriber.getEventCounters().get(GlobalStatus.Rollbacking).get());
|
||||
Assertions.assertEquals(1, subscriber.getEventCounters().get(GlobalStatus.Rollbacked).get());
|
||||
|
||||
//start more one new transaction for test timeout and let this transaction immediately timeout
|
||||
xid = core.begin("test_app_id", "default_group", "test_tran_name3", 0);
|
||||
|
||||
//sleep for check -> DefaultCoordinator.timeoutCheck
|
||||
Thread.sleep(1000);
|
||||
|
||||
//at lease retry once because DefaultCoordinator.timeoutCheck is 1 second
|
||||
Assertions.assertTrue(subscriber.getEventCounters().get(GlobalStatus.TimeoutRollbacking).get() >= 1);
|
||||
} finally {
|
||||
coordinator.destroy();
|
||||
SessionHolder.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
254
server/src/test/java/io/seata/server/lock/LockManagerTest.java
Normal file
254
server/src/test/java/io/seata/server/lock/LockManagerTest.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.lock;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.server.UUIDGenerator;
|
||||
import io.seata.server.lock.file.FileLockManagerForTest;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
/**
|
||||
* The type Lock manager test.
|
||||
*
|
||||
* @author tianming.xm @gmail.com
|
||||
* @since 2019 /1/23
|
||||
*/
|
||||
public class LockManagerTest {
|
||||
|
||||
/**
|
||||
* Acquire lock success.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("branchSessionProvider")
|
||||
public void acquireLock_success(BranchSession branchSession) throws Exception {
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession));
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire lock failed.
|
||||
*
|
||||
* @param branchSession1 the branch session 1
|
||||
* @param branchSession2 the branch session 2
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("branchSessionsProvider")
|
||||
public void acquireLock_failed(BranchSession branchSession1, BranchSession branchSession2) throws Exception {
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession1));
|
||||
Assertions.assertFalse(lockManager.acquireLock(branchSession2));
|
||||
}
|
||||
|
||||
/**
|
||||
* deadlock test.
|
||||
*
|
||||
* @param branchSession1 the branch session 1
|
||||
* @param branchSession2 the branch session 2
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("deadlockBranchSessionsProvider")
|
||||
public void deadlockTest(BranchSession branchSession1, BranchSession branchSession2) throws Exception {
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
try {
|
||||
CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
lockManager.acquireLock(branchSession1);
|
||||
} catch (TransactionException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
lockManager.acquireLock(branchSession2);
|
||||
} catch (TransactionException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}).start();
|
||||
// Assume execute more than 5 seconds means deadlock happened.
|
||||
Assertions.assertTrue(countDownLatch.await(5, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
lockManager.releaseLock(branchSession1);
|
||||
lockManager.releaseLock(branchSession2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure two concurrent branchSession register process with different row key list, at least one process could
|
||||
* success and only one process could success.
|
||||
*
|
||||
* @param branchSession1 the branch session 1
|
||||
* @param branchSession2 the branch session 2
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("deadlockBranchSessionsProvider")
|
||||
public void concurrentUseAbilityTest(BranchSession branchSession1, BranchSession branchSession2) throws Exception {
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
try {
|
||||
final AtomicBoolean first = new AtomicBoolean();
|
||||
final AtomicBoolean second = new AtomicBoolean();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
first.set(lockManager.acquireLock(branchSession1));
|
||||
} catch (TransactionException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
second.set(lockManager.acquireLock(branchSession2));
|
||||
} catch (TransactionException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}).start();
|
||||
// Assume execute more than 5 seconds means deadlock happened.
|
||||
if (countDownLatch.await(5, TimeUnit.SECONDS)) {
|
||||
Assertions.assertTrue(!first.get() || !second.get());
|
||||
}
|
||||
} finally {
|
||||
lockManager.releaseLock(branchSession1);
|
||||
lockManager.releaseLock(branchSession2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is lockable test.
|
||||
*
|
||||
* @param branchSession the branch session
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("branchSessionProvider")
|
||||
public void isLockableTest(BranchSession branchSession) throws Exception {
|
||||
branchSession.setLockKey("t:4");
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
Assertions.assertTrue(lockManager
|
||||
.isLockable(branchSession.getXid(), branchSession.getResourceId(), branchSession.getLockKey()));
|
||||
lockManager.acquireLock(branchSession);
|
||||
branchSession.setTransactionId(UUIDGenerator.generateUUID());
|
||||
Assertions.assertFalse(lockManager
|
||||
.isLockable(branchSession.getXid(), branchSession.getResourceId(), branchSession.getLockKey()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("duplicatePkBranchSessionsProvider")
|
||||
public void duplicatePkBranchSessionHolderTest(BranchSession branchSession1, BranchSession branchSession2) throws Exception {
|
||||
LockManager lockManager = new FileLockManagerForTest();
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession1));
|
||||
Assertions.assertEquals(4, branchSession1.getLockHolder().values().stream().map(Set::size).count());
|
||||
Assertions.assertTrue(lockManager.releaseLock(branchSession1));
|
||||
Assertions.assertEquals(0, branchSession1.getLockHolder().values().stream().map(Set::size).count());
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession2));
|
||||
Assertions.assertEquals(4, branchSession2.getLockHolder().values().stream().map(Set::size).count());
|
||||
Assertions.assertTrue(lockManager.releaseLock(branchSession2));
|
||||
Assertions.assertEquals(0, branchSession2.getLockHolder().values().stream().map(Set::size).count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch session provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> branchSessionProvider() {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setTransactionId(UUIDGenerator.generateUUID());
|
||||
branchSession.setBranchId(0L);
|
||||
branchSession.setClientId("c1");
|
||||
branchSession.setResourceGroupId("my_test_tx_group");
|
||||
branchSession.setResourceId("tb_1");
|
||||
branchSession.setLockKey("t:0");
|
||||
branchSession.setBranchType(BranchType.AT);
|
||||
branchSession.setApplicationData("{\"data\":\"test\"}");
|
||||
branchSession.setBranchType(BranchType.AT);
|
||||
return Stream.of(
|
||||
Arguments.of(branchSession));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base branch sessions provider object [ ] [ ]. Could assign resource and lock keys.
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> baseBranchSessionsProvider(String resource, String lockKey1, String lockKey2) {
|
||||
BranchSession branchSession1 = new BranchSession();
|
||||
branchSession1.setTransactionId(UUIDGenerator.generateUUID());
|
||||
branchSession1.setBranchId(1L);
|
||||
branchSession1.setClientId("c1");
|
||||
branchSession1.setResourceGroupId("my_test_tx_group");
|
||||
branchSession1.setResourceId(resource);
|
||||
branchSession1.setLockKey(lockKey1);
|
||||
branchSession1.setBranchType(BranchType.AT);
|
||||
branchSession1.setApplicationData("{\"data\":\"test\"}");
|
||||
branchSession1.setBranchType(BranchType.AT);
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setTransactionId(UUIDGenerator.generateUUID());
|
||||
branchSession2.setBranchId(2L);
|
||||
branchSession2.setClientId("c1");
|
||||
branchSession2.setResourceGroupId("my_test_tx_group");
|
||||
branchSession2.setResourceId(resource);
|
||||
branchSession2.setLockKey(lockKey2);
|
||||
branchSession2.setBranchType(BranchType.AT);
|
||||
branchSession2.setApplicationData("{\"data\":\"test\"}");
|
||||
branchSession2.setBranchType(BranchType.AT);
|
||||
return Stream.of(
|
||||
Arguments.of(branchSession1, branchSession2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch sessions provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> branchSessionsProvider() {
|
||||
return baseBranchSessionsProvider("tb_1", "t:1,2", "t:1,2");
|
||||
}
|
||||
|
||||
static Stream<Arguments> deadlockBranchSessionsProvider() {
|
||||
return baseBranchSessionsProvider("tb_2", "t:1,2,3,4,5", "t:5,4,3,2,1");
|
||||
}
|
||||
|
||||
static Stream<Arguments> duplicatePkBranchSessionsProvider() {
|
||||
return baseBranchSessionsProvider("tb_2", "t:1,2;t1:1;t2:2", "t:1,2;t1:1;t2:2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.server.lock.db;
|
||||
|
||||
import io.seata.common.util.IOUtil;
|
||||
import io.seata.core.exception.TransactionException;
|
||||
import io.seata.core.lock.Locker;
|
||||
import io.seata.server.storage.db.lock.LockStoreDataBaseDAO;
|
||||
import io.seata.server.storage.file.lock.FileLockManager;
|
||||
import io.seata.server.lock.LockManager;
|
||||
import io.seata.server.storage.db.lock.DataBaseLocker;
|
||||
import io.seata.server.session.BranchSession;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class DataBaseLockManagerImplTest {
|
||||
|
||||
static LockManager lockManager = null;
|
||||
|
||||
static BasicDataSource dataSource = null;
|
||||
|
||||
static LockStoreDataBaseDAO dataBaseLockStoreDAO = null;
|
||||
|
||||
@BeforeAll
|
||||
public static void start(){
|
||||
dataSource = new BasicDataSource();
|
||||
dataSource.setDriverClassName("org.h2.Driver");
|
||||
dataSource.setUrl("jdbc:h2:./db_store/db_lock");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
|
||||
dataBaseLockStoreDAO = new LockStoreDataBaseDAO(dataSource);
|
||||
dataBaseLockStoreDAO.setDbType("h2");
|
||||
dataBaseLockStoreDAO.setLockTable("lock_table");
|
||||
|
||||
lockManager = new DBLockManagerForTest(dataBaseLockStoreDAO);
|
||||
|
||||
prepareTable(dataSource);
|
||||
}
|
||||
|
||||
private static void prepareTable(BasicDataSource dataSource) {
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
Statement s = conn.createStatement();
|
||||
try {
|
||||
s.execute("drop table lock_table");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
s.execute("CREATE TABLE lock_table ( xid varchar(96), transaction_id long , branch_id long, resource_id varchar(32) ,table_name varchar(32) ,pk varchar(32) , row_key varchar(128) primary key not null, gmt_create TIMESTAMP(6) ,gmt_modified TIMESTAMP(6)) ");
|
||||
System.out.println("create table lock_table success.");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acquireLock() throws TransactionException, SQLException {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid("abc-123:786756");
|
||||
branchSession.setTransactionId(123543465);
|
||||
branchSession.setBranchId(5756678);
|
||||
branchSession.setResourceId("abcss");
|
||||
branchSession.setLockKey("t1:13,14;t2:11,12");
|
||||
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession));
|
||||
|
||||
String sql = "select * from lock_table where xid = 'abc-123:786756'" ;
|
||||
String sql2 = "select count(*) from lock_table where xid = 'abc-123:786756' " +
|
||||
"and row_key in ('abcss^^^t1^^^13', 'abcss^^^t1^^^14', 'abcss^^^t2^^^11', 'abcss^^^t2^^^12')" ;
|
||||
String delSql = "delete from lock_table where xid = 'abc-123:786756'" ;
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
|
||||
ResultSet rs = conn.createStatement().executeQuery(sql);
|
||||
if(rs.next()){
|
||||
Assertions.assertTrue(true);
|
||||
}else {
|
||||
Assertions.assertTrue(false);
|
||||
}
|
||||
rs.close();
|
||||
|
||||
rs = conn.createStatement().executeQuery(sql2);
|
||||
if(rs.next()){
|
||||
Assertions.assertTrue(true);
|
||||
Assertions.assertEquals(4, rs.getInt(1));
|
||||
}else {
|
||||
Assertions.assertTrue(false);
|
||||
}
|
||||
rs.close();
|
||||
|
||||
conn.createStatement().execute(delSql);
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void re_acquireLock() throws TransactionException, SQLException {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid("abc-123:65867978");
|
||||
branchSession.setTransactionId(123543465);
|
||||
branchSession.setBranchId(5756678);
|
||||
branchSession.setResourceId("abcss");
|
||||
branchSession.setLockKey("t1:53,54;t2:21,32");
|
||||
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession));
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setXid("abc-123:65867978");
|
||||
branchSession2.setTransactionId(123543465);
|
||||
branchSession2.setBranchId(575667854);
|
||||
branchSession2.setResourceId("abcss");
|
||||
branchSession2.setLockKey("t1:13,14;t2:21,45");
|
||||
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession2));
|
||||
|
||||
BranchSession branchSession3 = new BranchSession();
|
||||
branchSession3.setXid("abc-123:5678789");
|
||||
branchSession3.setTransactionId(334123);
|
||||
branchSession3.setBranchId(5657);
|
||||
branchSession3.setResourceId("abcss");
|
||||
branchSession3.setLockKey("t1:53,14;t2:21,45");
|
||||
|
||||
Assertions.assertTrue(!lockManager.acquireLock(branchSession3));
|
||||
|
||||
String delSql = "delete from lock_table where xid in( 'abc-123:65867978' , 'abc-123:65867978' , 'abc-123:5678789' )" ;
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
|
||||
conn.createStatement().execute(delSql);
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unLock() throws TransactionException, SQLException {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid("abc-123:56867");
|
||||
branchSession.setTransactionId(1236765);
|
||||
branchSession.setBranchId(204565);
|
||||
branchSession.setResourceId("abcss");
|
||||
branchSession.setLockKey("t1:3,4;t2:4,5");
|
||||
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession));
|
||||
|
||||
String sql = "select * from lock_table where xid = 'abc-123:56867'" ;
|
||||
String sql2 = "select count(*) from lock_table where xid = 'abc-123:56867' " +
|
||||
"and row_key in ('abcss^^^t1^^^3', 'abcss^^^t1^^^4', 'abcss^^^t2^^^4', 'abcss^^^t2^^^5')" ;
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
|
||||
ResultSet rs = conn.createStatement().executeQuery(sql);
|
||||
if(rs.next()){
|
||||
Assertions.assertTrue(true);
|
||||
}else {
|
||||
Assertions.assertTrue(false);
|
||||
}
|
||||
rs.close();
|
||||
|
||||
rs = conn.createStatement().executeQuery(sql2);
|
||||
if(rs.next()){
|
||||
Assertions.assertTrue(true);
|
||||
Assertions.assertEquals(4, rs.getInt(1));
|
||||
}else {
|
||||
Assertions.assertTrue(false);
|
||||
}
|
||||
rs.close();
|
||||
|
||||
//un lock
|
||||
Assertions.assertTrue(lockManager.releaseLock(branchSession));
|
||||
|
||||
rs = conn.createStatement().executeQuery(sql);
|
||||
if(rs.next()){
|
||||
Assertions.assertTrue(false);
|
||||
}else {
|
||||
Assertions.assertTrue(true);
|
||||
}
|
||||
rs.close();
|
||||
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLockable() throws TransactionException, SQLException {
|
||||
BranchSession branchSession = new BranchSession();
|
||||
branchSession.setXid("abc-123:56877898");
|
||||
branchSession.setTransactionId(245686786);
|
||||
branchSession.setBranchId(467568);
|
||||
branchSession.setResourceId("abcss");
|
||||
branchSession.setLockKey("t1:8,7;t2:1,2");
|
||||
|
||||
Assertions.assertTrue(lockManager.acquireLock(branchSession));
|
||||
|
||||
BranchSession branchSession2 = new BranchSession();
|
||||
branchSession2.setXid("abc-123:56877898");
|
||||
branchSession2.setTransactionId(245686786);
|
||||
branchSession2.setBranchId(1242354576);
|
||||
branchSession2.setResourceId("abcss");
|
||||
branchSession2.setLockKey("t1:8");
|
||||
|
||||
Assertions.assertTrue(lockManager.isLockable(branchSession2.getXid(), branchSession2.getResourceId(), branchSession2.getLockKey()));
|
||||
|
||||
BranchSession branchSession3 = new BranchSession();
|
||||
branchSession3.setXid("abc-123:4575614354");
|
||||
branchSession3.setTransactionId(65867867);
|
||||
branchSession3.setBranchId(123123);
|
||||
branchSession3.setResourceId("abcss");
|
||||
branchSession3.setLockKey("t2:1,12");
|
||||
|
||||
Assertions.assertTrue(!lockManager.isLockable(branchSession3.getXid(), branchSession3.getResourceId(), branchSession3.getLockKey()));
|
||||
|
||||
String delSql = "delete from lock_table where xid in( 'abc-123:56877898' , 'abc-123:56877898' , 'abc-123:4575614354' )" ;
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
|
||||
conn.createStatement().execute(delSql);
|
||||
} finally {
|
||||
IOUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DBLockManagerForTest extends FileLockManager {
|
||||
|
||||
protected LockStoreDataBaseDAO lockStore;
|
||||
|
||||
public DBLockManagerForTest(LockStoreDataBaseDAO db){
|
||||
lockStore = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locker getLocker(BranchSession branchSession) {
|
||||
DataBaseLocker locker = new DataBaseLocker();
|
||||
locker.setLockStore(lockStore);
|
||||
return locker;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user