From f13a0825df2a48486377088dd5ffa2bed0af0f14 Mon Sep 17 00:00:00 2001 From: skyesx Date: Mon, 7 Oct 2019 18:50:59 +0800 Subject: [PATCH] Easytransaction version 1.4.0 (#133) * upgrade Fescar0.3.1 to Seata 0.8.1 * support ET run without Zookeeper * implement DB based Master selector * implement DB based StringCodec * implement DB based snowflaker * fix #128 --- easytrans-core/pom.xml | 8 +- .../easytrans/core/ConsistentGuardian.java | 1 + .../easytrans/idgen/impl/SnowFlake.java | 78 +++++++ .../impl/ZkBasedSnowFlakeIdGenerator.java | 70 ------ .../ServerSideTransactionLogMonitor.java | 3 +- .../easytrans/protocol/TransactionId.java | 5 +- .../autocps/AbstractAutoCpsMethod.java | 14 +- .../AutoCpsLocalTransactionExecutor.java | 3 +- .../protocol/autocps/EtDataSourceManager.java | 75 +++--- .../yiqiniu/easytrans/util/ReflectUtil.java | 6 +- easytrans-demo/interface-call-nozk/.gitignore | 13 ++ .../.gitignore | 14 ++ .../interfacecallnozk-order-service/pom.xml | 88 +++++++ .../easytrans/demos/order/impl/Constant.java | 5 + .../demos/order/impl/OrderApplication.java | 31 +++ .../demos/order/impl/OrderController.java | 22 ++ .../demos/order/impl/OrderService.java | 66 ++++++ .../src/main/resources/application.yml | 53 +++++ .../src/main/resources/createDatabase.sql | 69 ++++++ .../src/main/resources/log4j.properties | 28 +++ .../interfacecallnozk-wallet-api/.gitignore | 13 ++ .../interfacecallnozk-wallet-api/pom.xml | 59 +++++ .../wallet/api/WalletPayMoneyService.java | 58 +++++ .../wallet/api/WalletServiceApiConstant.java | 5 + .../api/requestcfg/WalletPayRequestCfg.java | 15 ++ .../.gitignore | 13 ++ .../interfacecallnozk-wallet-service/pom.xml | 82 +++++++ .../demos/wallet/impl/WalletApplication.java | 18 ++ .../demos/wallet/impl/WalletService.java | 62 +++++ .../src/main/resources/application.yml | 54 +++++ .../src/main/resources/createDatabase.sql | 67 ++++++ .../src/main/resources/log4j.properties | 28 +++ easytrans-demo/interface-call-nozk/pom.xml | 61 +++++ easytrans-demo/interface-call-nozk/readme.md | 6 + easytrans-demo/tcc-and-fescar/pom.xml | 2 +- .../demos/wallet/impl/CouponApplication.java | 3 +- .../src/main/resources/createDatabase.sql | 4 +- .../.gitignore | 11 + .../LICENSE | 201 ++++++++++++++++ .../pom.xml | 40 ++++ .../readme.md | 22 ++ .../DatabaseExtensionSuiteProperties.java | 65 ++++++ .../DatabaseExtensionsSuiteConfiguration.java | 92 ++++++++ .../database/DatabaseMasterSelectorImpl.java | 221 ++++++++++++++++++ .../DatabaseSnowFlakeIdGenerator.java | 24 ++ .../database/DatabaseStringCodecImpl.java | 192 +++++++++++++++ .../DefaultGetExtensionSuiteDatasource.java | 29 +++ .../EnableExtensionSuiteDatabaseImpl.java | 22 ++ .../database/GetExtentionSuiteDatabase.java | 13 ++ easytrans-log-database-starter/readme.md | 5 +- .../RestRibbonEasyTransRpcProviderImpl.java | 4 +- easytrans-starter/pom.xml | 6 + .../easytrans/EasyTransCoreConfiguration.java | 2 + .../test/EasyTransTestConfiguration.java | 5 +- .../src/test/resources/application.yml | 19 +- .../src/test/resources/log4j.properties | 8 +- pom.xml | 9 +- readme.md | 30 ++- 58 files changed, 2079 insertions(+), 143 deletions(-) create mode 100644 easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/SnowFlake.java create mode 100644 easytrans-demo/interface-call-nozk/.gitignore create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/.gitignore create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/pom.xml create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/application.yml create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/createDatabase.sql create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/log4j.properties create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/.gitignore create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/pom.xml create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletPayMoneyService.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/requestcfg/WalletPayRequestCfg.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/.gitignore create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/pom.xml create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/application.yml create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/createDatabase.sql create mode 100644 easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/log4j.properties create mode 100644 easytrans-demo/interface-call-nozk/pom.xml create mode 100644 easytrans-demo/interface-call-nozk/readme.md create mode 100644 easytrans-extensionsuite-database-starter/.gitignore create mode 100644 easytrans-extensionsuite-database-starter/LICENSE create mode 100644 easytrans-extensionsuite-database-starter/pom.xml create mode 100644 easytrans-extensionsuite-database-starter/readme.md create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionSuiteProperties.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionsSuiteConfiguration.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseMasterSelectorImpl.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseSnowFlakeIdGenerator.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseStringCodecImpl.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DefaultGetExtensionSuiteDatasource.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/EnableExtensionSuiteDatabaseImpl.java create mode 100644 easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/GetExtentionSuiteDatabase.java diff --git a/easytrans-core/pom.xml b/easytrans-core/pom.xml index 7dea33c..6081262 100644 --- a/easytrans-core/pom.xml +++ b/easytrans-core/pom.xml @@ -44,10 +44,10 @@ - com.alibaba.fescar - fescar-rm-datasource - 0.3.1 - + io.seata + seata-rm-datasource + 0.8.1 + diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/core/ConsistentGuardian.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/core/ConsistentGuardian.java index 5f5b1e2..efc1e55 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/core/ConsistentGuardian.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/core/ConsistentGuardian.java @@ -74,6 +74,7 @@ public boolean process(LogProcessContext logCtx){ for(int i = 0;i < orderedContents.size() ; i++){ Content content = orderedContents.get(i); //check log order + LOG.info("log content item {}, cid {}, tostring : {}", i,content.getcId(),content.toString()); Assert.isTrue(content.getcId() != null && content.getcId().equals(i + 1),"content list did not sort or contentId is null"); Class proccessorClass = ContentType.getById(content.getLogType()).getProccessorClass(); diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/SnowFlake.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/SnowFlake.java new file mode 100644 index 0000000..a2b66a1 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/SnowFlake.java @@ -0,0 +1,78 @@ +package com.yiqiniu.easytrans.idgen.impl; + +public class SnowFlake { + /** + * modified from https://github.com/beyondfengyu/SnowFlake + */ + + + /** + * 每一部分占用的位数 + */ + private final static long TIMESTAMP_SECOND_BIT = 32; + private final static long SEQUENCE_BIT = 16; //序列号占用的位数 4096 + public final static long MACHINE_BIT = 64 - TIMESTAMP_SECOND_BIT - SEQUENCE_BIT; //机器标识占用的位数 32 + /** + * 每一部分的最大值 + */ + private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); + private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); + + /** + * 每一部分向左的位移 + */ + private final static long TIMESTMP_LEFT = SEQUENCE_BIT; + private final static long MACHINE_LEFT = TIMESTMP_LEFT + MACHINE_BIT; + + private long machineId; //机器标识 + private long sequence = 0L; //序列号 + private long lastStmp = -1L;//上一次时间戳 + + public SnowFlake(long machineId) { + if (machineId > MAX_MACHINE_NUM || machineId < 0) { + throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); + } + this.machineId = machineId; + } + + /** + * 产生下一个ID + * + * @return + */ + public synchronized long nextId() { + long currStmp = getCurrentSecond(); + if (currStmp < lastStmp) { + throw new RuntimeException("Clock moved backwards. Refusing to generate id"); + } + + if (currStmp == lastStmp) { + //相同秒内,序列号自增 + sequence = (sequence + 1) & MAX_SEQUENCE; + //同一秒的序列数已经达到最大 + if (sequence == 0L) { + throw new RuntimeException("max tp/ms reached!"); + } + } else { + //不同毫秒内,序列号置为0 + sequence = 0L; + } + + lastStmp = currStmp; + + return machineId << MACHINE_LEFT // 机器标识部分 + | currStmp << TIMESTMP_LEFT // 时间戳部分 + | sequence; //序列号部分 + } + + + private long getCurrentSecond() { + long mill = System.currentTimeMillis() / 1000; + return mill; + } + + + + + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/ZkBasedSnowFlakeIdGenerator.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/ZkBasedSnowFlakeIdGenerator.java index 831662e..b862717 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/ZkBasedSnowFlakeIdGenerator.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/idgen/impl/ZkBasedSnowFlakeIdGenerator.java @@ -36,76 +36,6 @@ public long getCurrentTrxId(String busCode) { } - /** - * modified from https://github.com/beyondfengyu/SnowFlake - */ - public static class SnowFlake { - /** - * 每一部分占用的位数 - */ - private final static long TIMESTAMP_SECOND_BIT = 32; - private final static long SEQUENCE_BIT = 16; //序列号占用的位数 4096 - private final static long MACHINE_BIT = 64 - TIMESTAMP_SECOND_BIT - SEQUENCE_BIT; //机器标识占用的位数 32 - /** - * 每一部分的最大值 - */ - private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); - private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); - - /** - * 每一部分向左的位移 - */ - private final static long TIMESTMP_LEFT = SEQUENCE_BIT; - private final static long MACHINE_LEFT = TIMESTMP_LEFT + MACHINE_BIT; - - private long machineId; //机器标识 - private long sequence = 0L; //序列号 - private long lastStmp = -1L;//上一次时间戳 - - public SnowFlake(long machineId) { - if (machineId > MAX_MACHINE_NUM || machineId < 0) { - throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); - } - this.machineId = machineId; - } - - /** - * 产生下一个ID - * - * @return - */ - public synchronized long nextId() { - long currStmp = getCurrentSecond(); - if (currStmp < lastStmp) { - throw new RuntimeException("Clock moved backwards. Refusing to generate id"); - } - - if (currStmp == lastStmp) { - //相同秒内,序列号自增 - sequence = (sequence + 1) & MAX_SEQUENCE; - //同一秒的序列数已经达到最大 - if (sequence == 0L) { - throw new RuntimeException("max tp/ms reached!"); - } - } else { - //不同毫秒内,序列号置为0 - sequence = 0L; - } - - lastStmp = currStmp; - - return machineId << MACHINE_LEFT // 机器标识部分 - | currStmp << TIMESTMP_LEFT // 时间戳部分 - | sequence; //序列号部分 - } - - - private long getCurrentSecond() { - long mill = System.currentTimeMillis() / 1000; - return mill; - } - - } } diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java index 96caeb4..8215af5 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java @@ -4,7 +4,8 @@ import java.util.Date; import java.util.List; -import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import org.springframework.util.CollectionUtils; + import com.yiqiniu.easytrans.core.ConsistentGuardian; import com.yiqiniu.easytrans.log.TransactionLogReader; import com.yiqiniu.easytrans.log.vo.LogCollection; diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/TransactionId.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/TransactionId.java index ee3fe21..63ed41a 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/TransactionId.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/TransactionId.java @@ -72,7 +72,10 @@ public boolean equals(Object obj) { return true; } - + @Override + public String toString() { + return "TransactionId [appId=" + appId + ", busCode=" + busCode + ", trxId=" + trxId + "]"; + } } diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AbstractAutoCpsMethod.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AbstractAutoCpsMethod.java index 1d3889d..3c49127 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AbstractAutoCpsMethod.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AbstractAutoCpsMethod.java @@ -7,14 +7,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.fescar.core.context.RootContext; -import com.alibaba.fescar.core.exception.TransactionException; -import com.alibaba.fescar.core.model.Resource; -import com.alibaba.fescar.rm.datasource.DataSourceManager; import com.yiqiniu.easytrans.core.EasytransConstant; import com.yiqiniu.easytrans.filter.MetaDataFilter; import com.yiqiniu.easytrans.protocol.TransactionId; +import io.seata.core.context.RootContext; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.rm.DefaultResourceManager; + public abstract class AbstractAutoCpsMethod

, R extends Serializable> implements AutoCpsMethod { { @@ -49,7 +51,7 @@ public final void doAutoCpsCommit(P param) { if (ds instanceof Resource) { Resource rs = (Resource) ds; try { - DataSourceManager.get().branchCommit(getFescarXid(transactionId), callSeq, rs.getResourceId(), null); + DefaultResourceManager.get().branchCommit(BranchType.AT,getFescarXid(transactionId), callSeq, rs.getResourceId(), null); } catch (TransactionException e) { LOGGER.error("transaction commit exception occour , code:" + e.getCode(), e); throw new RuntimeException("transaction exception", e); @@ -74,7 +76,7 @@ public final void doAutoCpsRollback(P param) { if (ds instanceof Resource) { Resource rs = (Resource) ds; try { - DataSourceManager.get().branchRollback(getFescarXid(transactionId), callSeq, rs.getResourceId(), null); + DefaultResourceManager.get().branchRollback(BranchType.AT,getFescarXid(transactionId), callSeq, rs.getResourceId(), null); } catch (TransactionException e) { LOGGER.error("transaction roll back exception occour , code:" + e.getCode(), e); throw new RuntimeException("transaction exception", e); diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AutoCpsLocalTransactionExecutor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AutoCpsLocalTransactionExecutor.java index 7618071..de91d75 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AutoCpsLocalTransactionExecutor.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/AutoCpsLocalTransactionExecutor.java @@ -2,7 +2,8 @@ import java.util.concurrent.Callable; -import com.alibaba.fescar.core.context.RootContext; +import io.seata.core.context.RootContext; + /** * 用于执行本地事务时,确保更新、Select for update等操作能获取正确值 diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java index e998c56..51543c2 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java @@ -1,6 +1,5 @@ package com.yiqiniu.easytrans.protocol.autocps; -import static com.alibaba.fescar.core.exception.TransactionExceptionCode.BranchRollbackFailed_Retriable; import java.sql.Blob; import java.sql.Connection; @@ -18,33 +17,38 @@ import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.DataSourceUtils; -import com.alibaba.fescar.common.exception.ShouldNeverHappenException; -import com.alibaba.fescar.common.util.StringUtils; -import com.alibaba.fescar.core.exception.TransactionException; -import com.alibaba.fescar.core.model.BranchStatus; -import com.alibaba.fescar.core.model.BranchType; -import com.alibaba.fescar.core.model.Resource; -import com.alibaba.fescar.rm.datasource.ConnectionProxy; -import com.alibaba.fescar.rm.datasource.DataSourceManager; -import com.alibaba.fescar.rm.datasource.DataSourceProxy; -import com.alibaba.fescar.rm.datasource.sql.struct.Field; -import com.alibaba.fescar.rm.datasource.sql.struct.TableMeta; -import com.alibaba.fescar.rm.datasource.sql.struct.TableMetaCache; -import com.alibaba.fescar.rm.datasource.undo.AbstractUndoExecutor; -import com.alibaba.fescar.rm.datasource.undo.BranchUndoLog; -import com.alibaba.fescar.rm.datasource.undo.SQLUndoLog; -import com.alibaba.fescar.rm.datasource.undo.UndoExecutorFactory; -import com.alibaba.fescar.rm.datasource.undo.UndoLogParserFactory; import com.yiqiniu.easytrans.core.EasytransConstant; import com.yiqiniu.easytrans.filter.MetaDataFilter; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.BlobUtils; +import io.seata.common.util.StringUtils; +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.Resource; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceManager; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCache; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoExecutorFactory; +import io.seata.rm.datasource.undo.UndoLogParserFactory; + public class EtDataSourceManager extends DataSourceManager { private static Logger LOGGER = LoggerFactory.getLogger(EtDataSourceManager.class); public static void initEtDataSourceManager() { - DataSourceManager.set(new EtDataSourceManager()); - LOGGER.info("Trigger EtDataSourceManager init!"); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.AT, new EtDataSourceManager()); + LOGGER.info("Trigger EtSeataDataSourceManager init!"); } private static final String UNDO_LOG_TABLE_NAME = "undo_log"; @@ -61,7 +65,8 @@ public static void initEtDataSourceManager() { @Override - public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String lockKey) throws TransactionException { + public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKey) throws TransactionException { +// public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String lockKey) throws TransactionException { Integer callSeq = MetaDataFilter.getMetaData(EasytransConstant.CallHeadKeys.CALL_SEQ); @@ -98,7 +103,7 @@ public Long branchRegister(BranchType branchType, String resourceId, String clie } @Override - public void branchReport(String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException { + public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException { // do nothing here, because in ET do not have remote centralized TC } @@ -123,7 +128,8 @@ public void registerResource(Resource resource) { } @Override - public BranchStatus branchCommit(String xid, long branchId, String resourceId, String applicationData) throws TransactionException { + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { executeInTransaction(resourceId, new ExecuteWithConn() { @@ -131,7 +137,7 @@ public BranchStatus branchCommit(String xid, long branchId, String resourceId, S public Void execute(Connection conn) throws SQLException { //get undo log - List rollbackInfos = getRollbackInfo(xid, branchId, conn, resourceId); + List rollbackInfos = getRollbackInfo(xid, branchId, conn, resourceId); // clean the lock cleanLocks(xid, branchId, conn, rollbackInfos, resourceId); @@ -147,7 +153,8 @@ public Void execute(Connection conn) throws SQLException { } @Override - public BranchStatus branchRollback(String xid, long branchId, String resourceId, String applicationData) throws TransactionException { + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { executeInTransaction(resourceId, new ExecuteWithConn() { @@ -155,7 +162,7 @@ public BranchStatus branchRollback(String xid, long branchId, String resourceId, public Void execute(Connection conn) throws SQLException { // rollback info - List rollbackInfos = getRollbackInfo(xid, branchId, conn, resourceId); + List rollbackInfos = getRollbackInfo(xid, branchId, conn, resourceId); // recover the records undoRecords(conn, resourceId, rollbackInfos); @@ -179,11 +186,11 @@ private void deleteUndoLog(String xid, long branchId, Connection conn) throws SQ deletePST.executeUpdate(); } - private void undoRecords(Connection conn, String resourceId, List rollbackInfos) throws SQLException { + private void undoRecords(Connection conn, String resourceId, List rollbackInfos) throws SQLException { DataSourceProxy dataSourceProxy = get(resourceId); - for (String rollbackInfo : rollbackInfos) { + for (byte[] rollbackInfo : rollbackInfos) { BranchUndoLog branchUndoLog = UndoLogParserFactory.getInstance().decode(rollbackInfo); for (SQLUndoLog sqlUndoLog : branchUndoLog.getSqlUndoLogs()) { @@ -197,7 +204,7 @@ private void undoRecords(Connection conn, String resourceId, List rollba } - private List getRollbackInfo(String xid, long branchId, Connection conn, String resourceId) throws SQLException { + private List getRollbackInfo(String xid, long branchId, Connection conn, String resourceId) throws SQLException { ResultSet rs = null; PreparedStatement selectPST = null; @@ -206,12 +213,12 @@ private List getRollbackInfo(String xid, long branchId, Connection conn, selectPST.setString(2, xid); rs = selectPST.executeQuery(); - List result = new ArrayList<>(); + List result = new ArrayList<>(); try { while (rs.next()) { Blob b = rs.getBlob("rollback_info"); - String rollbackInfo = StringUtils.blob2string(b); + byte[] rollbackInfo = BlobUtils.blob2Bytes(b); result.add(rollbackInfo); } } finally { @@ -222,13 +229,13 @@ private List getRollbackInfo(String xid, long branchId, Connection conn, } - private void cleanLocks(String xid, long branchId, Connection conn, List rollbackInfos, String resourceId) throws SQLException { + private void cleanLocks(String xid, long branchId, Connection conn, List rollbackInfos, String resourceId) throws SQLException { DataSourceProxy dataSourceProxy = get(resourceId); PreparedStatement pdst = conn.prepareStatement(DELETE_LOCK_SQL); int cleanRecords = 0; - for (String rollbackInfo : rollbackInfos) { + for (byte[] rollbackInfo : rollbackInfos) { BranchUndoLog branchUndoLog = UndoLogParserFactory.getInstance().decode(rollbackInfo); for (SQLUndoLog sqlUndoLog : branchUndoLog.getSqlUndoLogs()) { @@ -380,7 +387,7 @@ private R executeInTransaction(String resourceId, ExecuteWithConn execute LOGGER.warn("Failed to close JDBC resource ... ", rollbackEx); } } - throw new TransactionException(BranchRollbackFailed_Retriable, e); + throw new TransactionException(TransactionExceptionCode.BranchRollbackFailed_Retriable, e); } finally { try { diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java index 93c6bb0..52d8e43 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; +import org.springframework.aop.support.AopUtils; + import com.yiqiniu.easytrans.protocol.BusinessIdentifer; import com.yiqiniu.easytrans.protocol.BusinessProvider; import com.yiqiniu.easytrans.protocol.EasyTransRequest; @@ -28,7 +30,7 @@ private ReflectUtil(){ @SuppressWarnings("unchecked") public static Class> getRequestClass(Class> providerClass){ - List> pType = getTypeArguments(BusinessProvider.class, providerClass); + List> pType = getTypeArguments(BusinessProvider.class, providerClass); if(pType != null && pType.size() == 1) { return (Class>) pType.get(0); } else { @@ -42,7 +44,7 @@ private ReflectUtil(){ if (provider instanceof RequestClassAware) { return ((RequestClassAware) provider).getRequestClass(); } else { - return getRequestClass((Class>) provider.getClass()); + return getRequestClass((Class>) AopUtils.getTargetClass(provider)); } } diff --git a/easytrans-demo/interface-call-nozk/.gitignore b/easytrans-demo/interface-call-nozk/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/interface-call-nozk/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/.gitignore b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/.gitignore new file mode 100644 index 0000000..a22b96d --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/.gitignore @@ -0,0 +1,14 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store +.springBeans \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/pom.xml b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/pom.xml new file mode 100644 index 0000000..0386288 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/pom.xml @@ -0,0 +1,88 @@ + + 4.0.0 + + interfacecallnozk-order-service + + + com.yiqiniu.easytrans.demos + interfacecallnozk + ${revision} + ../pom.xml + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-starter + + + + + com.yiqiniu.easytrans + easytrans-queue-kafka-starter + + + + org.apache.curator + curator-recipes + + + + org.apache.zookeeper + zookeeper + + + + + + com.yiqiniu.easytrans + easytrans-extensionsuite-database-starter + + + + com.yiqiniu.easytrans.demos + interfacecallnozk-wallet-api + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + + + + org.apache.httpcomponents + httpclient + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java new file mode 100644 index 0000000..bdea2ad --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java @@ -0,0 +1,5 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +public class Constant { + public static final String APPID="order-service"; +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java new file mode 100644 index 0000000..0565683 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java @@ -0,0 +1,31 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.yiqiniu.easytrans.EnableEasyTransaction; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService; +import com.yiqiniu.easytrans.demos.wallet.api.requestcfg.WalletPayRequestCfg; +import com.yiqiniu.easytrans.extensionsuite.impl.database.EnableExtensionSuiteDatabaseImpl; +import com.yiqiniu.easytrans.util.CallWrapUtil; + +@SpringBootApplication +@EnableExtensionSuiteDatabaseImpl +@EnableEasyTransaction +@EnableTransactionManagement +public class OrderApplication { + public static void main(String[] args) { + SpringApplication.run(OrderApplication.class, args); + } + + /** + * create WalletPayMoneyService instance, you can inject the instance to call wallet tcc service + */ + @Bean + public WalletPayMoneyService payService(CallWrapUtil util) { + return util.createTransactionCallInstance(WalletPayMoneyService.class, WalletPayRequestCfg.class); + } + +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java new file mode 100644 index 0000000..9e1eed6 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java @@ -0,0 +1,22 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class OrderController { + + @Autowired + private OrderService orderService; + + + @RequestMapping("/buySth") + @ResponseBody + public String buySomethingProxy(@RequestParam int userId,@RequestParam int money){ + return orderService.buySomething(userId, money); + } + +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java new file mode 100644 index 0000000..eea62bb --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java @@ -0,0 +1,66 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.annotation.Resource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayResponseVO; + +@Component +public class OrderService { + + + @Resource + private WalletPayMoneyService payService; + @Resource + private JdbcTemplate jdbcTemplate; + + + @Transactional + public String buySomething(int userId,long money){ + + int id = saveOrderRecord(userId, money); + WalletPayRequestVO request = new WalletPayRequestVO(); + request.setUserId(userId); + request.setPayAmount(money); + + WalletPayResponseVO pay = payService.pay(request); + return "id:" + id + " freeze:" + pay.getFreezeAmount(); + } + + + private Integer saveOrderRecord(final int userId, final long money) { + + final String INSERT_SQL = "INSERT INTO `order` (`order_id`, `user_id`, `money`, `create_time`) VALUES (NULL, ?, ?, ?);"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update( + new PreparedStatementCreator() { + @Override + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = + connection.prepareStatement(INSERT_SQL, new String[] {"id"}); + ps.setInt(1, userId); + ps.setLong(2, money); + ps.setDate(3, new Date(System.currentTimeMillis())); + return ps; + } + }, + keyHolder); + + return keyHolder.getKey().intValue(); + } + + +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/application.yml b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/application.yml new file mode 100644 index 0000000..036d900 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/application.yml @@ -0,0 +1,53 @@ +spring: + application: + name: order-service # the same with com.yiqiniu.easytrans.demos.order.Constant.APPID + datasource: # order service datasource config + url: jdbc:mysql://localhost:3306/order?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + +server: + port: 8080 + +# RIBBON用,也可以直接开启Eureka +order-service: + ribbon: + listOfServers: localhost:8080 + +wallet-service: + ribbon: + listOfServers: localhost:8081 + +easytrans: + extensionsuite: # 扩展套装,如基于数据库实现的 选主、字符串编码、id生成器、事务日志等 + database: + enabled: true + logCleanEnabled: true + logReservedDays: 14 + logCleanTime: 01:22:00 + tablePrefix: '' + dbSetting: # extension suite 公用的数据源的配置,不一定要配置为当前业务数据库,可公用一个配置,也可以每个业务库单独配置一个 + driverClassName: com.mysql.jdbc.Driver + url: jdbc:mysql://localhost:3306/translog?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 +# close the default implement to use database extensionsuite + log: + database: + enabled: false + master: + zk: + enabled: false + stringcodec: + zk: + enabled: false + idgen: + trxId: + zkSnow: + enabled: false + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/createDatabase.sql b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/createDatabase.sql new file mode 100644 index 0000000..48bef3b --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/createDatabase.sql @@ -0,0 +1,69 @@ +CREATE DATABASE `order` ; +USE `order`; +CREATE TABLE `order` ( + `order_id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `money` bigint(20) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; + + + -- 用于记录业务发起方的最终业务有没有执行 + -- p_开头的,代表本事务对应的父事务id + -- select for update查询时,若事务ID对应的记录不存在则事务一定失败了 + -- 记录存在,但status为0表示事务成功,为1表示事务失败(包含父事务和本事务) + -- 记录存在,但status为2表示本方法存在父事务,且父事务的最终状态未知 + -- 父事务的状态将由发起方通过 优先同步告知 失败则 消息形式告知 + CREATE TABLE `executed_trans` ( + `app_id` smallint(5) unsigned NOT NULL, + `bus_code` smallint(5) unsigned NOT NULL, + `trx_id` bigint(20) unsigned NOT NULL, + `p_app_id` smallint(5) unsigned DEFAULT NULL, + `p_bus_code` smallint(5) unsigned DEFAULT NULL, + `p_trx_id` bigint(20) unsigned DEFAULT NULL, + `status` tinyint(1) NOT NULL, + PRIMARY KEY (`app_id`,`bus_code`,`trx_id`), + KEY `parent` (`p_app_id`,`p_bus_code`,`p_trx_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `idempotent` ( + `src_app_id` smallint(5) unsigned NOT NULL COMMENT '来源AppID', + `src_bus_code` smallint(5) unsigned NOT NULL COMMENT '来源业务类型', + `src_trx_id` bigint(20) unsigned NOT NULL COMMENT '来源交易ID', + `app_id` smallint(5) NOT NULL COMMENT '调用APPID', + `bus_code` smallint(5) NOT NULL COMMENT '调用的业务代码', + `call_seq` smallint(5) NOT NULL COMMENT '同一事务同一方法内调用的次数', + `handler` smallint(5) NOT NULL COMMENT '处理者appid', + `called_methods` varchar(64) NOT NULL COMMENT '被调用过的方法名', + `md5` binary(16) NOT NULL COMMENT '参数摘要', + `sync_method_result` blob COMMENT '同步方法的返回结果', + `create_time` datetime NOT NULL COMMENT '执行时间', + `update_time` datetime NOT NULL, + `lock_version` smallint(32) NOT NULL COMMENT '乐观锁版本号', + PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + + + + +CREATE DATABASE `order_translog` ; +USE `order_translog`; + +CREATE TABLE `trans_log_detail` ( + `log_detail_id` int(11) NOT NULL AUTO_INCREMENT, + `trans_log_id` binary(12) NOT NULL, + `log_detail` blob, + `create_time` datetime NOT NULL, + PRIMARY KEY (`log_detail_id`), + KEY `app_id` (`trans_log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE `trans_log_unfinished` ( + `trans_log_id` binary(12) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`trans_log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SELECT * FROM translog.trans_log_detail; \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/log4j.properties b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/log4j.properties new file mode 100644 index 0000000..a296f5b --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-order-service/src/main/resources/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=info,stdout,logfile,errfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.appender.stdout.Threshold = DEBUG + +log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.DatePattern='.'yyyy-MM-dd +log4j.appender.logfile.File=logs/info.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.errfile=org.apache.log4j.RollingFileAppender +log4j.appender.errfile.MaxFileSize=5000KB +log4j.appender.errfile.MaxBackupIndex=3 +log4j.appender.errfile.File=logs/err.log +log4j.appender.errfile.layout=org.apache.log4j.PatternLayout +log4j.appender.errfile.layout.ConversionPattern=%d %p [%c] - %m%n +log4j.appender.errfile.Threshold = ERROR + +log4j.logger.org.apache.zookeeper=OFF +log4j.logger.com.alibaba=OFF +log4j.logger.druid.sql=OFF +log4j.logger.org.springframework=OFF +log4j.logger.com.yiqiniu.easytrans=ON +log4j.logger.com.yiqiniu.easytrans.core=TRACE diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/.gitignore b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/pom.xml b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/pom.xml new file mode 100644 index 0000000..1dc4ecd --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + + interfacecallnozk-wallet-api + + + com.yiqiniu.easytrans.demos + interfacecallnozk + ${revision} + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-core + + + + + com.yiqiniu.easytrans + easytrans-queue-kafka-starter + + + + org.apache.curator + curator-recipes + + + + org.apache.zookeeper + zookeeper + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletPayMoneyService.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletPayMoneyService.java new file mode 100644 index 0000000..d0f7268 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletPayMoneyService.java @@ -0,0 +1,58 @@ +package com.yiqiniu.easytrans.demos.wallet.api; + +import java.io.Serializable; + +/** + * define an interface for calling wallet + * the constraint of this interface is that: + * 1. only contains one method + * 2. the method only has one parameter, it's not basic class and implements Serializable interface + * 3. the return parameter should not be basic class too, and it has to implements Serializable interface + * 4. the return parameter can also be Future<>, the generalization parameter class should be like point 3 + */ +public interface WalletPayMoneyService { + + WalletPayResponseVO pay(WalletPayRequestVO request); + + public static class WalletPayRequestVO implements Serializable { + + private static final long serialVersionUID = 1L; + + private Integer userId; + + private Long payAmount; + + public Long getPayAmount() { + return payAmount; + } + + public void setPayAmount(Long payAmount) { + this.payAmount = payAmount; + } + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + } + } + + public static class WalletPayResponseVO implements Serializable{ + private static final long serialVersionUID = 1L; + private Long freezeAmount; + public Long getFreezeAmount() { + return freezeAmount; + } + public void setFreezeAmount(Long freezeAmount) { + this.freezeAmount = freezeAmount; + } + + @Override + public String toString() { + return "WalletPayTccMethodResult [freezeAmount=" + freezeAmount + + "]"; + } + } +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java new file mode 100644 index 0000000..8752d1c --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java @@ -0,0 +1,5 @@ +package com.yiqiniu.easytrans.demos.wallet.api; + +public class WalletServiceApiConstant { + public static final String APPID="wallet-service"; +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/requestcfg/WalletPayRequestCfg.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/requestcfg/WalletPayRequestCfg.java new file mode 100644 index 0000000..0ad553f --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/requestcfg/WalletPayRequestCfg.java @@ -0,0 +1,15 @@ +package com.yiqiniu.easytrans.demos.wallet.api.requestcfg; + +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayResponseVO; +import com.yiqiniu.easytrans.demos.wallet.api.WalletServiceApiConstant; +import com.yiqiniu.easytrans.protocol.BusinessIdentifer; +import com.yiqiniu.easytrans.protocol.tcc.TccMethodRequest; + +/** + * define the calling configuration for WalletPayMoneyService + */ +@BusinessIdentifer(appId=WalletServiceApiConstant.APPID,busCode="pay",rpcTimeOut=2000) +public class WalletPayRequestCfg extends WalletPayRequestVO implements TccMethodRequest{ + private static final long serialVersionUID = 1L; +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/.gitignore b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/pom.xml b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/pom.xml new file mode 100644 index 0000000..c5a1d8f --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + interfacecallnozk-wallet-service + + com.yiqiniu.easytrans.demos + interfacecallnozk + ${revision} + + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-starter + + + + + com.yiqiniu.easytrans + easytrans-queue-kafka-starter + + + + org.apache.curator + curator-recipes + + + + org.apache.zookeeper + zookeeper + + + + + + + com.yiqiniu.easytrans + easytrans-extensionsuite-database-starter + + + + com.yiqiniu.easytrans.demos + interfacecallnozk-wallet-api + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java new file mode 100644 index 0000000..485cd3f --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java @@ -0,0 +1,18 @@ +package com.yiqiniu.easytrans.demos.wallet.impl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.yiqiniu.easytrans.EnableEasyTransaction; +import com.yiqiniu.easytrans.extensionsuite.impl.database.EnableExtensionSuiteDatabaseImpl; + +@SpringBootApplication +@EnableExtensionSuiteDatabaseImpl +@EnableEasyTransaction +@EnableTransactionManagement +public class WalletApplication { + public static void main(String[] args) { + SpringApplication.run(WalletApplication.class, args); + } +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java new file mode 100644 index 0000000..6ee01a7 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java @@ -0,0 +1,62 @@ +package com.yiqiniu.easytrans.demos.wallet.impl; + +import javax.annotation.Resource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.yiqiniu.easytrans.core.EasyTransFacade; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.WalletPayMoneyService.WalletPayResponseVO; +import com.yiqiniu.easytrans.demos.wallet.api.requestcfg.WalletPayRequestCfg; +import com.yiqiniu.easytrans.protocol.BusinessProvider; +import com.yiqiniu.easytrans.protocol.tcc.EtTcc; + +@Component +public class WalletService { + + @Resource + private EasyTransFacade transaction; + @Resource + private JdbcTemplate jdbcTemplate; + + + //如果doTryPay的入参为集成了EasyTransRequest并带有BusinessIdentiffer的话则无需指定cfgClass + @Transactional + @EtTcc(confirmMethod="doConfirmPay",cancelMethod="doCancelPay",idempotentType=BusinessProvider.IDENPOTENT_TYPE_FRAMEWORK,cfgClass=WalletPayRequestCfg.class) + public WalletPayResponseVO doTryPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount + ? where user_id = ? and (total_amount - freeze_amount) >= ?;", + param.getPayAmount(),param.getUserId(),param.getPayAmount()); + + if(update != 1){ + throw new RuntimeException("can not find specific user id or have not enought money"); + } + + WalletPayResponseVO walletPayTccMethodResult = new WalletPayResponseVO(); + walletPayTccMethodResult.setFreezeAmount(param.getPayAmount()); + return walletPayTccMethodResult; + } + + + @Transactional + public void doConfirmPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount - ?, total_amount = total_amount - ? where user_id = ?;", + param.getPayAmount(),param.getPayAmount(),param.getUserId()); + + if(update != 1){ + throw new RuntimeException("unknow Exception!"); + } + } + + @Transactional + public void doCancelPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount - ? where user_id = ?;", + param.getPayAmount(),param.getUserId()); + if(update != 1){ + throw new RuntimeException("unknow Exception!"); + } + } + + +} diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/application.yml b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/application.yml new file mode 100644 index 0000000..41cd658 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/application.yml @@ -0,0 +1,54 @@ +spring: + application: + name: wallet-service # the same with com.yiqiniu.easytrans.demos.order.Constant.APPID + datasource: # order service datasource config + url: jdbc:mysql://localhost:3306/wallet?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + +server: + port: 8081 + +# RIBBON用,也可以直接开启Eureka +order-service: + ribbon: + listOfServers: localhost:8080 + +wallet-service: + ribbon: + listOfServers: localhost:8081 + +easytrans: + extensionsuite: # 扩展套装,如基于数据库实现的 选主、字符串编码、id生成器、事务日志等 + database: + enabled: true + logCleanEnabled: true + logReservedDays: 14 + logCleanTime: 01:22:00 + tablePrefix: '' + dbSetting: # extension suite 公用的数据源的配置,不一定要配置为当前业务数据库,可公用一个配置,也可以每个业务库单独配置一个 + driverClassName: com.mysql.jdbc.Driver + url: jdbc:mysql://localhost:3306/translog?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 +# close the default implement to use database extensionsuite + log: + database: + enabled: false + master: + zk: + enabled: false + stringcodec: + zk: + enabled: false + idgen: + trxId: + zkSnow: + enabled: false + + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/createDatabase.sql b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/createDatabase.sql new file mode 100644 index 0000000..f200387 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/createDatabase.sql @@ -0,0 +1,67 @@ +CREATE DATABASE `wallet` ; +USE `wallet`; +CREATE TABLE `wallet` ( + `user_id` int(11) NOT NULL, + `total_amount` bigint(20) NOT NULL, + `freeze_amount` bigint(20) NOT NULL, + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `wallet`.`wallet` (`user_id`, `total_amount`, `freeze_amount`) VALUES ('1', '10000000', '0'); + + + -- 用于记录业务发起方的最终业务有没有执行 + -- p_开头的,代表本事务对应的父事务id + -- select for update查询时,若事务ID对应的记录不存在则事务一定失败了 + -- 记录存在,但status为0表示事务成功,为1表示事务失败(包含父事务和本事务) + -- 记录存在,但status为2表示本方法存在父事务,且父事务的最终状态未知 + -- 父事务的状态将由发起方通过 优先同步告知 失败则 消息形式告知 + CREATE TABLE `executed_trans` ( + `app_id` smallint(5) unsigned NOT NULL, + `bus_code` smallint(5) unsigned NOT NULL, + `trx_id` bigint(20) unsigned NOT NULL, + `p_app_id` smallint(5) unsigned DEFAULT NULL, + `p_bus_code` smallint(5) unsigned DEFAULT NULL, + `p_trx_id` bigint(20) unsigned DEFAULT NULL, + `status` tinyint(1) NOT NULL, + PRIMARY KEY (`app_id`,`bus_code`,`trx_id`), + KEY `parent` (`p_app_id`,`p_bus_code`,`p_trx_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `idempotent` ( + `src_app_id` smallint(5) unsigned NOT NULL COMMENT '来源AppID', + `src_bus_code` smallint(5) unsigned NOT NULL COMMENT '来源业务类型', + `src_trx_id` bigint(20) unsigned NOT NULL COMMENT '来源交易ID', + `app_id` smallint(5) NOT NULL COMMENT '调用APPID', + `bus_code` smallint(5) NOT NULL COMMENT '调用的业务代码', + `call_seq` smallint(5) NOT NULL COMMENT '同一事务同一方法内调用的次数', + `handler` smallint(5) NOT NULL COMMENT '处理者appid', + `called_methods` varchar(64) NOT NULL COMMENT '被调用过的方法名', + `md5` binary(16) NOT NULL COMMENT '参数摘要', + `sync_method_result` blob COMMENT '同步方法的返回结果', + `create_time` datetime NOT NULL COMMENT '执行时间', + `update_time` datetime NOT NULL, + `lock_version` smallint(32) NOT NULL COMMENT '乐观锁版本号', + PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +CREATE DATABASE `wallet_translog` ; +USE `wallet_translog`; + +CREATE TABLE `trans_log_detail` ( + `log_detail_id` int(11) NOT NULL AUTO_INCREMENT, + `trans_log_id` binary(12) NOT NULL, + `log_detail` blob, + `create_time` datetime NOT NULL, + PRIMARY KEY (`log_detail_id`), + KEY `app_id` (`trans_log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE `trans_log_unfinished` ( + `trans_log_id` binary(12) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`trans_log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SELECT * FROM translog.trans_log_detail; \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/log4j.properties b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/log4j.properties new file mode 100644 index 0000000..a296f5b --- /dev/null +++ b/easytrans-demo/interface-call-nozk/interfacecallnozk-wallet-service/src/main/resources/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=info,stdout,logfile,errfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.appender.stdout.Threshold = DEBUG + +log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.DatePattern='.'yyyy-MM-dd +log4j.appender.logfile.File=logs/info.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.errfile=org.apache.log4j.RollingFileAppender +log4j.appender.errfile.MaxFileSize=5000KB +log4j.appender.errfile.MaxBackupIndex=3 +log4j.appender.errfile.File=logs/err.log +log4j.appender.errfile.layout=org.apache.log4j.PatternLayout +log4j.appender.errfile.layout.ConversionPattern=%d %p [%c] - %m%n +log4j.appender.errfile.Threshold = ERROR + +log4j.logger.org.apache.zookeeper=OFF +log4j.logger.com.alibaba=OFF +log4j.logger.druid.sql=OFF +log4j.logger.org.springframework=OFF +log4j.logger.com.yiqiniu.easytrans=ON +log4j.logger.com.yiqiniu.easytrans.core=TRACE diff --git a/easytrans-demo/interface-call-nozk/pom.xml b/easytrans-demo/interface-call-nozk/pom.xml new file mode 100644 index 0000000..b9a8bfb --- /dev/null +++ b/easytrans-demo/interface-call-nozk/pom.xml @@ -0,0 +1,61 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.5.13.RELEASE + + + com.yiqiniu.easytrans.demos + interfacecallnozk + ${revision} + pom + + + + UTF-8 + UTF-8 + 1.8 + 1.4.0 + + + + + interfacecallnozk-wallet-api + interfacecallnozk-wallet-service + interfacecallnozk-order-service + + + + + + + com.yiqiniu.easytrans + easytrans + ${revision} + pom + import + + + + com.yiqiniu.easytrans.demos + interfacecallnozk-wallet-api + ${revision} + + + com.yiqiniu.easytrans.demos + interfacecallnozk-wallet-service + ${revision} + + + com.yiqiniu.easytrans.demos + interfacecallnozk-order-service + ${revision} + + + + + \ No newline at end of file diff --git a/easytrans-demo/interface-call-nozk/readme.md b/easytrans-demo/interface-call-nozk/readme.md new file mode 100644 index 0000000..4118a26 --- /dev/null +++ b/easytrans-demo/interface-call-nozk/readme.md @@ -0,0 +1,6 @@ +## no zk +本DEMO演示了去掉ZK的配置,使用基于MYSQL的 +* 选主 +* ID生成 +* 字符串编码 +* 事务日志 \ No newline at end of file diff --git a/easytrans-demo/tcc-and-fescar/pom.xml b/easytrans-demo/tcc-and-fescar/pom.xml index 43d5ea3..4114db7 100644 --- a/easytrans-demo/tcc-and-fescar/pom.xml +++ b/easytrans-demo/tcc-and-fescar/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.3.0 + 1.4.0 diff --git a/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/CouponApplication.java b/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/CouponApplication.java index 5ceda0d..7cc3c68 100644 --- a/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/CouponApplication.java +++ b/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/CouponApplication.java @@ -8,9 +8,10 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.fescar.rm.datasource.DataSourceProxy; import com.yiqiniu.easytrans.EnableEasyTransaction; +import io.seata.rm.datasource.DataSourceProxy; + @SpringBootApplication @EnableEasyTransaction @EnableTransactionManagement diff --git a/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/resources/createDatabase.sql b/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/resources/createDatabase.sql index 27ca9b9..80a0983 100644 --- a/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/resources/createDatabase.sql +++ b/easytrans-demo/tcc-and-fescar/tccandfescar-coupon-service/src/main/resources/createDatabase.sql @@ -46,16 +46,16 @@ CREATE TABLE `idempotent` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, + `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), - KEY `idx_unionkey` (`xid`,`branch_id`) + UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; - CREATE TABLE `fescar_lock` ( `t_name` varchar(64) NOT NULL, `t_pk` varchar(128) NOT NULL, diff --git a/easytrans-extensionsuite-database-starter/.gitignore b/easytrans-extensionsuite-database-starter/.gitignore new file mode 100644 index 0000000..42fcf80 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/.gitignore @@ -0,0 +1,11 @@ +server_back.properties +*.class +*.log +*.log.* +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ diff --git a/easytrans-extensionsuite-database-starter/LICENSE b/easytrans-extensionsuite-database-starter/LICENSE new file mode 100644 index 0000000..c0ee812 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/easytrans-extensionsuite-database-starter/pom.xml b/easytrans-extensionsuite-database-starter/pom.xml new file mode 100644 index 0000000..0f8f8b2 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + EasyTransaction extensionsuite RDBS implement + + easytrans-extensionsuite-database-starter + + + com.yiqiniu.easytrans + easytrans + ${revision} + ../pom.xml + + + + UTF-8 + + + + + com.yiqiniu.easytrans + easytrans-core + + + org.apache.curator + curator-recipes + + + org.apache.zookeeper + zookeeper + + + + + + com.yiqiniu.easytrans + easytrans-log-database-starter + + + + \ No newline at end of file diff --git a/easytrans-extensionsuite-database-starter/readme.md b/easytrans-extensionsuite-database-starter/readme.md new file mode 100644 index 0000000..594b6d2 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/readme.md @@ -0,0 +1,22 @@ +## distruibute transaction election relation database implement + + +create table in mysql, it's not necessary to use the same database of business(but you can use the same one). + + CREATE TABLE `election` ( + `app_id` varchar(64) NOT NULL COMMENT 'AppId', + `instance_id` int(11) NOT NULL COMMENT '实例id,递增', + `heart_beat_time` datetime NOT NULL COMMENT '上次master发送心跳的时间', + `instance_name` varchar(255) DEFAULT NULL COMMENT '当前实例的名称', + PRIMARY KEY (`app_id`,`instance_id`) + ) ENGINE=InnoDB ; + + + CREATE TABLE `str_codec` ( + `key_int` int(11) NOT NULL, + `str_type` varchar(45) NOT NULL, + `value_str` varchar(2000) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`key_int`), + UNIQUE KEY `str_type_UNIQUE` (`str_type`,`value_str`) + ) ENGINE=InnoDB; diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionSuiteProperties.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionSuiteProperties.java new file mode 100644 index 0000000..c3544b4 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionSuiteProperties.java @@ -0,0 +1,65 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** +* @author xudeyou +*/ + +@ConfigurationProperties(prefix="easytrans.extensionsuite.database") +public class DatabaseExtensionSuiteProperties { + + private Boolean enabled; + + private String tablePrefix; + + private int logReservedDays = 14; + + private String logCleanTime = "01:20:00"; + + private Map dbSetting; + + + public String getTablePrefix() { + return tablePrefix; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public int getLogReservedDays() { + return logReservedDays; + } + + public void setLogReservedDays(int logReservedDays) { + this.logReservedDays = logReservedDays; + } + + public String getLogCleanTime() { + return logCleanTime; + } + + public void setLogCleanTime(String logCleanTime) { + this.logCleanTime = logCleanTime; + } + + public Map getDbSetting() { + return dbSetting; + } + + public void setDbSetting(Map dbSetting) { + this.dbSetting = dbSetting; + } + +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionsSuiteConfiguration.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionsSuiteConfiguration.java new file mode 100644 index 0000000..5411967 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseExtensionsSuiteConfiguration.java @@ -0,0 +1,92 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import com.alibaba.druid.pool.DruidDataSource; +import com.yiqiniu.easytrans.idgen.TrxIdGenerator; +import com.yiqiniu.easytrans.log.TransactionLogReader; +import com.yiqiniu.easytrans.log.TransactionLogWritter; +import com.yiqiniu.easytrans.log.impl.database.DataBaseTransactionLogCleanJob; +import com.yiqiniu.easytrans.log.impl.database.DataBaseTransactionLogReaderImpl; +import com.yiqiniu.easytrans.log.impl.database.DataBaseTransactionLogWritterImpl; +import com.yiqiniu.easytrans.master.EasyTransMasterSelector; +import com.yiqiniu.easytrans.serialization.ObjectSerializer; +import com.yiqiniu.easytrans.stringcodec.StringCodec; +import com.yiqiniu.easytrans.util.ByteFormIdCodec; + +/** +* @author xudeyou +*/ +@Configuration +@ConditionalOnProperty(name="easytrans.extensionsuite.database.enabled",havingValue="true",matchIfMissing=true) +@EnableConfigurationProperties(DatabaseExtensionSuiteProperties.class) +public class DatabaseExtensionsSuiteConfiguration { + + @Value("${spring.application.name}") + private String applicationName; + + @Bean + @ConditionalOnMissingBean(GetExtentionSuiteDatabase.class) + public GetExtentionSuiteDatabase getMasterSelectorDatasource(DatabaseExtensionSuiteProperties properties) { + + DruidDataSource datasource = new DruidDataSource(); + Map druidPropertyMap = properties.getDbSetting(); + Properties druidProperties = new Properties(); + for (Entry entry : druidPropertyMap.entrySet()) { + druidProperties.put("druid." + entry.getKey(), entry.getValue()); + } + datasource.configFromPropety(druidProperties); + + return new DefaultGetExtensionSuiteDatasource(datasource, new DataSourceTransactionManager(datasource)); + } + + @Bean + @ConditionalOnMissingBean(EasyTransMasterSelector.class) + public DatabaseMasterSelectorImpl databaseMasterSelectorImpl(DatabaseExtensionSuiteProperties properties,GetExtentionSuiteDatabase dataSourceGetter){ + return new DatabaseMasterSelectorImpl(properties.getTablePrefix(), dataSourceGetter.getDataSource(), dataSourceGetter.getPlatformTransactionManager(), applicationName,60); + } + + @Bean + @ConditionalOnMissingBean(TrxIdGenerator.class) + public TrxIdGenerator trxIdGenerator(DatabaseMasterSelectorImpl dbMasterSelector){ + //TODO instanceId may change during processing,but it will not change in Id generator + return new DatabaseSnowFlakeIdGenerator(dbMasterSelector.getInstanceId()); + } + + @Bean + @ConditionalOnMissingBean(StringCodec.class) + public StringCodec stringCodec(DatabaseExtensionSuiteProperties properties,GetExtentionSuiteDatabase dataSourceGetter){ + return new DatabaseStringCodecImpl(properties.getTablePrefix(), dataSourceGetter.getDataSource(), dataSourceGetter.getPlatformTransactionManager()); + } + + + // configuration below is for log-database + + @Bean + @ConditionalOnProperty(name = { "logCleanEnabled" }, prefix = "easytrans.log.database") + public DataBaseTransactionLogCleanJob logCleanJob(EasyTransMasterSelector master, DataBaseTransactionLogWritterImpl logWritter, DatabaseExtensionSuiteProperties properties) { + return new DataBaseTransactionLogCleanJob(applicationName, master, logWritter, properties.getLogReservedDays(), properties.getLogCleanTime()); + } + + @Bean + @ConditionalOnMissingBean(TransactionLogReader.class) + public DataBaseTransactionLogReaderImpl dataBaseTransactionLogReaderImpl(ObjectSerializer serializer, GetExtentionSuiteDatabase dataBaseWrap, ByteFormIdCodec idCodec, DatabaseExtensionSuiteProperties properties) { + return new DataBaseTransactionLogReaderImpl(applicationName, serializer, dataBaseWrap.getDataSource(), idCodec, properties.getTablePrefix()); + } + + @Bean + @ConditionalOnMissingBean(TransactionLogWritter.class) + public DataBaseTransactionLogWritterImpl dataBaseTransactionLogWritterImpl(ObjectSerializer serializer, GetExtentionSuiteDatabase dataBaseWrap, ByteFormIdCodec idCodec, DatabaseExtensionSuiteProperties properties) { + return new DataBaseTransactionLogWritterImpl(serializer, dataBaseWrap.getDataSource(), idCodec, properties.getTablePrefix()); + } +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseMasterSelectorImpl.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseMasterSelectorImpl.java new file mode 100644 index 0000000..96bc0b9 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseMasterSelectorImpl.java @@ -0,0 +1,221 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Random; +import java.util.Scanner; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.sql.DataSource; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import com.yiqiniu.easytrans.master.EasyTransMasterSelector; + +/** + * 利用数据库实现一个简易非严谨,但适用于ET的选主功能 + * @author deyou + * + */ +public class DatabaseMasterSelectorImpl implements EasyTransMasterSelector { + + private Logger logger = LoggerFactory.getLogger(DatabaseMasterSelectorImpl.class); + + private String GET_MAX_INSTANCE_ID = "select ifnull(max(instance_id),0) max_instance_id from election where app_id = ? for update"; + private String INSERT_INSTANCE_CONTROL_LINE = "insert into election values(?, ?,now(),?)"; + private String UPDATE_HEARTBEAT_TIME = "update election set heart_beat_time = now() where app_id = ? and instance_id = ? and TIME_TO_SEC(TIMEDIFF(NOW(), heart_beat_time)) <= ?"; + private String GET_MASTER_INSTANCE = "select min(instance_id) from election where app_id = ? and TIME_TO_SEC(TIMEDIFF(NOW(), heart_beat_time)) <= ?"; + private String CLEAN_EXPIRED_INSTANCE_RECORD = "delete from election where app_id = ? and TIME_TO_SEC(TIMEDIFF(NOW(), heart_beat_time)) > ?"; + + + private volatile Integer instanceId; + private String applicationName; + private String instanceName; + private JdbcTemplate jdbcTemplate; + private PlatformTransactionManager transManager; + private Integer leaseSeconds; + + private Random random; + + + public DatabaseMasterSelectorImpl(String tablePrefix, DataSource dataSoruce, PlatformTransactionManager transManager ,String applicationName,Integer leaseSeconds) { + this.instanceName = getHostName().trim() + "-" + getSaltString().trim(); + this.jdbcTemplate = new JdbcTemplate(dataSoruce); + this.random = new Random(); + this.transManager = transManager; + this.applicationName = applicationName; + this.leaseSeconds = leaseSeconds; + + handleTablePrefix(tablePrefix); + initInstanceIdAndRecord(); + + ScheduledExecutorService heartBeatThreadPool = Executors.newSingleThreadScheduledExecutor(); + + heartBeatThreadPool.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + updateHeartBeat(); + } + }, leaseSeconds / 2, (leaseSeconds / 3) * 2 + 1, TimeUnit.SECONDS); + } + + private void updateHeartBeat() { + try { + logger.info("heart beat begin {} {}", applicationName, instanceId); + int update = jdbcTemplate.update(UPDATE_HEARTBEAT_TIME, applicationName,instanceId,leaseSeconds); + if(update != 1) { + logger.error("heart beat failed! use another instanceId!"); + initInstanceIdAndRecord(); + } + + int nextInt = random.nextInt(10); + if(nextInt == 1) { + logger.info("clean the expired instance recored begin {}", applicationName); + jdbcTemplate.update(CLEAN_EXPIRED_INSTANCE_RECORD,applicationName,leaseSeconds); + } + + } catch (Exception e) { + logger.error("heart beat failed!" + instanceId, e); + } + } + + private void handleTablePrefix(String tablePrefix) { + tablePrefix = tablePrefix.trim(); + + if(StringUtils.isNotBlank(tablePrefix)) { + GET_MAX_INSTANCE_ID = addTablePrefix(tablePrefix, GET_MAX_INSTANCE_ID); + INSERT_INSTANCE_CONTROL_LINE = addTablePrefix(tablePrefix, INSERT_INSTANCE_CONTROL_LINE); + UPDATE_HEARTBEAT_TIME = addTablePrefix(tablePrefix, UPDATE_HEARTBEAT_TIME); + GET_MASTER_INSTANCE = addTablePrefix(tablePrefix, GET_MASTER_INSTANCE); + CLEAN_EXPIRED_INSTANCE_RECORD = addTablePrefix(tablePrefix, CLEAN_EXPIRED_INSTANCE_RECORD); + } + } + + private void initInstanceIdAndRecord() { + TransactionTemplate transTemplate = new TransactionTemplate(transManager); + transTemplate.execute(new TransactionCallback() { + @Override + public Object doInTransaction(TransactionStatus status) { + + Integer maxInstanceId = jdbcTemplate.queryForObject(GET_MAX_INSTANCE_ID, Integer.class, new Object[] {applicationName}); + instanceId = maxInstanceId + 1; + int update = jdbcTemplate.update(INSERT_INSTANCE_CONTROL_LINE, applicationName,instanceId,instanceName); + if(update != 1) { + throw new RuntimeException("insert instance record failed! instanceId:" + maxInstanceId + " updated:" + update); + } + return null; + } + }); + } + + private String addTablePrefix(String tablePrefix, String sql) { + return sql.replace("election", tablePrefix + "election"); + } + + /** + * 本方法实现假设: + * DB与本进程通讯所需时间远小于租约时间,使得与DB交互的处理时间、网络来回时间可忽略不计 + * + * heartBeat有效时间内最小的instanceId记录为master + * + */ + @Override + public boolean hasLeaderShip() { + Integer masterInstance = jdbcTemplate.queryForObject(GET_MASTER_INSTANCE, new Object[] {applicationName,leaseSeconds}, Integer.class); + if(instanceId.equals(masterInstance)) { + return true; + } + return false; + } + + @Override + public void await() throws InterruptedException { + Thread.sleep(getLeaseSeconds() / 2 + random.nextInt(getLeaseSeconds() / 6) - getLeaseSeconds() / 12); + } + + private int getLeaseSeconds() { + return leaseSeconds; + } + + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + Thread.sleep(unit.toMillis(timeout)); + return hasLeaderShip(); + } + + + public int getInstanceId() { + return instanceId; + } + + protected String getSaltString() { + String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + StringBuilder salt = new StringBuilder(); + Random rnd = new Random(); + while (salt.length() < 8) { // length of the random string. + int index = (int) (rnd.nextFloat() * SALTCHARS.length()); + salt.append(SALTCHARS.charAt(index)); + } + String saltStr = salt.toString(); + return saltStr; + } + + protected String getHostName() { + String os = System.getProperty("os.name").toLowerCase(); + + String hostName = null; + if (os.contains("win")) { + hostName = System.getenv("COMPUTERNAME"); + if (StringUtils.isNotBlank(hostName)) { + return hostName; + } + hostName = execReadToString("hostname"); + if (StringUtils.isNotBlank(hostName)) { + return hostName; + } + + } else if (os.contains("nix") || os.contains("nux") || os.contains("mac os x")) { + + hostName = System.getenv("HOSTNAME"); + if (StringUtils.isNotBlank(hostName)) { + return hostName; + } + hostName = execReadToString("hostname"); + if (StringUtils.isNotBlank(hostName)) { + return hostName; + } + hostName = execReadToString("cat /etc/hostname"); + if (StringUtils.isNotBlank(hostName)) { + return hostName; + } + } else { + try { + return Inet4Address.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + return getSaltString(); + } + } + + return getSaltString(); + } + + @SuppressWarnings("resource") + protected static String execReadToString(String execCommand) { + try (Scanner s = new Scanner(Runtime.getRuntime().exec(execCommand).getInputStream()).useDelimiter("\\A")) { + return s.hasNext() ? s.next() : ""; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseSnowFlakeIdGenerator.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseSnowFlakeIdGenerator.java new file mode 100644 index 0000000..b726044 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseSnowFlakeIdGenerator.java @@ -0,0 +1,24 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.util.concurrent.ConcurrentHashMap; + +import com.yiqiniu.easytrans.idgen.TrxIdGenerator; +import com.yiqiniu.easytrans.idgen.impl.SnowFlake; + +public class DatabaseSnowFlakeIdGenerator implements TrxIdGenerator { + + private long hostSeq; + private ConcurrentHashMap mapSnowFlakers = new ConcurrentHashMap<>(); + + + public DatabaseSnowFlakeIdGenerator(long hostSeq) { + hostSeq = hostSeq % (2^SnowFlake.MACHINE_BIT); + } + + + @Override + public long getCurrentTrxId(String busCode) { + SnowFlake s = mapSnowFlakers.computeIfAbsent(busCode, k->new SnowFlake(hostSeq)); + return s.nextId(); + } +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseStringCodecImpl.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseStringCodecImpl.java new file mode 100644 index 0000000..7e49559 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DatabaseStringCodecImpl.java @@ -0,0 +1,192 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.sql.DataSource; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.CollectionUtils; + +import com.yiqiniu.easytrans.stringcodec.ListableStringCodec; + +/** + * + * @author deyou + * + */ +@SuppressWarnings("unused") +public class DatabaseStringCodecImpl implements ListableStringCodec { + + private Logger logger = LoggerFactory.getLogger(DatabaseStringCodecImpl.class); + + private static class DataObject { + private int keyInt; + private String typeStr; + private String valueStr; + public int getKeyInt() { + return keyInt; + } + public void setKeyInt(int strInt) { + this.keyInt = strInt; + } + public String getTypeStr() { + return typeStr; + } + public void setTypeStr(String strType) { + this.typeStr = strType; + } + public String getValueStr() { + return valueStr; + } + public void setValueStr(String valueStr) { + this.valueStr = valueStr; + } + } + BeanPropertyRowMapper dataObjectMapper = new BeanPropertyRowMapper(DataObject.class); + + private String GET_ALL = "SELECT key_int,type_str,value_str FROM str_codec"; + private String GET_BY_ID = "SELECT key_int,type_str,value_str FROM str_codec where key_int = ?"; + private String FIND_ID = "SELECT key_int FROM str_codec where type_str = ? and value_str = ?"; +// private String INSERT_ID = "insert into str_codec(key_int,type_str,value_str,create_time) values(?,?,?,now())"; + private String INSERT_ID = "insert into str_codec(key_int,type_str,value_str,create_time) select ifnull(max(key_int),0) + 1,?,?,now() from str_codec "; +// private String GET_MAX_ID_AND_LOCK = "select max(key_int) from str_codec for update"; + private String GET_MAX_ID_AND_LOCK = "select max(key_int) from str_codec"; + private String GET_KEY_AND_LOCK = "select key_int from str_codec where type_str = ? and value_str = ? "; +// private String GET_KEY_AND_LOCK = "select key_int from str_codec where type_str = ? and value_str = ?"; + + private JdbcTemplate jdbcTemplate; + private DataSource dataSoruce; + private PlatformTransactionManager transManager; + private ConcurrentHashMap> vale2KeyMapping = new ConcurrentHashMap>(); + + + public DatabaseStringCodecImpl(String tablePrefix, DataSource dataSource, PlatformTransactionManager transManager) { + this.dataSoruce = dataSource; + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.transManager = transManager; + + tablePrefix = tablePrefix.trim(); + if(StringUtils.isNotBlank(tablePrefix)) { + FIND_ID = addTablePrefix(tablePrefix, FIND_ID); + INSERT_ID = addTablePrefix(tablePrefix, INSERT_ID); + GET_MAX_ID_AND_LOCK = addTablePrefix(tablePrefix, GET_MAX_ID_AND_LOCK); + GET_KEY_AND_LOCK = addTablePrefix(tablePrefix, GET_KEY_AND_LOCK); + GET_ALL = addTablePrefix(tablePrefix, GET_ALL); + } + + List result = jdbcTemplate.query(GET_ALL, dataObjectMapper); + for(DataObject data:result) { + ConcurrentHashMap typeValue2Key = vale2KeyMapping.computeIfAbsent(data.getTypeStr(), key->new ConcurrentHashMap<>()); + typeValue2Key.computeIfAbsent(data.getValueStr(), key->data.getKeyInt()); + } + + } + + @Override + public Integer findId(String stringType, String value) { + + ConcurrentHashMap typeValue2Key = vale2KeyMapping.computeIfAbsent(stringType, key->new ConcurrentHashMap<>()); + int resultKey = typeValue2Key.computeIfAbsent(value, k->getOrInsertId(stringType,value)); + return resultKey; + } + + private Integer getOrInsertId(String stringType, String value) { + + DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); + transactionDefinition.setTimeout(3); + + TransactionTemplate transactionTemplate = new TransactionTemplate(transManager, transactionDefinition); + + return transactionTemplate.execute(new TransactionCallback() { + + @Override + public Integer doInTransaction(TransactionStatus status) { + + + for(int i = 0 ; i < 3 ; i ++) { + Integer keyInt = getValueId(stringType, value); + if(keyInt != null) { + return keyInt; + } + + try { + jdbcTemplate.update(INSERT_ID, new Object[] {stringType,value}); + } catch (Exception e) { + logger.warn("insert failed,may cause by concurrent,try again, count:" + i + " msg:" + e.getMessage()); + } + } + + + Integer valueId = getValueId(stringType, value); + + if(valueId == null) { + throw new RuntimeException("can not find specified id :" + value); + } + + return valueId; + } + + private Integer getValueId(String stringType, String value) { + List keyIntList = jdbcTemplate.queryForList(GET_KEY_AND_LOCK, new Object[] {stringType,value}, Integer.class); + if(!CollectionUtils.isEmpty(keyIntList)) { + if(keyIntList.size() == 1) { + return keyIntList.get(0); + } else { + throw new RuntimeException("unexpected row count selected!"); + } + } + return null; + } + }); + } + + @Override + public String findString(String stringType, int id) { + ConcurrentHashMap typeValue2Key = vale2KeyMapping.computeIfAbsent(stringType, key->new ConcurrentHashMap<>()); + + Set> entrySet = typeValue2Key.entrySet(); + for(Entry entry:entrySet) { + Integer dbKey = entry.getValue(); + if(dbKey.equals(id)) { + return entry.getKey(); + } + } + + //get from db + List item = jdbcTemplate.query(GET_BY_ID, new Object[] {id}, dataObjectMapper); + if(item != null && item.size() == 1) { + typeValue2Key.put(item.get(0).getValueStr(), item.get(0).getKeyInt()); + return item.get(0).getValueStr(); + } + + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Map> getMapStr2Id() { + Object obj = vale2KeyMapping; + return (Map>) obj; + } + + private String addTablePrefix(String tablePrefix, String sql) { + return sql.replace("str_codec", tablePrefix + "str_codec"); + } + +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DefaultGetExtensionSuiteDatasource.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DefaultGetExtensionSuiteDatasource.java new file mode 100644 index 0000000..0dac0e0 --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/DefaultGetExtensionSuiteDatasource.java @@ -0,0 +1,29 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import javax.sql.DataSource; + +import org.springframework.transaction.PlatformTransactionManager; + + +public class DefaultGetExtensionSuiteDatasource implements GetExtentionSuiteDatabase{ + + public DefaultGetExtensionSuiteDatasource(DataSource dataSource, PlatformTransactionManager transManager) { + super(); + this.dataSource = dataSource; + this.transManager = transManager; + } + + private DataSource dataSource; + private PlatformTransactionManager transManager; + + @Override + public DataSource getDataSource() { + return dataSource; + } + + @Override + public PlatformTransactionManager getPlatformTransactionManager() { + return transManager; + } + +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/EnableExtensionSuiteDatabaseImpl.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/EnableExtensionSuiteDatabaseImpl.java new file mode 100644 index 0000000..0227bcc --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/EnableExtensionSuiteDatabaseImpl.java @@ -0,0 +1,22 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * + * @author xudeyou + * + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(DatabaseExtensionsSuiteConfiguration.class) +public @interface EnableExtensionSuiteDatabaseImpl { + +} diff --git a/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/GetExtentionSuiteDatabase.java b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/GetExtentionSuiteDatabase.java new file mode 100644 index 0000000..cf17cff --- /dev/null +++ b/easytrans-extensionsuite-database-starter/src/main/java/com/yiqiniu/easytrans/extensionsuite/impl/database/GetExtentionSuiteDatabase.java @@ -0,0 +1,13 @@ +package com.yiqiniu.easytrans.extensionsuite.impl.database; + +import javax.sql.DataSource; + +import org.springframework.transaction.PlatformTransactionManager; + +public interface GetExtentionSuiteDatabase { + + DataSource getDataSource(); + + PlatformTransactionManager getPlatformTransactionManager(); + +} diff --git a/easytrans-log-database-starter/readme.md b/easytrans-log-database-starter/readme.md index f4e66e0..b3c8f7e 100644 --- a/easytrans-log-database-starter/readme.md +++ b/easytrans-log-database-starter/readme.md @@ -1 +1,4 @@ -## distruibute transaction log relation database implement \ No newline at end of file +## distruibute transaction log relation database implement + +可以自行替换DataBaseForLog的实例,选择自己使用的数据源 + diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java index aa0c115..9392ffa 100644 --- a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java @@ -104,8 +104,8 @@ public EasyTransResult invoke(EasyTransFilterChain filterChain, Map header = deserializeHeader(easyTransHeader); - if(logger.isDebugEnabled()) { - logger.debug("ET RPC call recived,busCode:{},innerMethod:{},header:{},body:{}",busCode,innerMethod, header, requestObj); + if(logger.isInfoEnabled()) { + logger.info("ET RPC call recived,busCode:{},innerMethod:{},header:{},body:{}",busCode,innerMethod, header, requestObj); } diff --git a/easytrans-starter/pom.xml b/easytrans-starter/pom.xml index 59b782b..a8c56fe 100644 --- a/easytrans-starter/pom.xml +++ b/easytrans-starter/pom.xml @@ -52,6 +52,12 @@ test + + com.yiqiniu.easytrans + + easytrans-extensionsuite-database-starter + + @@ -126,6 +127,12 @@ com.yiqiniu.easytrans + easytrans-extensionsuite-database-starter + ${revision} + + + + com.yiqiniu.easytrans easytrans-dashboard ${revision} diff --git a/readme.md b/readme.md index e75a75b..b87b85c 100644 --- a/readme.md +++ b/readme.md @@ -15,16 +15,15 @@ 特性: +* 引入JAR包即用,无需独立部署协调者,无需独立部署ZooKeeper(使用extensionsuite-database-starter时) * 一个框架包含多种事务形态,一个框架搞定所有类型的事务 * 多种事务形态可混合使用 * 高性能,大多数业务系统瓶颈在业务数据库,若不启用框架的幂等功能,对业务数据库的额外消耗仅为写入25字节的一行 * 可选的框架自带幂等实现及调用错乱次序处理,大幅减轻业务开发工作量,但启用的同时会在业务数据库增加一条幂等控制行 * 业务代码可实现完全无入侵 * 支持嵌套事务 -* 无需额外部署协调者,不同APP的服务协调自身发起的事务,也避免了单点故障 - * 也可以对某个APP单独部署一个协调者 * 分布式事务ID可关联业务ID,业务类型,APPID,便于监控各个业务的分布式事务执行情况 -* 整合并大幅改造阿里Fescar的自动补偿核心功能,提供分布式高可用的协调功能 +* 整合Seata的AT模式,改造行锁使其存储到本地,改造集中式TC为ET的分布式协调 ## 二、分布式事务场景及框架对应实现 @@ -96,7 +95,7 @@ com.yiqiniu.easytrans easytrans-starter - 1.3.1 + 1.4.0 Starter里包含了若干默认的组件实现:基于mysql的分布式事务日志存储,基于ribbon-rest的RPC实现,基于KAFKA的消息队列,若不需要或者要替换,可以EXCLUDE掉 @@ -323,13 +322,14 @@ Starter里包含了若干默认的组件实现:基于mysql的分布式事务日 `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, + `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), - KEY `idx_unionkey` (`xid`,`branch_id`) + UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; @@ -366,8 +366,14 @@ Starter里包含了若干默认的组件实现:基于mysql的分布式事务日 * 目前提供基于关系数据库及REDIS的实现 * 为提高效率,可自行实现基于其他形式的事务日志,如文件系统,HBASE等,欢迎PR * 主从选择器 - * 目前基于ZK实现了主从选择 - * 若不想采用ZK可自行替换ZK实现 + * 基于ZK的主从选择 + * 基于MySQL的主从选择 +* 字符串编码器 + * 基于ZK的字符串编码 + * 基于MYSQL的字符串编码 +* id生成器 + * 基于ZK的雪花ID生成 + * 基于MYSQL的雪花ID生成 ## 五、最佳实践 @@ -387,7 +393,7 @@ Starter里包含了若干默认的组件实现:基于mysql的分布式事务日 * 使用select for update是为了避免在MVCC情况下错误查询出最终事务提交结果的情况 ## 七、外部组件版本兼容性 -* ZK请使用3.4及以上版本 +* 若未使用 extensionsuite-database-starter需要使用ZK时,ZK请使用3.4及以上版本 * SpringBoot 2.0.x 以及 SpringCloud F版本的整合请参考Demo里的tccandfescar * 需要注意的是写文档时最新版的spring boot(2.0.8)引入的最新版mysql-connector-java (5.1.47)存在bug,要降级为5.1.46 * 另因SpringCloud大版本变更时导致某些包名变动,在F版本时使用ET的Ribbon时,需要在项目单独引入spring-cloud-starter-netflix-ribbon @@ -397,10 +403,16 @@ Starter里包含了若干默认的组件实现:基于mysql的分布式事务日 ## 八、其他 -欢迎加作者个人微信公众号 +欢迎加作者个人微信及公众号 + +公众号 ![wechat public account](https://raw.githubusercontent.com/QNJR-GROUP/ImageHub/master/easytrans/wechat_public_account.jpg) +微信(加微信请注明来源 ET) + +![wechat](https://github.com/QNJR-GROUP/ImageHub/blob/master/easytrans/wechat.png?raw=true) + 若觉得框架不错,希望能STAR,THX email: skyes.xu@qq.com