From 851e0f900dab971f5685cf70ce157fc7e96a7aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90li?= <15040126243@163.com> Date: Tue, 31 May 2022 19:08:41 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E5=8D=87=E7=BA=A7=20Seata=201.5.X=20?= =?UTF-8?q?=E6=BA=90=E7=A0=81=E9=9B=86=E6=88=90=E6=9C=8D=E5=8A=A1=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/dev/application-common.yml | 2 +- config/dev/seata-server.properties | 72 +- docker/deploy.sh | 6 +- docker/docker-compose.yml | 12 +- docker/seata/registry.conf | 28 - ruoyi-common/ruoyi-common-alibaba-bom/pom.xml | 2 +- ruoyi-common/ruoyi-common-seata/pom.xml | 31 +- ...acheDubboTransactionPropagationFilter.java | 107 +++ ruoyi-visual/pom.xml | 1 + ruoyi-visual/ruoyi-seata-server/Dockerfile | 18 + ruoyi-visual/ruoyi-seata-server/pom.xml | 189 +++++ .../server/AbstractTCInboundHandler.java | 345 ++++++++ .../java/io/seata/server/ParameterParser.java | 214 +++++ .../seata/server/SeataServerApplication.java | 32 + .../src/main/java/io/seata/server/Server.java | 95 +++ .../server/ServerApplicationListener.java | 136 +++ .../java/io/seata/server/ServerRunner.java | 82 ++ .../java/io/seata/server/UUIDGenerator.java | 51 ++ .../server/auth/AbstractCheckAuthHandler.java | 53 ++ .../server/auth/DefaultCheckAuthHandler.java | 37 + .../controller/BranchSessionController.java | 35 + .../controller/GlobalLockController.java | 51 ++ .../controller/GlobalSessionController.java | 50 ++ .../impl/db/BranchSessionDBServiceImpl.java | 102 +++ .../impl/db/GlobalLockDBServiceImpl.java | 146 ++++ .../impl/db/GlobalSessionDBServiceImpl.java | 161 ++++ .../file/BranchSessionFileServiceImpl.java | 39 + .../impl/file/GlobalLockFileServiceImpl.java | 171 ++++ .../file/GlobalSessionFileServiceImpl.java | 102 +++ .../redis/BranchSessionRedisServiceImpl.java | 64 ++ .../redis/GlobalLockRedisServiceImpl.java | 119 +++ .../redis/GlobalSessionRedisServiceImpl.java | 108 +++ .../server/console/param/GlobalLockParam.java | 114 +++ .../console/param/GlobalSessionParam.java | 102 +++ .../console/service/BranchSessionService.java | 34 + .../console/service/GlobalLockService.java | 37 + .../console/service/GlobalSessionService.java | 35 + .../server/console/vo/BranchSessionVO.java | 241 ++++++ .../seata/server/console/vo/GlobalLockVO.java | 196 +++++ .../server/console/vo/GlobalSessionVO.java | 217 +++++ .../server/controller/HealthController.java | 43 + .../server/coordinator/AbstractCore.java | 244 ++++++ .../io/seata/server/coordinator/Core.java | 59 ++ .../coordinator/DefaultCoordinator.java | 624 ++++++++++++++ .../seata/server/coordinator/DefaultCore.java | 392 +++++++++ .../TransactionCoordinatorInbound.java | 29 + .../TransactionCoordinatorOutbound.java | 54 ++ .../io/seata/server/env/ContainerHelper.java | 103 +++ .../java/io/seata/server/env/PortHelper.java | 134 +++ .../seata/server/event/EventBusManager.java | 34 + .../server/lock/AbstractLockManager.java | 197 +++++ .../io/seata/server/lock/LockManager.java | 106 +++ .../server/lock/LockerManagerFactory.java | 73 ++ .../distributed/DistributedLockerFactory.java | 69 ++ .../SystemPropertyLoggerContextListener.java | 75 ++ ...ndedWhitespaceThrowableProxyConverter.java | 36 + .../appender/EnhancedLogstashEncoder.java | 45 + .../server/metrics/MeterIdConstants.java | 108 +++ .../seata/server/metrics/MetricsManager.java | 63 ++ .../server/metrics/MetricsPublisher.java | 98 +++ .../server/metrics/MetricsSubscriber.java | 217 +++++ .../session/AbstractSessionManager.java | 199 +++++ .../seata/server/session/BranchSession.java | 469 ++++++++++ .../server/session/BranchSessionHandler.java | 39 + .../seata/server/session/GlobalSession.java | 776 +++++++++++++++++ .../server/session/GlobalSessionHandler.java | 36 + .../io/seata/server/session/Lockable.java | 42 + .../io/seata/server/session/Reloadable.java | 29 + .../server/session/SessionCondition.java | 144 ++++ .../seata/server/session/SessionHelper.java | 294 +++++++ .../seata/server/session/SessionHolder.java | 424 ++++++++++ .../server/session/SessionLifecycle.java | 89 ++ .../session/SessionLifecycleListener.java | 98 +++ .../seata/server/session/SessionManager.java | 125 +++ .../server/storage/SessionConverter.java | 210 +++++ .../db/lock/DataBaseDistributedLocker.java | 254 ++++++ .../storage/db/lock/DataBaseLockManager.java | 77 ++ .../storage/db/lock/DataBaseLocker.java | 143 ++++ .../storage/db/lock/LockStoreDataBaseDAO.java | 436 ++++++++++ .../db/session/DataBaseSessionManager.java | 193 +++++ .../DataBaseTransactionStoreManager.java | 255 ++++++ .../storage/db/store/LogStoreDataBaseDAO.java | 605 +++++++++++++ .../server/storage/file/FlushDiskMode.java | 43 + .../server/storage/file/ReloadableStore.java | 45 + .../storage/file/TransactionWriteStore.java | 129 +++ .../storage/file/lock/FileLockManager.java | 60 ++ .../server/storage/file/lock/FileLocker.java | 221 +++++ .../file/session/FileSessionManager.java | 379 +++++++++ .../store/FileTransactionStoreManager.java | 653 ++++++++++++++ .../storage/redis/JedisPooledFactory.java | 132 +++ .../redis/lock/RedisDistributedLocker.java | 88 ++ .../storage/redis/lock/RedisLockManager.java | 66 ++ .../storage/redis/lock/RedisLocker.java | 395 +++++++++ .../redis/session/RedisSessionManager.java | 194 +++++ .../store/RedisTransactionStoreManager.java | 799 ++++++++++++++++++ .../AbstractTransactionStoreManager.java | 54 ++ .../server/store/DbcpDataSourceProvider.java | 56 ++ .../server/store/DruidDataSourceProvider.java | 55 ++ .../store/HikariDataSourceProvider.java | 60 ++ .../seata/server/store/SessionStorable.java | 38 + .../io/seata/server/store/StoreConfig.java | 63 ++ .../server/store/TransactionStoreManager.java | 141 ++++ .../seata/server/transaction/at/ATCore.java | 106 +++ .../server/transaction/saga/SagaCore.java | 229 +++++ .../seata/server/transaction/tcc/TccCore.java | 37 + .../seata/server/transaction/xa/XACore.java | 48 ++ ...io.seata.core.rpc.RegisterCheckAuthHandler | 1 + .../io.seata.core.store.DistributedLocker | 2 + .../io.seata.core.store.db.DataSourceProvider | 3 + .../io.seata.server.coordinator.AbstractCore | 4 + .../services/io.seata.server.lock.LockManager | 3 + .../io.seata.server.session.SessionManager | 3 + .../spring-configuration-metadata.json | 22 + .../main/resources/META-INF/spring.factories | 2 + .../src/main/resources/README-zh.md | 32 + .../src/main/resources/README.md | 33 + .../src/main/resources/application.yml | 59 ++ .../src/main/resources/banner.txt | 7 + .../src/main/resources/logback-spring.xml | 61 ++ .../resources/logback/console-appender.xml | 13 + .../main/resources/logback/file-appender.xml | 69 ++ .../main/resources/logback/kafka-appender.xml | 34 + .../resources/logback/logstash-appender.xml | 29 + .../resources/lua/redislocker/redislock.lua | 52 ++ sql/ry-seata.sql | 7 +- 125 files changed, 15829 insertions(+), 75 deletions(-) delete mode 100644 docker/seata/registry.conf create mode 100644 ruoyi-common/ruoyi-common-seata/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java create mode 100644 ruoyi-visual/ruoyi-seata-server/Dockerfile create mode 100644 ruoyi-visual/ruoyi-seata-server/pom.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/AbstractTCInboundHandler.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ParameterParser.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/SeataServerApplication.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/Server.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerApplicationListener.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerRunner.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/UUIDGenerator.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/AbstractCheckAuthHandler.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/DefaultCheckAuthHandler.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/BranchSessionController.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalLockController.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalSessionController.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/BranchSessionDBServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalLockDBServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/BranchSessionFileServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalLockFileServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalLockParam.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalSessionParam.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/BranchSessionService.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalLockService.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalSessionService.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/BranchSessionVO.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalLockVO.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalSessionVO.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/controller/HealthController.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/AbstractCore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/Core.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorInbound.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorOutbound.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/ContainerHelper.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/PortHelper.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/event/EventBusManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/AbstractLockManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockerManagerFactory.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/distributed/DistributedLockerFactory.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/listener/SystemPropertyLoggerContextListener.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/ExtendedWhitespaceThrowableProxyConverter.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/appender/EnhancedLogstashEncoder.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MeterIdConstants.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsPublisher.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsSubscriber.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/AbstractSessionManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSession.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSessionHandler.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSession.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSessionHandler.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Lockable.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Reloadable.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionCondition.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHelper.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHolder.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycle.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycleListener.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/SessionConverter.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseDistributedLocker.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLockManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLocker.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/LockStoreDataBaseDAO.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/session/DataBaseSessionManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/DataBaseTransactionStoreManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/LogStoreDataBaseDAO.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/FlushDiskMode.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/ReloadableStore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/TransactionWriteStore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/store/FileTransactionStoreManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/JedisPooledFactory.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisDistributedLocker.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLockManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLocker.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/session/RedisSessionManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/store/RedisTransactionStoreManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/AbstractTransactionStoreManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DbcpDataSourceProvider.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DruidDataSourceProvider.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/HikariDataSourceProvider.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/SessionStorable.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/StoreConfig.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/TransactionStoreManager.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/at/ATCore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/saga/SagaCore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/tcc/TccCore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/xa/XACore.java create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.rpc.RegisterCheckAuthHandler create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.db.DataSourceProvider create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.coordinator.AbstractCore create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring-configuration-metadata.json create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring.factories create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/README-zh.md create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/README.md create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/application.yml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/banner.txt create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/logback-spring.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/console-appender.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/file-appender.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/kafka-appender.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/logstash-appender.xml create mode 100644 ruoyi-visual/ruoyi-seata-server/src/main/resources/lua/redislocker/redislock.lua diff --git a/config/dev/application-common.yml b/config/dev/application-common.yml index 4e5af6b6..21c79ddb 100644 --- a/config/dev/application-common.yml +++ b/config/dev/application-common.yml @@ -289,7 +289,7 @@ seata: registry: type: nacos nacos: - application: seata-server + application: ruoyi-seata-server server-addr: ${spring.cloud.nacos.server-addr} group: ${spring.cloud.nacos.discovery.group} namespace: ${spring.profiles.active} diff --git a/config/dev/seata-server.properties b/config/dev/seata-server.properties index af555eb0..4240bae3 100644 --- a/config/dev/seata-server.properties +++ b/config/dev/seata-server.properties @@ -3,10 +3,18 @@ service.vgroupMapping.ruoyi-system-group=default service.vgroupMapping.ruoyi-resource-group=default service.vgroupMapping.ruoyi-gen-group=default service.vgroupMapping.ruoyi-job-group=default + service.enableDegrade=false service.disableGlobalTransaction=false + +#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional. store.mode=db +store.lock.mode=db +store.session.mode=db +#Used for password encryption store.publicKey= + +#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block. store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.cj.jdbc.Driver @@ -21,6 +29,8 @@ store.db.distributedLockTable=distributed_lock store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 + +#Transaction rule configuration, only for the server server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 @@ -29,8 +39,32 @@ server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false server.distributedLockExpireTime=10000 +server.xaerNotaRetryTimeout=60000 +server.session.branchAsyncQueueSize=5000 +server.session.enableBranchAsyncRemove=false + +#Transaction rule configuration, only for the client +client.rm.asyncCommitBufferLimit=10000 +client.rm.lock.retryInterval=10 +client.rm.lock.retryTimes=30 +client.rm.lock.retryPolicyBranchRollbackOnConflict=true +client.rm.reportRetryCount=5 +client.rm.tableMetaCheckEnable=true +client.rm.tableMetaCheckerInterval=60000 +client.rm.sqlParserType=druid +client.rm.reportSuccessEnable=false +client.rm.sagaBranchRegisterEnable=false +client.rm.sagaJsonParser=fastjson +client.rm.tccActionInterceptorOrder=-2147482648 +client.tm.commitRetryCount=5 +client.tm.rollbackRetryCount=5 +client.tm.defaultGlobalTransactionTimeout=60000 +client.tm.degradeCheck=false +client.tm.degradeCheckAllowTimes=10 +client.tm.degradeCheckPeriod=2000 +client.tm.interceptorOrder=-2147482648 client.undo.dataValidation=true -client.undo.logSerialization=kryo +client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 @@ -38,12 +72,40 @@ client.undo.logTable=undo_log client.undo.compress.enable=true client.undo.compress.type=zip client.undo.compress.threshold=64k + +#For TCC transaction mode +tcc.fence.logTableName=tcc_fence_log +tcc.fence.cleanPeriod=1h + +#Log rule configuration, for client and server log.exceptionRate=100 -transport.serialization=seata -transport.compressor=none + +#Metrics configuration, only for the server metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898 -tcc.fence.logTableName=tcc_fence_log -tcc.fence.cleanPeriod=1h + +#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html +#Transport configuration, for client and server +transport.type=TCP +transport.server=NIO +transport.heartbeat=true +transport.enableTmClientBatchSendRequest=false +transport.enableRmClientBatchSendRequest=true +transport.enableTcServerBatchSendResponse=false +transport.rpcRmRequestTimeout=30000 +transport.rpcTmRequestTimeout=30000 +transport.rpcTcRequestTimeout=30000 +transport.threadFactory.bossThreadPrefix=NettyBoss +transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker +transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler +transport.threadFactory.shareBossWorker=false +transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector +transport.threadFactory.clientSelectorThreadSize=1 +transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread +transport.threadFactory.bossThreadSize=1 +transport.threadFactory.workerThreadSize=default +transport.shutdown.wait=3 +transport.serialization=seata +transport.compressor=none diff --git a/docker/deploy.sh b/docker/deploy.sh index cda05be2..54bcd6e2 100644 --- a/docker/deploy.sh +++ b/docker/deploy.sh @@ -27,6 +27,7 @@ port(){ # sentinel端口 firewall-cmd --add-port=8718/tcp --permanent # seata端口 + firewall-cmd --add-port=7091/tcp --permanent firewall-cmd --add-port=8091/tcp --permanent # 重启防火墙 service firewalld restart @@ -49,11 +50,6 @@ mount(){ mkdir -p /docker/nacos/conf cp nacos/custom.properties /docker/nacos/conf/custom.properties fi - #挂载 seata 配置文件 - if test ! -f "/docker/seata/conf/registry.conf" ;then - mkdir -p /docker/seata/conf - cp seata/registry.conf /docker/seata/conf/registry.conf - fi } #启动基础模块 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2c56aee2..4842e011 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -113,24 +113,22 @@ services: ipv4_address: 172.30.0.54 seata-server: - image: seataio/seata-server:1.4.2 + image: ruoyi/ruoyi-seata-server:0.11.0 container_name: seata-server ports: + - "7091:7091" - "8091:8091" environment: TZ: Asia/Shanghai # 注意 此处ip如果是外网使用 要改为外网ip - SEATA_IP: 172.30.0.58 + # SEATA_IP: 127.0.0.1 SEATA_PORT: 8091 - STORE_MODE: db - SEATA_CONFIG_NAME: file:/root/seata-config/registry volumes: - - /docker/seata/config:/root/seata-config + - /docker/ruoyi-seata-server/logs/:/ruoyi/seata-server/logs privileged: true restart: always networks: - ruoyi_net: - ipv4_address: 172.30.0.58 + - ruoyi_net nginx-web: image: nginx:1.21.3 diff --git a/docker/seata/registry.conf b/docker/seata/registry.conf deleted file mode 100644 index 2912ddb7..00000000 --- a/docker/seata/registry.conf +++ /dev/null @@ -1,28 +0,0 @@ -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "nacos" - - nacos { - application = "seata-server" - serverAddr = "172.30.0.40:8848" - group = "DEFAULT_GROUP" - namespace = "dev" - cluster = "default" - username = "" - password = "" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "nacos" - - nacos { - serverAddr = "172.30.0.40:8848" - group = "DEFAULT_GROUP" - namespace = "dev" - username = "" - password = "" - dataId = "seata-server.properties" - } -} diff --git a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml index ee5ec20e..1c28cb19 100644 --- a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml @@ -22,7 +22,7 @@ 2.2.1 1.1.0 2.0.2 - 1.4.2 + 1.5.1 3.0.8 1.0.11 diff --git a/ruoyi-common/ruoyi-common-seata/pom.xml b/ruoyi-common/ruoyi-common-seata/pom.xml index 9638adcc..d1fa5819 100644 --- a/ruoyi-common/ruoyi-common-seata/pom.xml +++ b/ruoyi-common/ruoyi-common-seata/pom.xml @@ -17,36 +17,17 @@ + + org.apache.dubbo + dubbo + true + + com.alibaba.cloud spring-cloud-starter-alibaba-seata - - - com.google.protobuf - protobuf-java - 3.16.1 - - - - - com.esotericsoftware - kryo - 4.0.2 - - - asm - org.ow2.asm - - - - - de.javakaffee - kryo-serializers - 0.43 - - diff --git a/ruoyi-common/ruoyi-common-seata/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java b/ruoyi-common/ruoyi-common-seata/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java new file mode 100644 index 00000000..5fd05da3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-seata/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java @@ -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.integration.dubbo; + +import io.seata.common.util.StringUtils; +import io.seata.core.constants.DubboConstants; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Transaction propagation filter. + * + * @author sharajava + */ +@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100) +public class ApacheDubboTransactionPropagationFilter implements Filter { + + private static final Logger LOGGER = LoggerFactory.getLogger(ApacheDubboTransactionPropagationFilter.class); + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + String xid = RootContext.getXID(); + BranchType branchType = RootContext.getBranchType(); + + String rpcXid = getRpcXid(); + String rpcBranchType = RpcContext.getServiceContext().getAttachment(RootContext.KEY_BRANCH_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid); + } + boolean bind = false; + if (xid != null) { + RpcContext.getServiceContext().setAttachment(RootContext.KEY_XID, xid); + RpcContext.getServiceContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name()); + } else { + if (rpcXid != null) { + RootContext.bind(rpcXid); + if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) { + RootContext.bindBranchType(BranchType.TCC); + } + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind xid [{}] branchType [{}] to RootContext", rpcXid, rpcBranchType); + } + } + } + try { + return invoker.invoke(invocation); + } finally { + if (bind) { + BranchType previousBranchType = RootContext.getBranchType(); + String unbindXid = RootContext.unbind(); + if (BranchType.TCC == previousBranchType) { + RootContext.unbindBranchType(); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind xid [{}] branchType [{}] from RootContext", unbindXid, previousBranchType); + } + if (!rpcXid.equalsIgnoreCase(unbindXid)) { + LOGGER.warn("xid in change during RPC from {} to {},branchType from {} to {}", rpcXid, unbindXid, + rpcBranchType != null ? rpcBranchType : "AT", previousBranchType); + if (unbindXid != null) { + RootContext.bind(unbindXid); + LOGGER.warn("bind xid [{}] back to RootContext", unbindXid); + if (BranchType.TCC == previousBranchType) { + RootContext.bindBranchType(BranchType.TCC); + LOGGER.warn("bind branchType [{}] back to RootContext", previousBranchType); + } + } + } + } + RpcContext.getServiceContext().removeAttachment(RootContext.KEY_XID); + RpcContext.getServiceContext().removeAttachment(RootContext.KEY_BRANCH_TYPE); + RpcContext.getServerContext().removeAttachment(RootContext.KEY_XID); + RpcContext.getServerContext().removeAttachment(RootContext.KEY_BRANCH_TYPE); + } + } + + /** + * get rpc xid + * @return + */ + private String getRpcXid() { + String rpcXid = RpcContext.getServiceContext().getAttachment(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = RpcContext.getServiceContext().getAttachment(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/ruoyi-visual/pom.xml b/ruoyi-visual/pom.xml index 80eea5ba..0ea35c80 100644 --- a/ruoyi-visual/pom.xml +++ b/ruoyi-visual/pom.xml @@ -13,6 +13,7 @@ ruoyi-xxl-job-admin ruoyi-doc ruoyi-sentinel-dashboard + ruoyi-seata-server ruoyi-visual diff --git a/ruoyi-visual/ruoyi-seata-server/Dockerfile b/ruoyi-visual/ruoyi-seata-server/Dockerfile new file mode 100644 index 00000000..a0455e85 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/Dockerfile @@ -0,0 +1,18 @@ +FROM anapsix/alpine-java:8_server-jre_unlimited + +MAINTAINER Lion Li + +RUN mkdir -p /ruoyi/seata-server +RUN mkdir -p /ruoyi/seata-server/logs + +WORKDIR /ruoyi/seata-server + +ENV TZ=PRC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +EXPOSE 7091 +EXPOSE 8091 + +ADD ./target/ruoyi-seata-server.jar ./app.jar + +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"] diff --git a/ruoyi-visual/ruoyi-seata-server/pom.xml b/ruoyi-visual/ruoyi-seata-server/pom.xml new file mode 100644 index 00000000..0bc089c6 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/pom.xml @@ -0,0 +1,189 @@ + + + + + com.ruoyi + ruoyi-visual + 0.11.0 + + 4.0.0 + ruoyi-seata-server + jar + + + 1.5.1 + 1.72 + 6.5 + 0.2.0-RC2 + + + + + + io.seata + seata-bom + ${seata.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + io.seata + seata-spring-autoconfigure-server + ${seata.version} + + + + io.seata + seata-core + + + io.seata + seata-config-all + + + io.seata + seata-discovery-all + + + io.seata + seata-serializer-all + + + io.seata + seata-compressor-all + + + + io.seata + seata-metrics-all + + + + io.seata + seata-console + ${seata.version} + + + + + com.alibaba + druid + ${druid.version} + + + org.apache.commons + commons-dbcp2 + + + com.zaxxer + HikariCP + + + com.h2database + h2 + + + mysql + mysql-connector-java + + + org.postgresql + postgresql + + + + + com.beust + jcommander + ${jcommander.version} + + + + + com.google.guava + guava + + + + + redis.clients + jedis + + + + com.alibaba + fastjson + + + + + ch.qos.logback + logback-classic + + + + net.logstash.logback + logstash-logback-encoder + ${logstash-logback-encoder.version} + + + com.github.danielwegener + logback-kafka-appender + ${kafka-appender.version} + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + com.spotify + docker-maven-plugin + + + + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/AbstractTCInboundHandler.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/AbstractTCInboundHandler.java new file mode 100644 index 00000000..18a26990 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/AbstractTCInboundHandler.java @@ -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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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()); + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ParameterParser.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ParameterParser.java new file mode 100644 index 00000000..1305fa99 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ParameterParser.java @@ -0,0 +1,214 @@ +/* + * 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.Configuration; +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_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)"; + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + @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; + @Parameter(names = {"--storeMode", "-m"}, description = "log store mode : file, db, redis", 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; + @Parameter(names = {"--sessionStoreMode", "-ssm"}, description = "session log store mode : file, db, redis", + order = 6) + private String sessionStoreMode; + @Parameter(names = {"--lockStoreMode", "-lsm"}, description = "lock log store mode : file, db, redis", order = 7) + private String lockStoreMode; + + /** + * Instantiates a new Parameter parser. + * + * @param args the args + */ + public ParameterParser(String... args) { + this.init(args); + } + + private void init(String[] args) { + try { + getCommandParameters(args); + getEnvParameters(); + if (StringUtils.isNotBlank(seataEnv)) { + System.setProperty(ENV_PROPERTY_KEY, seataEnv); + } + if (StringUtils.isBlank(storeMode)) { + storeMode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE, SERVER_DEFAULT_STORE_MODE); + } + if (StringUtils.isBlank(sessionStoreMode)) { + sessionStoreMode = CONFIG.getConfig(ConfigurationKeys.STORE_SESSION_MODE, storeMode); + } + if (StringUtils.isBlank(lockStoreMode)) { + lockStoreMode = CONFIG.getConfig(ConfigurationKeys.STORE_LOCK_MODE, storeMode); + } + } catch (ParameterException e) { + printError(e); + } + + } + + private void getCommandParameters(String[] args) { + JCommander jCommander = JCommander.newBuilder().addObject(this).build(); + jCommander.parse(args); + if (help) { + jCommander.setProgramName(PROGRAM_NAME); + jCommander.usage(); + System.exit(0); + } + } + + private void getEnvParameters() { + if (StringUtils.isBlank(seataEnv)) { + seataEnv = ContainerHelper.getEnv(); + } + if (StringUtils.isBlank(host)) { + host = ContainerHelper.getHost(); + } + if (port == 0) { + port = ContainerHelper.getPort(); + } + if (serverNode == null) { + serverNode = ContainerHelper.getServerNode(); + } + if (StringUtils.isBlank(storeMode)) { + storeMode = ContainerHelper.getStoreMode(); + } + if (StringUtils.isBlank(sessionStoreMode)) { + sessionStoreMode = ContainerHelper.getSessionStoreMode(); + } + if (StringUtils.isBlank(lockStoreMode)) { + lockStoreMode = ContainerHelper.getLockStoreMode(); + } + } + + 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; + } + + /** + * Gets lock store mode. + * + * @return the store mode + */ + public String getLockStoreMode() { + return StringUtils.isNotEmpty(lockStoreMode) ? lockStoreMode : storeMode; + } + + /** + * Gets session store mode. + * + * @return the store mode + */ + public String getSessionStoreMode() { + return StringUtils.isNotEmpty(sessionStoreMode) ? sessionStoreMode : 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); + } + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/SeataServerApplication.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/SeataServerApplication.java new file mode 100644 index 00000000..b8046178 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/SeataServerApplication.java @@ -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; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.io.IOException; + +/** + * @author spilledyear@outlook.com + */ +@SpringBootApplication(scanBasePackages = {"io.seata"}) +public class SeataServerApplication { + public static void main(String[] args) throws IOException { + // run the spring-boot application + SpringApplication.run(SeataServerApplication.class, args); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/Server.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/Server.java new file mode 100644 index 00000000..1603e9d9 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/Server.java @@ -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 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.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.rpc.netty.NettyRemotingServer; +import io.seata.core.rpc.netty.NettyServerConfig; +import io.seata.server.coordinator.DefaultCoordinator; +import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.metrics.MetricsManager; +import io.seata.server.session.SessionHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.spring.boot.autoconfigure.StarterConstants.REGEX_SPLIT_CHAR; +import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFERED_NETWORKS; + +/** + * The type Server. + * + * @author slievrly + */ +public class Server { + /** + * The entry point of application. + * + * @param args the input arguments + */ + public static void start(String[] args) { + // create logger + final Logger logger = LoggerFactory.getLogger(Server.class); + + //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); + UUIDGenerator.init(parameterParser.getServerNode()); + //log store mode : file, db, redis + SessionHolder.init(parameterParser.getSessionStoreMode()); + LockerManagerFactory.init(parameterParser.getLockStoreMode()); + DefaultCoordinator coordinator = DefaultCoordinator.getInstance(nettyRemotingServer); + coordinator.init(); + nettyRemotingServer.setHandler(coordinator); + + // let ServerRunner do destroy instead ShutdownHook, see https://github.com/seata/seata/issues/4028 + ServerRunner.addDisposable(coordinator); + + //127.0.0.1 and 0.0.0.0 are not valid here. + if (NetUtil.isValidIp(parameterParser.getHost(), false)) { + XID.setIpAddress(parameterParser.getHost()); + } else { + String preferredNetworks = ConfigurationFactory.getInstance().getConfig(REGISTRY_PREFERED_NETWORKS); + if (StringUtils.isNotBlank(preferredNetworks)) { + XID.setIpAddress(NetUtil.getLocalIp(preferredNetworks.split(REGEX_SPLIT_CHAR))); + } else { + XID.setIpAddress(NetUtil.getLocalIp()); + } + } + nettyRemotingServer.init(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerApplicationListener.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerApplicationListener.java new file mode 100644 index 00000000..79c54134 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerApplicationListener.java @@ -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; + +import java.util.Properties; +import io.seata.common.holder.ObjectHolder; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.spring.boot.autoconfigure.SeataCoreEnvironmentPostProcessor; +import io.seata.spring.boot.autoconfigure.SeataServerEnvironmentPostProcessor; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.GenericApplicationListener; +import org.springframework.core.ResolvableType; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; + +import static io.seata.common.ConfigurationKeys.STORE_LOCK_MODE; +import static io.seata.common.ConfigurationKeys.STORE_MODE; +import static io.seata.common.ConfigurationKeys.STORE_SESSION_MODE; +import static io.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; +import static io.seata.common.DefaultValues.SERVICE_OFFSET_SPRING_BOOT; +import static io.seata.core.constants.ConfigurationKeys.ENV_SEATA_PORT_KEY; +import static io.seata.core.constants.ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL; +import static io.seata.core.constants.ConfigurationKeys.SERVER_SERVICE_PORT_CONFIG; + +/** + * @author slievrly + * @author funkye + */ +public class ServerApplicationListener implements GenericApplicationListener { + + @Override + public boolean supportsEventType(ResolvableType eventType) { + return eventType.getRawClass() != null + && ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType.getRawClass()); + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (!(event instanceof ApplicationEnvironmentPreparedEvent)) { + return; + } + ApplicationEnvironmentPreparedEvent environmentPreparedEvent = (ApplicationEnvironmentPreparedEvent)event; + ConfigurableEnvironment environment = environmentPreparedEvent.getEnvironment(); + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + SeataCoreEnvironmentPostProcessor.init(); + SeataServerEnvironmentPostProcessor.init(); + Configuration config = ConfigurationFactory.getInstance(); + // Load by priority + System.setProperty("sessionMode", + config.getConfig(STORE_SESSION_MODE, config.getConfig(STORE_MODE, "file"))); + System.setProperty("lockMode", + config.getConfig(STORE_LOCK_MODE, config.getConfig(STORE_MODE, "file"))); + + String[] args = environmentPreparedEvent.getArgs(); + + // port: -p > -D > env > yml > default + + //-p 8091 + if (args != null && args.length >= 2) { + for (int i = 0; i < args.length; ++i) { + if ("-p".equalsIgnoreCase(args[i]) && i < args.length - 1) { + setTargetPort(environment, args[i + 1], true); + return; + } + } + } + + // -Dserver.servicePort=8091 + String dPort = environment.getProperty(SERVER_SERVICE_PORT_CAMEL, String.class); + if (StringUtils.isNotBlank(dPort)) { + setTargetPort(environment, dPort, true); + return; + } + + //docker -e SEATA_PORT=8091 + String envPort = environment.getProperty(ENV_SEATA_PORT_KEY, String.class); + if (StringUtils.isNotBlank(envPort)) { + setTargetPort(environment, envPort, true); + return; + } + + //yml properties server.service-port=8091 + String configPort = environment.getProperty(SERVER_SERVICE_PORT_CONFIG, String.class); + if (StringUtils.isNotBlank(configPort)) { + setTargetPort(environment, configPort, false); + return; + } + + // server.port=7091 + String serverPort = environment.getProperty("server.port", String.class); + if (StringUtils.isBlank(serverPort)) { + serverPort = "8080"; + } + String servicePort = String.valueOf(Integer.parseInt(serverPort) + SERVICE_OFFSET_SPRING_BOOT); + setTargetPort(environment, servicePort, true); + } + + private void setTargetPort(ConfigurableEnvironment environment, String port, boolean needAddPropertySource) { + // get rpc port first, use to logback-spring.xml, @see the class named `SystemPropertyLoggerContextListener` + System.setProperty(SERVER_SERVICE_PORT_CAMEL, port); + + if (needAddPropertySource) { + // add property source to the first position + Properties pro = new Properties(); + pro.setProperty(SERVER_SERVICE_PORT_CONFIG, port); + environment.getPropertySources().addFirst(new PropertiesPropertySource("serverProperties", pro)); + } + } + + /** + * higher than LoggingApplicationListener + * + * @return the order + */ + @Override + public int getOrder() { + return LoggingApplicationListener.DEFAULT_ORDER - 1; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerRunner.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerRunner.java new file mode 100644 index 00000000..2d567df6 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/ServerRunner.java @@ -0,0 +1,82 @@ +/* + * 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.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.seata.core.rpc.Disposable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + + +/** + * @author spilledyear@outlook.com + */ +@Component +public class ServerRunner implements CommandLineRunner, DisposableBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerRunner.class); + + private boolean started = Boolean.FALSE; + + private static final List DISPOSABLE_LIST = new CopyOnWriteArrayList<>(); + + public static void addDisposable(Disposable disposable) { + DISPOSABLE_LIST.add(disposable); + } + + @Override + public void run(String... args) { + try { + long start = System.currentTimeMillis(); + Server.start(args); + started = true; + + long cost = System.currentTimeMillis() - start; + LOGGER.info("seata server started in {} millSeconds", cost); + } catch (Throwable e) { + started = Boolean.FALSE; + LOGGER.error("seata server start error: {} ", e.getMessage(), e); + System.exit(-1); + } + } + + + public boolean started() { + return started; + } + + @Override + public void destroy() throws Exception { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("destoryAll starting"); + } + + for (Disposable disposable : DISPOSABLE_LIST) { + disposable.destroy(); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("destoryAll finish"); + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/UUIDGenerator.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/UUIDGenerator.java new file mode 100644 index 00000000..11aa492e --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/UUIDGenerator.java @@ -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); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/AbstractCheckAuthHandler.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/AbstractCheckAuthHandler.java new file mode 100644 index 00000000..ecfbfc12 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/AbstractCheckAuthHandler.java @@ -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. + */ +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); +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/DefaultCheckAuthHandler.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/DefaultCheckAuthHandler.java new file mode 100644 index 00000000..ed3e9343 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/auth/DefaultCheckAuthHandler.java @@ -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.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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/BranchSessionController.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/BranchSessionController.java new file mode 100644 index 00000000..b6980f42 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/BranchSessionController.java @@ -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.console.controller; + +import javax.annotation.Resource; +import io.seata.server.console.service.BranchSessionService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Branch Session Controller + * @author: zhongxiang.wang + */ +@RestController +@RequestMapping("console/branchSession") +public class BranchSessionController { + + @Resource(type = BranchSessionService.class) + private BranchSessionService branchSessionService; + + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalLockController.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalLockController.java new file mode 100644 index 00000000..0be8fa41 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalLockController.java @@ -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.console.controller; + +import javax.annotation.Resource; + +import io.seata.server.console.param.GlobalLockParam; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.GlobalLockVO; +import io.seata.server.console.service.GlobalLockService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * Global Lock Controller + * @author: zhongxiang.wang + */ +@RestController +@RequestMapping("/api/v1/console/globalLock") +public class GlobalLockController { + + @Resource(type = GlobalLockService.class) + private GlobalLockService globalLockService; + + /** + * Query locks by param + * @param param the param + * @return the list of GlobalLockVO + */ + @GetMapping("query") + public PageResult query(@ModelAttribute GlobalLockParam param) { + return globalLockService.query(param); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalSessionController.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalSessionController.java new file mode 100644 index 00000000..cc5ceb11 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/controller/GlobalSessionController.java @@ -0,0 +1,50 @@ +/* + * 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.console.controller; + +import javax.annotation.Resource; + +import io.seata.server.console.param.GlobalSessionParam; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.GlobalSessionVO; +import io.seata.server.console.service.GlobalSessionService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Global Session Controller + * @author: zhongxiang.wang + */ +@RestController +@RequestMapping("/api/v1/console/globalSession") +public class GlobalSessionController { + + @Resource(type = GlobalSessionService.class) + private GlobalSessionService globalSessionService; + + /** + * Query all globalSession + * @param param param for query globalSession + * @return the list of GlobalSessionVO + */ + @GetMapping("query") + public PageResult query(@ModelAttribute GlobalSessionParam param) { + return globalSessionService.query(param); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/BranchSessionDBServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/BranchSessionDBServiceImpl.java new file mode 100644 index 00000000..367e71c5 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/BranchSessionDBServiceImpl.java @@ -0,0 +1,102 @@ +/* + * 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.console.impl.db; + +import java.sql.Connection; +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.ConfigurationKeys; +import io.seata.common.exception.StoreException; +import io.seata.common.loader.EnhancedServiceLoader; +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.console.result.PageResult; +import io.seata.core.store.db.DataSourceProvider; +import io.seata.core.store.db.sql.log.LogStoreSqlsFactory; +import io.seata.server.console.service.BranchSessionService; +import io.seata.server.console.vo.BranchSessionVO; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import static io.seata.common.DefaultValues.DEFAULT_STORE_DB_BRANCH_TABLE; + +/** + * Branch Session DataBase ServiceImpl + * + * @author: zhongxiang.wang + * @author: lvekee 734843455@qq.com + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'db'.equals('${sessionMode}')}") +public class BranchSessionDBServiceImpl implements BranchSessionService { + + private String branchTable; + + private String dbType; + + private DataSource dataSource; + + public BranchSessionDBServiceImpl() { + Configuration configuration = ConfigurationFactory.getInstance(); + branchTable = configuration.getConfig(ConfigurationKeys.STORE_DB_BRANCH_TABLE, DEFAULT_STORE_DB_BRANCH_TABLE); + dbType = configuration.getConfig(ConfigurationKeys.STORE_DB_TYPE); + if (StringUtils.isBlank(dbType)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_TYPE + " should not be blank"); + } + String dbDataSource = configuration.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE); + if (StringUtils.isBlank(dbDataSource)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE + " should not be blank"); + } + dataSource = EnhancedServiceLoader.load(DataSourceProvider.class, dbDataSource).provide(); + } + + @Override + public PageResult queryByXid(String xid) { + if (StringUtils.isBlank(xid)) { + throw new IllegalArgumentException("xid should not be blank"); + } + + String whereCondition = " where xid = ? "; + String branchSessionSQL = LogStoreSqlsFactory.getLogStoreSqls(dbType).getAllBranchSessionSQL(branchTable, whereCondition); + + List list = new ArrayList<>(); + ResultSet rs = null; + + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(branchSessionSQL)) { + ps.setObject(1, xid); + rs = ps.executeQuery(); + while (rs.next()) { + list.add(BranchSessionVO.convert(rs)); + } + } catch (SQLException e) { + throw new StoreException(e); + } finally { + IOUtil.close(rs); + } + return PageResult.success(list, list.size(), 0, 0, 0); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalLockDBServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalLockDBServiceImpl.java new file mode 100644 index 00000000..4a8a5a23 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalLockDBServiceImpl.java @@ -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.console.impl.db; + +import java.sql.Connection; +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.ConfigurationKeys; +import io.seata.common.exception.StoreException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.IOUtil; +import io.seata.common.util.PageUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.console.result.PageResult; +import io.seata.core.store.db.DataSourceProvider; +import io.seata.core.store.db.sql.lock.LockStoreSqlFactory; +import io.seata.server.console.param.GlobalLockParam; +import io.seata.server.console.service.GlobalLockService; +import io.seata.server.console.vo.GlobalLockVO; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import static io.seata.common.DefaultValues.DEFAULT_LOCK_DB_TABLE; + + +/** + * Global Lock DB ServiceImpl + * + * @author: zhongxiang.wang + * @author: lvekee 734843455@qq.com + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'db'.equals('${lockMode}')}") +public class GlobalLockDBServiceImpl implements GlobalLockService { + + private String lockTable; + + private String dbType; + + private DataSource dataSource; + + public GlobalLockDBServiceImpl() { + Configuration configuration = ConfigurationFactory.getInstance(); + lockTable = configuration.getConfig(ConfigurationKeys.LOCK_DB_TABLE, DEFAULT_LOCK_DB_TABLE); + dbType = configuration.getConfig(ConfigurationKeys.STORE_DB_TYPE); + if (StringUtils.isBlank(dbType)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_TYPE + " should not be blank"); + } + String dbDataSource = configuration.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE); + if (StringUtils.isBlank(dbDataSource)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE + " should not be blank"); + } + dataSource = EnhancedServiceLoader.load(DataSourceProvider.class, dbDataSource).provide(); + } + + @Override + public PageResult query(GlobalLockParam param) { + PageUtil.checkParam(param.getPageNum(), param.getPageSize()); + + List sqlParamList = new ArrayList<>(); + String whereCondition = this.getWhereConditionByParam(param, sqlParamList); + + String sourceSql = LockStoreSqlFactory.getLogStoreSql(dbType).getAllLockSql(lockTable, whereCondition); + String queryLockSql = PageUtil.pageSql(sourceSql, dbType, param.getPageNum(), param.getPageSize()); + String lockCountSql = PageUtil.countSql(sourceSql, dbType); + + List list = new ArrayList<>(); + int count = 0; + + ResultSet rs = null; + ResultSet countRs = null; + + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(queryLockSql); + PreparedStatement countPs = conn.prepareStatement(lockCountSql)) { + PageUtil.setObject(ps, sqlParamList); + rs = ps.executeQuery(); + while (rs.next()) { + list.add(GlobalLockVO.convert(rs)); + } + PageUtil.setObject(countPs, sqlParamList); + countRs = countPs.executeQuery(); + if (countRs.next()) { + count = countRs.getInt(1); + } + } catch (SQLException e) { + throw new StoreException(e); + } finally { + IOUtil.close(rs, countRs); + } + return PageResult.success(list, count, param.getPageNum(), param.getPageSize()); + } + + private String getWhereConditionByParam(GlobalLockParam param, List sqlParamList) { + StringBuilder whereConditionBuilder = new StringBuilder(); + if (StringUtils.isNotBlank(param.getXid())) { + whereConditionBuilder.append(" and xid = ? "); + sqlParamList.add(param.getXid()); + } + if (StringUtils.isNotBlank(param.getTableName())) { + whereConditionBuilder.append(" and table_name = ? "); + sqlParamList.add(param.getTableName()); + } + if (StringUtils.isNotBlank(param.getTransactionId())) { + whereConditionBuilder.append(" and transaction_id = ? "); + sqlParamList.add(param.getTransactionId()); + } + if (StringUtils.isNotBlank(param.getBranchId())) { + whereConditionBuilder.append(" and branch_id = ? "); + sqlParamList.add(param.getBranchId()); + } + if (param.getTimeStart() != null) { + whereConditionBuilder.append(" and gmt_create >= ? "); + sqlParamList.add(param.getTimeStart()); + } + if (param.getTimeEnd() != null) { + whereConditionBuilder.append(" and gmt_create <= ? "); + sqlParamList.add(param.getTimeEnd()); + } + String whereCondition = whereConditionBuilder.toString(); + return whereCondition.replaceFirst("and", "where"); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java new file mode 100644 index 00000000..f5d720e8 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/db/GlobalSessionDBServiceImpl.java @@ -0,0 +1,161 @@ +/* + * 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.console.impl.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import javax.annotation.Resource; +import javax.sql.DataSource; + +import io.seata.common.ConfigurationKeys; +import io.seata.common.exception.StoreException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.IOUtil; +import io.seata.common.util.PageUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.console.result.PageResult; +import io.seata.core.store.db.DataSourceProvider; +import io.seata.core.store.db.sql.log.LogStoreSqlsFactory; +import io.seata.server.console.param.GlobalSessionParam; +import io.seata.server.console.service.BranchSessionService; +import io.seata.server.console.service.GlobalSessionService; +import io.seata.server.console.vo.BranchSessionVO; +import io.seata.server.console.vo.GlobalSessionVO; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import static io.seata.common.DefaultValues.DEFAULT_STORE_DB_GLOBAL_TABLE; + +/** + * Global Session DataBase ServiceImpl + * + * @author: zhongxiang.wang + * @author: lvekee 734843455@qq.com + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'db'.equals('${sessionMode}')}") +public class GlobalSessionDBServiceImpl implements GlobalSessionService { + + private String globalTable; + + private String dbType; + + private DataSource dataSource; + + @Resource(type = BranchSessionService.class) + private BranchSessionService branchSessionService; + + public GlobalSessionDBServiceImpl() { + Configuration configuration = ConfigurationFactory.getInstance(); + globalTable = configuration.getConfig(ConfigurationKeys.STORE_DB_GLOBAL_TABLE, DEFAULT_STORE_DB_GLOBAL_TABLE); + dbType = configuration.getConfig(ConfigurationKeys.STORE_DB_TYPE); + if (StringUtils.isBlank(dbType)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_TYPE + " should not be blank"); + } + String dbDataSource = configuration.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE); + if (StringUtils.isBlank(dbDataSource)) { + throw new IllegalArgumentException(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE + " should not be blank"); + } + dataSource = EnhancedServiceLoader.load(DataSourceProvider.class, dbDataSource).provide(); + } + + @Override + public PageResult query(GlobalSessionParam param) { + PageUtil.checkParam(param.getPageNum(), param.getPageSize()); + + List sqlParamList = new ArrayList<>(); + String whereCondition = getWhereConditionByParam(param, sqlParamList); + + String sourceSql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getAllGlobalSessionSql(globalTable, whereCondition); + String querySessionSql = PageUtil.pageSql(sourceSql, dbType, param.getPageNum(), param.getPageSize()); + String sessionCountSql = PageUtil.countSql(sourceSql, dbType); + + List list = new ArrayList<>(); + int count = 0; + + + ResultSet rs = null; + ResultSet countRs = null; + + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(querySessionSql); + PreparedStatement countPs = conn.prepareStatement(sessionCountSql)) { + PageUtil.setObject(ps, sqlParamList); + rs = ps.executeQuery(); + while (rs.next()) { + list.add(GlobalSessionVO.convert(rs)); + } + + PageUtil.setObject(countPs, sqlParamList); + countRs = countPs.executeQuery(); + if (countRs.next()) { + count = countRs.getInt(1); + } + if (param.isWithBranch()) { + for (GlobalSessionVO globalSessionVO : list) { + PageResult pageResp = branchSessionService.queryByXid(globalSessionVO.getXid()); + globalSessionVO.setBranchSessionVOs(new HashSet<>(pageResp.getData())); + } + } + } catch (SQLException e) { + throw new StoreException(e); + } finally { + IOUtil.close(rs, countRs); + } + return PageResult.success(list, count, param.getPageNum(), param.getPageSize()); + } + + private String getWhereConditionByParam(GlobalSessionParam param, List sqlParamList) { + StringBuilder whereConditionBuilder = new StringBuilder(); + if (StringUtils.isNotBlank(param.getXid())) { + whereConditionBuilder.append(" and xid = ? "); + sqlParamList.add(param.getXid()); + } + if (StringUtils.isNotBlank(param.getApplicationId())) { + whereConditionBuilder.append(" and application_id = ? "); + sqlParamList.add(param.getApplicationId()); + } + if (param.getStatus() != null) { + whereConditionBuilder.append(" and status = ? "); + sqlParamList.add(param.getStatus()); + } + if (StringUtils.isNotBlank(param.getTransactionName())) { + whereConditionBuilder.append(" and transaction_name = ? "); + sqlParamList.add(param.getTransactionName()); + } + if (param.getTimeStart() != null) { + whereConditionBuilder.append(" and gmt_create >= ? "); + sqlParamList.add(new Date(param.getTimeStart())); + } + if (param.getTimeEnd() != null) { + whereConditionBuilder.append(" and gmt_create <= ? "); + sqlParamList.add(new Date(param.getTimeEnd())); + } + String whereCondition = whereConditionBuilder.toString(); + return whereCondition.replaceFirst("and", "where"); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/BranchSessionFileServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/BranchSessionFileServiceImpl.java new file mode 100644 index 00000000..8305484c --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/BranchSessionFileServiceImpl.java @@ -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.console.impl.file; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.server.console.vo.BranchSessionVO; +import io.seata.console.result.PageResult; +import io.seata.server.console.service.BranchSessionService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Branch Session File ServiceImpl + * + * @author: zhongxiang.wang + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'file'.equals('${sessionMode}')}") +public class BranchSessionFileServiceImpl implements BranchSessionService { + + @Override + public PageResult queryByXid(String xid) { + throw new NotSupportYetException(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalLockFileServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalLockFileServiceImpl.java new file mode 100644 index 00000000..edfdc633 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalLockFileServiceImpl.java @@ -0,0 +1,171 @@ +/* + * 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.console.impl.file; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.server.console.param.GlobalLockParam; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.GlobalLockVO; +import io.seata.core.lock.RowLock; +import io.seata.server.console.service.GlobalLockService; +import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import static io.seata.common.util.StringUtils.isBlank; +import static io.seata.server.console.vo.GlobalLockVO.convert; +import static java.util.Objects.isNull; + +/** + * Global Lock File ServiceImpl + * + * @author zhongxiang.wang + * @author miaoxueyu + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'file'.equals('${lockMode}')}") +public class GlobalLockFileServiceImpl implements GlobalLockService { + + @Override + public PageResult query(GlobalLockParam param) { + checkParam(param); + + final Collection allSessions = SessionHolder.getRootSessionManager().allSessions(); + + final AtomicInteger total = new AtomicInteger(); + List result = allSessions + .parallelStream() + .filter(obtainGlobalSessionPredicate(param)) + .flatMap(globalSession -> globalSession.getBranchSessions().stream()) + .filter(obtainBranchSessionPredicate(param)) + .flatMap(branchSession -> filterAndMap(param, branchSession)) + .peek(globalSession -> total.incrementAndGet()) + .collect(Collectors.toList()); + + return PageResult.build(convert(result), param.getPageNum(), param.getPageSize()); + + } + + /** + * filter with tableName and generate RowLock + * + * @param param the query param + * @param branchSession the branch session + * @return the RowLock list + */ + private Stream filterAndMap(GlobalLockParam param, BranchSession branchSession) { + + final String tableName = param.getTableName(); + + // get rowLock from branchSession + final List rowLocks = LockerManagerFactory.getLockManager().collectRowLocks(branchSession); + + if (StringUtils.isNotBlank(tableName)) { + return rowLocks.parallelStream().filter(rowLock -> rowLock.getTableName().contains(param.getTableName())); + } + + return rowLocks.stream(); + } + + + /** + * check the param + * + * @param param the param + */ + private void checkParam(GlobalLockParam param) { + if (param.getPageSize() <= 0 || param.getPageNum() <= 0) { + throw new IllegalArgumentException("wrong pageSize or pageNum"); + } + + // verification data type + try { + Long.parseLong(param.getTransactionId()); + } catch (NumberFormatException e) { + param.setTransactionId(null); + } + try { + Long.parseLong(param.getBranchId()); + } catch (NumberFormatException e) { + param.setBranchId(null); + } + + + } + + /** + * obtain the branch session condition + * + * @param param condition for query branch session + * @return the filter condition + */ + private Predicate obtainBranchSessionPredicate(GlobalLockParam param) { + return branchSession -> { + // transactionId + return (isBlank(param.getTransactionId()) || + String.valueOf(branchSession.getTransactionId()).contains(param.getTransactionId())) + + && + // branch id + (isBlank(param.getBranchId()) || + String.valueOf(branchSession.getBranchId()).contains(param.getBranchId())) + ; + }; + } + + + /** + * obtain the global session condition + * + * @param param condition for query global session + * @return the filter condition + */ + private Predicate obtainGlobalSessionPredicate(GlobalLockParam param) { + + return globalSession -> { + // first, there must be withBranchSession + return CollectionUtils.isNotEmpty(globalSession.getBranchSessions()) + + && + // The second is other conditions + // xid + (isBlank(param.getXid()) || globalSession.getXid().contains(param.getXid())) + + && + // timeStart + (isNull(param.getTimeStart()) || param.getTimeStart() <= globalSession.getBeginTime()) + + && + // timeEnd + (isNull(param.getTimeEnd()) || param.getTimeEnd() >= globalSession.getBeginTime()); + }; + } + + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java new file mode 100644 index 00000000..0487aacc --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/file/GlobalSessionFileServiceImpl.java @@ -0,0 +1,102 @@ +/* + * 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.console.impl.file; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import io.seata.server.console.param.GlobalSessionParam; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.GlobalSessionVO; +import io.seata.server.console.service.GlobalSessionService; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.SessionConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import static io.seata.common.util.StringUtils.isBlank; +import static java.util.Objects.isNull; + +/** + * Global Session File ServiceImpl + * + * @author zhongxiang.wang + * @author miaoxueyu + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'file'.equals('${sessionMode}')}") +public class GlobalSessionFileServiceImpl implements GlobalSessionService { + + @Override + public PageResult query(GlobalSessionParam param) { + if (param.getPageSize() <= 0 || param.getPageNum() <= 0) { + throw new IllegalArgumentException("wrong pageSize or pageNum"); + } + + final Collection allSessions = SessionHolder.getRootSessionManager().allSessions(); + + final List filteredSessions = allSessions + .parallelStream() + .filter(obtainPredicate(param)) + .collect(Collectors.toList()); + + return PageResult.build(SessionConverter.convertGlobalSession(filteredSessions), param.getPageNum(), param.getPageSize()); + } + + + + /** + * obtain the condition + * + * @param param condition for query global session + * @return the filter condition + */ + private Predicate obtainPredicate(GlobalSessionParam param) { + + return session -> { + return + // xid + (isBlank(param.getXid()) || session.getXid().contains(param.getXid())) + + && + // applicationId + (isBlank(param.getApplicationId()) || session.getApplicationId().contains(param.getApplicationId())) + + && + // status + (isNull(param.getStatus()) || Objects.equals(session.getStatus().getCode(), param.getStatus())) + + && + // transactionName + (isBlank(param.getTransactionName()) || session.getTransactionName().contains(param.getTransactionName())) + + && + // timeStart + (isNull(param.getTimeStart()) || param.getTimeStart() <= session.getBeginTime()) + + && + // timeEnd + (isNull(param.getTimeEnd()) || param.getTimeEnd() >= session.getBeginTime()); + + }; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java new file mode 100644 index 00000000..0173f594 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java @@ -0,0 +1,64 @@ +/* + * 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.console.impl.redis; + +import java.util.ArrayList; +import java.util.List; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.BranchSessionVO; +import io.seata.core.store.BranchTransactionDO; +import io.seata.server.console.service.BranchSessionService; +import io.seata.server.storage.redis.store.RedisTransactionStoreManager; +import org.springframework.beans.BeanUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Branch Session Redis ServiceImpl + * + * @author: zhongxiang.wang + * @author: doubleDimple + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'redis'.equals('${sessionMode}')}") +public class BranchSessionRedisServiceImpl implements BranchSessionService { + + @Override + public PageResult queryByXid(String xid) { + if (StringUtils.isBlank(xid)) { + return PageResult.success(); + } + + List branchSessionVos = new ArrayList<>(); + + RedisTransactionStoreManager instance = RedisTransactionStoreManager.getInstance(); + + List branchSessionDos = instance.findBranchSessionByXid(xid); + + if (CollectionUtils.isNotEmpty(branchSessionDos)) { + for (BranchTransactionDO branchSessionDo : branchSessionDos) { + BranchSessionVO branchSessionVO = new BranchSessionVO(); + BeanUtils.copyProperties(branchSessionDo, branchSessionVO); + branchSessionVos.add(branchSessionVO); + } + } + + return PageResult.success(branchSessionVos, branchSessionVos.size(), 0, branchSessionVos.size()); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java new file mode 100644 index 00000000..91a53c9d --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java @@ -0,0 +1,119 @@ +/* + * 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.console.impl.redis; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import io.seata.common.util.CollectionUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import io.seata.common.util.BeanUtils; +import io.seata.server.console.param.GlobalLockParam; +import io.seata.console.result.PageResult; +import io.seata.server.console.vo.GlobalLockVO; +import io.seata.server.console.service.GlobalLockService; +import io.seata.server.storage.redis.JedisPooledFactory; +import redis.clients.jedis.Jedis; +import static io.seata.common.Constants.ROW_LOCK_KEY_SPLIT_CHAR; +import static io.seata.common.exception.FrameworkErrorCode.ParameterRequired; +import static io.seata.common.util.StringUtils.isNotBlank; +import static io.seata.console.result.PageResult.checkPage; +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX; +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX; +import static io.seata.core.constants.RedisKeyConstants.SPLIT; + +/** + * Global Lock Redis Service Impl + * @author: zhongxiang.wang + * @author: doubleDimple + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'redis'.equals('${lockMode}')}") +public class GlobalLockRedisServiceImpl implements GlobalLockService { + + @Override + public PageResult query(GlobalLockParam param) { + + int total = 0; + List globalLockVos; + checkPage(param); + if (isNotBlank(param.getXid())) { + globalLockVos = queryGlobalByXid(param.getXid()); + total = globalLockVos.size(); + return PageResult.success(globalLockVos,total,param.getPageNum(),param.getPageSize()); + } else if (isNotBlank(param.getTableName()) && isNotBlank(param.getPk()) && isNotBlank(param.getResourceId())) { + //SEATA_ROW_LOCK_jdbc:mysql://116.62.62.26/seata-order^^^order^^^2188 + String tableName = param.getTableName(); + String pk = param.getPk(); + String resourceId = param.getResourceId(); + globalLockVos = queryGlobalLockByRowKey(buildRowKey(tableName,pk,resourceId)); + total = globalLockVos.size(); + return PageResult.success(globalLockVos,total,param.getPageNum(),param.getPageSize()); + } else { + return PageResult.failure(ParameterRequired.getErrCode(),"only three parameters of tableName,pk,resourceId or Xid are supported"); + } + } + + private List queryGlobalLockByRowKey(String buildRowKey) { + return readGlobalLockByRowKey(buildRowKey); + } + + private String buildRowKey(String tableName, String pk,String resourceId) { + return DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX + resourceId + SPLIT + tableName + SPLIT + pk; + } + + + private List queryGlobalByXid(String xid) { + return readGlobalLockByXid(DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX + xid); + } + + private List readGlobalLockByXid(String key) { + List vos = new ArrayList<>(); + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + Map mapGlobalKeys = jedis.hgetAll(key); + if (CollectionUtils.isNotEmpty(mapGlobalKeys)) { + List rowLockKeys = new ArrayList<>(); + mapGlobalKeys.forEach((k,v) -> rowLockKeys.addAll(Arrays.asList(v.split(ROW_LOCK_KEY_SPLIT_CHAR)))); + for (String rowLoclKey : rowLockKeys) { + Map mapRowLockKey = jedis.hgetAll(rowLoclKey); + GlobalLockVO vo = (GlobalLockVO)BeanUtils.mapToObject(mapRowLockKey, GlobalLockVO.class); + if (vo != null) { + vos.add(vo); + } + } + } + } + + return vos; + } + + + private List readGlobalLockByRowKey(String key) { + List vos = new ArrayList<>(); + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + Map map = jedis.hgetAll(key); + GlobalLockVO vo = (GlobalLockVO)BeanUtils.mapToObject(map, GlobalLockVO.class); + if (vo != null) { + vos.add(vo); + } + } + return vos; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java new file mode 100644 index 00000000..779b8843 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java @@ -0,0 +1,108 @@ +/* + * 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.console.impl.redis; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import io.seata.common.util.CollectionUtils; +import io.seata.console.result.PageResult; +import io.seata.server.console.param.GlobalSessionParam; +import io.seata.server.console.vo.GlobalSessionVO; +import io.seata.core.model.GlobalStatus; +import io.seata.server.console.service.GlobalSessionService; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionCondition; +import io.seata.server.storage.redis.store.RedisTransactionStoreManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import static io.seata.common.exception.FrameworkErrorCode.ParameterRequired; +import static io.seata.common.util.StringUtils.isBlank; +import static io.seata.common.util.StringUtils.isNotBlank; +import static io.seata.console.result.PageResult.checkPage; +import static io.seata.server.storage.SessionConverter.convertToGlobalSessionVo; + +/** + * Global Session Redis ServiceImpl + * @author: zhongxiang.wang + * @author: doubleDimple + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'redis'.equals('${sessionMode}')}") +public class GlobalSessionRedisServiceImpl implements GlobalSessionService { + + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSessionRedisServiceImpl.class); + + @Override + public PageResult query(GlobalSessionParam param) { + List result = new ArrayList<>(); + Long total = 0L; + if (param.getTimeStart() != null || param.getTimeEnd() != null) { + //not support time range query + LOGGER.debug("not supported according to time range query"); + return PageResult.failure(ParameterRequired.getErrCode(),"not supported according to time range query"); + } + List globalSessions = new ArrayList<>(); + + RedisTransactionStoreManager instance = RedisTransactionStoreManager.getInstance(); + + checkPage(param); + + if (isBlank(param.getXid()) && param.getStatus() == null) { + total = instance.countByGlobalSessions(GlobalStatus.values()); + globalSessions = instance.findGlobalSessionByPage(param.getPageNum(), param.getPageSize(),param.isWithBranch()); + } else { + List globalSessionsNew = new ArrayList<>(); + if (isNotBlank(param.getXid())) { + SessionCondition sessionCondition = new SessionCondition(); + sessionCondition.setXid(param.getXid()); + sessionCondition.setLazyLoadBranch(!param.isWithBranch()); + globalSessions = instance.readSession(sessionCondition); + total = (long)globalSessions.size(); + } + + if (param.getStatus() != null && GlobalStatus.get(param.getStatus()) != null) { + if (CollectionUtils.isNotEmpty(globalSessions)) { + globalSessionsNew = globalSessions.stream().filter(globalSession -> globalSession.getStatus().getCode() == (param.getStatus())).collect(Collectors.toList()); + total = (long)globalSessionsNew.size(); + } else { + total = instance.countByGlobalSessions(new GlobalStatus[] {GlobalStatus.get(param.getStatus())}); + globalSessionsNew = instance.readSessionStatusByPage(param); + } + } + + if (LOGGER.isDebugEnabled()) { + if (isNotBlank(param.getApplicationId())) { + //not support + LOGGER.debug("not supported according to applicationId query"); + } + if (isNotBlank(param.getTransactionName())) { + //not support + LOGGER.debug("not supported according to transactionName query"); + } + } + globalSessions = globalSessionsNew.size() > 0 ? globalSessionsNew : globalSessions; + } + + convertToGlobalSessionVo(result,globalSessions); + + return PageResult.success(result,total.intValue(),param.getPageNum(),param.getPageSize()); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalLockParam.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalLockParam.java new file mode 100644 index 00000000..4cc16cc9 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalLockParam.java @@ -0,0 +1,114 @@ +/* + * 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.console.param; + +import java.io.Serializable; + +import io.seata.console.param.BaseParam; + +/** + * @description: Global lock param + * @author: zhongxiang.wang + */ +public class GlobalLockParam extends BaseParam implements Serializable { + + private static final long serialVersionUID = 615412528070131284L; + + /** + * the xid + */ + private String xid; + /** + * the table name + */ + private String tableName; + /** + * the transaction id + */ + private String transactionId; + /** + * the branch id + */ + private String branchId; + /** + * the primary Key + */ + private String pk; + /** + * the resourceId + */ + private String resourceId; + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public String getBranchId() { + return branchId; + } + + public void setBranchId(String branchId) { + this.branchId = branchId; + } + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getPk() { + return pk; + } + + public void setPk(String pk) { + this.pk = pk; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + @Override + public String toString() { + return "GlobalLockParam{" + + "xid='" + xid + '\'' + + ", tableName='" + tableName + '\'' + + ", transactionId='" + transactionId + '\'' + + ", branchId='" + branchId + '\'' + + ", pk='" + pk + '\'' + + ", resourceId='" + resourceId + '\'' + + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalSessionParam.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalSessionParam.java new file mode 100644 index 00000000..a02563dc --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/param/GlobalSessionParam.java @@ -0,0 +1,102 @@ +/* + * 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.console.param; + +import java.io.Serializable; + +import io.seata.console.param.BaseParam; + +/** + * @description: Global session param + * @author: zhongxiang.wang + */ +public class GlobalSessionParam extends BaseParam implements Serializable { + + private static final long serialVersionUID = 115488252809011284L; + /** + * the xid + */ + private String xid; + /** + * the application id + */ + private String applicationId; + /** + * the global session status + */ + private Integer status; + /** + * the transaction name + */ + private String transactionName; + /** + * if with branch + * true: with branch session + * false: no branch session + */ + private boolean withBranch; + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public String getTransactionName() { + return transactionName; + } + + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public boolean isWithBranch() { + return withBranch; + } + + public void setWithBranch(boolean withBranch) { + this.withBranch = withBranch; + } + + @Override + public String toString() { + return "GlobalSessionParam{" + + "xid='" + xid + '\'' + + ", applicationId='" + applicationId + '\'' + + ", status=" + status + + ", transactionName='" + transactionName + '\'' + + ", withBranch=" + withBranch + + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/BranchSessionService.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/BranchSessionService.java new file mode 100644 index 00000000..0a8b3105 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/BranchSessionService.java @@ -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.console.service; + +import io.seata.server.console.vo.BranchSessionVO; +import io.seata.console.result.PageResult; + +/** + * Branch session service + * @author wangzhongxiang + */ +public interface BranchSessionService { + + /** + * Query branch session by xid + * @param xid the xid + * @return the BranchSessionVO list + */ + PageResult queryByXid(String xid); + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalLockService.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalLockService.java new file mode 100644 index 00000000..1b774919 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalLockService.java @@ -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.console.service; + +import io.seata.server.console.param.GlobalLockParam; +import io.seata.server.console.vo.GlobalLockVO; +import io.seata.console.result.PageResult; + + +/** + * Global lock service + * @author wangzhongxiang + */ +public interface GlobalLockService { + + /** + * Query locks by param + * @param param the param + * @return the list of GlobalLockVO + */ + PageResult query(GlobalLockParam param); + + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalSessionService.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalSessionService.java new file mode 100644 index 00000000..2f700b67 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/service/GlobalSessionService.java @@ -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.console.service; + +import io.seata.server.console.param.GlobalSessionParam; +import io.seata.server.console.vo.GlobalSessionVO; +import io.seata.console.result.PageResult; + +/** + * Global session service + * @author wangzhongxiang + */ +public interface GlobalSessionService { + + /** + * Query global session + * @param param the param + * @return the GlobalSessionVO list + */ + PageResult query(GlobalSessionParam param); + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/BranchSessionVO.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/BranchSessionVO.java new file mode 100644 index 00000000..0980f6f4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/BranchSessionVO.java @@ -0,0 +1,241 @@ +/* + * 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.console.vo; + +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; + +import io.seata.core.constants.ServerTableColumnsName; + +/** + * BranchSessionVO + * @author: zhongxiang.wang + */ +public class BranchSessionVO { + + private String xid; + + private String transactionId; + + private String branchId; + + private String resourceGroupId; + + private String resourceId; + + private String branchType; + + private Integer status; + + private String clientId; + + private String applicationData; + + private Long gmtCreate; + + private Long gmtModified; + + + public BranchSessionVO(){ + + } + + public BranchSessionVO(String xid, + Long transactionId, + Long branchId, + String resourceGroupId, + String resourceId, + String branchType, + Integer status, + String clientId, + String applicationData) { + this.xid = xid; + this.transactionId = String.valueOf(transactionId); + this.branchId = String.valueOf(branchId); + this.resourceGroupId = resourceGroupId; + this.resourceId = resourceId; + this.branchType = branchType; + this.status = status; + this.clientId = clientId; + this.applicationData = applicationData; + } + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(Long transactionId) { + this.transactionId = String.valueOf(transactionId); + } + + public String getBranchId() { + return branchId; + } + + public void setBranchId(Long branchId) { + this.branchId = String.valueOf(branchId); + } + + public String getResourceGroupId() { + return resourceGroupId; + } + + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public String getBranchType() { + return branchType; + } + + public void setBranchType(String branchType) { + this.branchType = branchType; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getApplicationData() { + return applicationData; + } + + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + public Long getGmtCreate() { + return gmtCreate; + } + + public void setGmtCreate(Long gmtCreate) { + this.gmtCreate = gmtCreate; + } + + public Long getGmtModified() { + return gmtModified; + } + + public void setGmtModified(Long gmtModified) { + this.gmtModified = gmtModified; + } + + public static BranchSessionVO convert(ResultSet rs) throws SQLException { + BranchSessionVO branchSessionVO = new BranchSessionVO(); + branchSessionVO.setXid(rs.getString(ServerTableColumnsName.BRANCH_TABLE_XID)); + branchSessionVO.setTransactionId(rs.getLong(ServerTableColumnsName.BRANCH_TABLE_TRANSACTION_ID)); + branchSessionVO.setBranchId(rs.getLong(ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID)); + branchSessionVO.setResourceGroupId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_RESOURCE_GROUP_ID)); + branchSessionVO.setResourceId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_RESOURCE_ID)); + branchSessionVO.setBranchType(rs.getString(ServerTableColumnsName.BRANCH_TABLE_BRANCH_TYPE)); + branchSessionVO.setStatus(rs.getInt(ServerTableColumnsName.BRANCH_TABLE_STATUS)); + branchSessionVO.setClientId(rs.getString(ServerTableColumnsName.BRANCH_TABLE_CLIENT_ID)); + branchSessionVO.setApplicationData(rs.getString(ServerTableColumnsName.BRANCH_TABLE_APPLICATION_DATA)); + Date gmtCreateTimestamp = rs.getDate(ServerTableColumnsName.BRANCH_TABLE_GMT_CREATE); + if (gmtCreateTimestamp != null) { + branchSessionVO.setGmtCreate(gmtCreateTimestamp.getTime()); + } + Date gmtModifiedTimestamp = rs.getDate(ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED); + if (gmtModifiedTimestamp != null) { + branchSessionVO.setGmtModified(gmtModifiedTimestamp.getTime()); + } + return branchSessionVO; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BranchSessionVO that = (BranchSessionVO) o; + return Objects.equals(xid, that.xid) + && Objects.equals(transactionId, that.transactionId) + && Objects.equals(branchId, that.branchId) + && Objects.equals(resourceGroupId, that.resourceGroupId) + && Objects.equals(resourceId, that.resourceId) + && Objects.equals(branchType, that.branchType) + && Objects.equals(status, that.status) + && Objects.equals(clientId, that.clientId) + && Objects.equals(applicationData, that.applicationData) + && Objects.equals(gmtCreate, that.gmtCreate) + && Objects.equals(gmtModified, that.gmtModified); + } + + @Override + public int hashCode() { + return Objects.hash(xid, + transactionId, + branchId, + resourceGroupId, + resourceId, + branchType, + status, + clientId, + applicationData, + gmtCreate, + gmtModified); + } + + @Override + public String toString() { + return "BranchSessionVO{" + + "xid='" + xid + '\'' + + ", transactionId=" + transactionId + + ", branchId=" + branchId + + ", resourceGroupId='" + resourceGroupId + '\'' + + ", resourceId='" + resourceId + '\'' + + ", branchType='" + branchType + '\'' + + ", status=" + status + + ", clientId='" + clientId + '\'' + + ", applicationData='" + applicationData + '\'' + + ", gmtCreate=" + gmtCreate + + ", gmtModified=" + gmtModified + + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalLockVO.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalLockVO.java new file mode 100644 index 00000000..4823f676 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalLockVO.java @@ -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.console.vo; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.seata.common.util.CollectionUtils; +import io.seata.core.constants.ServerTableColumnsName; +import io.seata.core.lock.RowLock; + +/** + * GlobalLockVO + * @author: zhongxiang.wang + * @author miaoxueyu + */ +public class GlobalLockVO { + + private String xid; + + private String transactionId; + + private String branchId; + + private String resourceId; + + private String tableName; + + private String pk; + + private String rowKey; + + private Long gmtCreate; + + private Long gmtModified; + + /** + * convert RowLock list to GlobalLockVO list + * @param rowLocks the RowLock list + * @return the GlobalLockVO list + */ + public static List convert(List rowLocks) { + if (CollectionUtils.isEmpty(rowLocks)) { + return Collections.emptyList(); + } + final List result = new ArrayList<>(rowLocks.size()); + for (RowLock rowLock : rowLocks) { + result.add(convert(rowLock)); + } + + return result; + } + + + /** + * convert RowLock to GlobalLockVO + * @param rowLock the RowLock + * @return the GlobalLockVO + */ + public static GlobalLockVO convert(RowLock rowLock) { + final GlobalLockVO globalLockVO = new GlobalLockVO(); + globalLockVO.setXid(rowLock.getXid()); + globalLockVO.setTransactionId(rowLock.getTransactionId()); + globalLockVO.setBranchId(rowLock.getBranchId()); + globalLockVO.setResourceId(rowLock.getResourceId()); + globalLockVO.setTableName(rowLock.getTableName()); + globalLockVO.setPk(rowLock.getPk()); + globalLockVO.setRowKey(rowLock.getRowKey()); + return globalLockVO; + } + + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(Long transactionId) { + this.transactionId = String.valueOf(transactionId); + } + + public String getBranchId() { + return branchId; + } + + public void setBranchId(Long branchId) { + this.branchId = String.valueOf(branchId); + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getPk() { + return pk; + } + + public void setPk(String pk) { + this.pk = pk; + } + + public String getRowKey() { + return rowKey; + } + + public void setRowKey(String rowKey) { + this.rowKey = rowKey; + } + + public Long getGmtCreate() { + return gmtCreate; + } + + public void setGmtCreate(Long gmtCreate) { + this.gmtCreate = gmtCreate; + } + + public Long getGmtModified() { + return gmtModified; + } + + public void setGmtModified(Long gmtModified) { + this.gmtModified = gmtModified; + } + + public static GlobalLockVO convert(ResultSet rs) throws SQLException { + GlobalLockVO globalLockVO = new GlobalLockVO(); + globalLockVO.setRowKey(rs.getString(ServerTableColumnsName.LOCK_TABLE_ROW_KEY)); + globalLockVO.setXid(rs.getString(ServerTableColumnsName.LOCK_TABLE_XID)); + globalLockVO.setTransactionId(rs.getLong(ServerTableColumnsName.LOCK_TABLE_TRANSACTION_ID)); + globalLockVO.setBranchId(rs.getLong(ServerTableColumnsName.LOCK_TABLE_BRANCH_ID)); + globalLockVO.setResourceId(rs.getString(ServerTableColumnsName.LOCK_TABLE_RESOURCE_ID)); + globalLockVO.setTableName(rs.getString(ServerTableColumnsName.LOCK_TABLE_TABLE_NAME)); + globalLockVO.setPk(rs.getString(ServerTableColumnsName.LOCK_TABLE_PK)); + Timestamp gmtCreateTimestamp = rs.getTimestamp(ServerTableColumnsName.LOCK_TABLE_GMT_CREATE); + if (gmtCreateTimestamp != null) { + globalLockVO.setGmtCreate(gmtCreateTimestamp.getTime()); + } + Timestamp gmtModifiedTimestamp = rs.getTimestamp(ServerTableColumnsName.LOCK_TABLE_GMT_MODIFIED); + if (gmtModifiedTimestamp != null) { + globalLockVO.setGmtModified(gmtModifiedTimestamp.getTime()); + } + return globalLockVO; + } + + @Override + public String toString() { + return "GlobalLockVO{" + + "xid='" + xid + '\'' + + ", transactionId=" + transactionId + + ", branchId=" + branchId + + ", resourceId='" + resourceId + '\'' + + ", tableName='" + tableName + '\'' + + ", pk='" + pk + '\'' + + ", rowKey='" + rowKey + '\'' + + ", gmtCreate=" + gmtCreate + + ", gmtModified=" + gmtModified + + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalSessionVO.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalSessionVO.java new file mode 100644 index 00000000..05146a1d --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/console/vo/GlobalSessionVO.java @@ -0,0 +1,217 @@ +/* + * 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.console.vo; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Set; + +import io.seata.core.constants.ServerTableColumnsName; + +/** + * GlobalSessionVO + * @author: zhongxiang.wang + */ +public class GlobalSessionVO { + + private String xid; + + private String transactionId; + + private Integer status; + + private String applicationId; + + private String transactionServiceGroup; + + private String transactionName; + + private Long timeout; + + private Long beginTime; + + private String applicationData; + + private Long gmtCreate; + + private Long gmtModified; + + private Set branchSessionVOs; + + + public GlobalSessionVO() { + + } + + public GlobalSessionVO(String xid, + Long transactionId, + Integer status, + String applicationId, + String transactionServiceGroup, + String transactionName, + Long timeout, + Long beginTime, + String applicationData, + Set branchSessionVOs) { + this.xid = xid; + this.transactionId = String.valueOf(transactionId); + this.status = status; + this.applicationId = applicationId; + this.transactionServiceGroup = transactionServiceGroup; + this.transactionName = transactionName; + this.timeout = timeout; + this.beginTime = beginTime; + this.applicationData = applicationData; + this.branchSessionVOs = branchSessionVOs; + } + + public String getXid() { + return xid; + } + + public void setXid(String xid) { + this.xid = xid; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(Long transactionId) { + this.transactionId = String.valueOf(transactionId); + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + public String getTransactionName() { + return transactionName; + } + + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + public Long getTimeout() { + return timeout; + } + + public void setTimeout(Long timeout) { + this.timeout = timeout; + } + + public Long getBeginTime() { + return beginTime; + } + + public void setBeginTime(Long beginTime) { + this.beginTime = beginTime; + } + + public String getApplicationData() { + return applicationData; + } + + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + public Long getGmtCreate() { + return gmtCreate; + } + + public void setGmtCreate(Long gmtCreate) { + this.gmtCreate = gmtCreate; + } + + public Long getGmtModified() { + return gmtModified; + } + + public void setGmtModified(Long gmtModified) { + this.gmtModified = gmtModified; + } + + public Set getBranchSessionVOs() { + return branchSessionVOs; + } + + public void setBranchSessionVOs(Set branchSessionVOs) { + this.branchSessionVOs = branchSessionVOs; + } + + public static GlobalSessionVO convert(ResultSet rs) throws SQLException { + GlobalSessionVO globalSessionVO = new GlobalSessionVO(); + globalSessionVO.setXid(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_XID)); + globalSessionVO.setTransactionId(rs.getLong(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID)); + globalSessionVO.setStatus(rs.getInt(ServerTableColumnsName.GLOBAL_TABLE_STATUS)); + globalSessionVO.setApplicationId(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_ID)); + globalSessionVO.setTransactionServiceGroup(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_SERVICE_GROUP)); + globalSessionVO.setTransactionName(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_NAME)); + globalSessionVO.setTimeout(rs.getLong(ServerTableColumnsName.GLOBAL_TABLE_TIMEOUT)); + globalSessionVO.setBeginTime(rs.getLong(ServerTableColumnsName.GLOBAL_TABLE_BEGIN_TIME)); + globalSessionVO.setApplicationData(rs.getString(ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_DATA)); + Timestamp gmtCreateTimestamp = rs.getTimestamp(ServerTableColumnsName.GLOBAL_TABLE_GMT_CREATE); + if (gmtCreateTimestamp != null) { + globalSessionVO.setGmtCreate(gmtCreateTimestamp.getTime()); + } + Timestamp gmtModifiedTimestamp = rs.getTimestamp(ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED); + if (gmtModifiedTimestamp != null) { + globalSessionVO.setGmtModified(gmtModifiedTimestamp.getTime()); + } + return globalSessionVO; + } + + @Override + public String toString() { + return "GlobalSessionVO{" + + "xid='" + xid + '\'' + + ", transactionId=" + transactionId + + ", status=" + status + + ", applicationId='" + applicationId + '\'' + + ", transactionServiceGroup='" + transactionServiceGroup + '\'' + + ", transactionName='" + transactionName + '\'' + + ", timeout=" + timeout + + ", beginTime=" + beginTime + + ", applicationData='" + applicationData + '\'' + + ", gmtCreate=" + gmtCreate + + ", gmtModified=" + gmtModified + + ", branchSessionVOs=" + branchSessionVOs + + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/controller/HealthController.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/controller/HealthController.java new file mode 100644 index 00000000..155045af --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/controller/HealthController.java @@ -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.controller; + +import io.seata.server.ServerRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author spilledyear@outlook.com + */ +@Controller +@RequestMapping +public class HealthController { + + private static final String OK = "ok"; + private static final String NOT_OK = "not_ok"; + + @Autowired + private ServerRunner serverRunner; + + + @RequestMapping("/health") + @ResponseBody + String healthCheck() { + return serverRunner.started() ? OK : NOT_OK; + } +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/AbstractCore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/AbstractCore.java new file mode 100644 index 00000000..84ab8036 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/AbstractCore.java @@ -0,0 +1,244 @@ +/* + * 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)); + } + branchSession.setApplicationData(applicationData); + 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 { + + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/Core.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/Core.java new file mode 100644 index 00000000..8ad19cbd --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/Core.java @@ -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; + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java new file mode 100644 index 00000000..22d42f0e --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java @@ -0,0 +1,624 @@ +/* + * 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.ArrayBlockingQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +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.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.metrics.MetricsPublisher; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionCondition; +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.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 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; + + /** + * default branch async queue size + */ + private static final int DEFAULT_BRANCH_ASYNC_QUEUE_SIZE = 5000; + + /** + * the pool size of branch asynchronous remove thread pool + */ + private static final int BRANCH_ASYNC_POOL_SIZE = Runtime.getRuntime().availableProcessors(); + + 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 final ScheduledThreadPoolExecutor retryRollbacking = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(RETRY_ROLLBACKING, 1)); + + private final ScheduledThreadPoolExecutor retryCommitting = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(RETRY_COMMITTING, 1)); + + private final ScheduledThreadPoolExecutor asyncCommitting = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(ASYNC_COMMITTING, 1)); + + private final ScheduledThreadPoolExecutor timeoutCheck = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(TX_TIMEOUT_CHECK, 1)); + + private final ScheduledThreadPoolExecutor undoLogDelete = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(UNDOLOG_DELETE, 1)); + + private final GlobalStatus[] rollbackingStatuses = new GlobalStatus[] {GlobalStatus.TimeoutRollbacking, + GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.RollbackRetrying, GlobalStatus.Rollbacking}; + + private final GlobalStatus[] retryCommittingStatuses = + new GlobalStatus[] {GlobalStatus.Committing, GlobalStatus.CommitRetrying}; + + private final ThreadPoolExecutor branchRemoveExecutor = new ThreadPoolExecutor(BRANCH_ASYNC_POOL_SIZE, BRANCH_ASYNC_POOL_SIZE, + Integer.MAX_VALUE, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>( + CONFIG.getInt(ConfigurationKeys.SESSION_BRANCH_ASYNC_QUEUE_SIZE, DEFAULT_BRANCH_ASYNC_QUEUE_SIZE) + ), new NamedThreadFactory("branchSessionRemove", BRANCH_ASYNC_POOL_SIZE), + new ThreadPoolExecutor.CallerRunsPolicy()); + + private RemotingServer remotingServer; + + private final DefaultCore core; + + private static volatile DefaultCoordinator instance; + + /** + * Instantiates a new Default coordinator. + * + * @param remotingServer the remoting server + */ + private DefaultCoordinator(RemotingServer remotingServer) { + if (remotingServer == null) { + throw new IllegalArgumentException("RemotingServer not allowed be null."); + } + this.remotingServer = remotingServer; + this.core = new DefaultCore(remotingServer); + } + + public static DefaultCoordinator getInstance(RemotingServer remotingServer) { + if (null == instance) { + synchronized (DefaultCoordinator.class) { + if (null == instance) { + instance = new DefaultCoordinator(remotingServer); + } + } + } + return instance; + } + + public static DefaultCoordinator getInstance() { + if (null == instance) { + throw new IllegalArgumentException("The instance has not been created."); + } + return instance; + } + + /** + * Asynchronous remove branch + * + * @param globalSession the globalSession + * @param branchSession the branchSession + */ + public void doBranchRemoveAsync(GlobalSession globalSession, BranchSession branchSession) { + if (globalSession == null) { + return; + } + branchRemoveExecutor.execute(new BranchRemoveTask(globalSession, branchSession)); + } + + /** + * Asynchronous remove all branch + * + * @param globalSession the globalSession + */ + public void doBranchRemoveAllAsync(GlobalSession globalSession) { + if (globalSession == null) { + return; + } + branchRemoveExecutor.execute(new BranchRemoveTask(globalSession)); + } + + @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. + */ + protected void timeoutCheck() { + SessionCondition sessionCondition = new SessionCondition(GlobalStatus.Begin); + sessionCondition.setLazyLoadBranch(true); + Collection beginGlobalsessions = + SessionHolder.getRootSessionManager().findGlobalSessions(sessionCondition); + if (CollectionUtils.isEmpty(beginGlobalsessions)) { + return; + } + if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) { + LOGGER.debug("Global transaction timeout check begin, size: {}", beginGlobalsessions.size()); + } + SessionHelper.forEach(beginGlobalsessions, globalSession -> { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + globalSession.getXid() + " " + globalSession.getStatus() + " " + globalSession.getBeginTime() + " " + + globalSession.getTimeout()); + } + SessionHolder.lockAndExecute(globalSession, () -> { + if (globalSession.getStatus() != GlobalStatus.Begin || !globalSession.isTimeout()) { + return false; + } + + LOGGER.info("Global transaction[{}] is timeout and will be rollback.", globalSession.getXid()); + + globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager()); + globalSession.close(); + globalSession.setStatus(GlobalStatus.TimeoutRollbacking); + + globalSession.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager()); + SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(globalSession); + + // transaction timeout and start rollbacking event + MetricsPublisher.postSessionDoingEvent(globalSession, GlobalStatus.TimeoutRollbacking.name(), false, false); + + return true; + }); + }); + if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) { + LOGGER.debug("Global transaction timeout check end. "); + } + + } + + + /** + * Handle retry rollbacking. + */ + protected void handleRetryRollbacking() { + SessionCondition sessionCondition = new SessionCondition(rollbackingStatuses); + sessionCondition.setLazyLoadBranch(true); + Collection rollbackingSessions = + SessionHolder.getRetryRollbackingSessionManager().findGlobalSessions(sessionCondition); + 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.error("Global transaction rollback retry timeout and has removed [{}]", rollbackingSession.getXid()); + + SessionHelper.endRollbackFailed(rollbackingSession, true); + + // rollback retry timeout event + MetricsPublisher.postSessionDoneEvent(rollbackingSession, GlobalStatus.RollbackRetryTimeout, true, false); + + //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() { + SessionCondition retryCommittingSessionCondition = new SessionCondition(retryCommittingStatuses); + retryCommittingSessionCondition.setLazyLoadBranch(true); + Collection committingSessions = + SessionHolder.getRetryCommittingSessionManager().findGlobalSessions(retryCommittingSessionCondition); + 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()); + + // commit retry timeout event + MetricsPublisher.postSessionDoneEvent(committingSession, GlobalStatus.CommitRetryTimeout, true, false); + + //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()); + } + }); + } + + /** + * Handle async committing. + */ + protected void handleAsyncCommitting() { + SessionCondition sessionCondition = new SessionCondition(GlobalStatus.AsyncCommitting); + Collection asyncCommittingSessions = + SessionHolder.getAsyncCommittingSessionManager().findGlobalSessions(sessionCondition); + if (CollectionUtils.isEmpty(asyncCommittingSessions)) { + return; + } + SessionHelper.forEach(asyncCommittingSessions, asyncCommittingSession -> { + try { + 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 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 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()); + } + } + } + + private boolean isRetryTimeout(long now, long timeout, long beginTime) { + return timeout >= ALWAYS_RETRY_BOUNDARY && now - beginTime > timeout; + } + + /** + * Init. + */ + public void init() { + retryRollbacking.scheduleAtFixedRate( + () -> SessionHolder.distributedLockAndExecute(RETRY_ROLLBACKING, this::handleRetryRollbacking), 0, + ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS); + + retryCommitting.scheduleAtFixedRate( + () -> SessionHolder.distributedLockAndExecute(RETRY_COMMITTING, this::handleRetryCommitting), 0, + COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS); + + asyncCommitting.scheduleAtFixedRate( + () -> SessionHolder.distributedLockAndExecute(ASYNC_COMMITTING, this::handleAsyncCommitting), 0, + ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS); + + timeoutCheck.scheduleAtFixedRate( + () -> SessionHolder.distributedLockAndExecute(TX_TIMEOUT_CHECK, this::timeoutCheck), 0, + TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS); + + undoLogDelete.scheduleAtFixedRate( + () -> SessionHolder.distributedLockAndExecute(UNDOLOG_DELETE, this::undoLogDelete), + 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(); + undoLogDelete.shutdown(); + branchRemoveExecutor.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); + undoLogDelete.awaitTermination(TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS, TimeUnit.MILLISECONDS); + branchRemoveExecutor.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. third destroy SessionHolder + SessionHolder.destroy(); + instance = null; + } + + /** + * only used for mock test + * @param remotingServer + */ + public void setRemotingServer(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + } + + /** + * the task to remove branchSession + */ + static class BranchRemoveTask implements Runnable { + + /** + * the globalSession + */ + private final GlobalSession globalSession; + + /** + * the branchSession + */ + private final BranchSession branchSession; + + /** + * If you use this construct, the task will remove the branchSession provided by the parameter + * @param globalSession the globalSession + */ + public BranchRemoveTask(GlobalSession globalSession, BranchSession branchSession) { + this.globalSession = globalSession; + this.branchSession = branchSession; + } + + /** + * If you use this construct, the task will remove all branchSession + * @param globalSession the globalSession + */ + public BranchRemoveTask(GlobalSession globalSession) { + this.globalSession = globalSession; + this.branchSession = null; + } + + @Override + public void run() { + if (globalSession == null) { + return; + } + try { + MDC.put(RootContext.MDC_KEY_XID, globalSession.getXid()); + if (branchSession != null) { + doRemove(branchSession); + } else { + globalSession.getSortedBranches().forEach(this::doRemove); + } + } catch (Exception unKnowException) { + LOGGER.error("Asynchronous delete branchSession error, xid = {}", globalSession.getXid(), unKnowException); + } finally { + MDC.remove(RootContext.MDC_KEY_XID); + } + } + + private void doRemove(BranchSession bt) { + try { + MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(bt.getBranchId())); + globalSession.removeBranch(bt); + LOGGER.info("Asynchronous delete branchSession successfully, xid = {}, branchId = {}", + globalSession.getXid(), bt.getBranchId()); + } catch (TransactionException transactionException) { + LOGGER.error("Asynchronous delete branchSession error, xid = {}, branchId = {}", + globalSession.getXid(), bt.getBranchId(), transactionException); + } finally { + MDC.remove(RootContext.MDC_KEY_BRANCH_ID); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCore.java new file mode 100644 index 00000000..42b88722 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/DefaultCore.java @@ -0,0 +1,392 @@ +/* + * 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.DefaultValues; +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.context.RootContext; +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.metrics.MetricsPublisher; +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.constants.ConfigurationKeys.XAER_NOTA_RETRY_TIMEOUT; +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 static final int RETRY_XAER_NOTA_TIMEOUT = ConfigurationFactory.getInstance().getInt(XAER_NOTA_RETRY_TIMEOUT, + DefaultValues.DEFAULT_XAER_NOTA_RETRY_TIMEOUT); + + private static Map coreMap = new ConcurrentHashMap<>(); + + /** + * get the Default core. + * + * @param remotingServer the remoting server + */ + public DefaultCore(RemotingServer remotingServer) { + List 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 + MetricsPublisher.postSessionDoingEvent(session, false); + + 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, () -> { + if (globalSession.getStatus() == GlobalStatus.Begin) { + // Highlight: Firstly, close the session, then no more branch can be registered. + globalSession.closeAndClean(); + if (globalSession.canBeCommittedAsync()) { + globalSession.asyncCommit(); + MetricsPublisher.postSessionDoneEvent(globalSession, GlobalStatus.Committed, false, false); + return false; + } else { + globalSession.changeGlobalStatus(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 + MetricsPublisher.postSessionDoingEvent(globalSession, retrying); + + 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) { + SessionHelper.removeBranch(globalSession, branchSession, !retrying); + return CONTINUE; + } + try { + BranchStatus branchStatus = getCore(branchSession.getBranchType()).branchCommit(globalSession, branchSession); + if (isXaerNotaTimeout(globalSession,branchStatus)) { + LOGGER.info("Commit branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); + branchStatus = BranchStatus.PhaseTwo_Committed; + } + switch (branchStatus) { + case PhaseTwo_Committed: + SessionHelper.removeBranch(globalSession, branchSession, !retrying); + return CONTINUE; + case PhaseTwo_CommitFailed_Unretryable: + //not at branch + SessionHelper.endCommitFailed(globalSession, retrying); + 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 (!retrying) { + //contains not AT branch + globalSession.setStatus(GlobalStatus.Committed); + } + } + // if it succeeds and there is no branch, retrying=true is the asynchronous state when retrying. EndCommitted is + // executed to improve concurrency performance, and the global transaction ends.. + if (success && globalSession.getBranchSessions().isEmpty()) { + SessionHelper.endCommitted(globalSession, retrying); + 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.changeGlobalStatus(GlobalStatus.Rollbacking); + return true; + } + return false; + }); + if (!shouldRollBack) { + return globalSession.getStatus(); + } + + boolean rollbackSuccess = doGlobalRollback(globalSession, false); + return rollbackSuccess ? GlobalStatus.Rollbacked : globalSession.getStatus(); + } + + @Override + public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException { + boolean success = true; + // start rollback event + MetricsPublisher.postSessionDoingEvent(globalSession, retrying); + + 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) { + SessionHelper.removeBranch(globalSession, branchSession, !retrying); + return CONTINUE; + } + try { + BranchStatus branchStatus = branchRollback(globalSession, branchSession); + if (isXaerNotaTimeout(globalSession, branchStatus)) { + LOGGER.info("Rollback branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); + branchStatus = BranchStatus.PhaseTwo_Rollbacked; + } + switch (branchStatus) { + case PhaseTwo_Rollbacked: + SessionHelper.removeBranch(globalSession, branchSession, !retrying); + LOGGER.info("Rollback branch transaction successfully, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId()); + return CONTINUE; + case PhaseTwo_RollbackFailed_Unretryable: + SessionHelper.endRollbackFailed(globalSession, retrying); + 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, lock and branch data residual problems may occur. + // Therefore, execution needs to be delayed here and cannot be executed synchronously. + if (success) { + SessionHelper.endRollbacked(globalSession, retrying); + 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); + } + } + + private boolean isXaerNotaTimeout(GlobalSession globalSession, BranchStatus branchStatus) { + if (BranchStatus.PhaseTwo_CommitFailed_XAER_NOTA_Retryable.equals(branchStatus) || + BranchStatus.PhaseTwo_RollbackFailed_XAER_NOTA_Retryable.equals(branchStatus)) { + return System.currentTimeMillis() > globalSession.getBeginTime() + globalSession.getTimeout() + + Math.max(RETRY_XAER_NOTA_TIMEOUT, globalSession.getTimeout()); + } else { + return false; + } + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorInbound.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorInbound.java new file mode 100644 index 00000000..d8c9932b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorInbound.java @@ -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 { + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorOutbound.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorOutbound.java new file mode 100644 index 00000000..a213bd40 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/coordinator/TransactionCoordinatorOutbound.java @@ -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; + + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/ContainerHelper.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/ContainerHelper.java new file mode 100644 index 00000000..a95242da --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/ContainerHelper.java @@ -0,0 +1,103 @@ +/* + * 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 static io.seata.core.constants.ConfigurationKeys.ENV_SEATA_PORT_KEY; + +/** + * @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_STORE_MODE_KEY = "STORE_MODE"; + private static final String ENV_LOCK_STORE_MODE_KEY = "LOCK_STORE_MODE"; + private static final String ENV_SESSION_STORE_MODE_KEY = "SESSION_STORE_MODE"; + + /** + * 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), 0); + } + + /** + * 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)); + } + + /** + * Gets session store mode from container. + * + * @return the env + */ + public static String getSessionStoreMode() { + return StringUtils.trimToNull(System.getenv(ENV_SESSION_STORE_MODE_KEY)); + } + + /** + * Gets lock store mode from container. + * + * @return the env + */ + public static String getLockStoreMode() { + return StringUtils.trimToNull(System.getenv(ENV_LOCK_STORE_MODE_KEY)); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/PortHelper.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/PortHelper.java new file mode 100644 index 00000000..cb5968dc --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/env/PortHelper.java @@ -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.env; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.MapUtil; +import io.seata.common.util.NumberUtils; +import io.seata.common.util.StringUtils; +import org.springframework.util.ResourceUtils; +import org.yaml.snakeyaml.Yaml; + +/** + * @author wang.liang + */ +public class PortHelper { + + public static int getPortFromEnvOrStartup(String[] args) { + int port = 0; + if (args != null && args.length >= 2) { + for (int i = 0; i < args.length; ++i) { + if ("-p".equalsIgnoreCase(args[i]) && i < args.length - 1) { + port = NumberUtils.toInt(args[i + 1], 0); + } + } + } + if (port == 0) { + port = ContainerHelper.getPort(); + } + return port; + } + + /** + * get config from configFile + * -Dspring.config.location > classpath:application.properties > classpath:application.yml + * + * @return + * @throws IOException + */ + public static int getPortFromConfigFile() throws IOException { + + int port = 8080; + File configFile = null; + File startupConfigFile = getConfigFromStartup(); + if (null != startupConfigFile) { + configFile = startupConfigFile; + } else { + try { + File propertiesFile = ResourceUtils.getFile("classpath:application.properties"); + configFile = propertiesFile; + } catch (FileNotFoundException exx) { + File ymlFile = ResourceUtils.getFile("classpath:application.yml"); + configFile = ymlFile; + } + } + InputStream inputStream = null; + try { + inputStream = new FileInputStream(configFile); + String fileName = configFile.getName(); + String portNum = null; + if (fileName.endsWith("yml")) { + Map yamlMap = new Yaml().load(inputStream); + Map configMap = MapUtil.getFlattenedMap(yamlMap); + if (CollectionUtils.isNotEmpty(configMap)) { + Object serverPort = configMap.get("server.port"); + if (null != serverPort) { + portNum = serverPort.toString(); + } + } + } else { + Properties properties = new Properties(); + properties.load(inputStream); + portNum = properties.getProperty("server.port"); + } + if (null != portNum) { + try { + port = Integer.parseInt(portNum); + } catch (NumberFormatException exx) { + //ignore + } + } + } finally { + if (null != inputStream) { + inputStream.close(); + } + } + return port; + + } + private static File getConfigFromStartup() { + + String configLocation = System.getProperty("spring.config.location"); + if (StringUtils.isNotBlank(configLocation)) { + try { + File configFile = ResourceUtils.getFile(configLocation); + if (!configFile.isFile()) { + return null; + } + String fileName = configFile.getName(); + if (!(fileName.endsWith("yml") || fileName.endsWith("properties"))) { + return null; + } + return configFile; + } catch (FileNotFoundException e) { + return null; + } + } + return null; + + } + + +} + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/event/EventBusManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/event/EventBusManager.java new file mode 100644 index 00000000..5500bc75 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/event/EventBusManager.java @@ -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",true); + } + + public static EventBus get() { + return SingletonHolder.INSTANCE; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/AbstractLockManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/AbstractLockManager.java new file mode 100644 index 00000000..1334d39b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/AbstractLockManager.java @@ -0,0 +1,197 @@ +/* + * 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.Collections; +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.core.model.LockStatus; +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 { + return acquireLock(branchSession, true, false); + } + + @Override + public boolean acquireLock(BranchSession branchSession, boolean autoCommit, boolean skipCheckLock) 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 locks = collectRowLocks(branchSession); + if (CollectionUtils.isEmpty(locks)) { + // no lock + return true; + } + return getLocker(branchSession).acquireLock(locks, autoCommit, skipCheckLock); + } + + @Override + public boolean releaseLock(BranchSession branchSession) throws TransactionException { + if (branchSession == null) { + throw new IllegalArgumentException("branchSession can't be null for memory/file locker."); + } + List 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 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); + + @Override + public List collectRowLocks(BranchSession branchSession) { + if (branchSession == null || StringUtils.isBlank(branchSession.getLockKey())) { + return Collections.emptyList(); + } + + String lockKey = branchSession.getLockKey(); + String resourceId = branchSession.getResourceId(); + String xid = branchSession.getXid(); + long transactionId = branchSession.getTransactionId(); + long branchId = branchSession.getBranchId(); + + return collectRowLocks(lockKey, resourceId, xid, transactionId, branchId); + } + + /** + * Collect row locks list. + * + * @param lockKey the lock key + * @param resourceId the resource id + * @param xid the xid + * @return the list + */ + protected List 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 collectRowLocks(String lockKey, String resourceId, String xid, Long transactionId, + Long branchID) { + List locks = new ArrayList<>(); + + 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; + } + + @Override + public void updateLockStatus(String xid, LockStatus lockStatus) { + this.getLocker().updateLockStatus(xid, lockStatus); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockManager.java new file mode 100644 index 00000000..c35338e9 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockManager.java @@ -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.lock; + +import java.util.List; + +import io.seata.core.exception.TransactionException; +import io.seata.core.lock.RowLock; +import io.seata.core.model.LockStatus; +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; + + /** + * Acquire lock boolean. + * + * @param branchSession the branch session + * @param autoCommit the auto commit + * @param skipCheckLock whether skip check lock or not + * @return the boolean + * @throws TransactionException the transaction exception + */ + boolean acquireLock(BranchSession branchSession, boolean autoCommit, boolean skipCheckLock) 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; + + /** + * Collect row locks list.` + * + * @param branchSession the branch session + * @return the list + */ + List collectRowLocks(BranchSession branchSession); + + /** + * update lock status. + * @param xid the xid + * @param lockStatus the lock status + * @throws TransactionException the transaction exception + * + */ + void updateLockStatus(String xid, LockStatus lockStatus) throws TransactionException; + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockerManagerFactory.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockerManagerFactory.java new file mode 100644 index 00000000..65303260 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/LockerManagerFactory.java @@ -0,0 +1,73 @@ +/* + * 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.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.store.StoreMode; + +import static io.seata.common.DefaultValues.SERVER_DEFAULT_STORE_MODE; + +/** + * The type Lock manager factory. + * + * @author sharajava + */ +public class LockerManagerFactory { + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + /** + * the lock manager + */ + private static volatile LockManager LOCK_MANAGER; + + /** + * Get lock manager. + * + * @return the lock manager + */ + public static LockManager getLockManager() { + if (LOCK_MANAGER == null) { + init(); + } + return LOCK_MANAGER; + } + + public static void init() { + init(null); + } + + public static void init(String lockMode) { + if (LOCK_MANAGER == null) { + synchronized (LockerManagerFactory.class) { + if (LOCK_MANAGER == null) { + if (StringUtils.isBlank(lockMode)) { + lockMode = CONFIG.getConfig(ConfigurationKeys.STORE_LOCK_MODE, + CONFIG.getConfig(ConfigurationKeys.STORE_MODE, SERVER_DEFAULT_STORE_MODE)); + } + if (StoreMode.contains(lockMode)) { + LOCK_MANAGER = EnhancedServiceLoader.load(LockManager.class, lockMode); + } + } + } + } + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/distributed/DistributedLockerFactory.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/distributed/DistributedLockerFactory.java new file mode 100644 index 00000000..1999b997 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/lock/distributed/DistributedLockerFactory.java @@ -0,0 +1,69 @@ +/* + * 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.distributed; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.core.store.DefaultDistributedLocker; +import io.seata.core.store.DistributedLocker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author zhongxiang.wang + * @description Distributed locker factory + */ +public class DistributedLockerFactory { + + /** + * The constant LOGGER. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(DistributedLockerFactory.class); + + private static volatile DistributedLocker DISTRIBUTED_LOCKER = null; + + /** + * Get the distributed locker by lockerType + * + * @param lockerType the locker type + * @return the distributed locker + */ + public static DistributedLocker getDistributedLocker(String lockerType) { + if (DISTRIBUTED_LOCKER == null) { + synchronized (DistributedLocker.class) { + if (DISTRIBUTED_LOCKER == null) { + DistributedLocker distributedLocker = null; + try { + if (!"file".equals(lockerType)) { + distributedLocker = EnhancedServiceLoader.load(DistributedLocker.class, lockerType); + } + } catch (EnhancedServiceNotFoundException ex) { + LOGGER.error("Get distributed locker failed: {}", ex.getMessage(), ex); + } + if (distributedLocker == null) { + distributedLocker = new DefaultDistributedLocker(); + } + DISTRIBUTED_LOCKER = distributedLocker; + } + } + } + return DISTRIBUTED_LOCKER; + } + + public static void cleanLocker() { + DISTRIBUTED_LOCKER = null; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/listener/SystemPropertyLoggerContextListener.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/listener/SystemPropertyLoggerContextListener.java new file mode 100644 index 00000000..c0c96598 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/listener/SystemPropertyLoggerContextListener.java @@ -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("RPC_PORT", System.getProperty(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL)); + + 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) { + } +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/ExtendedWhitespaceThrowableProxyConverter.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/ExtendedWhitespaceThrowableProxyConverter.java new file mode 100644 index 00000000..6995f6f3 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/ExtendedWhitespaceThrowableProxyConverter.java @@ -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-xxx.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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/appender/EnhancedLogstashEncoder.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/appender/EnhancedLogstashEncoder.java new file mode 100644 index 00000000..665affe7 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/logging/logback/appender/EnhancedLogstashEncoder.java @@ -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); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MeterIdConstants.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MeterIdConstants.java new file mode 100644 index 00000000..29d55af2 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MeterIdConstants.java @@ -0,0 +1,108 @@ +/* + * 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 COUNTER_AFTER_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_AFTER_ROLLBACKED_KEY); + + Id COUNTER_AFTER_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_AFTER_COMMITTED_KEY); + + + 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 SUMMARY_FAILED = 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_FAILED); + + Id SUMMARY_TWO_PHASE_TIMEOUT = 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_TWO_PHASE_TIMEOUT); + + Id SUMMARY_AFTER_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_AFTER_ROLLBACKED_KEY); + + Id SUMMARY_AFTER_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_AFTER_COMMITTED_KEY); + + 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); + + Id TIMER_FAILED = 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_FAILED); + + Id TIMER_AFTER_ROLLBACKED = 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_AFTER_ROLLBACKED_KEY); + + Id TIMER_AFTER_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_AFTER_COMMITTED_KEY); + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsManager.java new file mode 100644 index 00000000..1ea9055c --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsManager.java @@ -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 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)); + } + } + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsPublisher.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsPublisher.java new file mode 100644 index 00000000..393583c2 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsPublisher.java @@ -0,0 +1,98 @@ +/* + * 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.core.event.EventBus; +import io.seata.core.event.GlobalTransactionEvent; +import io.seata.core.model.GlobalStatus; +import io.seata.server.event.EventBusManager; +import io.seata.server.session.GlobalSession; + +/** + * The type Metrics publisher. + * + * @author slievrly + */ +public class MetricsPublisher { + + private static final EventBus EVENT_BUS = EventBusManager.get(); + + /** + * post end event + * + * @param globalSession the global session + * @param retryGlobal the retry global + * @param retryBranch the retry branch + */ + public static void postSessionDoneEvent(final GlobalSession globalSession, boolean retryGlobal, + boolean retryBranch) { + postSessionDoneEvent(globalSession, globalSession.getStatus(), retryGlobal, retryBranch); + } + + /** + * post end event (force specified state) + * + * @param globalSession the global session + * @param status the global status + * @param retryGlobal the retry global + * @param retryBranch the retry branch + */ + public static void postSessionDoneEvent(final GlobalSession globalSession, GlobalStatus status, boolean retryGlobal, + boolean retryBranch) { + postSessionDoneEvent(globalSession, status.name(), retryGlobal, globalSession.getBeginTime(), retryBranch); + } + + /** + * Post session done event. + * + * @param globalSession the global session + * @param status the status + * @param retryGlobal the retry global + * @param beginTime the begin time + * @param retryBranch the retry branch + */ + public static void postSessionDoneEvent(final GlobalSession globalSession, String status, boolean retryGlobal, long beginTime, boolean retryBranch) { + EVENT_BUS.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC, + globalSession.getTransactionName(), globalSession.getApplicationId(), + globalSession.getTransactionServiceGroup(), beginTime, System.currentTimeMillis(), status, retryGlobal, retryBranch)); + } + + /** + * Post session doing event. + * + * @param globalSession the global session + * @param retryGlobal the retry global + */ + public static void postSessionDoingEvent(final GlobalSession globalSession, boolean retryGlobal) { + postSessionDoingEvent(globalSession, globalSession.getStatus().name(), retryGlobal, false); + } + + /** + * Post session doing event. + * + * @param globalSession the global session + * @param status the status + * @param retryGlobal the retry global + * @param retryBranch the retry branch + */ + public static void postSessionDoingEvent(final GlobalSession globalSession, String status, boolean retryGlobal, + boolean retryBranch) { + EVENT_BUS.post(new GlobalTransactionEvent(globalSession.getTransactionId(), GlobalTransactionEvent.ROLE_TC, + globalSession.getTransactionName(), globalSession.getApplicationId(), + globalSession.getTransactionServiceGroup(), globalSession.getBeginTime(), null, status, retryGlobal, retryBranch)); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsSubscriber.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsSubscriber.java new file mode 100644 index 00000000..727b1993 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/metrics/MetricsSubscriber.java @@ -0,0 +1,217 @@ +/* + * 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.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +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 io.seata.server.event.EventBusManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.metrics.IdConstants.APP_ID_KEY; +import static io.seata.metrics.IdConstants.GROUP_KEY; +import static io.seata.metrics.IdConstants.STATUS_VALUE_AFTER_COMMITTED_KEY; +import static io.seata.metrics.IdConstants.STATUS_VALUE_AFTER_ROLLBACKED_KEY; + +/** + * Event subscriber for metrics + * + * @author zhengyangyong + */ +public class MetricsSubscriber { + + private static final Logger LOGGER = LoggerFactory.getLogger(MetricsSubscriber.class); + private final Registry registry; + + private final Map> consumers; + + public MetricsSubscriber(Registry registry) { + this.registry = registry; + consumers = new HashMap<>(); + consumers.put(GlobalStatus.Begin.name(), this::processGlobalStatusBegin); + consumers.put(GlobalStatus.Committed.name(), this::processGlobalStatusCommitted); + consumers.put(GlobalStatus.Rollbacked.name(), this::processGlobalStatusRollbacked); + + consumers.put(GlobalStatus.CommitFailed.name(), this::processGlobalStatusCommitFailed); + consumers.put(GlobalStatus.RollbackFailed.name(), this::processGlobalStatusRollbackFailed); + consumers.put(GlobalStatus.TimeoutRollbacked.name(), this::processGlobalStatusTimeoutRollbacked); + consumers.put(GlobalStatus.TimeoutRollbackFailed.name(), this::processGlobalStatusTimeoutRollbackFailed); + + consumers.put(GlobalStatus.CommitRetryTimeout.name(), this::processGlobalStatusCommitRetryTimeout); + consumers.put(GlobalStatus.RollbackRetryTimeout.name(), this::processGlobalStatusTimeoutRollbackRetryTimeout); + + consumers.put(STATUS_VALUE_AFTER_COMMITTED_KEY, this::processAfterGlobalCommitted); + consumers.put(STATUS_VALUE_AFTER_ROLLBACKED_KEY, this::processAfterGlobalRollbacked); + } + + private void processGlobalStatusBegin(GlobalTransactionEvent event) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("accept new event,xid:{},event:{}", event.getId(), event); + for (Object object : EventBusManager.get().getSubscribers()) { + LOGGER.debug("subscribe:{},threadName:{}", object.toString(), Thread.currentThread().getName()); + } + } + registry.getCounter(MeterIdConstants.COUNTER_ACTIVE.withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + } + + private void processGlobalStatusCommitted(GlobalTransactionEvent event) { + if (event.isRetryGlobal()) { + return; + } + decreaseActive(event); + 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) { + if (event.isRetryGlobal()) { + return; + } + decreaseActive(event); + 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 processAfterGlobalRollbacked(GlobalTransactionEvent event) { + if (event.isRetryGlobal() && event.isRetryBranch()) { + decreaseActive(event); + } + registry.getCounter(MeterIdConstants.COUNTER_AFTER_ROLLBACKED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + registry.getSummary(MeterIdConstants.SUMMARY_AFTER_ROLLBACKED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + registry.getTimer(MeterIdConstants.TIMER_AFTER_ROLLBACKED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())) + .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + } + + private void processAfterGlobalCommitted(GlobalTransactionEvent event) { + if (event.isRetryGlobal() && event.isRetryBranch()) { + decreaseActive(event); + } + registry.getCounter(MeterIdConstants.COUNTER_AFTER_COMMITTED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + registry.getSummary(MeterIdConstants.SUMMARY_AFTER_COMMITTED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + registry.getTimer(MeterIdConstants.TIMER_AFTER_COMMITTED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())) + .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + } + + private void processGlobalStatusCommitFailed(GlobalTransactionEvent event) { + decreaseActive(event); + reportFailed(event); + } + + private void processGlobalStatusRollbackFailed(GlobalTransactionEvent event) { + decreaseActive(event); + reportFailed(event); + } + + private void processGlobalStatusTimeoutRollbacked(GlobalTransactionEvent event) { + decreaseActive(event); + } + + private void processGlobalStatusTimeoutRollbackFailed(GlobalTransactionEvent event) { + decreaseActive(event); + reportTwoPhaseTimeout(event); + } + + private void processGlobalStatusCommitRetryTimeout(GlobalTransactionEvent event) { + decreaseActive(event); + reportTwoPhaseTimeout(event); + } + + private void processGlobalStatusTimeoutRollbackRetryTimeout(GlobalTransactionEvent event) { + decreaseActive(event); + } + + private void decreaseActive(GlobalTransactionEvent event) { + registry.getCounter(MeterIdConstants.COUNTER_ACTIVE + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).decrease(1); + } + + private void reportFailed(GlobalTransactionEvent event) { + registry.getSummary(MeterIdConstants.SUMMARY_FAILED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + registry.getTimer(MeterIdConstants.TIMER_FAILED + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())) + .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + } + + private void reportTwoPhaseTimeout(GlobalTransactionEvent event) { + registry.getSummary(MeterIdConstants.SUMMARY_TWO_PHASE_TIMEOUT + .withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + } + + + + @Subscribe + public void recordGlobalTransactionEventForMetrics(GlobalTransactionEvent event) { + if (registry != null && consumers.containsKey(event.getStatus())) { + consumers.get(event.getStatus()).accept(event); + } + } + + @Override + public boolean equals(Object obj) { + return this.getClass().getName().equals(obj.getClass().getName()); + } + + /** + * PMD check + * SuppressWarnings("checkstyle:EqualsHashCode") + * @return + */ + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/AbstractSessionManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/AbstractSessionManager.java new file mode 100644 index 00000000..2668c2c5 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/AbstractSessionManager.java @@ -0,0 +1,199 @@ +/* + * 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.core.model.LockStatus; +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[{}] SESSION[{}] {}", name, 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[{}] SESSION[{}] {}", name, session, LogOperation.GLOBAL_UPDATE); + } + if (GlobalStatus.Rollbacking == status) { + session.getBranchSessions().forEach(i -> i.setLockStatus(LockStatus.Rollbacking)); + } + writeSession(LogOperation.GLOBAL_UPDATE, session); + } + + @Override + public void removeGlobalSession(GlobalSession session) throws TransactionException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("MANAGER[{}] SESSION[{}] {}", name, 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[{}] SESSION[{}] {}", name, 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[{}] SESSION[{}] {}", name, 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[{}] SESSION[{}] {}", name, 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 onSuccessEnd(GlobalSession globalSession) throws TransactionException { + removeGlobalSession(globalSession); + } + + @Override + public void onFailEnd(GlobalSession globalSession) throws TransactionException { + LOGGER.info("xid:{} fail end, transaction:{}",globalSession.getXid(),globalSession.toString()); + } + + 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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSession.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSession.java new file mode 100644 index 00000000..402701f4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSession.java @@ -0,0 +1,469 @@ +/* + * 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.common.util.CompressUtil; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.LockStatus; +import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.storage.file.lock.FileLocker; +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.LockStatus.Locked; + +/** + * The type Branch session. + * + * @author sharajava + */ +public class BranchSession implements Lockable, Comparable, SessionStorable { + + private static final Logger LOGGER = LoggerFactory.getLogger(BranchSession.class); + + private static final int MAX_BRANCH_SESSION_SIZE = StoreConfig.getMaxBranchSessionSize(); + + private static ThreadLocal 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 LockStatus lockStatus = Locked; + + private ConcurrentMap> 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> getLockHolder() { + return lockHolder; + } + + @Override + public boolean lock() throws TransactionException { + return this.lock(true, false); + } + + public boolean lock(boolean autoCommit, boolean skipCheckLock) throws TransactionException { + if (this.getBranchType().equals(BranchType.AT)) { + return LockerManagerFactory.getLockManager().acquireLock(this, autoCommit, skipCheckLock); + } + return true; + } + + @Override + public boolean unlock() throws TransactionException { + if (this.getBranchType() == BranchType.AT) { + return LockerManagerFactory.getLockManager().releaseLock(this); + } + return true; + } + + public LockStatus getLockStatus() { + return lockStatus; + } + + public void setLockStatus(LockStatus lockStatus) { + this.lockStatus = lockStatus; + } + + @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.put((byte)lockStatus.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()); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSessionHandler.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSessionHandler.java new file mode 100644 index 00000000..3523018b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/BranchSessionHandler.java @@ -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; +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSession.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSession.java new file mode 100644 index 00000000..03445bd4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSession.java @@ -0,0 +1,776 @@ +/* + * 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.List; +import java.util.Optional; +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.core.model.LockStatus; +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 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 final boolean lazyLoadBranch; + + private volatile boolean active = true; + + private List branchSessions; + + private GlobalSessionLock globalSessionLock = new GlobalSessionLock(); + + + /** + * Add boolean. + * + * @param branchSession the branch session + * @return the boolean + */ + public boolean add(BranchSession branchSession) { + if (null != branchSessions) { + return branchSessions.add(branchSession); + } else { + // db and redis no need to deal with + return true; + } + } + + /** + * Remove boolean. + * + * @param branchSession the branch session + * @return the boolean + */ + public boolean remove(BranchSession branchSession) { + return branchSessions.remove(branchSession); + } + + private Set lifecycleListeners = new HashSet<>(); + + /** + * Can be committed async boolean. + * + * @return the boolean + */ + public boolean canBeCommittedAsync() { + List branchSessions = getBranchSessions(); + for (BranchSession branchSession : branchSessions) { + if (!branchSession.canBeCommittedAsync()) { + return false; + } + } + return true; + } + + /** + * Has AT branch + * + * @return the boolean + */ + public boolean hasATBranch() { + List branchSessions = getBranchSessions(); + for (BranchSession branchSession : branchSessions) { + if (branchSession.getBranchType() == BranchType.AT) { + return true; + } + } + return false; + } + + /** + * Is saga type transaction + * + * @return is saga + */ + public boolean isSaga() { + List branchSessions = getBranchSessions(); + 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 changeGlobalStatus(GlobalStatus status) throws TransactionException { + if (GlobalStatus.Rollbacking == status) { + LockerManagerFactory.getLockManager().updateLockStatus(xid, LockStatus.Rollbacking); + } + 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 { + if (isSuccessEnd()) { + // Clean locks first + clean(); + for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { + lifecycleListener.onSuccessEnd(this); + } + } else { + for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { + lifecycleListener.onFailEnd(this); + } + } + } + + public boolean isSuccessEnd() { + if (status == GlobalStatus.Committed || status == GlobalStatus.Rollbacked + || status == GlobalStatus.TimeoutRollbacked) { + return true; + } + return false; + } + + public void clean() throws TransactionException { + 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(); + if (this.hasATBranch()) { + 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); + } + + public void loadBranchs() { + if (branchSessions == null && isLazyLoadBranch()) { + synchronized (this) { + if (branchSessions == null && isLazyLoadBranch()) { + branchSessions = new ArrayList<>(); + Optional.ofNullable(SessionHolder.getRootSessionManager().findGlobalSession(xid, true)) + .ifPresent(globalSession -> branchSessions.addAll(globalSession.getBranchSessions())); + } + } + } + } + + @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 (this) { + List branchSessions = getBranchSessions(); + for (BranchSession branchSession : branchSessions) { + if (branchSession.getBranchId() == branchId) { + return branchSession; + } + } + + return null; + } + } + + /** + * Gets sorted branches. + * + * @return the sorted branches + */ + public List getSortedBranches() { + return new ArrayList<>(getBranchSessions()); + } + + /** + * Gets reverse sorted branches. + * + * @return the reverse sorted branches + */ + public List getReverseSortedBranches() { + List reversed = new ArrayList<>(getBranchSessions()); + Collections.reverse(reversed); + return reversed; + } + + /** + * Instantiates a new Global session. + */ + public GlobalSession() { + this.lazyLoadBranch = false; + } + + /** + * 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 + * @param lazyLoadBranch the lazy load branch + */ + public GlobalSession(String applicationId, String transactionServiceGroup, String transactionName, int timeout, boolean lazyLoadBranch) { + this.transactionId = UUIDGenerator.generateUUID(); + this.status = GlobalStatus.Begin; + this.lazyLoadBranch = lazyLoadBranch; + if (!lazyLoadBranch) { + this.branchSessions = new ArrayList<>(); + } + this.applicationId = applicationId; + this.transactionServiceGroup = transactionServiceGroup; + this.transactionName = transactionName; + this.timeout = timeout; + this.xid = XID.generateXID(transactionId); + } + + /** + * 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(applicationId, transactionServiceGroup, transactionName, timeout, false); + } + + /** + * 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; + } + + public boolean isLazyLoadBranch() { + return lazyLoadBranch; + } + + /** + * 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, false); + 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) { + this.branchSessions = new ArrayList<>(); + 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 getBranchSessions().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 call() throws TransactionException; + } + + public List getBranchSessions() { + loadBranchs(); + return branchSessions; + } + + public void asyncCommit() throws TransactionException { + this.addSessionLifecycleListener(SessionHolder.getAsyncCommittingSessionManager()); + this.setStatus(GlobalStatus.AsyncCommitting); + SessionHolder.getAsyncCommittingSessionManager().addGlobalSession(this); + } + + public void queueToRetryCommit() throws TransactionException { + this.addSessionLifecycleListener(SessionHolder.getRetryCommittingSessionManager()); + this.setStatus(GlobalStatus.CommitRetrying); + SessionHolder.getRetryCommittingSessionManager().addGlobalSession(this); + } + + public void queueToRetryRollback() throws TransactionException { + this.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager()); + GlobalStatus currentStatus = this.getStatus(); + if (SessionHelper.isTimeoutGlobalStatus(currentStatus)) { + this.setStatus(GlobalStatus.TimeoutRollbackRetrying); + } else { + this.setStatus(GlobalStatus.RollbackRetrying); + } + SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(this); + } + + @Override + public String toString() { + return "GlobalSession{" + "xid='" + xid + '\'' + ", transactionId=" + transactionId + ", status=" + status + + ", applicationId='" + applicationId + '\'' + ", transactionServiceGroup='" + transactionServiceGroup + + '\'' + ", transactionName='" + transactionName + '\'' + ", timeout=" + timeout + ", beginTime=" + + beginTime + ", applicationData='" + applicationData + '\'' + ", lazyLoadBranch=" + lazyLoadBranch + + ", active=" + active + ", branchSessions=" + branchSessions + ", globalSessionLock=" + globalSessionLock + + ", lifecycleListeners=" + lifecycleListeners + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSessionHandler.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSessionHandler.java new file mode 100644 index 00000000..515b155b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/GlobalSessionHandler.java @@ -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; +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Lockable.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Lockable.java new file mode 100644 index 00000000..d023e632 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Lockable.java @@ -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; +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Reloadable.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Reloadable.java new file mode 100644 index 00000000..5701c69d --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/Reloadable.java @@ -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(); +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionCondition.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionCondition.java new file mode 100644 index 00000000..3b946ea7 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionCondition.java @@ -0,0 +1,144 @@ +/* + * 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; + private boolean lazyLoadBranch; + + /** + * 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; + this.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; + } + + public boolean isLazyLoadBranch() { + return lazyLoadBranch; + } + + public void setLazyLoadBranch(boolean lazyLoadBranch) { + this.lazyLoadBranch = lazyLoadBranch; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHelper.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHelper.java new file mode 100644 index 00000000..4b2196e7 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHelper.java @@ -0,0 +1,294 @@ +/* + * 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 java.util.Objects; + +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.context.RootContext; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchType; +import io.seata.core.model.GlobalStatus; +import io.seata.core.store.StoreMode; +import io.seata.metrics.IdConstants; +import io.seata.server.UUIDGenerator; +import io.seata.server.coordinator.DefaultCoordinator; +import io.seata.server.metrics.MetricsPublisher; +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); + + /** + * The constant CONFIG. + */ + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private static final Boolean ENABLE_BRANCH_ASYNC_REMOVE = CONFIG.getBoolean( + ConfigurationKeys.ENABLE_BRANCH_ASYNC_REMOVE, false); + + /** + * The instance of DefaultCoordinator + */ + private static final DefaultCoordinator COORDINATOR = DefaultCoordinator.getInstance(); + + private static final boolean DELAY_HANDLE_SESSION = + !StringUtils.equalsIgnoreCase(ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_SESSION_MODE, + ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE)), StoreMode.FILE.getName()); + + 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 + * @param retryGlobal the retry global + * @throws TransactionException the transaction exception + */ + public static void endCommitted(GlobalSession globalSession, boolean retryGlobal) throws TransactionException { + if (retryGlobal || !DELAY_HANDLE_SESSION) { + long beginTime = System.currentTimeMillis(); + boolean retryBranch = globalSession.getStatus() == GlobalStatus.CommitRetrying; + globalSession.changeGlobalStatus(GlobalStatus.Committed); + globalSession.end(); + if (!DELAY_HANDLE_SESSION) { + MetricsPublisher.postSessionDoneEvent(globalSession, false, false); + } + MetricsPublisher.postSessionDoneEvent(globalSession, IdConstants.STATUS_VALUE_AFTER_COMMITTED_KEY, true, + beginTime, retryBranch); + } else { + MetricsPublisher.postSessionDoneEvent(globalSession, false, false); + } + } + + /** + * End commit failed. + * + * @param globalSession the global session + * @param retryGlobal the retry global + * @throws TransactionException the transaction exception + */ + public static void endCommitFailed(GlobalSession globalSession, boolean retryGlobal) throws TransactionException { + globalSession.changeGlobalStatus(GlobalStatus.CommitFailed); + LOGGER.error("The Global session {} has changed the status to {}, need to be handled it manually.", + globalSession.getXid(), globalSession.getStatus()); + + globalSession.end(); + MetricsPublisher.postSessionDoneEvent(globalSession, retryGlobal, false); + } + + /** + * End rollbacked. + * + * @param globalSession the global session + * @param retryGlobal the retry global + * @throws TransactionException the transaction exception + */ + public static void endRollbacked(GlobalSession globalSession, boolean retryGlobal) throws TransactionException { + if (retryGlobal || !DELAY_HANDLE_SESSION) { + long beginTime = System.currentTimeMillis(); + GlobalStatus currentStatus = globalSession.getStatus(); + boolean retryBranch = + currentStatus == GlobalStatus.TimeoutRollbackRetrying || currentStatus == GlobalStatus.RollbackRetrying; + if (isTimeoutGlobalStatus(currentStatus)) { + globalSession.changeGlobalStatus(GlobalStatus.TimeoutRollbacked); + } else { + globalSession.changeGlobalStatus(GlobalStatus.Rollbacked); + } + globalSession.end(); + if (!DELAY_HANDLE_SESSION) { + MetricsPublisher.postSessionDoneEvent(globalSession, false, false); + } + MetricsPublisher.postSessionDoneEvent(globalSession, IdConstants.STATUS_VALUE_AFTER_ROLLBACKED_KEY, true, + beginTime, retryBranch); + } else { + MetricsPublisher.postSessionDoneEvent(globalSession, false, false); + } + } + + /** + * End rollback failed. + * + * @param globalSession the global session + * @param retryGlobal the retry global + * @throws TransactionException the transaction exception + */ + public static void endRollbackFailed(GlobalSession globalSession, boolean retryGlobal) throws TransactionException { + GlobalStatus currentStatus = globalSession.getStatus(); + if (isTimeoutGlobalStatus(currentStatus)) { + globalSession.changeGlobalStatus(GlobalStatus.TimeoutRollbackFailed); + } else { + globalSession.changeGlobalStatus(GlobalStatus.RollbackFailed); + } + LOGGER.error("The Global session {} has changed the status to {}, need to be handled it manually.", globalSession.getXid(), globalSession.getStatus()); + globalSession.end(); + MetricsPublisher.postSessionDoneEvent(globalSession, retryGlobal, false); + } + + 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 sessions, GlobalSessionHandler handler) { + if (CollectionUtils.isEmpty(sessions)) { + return; + } + sessions.parallelStream().forEach(globalSession -> { + 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 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; + } + + + /** + * remove branchSession from globalSession + * @param globalSession the globalSession + * @param branchSession the branchSession + * @param isAsync if asynchronous remove + */ + public static void removeBranch(GlobalSession globalSession, BranchSession branchSession, boolean isAsync) + throws TransactionException { + if (Objects.equals(Boolean.TRUE, ENABLE_BRANCH_ASYNC_REMOVE) && isAsync) { + COORDINATOR.doBranchRemoveAsync(globalSession, branchSession); + } else { + globalSession.removeBranch(branchSession); + } + } + + /** + * remove branchSession from globalSession + * @param globalSession the globalSession + * @param isAsync if asynchronous remove + */ + public static void removeAllBranch(GlobalSession globalSession, boolean isAsync) + throws TransactionException { + List branchSessions = globalSession.getSortedBranches(); + if (branchSessions == null || branchSessions.isEmpty()) { + return; + } + if (Objects.equals(Boolean.TRUE, ENABLE_BRANCH_ASYNC_REMOVE) && isAsync) { + COORDINATOR.doBranchRemoveAllAsync(globalSession); + } else { + for (BranchSession branchSession : branchSessions) { + globalSession.removeBranch(branchSession); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHolder.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHolder.java new file mode 100644 index 00000000..f22153cd --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionHolder.java @@ -0,0 +1,424 @@ +/* + * 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.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import io.seata.common.ConfigurationKeys; +import io.seata.core.model.LockStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.seata.common.XID; +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.exception.TransactionException; +import io.seata.core.model.GlobalStatus; +import io.seata.core.store.DistributedLockDO; +import io.seata.core.store.DistributedLocker; +import io.seata.server.lock.distributed.DistributedLockerFactory; +import io.seata.core.store.StoreMode; + +import static io.seata.common.DefaultValues.SERVER_DEFAULT_STORE_MODE; + +/** + * 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"; + + /** + * The redis distributed lock expire time + */ + private static long DISTRIBUTED_LOCK_EXPIRE_TIME = CONFIG.getLong(ConfigurationKeys.DISTRIBUTED_LOCK_EXPIRE_TIME, 10000); + + 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; + + private static DistributedLocker DISTRIBUTED_LOCKER; + + /** + * Init. + * + * @param mode the store mode: file, db, redis + * @throws IOException the io exception + */ + public static void init(String mode) { + if (StringUtils.isBlank(mode)) { + mode = CONFIG.getConfig(ConfigurationKeys.STORE_SESSION_MODE, + CONFIG.getConfig(ConfigurationKeys.STORE_MODE, SERVER_DEFAULT_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}); + + DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(StoreMode.DB.getName()); + } 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 = ROOT_SESSION_MANAGER; + RETRY_COMMITTING_SESSION_MANAGER = ROOT_SESSION_MANAGER; + RETRY_ROLLBACKING_SESSION_MANAGER = ROOT_SESSION_MANAGER; + + DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(StoreMode.FILE.getName()); + } 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}); + + DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(StoreMode.REDIS.getName()); + } else { + // unknown store + throw new IllegalArgumentException("unknown store mode:" + mode); + } + reload(storeMode); + } + + //region reload + + /** + * Reload. + * + * @param storeMode the mode of store + */ + protected static void reload(StoreMode storeMode) { + + if (ROOT_SESSION_MANAGER instanceof Reloadable) { + ((Reloadable) ROOT_SESSION_MANAGER).reload(); + } + + if (storeMode == StoreMode.FILE) { + Collection allSessions = ROOT_SESSION_MANAGER.allSessions(); + if (CollectionUtils.isNotEmpty(allSessions)) { + for (GlobalSession globalSession : allSessions) { + GlobalStatus globalStatus = globalSession.getStatus(); + switch (globalStatus) { + case UnKnown: + case Committed: + case CommitFailed: + case Rollbacked: + case RollbackFailed: + case TimeoutRollbacked: + case TimeoutRollbackFailed: + case Finished: + removeInErrorState(globalSession); + break; + case AsyncCommitting: + queueToAsyncCommitting(globalSession); + break; + case Committing: + case CommitRetrying: + queueToRetryCommit(globalSession); + break; + default: { + lockBranchSessions(globalSession.getSortedBranches()); + switch (globalStatus) { + case Rollbacking: + case RollbackRetrying: + case TimeoutRollbacking: + case TimeoutRollbackRetrying: + globalSession.getBranchSessions().parallelStream() + .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); + queueToRetryRollback(globalSession); + break; + case Begin: + globalSession.setActive(true); + break; + default: + LOGGER.error("Could not handle the global session, xid: {}", globalSession.getXid()); + throw new ShouldNeverHappenException("NOT properly handled " + globalStatus); + } + break; + } + } + } + } + } else { + // Redis, db and so on + CompletableFuture.runAsync(() -> { + SessionCondition searchCondition = new SessionCondition(GlobalStatus.UnKnown, GlobalStatus.Committed, + GlobalStatus.Rollbacked, GlobalStatus.TimeoutRollbacked, GlobalStatus.Finished); + searchCondition.setLazyLoadBranch(true); + + long now = System.currentTimeMillis(); + List errorStatusGlobalSessions = ROOT_SESSION_MANAGER.findGlobalSessions(searchCondition); + while (!CollectionUtils.isEmpty(errorStatusGlobalSessions)) { + for (GlobalSession errorStatusGlobalSession : errorStatusGlobalSessions) { + if (errorStatusGlobalSession.getBeginTime() >= now) { + // Exit when the global transaction begin after the instance started + return; + } + + removeInErrorState(errorStatusGlobalSession); + } + + // Load the next part + errorStatusGlobalSessions = ROOT_SESSION_MANAGER.findGlobalSessions(searchCondition); + } + }); + } + } + + 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(List 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 + */ + @Deprecated + 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 + */ + @Deprecated + 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 + */ + @Deprecated + 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 lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable lockCallable) + throws TransactionException { + return getRootSessionManager().lockAndExecute(globalSession, lockCallable); + } + + /** + * acquire lock + * + * @param lockKey the lock key, should be distinct for each lock + * @return the boolean + */ + public static boolean acquireDistributedLock(String lockKey) { + return DISTRIBUTED_LOCKER.acquireLock(new DistributedLockDO(lockKey, XID.getIpAddressAndPort(), DISTRIBUTED_LOCK_EXPIRE_TIME)); + } + + /** + * release lock + * + * @return the boolean + */ + public static boolean releaseDistributedLock(String lockKey) { + return DISTRIBUTED_LOCKER.releaseLock(new DistributedLockDO(lockKey, XID.getIpAddressAndPort(), DISTRIBUTED_LOCK_EXPIRE_TIME)); + } + + /** + * Execute the function after get the distribute lock + * + * @param key the distribute lock key + * @param func the function to be call + * @return whether the func be call + */ + public static boolean distributedLockAndExecute(String key, NoArgsFunc func) { + boolean lock = false; + try { + if (lock = acquireDistributedLock(key)) { + func.call(); + } + } catch (Exception e) { + LOGGER.info("Exception running function with key = {}", key, e); + } finally { + if (lock) { + try { + SessionHolder.releaseDistributedLock(key); + } catch (Exception ex) { + LOGGER.warn("release distribute lock failure, message = {}", ex.getMessage(), ex); + } + } + } + return lock; + } + + public static void destroy() { + if (ROOT_SESSION_MANAGER != null) { + ROOT_SESSION_MANAGER.destroy(); + } + } + + @FunctionalInterface + public interface NoArgsFunc { + void call(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycle.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycle.java new file mode 100644 index 00000000..05cd87a3 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycle.java @@ -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 changeGlobalStatus(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; +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycleListener.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycleListener.java new file mode 100644 index 00000000..693d5b69 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionLifecycleListener.java @@ -0,0 +1,98 @@ +/* + * 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 onSuccessEnd(GlobalSession globalSession) throws TransactionException; + + /** + * On fail end. + * + * @param globalSession the global session + * @throws TransactionException the transaction exception + */ + void onFailEnd(GlobalSession globalSession) throws TransactionException; +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionManager.java new file mode 100644 index 00000000..6aaba67a --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/session/SessionManager.java @@ -0,0 +1,125 @@ +/* + * 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 allSessions(); + + /** + * Find global sessions list. + * + * @param condition the condition + * @return the list + */ + List findGlobalSessions(SessionCondition condition); + + /** + * lock and execute + * + * @param globalSession the global session + * @param lockCallable the lock Callable + * @return the value + */ + T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable lockCallable) + throws TransactionException; +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/SessionConverter.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/SessionConverter.java new file mode 100644 index 00000000..61170d03 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/SessionConverter.java @@ -0,0 +1,210 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Collections; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.server.console.vo.BranchSessionVO; +import io.seata.server.console.vo.GlobalSessionVO; +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; +import org.springframework.beans.BeanUtils; + +/** + * The session converter + * + * @author wangzhongxiang + * @author doubleDimple + */ +public class SessionConverter { + + public static GlobalSession convertGlobalSession(GlobalTransactionDO globalTransactionDO, boolean lazyLoadBranch) { + if (globalTransactionDO == null) { + return null; + } + GlobalSession session = new GlobalSession(globalTransactionDO.getApplicationId(), + globalTransactionDO.getTransactionServiceGroup(), + globalTransactionDO.getTransactionName(), + globalTransactionDO.getTimeout(), lazyLoadBranch); + 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 GlobalSession convertGlobalSession(GlobalTransactionDO globalTransactionDO) { + return convertGlobalSession(globalTransactionDO, false); + } + + 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; + } + + public static void convertToGlobalSessionVo(List result, List globalSessions) { + if (CollectionUtils.isNotEmpty(globalSessions)) { + for (GlobalSession globalSession : globalSessions) { + GlobalSessionVO globalSessionVO = new GlobalSessionVO(); + BeanUtils.copyProperties(globalSession,globalSessionVO); + globalSessionVO.setStatus(globalSession.getStatus().getCode()); + globalSessionVO.setTimeout(Long.valueOf(globalSession.getTimeout())); + globalSessionVO.setBranchSessionVOs(converToBranchSession(globalSession.getBranchSessions())); + result.add(globalSessionVO); + } + } + } + + public static Set converToBranchSession(List branchSessions) { + Set branchSessionVOs = new HashSet<>(branchSessions.size()); + if (CollectionUtils.isNotEmpty(branchSessions)) { + for (BranchSession branchSession : branchSessions) { + BranchSessionVO branchSessionVONew = new BranchSessionVO(); + BeanUtils.copyProperties(branchSession,branchSessionVONew); + + branchSessionVONew.setBranchType(branchSession.getBranchType().name()); + branchSessionVONew.setStatus(branchSession.getStatus().getCode()); + branchSessionVOs.add(branchSessionVONew); + } + } + return branchSessionVOs; + } + + /** + * convert GlobalSession to GlobalSessionVO + * + * @param filteredSessions the GlobalSession list + * @return the GlobalSessionVO list + */ + public static List convertGlobalSession(List filteredSessions) { + + if (CollectionUtils.isEmpty(filteredSessions)) { + return Collections.emptyList(); + } + + final ArrayList result = new ArrayList<>(filteredSessions.size()); + + for (GlobalSession session : filteredSessions) { + result.add(new GlobalSessionVO( + session.getXid(), + session.getTransactionId(), + session.getStatus().getCode(), + session.getApplicationId(), + session.getTransactionServiceGroup(), + session.getTransactionName(), + (long) session.getTimeout(), + session.getBeginTime(), + session.getApplicationData(), + convertBranchSession(session.getBranchSessions()) + )); + } + return result; + } + + /** + * convert BranchSession to BranchSessionVO + * + * @param branchSessions the BranchSession list + * @return the BranchSessionVO list + */ + public static Set convertBranchSession(List branchSessions) { + + if (CollectionUtils.isEmpty(branchSessions)) { + return Collections.emptySet(); + } + + final Set result = new HashSet<>(branchSessions.size()); + + for (BranchSession session : branchSessions) { + result.add(new BranchSessionVO( + session.getXid(), + session.getTransactionId(), + session.getBranchId(), + session.getResourceGroupId(), + session.getResourceId(), + session.getBranchType().name(), + session.getStatus().getCode(), + session.getClientId(), + session.getApplicationData() + )); + } + return result; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseDistributedLocker.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseDistributedLocker.java new file mode 100644 index 00000000..0b39a012 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseDistributedLocker.java @@ -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.storage.db.lock; + + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; +import javax.sql.DataSource; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.common.util.IOUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationCache; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.constants.ServerTableColumnsName; +import io.seata.core.store.DistributedLockDO; +import io.seata.core.store.DistributedLocker; +import io.seata.core.store.db.DataSourceProvider; +import io.seata.core.store.db.sql.distributed.lock.DistributedLockSqlFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.core.constants.ConfigurationKeys.DISTRIBUTED_LOCK_DB_TABLE; + +/** + * @author chd + */ +@LoadLevel(name = "db", scope = Scope.SINGLETON) +public class DataBaseDistributedLocker implements DistributedLocker { + private static final Logger LOGGER = LoggerFactory.getLogger(DataBaseDistributedLocker.class); + + private final String dbType; + + private final String datasourceType; + + private volatile String distributedLockTable; + + private DataSource distributedLockDataSource; + + /** + * whether the distribute lock demotion + * using for 1.5.0 only and will remove in 1.6.0 + */ + @Deprecated + private volatile boolean demotion; + + /** + * Instantiates a new Log store data base dao. + */ + public DataBaseDistributedLocker() { + Configuration configuration = ConfigurationFactory.getInstance(); + + distributedLockTable = configuration.getConfig(DISTRIBUTED_LOCK_DB_TABLE); + dbType = configuration.getConfig(ConfigurationKeys.STORE_DB_TYPE); + datasourceType = configuration.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE); + + if (StringUtils.isBlank(distributedLockTable)) { + demotion = true; + ConfigurationCache.addConfigListener(DISTRIBUTED_LOCK_DB_TABLE, new ConfigurationChangeListener() { + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + String newValue = event.getNewValue(); + if (StringUtils.isNotBlank(newValue)) { + distributedLockTable = newValue; + init(); + demotion = false; + ConfigurationCache.removeConfigListener(DISTRIBUTED_LOCK_DB_TABLE, this); + } + } + }); + + LOGGER.error("The distribute lock table is not config, please create the target table and config it"); + return; + } + + init(); + } + + + @Override + public boolean acquireLock(DistributedLockDO distributedLockDO) { + if (demotion) { + return true; + } + + Connection connection = null; + boolean originalAutoCommit = false; + try { + connection = distributedLockDataSource.getConnection(); + originalAutoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + + DistributedLockDO distributedLockDOFromDB = getDistributedLockDO(connection, distributedLockDO.getLockKey()); + if (null == distributedLockDOFromDB) { + boolean ret = insertDistribute(connection, distributedLockDO); + connection.commit(); + return ret; + } + + if (distributedLockDOFromDB.getExpireTime() >= System.currentTimeMillis()) { + LOGGER.debug("the distribute lock for key :{} is holding by :{}, acquire lock failure.", + distributedLockDO.getLockKey(), distributedLockDOFromDB.getLockValue()); + connection.commit(); + return false; + } + + boolean ret = updateDistributedLock(connection, distributedLockDO); + connection.commit(); + + return ret; + } catch (SQLException ex) { + LOGGER.error("execute acquire lock failure, key is: {}", distributedLockDO.getLockKey(), ex); + try { + if (connection != null) { + connection.rollback(); + } + } catch (SQLException e) { + LOGGER.warn("rollback fail because of {}", e.getMessage(), e); + } + return false; + } finally { + try { + if (originalAutoCommit) { + connection.setAutoCommit(true); + } + IOUtil.close(connection); + } catch (SQLException ignore) { } + } + } + + @Override + public boolean releaseLock(DistributedLockDO distributedLockDO) { + if (demotion) { + return true; + } + + Connection connection = null; + boolean originalAutoCommit = false; + try { + connection = distributedLockDataSource.getConnection(); + originalAutoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + + DistributedLockDO distributedLockDOFromDB = getDistributedLockDO(connection, distributedLockDO.getLockKey()); + if (null == distributedLockDOFromDB) { + throw new ShouldNeverHappenException("distributedLockDO would not be null when release distribute lock"); + } + + if (distributedLockDOFromDB.getExpireTime() >= System.currentTimeMillis() + && !Objects.equals(distributedLockDOFromDB.getLockValue(), distributedLockDO.getLockValue())) { + LOGGER.debug("the distribute lock for key :{} is holding by :{}, skip the release lock.", + distributedLockDO.getLockKey(), distributedLockDOFromDB.getLockValue()); + connection.commit(); + return true; + } + + distributedLockDO.setLockValue(StringUtils.SPACE); + distributedLockDO.setExpireTime(0L); + boolean ret = updateDistributedLock(connection, distributedLockDO); + + connection.commit(); + return ret; + } catch (SQLException ex) { + LOGGER.error("execute release lock failure, key is: {}", distributedLockDO.getLockKey(), ex); + + try { + if (connection != null) { + connection.rollback(); + } + } catch (SQLException e) { + LOGGER.warn("rollback fail because of {}", e.getMessage(), e); + } + return false; + } finally { + try { + if (originalAutoCommit) { + connection.setAutoCommit(true); + } + IOUtil.close(connection); + } catch (SQLException ignore) { } + } + } + + protected DistributedLockDO getDistributedLockDO(Connection connection, String key) throws SQLException { + try (PreparedStatement pst = connection.prepareStatement(DistributedLockSqlFactory.getDistributedLogStoreSql(dbType) + .getSelectDistributeForUpdateSql(distributedLockTable))) { + + pst.setString(1, key); + ResultSet resultSet = pst.executeQuery(); + + if (resultSet.next()) { + DistributedLockDO distributedLock = new DistributedLockDO(); + distributedLock.setExpireTime(resultSet.getLong(ServerTableColumnsName.DISTRIBUTED_LOCK_EXPIRE)); + distributedLock.setLockValue(resultSet.getString(ServerTableColumnsName.DISTRIBUTED_LOCK_VALUE)); + distributedLock.setLockKey(key); + return distributedLock; + } + return null; + } + } + + protected boolean insertDistribute(Connection connection, DistributedLockDO distributedLockDO) throws SQLException { + try (PreparedStatement insertPst = connection.prepareStatement(DistributedLockSqlFactory.getDistributedLogStoreSql(dbType) + .getInsertSql(distributedLockTable))) { + insertPst.setString(1, distributedLockDO.getLockKey()); + insertPst.setString(2, distributedLockDO.getLockValue()); + if (distributedLockDO.getExpireTime() > 0) { + distributedLockDO.setExpireTime(distributedLockDO.getExpireTime() + System.currentTimeMillis()); + } + insertPst.setLong(3, distributedLockDO.getExpireTime()); + return insertPst.executeUpdate() > 0; + } + } + + protected boolean updateDistributedLock(Connection connection, DistributedLockDO distributedLockDO) throws SQLException { + try (PreparedStatement updatePst = connection.prepareStatement(DistributedLockSqlFactory.getDistributedLogStoreSql(dbType) + .getUpdateSql(distributedLockTable))) { + updatePst.setString(1, distributedLockDO.getLockValue()); + if (distributedLockDO.getExpireTime() > 0) { + distributedLockDO.setExpireTime(distributedLockDO.getExpireTime() + System.currentTimeMillis()); + } + updatePst.setLong(2, distributedLockDO.getExpireTime()); + updatePst.setString(3, distributedLockDO.getLockKey()); + return updatePst.executeUpdate() > 0; + } + } + + private void init() { + this.distributedLockDataSource = EnhancedServiceLoader.load(DataSourceProvider.class, datasourceType).provide(); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLockManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLockManager.java new file mode 100644 index 00000000..1e4736fb --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLockManager.java @@ -0,0 +1,77 @@ +/* + * 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 io.seata.common.executor.Initialize; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.LoadLevel; +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 { + try { + return getLocker().releaseLock(globalSession.getXid()); + } catch (Exception t) { + LOGGER.error("unLock globalSession error, xid:{}", globalSession.getXid(), t); + return false; + } + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLocker.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLocker.java new file mode 100644 index 00000000..92d46cec --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/DataBaseLocker.java @@ -0,0 +1,143 @@ +/* + * 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.model.LockStatus; +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 locks) { + return acquireLock(locks, true, false); + } + + @Override + public boolean acquireLock(List locks, boolean autoCommit, boolean skipCheckLock) { + if (CollectionUtils.isEmpty(locks)) { + // no lock + return true; + } + try { + return lockStore.acquireLock(convertToLockDO(locks), autoCommit, skipCheckLock); + } catch (StoreException e) { + throw e; + } catch (Exception t) { + LOGGER.error("AcquireLock error, locks:{}", CollectionUtils.toString(locks), t); + return false; + } + } + + @Override + public boolean releaseLock(List 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) { + try { + return lockStore.unLock(xid); + } catch (StoreException e) { + throw e; + } catch (Exception t) { + LOGGER.error("unLock by branchIds error, xid {}", xid, t); + return false; + } + } + + @Override + public boolean isLockable(List 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; + } + } + + @Override + public void updateLockStatus(String xid, LockStatus lockStatus) { + lockStore.updateLockStatus(xid, lockStatus); + } + + /** + * Sets lock store. + * + * @param lockStore the lock store + */ + public void setLockStore(LockStore lockStore) { + this.lockStore = lockStore; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/LockStoreDataBaseDAO.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/LockStoreDataBaseDAO.java new file mode 100644 index 00000000..f732fef4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/lock/LockStoreDataBaseDAO.java @@ -0,0 +1,436 @@ +/* + * 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.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.stream.Collectors; +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.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.exception.BranchTransactionException; +import io.seata.core.model.LockStatus; +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; +import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflictFailFast; + +/** + * 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 lockDOs) { + return acquireLock(lockDOs, true, false); + } + + @Override + public boolean acquireLock(List lockDOs, boolean autoCommit, boolean skipCheckLock) { + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + Set 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); + } + List unrepeatedLockDOs = lockDOs; + + //check lock + if (!skipCheckLock) { + + boolean canLock = true; + //query + String checkLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getCheckLockableSql(lockTable, lockDOs.size()); + 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(); + boolean failFast = false; + 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); + } + if (!autoCommit) { + int status = rs.getInt(ServerTableColumnsName.LOCK_TABLE_STATUS); + if (status == LockStatus.Rollbacking.getCode()) { + failFast = true; + } + } + canLock = false; + break; + } + + dbExistedRowKeys.add(rs.getString(ServerTableColumnsName.LOCK_TABLE_ROW_KEY)); + } + if (!canLock) { + conn.rollback(); + if (failFast) { + throw new StoreException(new BranchTransactionException(LockKeyConflictFailFast)); + } + return false; + } + // If the lock has been exists in db, remove it from the lockDOs + if (CollectionUtils.isNotEmpty(dbExistedRowKeys)) { + unrepeatedLockDOs = lockDOs.stream().filter(lockDO -> !dbExistedRowKeys.contains(lockDO.getRowKey())) + .collect(Collectors.toList()); + } + 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 lockDOs) { + Connection conn = null; + PreparedStatement ps = null; + try { + conn = lockStoreDataSource.getConnection(); + conn.setAutoCommit(true); + + //batch release lock + String batchDeleteSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getBatchDeleteLockSql(lockTable, lockDOs.size()); + 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) { + Connection conn = null; + PreparedStatement ps = null; + try { + conn = lockStoreDataSource.getConnection(); + conn.setAutoCommit(true); + //batch release lock by branch list + String batchDeleteSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getBatchDeleteLockSqlByXid(lockTable); + ps = conn.prepareStatement(batchDeleteSQL); + ps.setString(1, xid); + ps.executeUpdate(); + } catch (SQLException e) { + throw new StoreException(e); + } finally { + IOUtil.close(ps, conn); + } + return true; + } + + @Override + public boolean isLockable(List 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); + } + } + + @Override + public void updateLockStatus(String xid, LockStatus lockStatus) { + String updateStatusLockByGlobalSql = + LockStoreSqlFactory.getLogStoreSql(dbType).getBatchUpdateStatusLockByGlobalSql(lockTable); + try (Connection conn = lockStoreDataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(updateStatusLockByGlobalSql)) { + conn.setAutoCommit(true); + ps.setInt(1, lockStatus.getCode()); + ps.setString(2, xid); + ps.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException(e); + } + } + + /** + * 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()); + ps.setInt(8, LockStatus.Locked.getCode()); + 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 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.setInt(8, lockDO.getStatus()); + 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 lockDOs) { + PreparedStatement ps = null; + ResultSet rs = null; + try { + //query + String checkLockSQL = LockStoreSqlFactory.getLogStoreSql(dbType).getCheckLockableSql(lockTable, lockDOs.size()); + 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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/session/DataBaseSessionManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/session/DataBaseSessionManager.java new file mode 100644 index 00000000..8bc10ad8 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/session/DataBaseSessionManager.java @@ -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.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.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.db.store.DataBaseTransactionStoreManager; +import io.seata.server.store.TransactionStoreManager.LogOperation; +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 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(GlobalStatus.CommitRetrying, GlobalStatus.Committing)); + } else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) { + return findGlobalSessions(new SessionCondition(GlobalStatus.RollbackRetrying, GlobalStatus.Rollbacking, + GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying)); + } else { + // all data + return findGlobalSessions(new SessionCondition(GlobalStatus.UnKnown, GlobalStatus.Begin, GlobalStatus.Committing, + GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking, GlobalStatus.RollbackRetrying, GlobalStatus.TimeoutRollbacking, + GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting)); + } + } + + @Override + public List findGlobalSessions(SessionCondition condition) { + // nothing need to do + return transactionStoreManager.readSession(condition); + } + + @Override + public T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable lockCallable) + throws TransactionException { + return lockCallable.call(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/DataBaseTransactionStoreManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/DataBaseTransactionStoreManager.java new file mode 100644 index 00000000..16fac553 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/DataBaseTransactionStoreManager.java @@ -0,0 +1,255 @@ +/* + * 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.Collections; +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; + +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_LOG_QUERY_LIMIT; + +/** + * 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 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 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 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 + */ + @Override + public List readSession(GlobalStatus[] statuses, boolean withBranchSessions) { + int[] states = new int[statuses.length]; + for (int i = 0; i < statuses.length; i++) { + states[i] = statuses[i].getCode(); + } + //global transaction + List globalTransactionDOs = logStore.queryGlobalTransactionDO(states, logQueryLimit); + Map> branchTransactionDOsMap = Collections.emptyMap(); + if (CollectionUtils.isNotEmpty(globalTransactionDOs)) { + List xids = + globalTransactionDOs.stream().map(GlobalTransactionDO::getXid).collect(Collectors.toList()); + if (withBranchSessions) { + List branchTransactionDOs = logStore.queryBranchTransactionDO(xids); + branchTransactionDOsMap = branchTransactionDOs.stream().collect( + Collectors.groupingBy(BranchTransactionDO::getXid, LinkedHashMap::new, Collectors.toList())); + } + } + Map> finalBranchTransactionDOsMap = branchTransactionDOsMap; + return globalTransactionDOs.stream() + .map(globalTransactionDO -> getGlobalSession(globalTransactionDO, + finalBranchTransactionDOsMap.get(globalTransactionDO.getXid()), withBranchSessions)) + .collect(Collectors.toList()); + } + + @Override + public List readSession(SessionCondition sessionCondition) { + if (StringUtils.isNotBlank(sessionCondition.getXid())) { + GlobalSession globalSession = readSession(sessionCondition.getXid()); + if (globalSession != null) { + List globalSessions = new ArrayList<>(); + globalSessions.add(globalSession); + return globalSessions; + } + } else if (sessionCondition.getTransactionId() != null) { + GlobalSession globalSession = readSession(sessionCondition.getTransactionId()); + if (globalSession != null) { + List globalSessions = new ArrayList<>(); + globalSessions.add(globalSession); + return globalSessions; + } + } else if (CollectionUtils.isNotEmpty(sessionCondition.getStatuses())) { + return readSession(sessionCondition.getStatuses(), !sessionCondition.isLazyLoadBranch()); + } + return null; + } + + private GlobalSession getGlobalSession(GlobalTransactionDO globalTransactionDO, + List branchTransactionDOs) { + return getGlobalSession(globalTransactionDO, branchTransactionDOs, true); + } + + private GlobalSession getGlobalSession(GlobalTransactionDO globalTransactionDO, + List branchTransactionDOs, boolean withBranchSessions) { + GlobalSession globalSession = SessionConverter.convertGlobalSession(globalTransactionDO, !withBranchSessions); + // 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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/LogStoreDataBaseDAO.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/LogStoreDataBaseDAO.java new file mode 100644 index 00000000..3aa86db0 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/db/store/LogStoreDataBaseDAO.java @@ -0,0 +1,605 @@ +/* + * 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 queryGlobalTransactionDO(int[] statuses, int limit) { + List 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 { + int index = 1; + conn = logStoreDataSource.getConnection(); + conn.setAutoCommit(true); + ps = conn.prepareStatement(sql); + ps.setString(index++, globalTransactionDO.getXid()); + ps.setLong(index++, globalTransactionDO.getTransactionId()); + ps.setInt(index++, globalTransactionDO.getStatus()); + ps.setString(index++, globalTransactionDO.getApplicationId()); + ps.setString(index++, globalTransactionDO.getTransactionServiceGroup()); + String transactionName = globalTransactionDO.getTransactionName(); + transactionName = transactionName.length() > transactionNameColumnSize ? + transactionName.substring(0, transactionNameColumnSize) : + transactionName; + ps.setString(index++, transactionName); + ps.setInt(index++, globalTransactionDO.getTimeout()); + ps.setLong(index++, globalTransactionDO.getBeginTime()); + ps.setString(index++, 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 { + int index = 1; + conn = logStoreDataSource.getConnection(); + conn.setAutoCommit(true); + ps = conn.prepareStatement(sql); + ps.setInt(index++, globalTransactionDO.getStatus()); + ps.setString(index++, 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 queryBranchTransactionDO(String xid) { + List 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 queryBranchTransactionDO(List xids) { + int length = xids.size(); + List 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 { + int index = 1; + conn = logStoreDataSource.getConnection(); + conn.setAutoCommit(true); + ps = conn.prepareStatement(sql); + ps.setString(index++, branchTransactionDO.getXid()); + ps.setLong(index++, branchTransactionDO.getTransactionId()); + ps.setLong(index++, branchTransactionDO.getBranchId()); + ps.setString(index++, branchTransactionDO.getResourceGroupId()); + ps.setString(index++, branchTransactionDO.getResourceId()); + ps.setString(index++, branchTransactionDO.getBranchType()); + ps.setInt(index++, branchTransactionDO.getStatus()); + ps.setString(index++, branchTransactionDO.getClientId()); + ps.setString(index++, branchTransactionDO.getApplicationData()); + return ps.executeUpdate() > 0; + } catch (SQLException e) { + throw new StoreException(e); + } finally { + IOUtil.close(ps, conn); + } + } + + @Override + public boolean updateBranchTransactionDO(BranchTransactionDO branchTransactionDO) { + boolean shouldUpdateAppData = StringUtils.isNotBlank(branchTransactionDO.getApplicationData()); + String sql = shouldUpdateAppData ? + LogStoreSqlsFactory.getLogStoreSqls(dbType).getUpdateBranchTransactionStatusAppDataSQL(branchTable) : + LogStoreSqlsFactory.getLogStoreSqls(dbType).getUpdateBranchTransactionStatusSQL(branchTable); + Connection conn = null; + PreparedStatement ps = null; + try { + int index = 1; + conn = logStoreDataSource.getConnection(); + conn.setAutoCommit(true); + ps = conn.prepareStatement(sql); + ps.setInt(index++, branchTransactionDO.getStatus()); + if (shouldUpdateAppData) { + ps.setString(index++, branchTransactionDO.getApplicationData()); + } + ps.setString(index++, branchTransactionDO.getXid()); + ps.setLong(index++, 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 Math.max(maxBranchId, maxTransId); + } + + private long getCurrentMaxSessionId(String sql, long high, long low) { + long max = 0; + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + int index = 1; + conn = logStoreDataSource.getConnection(); + conn.setAutoCommit(true); + ps = conn.prepareStatement(sql); + ps.setLong(index++, high); + ps.setLong(index++, 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; + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/FlushDiskMode.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/FlushDiskMode.java new file mode 100644 index 00000000..9fdb50ee --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/FlushDiskMode.java @@ -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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/ReloadableStore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/ReloadableStore.java new file mode 100644 index 00000000..be45305c --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/ReloadableStore.java @@ -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 readWriteStore(int readSize, boolean isHistory); + + /** + * Has remaining boolean. + * + * @param isHistory the is history + * @return the boolean + */ + boolean hasRemaining(boolean isHistory); + + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/TransactionWriteStore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/TransactionWriteStore.java new file mode 100644 index 00000000..177d0e52 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/TransactionWriteStore.java @@ -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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java new file mode 100644 index 00000000..37f8e2df --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java @@ -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.List; + +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 { + List 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; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java new file mode 100644 index 00000000..f3f40333 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java @@ -0,0 +1,221 @@ +/* + * 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.seata.common.exception.FrameworkException; +import io.seata.common.exception.StoreException; +import io.seata.common.util.CollectionUtils; +import io.seata.core.exception.BranchTransactionException; +import io.seata.core.exception.TransactionException; +import io.seata.core.lock.AbstractLocker; +import io.seata.core.lock.RowLock; +import io.seata.core.model.LockStatus; +import io.seata.server.session.BranchSession; + + +import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflictFailFast; + +/** + * The type Memory locker. + * + * @author zhangsen + */ +public class FileLocker extends AbstractLocker { + + private static final int BUCKET_PER_TABLE = 128; + + private static final ConcurrentMap>> + LOCK_MAP = new ConcurrentHashMap<>(); + + /** + * The Branch session. + */ + protected BranchSession branchSession; + + /** + * Instantiates a new Memory locker. + * + * @param branchSession the branch session + */ + public FileLocker(BranchSession branchSession) { + this.branchSession = branchSession; + } + + @Override + public boolean acquireLock(List rowLocks) { + return acquireLock(rowLocks, true, false); + } + + @Override + public boolean acquireLock(List rowLocks, boolean autoCommit, boolean skipCheckLock) { + if (CollectionUtils.isEmpty(rowLocks)) { + // no lock + return true; + } + String resourceId = branchSession.getResourceId(); + long transactionId = branchSession.getTransactionId(); + + ConcurrentMap> bucketHolder = branchSession.getLockHolder(); + ConcurrentMap> dbLockMap = CollectionUtils.computeIfAbsent( + LOCK_MAP, resourceId, key -> new ConcurrentHashMap<>()); + boolean failFast = false; + boolean canLock = true; + for (RowLock lock : rowLocks) { + String tableName = lock.getTableName(); + String pk = lock.getPk(); + ConcurrentMap tableLockMap = CollectionUtils.computeIfAbsent(dbLockMap, tableName, + key -> new ConcurrentHashMap<>()); + + int bucketId = pk.hashCode() % BUCKET_PER_TABLE; + BucketLockMap bucketLockMap = CollectionUtils.computeIfAbsent(tableLockMap, bucketId, + key -> new BucketLockMap()); + BranchSession previousLockBranchSession = bucketLockMap.get().putIfAbsent(pk, branchSession); + if (previousLockBranchSession == null) { + // No existing lock, and now locked by myself + Set keysInHolder = CollectionUtils.computeIfAbsent(bucketHolder, bucketLockMap, + key -> ConcurrentHashMap.newKeySet()); + keysInHolder.add(pk); + } else if (previousLockBranchSession.getTransactionId() == transactionId) { + // Locked by me before + continue; + } else { + LOGGER.info("Global lock on [" + tableName + ":" + pk + "] is holding by " + previousLockBranchSession.getBranchId()); + try { + // Release all acquired locks. + branchSession.unlock(); + } catch (TransactionException e) { + throw new FrameworkException(e); + } + if (!autoCommit && previousLockBranchSession.getLockStatus() == LockStatus.Rollbacking) { + failFast = true; + break; + } + if (canLock) { + canLock = false; + if (autoCommit) { + break; + } + } + } + } + if (failFast) { + throw new StoreException(new BranchTransactionException(LockKeyConflictFailFast)); + } + return canLock; + } + + @Override + public boolean releaseLock(List rowLock) { + if (CollectionUtils.isEmpty(rowLock)) { + //no lock + return true; + } + ConcurrentMap> lockHolder = branchSession.getLockHolder(); + if (CollectionUtils.isEmpty(lockHolder)) { + return true; + } + for (Map.Entry> entry : lockHolder.entrySet()) { + BucketLockMap bucket = entry.getKey(); + Set keys = entry.getValue(); + for (String key : keys) { + // remove lock only if it locked by myself + bucket.get().remove(key, branchSession); + } + } + lockHolder.clear(); + return true; + } + + @Override + public boolean isLockable(List rowLocks) { + if (CollectionUtils.isEmpty(rowLocks)) { + //no lock + return true; + } + Long transactionId = rowLocks.get(0).getTransactionId(); + String resourceId = rowLocks.get(0).getResourceId(); + ConcurrentMap> dbLockMap = LOCK_MAP.get(resourceId); + if (dbLockMap == null) { + return true; + } + for (RowLock rowLock : rowLocks) { + String tableName = rowLock.getTableName(); + String pk = rowLock.getPk(); + + ConcurrentMap tableLockMap = dbLockMap.get(tableName); + if (tableLockMap == null) { + continue; + } + int bucketId = pk.hashCode() % BUCKET_PER_TABLE; + BucketLockMap bucketLockMap = tableLockMap.get(bucketId); + if (bucketLockMap == null) { + continue; + } + BranchSession branchSession = bucketLockMap.get().get(pk); + Long lockingTransactionId = branchSession != null ? branchSession.getTransactionId() : null; + 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 updateLockStatus(String xid, LockStatus lockStatus) { + } + + @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 bucketLockMap + = new ConcurrentHashMap<>(); + + ConcurrentHashMap get() { + return bucketLockMap; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java new file mode 100644 index 00000000..b5e1a0f9 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java @@ -0,0 +1,379 @@ +/* + * 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.Arrays; +import java.util.Objects; +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.CollectionUtils; +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 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 { + CollectionUtils.computeIfAbsent(sessionMap, session.getXid(), k -> { + try { + super.addGlobalSession(session); + } catch (TransactionException e) { + LOGGER.error("addGlobalSession fail, msg: {}", e.getMessage()); + } + return 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 { + if (sessionMap.remove(session.getXid()) != null) { + super.removeGlobalSession(session); + } + } + + @Override + public Collection allSessions() { + return sessionMap.values(); + } + + @Override + public List findGlobalSessions(SessionCondition condition) { + List found = new ArrayList<>(); + + List globalStatuses = null; + if (null != condition.getStatuses() && condition.getStatuses().length > 0) { + globalStatuses = Arrays.asList(condition.getStatuses()); + } + for (GlobalSession globalSession : sessionMap.values()) { + if (null != condition.getOverTimeAliveMills() && condition.getOverTimeAliveMills() > 0) { + if (System.currentTimeMillis() - globalSession.getBeginTime() <= condition.getOverTimeAliveMills()) { + continue; + } + } + + if (!StringUtils.isEmpty(condition.getXid())) { + if (Objects.equals(condition.getXid(), globalSession.getXid())) { + // Only one will be found, just add and return + found.add(globalSession); + return found; + } else { + continue; + } + } + + if (null != condition.getTransactionId() && condition.getTransactionId() > 0) { + if (Objects.equals(condition.getTransactionId(), globalSession.getTransactionId())) { + // Only one will be found, just add and return + found.add(globalSession); + return found; + } else { + continue; + } + } + + if (null != globalStatuses) { + if (!globalStatuses.contains(globalSession.getStatus())) { + continue; + } + } + + // All test pass, add to resp + found.add(globalSession); + } + return found; + } + + @Override + public T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable lockCallable) + throws TransactionException { + globalSession.lock(); + try { + return lockCallable.call(); + } finally { + globalSession.unlock(); + } + } + + private void restoreSessions() { + final Set removedGlobalBuffer = new HashSet<>(); + final Map> 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 removedGlobalBuffer, Map> unhandledBranchBuffer) { + if (!(transactionStoreManager instanceof ReloadableStore)) { + return; + } + while (((ReloadableStore)transactionStoreManager).hasRemaining(isHistory)) { + List stores = ((ReloadableStore)transactionStoreManager).readWriteStore(READ_SIZE, + isHistory); + restore(stores, removedGlobalBuffer, unhandledBranchBuffer); + } + } + + private void restore(List stores, Set removedGlobalBuffer, + Map> 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(); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/store/FileTransactionStoreManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/store/FileTransactionStoreManager.java new file mode 100644 index 00000000..69b6f4d1 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/file/store/FileTransactionStoreManager.java @@ -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_INTERVAL = 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(), + 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) { + long curFileTrxNum; + writeSessionLock.lock(); + 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 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 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 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_INTERVAL); + } 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 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; + RandomAccessFile raf = null; + long currentOffset; + 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 parseDataFile(File file, int readSize, long currentOffset, boolean isHistory) { + List 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 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); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/JedisPooledFactory.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/JedisPooledFactory.java new file mode 100644 index 00000000..6e580ca6 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/JedisPooledFactory.java @@ -0,0 +1,132 @@ +/* + * 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 io.seata.common.exception.RedisException; +import io.seata.common.util.ConfigTools; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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) { + JedisPoolAbstract tempJedisPool = null; + if (jedisPools != null && jedisPools.length > 0) { + tempJedisPool = 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 sentinels = new HashSet<>(SENTINEL_HOST_NUMBER); + String[] sentinelHosts = CONFIGURATION.getConfig(ConfigurationKeys.STORE_REDIS_SENTINEL_HOST).split(","); + Arrays.asList(sentinelHosts).forEach(sentinelHost -> sentinels.add(sentinelHost)); + tempJedisPool = 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; + tempJedisPool = 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"); + } + jedisPool = tempJedisPool; + } + } + } + return jedisPool; + } + + /** + * get an instance of Jedis (connection) from the connection pool + * + * @return jedis + */ + public static Jedis getJedisInstance() { + return getJedisPoolInstance().getResource(); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisDistributedLocker.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisDistributedLocker.java new file mode 100644 index 00000000..4aa20e2b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisDistributedLocker.java @@ -0,0 +1,88 @@ +/* + * 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 io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.core.store.DistributedLockDO; +import io.seata.core.store.DistributedLocker; +import io.seata.server.storage.redis.JedisPooledFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Transaction; +import redis.clients.jedis.params.SetParams; + +/** + * @description Redis distributed lock + * @author zhongxiang.wang + */ +@LoadLevel(name = "redis", scope = Scope.SINGLETON) +public class RedisDistributedLocker implements DistributedLocker { + + protected static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributedLocker.class); + private static final String SUCCESS = "OK"; + + /** + * Acquire the distributed lock + * + * @param distributedLockDO + * @return + */ + @Override + public boolean acquireLock(DistributedLockDO distributedLockDO) { + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + //Don't need retry,if can't acquire the lock,let the other get the lock + String result = jedis.set(distributedLockDO.getLockKey(), distributedLockDO.getLockValue(), SetParams.setParams().nx().px(distributedLockDO.getExpireTime())); + if (SUCCESS.equalsIgnoreCase(result)) { + return true; + } + return false; + } catch (Exception ex) { + LOGGER.error("The {} acquired the {} distributed lock failed.", distributedLockDO.getLockValue(), distributedLockDO.getLockKey(), ex); + return false; + } + } + + + /** + * Release the distributed lock + * + * @param distributedLockDO + * @return + */ + @Override + public boolean releaseLock(DistributedLockDO distributedLockDO) { + String lockKey = distributedLockDO.getLockKey(); + String lockValue = distributedLockDO.getLockValue(); + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + jedis.watch(lockKey); + //Check the value to prevent release the other's lock + if (lockValue.equals(jedis.get(lockKey))) { + Transaction multi = jedis.multi(); + multi.del(lockKey); + multi.exec(); + return true; + } + //The lock hold by others,If other one get the lock,we release lock success too as for current lockKey + jedis.unwatch(); + return true; + } catch (Exception ex) { + LOGGER.error("The {} release the {} distributed lock failed.", lockValue, lockKey, ex); + return false; + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLockManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLockManager.java new file mode 100644 index 00000000..067fd50d --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLockManager.java @@ -0,0 +1,66 @@ +/* + * 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 io.seata.common.executor.Initialize; +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; + +/** + * @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 { + try { + return getLocker().releaseLock(globalSession.getXid()); + } catch (Exception t) { + LOGGER.error("unLock globalSession error, xid:{}", globalSession.getXid(), t); + return false; + } + } +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLocker.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLocker.java new file mode 100644 index 00000000..81260e6f --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/lock/RedisLocker.java @@ -0,0 +1,395 @@ +/* + * 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.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +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.exception.StoreException; +import io.seata.common.io.FileLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.LambdaUtils; +import io.seata.common.util.StringUtils; +import io.seata.core.exception.BranchTransactionException; +import io.seata.core.lock.AbstractLocker; +import io.seata.core.lock.RowLock; +import io.seata.core.model.LockStatus; +import io.seata.core.store.LockDO; +import io.seata.server.storage.redis.JedisPooledFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Pipeline; + +import static io.seata.common.Constants.ROW_LOCK_KEY_SPLIT_CHAR; +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX; +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX; +import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflictFailFast; +/** + * The redis lock store operation + * + * @author funkye + * @author wangzhongxiang + */ +public class RedisLocker extends AbstractLocker { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedisLocker.class); + + private static final Integer SUCCEED = 1; + + private static final Integer FAILED = 0; + + 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 STATUS = "status"; + + private static final String ROW_KEY = "rowKey"; + + private static final String REDIS_LUA_FILE_NAME = "lua/redislocker/redislock.lua"; + + private static String ACQUIRE_LOCK_SHA; + + private static final String WHITE_SPACE = " "; + + private static final String ANNOTATION_LUA = "--"; + + /** + * Instantiates a new Redis locker. + */ + public RedisLocker() { + if (ACQUIRE_LOCK_SHA == null) { + File luaFile = FileLoader.load(REDIS_LUA_FILE_NAME); + if (luaFile != null) { + StringBuilder acquireLockLuaByFile = new StringBuilder(); + try (FileInputStream fis = new FileInputStream(luaFile)) { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + if (line.trim().startsWith(ANNOTATION_LUA)) { + continue; + } + acquireLockLuaByFile.append(line); + acquireLockLuaByFile.append(WHITE_SPACE); + } + // if it fails to read the file, pipeline mode is used + } catch (IOException e) { + LOGGER.info("redis locker use pipeline mode"); + return; + } + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + ACQUIRE_LOCK_SHA = jedis.scriptLoad(acquireLockLuaByFile.toString()); + LOGGER.info("redis locker use lua mode"); + } + } else { + LOGGER.info("redis locker use pipeline mode"); + } + } + } + + @Override + public boolean acquireLock(List rowLocks) { + return acquireLock(rowLocks, true, false); + } + + @Override + public boolean acquireLock(List rowLocks, boolean autoCommit, boolean skipCheckLock) { + if (CollectionUtils.isEmpty(rowLocks)) { + return true; + } + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + if (ACQUIRE_LOCK_SHA != null && autoCommit) { + return acquireLockByLua(jedis, rowLocks); + } else { + return acquireLockByPipeline(jedis, rowLocks, autoCommit, skipCheckLock); + } + } + } + + private boolean acquireLockByPipeline(Jedis jedis, List rowLocks, boolean autoCommit, boolean skipCheckLock) { + String needLockXid = rowLocks.get(0).getXid(); + Long branchId = rowLocks.get(0).getBranchId(); + List needLockDOS = convertToLockDO(rowLocks); + if (needLockDOS.size() > 1) { + needLockDOS = + needLockDOS.stream().filter(LambdaUtils.distinctByKey(LockDO::getRowKey)).collect(Collectors.toList()); + } + List needLockKeys = new ArrayList<>(); + needLockDOS.forEach(lockDO -> needLockKeys.add(buildLockKey(lockDO.getRowKey()))); + Map needAddLock = new HashMap<>(needLockKeys.size(), 1); + + if (!skipCheckLock) { + Pipeline pipeline1 = jedis.pipelined(); + needLockKeys.stream().forEachOrdered(needLockKey -> { + pipeline1.hget(needLockKey, XID); + if (!autoCommit) { + pipeline1.hget(needLockKey, STATUS); + } + }); + List> existedLockInfos = + Lists.partition((List)(List)pipeline1.syncAndReturnAll(), autoCommit ? 1 : 2); + + // When the local transaction and the global transaction are enabled, + // the branch registration fails to acquire the global lock, + // the lock holder is in the second-stage rollback, + // and the branch registration fails to be retried quickly, + // because the retry with the local transaction does not release the database lock , + // resulting in a two-phase rollback wait. + // Therefore, if a global lock is found in the Rollbacking state, + // the fail-fast code is returned directly. + if (!autoCommit) { + boolean hasRollBackingLock = existedLockInfos.parallelStream().anyMatch( + result -> StringUtils.equals(result.get(1), String.valueOf(LockStatus.Rollbacking.getCode()))); + if (hasRollBackingLock) { + throw new StoreException(new BranchTransactionException(LockKeyConflictFailFast)); + } + } + + // The logic is executed here, there must be a lock without Rollbacking status when autoCommit equals false + for (int i = 0; i < needLockKeys.size(); i++) { + List results = existedLockInfos.get(i); + String existedLockXid = CollectionUtils.isEmpty(results) ? null : existedLockInfos.get(i).get(0); + 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 + logGlobalLockConflictInfo(needLockXid, needLockKeys.get(i), existedLockXid); + return false; + } + } + } + if (needAddLock.isEmpty()) { + return true; + } + } + + Pipeline pipeline = jedis.pipelined(); + List readyKeys = new ArrayList<>(needAddLock.keySet()); + 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()); + }); + List results = (List) (List) pipeline.syncAndReturnAll(); + List> partitions = Lists.partition(results, 7); + + ArrayList 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; + } + + private boolean acquireLockByLua(Jedis jedis, List rowLocks) { + String needLockXid = rowLocks.get(0).getXid(); + Long branchId = rowLocks.get(0).getBranchId(); + List needLockDOs = rowLocks.stream() + .map(this::convertToLockDO) + .filter(LambdaUtils.distinctByKey(LockDO::getRowKey)) + .collect(Collectors.toList()); + ArrayList keys = new ArrayList<>(); + ArrayList args = new ArrayList<>(); + int size = needLockDOs.size(); + args.add(String.valueOf(size)); + // args index 2 placeholder + args.add(null); + args.add(needLockXid); + for (LockDO lockDO : needLockDOs) { + keys.add(buildLockKey(lockDO.getRowKey())); + args.add(lockDO.getTransactionId().toString()); + args.add(lockDO.getBranchId().toString()); + args.add(lockDO.getResourceId()); + args.add(lockDO.getTableName()); + args.add(lockDO.getRowKey()); + args.add(lockDO.getPk()); + } + String xidLockKey = buildXidLockKey(needLockXid); + StringJoiner lockKeysString = new StringJoiner(ROW_LOCK_KEY_SPLIT_CHAR); + needLockDOs.stream().map(lockDO -> buildLockKey(lockDO.getRowKey())).forEach(lockKeysString::add); + keys.add(xidLockKey); + keys.add(branchId.toString()); + args.add(lockKeysString.toString()); + // reset args index 2 + args.set(1, String.valueOf(args.size())); + String xIdOwnLock = (String) jedis.evalsha(ACQUIRE_LOCK_SHA, keys, args); + if (xIdOwnLock.equals(needLockXid)) { + return true; + } else { + logGlobalLockConflictInfo(needLockXid, keys.get(0), xIdOwnLock); + return false; + } + } + + private void logGlobalLockConflictInfo(String needLockXid, String lockKey, String xIdOwnLock) { + LOGGER.info("tx:[{}] acquire Global lock failed. Global lock on [{}] is holding by xid {}", needLockXid, lockKey, xIdOwnLock); + } + + @Override + public boolean releaseLock(List rowLocks) { + if (CollectionUtils.isEmpty(rowLocks)) { + return true; + } + String currentXid = rowLocks.get(0).getXid(); + Long branchId = rowLocks.get(0).getBranchId(); + List 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) { + return doReleaseLock(xid, null); + } + + @Override + public boolean releaseLock(String xid, Long branchId) { + if (branchId == null) { + return true; + } + return doReleaseLock(xid, branchId); + } + + @Override + public boolean isLockable(List rowLocks) { + if (CollectionUtils.isEmpty(rowLocks)) { + return true; + } + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + List locks = convertToLockDO(rowLocks); + Set lockKeys = new HashSet<>(); + for (LockDO rowlock : locks) { + lockKeys.add(buildLockKey(rowlock.getRowKey())); + } + + String xid = rowLocks.get(0).getXid(); + try (Pipeline pipeline = jedis.pipelined()) { + lockKeys.forEach(key -> pipeline.hget(key, XID)); + List existedXids = (List)(List)pipeline.syncAndReturnAll(); + return existedXids.stream().allMatch(existedXid -> existedXid == null || xid.equals(existedXid)); + } + } + } + + @Override + public void updateLockStatus(String xid, LockStatus lockStatus) { + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + String xidLockKey = buildXidLockKey(xid); + Map branchAndLockKeys = jedis.hgetAll(xidLockKey); + if (CollectionUtils.isNotEmpty(branchAndLockKeys)) { + try (Pipeline pipeline = jedis.pipelined()) { + branchAndLockKeys.values() + .forEach(k -> pipeline.hset(k, STATUS, String.valueOf(lockStatus.getCode()))); + pipeline.sync(); + } + } + } + } + + private boolean doReleaseLock(String xid, Long branchId) { + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + String xidLockKey = buildXidLockKey(xid); + final List rowKeys = new ArrayList<>(); + if (null == branchId) { + Map rowKeyMap = jedis.hgetAll(xidLockKey); + rowKeyMap.forEach((branch, rowKey) -> rowKeys.add(rowKey)); + } else { + rowKeys.addAll(jedis.hmget(xidLockKey, branchId.toString())); + } + if (CollectionUtils.isNotEmpty(rowKeys)) { + Pipeline pipelined = jedis.pipelined(); + if (null == branchId) { + pipelined.del(xidLockKey); + } else { + pipelined.hdel(xidLockKey, branchId.toString()); + } + 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; + } + } + + 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; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/session/RedisSessionManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/session/RedisSessionManager.java new file mode 100644 index 00000000..7eecafd4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/session/RedisSessionManager.java @@ -0,0 +1,194 @@ +/* + * 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 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 findGlobalSessions(SessionCondition condition) { + // nothing need to do + return transactionStoreManager.readSession(condition); + } + + @Override + public T lockAndExecute(GlobalSession globalSession, GlobalSession.LockCallable lockCallable) + throws TransactionException { + return lockCallable.call(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/store/RedisTransactionStoreManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/store/RedisTransactionStoreManager.java new file mode 100644 index 00000000..d1510379 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/storage/redis/store/RedisTransactionStoreManager.java @@ -0,0 +1,799 @@ +/* + * 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.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Date; +import java.util.Optional; +import java.util.Collections; +import java.util.function.Function; +import java.util.stream.Collectors; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableMap; +import io.seata.common.XID; +import io.seata.common.exception.RedisException; +import io.seata.common.exception.StoreException; +import io.seata.common.util.BeanUtils; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.server.console.param.GlobalSessionParam; +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 redis.clients.jedis.Jedis; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Transaction; +import static io.seata.common.ConfigurationKeys.STORE_REDIS_QUERY_LIMIT; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_XID; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_XID; +import static io.seata.core.constants.RedisKeyConstants.DEFAULT_LOG_QUERY_LIMIT; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_STATUS; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_STATUS; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_GLOBAL_GMT_MODIFIED; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_GMT_MODIFIED; +import static io.seata.core.constants.RedisKeyConstants.REDIS_KEY_BRANCH_APPLICATION_DATA; + +/** + * The redis transaction store manager + * + * @author funkye + * @author wangzhongxiang + * @author doubleDimple + */ +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"; + + /** + * The constant CONFIG. + */ + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + /** + * The Log query limit. + */ + private int logQueryLimit; + + /** + * Get the instance. + */ + public static RedisTransactionStoreManager getInstance() { + if (instance == null) { + synchronized (RedisTransactionStoreManager.class) { + if (instance == null) { + instance = new RedisTransactionStoreManager(); + } + } + } + return instance; + } + + /** + * init map to constructor + */ + public RedisTransactionStoreManager() { + super(); + initGlobalMap(); + initBranchMap(); + logQueryLimit = CONFIG.getInt(STORE_REDIS_QUERY_LIMIT, DEFAULT_LOG_QUERY_LIMIT); + /** + * redis mode: if DEFAULT_LOG_QUERY_LIMIT < STORE_REDIS_QUERY_LIMIT get DEFAULT_LOG_QUERY_LIMIT if + * DEFAULT_LOG_QUERY_LIMIT >= STORE_REDIS_QUERY_LIMIT get STORE_REDIS_QUERY_LIMIT + */ + if (logQueryLimit > DEFAULT_LOG_QUERY_LIMIT) { + logQueryLimit = DEFAULT_LOG_QUERY_LIMIT; + } + } + + /** + * Map for LogOperation Global Operation + */ + public static volatile ImmutableMap> globalMap; + + /** + * Map for LogOperation Branch Operation + */ + public static volatile ImmutableMap> branchMap; + + + /** + * init globalMap + * + * @return void + */ + public void initGlobalMap() { + if (CollectionUtils.isEmpty(branchMap)) { + globalMap = ImmutableMap.>builder() + .put(LogOperation.GLOBAL_ADD, this::insertGlobalTransactionDO) + .put(LogOperation.GLOBAL_UPDATE, this::updateGlobalTransactionDO) + .put(LogOperation.GLOBAL_REMOVE, this::deleteGlobalTransactionDO) + .build(); + } + } + + /** + * init branchMap + * + * @return void + */ + public void initBranchMap() { + if (CollectionUtils.isEmpty(branchMap)) { + branchMap = ImmutableMap.>builder() + .put(LogOperation.BRANCH_ADD, this::insertBranchTransactionDO) + .put(LogOperation.BRANCH_UPDATE, this::updateBranchTransactionDO) + .put(LogOperation.BRANCH_REMOVE, this::deleteBranchTransactionDO) + .build(); + } + } + + + @Override + public boolean writeSession(LogOperation logOperation, SessionStorable session) { + if (globalMap.containsKey(logOperation) || branchMap.containsKey(logOperation)) { + return globalMap.containsKey(logOperation) ? + globalMap.get(logOperation).apply(SessionConverter.convertGlobalTransactionDO(session)) : + branchMap.get(logOperation).apply(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(); Pipeline pipelined = jedis.pipelined()) { + Date now = new Date(); + branchTransactionDO.setGmtCreate(now); + branchTransactionDO.setGmtModified(now); + 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()) { + String xid = jedis.hget(branchKey, REDIS_KEY_BRANCH_XID); + if (StringUtils.isEmpty(xid)) { + return true; + } + String branchListKey = buildBranchListKeyByXid(branchTransactionDO.getXid()); + try (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 map = new HashMap<>(3, 1); + map.put(REDIS_KEY_BRANCH_STATUS, String.valueOf(branchTransactionDO.getStatus())); + map.put(REDIS_KEY_BRANCH_GMT_MODIFIED, String.valueOf((new Date()).getTime())); + if (StringUtils.isNotBlank(branchTransactionDO.getApplicationData())) { + map.put(REDIS_KEY_BRANCH_APPLICATION_DATA, String.valueOf(branchTransactionDO.getApplicationData())); + } + 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(); Pipeline pipelined = jedis.pipelined()) { + Date now = new Date(); + globalTransactionDO.setGmtCreate(now); + globalTransactionDO.setGmtModified(now); + 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()) { + String xid = jedis.hget(globalKey, 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; + } + try (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 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 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 exec = multi.exec(); + if (CollectionUtils.isEmpty(exec)) { + //The data has changed by another tc, so we still think the modification is successful. + LOGGER.warn("The global transaction xid = {}, maybe changed by another TC. It does not affect the results",globalTransactionDO.getXid()); + return true; + } + 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 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 map = jedis.hgetAll(globalKey); + if (CollectionUtils.isEmpty(map)) { + return null; + } + GlobalTransactionDO globalTransactionDO = (GlobalTransactionDO)BeanUtils.mapToObject(map, GlobalTransactionDO.class); + List branchTransactionDOs = null; + if (withBranchSessions) { + branchTransactionDOs = this.readBranchSessionByXid(jedis, xid); + } + GlobalSession session = getGlobalSession(globalTransactionDO, branchTransactionDOs, withBranchSessions); + return session; + } + } + + /** + * 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 + */ + @Override + public List readSession(GlobalStatus[] statuses, boolean withBranchSessions) { + + List globalSessions = Collections.synchronizedList(new ArrayList<>()); + List statusKeys = convertStatusKeys(statuses); + + Map targetMap = calculateStatuskeysHasData(statusKeys); + if (targetMap.size() == 0 || logQueryLimit <= 0) { + return globalSessions; + } + int perStatusLimit = resetLogQueryLimit(targetMap); + final long countGlobalSessions = targetMap.values().stream().collect(Collectors.summarizingInt(Integer::intValue)).getSum(); + // queryCount + final long queryCount = Math.min(logQueryLimit, countGlobalSessions); + List> list = new ArrayList<>(); + dogetXidsForTargetMapRecursive(targetMap, 0L, perStatusLimit - 1, queryCount, list); + if (CollectionUtils.isNotEmpty(list)) { + List xids = list.stream().flatMap(Collection::stream).collect(Collectors.toList()); + xids.parallelStream().forEach(xid -> { + GlobalSession globalSession = this.readSession(xid, withBranchSessions); + if (globalSession != null) { + globalSessions.add(globalSession); + } + }); + } + return globalSessions; + } + + /** + * get everyone keys limit + * + * @param targetMap + * @return + */ + private int resetLogQueryLimit(Map targetMap) { + int resetLimitQuery = logQueryLimit; + if (targetMap.size() > 1) { + int size = targetMap.size(); + resetLimitQuery = (logQueryLimit / size) == 0 ? 1 : (logQueryLimit / size); + } + return resetLimitQuery; + } + + /** + * read the global session list by different condition + * @param sessionCondition the session condition + * @return the global sessions + */ + @Override + public List readSession(SessionCondition sessionCondition) { + List globalSessions = new ArrayList<>(); + if (StringUtils.isNotEmpty(sessionCondition.getXid())) { + GlobalSession globalSession = this.readSession(sessionCondition.getXid(), !sessionCondition.isLazyLoadBranch()); + if (globalSession != null) { + globalSessions.add(globalSession); + } + return globalSessions; + } else if (sessionCondition.getTransactionId() != null) { + GlobalSession globalSession = this + .readSessionByTransactionId(sessionCondition.getTransactionId().toString(), !sessionCondition.isLazyLoadBranch()); + if (globalSession != null) { + globalSessions.add(globalSession); + } + return globalSessions; + } else if (CollectionUtils.isNotEmpty(sessionCondition.getStatuses())) { + return readSession(sessionCondition.getStatuses(), !sessionCondition.isLazyLoadBranch()); + } else if (sessionCondition.getStatus() != null) { + return readSession(new GlobalStatus[] {sessionCondition.getStatus()}, !sessionCondition.isLazyLoadBranch()); + } + return null; + } + + /** + * query GlobalSession by status with page + * + * @param param + * @return List + */ + public List readSessionStatusByPage(GlobalSessionParam param) { + List globalSessions = new ArrayList<>(); + + int pageNum = param.getPageNum(); + int pageSize = param.getPageSize(); + int start = Math.max((pageNum - 1) * pageSize, 0); + int end = pageNum * pageSize - 1; + + if (param.getStatus() != null) { + String statusKey = buildGlobalStatus(GlobalStatus.get(param.getStatus()).getCode()); + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + final List xids = jedis.lrange(statusKey, start, end); + xids.forEach(xid -> { + GlobalSession globalSession = this.readSession(xid, param.isWithBranch()); + if (globalSession != null) { + globalSessions.add(globalSession); + } + }); + } + } + return globalSessions; + } + + /** + * assemble the global session and branch session + * @param globalTransactionDO the global transactionDo + * @param branchTransactionDOs the branch transactionDos + * @param withBranchSessions if read branch sessions + * @return the global session with branch session + */ + private GlobalSession getGlobalSession(GlobalTransactionDO globalTransactionDO, + List branchTransactionDOs, boolean withBranchSessions) { + GlobalSession globalSession = SessionConverter.convertGlobalSession(globalTransactionDO, !withBranchSessions); + 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 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 branchTransactionDOs = new ArrayList<>(); + if (withBranchSessions) { + branchTransactionDOs = this.readBranchSessionByXid(jedis, xid); + } + return getGlobalSession(globalTransactionDO, branchTransactionDOs, withBranchSessions); + } + } + + + /** + * Read the branch session list by xid + * @param jedis the jedis + * @param xid the xid + * @return the branch transactionDo list + */ + private List readBranchSessionByXid(Jedis jedis, String xid) { + List branchTransactionDOs = new ArrayList<>(); + String branchListKey = buildBranchListKeyByXid(xid); + List branchKeys = lRange(jedis, branchListKey); + if (CollectionUtils.isNotEmpty(branchKeys)) { + try (Pipeline pipeline = jedis.pipelined()) { + branchKeys.stream().forEach(branchKey -> pipeline.hgetAll(branchKey)); + List branchInfos = pipeline.syncAndReturnAll(); + for (Object branchInfo : branchInfos) { + if (branchInfo != null) { + Map branchInfoMap = (Map)branchInfo; + Optional branchTransactionDO = Optional.ofNullable( + (BranchTransactionDO)BeanUtils.mapToObject(branchInfoMap, BranchTransactionDO.class)); + branchTransactionDO.ifPresent(branchTransactionDOs::add); + } + } + } + } + if (CollectionUtils.isNotEmpty(branchTransactionDOs)) { + Collections.sort(branchTransactionDOs); + } + return branchTransactionDOs; + } + + private List lRange(Jedis jedis, String key) { + List keys = new ArrayList<>(); + List values; + int limit = 20; + int start = 0; + int stop = limit; + for (;;) { + values = jedis.lrange(key, start, stop); + keys.addAll(values); + if (CollectionUtils.isEmpty(values) || values.size() < limit) { + break; + } + start = keys.size(); + stop = start + limit; + } + return keys; + } + + public List findBranchSessionByXid(String xid) { + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + return readBranchSessionByXid(jedis, xid); + } + } + + /** + * query globalSession by page + * + * @param pageNum + * @param pageSize + * @param withBranchSessions + * @return List + */ + public List findGlobalSessionByPage(int pageNum, int pageSize, boolean withBranchSessions) { + List globalSessions = new ArrayList<>(); + int start = Math.max((pageNum - 1) * pageSize, 0); + int end = pageNum * pageSize - 1; + + List statusKeys = convertStatusKeys(GlobalStatus.values()); + Map stringLongMap = calculateStatuskeysHasData(statusKeys); + + List> list = dogetXidsForTargetMap(stringLongMap, start, end, pageSize); + + if (CollectionUtils.isNotEmpty(list)) { + List xids = list.stream().flatMap(Collection::stream).collect(Collectors.toList()); + xids.forEach(xid -> { + if (globalSessions.size() < pageSize) { + GlobalSession globalSession = this.readSession(xid, withBranchSessions); + if (globalSession != null) { + globalSessions.add(globalSession); + } + } + }); + } + return globalSessions; + } + + /** + * query and sort existing values + * + * @param statusKeys + * @return + */ + private Map calculateStatuskeysHasData(List statusKeys) { + Map resultMap = Collections.synchronizedMap(new LinkedHashMap<>()); + Map keysMap = new HashMap<>(statusKeys.size()); + try (Jedis jedis = JedisPooledFactory.getJedisInstance(); Pipeline pipelined = jedis.pipelined()) { + statusKeys.forEach(key -> pipelined.llen(key)); + List counts = (List) pipelined.syncAndReturnAll(); + for (int i = 0; i < counts.size(); i++) { + if (counts.get(i) > 0) { + keysMap.put(statusKeys.get(i), counts.get(i).intValue()); + } + } + } + + //sort + List> list = new ArrayList<>(keysMap.entrySet()); + list.sort((o1, o2) -> o2.getValue() - o1.getValue()); + list.forEach(e -> resultMap.put(e.getKey(), e.getValue())); + + return resultMap; + } + + /** + * count GlobalSession total by status + * + * @param values + * @return Long + */ + public Long countByGlobalSessions(GlobalStatus[] values) { + List statusKeys = new ArrayList<>(); + Long total = 0L; + for (GlobalStatus status : values) { + statusKeys.add(buildGlobalStatus(status.getCode())); + } + try (Jedis jedis = JedisPooledFactory.getJedisInstance(); Pipeline pipelined = jedis.pipelined()) { + statusKeys.stream().forEach(statusKey -> pipelined.llen(statusKey)); + List list = (List)(List)pipelined.syncAndReturnAll(); + if (list.size() > 0) { + total = list.stream().mapToLong(value -> value).sum(); + } + return total; + } + } + + private List convertStatusKeys(GlobalStatus[] statuses) { + List statusKeys = new ArrayList<>(); + for (int i = 0; i < statuses.length; i++) { + statusKeys.add(buildGlobalStatus(statuses[i].getCode())); + } + return statusKeys; + } + + private void dogetXidsForTargetMapRecursive(Map targetMap, long start, long end, + long queryCount, List> listList) { + + long total = listList.stream().mapToLong(List::size).sum(); + + if (total >= queryCount) { + return; + } + // when start greater than offset(totalCount) + if (start >= queryCount) { + return; + } + + if (targetMap.size() == 0) { + return; + } + + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + for (String key : targetMap.keySet()) { + final long sum = listList.stream().mapToLong(List::size).sum(); + final long diffCount = queryCount - sum; + if (diffCount <= 0) { + return; + } + List list; + if (end - start >= diffCount) { + long endNew = start + diffCount - 1; + list = jedis.lrange(key, start, endNew); + } else { + list = jedis.lrange(key, start, end); + } + + if (list.size() > 0 && sum < queryCount) { + listList.add(list); + } else { + if (list.size() == 0) { + targetMap.remove(key); + } + } + } + } + long startNew = end + 1; + long endNew = startNew + end - start; + dogetXidsForTargetMapRecursive(targetMap, startNew, endNew, queryCount, listList); + } + + private List> dogetXidsForTargetMap(Map targetMap, int start, int end, + int totalCount) { + List> listList = new ArrayList<>(); + try (Jedis jedis = JedisPooledFactory.getJedisInstance()) { + for (String key : targetMap.keySet()) { + final List list = jedis.lrange(key, start, end); + final long sum = listList.stream().mapToLong(List::size).sum(); + if (list.size() > 0 && sum < totalCount) { + listList.add(list); + } else { + start = 0; + end = totalCount - 1; + } + } + } + return listList; + } + + 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; + } + + /** + * Sets log query limit. + * + * @param logQueryLimit the log query limit + */ + public void setLogQueryLimit(int logQueryLimit) { + this.logQueryLimit = logQueryLimit; + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/AbstractTransactionStoreManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/AbstractTransactionStoreManager.java new file mode 100644 index 00000000..a4b019f5 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/AbstractTransactionStoreManager.java @@ -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.store; + +import io.seata.core.model.GlobalStatus; +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 readSession(GlobalStatus[] statuses, boolean withBranchSessions) { + return null; + } + + @Override + public List readSession(SessionCondition sessionCondition) { + return null; + } + + @Override + public void shutdown() { + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DbcpDataSourceProvider.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DbcpDataSourceProvider.java new file mode 100644 index 00000000..8880943b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DbcpDataSourceProvider.java @@ -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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DruidDataSourceProvider.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DruidDataSourceProvider.java new file mode 100644 index 00000000..4a33d946 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/DruidDataSourceProvider.java @@ -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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/HikariDataSourceProvider.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/HikariDataSourceProvider.java new file mode 100644 index 00000000..8adb354f --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/HikariDataSourceProvider.java @@ -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); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/SessionStorable.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/SessionStorable.java new file mode 100644 index 00000000..cdcebeb0 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/SessionStorable.java @@ -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); +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/StoreConfig.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/StoreConfig.java new file mode 100644 index 00000000..3903c4ad --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/StoreConfig.java @@ -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")); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/TransactionStoreManager.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/TransactionStoreManager.java new file mode 100644 index 00000000..b71d7054 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/store/TransactionStoreManager.java @@ -0,0 +1,141 @@ +/* + * 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.core.model.GlobalStatus; +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 global session. + * + * @param statuses the statuses + * @param withBranchSessions the withBranchSessions + * @return the global session list + */ + List readSession(GlobalStatus[] statuses, boolean withBranchSessions); + + /** + * Read session by status list. + * + * @param sessionCondition the session condition + * @return the list + */ + List 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 + "]"); + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/at/ATCore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/at/ATCore.java new file mode 100644 index 00000000..d7b06ae7 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/at/ATCore.java @@ -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.transaction.at; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.seata.common.exception.StoreException; +import io.seata.common.util.StringUtils; +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.common.Constants.AUTO_COMMIT; +import static io.seata.common.Constants.SKIP_CHECK_LOCK; +import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflict; + +/** + * The type at core. + * + * @author ph3636 + */ +public class ATCore extends AbstractCore { + + private ObjectMapper objectMapper; + + public ATCore(RemotingServer remotingServer) { + super(remotingServer); + } + + @Override + public BranchType getHandleBranchType() { + return BranchType.AT; + } + + @Override + protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) + throws TransactionException { + String applicationData = branchSession.getApplicationData(); + boolean autoCommit = true; + boolean skipCheckLock = false; + if (StringUtils.isNotBlank(applicationData)) { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + } + try { + Map data = objectMapper.readValue(applicationData, HashMap.class); + Object clientAutoCommit = data.get(AUTO_COMMIT); + if (clientAutoCommit != null && !(boolean)clientAutoCommit) { + autoCommit = (boolean)clientAutoCommit; + } + Object clientSkipCheckLock = data.get(SKIP_CHECK_LOCK); + if (clientSkipCheckLock instanceof Boolean) { + skipCheckLock = (boolean)clientSkipCheckLock; + } + } catch (IOException e) { + LOGGER.error("failed to get application data: {}", e.getMessage(), e); + } + } + try { + if (!branchSession.lock(autoCommit, skipCheckLock)) { + throw new BranchTransactionException(LockKeyConflict, + String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(), + branchSession.getBranchId())); + } + } catch (StoreException e) { + if (e.getCause() instanceof BranchTransactionException) { + throw new BranchTransactionException(((BranchTransactionException)e.getCause()).getCode(), + String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(), + branchSession.getBranchId())); + } + throw e; + } + } + + @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); + } + +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/saga/SagaCore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/saga/SagaCore.java new file mode 100644 index 00000000..d9285d17 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/saga/SagaCore.java @@ -0,0 +1,229 @@ +/* + * 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.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.RemotingServer; +import io.seata.core.rpc.netty.ChannelManager; +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 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 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: + SessionHelper.removeAllBranch(globalSession, !retrying); + LOGGER.info("Successfully committed SAGA global[" + globalSession.getXid() + "]"); + break; + case PhaseTwo_Rollbacked: + LOGGER.info("Successfully rollbacked SAGA global[" + globalSession.getXid() + "]"); + SessionHelper.removeAllBranch(globalSession, !retrying); + SessionHelper.endRollbacked(globalSession, retrying); + 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()); + SessionHelper.removeAllBranch(globalSession, !retrying); + globalSession.changeGlobalStatus(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,retrying); + LOGGER.error("Finally, failed to commit SAGA global[{}]", globalSession.getXid()); + return false; + } + default: + if (!retrying) { + globalSession.queueToRetryCommit(); + } 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: + SessionHelper.removeAllBranch(globalSession, !retrying); + LOGGER.info("Successfully rollbacked SAGA global[{}]",globalSession.getXid()); + break; + case PhaseTwo_RollbackFailed_Unretryable: + SessionHelper.endRollbackFailed(globalSession, retrying); + 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)) { + SessionHelper.removeAllBranch(globalSession, false); + SessionHelper.endCommitted(globalSession,false); + LOGGER.info("Global[{}] committed", globalSession.getXid()); + } else if (GlobalStatus.Rollbacked.equals(globalStatus) + || GlobalStatus.Finished.equals(globalStatus)) { + SessionHelper.removeAllBranch(globalSession, false); + SessionHelper.endRollbacked(globalSession, false); + LOGGER.info("Global[{}] rollbacked", globalSession.getXid()); + } else { + globalSession.changeGlobalStatus(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()); + } + } + } + + /** + * get saga ResourceId + * + * @param globalSession the globalSession + * @return sagaResourceId + */ + private String getSagaResourceId(GlobalSession globalSession) { + return globalSession.getApplicationId() + "#" + globalSession.getTransactionServiceGroup(); + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/tcc/TccCore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/tcc/TccCore.java new file mode 100644 index 00000000..e0171aa5 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/tcc/TccCore.java @@ -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; + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/xa/XACore.java b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/xa/XACore.java new file mode 100644 index 00000000..de4620d4 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/java/io/seata/server/transaction/xa/XACore.java @@ -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) { + + } + } +} diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.rpc.RegisterCheckAuthHandler b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.rpc.RegisterCheckAuthHandler new file mode 100644 index 00000000..1a54728b --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.rpc.RegisterCheckAuthHandler @@ -0,0 +1 @@ +io.seata.server.auth.DefaultCheckAuthHandler \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker new file mode 100644 index 00000000..874e8b91 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker @@ -0,0 +1,2 @@ +io.seata.server.storage.redis.lock.RedisDistributedLocker +io.seata.server.storage.db.lock.DataBaseDistributedLocker \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.db.DataSourceProvider b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.db.DataSourceProvider new file mode 100644 index 00000000..ac04a85e --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.core.store.db.DataSourceProvider @@ -0,0 +1,3 @@ +io.seata.server.store.DbcpDataSourceProvider +io.seata.server.store.DruidDataSourceProvider +io.seata.server.store.HikariDataSourceProvider \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.coordinator.AbstractCore b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.coordinator.AbstractCore new file mode 100644 index 00000000..a80662b0 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.coordinator.AbstractCore @@ -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 \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager new file mode 100644 index 00000000..bca40c85 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager @@ -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 \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager new file mode 100644 index 00000000..f2e82316 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager @@ -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 \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring-configuration-metadata.json b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 00000000..05449177 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,22 @@ +{ + "groups": [], + "properties": [ + { + "name": "logging.extend.kafka-appender.bootstrap-servers", + "type": "java.lang.String", + "defaultValue": "localhost:9092" + }, + { + "name": "logging.extend.kafka-appender.topic", + "type": "java.lang.String", + "defaultValue": "logback_to_logstash" + }, + { + "name": "logging.extend.logstash-appender.destination", + "type": "java.lang.String", + "defaultValue": "localhost:4560" + } + ], + "hints": [ + ] +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring.factories b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..915bf1b7 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ +io.seata.server.ServerApplicationListener \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/README-zh.md b/ruoyi-visual/ruoyi-seata-server/src/main/resources/README-zh.md new file mode 100644 index 00000000..05d1620e --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/README-zh.md @@ -0,0 +1,32 @@ +# 脚本说明 + +## [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 中添加配置 + +## 打包 +./mvnw -Prelease-seata -Dmaven.test.skip=true clean install -U diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/README.md b/ruoyi-visual/ruoyi-seata-server/src/main/resources/README.md new file mode 100644 index 00000000..ddabd559 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/README.md @@ -0,0 +1,33 @@ +# 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 + +## build packege +./mvnw -Prelease-seata -Dmaven.test.skip=true clean install -U + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/application.yml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/application.yml new file mode 100644 index 00000000..8df8aebf --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/application.yml @@ -0,0 +1,59 @@ +server: + port: 7091 + +spring: + application: + name: ruoyi-seata-server + profiles: + # 环境配置 + active: @profiles.active@ + +logging: + config: classpath:logback-spring.xml + file: + path: ./logs/seata +# extend: +# logstash-appender: +# destination: 127.0.0.1:4560 +# kafka-appender: +# bootstrap-servers: 127.0.0.1:9092 +# topic: logback_to_logstash + +console: + user: + username: seata + password: seata + +seata: + config: + # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 + type: nacos + nacos: + server-addr: @nacos.server@ + group: @nacos.discovery.group@ + namespace: ${spring.profiles.active} + username: + password: + ##if use MSE Nacos with auth, mutex with username/password attribute + #access-key: "" + #secret-key: "" + data-id: seata-server.properties + registry: + # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa + type: nacos + nacos: + application: ${spring.application.name} + server-addr: @nacos.server@ + group: @nacos.discovery.group@ + namespace: ${spring.profiles.active} + cluster: default + username: + password: + ##if use MSE Nacos with auth, mutex with username/password attribute + #access-key: "" + #secret-key: "" + security: + secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 + tokenValidityInMilliseconds: 1800000 + ignore: + urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/banner.txt b/ruoyi-visual/ruoyi-seata-server/src/main/resources/banner.txt new file mode 100644 index 00000000..e561e763 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/banner.txt @@ -0,0 +1,7 @@ +███████╗███████╗ █████╗ ████████╗ █████╗ +██╔════╝██╔════╝██╔══██╗╚══██╔══╝██╔══██╗ +███████╗█████╗ ███████║ ██║ ███████║ +╚════██║██╔══╝ ██╔══██║ ██║ ██╔══██║ +███████║███████╗██║ ██║ ██║ ██║ ██║ +╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback-spring.xml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..7d7c2cf2 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback-spring.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/console-appender.xml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/console-appender.xml new file mode 100644 index 00000000..e323f680 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/console-appender.xml @@ -0,0 +1,13 @@ + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/file-appender.xml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/file-appender.xml new file mode 100644 index 00000000..d0877544 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/file-appender.xml @@ -0,0 +1,69 @@ + + + + + + + + + ${LOG_FILE_PATH}/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.all.log + true + + ${LOG_FILE_PATH}/history/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.all.%d{yyyy-MM-dd}.%i.log.gz + 2GB + 7 + 7GB + true + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + + + + WARN + ACCEPT + DENY + + ${LOG_FILE_PATH}/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.warn.log + true + + ${LOG_FILE_PATH}/history/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.warn.%d{yyyy-MM-dd}.%i.log.gz + 2GB + 7 + 7GB + true + + + ${FILE_LOG_PATTERN} + UTF-8 + + + + + + + ERROR + ACCEPT + DENY + + ${LOG_FILE_PATH}/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.error.log + true + + ${LOG_FILE_PATH}/history/${APPLICATION_NAME:-seata-server}.${RPC_PORT}.error.%d{yyyy-MM-dd}.%i.log.gz + 2GB + 7 + 7GB + true + + + ${FILE_LOG_PATTERN} + UTF-8 + + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/kafka-appender.xml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/kafka-appender.xml new file mode 100644 index 00000000..f2dc7167 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/kafka-appender.xml @@ -0,0 +1,34 @@ + + + + + + + + + + { + "@timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}", + "level":"%p", + "app_name":"${APPLICATION_NAME:-seata-server}", + "PORT": ${RPC_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" +} + + + ${KAFKA_TOPIC} + + + bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS} + acks=0 + linger.ms=1000 + max.block.ms=0 + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/logstash-appender.xml b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/logstash-appender.xml new file mode 100644 index 00000000..75f10126 --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/logback/logstash-appender.xml @@ -0,0 +1,29 @@ + + + + + + + + ${LOGSTASH_DESTINATION} + + + + + + { + "app_name": "${APPLICATION_NAME:-seata-server}" + } + + + + net.logstash.logback.composite.LogstashVersionJsonProvider + + net.logstash.logback.composite.loggingevent.JsonMessageJsonProvider + net.logstash.logback.composite.loggingevent.TagsJsonProvider + net.logstash.logback.composite.loggingevent.LogstashMarkersJsonProvider + net.logstash.logback.composite.loggingevent.ArgumentsJsonProvider + + + diff --git a/ruoyi-visual/ruoyi-seata-server/src/main/resources/lua/redislocker/redislock.lua b/ruoyi-visual/ruoyi-seata-server/src/main/resources/lua/redislocker/redislock.lua new file mode 100644 index 00000000..b004408c --- /dev/null +++ b/ruoyi-visual/ruoyi-seata-server/src/main/resources/lua/redislocker/redislock.lua @@ -0,0 +1,52 @@ +-- +-- User: tianyu.li +-- Date: 2021/1/19 +-- +-- init data +local array = {}; local result; local keySize = ARGV[1]; local argSize = ARGV[2]; +-- Loop through all keys to see if they can be used , when a key is not available, exit +for i= 1, keySize do + -- search lock xid + result = redis.call('HGET',KEYS[i],'xid'); + -- if lock xid is nil + if (not result) + -- set 'no' mean There is need to store lock information + then array[i]='no' + else + if (result ~= ARGV[3]) + then + -- return fail + return result + else + -- set 'yes' mean There is not need to store lock information + array[i]= 'yes' + end + end +end +-- Loop through array +for i =1, keySize do + -- if is no ,The lock information is stored + if(array[i] == 'no') + then + -- set xid + redis.call('HSET',KEYS[i],'xid',ARGV[3]); + -- set transactionId + redis.call('HSET',KEYS[i],'transactionId',ARGV[(i-1)*6+4]); + -- set branchId + redis.call('HSET',KEYS[i],'branchId',ARGV[(i-1)*6+5]); + -- set resourceId + redis.call('HSET',KEYS[i],'resourceId',ARGV[(i-1)*6+6]); + -- set tableName + redis.call('HSET',KEYS[i],'tableName',ARGV[(i-1)*6+7]); + -- set rowKey + redis.call('HSET',KEYS[i],'rowKey',ARGV[(i-1)*6+8]); + -- set pk + redis.call('HSET',KEYS[i],'pk',ARGV[(i-1)*6+9]); + -- exit if + end +-- exit for +end +-- set SEATA_GLOBAL_LOCK +redis.call('HSET',KEYS[(keySize+1)],KEYS[(keySize+2)],ARGV[(argSize+0)]); +-- return success +return ARGV[3] diff --git a/sql/ry-seata.sql b/sql/ry-seata.sql index 2e7e7e42..b95613c4 100644 --- a/sql/ry-seata.sql +++ b/sql/ry-seata.sql @@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS `global_table` `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), - KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), + KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; @@ -62,9 +62,10 @@ CREATE TABLE IF NOT EXISTS `lock_table` `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), - KEY `idx_branch_id` (`branch_id`) + KEY `idx_branch_id` (`branch_id`), + KEY `idx_xid_and_branch_id` (`xid` , `branch_id`) ) ENGINE = InnoDB - DEFAULT CHARSET = utf8; + DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` (