diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf2e1f9b229..cbe72c2c069 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: # step 2 - name: "Set up QEMU" id: qemu - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 # step 3 - name: "Build arm-binary" run: | diff --git a/all/pom.xml b/all/pom.xml index 820ea6837d4..9a00a2a30ef 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -128,6 +128,11 @@ seata-discovery-sofa ${project.version} + + io.seata + seata-discovery-raft + ${project.version} + io.seata seata-discovery-zk diff --git a/bom/pom.xml b/bom/pom.xml index e71dc4c3988..6fd3cf91227 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -143,6 +143,11 @@ seata-discovery-sofa ${project.version} + + io.seata + seata-discovery-raft + ${project.version} + io.seata seata-brpc diff --git a/build/pom.xml b/build/pom.xml index db8caeb3c56..32401ff7795 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -402,9 +402,6 @@ --add-opens java.sql/java.sql=ALL-UNNAMED --add-opens java.sql.rowset/javax.sql.rowset.serial=ALL-UNNAMED - - --illegal-access=permit - -Dillegal-access=permit diff --git a/changes/en-us/2.0.0.md b/changes/en-us/2.0.0.md index 845355809ad..3a3e3f185b8 100644 --- a/changes/en-us/2.0.0.md +++ b/changes/en-us/2.0.0.md @@ -28,6 +28,8 @@ The version is updated as follows: - [[#5902](https://github.com/seata/seata/pull/5902)] support IPv6 - [[#5907](https://github.com/seata/seata/pull/5907)] support polardb-x 2.0 in AT mode - [[#5932](https://github.com/seata/seata/pull/5932)] support Dameng database +- [[#5946](https://github.com/seata/seata/pull/5946)] add sqlserver's adaptation to the console paging interface +- [[#5226](https://github.com/seata/seata/pull/5226)] support raft cluster and store mode ### bugfix: - [[#5677](https://github.com/seata/seata/pull/5677)] fix saga mode serviceTask inputParams json autoType convert exception @@ -77,8 +79,13 @@ The version is updated as follows: - [[#5835](https://github.com/seata/seata/pull/5835)] bugfix: fix TC retry rollback wrongly, after the XA transaction fail and rollback - [[#5881](https://github.com/seata/seata/pull/5880)] fix delete branch table unlock failed - [[#5930](https://github.com/seata/seata/pull/5930)] fix the issue of missing sentinel password in store redis mode +- [[#5958](https://github.com/seata/seata/pull/5958)] required to be unlocked when a re-election occurs in a commit state +- [[#5971](https://github.com/seata/seata/pull/5971)] fix some configurations that are not deprecated show "Deprecated" +- [[#5954](https://github.com/seata/seata/pull/5954)] fix the issue of saved branch session status does not match the actual branch session status ### optimize: +- [[#5966](https://github.com/seata/seata/pull/5966)] decouple saga expression handling and remove evaluator package +- [[#5928](https://github.com/seata/seata/pull/5928)] add Saga statelang semantic validation - [[#5208](https://github.com/seata/seata/pull/5208)] optimize throwable getCause once more - [[#5212](https://github.com/seata/seata/pull/5212)] optimize log message level - [[#5237](https://github.com/seata/seata/pull/5237)] optimize exception log message print(EnhancedServiceLoader.loadFile#cahtch) @@ -134,6 +141,9 @@ The version is updated as follows: - [[#5878](https://github.com/seata/seata/pull/5878)] optimize `httpcore` and `httpclient` dependencies - [[#5917](https://github.com/seata/seata/pull/5917)] upgrade native-lib-loader version - [[#5926](https://github.com/seata/seata/pull/5926)] optimize some scripts related to Apollo +- [[#5938](https://github.com/seata/seata/pull/5938)] support jmx port in seata +- [[#5951](https://github.com/seata/seata/pull/5951)] remove un support config in jdk17 +- [[#5959](https://github.com/seata/seata/pull/5959)] modify code style and remove unused import ### security: - [[#5642](https://github.com/seata/seata/pull/5642)] add Hessian Serializer WhiteDenyList @@ -142,6 +152,8 @@ The version is updated as follows: - [[#5805](https://github.com/seata/seata/pull/5805)] fix some serializer vulnerabilities - [[#5868](https://github.com/seata/seata/pull/5868)] fix npm package vulnerabilities - [[#5916](https://github.com/seata/seata/pull/5916)] upgrade nodejs dependency +- [[#5942](https://github.com/seata/seata/pull/5942)] upgrade dependencies version +- [[#5987](https://github.com/seata/seata/pull/5987)] upgrade some dependencies version ### test: - [[#5308](https://github.com/seata/seata/pull/5308)] add unit test [FileLoader, ObjectHolder, StringUtils] @@ -156,6 +168,7 @@ The version is updated as follows: - [[#5893](https://github.com/seata/seata/pull/5893)] remove sofa test cases - [[#5845](https://github.com/seata/seata/pull/5845)] upgrade druid and add `test-druid.yml` - [[#5863](https://github.com/seata/seata/pull/5863)] fix unit test in java 21 +- [[#5986](https://github.com/seata/seata/pull/5986)] fix zookeeper UT failed ### Contributors: @@ -173,7 +186,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [pengten](https://github.com/pengten) - [wangliang181230](https://github.com/wangliang181230) - [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [a364176773](https://github.com/a364176773) +- [funky-eyes](https://github.com/funky-eyes) - [isharpever](https://github.com/isharpever) - [mxsm](https://github.com/mxsm) - [liuqiufeng](https://github.com/liuqiufeng) @@ -189,7 +202,8 @@ Thanks to these contributors for their code commits. Please report an unintended - [Weelerer](https://github.com/Weelerer) - [Ifdevil](https://github.com/Ifdevil) - [iquanzhan](https://github.com/iquanzhan) - +- [leizhiyuan](https://github.com/leizhiyuan) +- [Aruato](https://github.com/Aruato) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.0.0.md b/changes/zh-cn/2.0.0.md index e6550da595d..503968496c7 100644 --- a/changes/zh-cn/2.0.0.md +++ b/changes/zh-cn/2.0.0.md @@ -28,6 +28,8 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [[#5902](https://github.com/seata/seata/pull/5902)] 支持IPv6网络环境 - [[#5907](https://github.com/seata/seata/pull/5907)] 增加AT模式的PolarDB-X 2.0数据库支持 - [[#5932](https://github.com/seata/seata/pull/5932)] AT模式支持达梦数据库 +- [[#5946](https://github.com/seata/seata/pull/5946)] 增加sqlserver对控制台分页接口的适配 +- [[#5226](https://github.com/seata/seata/pull/5226)] 支持Raft集群部署和事务存储模式 ### bugfix: - [[#5677](https://github.com/seata/seata/pull/5677)] 修复saga模式下serviceTask入参autoType转化失败问题 @@ -76,8 +78,13 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [[#5835](https://github.com/seata/seata/pull/5835)] bugfix: 修复当 XA 事务失败回滚后,TC 还会继续重试回滚的问题 - [[#5881](https://github.com/seata/seata/pull/5880)] 修复事务回滚时锁未删除的问题 - [[#5930](https://github.com/seata/seata/pull/5930)] 修复存储为redis哨兵模式下哨兵密码缺失的问题 +- [[#5958](https://github.com/seata/seata/pull/5958)] 在二阶段提交状态下发生重选时需要进行解除全局锁 +- [[#5971](https://github.com/seata/seata/pull/5971)] 修复某些未弃用的配置显示"已弃用" +- [[#5954](https://github.com/seata/seata/pull/5954)] 修复保存的分支会话状态与实际的分支会话状态不一致的问题 ### optimize: +- [[#5966](https://github.com/seata/seata/pull/5966)] Saga 表达式解耦并统一格式 +- [[#5928](https://github.com/seata/seata/pull/5928)] 增加Saga模式状态机语义验证阶段 - [[#5208](https://github.com/seata/seata/pull/5208)] 优化多次重复获取Throwable#getCause问题 - [[#5212](https://github.com/seata/seata/pull/5212)] 优化不合理的日志信息级别 - [[#5237](https://github.com/seata/seata/pull/5237)] 优化异常日志打印(EnhancedServiceLoader.loadFile#cahtch) @@ -134,6 +141,10 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [[#5878](https://github.com/seata/seata/pull/5878)] 优化 `httpcore` 和 `httpclient` 的依赖定义 - [[#5917](https://github.com/seata/seata/pull/5917)] 升级 native-lib-loader 版本 - [[#5926](https://github.com/seata/seata/pull/5926)] 优化一些与 Apollo 相关的脚本 +- [[#5938](https://github.com/seata/seata/pull/5938)] 支持 jmx 监控配置 +- [[#5951](https://github.com/seata/seata/pull/5951)] 删除在 jdk17 中不支持的配置项 +- [[#5959](https://github.com/seata/seata/pull/5959)] 修正代码风格问题及去除无用的类引用 + ### security: - [[#5642](https://github.com/seata/seata/pull/5642)] 增加Hessian 序列化黑白名单 @@ -142,6 +153,8 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [[#5805](https://github.com/seata/seata/pull/5805)] 修复序列化漏洞 - [[#5868](https://github.com/seata/seata/pull/5868)] 修复npm package漏洞 - [[#5916](https://github.com/seata/seata/pull/5916)] 修复npm package漏洞 +- [[#5942](https://github.com/seata/seata/pull/5942)] 升级依赖版本 +- [[#5987](https://github.com/seata/seata/pull/5987)] 升级依赖版本 ### test: - [[#5308](https://github.com/seata/seata/pull/5308)] 添加单元测试用例 [FileLoader, ObjectHolder, StringUtils] @@ -156,6 +169,7 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [[#5893](https://github.com/seata/seata/pull/5893)] 移除 sofa 测试用例 - [[#5845](https://github.com/seata/seata/pull/5845)] 升级 `druid` 版本,并添加 `test-druid.yml` 用于测试seata与druid各版本的兼容性。 - [[#5863](https://github.com/seata/seata/pull/5863)] 修复单元测试在Java21下无法正常运行的问题。 +- [[#5986](https://github.com/seata/seata/pull/5986)] 修复 zookeeper 单测失败问题 ### Contributors: @@ -172,7 +186,7 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [pengten](https://github.com/pengten) - [wangliang181230](https://github.com/wangliang181230) - [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [a364176773](https://github.com/a364176773) +- [funky-eyes](https://github.com/funky-eyes) - [isharpever](https://github.com/isharpever) - [mxsm](https://github.com/mxsm) - [liuqiufeng](https://github.com/liuqiufeng) @@ -188,6 +202,8 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单 - [Weelerer](https://github.com/Weelerer) - [Ifdevil](https://github.com/Ifdevil) - [iquanzhan](https://github.com/iquanzhan) +- [leizhiyuan](https://github.com/leizhiyuan) +- [Aruato](https://github.com/Aruato) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/common/src/main/java/io/seata/common/ConfigurationKeys.java b/common/src/main/java/io/seata/common/ConfigurationKeys.java index 98bb53f388a..dba53e2d2ac 100644 --- a/common/src/main/java/io/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/io/seata/common/ConfigurationKeys.java @@ -364,6 +364,11 @@ public interface ConfigurationKeys { */ String SERVER_SERVICE_PORT_CAMEL = SERVER_PREFIX + "servicePort"; + /** + * The constant SERVER_RAFT_PORT. + */ + String SERVER_RAFT_PORT_CAMEL = SERVER_PREFIX + "raftPort"; + /** * The constant SERVER_SERVICE_PORT_CONFIG. */ @@ -866,6 +871,81 @@ public interface ConfigurationKeys { */ String ENABLE_BRANCH_ASYNC_REMOVE = SERVER_PREFIX + SESSION_PREFIX + "enableBranchAsyncRemove"; + /** + * The constant SERVER_RAFT. + */ + String SERVER_RAFT = SERVER_PREFIX + "raft."; + + /** + * The constant SERVER_RAFT_SERVER_ADDR. + */ + String SERVER_RAFT_SERVER_ADDR = SERVER_RAFT + "serverAddr"; + + /** + * The constant SERVER_RAFT_GROUP. + */ + String SERVER_RAFT_GROUP = SERVER_RAFT + "group"; + + /** + * The constant SERVER_RAFT_SNAPSHOT_INTERVAL. + */ + String SERVER_RAFT_SNAPSHOT_INTERVAL = SERVER_RAFT + "snapshotInterval"; + + /** + * The constant SERVER_RAFT_DISRUPTOR_BUFFER_SIZE. + */ + String SERVER_RAFT_DISRUPTOR_BUFFER_SIZE = SERVER_RAFT + "disruptorBufferSize"; + + /** + * The constant SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS. + */ + String SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS = SERVER_RAFT + "maxReplicatorInflightMsgs"; + + /** + * The constant SERVER_RAFT_SYNC. + */ + String SERVER_RAFT_SYNC = SERVER_RAFT + "sync"; + + /** + * The constant SERVER_RAFT_MAX_APPEND_BUFFER_SIZE. + */ + String SERVER_RAFT_MAX_APPEND_BUFFER_SIZE = SERVER_RAFT + "maxAppendBufferSize"; + + /** + * The constant SERVER_RAFT_APPLY_BATCH. + */ + String SERVER_RAFT_APPLY_BATCH = SERVER_RAFT + "applyBatch"; + + /** + * The constant SERVER_RAFT_APPLY_BATCH. + */ + String SERVER_RAFT_ELECTION_TIMEOUT_MS = SERVER_RAFT + "electionTimeoutMs"; + + /** + * The constant SERVER_RAFT_REPORTER_ENABLED. + */ + String SERVER_RAFT_REPORTER_ENABLED = SERVER_RAFT + "reporterEnabled"; + + /** + * The constant SERVER_RAFT_REPORTER_INITIAL_DELAY. + */ + String SERVER_RAFT_REPORTER_INITIAL_DELAY = SERVER_RAFT + "reporterInitialDelay"; + + /** + * The constant SERVER_RAFT_SERIALIZATION. + */ + String SERVER_RAFT_SERIALIZATION = SERVER_RAFT + "serialization"; + + /** + * The constant SERVER_RAFT_COMPRESSOR. + */ + String SERVER_RAFT_COMPRESSOR = SERVER_RAFT + "compressor"; + + /** + * The constant CLIENT_METADATA_MAX_AGE_MS. + */ + String CLIENT_METADATA_MAX_AGE_MS = CLIENT_PREFIX + "metadataMaxAgeMs"; + /** * The constant IS_USE_CLOUD_NAMESPACE_PARSING. */ @@ -891,6 +971,7 @@ public interface ConfigurationKeys { */ String XA_CONNECTION_TWO_PHASE_HOLD_TIMEOUT = CLIENT_RM_PREFIX + "connectionTwoPhaseHoldTimeoutXA"; + /** * The constant ENABLE_PARALLEL_REQUEST_HANDLE_KEY */ diff --git a/common/src/main/java/io/seata/common/DefaultValues.java b/common/src/main/java/io/seata/common/DefaultValues.java index ce6e03092c7..47227c2da8d 100644 --- a/common/src/main/java/io/seata/common/DefaultValues.java +++ b/common/src/main/java/io/seata/common/DefaultValues.java @@ -34,8 +34,15 @@ public interface DefaultValues { long DEFAULT_TABLE_META_CHECKER_INTERVAL = 60000L; boolean DEFAULT_TM_DEGRADE_CHECK = false; boolean DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE = false; + + /** + * The default session store dir + */ + String DEFAULT_SESSION_STORE_FILE_DIR = "sessionStore"; boolean DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE = false; boolean DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE = false; + String DEFAULT_RAFT_SERIALIZATION = "jackson"; + String DEFAULT_RAFT_COMPRESSOR = "none"; /** * Shutdown timeout default 3s @@ -53,7 +60,6 @@ public interface DefaultValues { boolean DEFAULT_ENABLE_RM_CLIENT_BATCH_SEND_REQUEST = true; boolean DEFAULT_ENABLE_TC_SERVER_BATCH_SEND_RESPONSE = false; - String DEFAULT_BOSS_THREAD_PREFIX = "NettyBoss"; String DEFAULT_NIO_WORKER_THREAD_PREFIX = "NettyServerNIOWorker"; String DEFAULT_EXECUTOR_THREAD_PREFIX = "NettyServerBizHandler"; @@ -120,6 +126,8 @@ public interface DefaultValues { String DEFAULT_LOAD_BALANCE = "XID"; int VIRTUAL_NODES_DEFAULT = 10; + String DEFAULT_SEATA_GROUP = "default"; + /** * the constant DEFAULT_CLIENT_UNDO_COMPRESS_ENABLE */ @@ -135,6 +143,7 @@ public interface DefaultValues { */ String DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD = "64k"; + /** * the constant DEFAULT_RETRY_DEAD_THRESHOLD */ @@ -203,6 +212,10 @@ public interface DefaultValues { */ int DEFAULT_XA_CONNECTION_TWO_PHASE_HOLD_TIMEOUT = 10000; + /** + * the constant DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS + */ + int DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS = 1000; /** * the constant DEFAULT_COMMITING_RETRY_PERIOD */ diff --git a/common/src/main/java/io/seata/common/metadata/ClusterRole.java b/common/src/main/java/io/seata/common/metadata/ClusterRole.java new file mode 100644 index 00000000000..cbe6ab5f5d3 --- /dev/null +++ b/common/src/main/java/io/seata/common/metadata/ClusterRole.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.metadata; + +/** + * @author funkye + */ +public enum ClusterRole { + + /** + * raft mode leader + */ + LEADER(0), + /** + * raft mode follower + */ + FOLLOWER(1), + /** + * raft mode learner + */ + LEARNER(2), + /** + * cluster mode member + */ + MEMBER(3); + + private int roleCode; + + ClusterRole(int roleCode) { + this.roleCode = roleCode; + } + + public int getRoleCode() { + return roleCode; + } + + public void setRoleCode(int roleCode) { + this.roleCode = roleCode; + } + +} diff --git a/common/src/main/java/io/seata/common/metadata/Metadata.java b/common/src/main/java/io/seata/common/metadata/Metadata.java new file mode 100644 index 00000000000..cf3eac3cc1a --- /dev/null +++ b/common/src/main/java/io/seata/common/metadata/Metadata.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.metadata; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import io.seata.common.store.StoreMode; +import io.seata.common.util.StringUtils; + +/** + * @author funkye + */ +public class Metadata { + + private final Map> leaders = new ConcurrentHashMap<>(); + + private final Map> clusterTerm = new ConcurrentHashMap<>(); + + private final Map>> clusterNodes = + new ConcurrentHashMap<>(); + + private StoreMode storeMode = StoreMode.FILE; + + public Node getLeader(String clusterName) { + Map map = leaders.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()); + List nodes = new ArrayList<>(map.values()); + return nodes.size() > 0 ? nodes.get(ThreadLocalRandom.current().nextInt(nodes.size())) : null; + } + + public void setLeaderNode(String clusterName, Node node) { + String group = node.getGroup(); + Map map = leaders.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()); + map.put(group, node); + this.leaders.put(clusterName, map); + } + + public List getNodes(String clusterName, String group) { + return clusterNodes.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()).get(group); + } + + public List getNodes(String clusterName) { + return clusterNodes.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()).values().stream() + .flatMap(List::stream).collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + } + + public void setNodes(String clusterName, String group, List nodes) { + this.clusterNodes.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()).put(group, nodes); + } + + public boolean containsGroup(String group) { + return clusterNodes.containsKey(group); + } + + public Set groups(String clusterName) { + return clusterNodes.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()).keySet(); + } + + public StoreMode getStoreMode() { + return storeMode; + } + + public boolean isRaftMode() { + return Objects.equals(storeMode, StoreMode.RAFT); + } + + public void setStoreMode(StoreMode storeMode) { + this.storeMode = storeMode; + } + + public Map getClusterTerm(String clusterName) { + return clusterTerm.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()); + } + + public void refreshMetadata(String clusterName, MetadataResponse metadataResponse) { + List list = new ArrayList<>(); + for (Node node : metadataResponse.getNodes()) { + if (node.getRole() == ClusterRole.LEADER) { + this.setLeaderNode(clusterName, node); + } + list.add(node); + } + this.storeMode = StoreMode.get(metadataResponse.getStoreMode()); + if (!list.isEmpty()) { + String group = list.get(0).getGroup(); + this.setNodes(clusterName, group, list); + this.clusterTerm.computeIfAbsent(clusterName, k -> new ConcurrentHashMap<>()).put(group, + metadataResponse.getTerm()); + } + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/common/src/main/java/io/seata/common/metadata/MetadataResponse.java b/common/src/main/java/io/seata/common/metadata/MetadataResponse.java new file mode 100644 index 00000000000..500df2b1ebc --- /dev/null +++ b/common/src/main/java/io/seata/common/metadata/MetadataResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.metadata; + +import java.util.List; + +/** + * @author funkye + */ +public class MetadataResponse { + + List nodes; + + String storeMode; + + long term; + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public String getStoreMode() { + return storeMode; + } + + public void setStoreMode(String storeMode) { + this.storeMode = storeMode; + } + + public long getTerm() { + return term; + } + + public void setTerm(long term) { + this.term = term; + } + +} diff --git a/common/src/main/java/io/seata/common/metadata/Node.java b/common/src/main/java/io/seata/common/metadata/Node.java new file mode 100644 index 00000000000..022b25bd89a --- /dev/null +++ b/common/src/main/java/io/seata/common/metadata/Node.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.metadata; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author funkye + */ +public class Node { + + Map metadata = new HashMap<>(); + private Endpoint control; + private Endpoint transaction; + + private String group; + private ClusterRole role = ClusterRole.MEMBER; + + public Node() {} + + public Endpoint createEndpoint(String host, int port, String protocol) { + return new Endpoint(host, port); + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public ClusterRole getRole() { + return role; + } + + public void setRole(ClusterRole role) { + this.role = role; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public Endpoint getControl() { + return control; + } + + public void setControl(Endpoint control) { + this.control = control; + } + + public Endpoint getTransaction() { + return transaction; + } + + public void setTransaction(Endpoint transaction) { + this.transaction = transaction; + } + + public static class Endpoint { + + private String host; + private String protocol; + private int port; + + public Endpoint() {} + + public Endpoint(String host, int port) { + this.host = host; + this.port = port; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String createAddress() { + return host + ":" + port; + } + + @Override + public String toString() { + return "Endpoint{" + "host='" + host + '\'' + ", port=" + port + '}'; + } + } + +} diff --git a/common/src/main/java/io/seata/common/store/StoreMode.java b/common/src/main/java/io/seata/common/store/StoreMode.java new file mode 100644 index 00000000000..0d5c0918424 --- /dev/null +++ b/common/src/main/java/io/seata/common/store/StoreMode.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.store; + +/** + * transaction log store mode + * + * @author zhangsen + */ +public enum StoreMode { + + /** + * file store + */ + FILE("file"), + + /** + * database store + */ + DB("db"), + + /** + * redis store + */ + REDIS("redis"), + + /** + * raft store + */ + RAFT("raft"); + + private String name; + + StoreMode(String name) { + this.name = name; + } + + /** + * get value of store mode + * + * @param name the mode name + * @return the store mode + */ + public static StoreMode get(String name) { + for (StoreMode sm : StoreMode.class.getEnumConstants()) { + if (sm.name.equalsIgnoreCase(name)) { + return sm; + } + } + throw new IllegalArgumentException("unknown store mode:" + name); + } + + /** + * whether contains value of store mode + * + * @param name the mode name + * @return the boolean + */ + public static boolean contains(String name) { + try { + return get(name) != null ? true : false; + } catch (IllegalArgumentException e) { + return false; + } + } + + public String getName() { + return name; + } + +} \ No newline at end of file diff --git a/common/src/main/java/io/seata/common/util/PageUtil.java b/common/src/main/java/io/seata/common/util/PageUtil.java index 2583f91070f..6e40c6dd67a 100644 --- a/common/src/main/java/io/seata/common/util/PageUtil.java +++ b/common/src/main/java/io/seata/common/util/PageUtil.java @@ -75,6 +75,11 @@ public class PageUtil { private static final String ORACLE_PAGE_TEMPLATE = "select * from ( select ROWNUM rn, temp.* from (" + SOURCE_SQL_PLACE_HOLD + ") temp ) where rn between " + START_PLACE_HOLD + " and " + END_PLACE_HOLD; + /** + * The constant SQLSERVER_PAGE_TEMPLATE + */ + private static final String SQLSERVER_PAGE_TEMPLATE = "select * from (select temp.*, ROW_NUMBER() OVER(ORDER BY (select NULL)) AS rowId from (" + + SOURCE_SQL_PLACE_HOLD + ") temp ) t where t.rowId between " + START_PLACE_HOLD + " and " + END_PLACE_HOLD; /** * check page parm * @@ -113,6 +118,10 @@ public static String pageSql(String sourceSql, String dbType, int pageNum, int p return ORACLE_PAGE_TEMPLATE.replace(SOURCE_SQL_PLACE_HOLD, sourceSql) .replace(START_PLACE_HOLD, String.valueOf(pageSize * (pageNum - 1) + 1)) .replace(END_PLACE_HOLD, String.valueOf(pageSize * pageNum)); + case "sqlserver": + return SQLSERVER_PAGE_TEMPLATE.replace(SOURCE_SQL_PLACE_HOLD, sourceSql) + .replace(START_PLACE_HOLD, String.valueOf(pageSize * (pageNum - 1) + 1)) + .replace(END_PLACE_HOLD, String.valueOf(pageSize * pageNum)); default: throw new NotSupportYetException("PageUtil not support this dbType:" + dbType); } @@ -134,6 +143,7 @@ public static String countSql(String sourceSql, String dbType) { case "dm": return sourceSql.replaceAll("(?i)(?<=select)(.*)(?=from)", " count(1) "); case "postgresql": + case "sqlserver": int lastIndexOfOrderBy = sourceSql.toLowerCase().lastIndexOf("order by"); if (lastIndexOfOrderBy != -1) { return sourceSql.substring(0, lastIndexOfOrderBy).replaceAll("(?i)(?<=select)(.*)(?=from)", " count(1) "); diff --git a/common/src/test/java/io/seata/common/util/PageUtilTest.java b/common/src/test/java/io/seata/common/util/PageUtilTest.java index 713f95d2596..60e775568a3 100644 --- a/common/src/test/java/io/seata/common/util/PageUtilTest.java +++ b/common/src/test/java/io/seata/common/util/PageUtilTest.java @@ -37,6 +37,7 @@ public void testPageSql() { String oracleTargetSql = "select * from " + "( select ROWNUM rn, temp.* from (select * from test where a = 1) temp )" + " where rn between 1 and 5"; + String sqlserverTargetSql = "select * from (select temp.*, ROW_NUMBER() OVER(ORDER BY (select NULL)) AS rowId from (select * from test where a = 1) temp ) t where t.rowId between 1 and 5"; assertEquals(PageUtil.pageSql(sourceSql, "mysql", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "h2", 1, 5), mysqlTargetSql); @@ -44,6 +45,7 @@ public void testPageSql() { assertEquals(PageUtil.pageSql(sourceSql, "oceanbase", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "dm", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "oracle", 1, 5), oracleTargetSql); + assertEquals(PageUtil.pageSql(sourceSql, "sqlserver", 1, 5), sqlserverTargetSql); assertThrows(NotSupportYetException.class, () -> PageUtil.pageSql(sourceSql, "xxx", 1, 5)); } @@ -60,6 +62,7 @@ void testCountSql() { assertEquals(PageUtil.countSql(sourceSql, "oceanbase"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "dm"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "oracle"), targetSql); + assertEquals(PageUtil.countSql(sourceSql, "sqlserver"), targetSql); assertThrows(NotSupportYetException.class, () -> PageUtil.countSql(sourceSql, "xxx")); } diff --git a/console/src/main/resources/static/console-fe/package-lock.json b/console/src/main/resources/static/console-fe/package-lock.json index 31172861976..c791b2a9ad5 100644 --- a/console/src/main/resources/static/console-fe/package-lock.json +++ b/console/src/main/resources/static/console-fe/package-lock.json @@ -187,6 +187,7 @@ "version": "7.18.2", "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.18.2.tgz", "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, "requires": { "@babel/types": "^7.18.2", "@jridgewell/gen-mapping": "^0.3.0", @@ -306,7 +307,8 @@ "@babel/helper-environment-visitor": { "version": "7.18.2", "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz", - "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==" + "integrity": "sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==", + "dev": true }, "@babel/helper-explode-assignable-expression": { "version": "7.16.7", @@ -321,6 +323,7 @@ "version": "7.17.9", "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, "requires": { "@babel/template": "^7.16.7", "@babel/types": "^7.17.0" @@ -330,6 +333,7 @@ "version": "7.16.7", "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -428,6 +432,7 @@ "version": "7.16.7", "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -435,8 +440,7 @@ "@babel/helper-string-parser": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { "version": "7.16.7", @@ -485,7 +489,8 @@ "@babel/parser": { "version": "7.18.5", "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.18.5.tgz", - "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==" + "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==", + "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -1071,8 +1076,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -1353,8 +1357,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -1613,8 +1616,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -1731,15 +1733,34 @@ "dev": true }, "@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-annotate-as-pure": { @@ -1877,21 +1898,103 @@ } }, "@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/types": { @@ -1917,25 +2020,25 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" }, "dependencies": { "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true } } @@ -2749,8 +2852,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -3010,8 +3112,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -4076,8 +4177,7 @@ }, "@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.22.5", @@ -4323,6 +4423,7 @@ "version": "7.16.7", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.16.7.tgz", "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", @@ -4330,22 +4431,143 @@ } }, "@babel/traverse": { - "version": "7.18.5", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.18.5.tgz", - "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.2", - "@babel/helper-environment-visitor": "^7.18.2", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.5", - "@babel/types": "^7.18.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "requires": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + } + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", @@ -4463,6 +4685,7 @@ "version": "0.3.1", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, "requires": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -4472,7 +4695,8 @@ "@jridgewell/resolve-uri": { "version": "3.0.7", "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==" + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true }, "@jridgewell/set-array": { "version": "1.1.1", @@ -4488,6 +4712,7 @@ "version": "0.3.13", "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -5135,7 +5360,6 @@ "version": "5.4.1", "resolved": "https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz", "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -5146,8 +5370,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -5927,8 +6150,7 @@ "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "body-parser": { "version": "1.20.0", @@ -6018,14 +6240,12 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -6062,34 +6282,31 @@ "version": "4.1.0", "resolved": "https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz", "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, "requires": { "bn.js": "^5.0.0", "randombytes": "^2.0.1" } }, "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", + "elliptic": "^6.5.4", "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6099,8 +6316,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -6160,8 +6376,7 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" }, "builtin-status-codes": { "version": "3.0.0", @@ -6378,7 +6593,6 @@ "version": "1.0.4", "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -7000,7 +7214,6 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -7013,7 +7226,6 @@ "version": "1.1.7", "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -7781,7 +7993,6 @@ "version": "6.5.4", "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -7795,8 +8006,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -8387,7 +8597,6 @@ "version": "1.0.3", "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -9465,7 +9674,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -9476,7 +9684,6 @@ "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9486,8 +9693,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -9495,7 +9701,6 @@ "version": "1.1.7", "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -9530,7 +9735,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -11086,7 +11290,6 @@ "version": "1.3.5", "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -11359,14 +11562,12 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "minimatch": { "version": "3.1.2", @@ -12137,7 +12338,6 @@ "version": "5.1.6", "resolved": "https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.6.tgz", "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, "requires": { "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", @@ -12232,7 +12432,6 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -12926,7 +13125,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -13545,7 +13743,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -13598,8 +13795,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass-graph": { "version": "2.2.5", @@ -13905,7 +14101,6 @@ "version": "2.4.11", "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -14528,7 +14723,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15776,8 +15970,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util.promisify": { "version": "1.1.1", diff --git a/console/src/main/resources/static/console-fe/package.json b/console/src/main/resources/static/console-fe/package.json index 45d09bfaff0..e16c89407aa 100644 --- a/console/src/main/resources/static/console-fe/package.json +++ b/console/src/main/resources/static/console-fe/package.json @@ -78,7 +78,9 @@ "@alicloud/console-components-actions": "^1.0.4", "@alicloud/console-components-app-layout": "^1.0.4", "@alicloud/console-components-console-menu": "^1.0.4", + "@babel/traverse": "^7.23.2", "axios": "^0.27.2", + "browserify-sign": "^4.2.2", "decode-uri-component": "^0.2.2", "history": "^4.10.1", "jquery": "^3.3.1", diff --git a/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java b/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java index 9e8d79051b4..860a0fb883a 100644 --- a/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java +++ b/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java @@ -117,6 +117,11 @@ public enum TransactionExceptionCode { */ FailedStore, + /** + * not raft leader exception code + */ + NotRaftLeader, + /** * Lock key conflict fail fast transaction exception code. */ diff --git a/core/src/main/java/io/seata/core/lock/RowLock.java b/core/src/main/java/io/seata/core/lock/RowLock.java index 9a41913104f..9028ff1c9f4 100644 --- a/core/src/main/java/io/seata/core/lock/RowLock.java +++ b/core/src/main/java/io/seata/core/lock/RowLock.java @@ -22,8 +22,10 @@ * * @author zhangsen */ -public class RowLock { +public class RowLock implements java.io.Serializable { + private static final long serialVersionUID = 5427149286363576988L; + private String xid; private Long transactionId; diff --git a/core/src/main/java/io/seata/core/protocol/MessageType.java b/core/src/main/java/io/seata/core/protocol/MessageType.java index 786414a8a77..b48b6eb0380 100644 --- a/core/src/main/java/io/seata/core/protocol/MessageType.java +++ b/core/src/main/java/io/seata/core/protocol/MessageType.java @@ -143,4 +143,5 @@ public interface MessageType { * the constant TYPE_BATCH_RESULT_MSG */ short TYPE_BATCH_RESULT_MSG = 121; + } diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java index f996f2a2f78..03d23262f8d 100644 --- a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java @@ -33,4 +33,5 @@ public abstract class AbstractTransactionRequest extends AbstractMessage { * @return the abstract transaction response */ public abstract AbstractTransactionResponse handle(RpcContext rpcContext); + } diff --git a/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java b/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java index f3fb20935ef..a462dfa3fb6 100644 --- a/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java +++ b/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java @@ -44,4 +44,5 @@ public interface RMInboundHandler { * @param request the request */ void handle(UndoLogDeleteRequest request); + } diff --git a/core/src/main/java/io/seata/core/rpc/RemotingClient.java b/core/src/main/java/io/seata/core/rpc/RemotingClient.java index b1d58ae132f..fe6374ab2da 100644 --- a/core/src/main/java/io/seata/core/rpc/RemotingClient.java +++ b/core/src/main/java/io/seata/core/rpc/RemotingClient.java @@ -15,6 +15,8 @@ */ package io.seata.core.rpc; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeoutException; import io.netty.channel.Channel; import io.seata.core.protocol.AbstractMessage; import io.seata.core.protocol.RpcMessage; @@ -22,9 +24,6 @@ import io.seata.core.rpc.netty.TmNettyRemotingClient; import io.seata.core.rpc.processor.RemotingProcessor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeoutException; - /** * The interface remoting client. * diff --git a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java index 5b9cd51ae3e..a4bb348aae1 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java +++ b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java @@ -15,6 +15,20 @@ */ package io.seata.core.rpc.netty; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.SocketAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; @@ -36,21 +50,6 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.net.SocketAddress; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - /** * The abstract netty remoting. * diff --git a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java index 8766cde4873..9ef4925cd1a 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java +++ b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java @@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; - import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler.Sharable; @@ -80,7 +79,6 @@ public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting private static final String SINGLE_LOG_POSTFIX = ";"; private static final int MAX_MERGE_SEND_MILLS = 1; private static final String THREAD_PREFIX_SPLIT_CHAR = "_"; - private static final int MAX_MERGE_SEND_THREAD = 1; private static final long KEEP_ALIVE_TIME = Integer.MAX_VALUE; private static final long SCHEDULE_DELAY_MILLS = 60 * 1000L; @@ -99,9 +97,8 @@ public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting * {@link AbstractNettyRemotingClient#isEnableClientBatchSendRequest()} */ protected final ConcurrentHashMap> basketMap = new ConcurrentHashMap<>(); - private final NettyClientBootstrap clientBootstrap; - private NettyClientChannelManager clientChannelManager; + private final NettyClientChannelManager clientChannelManager; private final NettyPoolKey.TransactionRole transactionRole; private ExecutorService mergeSendExecutorService; private TransactionMessageHandler transactionMessageHandler; @@ -109,12 +106,7 @@ public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting @Override public void init() { - timerExecutor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - clientChannelManager.reconnect(getTransactionServiceGroup()); - } - }, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS); + timerExecutor.scheduleAtFixedRate(() -> clientChannelManager.reconnect(getTransactionServiceGroup()), SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS); if (this.isEnableClientBatchSendRequest()) { mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD, MAX_MERGE_SEND_THREAD, @@ -158,7 +150,7 @@ public Object sendSyncRequest(Object msg) throws TimeoutException { key -> new LinkedBlockingQueue<>()); if (!basket.offer(rpcMessage)) { LOGGER.error("put message into basketMap offer failed, serverAddress:{},rpcMessage:{}", - serverAddress, rpcMessage); + serverAddress, rpcMessage); return null; } if (LOGGER.isDebugEnabled()) { @@ -171,17 +163,16 @@ public Object sendSyncRequest(Object msg) throws TimeoutException { } try { - return messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); + Object response = messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); + return response; } catch (Exception exx) { - LOGGER.error("wait response error:{},ip:{},request:{}", - exx.getMessage(), serverAddress, rpcMessage.getBody()); + LOGGER.error("wait response error:{},ip:{},request:{}", exx.getMessage(), serverAddress, rpcMessage.getBody()); if (exx instanceof TimeoutException) { - throw (TimeoutException) exx; + throw (TimeoutException)exx; } else { throw new RuntimeException(exx); } } - } else { Channel channel = clientChannelManager.acquireChannel(serverAddress); return super.sendSync(channel, rpcMessage, timeoutMillis); @@ -257,7 +248,8 @@ protected String loadBalance(String transactionServiceGroup, Object msg) { InetSocketAddress address = null; try { @SuppressWarnings("unchecked") - List inetSocketAddressList = RegistryFactory.getInstance().aliveLookup(transactionServiceGroup); + List inetSocketAddressList = + RegistryFactory.getInstance().aliveLookup(transactionServiceGroup); address = this.doSelect(inetSocketAddressList, msg); } catch (Exception ex) { LOGGER.error(ex.getMessage()); diff --git a/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java b/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java index 49bd6b12d9c..278c1b4005a 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java +++ b/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java @@ -15,6 +15,15 @@ */ package io.seata.core.rpc.netty; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + import io.netty.channel.Channel; import io.seata.common.Constants; import io.seata.common.exception.FrameworkException; @@ -29,14 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - /** * The type channel manager. * @@ -465,7 +466,7 @@ private static Channel tryOtherApp(ConcurrentMap getRmChannels() { if (RM_CHANNELS.isEmpty()) { - return null; + return Collections.emptyMap(); } Map channels = new HashMap<>(RM_CHANNELS.size()); RM_CHANNELS.forEach((resourceId, value) -> { diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java b/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java index 8de294551a1..35ad49f2a5e 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java @@ -29,10 +29,12 @@ import java.util.stream.Collectors; import io.netty.channel.Channel; +import io.seata.common.ConfigurationKeys; import io.seata.common.exception.FrameworkErrorCode; import io.seata.common.exception.FrameworkException; import io.seata.common.util.CollectionUtils; import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; import io.seata.core.protocol.RegisterRMRequest; import io.seata.core.protocol.RegisterTMRequest; import io.seata.discovery.registry.FileRegistryServiceImpl; @@ -162,25 +164,10 @@ void destroyChannel(String serverAddress, Channel channel) { /** * Reconnect to remote server of current transaction service group. * + * @param availList avail list * @param transactionServiceGroup transaction service group */ - void reconnect(String transactionServiceGroup) { - List availList = null; - try { - availList = getAvailServerList(transactionServiceGroup); - } catch (Exception e) { - LOGGER.error("Failed to get available servers: {}", e.getMessage(), e); - return; - } - if (CollectionUtils.isEmpty(availList)) { - RegistryService registryService = RegistryFactory.getInstance(); - String clusterName = registryService.getServiceGroup(transactionServiceGroup); - - if (!(registryService instanceof FileRegistryServiceImpl)) { - LOGGER.error("no available service endpoint found in cluster '{}', please make sure registry config correct and keep your seata-server is running", clusterName); - } - return; - } + void reconnect(List availList, String transactionServiceGroup) { Set channelAddress = new HashSet<>(availList.size()); Map failedMap = new HashMap<>(); try { @@ -215,6 +202,38 @@ void reconnect(String transactionServiceGroup) { } } + /** + * Reconnect to remote server of current transaction service group. + * + * @param transactionServiceGroup transaction service group + */ + void reconnect(String transactionServiceGroup) { + List availList; + try { + availList = getAvailServerList(transactionServiceGroup); + } catch (Exception e) { + LOGGER.error("Failed to get available servers: {}", e.getMessage(), e); + return; + } + if (CollectionUtils.isEmpty(availList)) { + RegistryService registryService = RegistryFactory.getInstance(); + String clusterName = registryService.getServiceGroup(transactionServiceGroup); + + if (StringUtils.isBlank(clusterName)) { + LOGGER.error("can not get cluster name in registry config '{}{}', please make sure registry config correct", + ConfigurationKeys.SERVICE_GROUP_MAPPING_PREFIX, + transactionServiceGroup); + return; + } + + if (!(registryService instanceof FileRegistryServiceImpl)) { + LOGGER.error("no available service found in cluster '{}', please make sure registry config correct and keep your seata server running", clusterName); + } + return; + } + reconnect(availList, transactionServiceGroup); + } + void invalidateObject(final String serverAddress, final Channel channel) throws Exception { nettyClientKeyPool.invalidateObject(poolKeyMap.get(serverAddress), channel); } diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java b/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java index e498eb780ce..fb3ec2dffbd 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java @@ -15,6 +15,10 @@ */ package io.seata.core.rpc.netty; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import io.netty.channel.Channel; import io.seata.common.thread.NamedThreadFactory; import io.seata.core.protocol.MessageType; @@ -28,11 +32,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - /** * The netty remoting server. * diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java b/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java index d41e296ee97..1181435d0af 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java @@ -140,6 +140,7 @@ public int getListenPort() { @Override public void start() { + int port = getListenPort(); this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker) .channel(NettyServerConfig.SERVER_CHANNEL_CLAZZ) .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize()) @@ -151,7 +152,7 @@ public void start() { .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(nettyServerConfig.getWriteBufferLowWaterMark(), nettyServerConfig.getWriteBufferHighWaterMark())) - .localAddress(new InetSocketAddress(getListenPort())) + .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { @@ -166,10 +167,10 @@ public void initChannel(SocketChannel ch) { }); try { - this.serverBootstrap.bind(getListenPort()).sync(); + this.serverBootstrap.bind(port).sync(); LOGGER.info("Server started, service listen port: {}", getListenPort()); InetSocketAddress address = new InetSocketAddress(XID.getIpAddress(), XID.getPort()); - for (RegistryService registryService : MultiRegistryFactory.getInstances()) { + for (RegistryService registryService : MultiRegistryFactory.getInstances()) { registryService.register(address); } initialized.set(true); diff --git a/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java b/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java index e2a238b61df..7c5ddf1af3b 100644 --- a/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java +++ b/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java @@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; - import io.netty.channel.Channel; import io.netty.util.concurrent.EventExecutorGroup; import io.seata.common.DefaultValues; @@ -57,7 +56,6 @@ * @author zhaojun * @author zhangchenghui.dev@gmail.com */ - public final class TmNettyRemotingClient extends AbstractNettyRemotingClient { private static final Logger LOGGER = LoggerFactory.getLogger(TmNettyRemotingClient.class); private static volatile TmNettyRemotingClient instance; @@ -195,7 +193,7 @@ public void init() { if (initialized.compareAndSet(false, true)) { super.init(); if (io.seata.common.util.StringUtils.isNotBlank(transactionServiceGroup)) { - getClientChannelManager().reconnect(transactionServiceGroup); + initConnection(); } } } @@ -285,4 +283,9 @@ private String getExtraData() { sb.append(RegisterTMRequest.UDATA_AUTH_VERSION).append(EXTRA_DATA_KV_CHAR).append(signer.getSignVersion()).append(EXTRA_DATA_SPLIT_CHAR); return sb.toString(); } + + private void initConnection() { + getClientChannelManager().reconnect(transactionServiceGroup); + } + } diff --git a/core/src/main/java/io/seata/core/serializer/SerializerType.java b/core/src/main/java/io/seata/core/serializer/SerializerType.java index b99653b8510..b5f468f9e7f 100644 --- a/core/src/main/java/io/seata/core/serializer/SerializerType.java +++ b/core/src/main/java/io/seata/core/serializer/SerializerType.java @@ -56,6 +56,12 @@ public enum SerializerType { * Math.pow(2, 4) */ HESSIAN((byte)0x16), + /** + * The hessian. + *

+ * Math.pow(2, 5) + */ + JACKSON((byte)0x32), ; private final byte code; diff --git a/core/src/main/java/io/seata/core/store/BranchTransactionDO.java b/core/src/main/java/io/seata/core/store/BranchTransactionDO.java index e0fa63e876d..b14efcfb826 100644 --- a/core/src/main/java/io/seata/core/store/BranchTransactionDO.java +++ b/core/src/main/java/io/seata/core/store/BranchTransactionDO.java @@ -25,7 +25,9 @@ * * @author zhangsen */ -public class BranchTransactionDO implements Comparable { +public class BranchTransactionDO implements Comparable, java.io.Serializable { + + private static final long serialVersionUID = -2108665795230590896L; private String xid; @@ -49,6 +51,13 @@ public class BranchTransactionDO implements Comparable { private Date gmtModified; + public BranchTransactionDO(String xid, long branchId) { + this.xid = xid; + this.branchId = branchId; + } + + public BranchTransactionDO() {} + /** * Gets xid. * @@ -72,7 +81,7 @@ public void setXid(String xid) { * * @return the transaction id */ - public long getTransactionId() { + public Long getTransactionId() { return transactionId; } @@ -90,7 +99,7 @@ public void setTransactionId(long transactionId) { * * @return the branch id */ - public long getBranchId() { + public Long getBranchId() { return branchId; } @@ -162,7 +171,7 @@ public void setBranchType(String branchType) { * * @return the status */ - public int getStatus() { + public Integer getStatus() { return status; } diff --git a/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java b/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java index d94d2bca1f2..401a64da2f0 100644 --- a/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java +++ b/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java @@ -15,16 +15,17 @@ */ package io.seata.core.store; -import io.seata.common.util.StringUtils; - import java.util.Date; +import io.seata.common.util.StringUtils; /** * Global Transaction data object * * @author zhangsen */ -public class GlobalTransactionDO { +public class GlobalTransactionDO implements java.io.Serializable { + + private static final long serialVersionUID = -6770955173129666389L; private String xid; @@ -48,6 +49,12 @@ public class GlobalTransactionDO { private Date gmtModified; + public GlobalTransactionDO(String xid) { + this.xid = xid; + } + + public GlobalTransactionDO() {} + /** * Gets xid. * @@ -71,7 +78,7 @@ public void setXid(String xid) { * * @return the status */ - public int getStatus() { + public Integer getStatus() { return status; } @@ -143,7 +150,7 @@ public void setTransactionName(String transactionName) { * * @return the timeout */ - public int getTimeout() { + public Integer getTimeout() { return timeout; } @@ -161,7 +168,7 @@ public void setTimeout(int timeout) { * * @return the begin time */ - public long getBeginTime() { + public Long getBeginTime() { return beginTime; } @@ -179,7 +186,7 @@ public void setBeginTime(long beginTime) { * * @return the transaction id */ - public long getTransactionId() { + public Long getTransactionId() { return transactionId; } diff --git a/dependencies/pom.xml b/dependencies/pom.xml index e40c967a2ef..4b3378be038 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -26,6 +26,7 @@ 4.0.0 seata-dependencies + pom Seata dependencies ${project.version} @@ -46,7 +47,7 @@ 1.0 0.11 3.5.9 - 2.9.1 + 5.1.0 1.0.2 0.3.0 2.0.1 @@ -67,6 +68,7 @@ 1.21 1.10.12 1.7.1 + 1.3.13 4.1.86.Final 2.0 4.0.3 @@ -634,6 +636,11 @@ + + com.alipay.sofa + jraft-core + ${jraft.version} + com.github.luben zstd-jni diff --git a/discovery/pom.xml b/discovery/pom.xml index 7a215d57a24..754e5428034 100644 --- a/discovery/pom.xml +++ b/discovery/pom.xml @@ -40,5 +40,6 @@ seata-discovery-nacos seata-discovery-etcd3 seata-discovery-sofa + seata-discovery-raft diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java index be8b64aa515..8f8d09f4028 100644 --- a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java @@ -24,6 +24,8 @@ */ public interface LoadBalance { + String SPLIT = ":"; + /** * Select t. * diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/XIDLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/XIDLoadBalance.java index 1d210d0a551..2d4a45cf20c 100644 --- a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/XIDLoadBalance.java +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/XIDLoadBalance.java @@ -41,8 +41,6 @@ public class XIDLoadBalance implements LoadBalance { private static final LoadBalance RANDOM_LOAD_BALANCE = EnhancedServiceLoader.load(LoadBalance.class, LoadBalanceFactory.RANDOM_LOAD_BALANCE); - private static final String SPLIT = ":"; - @Override public T select(List invokers, String xid) throws Exception { if (StringUtils.isNotBlank(xid) && xid.contains(SPLIT)) { diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java index 8daad242736..16614cd1807 100644 --- a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java @@ -37,7 +37,6 @@ public class FileRegistryServiceImpl implements RegistryService + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-raft + seata-discovery-raft ${project.version} + discovery-raft for Seata built with Maven + + + + io.seata + seata-discovery-core + ${project.version} + + + org.apache.httpcomponents + httpclient + + + com.fasterxml.jackson.core + jackson-databind + + + \ No newline at end of file diff --git a/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryProvider.java b/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryProvider.java new file mode 100644 index 00000000000..26469bc7985 --- /dev/null +++ b/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.raft; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author funkye + */ +@LoadLevel(name = "Raft", order = 1) +public class RaftRegistryProvider implements RegistryProvider { + + @Override + public RegistryService provide() { + return RaftRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryServiceImpl.java b/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryServiceImpl.java new file mode 100644 index 00000000000..feb512bdda8 --- /dev/null +++ b/discovery/seata-discovery-raft/src/main/java/io/seata/discovery/registry/raft/RaftRegistryServiceImpl.java @@ -0,0 +1,435 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.raft; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.seata.common.ConfigurationKeys; +import io.seata.common.metadata.Metadata; +import io.seata.common.metadata.MetadataResponse; +import io.seata.common.metadata.Node; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigChangeListener; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type File registry service. + * + * @author funkye + */ +public class RaftRegistryServiceImpl implements RegistryService { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftRegistryServiceImpl.class); + + private static final String REGISTRY_TYPE = "raft"; + + private static final String PRO_SERVER_ADDR_KEY = "serverAddr"; + + private static volatile RaftRegistryServiceImpl instance; + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private static final String IP_PORT_SPLIT_CHAR = ":"; + + private static final Map> INIT_ADDRESSES = new HashMap<>(); + + private static final Metadata METADATA = new Metadata(); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final PoolingHttpClientConnectionManager POOLING_HTTP_CLIENT_CONNECTION_MANAGER = + new PoolingHttpClientConnectionManager(); + + private static final Map HTTP_CLIENT_MAP = new ConcurrentHashMap<>(); + + private static volatile String CURRENT_TRANSACTION_SERVICE_GROUP; + + private static volatile String CURRENT_TRANSACTION_CLUSTER_NAME; + + private static volatile ThreadPoolExecutor REFRESH_METADATA_EXECUTOR; + + private static final AtomicBoolean CLOSED = new AtomicBoolean(false); + + /** + * Service node health check + */ + private static final Map> ALIVE_NODES = new ConcurrentHashMap<>(); + + static { + POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(10); + POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(10); + } + + private RaftRegistryServiceImpl() {} + + /** + * Gets instance. + * + * @return the instance + */ + static RaftRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (RaftRegistryServiceImpl.class) { + if (instance == null) { + instance = new RaftRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + + } + + @Override + public void subscribe(String cluster, ConfigChangeListener listener) throws Exception { + + } + + @Override + public void unsubscribe(String cluster, ConfigChangeListener listener) throws Exception { + + } + + protected static void startQueryMetadata() { + if (REFRESH_METADATA_EXECUTOR == null) { + synchronized (INIT_ADDRESSES) { + if (REFRESH_METADATA_EXECUTOR == null) { + REFRESH_METADATA_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshMetadata", 1, true)); + REFRESH_METADATA_EXECUTOR.execute(() -> { + long metadataMaxAgeMs = CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); + long currentTime = System.currentTimeMillis(); + while (!CLOSED.get()) { + // Forced refresh of metadata information after set age + boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; + String clusterName = CURRENT_TRANSACTION_CLUSTER_NAME; + if (!fetch) { + fetch = watch(); + } + // Cluster changes or reaches timeout refresh time + if (fetch) { + AtomicBoolean success = new AtomicBoolean(true); + METADATA.groups(clusterName).parallelStream().forEach(group -> { + try { + acquireClusterMetaData(clusterName, group); + } catch (Exception e) { + success.set(false); + // prevents an exception from being thrown that causes the thread to break + LOGGER.error("failed to get the leader address,error: {}", e.getMessage()); + } + }); + if (success.get()) { + currentTime = System.currentTimeMillis(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refresh seata cluster metadata time: {}", currentTime); + } + } + } + } + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + CLOSED.compareAndSet(false, true); + REFRESH_METADATA_EXECUTOR.shutdown(); + HTTP_CLIENT_MAP.values().parallelStream().forEach(client -> { + try { + client.close(); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + }); + })); + } + } + } + } + + private static String queryHttpAddress(String clusterName, String group) { + List nodeList = METADATA.getNodes(clusterName, group); + List addressList = null; + Stream stream = null; + if (CollectionUtils.isNotEmpty(nodeList)) { + List inetSocketAddresses = ALIVE_NODES.get(CURRENT_TRANSACTION_SERVICE_GROUP); + if (CollectionUtils.isEmpty(inetSocketAddresses)) { + addressList = + nodeList.stream().map(node -> node.getControl().createAddress()).collect(Collectors.toList()); + } else { + stream = inetSocketAddresses.stream(); + } + } else { + stream = INIT_ADDRESSES.get(clusterName).stream(); + } + if (addressList != null) { + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } else { + Map map = new HashMap<>(); + if (CollectionUtils.isNotEmpty(nodeList)) { + for (Node node : nodeList) { + map.put(node.getTransaction().getHost() + IP_PORT_SPLIT_CHAR + node.getTransaction().getPort(), + node); + } + } + addressList = stream.map(inetSocketAddress -> { + String host = inetSocketAddress.getAddress().getHostAddress(); + Node node = map.get(host + IP_PORT_SPLIT_CHAR + inetSocketAddress.getPort()); + return host + IP_PORT_SPLIT_CHAR + + (node != null ? node.getControl().getPort() : inetSocketAddress.getPort()); + }).collect(Collectors.toList()); + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } + } + + private static String getRaftAddrFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, + REGISTRY_TYPE, PRO_SERVER_ADDR_KEY); + } + + private InetSocketAddress convertInetSocketAddress(Node node) { + Node.Endpoint endpoint = node.getTransaction(); + return new InetSocketAddress(endpoint.getHost(), endpoint.getPort()); + } + + @Override + public void close() { + CLOSED.compareAndSet(false, true); + } + + @Override + public List aliveLookup(String transactionServiceGroup) { + if (METADATA.isRaftMode()) { + String clusterName = getServiceGroup(transactionServiceGroup); + Node leader = METADATA.getLeader(clusterName); + if (leader != null) { + return Collections.singletonList(convertInetSocketAddress(leader)); + } + } + return RegistryService.super.aliveLookup(transactionServiceGroup); + } + + private static boolean watch() { + Map param = new HashMap<>(); + String clusterName = CURRENT_TRANSACTION_CLUSTER_NAME; + Map groupTerms = METADATA.getClusterTerm(clusterName); + groupTerms.forEach((k, v) -> param.put(k, String.valueOf(v))); + for (String group : groupTerms.keySet()) { + String tcAddress = queryHttpAddress(clusterName, group); + try (CloseableHttpResponse response = + doPost("http://" + tcAddress + "/metadata/v1/watch", param, null, 30000)) { + if (response != null) { + StatusLine statusLine = response.getStatusLine(); + return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + } + } catch (Exception e) { + LOGGER.error("watch cluster fail: {}", e.getMessage()); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + continue; + } + break; + } + return false; + } + + @Override + public List refreshAliveLookup(String transactionServiceGroup, + List aliveAddress) { + if (METADATA.isRaftMode()) { + Node leader = METADATA.getLeader(getServiceGroup(transactionServiceGroup)); + InetSocketAddress leaderAddress = convertInetSocketAddress(leader); + return ALIVE_NODES.put(transactionServiceGroup, + aliveAddress.isEmpty() ? aliveAddress : aliveAddress.parallelStream().filter(inetSocketAddress -> { + // Since only follower will turn into leader, only the follower node needs to be listened to + return inetSocketAddress.getPort() != leaderAddress.getPort() || !inetSocketAddress.getAddress() + .getHostAddress().equals(leaderAddress.getAddress().getHostAddress()); + }).collect(Collectors.toList())); + } else { + return RegistryService.super.refreshAliveLookup(transactionServiceGroup, aliveAddress); + } + } + + private static void acquireClusterMetaDataByClusterName(String clusterName) { + acquireClusterMetaData(clusterName, ""); + } + + private static void acquireClusterMetaData(String clusterName, String group) { + String tcAddress = queryHttpAddress(clusterName, group); + if (StringUtils.isNotBlank(tcAddress)) { + Map param = new HashMap<>(); + param.put("group", group); + String response = null; + try (CloseableHttpResponse httpResponse = + doGet("http://" + tcAddress + "/metadata/v1/cluster", param, null, 1000)) { + if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + } + MetadataResponse metadataResponse; + if (StringUtils.isNotBlank(response)) { + try { + metadataResponse = OBJECT_MAPPER.readValue(response, MetadataResponse.class); + METADATA.refreshMetadata(clusterName, metadataResponse); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + } + } + + public static CloseableHttpResponse doGet(String url, Map param, Map header, + int timeout) { + CloseableHttpClient client; + try { + URIBuilder builder = new URIBuilder(url); + if (param != null) { + for (String key : param.keySet()) { + builder.addParameter(key, param.get(key)); + } + } + URI uri = builder.build(); + HttpGet httpGet = new HttpGet(uri); + if (header != null) { + header.forEach(httpGet::addHeader); + } + client = HTTP_CLIENT_MAP.computeIfAbsent(timeout, + k -> HttpClients.custom().setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) + .setDefaultRequestConfig(RequestConfig.custom().setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout).setConnectTimeout(timeout).build()) + .build()); + return client.execute(httpGet); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + public static CloseableHttpResponse doPost(String url, Map params, Map header, + int timeout) { + CloseableHttpClient client = null; + try { + URIBuilder builder = new URIBuilder(url); + URI uri = builder.build(); + HttpPost httpPost = new HttpPost(uri); + if (header != null) { + header.forEach(httpPost::addHeader); + } + List nameValuePairs = new ArrayList<>(); + params.forEach((k, v) -> { + nameValuePairs.add(new BasicNameValuePair(k, v)); + }); + String requestBody = URLEncodedUtils.format(nameValuePairs, StandardCharsets.UTF_8); + + StringEntity stringEntity = new StringEntity(requestBody, ContentType.APPLICATION_FORM_URLENCODED); + httpPost.setEntity(stringEntity); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); + client = HTTP_CLIENT_MAP.computeIfAbsent(timeout, + k -> HttpClients.custom().setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) + .setDefaultRequestConfig(RequestConfig.custom().setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout).setConnectTimeout(timeout).build()) + .build()); + return client.execute(httpPost); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + CURRENT_TRANSACTION_SERVICE_GROUP = key; + CURRENT_TRANSACTION_CLUSTER_NAME = clusterName; + if (!METADATA.containsGroup(clusterName)) { + String raftClusterAddress = CONFIG.getConfig(getRaftAddrFileKey()); + if (StringUtils.isNotBlank(raftClusterAddress)) { + List list = new ArrayList<>(); + String[] addresses = raftClusterAddress.split(","); + for (String address : addresses) { + String[] endpoint = address.split(IP_PORT_SPLIT_CHAR); + String host = endpoint[0]; + int port = Integer.parseInt(endpoint[1]); + list.add(new InetSocketAddress(host, port)); + } + if (CollectionUtils.isEmpty(list)) { + return null; + } + INIT_ADDRESSES.put(clusterName, list); + // Refresh the metadata by initializing the address + acquireClusterMetaDataByClusterName(clusterName); + startQueryMetadata(); + } + } + List nodes = METADATA.getNodes(clusterName); + if (CollectionUtils.isNotEmpty(nodes)) { + return nodes.parallelStream().map(this::convertInetSocketAddress).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + +} diff --git a/discovery/seata-discovery-raft/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-raft/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 00000000000..19bc466655b --- /dev/null +++ b/discovery/seata-discovery-raft/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.raft.RaftRegistryProvider \ No newline at end of file diff --git a/distribution/bin/seata-server.bat b/distribution/bin/seata-server.bat index 784c51f9479..3f99c8cfbea 100644 --- a/distribution/bin/seata-server.bat +++ b/distribution/bin/seata-server.bat @@ -92,8 +92,21 @@ if "%SKYWALKING_ENABLE%"=="true" ( ) else ( echo "apm-skywalking not enabled" ) +if "%JMX_ENABLE%"=="true" ( + set JMX_PORT=%JMX_PORT% + set JMX_OPTS=%JMX_OPTS% + if "%JMX_OPTS%"=="" ( + set "JMX_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" + ) + if "%JMX_PORT%"=="" ( + set "JMX_OPTS=%JMX_OPTS% -Dcom.sun.management.jmxremote.port=10055 -Dcom.sun.management.jmxremote.rmi.port=10055" + ) + echo "JMX enabled" +) else ( + echo "JMX disabled" +) -%JAVACMD% %JAVA_OPTS% %SKYWALKING_OPTS% -server -Dloader.path="%BASEDIR%"/lib -Xmx2048m -Xms2048m -Xss512k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%BASEDIR%"/logs/java_heapdump.hprof -XX:+DisableExplicitGC -Xloggc:"%BASEDIR%"/logs/seata_gc.log -verbose:gc -Dio.netty.leakDetectionLevel=advanced -classpath %CLASSPATH% -Dapp.name="seata-server" -Dapp.repo="%REPO%" -Dapp.home="%BASEDIR%" -Dbasedir="%BASEDIR%" -Dspring.config.location="%BASEDIR%"/conf/application.yml -Dspring.config.additional-location="%BASEDIR%"/conf/ -Dlogging.config="%BASEDIR%"/conf/logback-spring.xml -jar "%BASEDIR%"/target/seata-server.jar %CMD_LINE_ARGS% +%JAVACMD% %JAVA_OPTS% %SKYWALKING_OPTS% %JMX_OPTS% -server -Dloader.path="%BASEDIR%"/lib -Xmx2048m -Xms2048m -Xss512k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%BASEDIR%"/logs/java_heapdump.hprof -XX:+DisableExplicitGC -Xloggc:"%BASEDIR%"/logs/seata_gc.log -verbose:gc -Dio.netty.leakDetectionLevel=advanced -classpath %CLASSPATH% -Dapp.name="seata-server" -Dapp.repo="%REPO%" -Dapp.home="%BASEDIR%" -Dbasedir="%BASEDIR%" -Dspring.config.location="%BASEDIR%"/conf/application.yml -Dspring.config.additional-location="%BASEDIR%"/conf/ -Dlogging.config="%BASEDIR%"/conf/logback-spring.xml -jar "%BASEDIR%"/target/seata-server.jar %CMD_LINE_ARGS% if %ERRORLEVEL% NEQ 0 goto error goto end diff --git a/distribution/bin/seata-setup.sh b/distribution/bin/seata-setup.sh index dac8bcccea8..82f890a6707 100644 --- a/distribution/bin/seata-setup.sh +++ b/distribution/bin/seata-setup.sh @@ -150,6 +150,21 @@ fi JAVA_OPT="${JAVA_OPT} -Dio.netty.leakDetectionLevel=advanced" JAVA_OPT="${JAVA_OPT} -Dapp.name=seata-server -Dapp.pid=${$} -Dapp.home=${BASEDIR} -Dbasedir=${BASEDIR}" +if [ "$JMX_ENABLE" = "true" ]; then + JMX_PORT=$JMX_PORT + JMX_OPTS=$JMX_OPTS + if [ -z "$JMX_OPTS" ]; then + JMX_OPTS=" -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false " + fi + if [ -z "$JMX_PORT" ]; then + JMX_OPTS=" $JMX_OPTS -Dcom.sun.management.jmxremote.port=${JMX_PORT:="10055"} -Dcom.sun.management.jmxremote.rmi.port=${JMX_PORT:="10055"} " + fi + echo "JMX enabled" +else + echo "JMX disabled" +fi + +JAVA_OPT="${JAVA_OPT} ${JMX_OPTS}" if [ ! -x "$BASEDIR"/logs ]; then mkdir "$BASEDIR"/logs diff --git a/distribution/release-seata.xml b/distribution/release-seata.xml index f6663e34ddf..2d2e78340de 100644 --- a/distribution/release-seata.xml +++ b/distribution/release-seata.xml @@ -96,6 +96,10 @@ ../server/src/main/resources/application.example.yml conf/ + + ../server/src/main/resources/application.raft.example.yml + conf/ + ../server/src/main/resources/logback-spring.xml diff --git a/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java b/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java index 2f317b9ddf0..2958def37ec 100644 --- a/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java +++ b/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java @@ -131,7 +131,7 @@ private CloseableHttpClient initHttpClientInstance(T paramObject) { protected abstract void buildClientEntity(CloseableHttpClient httpClient, T paramObject); private K wrapHttpExecute(Class returnType, CloseableHttpClient httpClient, HttpUriRequest httpUriRequest, - Map headers) throws IOException { + Map headers) throws IOException { CloseableHttpResponse response; String xid = RootContext.getXID(); if (xid != null) { diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java index 50c59cf6bc5..e97387a99dd 100644 --- a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java @@ -37,7 +37,7 @@ public class MySQLUndoUpdateExecutor extends AbstractUndoExecutor { /** - * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 =? and pk2 =? + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 = ? and pk2 = ? */ private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java index 7d034fdbed4..744fa43d81b 100644 --- a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java @@ -42,6 +42,7 @@ public class KryoSerializerFactory { private static final KryoSerializerFactory FACTORY = new KryoSerializerFactory(); + private static final Map TYPE_MAP = new ConcurrentHashMap<>(); private Pool pool = new Pool(true, true) { @Override @@ -74,8 +75,6 @@ public Kryo create() { }; - private static final Map TYPE_MAP = new ConcurrentHashMap<>(); - private KryoSerializerFactory() {} public static KryoSerializerFactory getInstance() { diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java index 89b27d61f8e..b78d140e6f5 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java @@ -17,8 +17,8 @@ import java.util.concurrent.ThreadPoolExecutor; -import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.ExpressionResolver; import io.seata.saga.engine.invoker.ServiceInvokerManager; import io.seata.saga.engine.repo.StateLogRepository; import io.seata.saga.engine.repo.StateMachineRepository; @@ -67,11 +67,11 @@ public interface StateMachineConfig { ExpressionFactoryManager getExpressionFactoryManager(); /** - * Gets get evaluator factory manager. + * Gets get expression resolver * - * @return the get evaluator factory manager + * @return the get expression resolver */ - EvaluatorFactoryManager getEvaluatorFactoryManager(); + ExpressionResolver getExpressionResolver(); /** * Gets get charset. diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java deleted file mode 100644 index 3e2e6ae5a12..00000000000 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 1999-2019 Seata.io Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.seata.saga.engine.evaluation; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import io.seata.common.util.StringUtils; - -/** - * Evaluator Factory Manager - * - * @author lorne.cl - * @see EvaluatorFactory - * @see Evaluator - */ -public class EvaluatorFactoryManager { - - public static final String EVALUATOR_TYPE_DEFAULT = "Default"; - - private Map evaluatorFactoryMap = new ConcurrentHashMap<>(); - - public EvaluatorFactory getEvaluatorFactory(String type) { - - if (StringUtils.isBlank(type)) { - type = EVALUATOR_TYPE_DEFAULT; - } - return this.evaluatorFactoryMap.get(type); - } - - public Map getEvaluatorFactoryMap() { - return evaluatorFactoryMap; - } - - public void setEvaluatorFactoryMap(Map evaluatorFactoryMap) { - this.evaluatorFactoryMap.putAll(evaluatorFactoryMap); - } - - public void putEvaluatorFactory(String type, EvaluatorFactory factory) { - this.evaluatorFactoryMap.put(type, factory); - } -} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java deleted file mode 100644 index 5f5ffc0e206..00000000000 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 1999-2019 Seata.io Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.seata.saga.engine.evaluation.exception; - -import java.util.Map; - -import io.seata.common.exception.FrameworkErrorCode; -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.exception.EngineExecutionException; -import io.seata.saga.statelang.domain.DomainConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.StringUtils; - -/** - * Exception match evaluator - * - * @author lorne.cl - */ -public class ExceptionMatchEvaluator implements Evaluator { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionMatchEvaluator.class); - - private String exceptionString; - - private Class exceptionClass; - - private String rootObjectName = DomainConstants.VAR_NAME_CURRENT_EXCEPTION; - - @Override - public boolean evaluate(Map variables) { - - Object eObj = variables.get(getRootObjectName()); - if ((eObj instanceof Exception) && StringUtils.hasText(exceptionString)) { - - Exception e = (Exception)eObj; - - String exceptionClassName = e.getClass().getName(); - if (exceptionClassName.equals(exceptionString)) { - return true; - } - try { - if (exceptionClass.isAssignableFrom(e.getClass())) { - return true; - } - } catch (Exception e1) { - LOGGER.error("Exception Match failed. expression[{}]", exceptionString, e1); - } - } - - return false; - } - - public String getExceptionString() { - return exceptionString; - } - - @SuppressWarnings("unchecked") - public void setExceptionString(String exceptionString) { - this.exceptionString = exceptionString; - try { - this.exceptionClass = (Class)Class.forName(exceptionString); - } catch (ClassNotFoundException e) { - throw new EngineExecutionException(e, exceptionString + " is not a Exception Class", - FrameworkErrorCode.NotExceptionClass); - } - } - - public Class getExceptionClass() { - return exceptionClass; - } - - public void setExceptionClass(Class exceptionClass) { - this.exceptionClass = exceptionClass; - this.exceptionString = exceptionClass.getName(); - } - - public String getRootObjectName() { - return rootObjectName; - } - - public void setRootObjectName(String rootObjectName) { - this.rootObjectName = rootObjectName; - } -} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java deleted file mode 100644 index b35792beb53..00000000000 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 1999-2019 Seata.io Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.seata.saga.engine.evaluation.expression; - -import java.util.Map; - -import io.seata.common.exception.FrameworkErrorCode; -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.exception.EngineExecutionException; -import io.seata.saga.engine.expression.Expression; -import io.seata.saga.statelang.domain.DomainConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.StringUtils; - -/** - * Expression evaluator - * - * @author lorne.cl - */ -public class ExpressionEvaluator implements Evaluator { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionEvaluator.class); - - private Expression expression; - - /** - * If it is empty, use variables as the root variable, otherwise take rootObjectName as the root. - */ - private String rootObjectName = DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT; - - @Override - public boolean evaluate(Map variables) { - - Object rootObject; - if (StringUtils.hasText(this.rootObjectName)) { - rootObject = variables.get(this.rootObjectName); - } else { - rootObject = variables; - } - - Object result; - try { - result = expression.getValue(rootObject); - } catch (Exception e) { - result = Boolean.FALSE; - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Expression [{}] execute failed, and it will return false by default. variables:{}", - expression.getExpressionString(), variables, e); - } - } - - if (result == null) { - throw new EngineExecutionException("Evaluation returns null", FrameworkErrorCode.EvaluationReturnsNull); - } - if (!(result instanceof Boolean)) { - throw new EngineExecutionException( - "Evaluation returns non-Boolean: " + result + " (" + result.getClass().getName() + ")", - FrameworkErrorCode.EvaluationReturnsNonBoolean); - } - return (Boolean)result; - } - - public Expression getExpression() { - return expression; - } - - public void setExpression(Expression expression) { - this.expression = expression; - } - - public String getRootObjectName() { - return rootObjectName; - } - - public void setRootObjectName(String rootObjectName) { - this.rootObjectName = rootObjectName; - } -} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java deleted file mode 100644 index 8dd47610476..00000000000 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 1999-2019 Seata.io Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.seata.saga.engine.evaluation.expression; - -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.evaluation.EvaluatorFactory; -import io.seata.saga.engine.expression.ExpressionFactory; - -/** - * Expression evaluator factory - * - * @author lorne.cl - */ -public class ExpressionEvaluatorFactory implements EvaluatorFactory { - - private ExpressionFactory expressionFactory; - - @Override - public Evaluator createEvaluator(String expressionString) { - - ExpressionEvaluator evaluator = new ExpressionEvaluator(); - evaluator.setExpression(this.expressionFactory.createExpression(expressionString)); - return evaluator; - } - - public ExpressionFactory getExpressionFactory() { - return expressionFactory; - } - - public void setExpressionFactory(ExpressionFactory expressionFactory) { - this.expressionFactory = expressionFactory; - } -} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionResolver.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionResolver.java new file mode 100644 index 00000000000..2f262f93cb3 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionResolver.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.engine.expression; + +/** + * Expression structure resolver + * + * @author ptyin + */ +public interface ExpressionResolver { + Expression getExpression(String expressionStr); + + ExpressionFactoryManager getExpressionFactoryManager(); + + void setExpressionFactoryManager(ExpressionFactoryManager expressionFactoryManager); +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpression.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpression.java new file mode 100644 index 00000000000..a1b454c1ab2 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpression.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.engine.expression.exception; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.expression.Expression; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * Exception match evaluator expression + * + * @author ptyin + */ +public class ExceptionMatchExpression implements Expression { + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionMatchExpression.class); + private String expressionString; + private Class exceptionClass; + + @Override + public Object getValue(Object elContext) { + if (elContext instanceof Exception && StringUtils.hasText(expressionString)) { + + Exception e = (Exception) elContext; + + String exceptionClassName = e.getClass().getName(); + if (exceptionClassName.equals(expressionString)) { + return true; + } + try { + if (exceptionClass.isAssignableFrom(e.getClass())) { + return true; + } + } catch (Exception e1) { + LOGGER.error("Exception Match failed. expression[{}]", expressionString, e1); + } + } + + return false; + } + + @Override + public void setValue(Object value, Object elContext) { + + } + + @Override + public String getExpressionString() { + return expressionString; + } + + @SuppressWarnings("unchecked") + public void setExpressionString(String expressionString) { + this.expressionString = expressionString; + try { + this.exceptionClass = (Class) Class.forName(expressionString); + } catch (ClassNotFoundException e) { + throw new EngineExecutionException(e, expressionString + " is not a Exception Class", + FrameworkErrorCode.NotExceptionClass); + } + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpressionFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpressionFactory.java new file mode 100644 index 00000000000..74585acf24b --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/exception/ExceptionMatchExpressionFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.engine.expression.exception; + +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionFactory; + +/** + * Exception match expression factory + * + * @author ptyin + */ +public class ExceptionMatchExpressionFactory implements ExpressionFactory { + @Override + public Expression createExpression(String expressionString) { + ExceptionMatchExpression expression = new ExceptionMatchExpression(); + expression.setExpressionString(expressionString); + return expression; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/impl/DefaultExpressionResolver.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/impl/DefaultExpressionResolver.java new file mode 100644 index 00000000000..4bbd56c02f3 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/impl/DefaultExpressionResolver.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.engine.expression.impl; + +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionFactory; +import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.ExpressionResolver; + +/** + * Default {@link ExpressionResolver} implementation + * + * @author ptyin + */ +public class DefaultExpressionResolver implements ExpressionResolver { + + protected static class ExpressionStruct { + int typeStart; + + int typeEnd; + + int end; + + String type; + + String content; + } + + private ExpressionFactoryManager expressionFactoryManager; + + @Override + public Expression getExpression(String expressionStr) { + ExpressionStruct struct = parseExpressionStruct(expressionStr); + + ExpressionFactory expressionFactory = expressionFactoryManager.getExpressionFactory(struct.type); + if (expressionFactory == null) { + throw new IllegalArgumentException("Cannot get ExpressionFactory by Type[" + struct + "]"); + } + return expressionFactory.createExpression(struct.content); + } + + protected ExpressionStruct parseExpressionStruct(String expressionStr) { + ExpressionStruct struct = new ExpressionStruct(); + struct.typeStart = expressionStr.indexOf("$"); + int dot = expressionStr.indexOf(".", struct.typeStart); + int leftBracket = expressionStr.indexOf("{", struct.typeStart); + + + boolean isOldEvaluatorStyle = false; + if (struct.typeStart == 0) { + if (leftBracket < 0 && dot < 0) { + throw new IllegalArgumentException(String.format("Expression [%s] type is not closed", expressionStr)); + } + // Backward compatible for structure: $expressionType{expressionContent} + if (leftBracket > 0 && (leftBracket < dot || dot < 0)) { + struct.typeEnd = leftBracket; + isOldEvaluatorStyle = true; + } + if (dot > 0 && (dot < leftBracket || leftBracket < 0)) { + struct.typeEnd = dot; + } + } + + if (struct.typeStart == 0 && leftBracket != -1 && leftBracket < dot) { + // Backward compatible for structure: $expressionType{expressionContent} + struct.typeEnd = expressionStr.indexOf("{", struct.typeStart); + isOldEvaluatorStyle = true; + } + + // No $ indicator denotes default type + if (struct.typeStart != 0) { + struct.typeStart = struct.typeEnd = -1; + struct.type = null; + } else { + struct.type = expressionStr.substring(struct.typeStart + 1, struct.typeEnd); + } + + if (isOldEvaluatorStyle) { + struct.end = expressionStr.indexOf("}", struct.typeEnd); + } else { + struct.end = expressionStr.length(); + } + + struct.content = expressionStr.substring(struct.typeEnd + 1, struct.end); + + return struct; + } + + @Override + public ExpressionFactoryManager getExpressionFactoryManager() { + return expressionFactoryManager; + } + + @Override + public void setExpressionFactoryManager(ExpressionFactoryManager expressionFactoryManager) { + this.expressionFactoryManager = expressionFactoryManager; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java index e91b67a56ad..10e98c6ac4f 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java @@ -24,10 +24,10 @@ import io.seata.common.loader.EnhancedServiceLoader; import io.seata.saga.engine.StateMachineConfig; -import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; -import io.seata.saga.engine.evaluation.exception.ExceptionMatchEvaluatorFactory; -import io.seata.saga.engine.evaluation.expression.ExpressionEvaluatorFactory; import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.ExpressionResolver; +import io.seata.saga.engine.expression.exception.ExceptionMatchExpressionFactory; +import io.seata.saga.engine.expression.impl.DefaultExpressionResolver; import io.seata.saga.engine.expression.seq.SequenceExpressionFactory; import io.seata.saga.engine.expression.spel.SpringELExpressionFactory; import io.seata.saga.engine.invoker.ServiceInvokerManager; @@ -94,7 +94,7 @@ public class DefaultStateMachineConfig implements StateMachineConfig, Applicatio private StateLogStore stateLogStore; private StateLangStore stateLangStore; private ExpressionFactoryManager expressionFactoryManager; - private EvaluatorFactoryManager evaluatorFactoryManager; + private ExpressionResolver expressionResolver; private StateMachineRepository stateMachineRepository; private StatusDecisionStrategy statusDecisionStrategy; private SeqGenerator seqGenerator; @@ -129,19 +129,16 @@ protected void init() throws Exception { sequenceExpressionFactory.setSeqGenerator(getSeqGenerator()); expressionFactoryManager.putExpressionFactory(DomainConstants.EXPRESSION_TYPE_SEQUENCE, sequenceExpressionFactory); - } - - if (evaluatorFactoryManager == null) { - evaluatorFactoryManager = new EvaluatorFactoryManager(); - ExpressionEvaluatorFactory expressionEvaluatorFactory = new ExpressionEvaluatorFactory(); - expressionEvaluatorFactory.setExpressionFactory( - expressionFactoryManager.getExpressionFactory(ExpressionFactoryManager.DEFAULT_EXPRESSION_TYPE)); - evaluatorFactoryManager.putEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT, - expressionEvaluatorFactory); + ExceptionMatchExpressionFactory exceptionMatchExpressionFactory = new ExceptionMatchExpressionFactory(); + expressionFactoryManager.putExpressionFactory(DomainConstants.EXPRESSION_TYPE_EXCEPTION, + exceptionMatchExpressionFactory); + } - evaluatorFactoryManager.putEvaluatorFactory(DomainConstants.EVALUATOR_TYPE_EXCEPTION, - new ExceptionMatchEvaluatorFactory()); + if (expressionResolver == null) { + DefaultExpressionResolver defaultExpressionResolver = new DefaultExpressionResolver(); + defaultExpressionResolver.setExpressionFactoryManager(expressionFactoryManager); + expressionResolver = defaultExpressionResolver; } if (stateMachineRepository == null) { @@ -321,15 +318,16 @@ public ExpressionFactoryManager getExpressionFactoryManager() { public void setExpressionFactoryManager(ExpressionFactoryManager expressionFactoryManager) { this.expressionFactoryManager = expressionFactoryManager; + this.expressionResolver.setExpressionFactoryManager(expressionFactoryManager); } @Override - public EvaluatorFactoryManager getEvaluatorFactoryManager() { - return this.evaluatorFactoryManager; + public ExpressionResolver getExpressionResolver() { + return expressionResolver; } - public void setEvaluatorFactoryManager(EvaluatorFactoryManager evaluatorFactoryManager) { - this.evaluatorFactoryManager = evaluatorFactoryManager; + public void setExpressionResolver(ExpressionResolver expressionResolver) { + this.expressionResolver = expressionResolver; } @Override diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java index 6e2cc620afb..f75290b6a67 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java @@ -378,7 +378,7 @@ protected Map replayContextVariables(StateMachineInstance stateM if (CollectionUtils.isNotEmpty(state.getOutput())) { try { Map outputVariablesToContext = ParameterUtils - .createOutputParams(stateMachineConfig.getExpressionFactoryManager(), state, + .createOutputParams(stateMachineConfig.getExpressionResolver(), state, serviceOutputParams); if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { contextVariables.putAll(outputVariablesToContext); diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java index 19925137f12..48eb1be8174 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java @@ -21,10 +21,9 @@ import io.seata.common.exception.FrameworkErrorCode; import io.seata.saga.engine.StateMachineConfig; -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.evaluation.EvaluatorFactory; -import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionResolver; import io.seata.saga.engine.pcext.StateHandler; import io.seata.saga.engine.pcext.StateInstruction; import io.seata.saga.engine.pcext.utils.EngineUtils; @@ -60,8 +59,10 @@ public void process(ProcessContext context) throws EngineExecutionException { choiceEvaluators = new LinkedHashMap<>(0); } else { choiceEvaluators = new LinkedHashMap<>(choices.size()); + ExpressionResolver resolver = ((StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG)).getExpressionResolver(); for (ChoiceState.Choice choice : choices) { - Evaluator evaluator = getEvaluatorFactory(context).createEvaluator(choice.getExpression()); + Expression evaluator = resolver.getExpression(choice.getExpression()); choiceEvaluators.put(evaluator, choice.getNext()); } } @@ -70,10 +71,11 @@ public void process(ProcessContext context) throws EngineExecutionException { } } - Evaluator evaluator; + Expression expression; for (Map.Entry entry : choiceEvaluators.entrySet()) { - evaluator = (Evaluator)entry.getKey(); - if (evaluator.evaluate(context.getVariables())) { + expression = (Expression) entry.getKey(); + if (Boolean.TRUE.equals(expression.getValue(context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT)))) { context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, entry.getValue()); return; } @@ -93,11 +95,4 @@ public void process(ProcessContext context) throws EngineExecutionException { context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, choiceState.getDefault()); } - - public EvaluatorFactory getEvaluatorFactory(ProcessContext context) { - StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( - DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); - return stateMachineConfig.getEvaluatorFactoryManager().getEvaluatorFactory( - EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT); - } } \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java index 9e26796ac78..f5647df2ac4 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java @@ -66,7 +66,7 @@ public void preProcess(ProcessContext context) throws EngineExecutionException { List serviceInputParams = null; if (contextVariables != null) { try { - serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), null, + serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionResolver(), null, state, contextVariables); } catch (Exception e) { @@ -108,7 +108,7 @@ public void postProcess(ProcessContext context, Exception exp) throws EngineExec if (serviceOutputParams != null) { try { Map outputVariablesToContext = ParameterUtils.createOutputParams( - stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams); + stateMachineConfig.getExpressionResolver(), state, serviceOutputParams); if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { contextVariables.putAll(outputVariablesToContext); } diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java index c17bb9ec2f7..2af9e2b9641 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java @@ -24,11 +24,11 @@ import io.seata.common.loader.LoadLevel; import io.seata.common.util.CollectionUtils; import io.seata.saga.engine.StateMachineConfig; -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.evaluation.EvaluatorFactory; -import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; -import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator; import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionResolver; +import io.seata.saga.engine.expression.exception.ExceptionMatchExpression; +import io.seata.saga.engine.expression.spel.SpringELExpression; import io.seata.saga.engine.pcext.InterceptableStateHandler; import io.seata.saga.engine.pcext.StateHandlerInterceptor; import io.seata.saga.engine.pcext.StateInstruction; @@ -100,7 +100,7 @@ public void preProcess(ProcessContext context) throws EngineExecutionException { List serviceInputParams = null; if (contextVariables != null) { try { - serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), stateInstance, + serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionResolver(), stateInstance, state, contextVariables); } catch (Exception e) { @@ -250,7 +250,7 @@ public void postProcess(ProcessContext context, Exception exp) throws EngineExec if (serviceOutputParams != null) { try { Map outputVariablesToContext = ParameterUtils.createOutputParams( - stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams); + stateMachineConfig.getExpressionResolver(), state, serviceOutputParams); if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { contextVariables.putAll(outputVariablesToContext); } @@ -308,6 +308,7 @@ private void decideExecutionStatus(ProcessContext context, StateInstance stateIn } else { StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + ExpressionResolver resolver = stateMachineConfig.getExpressionResolver(); Map statusEvaluators = state.getStatusEvaluators(); if (statusEvaluators == null) { @@ -316,11 +317,11 @@ private void decideExecutionStatus(ProcessContext context, StateInstance stateIn if (statusEvaluators == null) { statusEvaluators = new LinkedHashMap<>(statusMatchList.size()); String expressionStr, statusVal; - Evaluator evaluator; + Expression evaluator; for (Map.Entry entry : statusMatchList.entrySet()) { expressionStr = entry.getKey(); statusVal = entry.getValue(); - evaluator = createEvaluator(stateMachineConfig.getEvaluatorFactoryManager(), expressionStr); + evaluator = resolver.getExpression(expressionStr); if (evaluator != null) { statusEvaluators.put(evaluator, statusVal); } @@ -331,9 +332,20 @@ private void decideExecutionStatus(ProcessContext context, StateInstance stateIn } for (Object evaluatorObj : statusEvaluators.keySet()) { - Evaluator evaluator = (Evaluator)evaluatorObj; + Expression evaluator = (Expression) evaluatorObj; String statusVal = statusEvaluators.get(evaluator); - if (evaluator.evaluate(context.getVariables())) { + Object elContext; + + Class expressionClass = evaluator.getClass(); + if (expressionClass.isAssignableFrom(ExceptionMatchExpression.class)) { + elContext = context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + } else if (expressionClass.isAssignableFrom(SpringELExpression.class)) { + elContext = context.getVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + } else { + elContext = context.getVariables(); + } + + if (Boolean.TRUE.equals(evaluator.getValue(elContext))) { stateInstance.setStatus(ExecutionStatus.valueOf(statusVal)); break; } @@ -398,37 +410,4 @@ private void decideExecutionStatus(ProcessContext context, StateInstance stateIn LOGGER.info("State[{}] finish with status[{}]", state.getName(), stateInstance.getStatus()); } } - - private Evaluator createEvaluator(EvaluatorFactoryManager evaluatorFactoryManager, String expressionStr) { - String expressionType = null; - String expressionContent = null; - Evaluator evaluator = null; - if (StringUtils.hasLength(expressionStr)) { - if (expressionStr.startsWith("$")) { - int expTypeStart = expressionStr.indexOf("$"); - int expTypeEnd = expressionStr.indexOf("{", expTypeStart); - - if (expTypeStart >= 0 && expTypeEnd > expTypeStart) { - expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd); - } - - int expEnd = expressionStr.lastIndexOf("}"); - if (expTypeEnd > 0 && expEnd > expTypeEnd) { - expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd); - } - } else { - expressionContent = expressionStr; - } - - EvaluatorFactory evaluatorFactory = evaluatorFactoryManager.getEvaluatorFactory(expressionType); - if (evaluatorFactory == null) { - throw new IllegalArgumentException("Cannot get EvaluatorFactory by Type[" + expressionType + "]"); - } - evaluator = evaluatorFactory.createEvaluator(expressionContent); - if (evaluator instanceof ExpressionEvaluator) { - ((ExpressionEvaluator)evaluator).setRootObjectName(DomainConstants.VAR_NAME_OUTPUT_PARAMS); - } - } - return evaluator; - } } diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java index 1f07b2f0e8b..6c36cbcd877 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java @@ -21,7 +21,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import io.seata.common.exception.FrameworkErrorCode; @@ -29,9 +28,8 @@ import io.seata.common.util.NumberUtils; import io.seata.common.util.StringUtils; import io.seata.saga.engine.StateMachineConfig; -import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; -import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator; import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.engine.expression.ExpressionResolver; import io.seata.saga.engine.pcext.StateInstruction; import io.seata.saga.proctrl.ProcessContext; import io.seata.saga.proctrl.impl.ProcessContextImpl; @@ -55,11 +53,8 @@ public class LoopTaskUtils { private static final Logger LOGGER = LoggerFactory.getLogger(LoopTaskUtils.class); - private static final String DEFAULT_COMPLETION_CONDITION = "[nrOfInstances] == [nrOfCompletedInstances]"; public static final String LOOP_STATE_NAME_PATTERN = "-loop-"; - private static final Map EXPRESSION_EVALUATOR_MAP = new ConcurrentHashMap<>(); - /** * get Loop Config from State * @@ -81,7 +76,7 @@ public static Loop getLoopConfig(ProcessContext context, State currentState) { String collectionName = loop.getCollection(); if (StringUtils.isNotBlank(collectionName)) { Object expression = ParameterUtils.createValueExpression( - stateMachineConfig.getExpressionFactoryManager(), collectionName); + stateMachineConfig.getExpressionResolver(), collectionName); Object collection = ParameterUtils.getValue(expression, stateMachineInstance.getContext(), null); if (collection instanceof Collection && ((Collection)collection).size() > 0) { LoopContextHolder.getCurrent(context, true).setCollection((Collection)collection); @@ -237,9 +232,12 @@ public static boolean isCompletionConditionSatisfied(ProcessContext context) { stateMachineContext.put(DomainConstants.NUMBER_OF_ACTIVE_INSTANCES, (double)nrOfActiveInstances); stateMachineContext.put(DomainConstants.NUMBER_OF_COMPLETED_INSTANCES, (double)nrOfCompletedInstances); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + ExpressionResolver resolver = stateMachineConfig.getExpressionResolver(); - if (nrOfCompletedInstances >= nrOfInstances || getEvaluator(context, - currentState.getLoop().getCompletionCondition()).evaluate(stateMachineContext)) { + if (nrOfCompletedInstances >= nrOfInstances || Boolean.TRUE.equals(resolver.getExpression( + currentState.getLoop().getCompletionCondition()).getValue(stateMachineContext))) { currentLoopContext.setCompletionConditionSatisfied(true); } } @@ -309,7 +307,7 @@ public static void putContextToParent(ProcessContext context, List outputVariablesToContext = ParameterUtils.createOutputParams( - stateMachineConfig.getExpressionFactoryManager(), (AbstractTaskState)state, stateInstance.getOutputParams()); + stateMachineConfig.getExpressionResolver(), (AbstractTaskState)state, stateInstance.getOutputParams()); subContextVariables.add(outputVariablesToContext); } @@ -394,29 +392,6 @@ public static String decideCurrentExceptionRoute(List subContext return route; } - /** - * get loop completion condition evaluator - * - * @param context the process context - * @param completionCondition the completion condition - * @return the expression evaluator - */ - private static ExpressionEvaluator getEvaluator(ProcessContext context, String completionCondition) { - if (StringUtils.isBlank(completionCondition)) { - completionCondition = DEFAULT_COMPLETION_CONDITION; - } - if (!EXPRESSION_EVALUATOR_MAP.containsKey(completionCondition)) { - StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( - DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); - ExpressionEvaluator expressionEvaluator = (ExpressionEvaluator)stateMachineConfig - .getEvaluatorFactoryManager().getEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT) - .createEvaluator(completionCondition); - expressionEvaluator.setRootObjectName(null); - EXPRESSION_EVALUATOR_MAP.put(completionCondition, expressionEvaluator); - } - return EXPRESSION_EVALUATOR_MAP.get(completionCondition); - } - private static StateInstruction copyInstruction(StateInstruction instruction) { StateInstruction targetInstruction = new StateInstruction(); targetInstruction.setStateMachineName(instruction.getStateMachineName()); @@ -426,4 +401,4 @@ private static StateInstruction copyInstruction(StateInstruction instruction) { return targetInstruction; } -} \ No newline at end of file +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java index a44432b5a92..62cf57cae45 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java @@ -17,8 +17,7 @@ import io.seata.common.util.CollectionUtils; import io.seata.saga.engine.expression.Expression; -import io.seata.saga.engine.expression.ExpressionFactory; -import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.ExpressionResolver; import io.seata.saga.engine.expression.seq.SequenceExpression; import io.seata.saga.statelang.domain.StateInstance; import io.seata.saga.statelang.domain.impl.AbstractTaskState; @@ -38,7 +37,7 @@ */ public class ParameterUtils { - public static List createInputParams(ExpressionFactoryManager expressionFactoryManager, + public static List createInputParams(ExpressionResolver expressionResolver, StateInstanceImpl stateInstance, AbstractTaskState serviceTaskState, Object variablesFrom) { List inputAssignments = serviceTaskState.getInput(); @@ -53,7 +52,7 @@ public static List createInputParams(ExpressionFactoryManager expression if (inputExpressions == null) { inputExpressions = new ArrayList<>(inputAssignments.size()); for (Object inputAssignment : inputAssignments) { - inputExpressions.add(createValueExpression(expressionFactoryManager, inputAssignment)); + inputExpressions.add(createValueExpression(expressionResolver, inputAssignment)); } } serviceTaskState.setInputExpressions(inputExpressions); @@ -68,7 +67,7 @@ public static List createInputParams(ExpressionFactoryManager expression return inputValues; } - public static Map createOutputParams(ExpressionFactoryManager expressionFactoryManager, + public static Map createOutputParams(ExpressionResolver expressionResolver, AbstractTaskState serviceTaskState, Object variablesFrom) { Map outputAssignments = serviceTaskState.getOutput(); if (CollectionUtils.isEmpty(outputAssignments)) { @@ -83,7 +82,7 @@ public static Map createOutputParams(ExpressionFactoryManager ex outputExpressions = new LinkedHashMap<>(outputAssignments.size()); for (Map.Entry entry : outputAssignments.entrySet()) { outputExpressions.put(entry.getKey(), - createValueExpression(expressionFactoryManager, entry.getValue())); + createValueExpression(expressionResolver, entry.getValue())); } } serviceTaskState.setOutputExpressions(outputExpressions); @@ -126,8 +125,8 @@ public static Object getValue(Object valueExpression, Object variablesFrom, Stat } } - public static Object createValueExpression(ExpressionFactoryManager expressionFactoryManager, - Object paramAssignment) { + public static Object createValueExpression(ExpressionResolver expressionResolver, + Object paramAssignment) { Object valueExpression; @@ -137,41 +136,21 @@ public static Object createValueExpression(ExpressionFactoryManager expressionFa Map paramMapAssignment = (Map)paramAssignment; Map paramMap = new LinkedHashMap<>(paramMapAssignment.size()); paramMapAssignment.forEach((paramName, valueAssignment) -> { - paramMap.put(paramName, createValueExpression(expressionFactoryManager, valueAssignment)); + paramMap.put(paramName, createValueExpression(expressionResolver, valueAssignment)); }); valueExpression = paramMap; } else if (paramAssignment instanceof List) { List paramListAssignment = (List)paramAssignment; List paramList = new ArrayList<>(paramListAssignment.size()); for (Object aParamAssignment : paramListAssignment) { - paramList.add(createValueExpression(expressionFactoryManager, aParamAssignment)); + paramList.add(createValueExpression(expressionResolver, aParamAssignment)); } valueExpression = paramList; } else if (paramAssignment instanceof String && ((String)paramAssignment).startsWith("$")) { - - String expressionStr = (String)paramAssignment; - int expTypeStart = expressionStr.indexOf("$"); - int expTypeEnd = expressionStr.indexOf(".", expTypeStart); - - String expressionType = null; - if (expTypeStart >= 0 && expTypeEnd > expTypeStart) { - expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd); - } - - int expEnd = expressionStr.length(); - String expressionContent = null; - if (expTypeEnd > 0 && expEnd > expTypeEnd) { - expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd); - } - - ExpressionFactory expressionFactory = expressionFactoryManager.getExpressionFactory(expressionType); - if (expressionFactory == null) { - throw new IllegalArgumentException("Cannot get ExpressionFactory by Type[" + expressionType + "]"); - } - valueExpression = expressionFactory.createExpression(expressionContent); + valueExpression = expressionResolver.getExpression((String) paramAssignment); } else { valueExpression = paramAssignment; } return valueExpression; } -} \ No newline at end of file +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java index 586fb8e5fe2..333c5461473 100644 --- a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java @@ -91,7 +91,7 @@ public interface DomainConstants { String SEQ_ENTITY_STATE_INST = "STATE_INST"; String EXPRESSION_TYPE_SEQUENCE = "Sequence"; - String EVALUATOR_TYPE_EXCEPTION = "Exception"; + String EXPRESSION_TYPE_EXCEPTION = "Exception"; String SEPERATOR_PARENT_ID = ":"; diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java index 5fd73e75f0e..783dfe34ead 100644 --- a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java @@ -30,6 +30,7 @@ import io.seata.saga.statelang.parser.StateParser; import io.seata.saga.statelang.parser.StateParserFactory; import io.seata.saga.statelang.parser.utils.DesignerJsonTransformer; +import io.seata.saga.statelang.validator.StateMachineValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +45,8 @@ public class StateMachineParserImpl implements StateMachineParser { private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER; + private final StateMachineValidator validator = new StateMachineValidator(); + public StateMachineParserImpl(String jsonParserName) { if (StringUtils.isNotBlank(jsonParserName)) { this.jsonParserName = jsonParserName; @@ -124,6 +127,8 @@ public StateMachine parse(String json) { } } } + + validator.validate(stateMachine); return stateMachine; } @@ -134,4 +139,4 @@ public String getJsonParserName() { public void setJsonParserName(String jsonParserName) { this.jsonParserName = jsonParserName; } -} \ No newline at end of file +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/StateMachineUtils.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/StateMachineUtils.java new file mode 100644 index 00000000000..c7179841f0c --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/StateMachineUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.parser.utils; + +import io.seata.common.util.StringUtils; +import io.seata.saga.statelang.domain.ChoiceState; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.TaskState; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Util class for parsing StateMachine + * + * @author ptyin + */ +public class StateMachineUtils { + public static Set getAllPossibleSubsequentStates(State state) { + Set subsequentStates = new HashSet<>(); + // Next state + subsequentStates.add(state.getNext()); + switch (state.getType()) { + case DomainConstants.STATE_TYPE_SCRIPT_TASK: + case DomainConstants.STATE_TYPE_SERVICE_TASK: + case DomainConstants.STATE_TYPE_SUB_STATE_MACHINE: + case DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION: + // Next state in catches + Optional.ofNullable(((TaskState) state).getCatches()) + .ifPresent(c -> c.forEach(e -> subsequentStates.add(e.getNext()))); + break; + + case DomainConstants.STATE_TYPE_CHOICE: + // Choice state + Optional.ofNullable(((ChoiceState) state).getChoices()) + .ifPresent(c -> c.forEach(e -> subsequentStates.add(e.getNext()))); + // Default choice + subsequentStates.add(((ChoiceState) state).getDefault()); + break; + default: + // Otherwise do nothing + } + return subsequentStates.stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + } +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/Rule.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/Rule.java new file mode 100644 index 00000000000..e4c13977430 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/Rule.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator; + +import io.seata.saga.statelang.domain.StateMachine; + +/** + * Validation rule interface, use SPI to inject rules + * + * @author ptyin + */ +public interface Rule { + /** + * Validate a state machine + * + * @param stateMachine state machine + * @return true if passes, false if fails + */ + boolean validate(StateMachine stateMachine); + + /** + * Get the rule name + * + * @return name of the rule + */ + String getName(); + + /** + * Get hints why validation passes or fails. Use this method to show more messages about validation result. + * + * @return hint of the rule + */ + String getHint(); +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/RuleFactory.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/RuleFactory.java new file mode 100644 index 00000000000..74bf3548f06 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/RuleFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator; + +import io.seata.common.loader.EnhancedServiceLoader; + +import java.util.List; + +/** + * Factorial class to get all rules. + * + * @author ptyin + */ +public class RuleFactory { + private static final List RULES = EnhancedServiceLoader.loadAll(Rule.class); + + public static List getRules() { + return RULES; + } +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/StateMachineValidator.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/StateMachineValidator.java new file mode 100644 index 00000000000..1403644dd0e --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/StateMachineValidator.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator; + +import io.seata.saga.statelang.domain.StateMachine; + +import java.util.List; + +/** + * State machine validator used to validate rules. + * + * @author ptyin + */ +public class StateMachineValidator { + + /** + * Validate on state machine + * + * @param stateMachine state machine + * @throws ValidationException throws if there is a validation rule failed + */ + public void validate(StateMachine stateMachine) throws ValidationException { + List rules = RuleFactory.getRules(); + for (Rule rule: rules) { + boolean pass; + try { + pass = rule.validate(stateMachine); + } catch (Throwable e) { + throw new ValidationException(rule, "Exception occurs", e); + } + if (!pass) { + throw new ValidationException(rule, "Failed"); + } + } + } +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/ValidationException.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/ValidationException.java new file mode 100644 index 00000000000..ef654550b35 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/ValidationException.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator; + +import io.seata.common.util.StringUtils; + +/** + * Validation exception throws if exception occurs in validation phase. + * + * @author ptyin + */ +public class ValidationException extends RuntimeException { + + public ValidationException(Rule rule, String message) { + super(spliceMessage(rule, message)); + } + + public ValidationException(Rule rule, String message, Throwable cause) { + super(spliceMessage(rule, message), cause); + } + + private static String spliceMessage(Rule rule, String message) { + String canonicalMessage = String.format("Rule [%s]: %s", rule.getName(), message); + if (StringUtils.isNotBlank(rule.getHint())) { + canonicalMessage = canonicalMessage + ", hints: " + rule.getHint(); + } + return canonicalMessage; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/AbstractRule.java similarity index 50% rename from saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java rename to saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/AbstractRule.java index bcb9f3584db..99105b8b867 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/AbstractRule.java @@ -13,25 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.saga.engine.evaluation.exception; -import io.seata.saga.engine.evaluation.Evaluator; -import io.seata.saga.engine.evaluation.EvaluatorFactory; -import io.seata.saga.statelang.domain.DomainConstants; +package io.seata.saga.statelang.validator.impl; + +import io.seata.saga.statelang.validator.Rule; /** - * Exception match evaluator factory + * Abstract class for {@link Rule} * - * @author lorne.cl + * @author ptyin */ -public class ExceptionMatchEvaluatorFactory implements EvaluatorFactory { +public abstract class AbstractRule implements Rule { + + protected String hint; @Override - public Evaluator createEvaluator(String expressionString) { + public String getName() { + return getClass().getSimpleName(); + } - ExceptionMatchEvaluator evaluator = new ExceptionMatchEvaluator(); - evaluator.setExceptionString(expressionString); - evaluator.setRootObjectName(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); - return evaluator; + @Override + public String getHint() { + return hint; } -} \ No newline at end of file +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/FiniteTerminationRule.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/FiniteTerminationRule.java new file mode 100644 index 00000000000..61650acb816 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/FiniteTerminationRule.java @@ -0,0 +1,156 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator.impl; + +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.parser.utils.StateMachineUtils; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * Rule to check if the state machine can terminate in finite time, i.e. if there is an infinite loop + * + * @author ptyin + */ +public class FiniteTerminationRule extends AbstractRule { + + @Override + public boolean validate(StateMachine stateMachine) { + String stateName = stateMachine.getStartState(); + State startState = stateMachine.getState(stateMachine.getStartState()); + String notFoundHintTemplate = "State [%s] is not defined in state machine"; + if (startState == null) { + hint = String.format(notFoundHintTemplate, stateMachine.getStartState()); + return false; + } + + DisjointSet disjointSet = new DisjointSet(stateMachine.getStates().keySet()); + Set visited = new HashSet<>(); + Map> nextStateNameMap = new HashMap<>(); + + iterate(stateMachine, stateName, disjointSet, visited, nextStateNameMap, new Stack<>()); + + Map> rootToStateNames = new HashMap<>(); + for (String disjointStateName : stateMachine.getStates().keySet()) { + String root = disjointSet.find(disjointStateName); + if (!rootToStateNames.containsKey(root)) { + rootToStateNames.put(root, new HashSet<>()); + } + rootToStateNames.get(root).add(disjointStateName); + } + + for (Set cycleStateNames : rootToStateNames.values()) { + if (cycleStateNames.size() <= 1) { + // Not in a cycle + continue; + } + boolean noOutgoingFlow = true; + for (String cycleStateName : cycleStateNames) { + if (!cycleStateNames.containsAll(nextStateNameMap.get(cycleStateName))) { + // There is at least an outgoing flow not in this cycle + noOutgoingFlow = false; + break; + } + } + if (noOutgoingFlow) { + hint = String.format("There is a infinite loop [%s] without outgoing flow to end", + String.join(", ", cycleStateNames)); + return false; + } + } + return true; + } + + private static void iterate( + StateMachine stateMachine, + String stateName, + DisjointSet disjointSet, + Set visited, + Map> nextStateNameMap, + Stack currentPathWithoutCycles + ) { + State state = stateMachine.getState(stateName); + + if (visited.contains(stateName)) { + // If it has ever been visited before, means it is in a cycle + if (currentPathWithoutCycles.size() > 1) { + // Union all states in a cycle + int curr = currentPathWithoutCycles.size() - 1; + do { + disjointSet.union(currentPathWithoutCycles.get(curr), currentPathWithoutCycles.get(--curr)); + } while (!currentPathWithoutCycles.get(curr).equals(stateName)); + } + } else { + Set nextStateNames = StateMachineUtils.getAllPossibleSubsequentStates(state); + nextStateNameMap.put(stateName, nextStateNames); + + visited.add(stateName); + currentPathWithoutCycles.push(stateName); + for (String nextStateName: nextStateNames) { + iterate(stateMachine, nextStateName, disjointSet, visited, nextStateNameMap, currentPathWithoutCycles); + } + currentPathWithoutCycles.pop(); + visited.remove(stateName); + } + + } + + private static class DisjointSet { + Map parent = new HashMap<>(); + Map rank = new HashMap<>(); + + DisjointSet(Collection stateNames) { + for (String stateName : stateNames) { + parent.put(stateName, stateName); + rank.put(stateName, 0); + } + } + + String find(String state) { + if (parent.get(state).equals(state)) { + return state; + } + + String root = find(parent.get(state)); + parent.put(state, root); + return root; + } + + void union(String i, String j) { + String parentI = find(i), parentJ = find(j); + int rankI = rank.get(parentI), rankJ = rank.get(parentJ); + + if (!parentI.equals(parentJ)) { + + if (rankI > rankJ) { + parent.put(parentJ, parentI); + } else { + parent.put(parentI, parentJ); + if (rankI == rankJ) { + rank.put(parentI, rankI + 1); + } + } + } + } + } +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRule.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRule.java new file mode 100644 index 00000000000..821dd5b41c2 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRule.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.SubStateMachine; + +/** + * Rule to check if all SubStateMachines has no recursive call + * + * @author ptyin + */ +public class NoRecursiveSubStateMachineRule extends AbstractRule { + + @Override + public boolean validate(StateMachine stateMachine) { + for (State state: stateMachine.getStates().values()) { + if (!DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(state.getType())) { + continue; + } + if (stateMachine.getName().equals(((SubStateMachine) state).getStateMachineName())) { + hint = String.format("SubStateMachine state [%s] call itself", state.getName()); + return false; + } + } + return true; + } +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/StateNameExistsRule.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/StateNameExistsRule.java new file mode 100644 index 00000000000..51a95447c97 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/validator/impl/StateNameExistsRule.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.saga.statelang.validator.impl; + +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.parser.utils.StateMachineUtils; + +import java.util.Set; + +/** + * Rule to check if all the state name exists + * + * @author ptyin + */ +public class StateNameExistsRule extends AbstractRule { + + @Override + public boolean validate(StateMachine stateMachine) { + for (State state: stateMachine.getStates().values()) { + Set subsequentStates = StateMachineUtils.getAllPossibleSubsequentStates(state); + for (String subsequentState: subsequentStates) { + if (stateMachine.getState(subsequentState) == null) { + hint = String.format("Subsequent state [%s] of [%s] does not exist", subsequentState, state); + return false; + } + } + } + return true; + } +} diff --git a/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.validator.Rule b/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.validator.Rule new file mode 100644 index 00000000000..7137ac1326d --- /dev/null +++ b/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.validator.Rule @@ -0,0 +1,3 @@ +io.seata.saga.statelang.validator.impl.StateNameExistsRule +io.seata.saga.statelang.validator.impl.FiniteTerminationRule +io.seata.saga.statelang.validator.impl.NoRecursiveSubStateMachineRule \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java index bfe37507918..4f1edb489d3 100644 --- a/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java +++ b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java @@ -21,6 +21,7 @@ import io.seata.saga.statelang.domain.StateMachine; import io.seata.saga.statelang.parser.utils.DesignerJsonTransformer; +import io.seata.saga.statelang.validator.ValidationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; @@ -74,4 +75,47 @@ public void testDesignerJsonTransformer() throws IOException { String fastjsonOutputJson = fastjsonParser.toJsonString(fastjsonParsedObj, true); System.out.println(fastjsonOutputJson); } + + @Test + public void singleInfiniteLoopTest() throws IOException { + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine_with_single_infinite_loop.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + Throwable e = Assertions.assertThrows(ValidationException.class, () -> { + StateMachineParserFactory.getStateMachineParser(null).parse(json); + }); + System.out.println(e.getMessage()); + Assertions.assertTrue(e.getMessage().endsWith("without outgoing flow to end")); + } + + @Test + public void testMultipleInfiniteLoop() throws IOException { + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine_with_multiple_infinite_loop.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + Throwable e = Assertions.assertThrows(ValidationException.class, () -> { + StateMachineParserFactory.getStateMachineParser(null).parse(json); + }); + System.out.println(e.getMessage()); + Assertions.assertTrue(e.getMessage().endsWith("without outgoing flow to end")); + } + + @Test + public void testNonExistedName() throws IOException { + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine_with_non_existed_name.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + Throwable e = Assertions.assertThrows(ValidationException.class, () -> { + StateMachineParserFactory.getStateMachineParser(null).parse(json); + }); + System.out.println(e.getMessage()); + Assertions.assertTrue(e.getMessage().endsWith("does not exist")); + } + + @Test + public void testRecursiveSubStateMachine() throws IOException { + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine_with_recursive_sub_machine.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + Throwable e = Assertions.assertThrows(ValidationException.class, () -> { + StateMachineParserFactory.getStateMachineParser(null).parse(json); + }); + Assertions.assertTrue(e.getMessage().endsWith("call itself")); + } } \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/utils/ResourceUtilTests.java b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/utils/ResourceUtilTests.java index 6db1ded8312..f1ec7b277c1 100644 --- a/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/utils/ResourceUtilTests.java +++ b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/utils/ResourceUtilTests.java @@ -30,9 +30,9 @@ public class ResourceUtilTests { @Test public void getResources_test() { Resource[] resources = ResourceUtil.getResources("classpath*:statelang/*.json"); - assertThat(resources.length).isEqualTo(2); + assertThat(resources.length).isEqualTo(6); Resource[] resources2 = ResourceUtil.getResources(new String[]{"classpath*:statelang/*.json"}); - assertThat(resources2.length).isEqualTo(2); + assertThat(resources2.length).isEqualTo(6); } } \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json index 417d3b5b8de..91ed501953f 100644 --- a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json @@ -112,7 +112,7 @@ }, "SecondMatchState": { "Type": "SubStateMachine", - "StateMachineName": "simpleTestStateMachine", + "StateMachineName": "simpleTestSubStateMachine", "Input": [ { "input": "$.data" diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_multiple_infinite_loop.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_multiple_infinite_loop.json new file mode 100644 index 00000000000..85d934a8439 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_multiple_infinite_loop.json @@ -0,0 +1,33 @@ +{ + "Name": "simpleTestStateMachineWithMultipleInfiniteLoop", + "Comment": "测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.2", + "States": { + "FirstState": { + "Type": "ServiceTask", + "Next": "ChoiceState" + }, + "ChoiceState": { + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ] + }, + "SecondState": { + "Type": "ServiceTask", + "Next": "FirstState" + }, + "ThirdState": { + "Type": "ServiceTask", + "Next": "FirstState" + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_non_existed_name.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_non_existed_name.json new file mode 100644 index 00000000000..03ad391b4bf --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_non_existed_name.json @@ -0,0 +1,29 @@ +{ + "Name": "simpleTestStateMachineWithNonExistedName", + "Comment": "测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.2", + "States": { + "FirstState": { + "Type": "ServiceTask", + "Next": "ChoiceState" + }, + "ChoiceState": { + "Type": "Choice", + "Choices":[ + { + "Expression":"[a] == 1", + "Next":"SecondState" + }, + { + "Expression":"[a] == 2", + "Next":"ThirdState" + } + ] + }, + "SecondState": { + "Type": "ServiceTask", + "Next": "FirstState" + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_recursive_sub_machine.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_recursive_sub_machine.json new file mode 100644 index 00000000000..56b566c17f9 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_recursive_sub_machine.json @@ -0,0 +1,18 @@ +{ + "Name": "simpleStateMachineWithRecursiveSubMachine", + "Comment": "递归调用子状态机", + "StartState": "CallSubStateMachine", + "Version": "0.0.1", + "IsRetryPersistModeUpdate": false, + "IsCompensatePersistModeUpdate": false, + "States": { + "CallSubStateMachine": { + "Type": "SubStateMachine", + "StateMachineName": "simpleStateMachineWithRecursiveSubMachine", + "Next": "Succeed" + }, + "Succeed": { + "Type":"Succeed" + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_single_infinite_loop.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_single_infinite_loop.json new file mode 100644 index 00000000000..d2ff7c82d14 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_single_infinite_loop.json @@ -0,0 +1,20 @@ +{ + "Name": "simpleTestStateMachineWithSimpleInfiniteLoop", + "Comment": "测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.2", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Next": "SecondState" + }, + "SecondState": { + "Type": "ServiceTask", + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Next": "FirstState" + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/package-lock.json b/saga/seata-saga-statemachine-designer/package-lock.json index e24f87b8680..ecb0275912b 100644 --- a/saga/seata-saga-statemachine-designer/package-lock.json +++ b/saga/seata-saga-statemachine-designer/package-lock.json @@ -413,13 +413,26 @@ } }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-hoist-variables": { @@ -506,21 +519,52 @@ } }, "@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/types": { @@ -608,15 +652,40 @@ } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.10.4.tgz", - "integrity": "sha1-5J7u2f4RS2L6WxgYVqQ6XjL18kM=", - "dev": true, + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "requires": { - "@babel/types": "^7.10.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } } }, "@babel/helper-annotate-as-pure": { @@ -743,8 +812,7 @@ "@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-explode-assignable-expression": { "version": "7.10.4", @@ -889,8 +957,7 @@ "@babel/helper-string-parser": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { "version": "7.10.4", @@ -938,25 +1005,51 @@ } }, "@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-hoist-variables": { @@ -1012,21 +1105,40 @@ } }, "@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/types": { @@ -1041,9 +1153,9 @@ } }, "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1887,56 +1999,108 @@ } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.10.4.tgz?cache=0&sync_timestamp=1593522841483&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftraverse%2Fdownload%2F%40babel%2Ftraverse-7.10.4.tgz", - "integrity": "sha1-5kLlOVo7CcyVyOdKJ0MrSEtpeBg=", - "dev": true, + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz?cache=0&sync_timestamp=1593522826253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.10.4.tgz", - "integrity": "sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=", - "dev": true, + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + } + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.10.4.tgz", - "integrity": "sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=", - "dev": true, + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, + "@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", - "dev": true, + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", - "dev": true + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -1961,7 +2125,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1971,26 +2134,22 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { "version": "0.3.15", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2311,7 +2470,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -2946,10 +3104,9 @@ "dev": true }, "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-5.1.2.tgz", - "integrity": "sha1-yWhpAtPJoncp9DqxD515wgBNp7A=", - "dev": true + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "body-parser": { "version": "1.20.1", @@ -3067,14 +3224,12 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npm.taobao.org/brorand/download/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npm.taobao.org/browserify-aes/download/browserify-aes-1.2.0.tgz", "integrity": "sha1-Mmc0ZC9APavDADIJhTu3CtQo70g=", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -3126,27 +3281,64 @@ } }, "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npm.taobao.org/browserify-sign/download/browserify-sign-4.2.0.tgz", - "integrity": "sha1-VF0LGwfmssmSEQgr8bEsznoLDhE=", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.4", "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" }, "dependencies": { + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz", - "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=", - "dev": true, + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3155,9 +3347,8 @@ }, "safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz", - "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", - "dev": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -3228,8 +3419,7 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npm.taobao.org/buffer-xor/download/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", @@ -3348,7 +3538,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3400,7 +3589,6 @@ "version": "1.0.4", "resolved": "https://registry.npm.taobao.org/cipher-base/download/cipher-base-1.0.4.tgz", "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3513,7 +3701,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -3521,8 +3708,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { "version": "1.0.8", @@ -3811,7 +3997,6 @@ "version": "1.2.0", "resolved": "https://registry.npm.taobao.org/create-hash/download/create-hash-1.2.0.tgz", "integrity": "sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY=", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -3824,7 +4009,6 @@ "version": "1.1.7", "resolved": "https://registry.npm.taobao.org/create-hmac/download/create-hmac-1.1.7.tgz", "integrity": "sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8=", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -4602,7 +4786,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -4616,8 +4799,7 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -4733,8 +4915,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "5.16.0", @@ -4781,12 +4962,12 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -5160,7 +5341,6 @@ "version": "1.0.3", "resolved": "https://registry.npm.taobao.org/evp_bytestokey/download/evp_bytestokey-1.0.3.tgz", "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -5893,8 +6073,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { "version": "6.1.0", @@ -5967,8 +6146,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.1", @@ -6012,7 +6190,6 @@ "version": "3.1.0", "resolved": "https://registry.npm.taobao.org/hash-base/download/hash-base-3.1.0.tgz", "integrity": "sha1-VcOB2eBuHSmXqIO0o/3f5/DTrzM=", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -6023,7 +6200,6 @@ "version": "3.6.0", "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz", "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6033,8 +6209,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz", - "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", - "dev": true + "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=" } } }, @@ -6042,7 +6217,6 @@ "version": "1.1.7", "resolved": "https://registry.npm.taobao.org/hash.js/download/hash.js-1.1.7.tgz", "integrity": "sha1-C6vKU46NTuSg+JiNaIZlN6ADz0I=", - "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -6052,7 +6226,6 @@ "version": "1.0.1", "resolved": "https://registry.npm.taobao.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -6291,8 +6464,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -6723,8 +6895,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npm.taobao.org/jsesc/download/jsesc-2.5.2.tgz", - "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", - "dev": true + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=" }, "json-parse-better-errors": { "version": "1.0.2", @@ -7055,7 +7226,6 @@ "version": "1.3.5", "resolved": "https://registry.npm.taobao.org/md5.js/download/md5.js-1.3.5.tgz", "integrity": "sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8=", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -7159,14 +7329,12 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npm.taobao.org/minimalistic-assert/download/minimalistic-assert-1.0.1.tgz", - "integrity": "sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=", - "dev": true + "integrity": "sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npm.taobao.org/minimalistic-crypto-utils/download/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.1.2", @@ -7916,7 +8084,6 @@ "version": "3.1.1", "resolved": "https://registry.npm.taobao.org/pbkdf2/download/pbkdf2-3.1.1.tgz", "integrity": "sha1-y4cksPramEWWhW0abrr9NYRlS5Q=", - "dev": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -8743,7 +8910,6 @@ "version": "2.1.0", "resolved": "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz", "integrity": "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -9196,7 +9362,6 @@ "version": "2.0.2", "resolved": "https://registry.npm.taobao.org/ripemd160/download/ripemd160-2.0.2.tgz", "integrity": "sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw=", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -9237,8 +9402,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", - "dev": true + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" }, "safe-regex": { "version": "1.1.0", @@ -9434,7 +9598,6 @@ "version": "2.4.11", "resolved": "https://registry.npm.taobao.org/sha.js/download/sha.js-2.4.11.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsha.js%2Fdownload%2Fsha.js-2.4.11.tgz", "integrity": "sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc=", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -9657,9 +9820,9 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", - "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -9675,9 +9838,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } @@ -9777,18 +9940,18 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -9808,18 +9971,18 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "readable-stream": { @@ -10119,7 +10282,6 @@ "version": "1.1.1", "resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz", "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -10165,7 +10327,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -10184,8 +10345,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "resolved": "", "dev": true }, "string-width": { @@ -10315,8 +10475,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -10657,8 +10816,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -11027,18 +11185,18 @@ "dev": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "semver": { @@ -11246,8 +11404,7 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "resolved": "", "dev": true }, "string-width": { diff --git a/saga/seata-saga-statemachine-designer/package.json b/saga/seata-saga-statemachine-designer/package.json index 635fdff8edb..0f2fbfe95e6 100644 --- a/saga/seata-saga-statemachine-designer/package.json +++ b/saga/seata-saga-statemachine-designer/package.json @@ -45,6 +45,8 @@ }, "dependencies": { "@antv/g6": "^2.2.6", + "@babel/traverse": "^7.23.2", + "browserify-sign": "^4.2.2", "codemirror": "^5.65.8", "core-js": "^3.6.5", "decode-uri-component": "^0.2.2", diff --git a/script/client/conf/registry.conf b/script/client/conf/registry.conf index 976f135f01f..e13588b9df3 100644 --- a/script/client/conf/registry.conf +++ b/script/client/conf/registry.conf @@ -72,7 +72,10 @@ registry { config { # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom type = "file" - + raft { + metadata-max-age-ms = 30000 + serverAddr = "127.0.0.1:8848" + } nacos { serverAddr = "127.0.0.1:8848" namespace = "" diff --git a/script/client/spring/application.properties b/script/client/spring/application.properties index 95e828d45f7..51f4ac0f7ab 100755 --- a/script/client/spring/application.properties +++ b/script/client/spring/application.properties @@ -116,6 +116,8 @@ seata.config.custom.name= seata.registry.type=file +seata.registry.raft.server-addr= +seata.registry.raft.metadata-max-age-ms=30000 seata.registry.consul.server-addr=127.0.0.1:8500 seata.registry.etcd3.server-addr=http://localhost:2379 diff --git a/script/client/spring/application.yml b/script/client/spring/application.yml index bedc7852ed3..88de0df8b8a 100755 --- a/script/client/spring/application.yml +++ b/script/client/spring/application.yml @@ -109,6 +109,9 @@ seata: name: registry: type: file + raft: + server-addr: + metadata-max-age-ms: 30000 file: name: file.conf consul: diff --git a/script/config-center/config.txt b/script/config-center/config.txt index ee0caa02dcc..bb13d9a442c 100644 --- a/script/config-center/config.txt +++ b/script/config-center/config.txt @@ -29,6 +29,7 @@ service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false +client.metadataMaxAgeMs=30000 #Transaction rule configuration, only for the client client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 @@ -42,6 +43,7 @@ client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.rm.sagaJsonParser=fastjson client.rm.tccActionInterceptorOrder=-2147482648 +client.rm.sqlParserType=druid client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 @@ -122,12 +124,26 @@ server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false server.distributedLockExpireTime=10000 -server.xaerNotaRetryTimeout=60000 server.session.branchAsyncQueueSize=5000 server.session.enableBranchAsyncRemove=false server.enableParallelRequestHandle=true server.enableParallelHandleBranch=false +server.raft.cluster=127.0.0.1:7091,127.0.0.1:7092,127.0.0.1:7093 +server.raft.snapshotInterval=600 +server.raft.applyBatch=32 +server.raft.maxAppendBufferSize=262144 +server.raft.maxReplicatorInflightMsgs=256 +server.raft.disruptorBufferSize=16384 +server.raft.electionTimeoutMs=2000 +server.raft.reporterEnabled=false +server.raft.reporterInitialDelay=60 +server.raft.serialization=jackson +server.raft.compressor=none +server.raft.sync=true + + + #Metrics configuration, only for the server metrics.enabled=false metrics.registryType=compact diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java index d81883b89b9..cb9faed95d8 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java @@ -34,6 +34,7 @@ import io.seata.spring.boot.autoconfigure.properties.registry.RegistryEurekaProperties; import io.seata.spring.boot.autoconfigure.properties.registry.RegistryNacosProperties; import io.seata.spring.boot.autoconfigure.properties.registry.RegistryProperties; +import io.seata.spring.boot.autoconfigure.properties.registry.RegistryRaftProperties; import io.seata.spring.boot.autoconfigure.properties.registry.RegistryRedisProperties; import io.seata.spring.boot.autoconfigure.properties.registry.RegistrySofaProperties; import io.seata.spring.boot.autoconfigure.properties.registry.RegistryZooKeeperProperties; @@ -58,6 +59,7 @@ import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_EUREKA_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_NACOS_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFIX; +import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_RAFT_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_REDIS_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_SOFA_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_ZK_PREFIX; @@ -105,6 +107,7 @@ public static void init() { PROPERTY_BEAN_MAP.put(REGISTRY_SOFA_PREFIX, RegistrySofaProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_ZK_PREFIX, RegistryZooKeeperProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_CUSTOM_PREFIX, RegistryCustomProperties.class); + PROPERTY_BEAN_MAP.put(REGISTRY_RAFT_PREFIX, RegistryRaftProperties.class); PROPERTY_BEAN_MAP.put(THREAD_FACTORY_PREFIX, ThreadFactoryProperties.class); PROPERTY_BEAN_MAP.put(TRANSPORT_PREFIX, TransportProperties.class); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/StarterConstants.java index b04d7582081..759fe66e206 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/StarterConstants.java @@ -49,6 +49,7 @@ public interface StarterConstants { String REGISTRY_PREFIX = SEATA_PREFIX + ".registry"; String REGISTRY_PREFERED_NETWORKS = ConfigurationKeys.FILE_ROOT_REGISTRY + ".preferredNetworks"; String REGISTRY_NACOS_PREFIX = REGISTRY_PREFIX + ".nacos"; + String REGISTRY_RAFT_PREFIX = REGISTRY_PREFIX + ".raft"; String REGISTRY_EUREKA_PREFIX = REGISTRY_PREFIX + ".eureka"; String REGISTRY_REDIS_PREFIX = REGISTRY_PREFIX + ".redis"; String REGISTRY_ZK_PREFIX = REGISTRY_PREFIX + ".zk"; @@ -69,6 +70,7 @@ public interface StarterConstants { String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; + String SERVER_RAFT_PREFIX = SERVER_PREFIX + ".raft"; String SERVER_RECOVERY_PREFIX = SERVER_PREFIX + ".recovery"; String METRICS_PREFIX = SEATA_PREFIX + ".metrics"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/properties/registry/RegistryRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/properties/registry/RegistryRaftProperties.java new file mode 100644 index 00000000000..fa107b73831 --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/io/seata/spring/boot/autoconfigure/properties/registry/RegistryRaftProperties.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.spring.boot.autoconfigure.properties.registry; + +import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_RAFT_PREFIX; + + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author xingfudeshi@gmail.com + */ +@Component +@ConfigurationProperties(prefix = REGISTRY_RAFT_PREFIX) +public class RegistryRaftProperties { + private String serverAddr; + private Long metadataMaxAgeMs = 30000L; + + public Long getMetadataMaxAgeMs() { + return metadataMaxAgeMs; + } + + public void setMetadataMaxAgeMs(Long metadataMaxAgeMs) { + this.metadataMaxAgeMs = metadataMaxAgeMs; + } + + public String getServerAddr() { + return serverAddr; + } + + public RegistryRaftProperties setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + return this; + } + +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3c17919d667..a23e23bccd7 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -104,29 +104,17 @@ { "name": "seata.transport.rpc-rm-request-timeout", "type": "java.lang.Long", - "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties", - "deprecation": { - "level": "error", - "reason": "Please configure to 'seata.transport.rpcRmRequestTimeout'." - } + "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties" }, { "name": "seata.transport.rpc-tm-request-timeout", "type": "java.lang.Long", - "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties", - "deprecation": { - "level": "error", - "reason": "Please configure to 'seata.transport.rpcTmRequestTimeout'." - } + "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties" }, { "name": "seata.transport.rpc-tc-request-timeout", "type": "java.lang.Long", - "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties", - "deprecation": { - "level": "error", - "reason": "Please configure to 'seata.transport.rpcTcRequestTimeout'." - } + "sourceType": "io.seata.spring.boot.autoconfigure.properties.TransportProperties" } ], "hints": [ diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java index 57f5d99a36c..66d5a0801d7 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java @@ -16,14 +16,15 @@ package io.seata.spring.boot.autoconfigure; import java.util.concurrent.atomic.AtomicBoolean; +import io.seata.spring.boot.autoconfigure.properties.server.store.StoreProperties; import io.seata.spring.boot.autoconfigure.properties.server.MetricsProperties; import io.seata.spring.boot.autoconfigure.properties.server.ServerProperties; +import io.seata.spring.boot.autoconfigure.properties.server.ServerRaftProperties; import io.seata.spring.boot.autoconfigure.properties.server.ServerRecoveryProperties; import io.seata.spring.boot.autoconfigure.properties.server.ServerUndoProperties; import io.seata.spring.boot.autoconfigure.properties.server.session.SessionProperties; import io.seata.spring.boot.autoconfigure.properties.server.store.StoreDBProperties; import io.seata.spring.boot.autoconfigure.properties.server.store.StoreFileProperties; -import io.seata.spring.boot.autoconfigure.properties.server.store.StoreProperties; import io.seata.spring.boot.autoconfigure.properties.server.store.StoreRedisProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; @@ -33,6 +34,7 @@ import static io.seata.spring.boot.autoconfigure.StarterConstants.METRICS_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.PROPERTY_BEAN_MAP; import static io.seata.spring.boot.autoconfigure.StarterConstants.SERVER_PREFIX; +import static io.seata.spring.boot.autoconfigure.StarterConstants.SERVER_RAFT_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.SERVER_RECOVERY_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.SERVER_UNDO_PREFIX; import static io.seata.spring.boot.autoconfigure.StarterConstants.SESSION_PREFIX; @@ -69,7 +71,6 @@ public static void init() { PROPERTY_BEAN_MAP.put(SERVER_UNDO_PREFIX, ServerUndoProperties.class); PROPERTY_BEAN_MAP.put(SERVER_RECOVERY_PREFIX, ServerRecoveryProperties.class); PROPERTY_BEAN_MAP.put(METRICS_PREFIX, MetricsProperties.class); - PROPERTY_BEAN_MAP.put(STORE_PREFIX, StoreProperties.class); PROPERTY_BEAN_MAP.put(STORE_SESSION_PREFIX, StoreProperties.Session.class); PROPERTY_BEAN_MAP.put(STORE_LOCK_PREFIX, StoreProperties.Lock.class); PROPERTY_BEAN_MAP.put(STORE_FILE_PREFIX, StoreFileProperties.class); @@ -77,7 +78,9 @@ public static void init() { PROPERTY_BEAN_MAP.put(STORE_REDIS_PREFIX, StoreRedisProperties.class); PROPERTY_BEAN_MAP.put(STORE_REDIS_SINGLE_PREFIX, StoreRedisProperties.Single.class); PROPERTY_BEAN_MAP.put(STORE_REDIS_SENTINEL_PREFIX, StoreRedisProperties.Sentinel.class); + PROPERTY_BEAN_MAP.put(SERVER_RAFT_PREFIX, ServerRaftProperties.class); PROPERTY_BEAN_MAP.put(SESSION_PREFIX, SessionProperties.class); + PROPERTY_BEAN_MAP.put(STORE_PREFIX, StoreProperties.class); } } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/properties/server/ServerRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/properties/server/ServerRaftProperties.java new file mode 100644 index 00000000000..e1d01da1b4c --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/io/seata/spring/boot/autoconfigure/properties/server/ServerRaftProperties.java @@ -0,0 +1,181 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.spring.boot.autoconfigure.properties.server; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + + +import static io.seata.spring.boot.autoconfigure.StarterConstants.SERVER_RAFT_PREFIX; + +/** + * @author funkye + */ +@Component +@ConfigurationProperties(prefix = SERVER_RAFT_PREFIX) +public class ServerRaftProperties { + + private String serverAddr; + + private String group; + + private Boolean autoJoin = false; + + private Integer snapshotInterval = 600; + + private Integer applyBatch = 32; + + private Integer maxAppendBufferSize = 256 * 1024; + + private Integer maxReplicatorInflightMsgs = 256; + + private Integer disruptorBufferSize = 16384; + + private Integer electionTimeoutMs = 1000; + + private boolean reporterEnabled = false; + + private Integer reporterInitialDelay = 60; + + private String serialization = "jackson"; + + private String compressor = "none"; + + private boolean sync = true; + + public String getServerAddr() { + return serverAddr; + } + + public ServerRaftProperties setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + return this; + } + + public Integer getSnapshotInterval() { + return snapshotInterval; + } + + public ServerRaftProperties setSnapshotInterval(Integer snapshotInterval) { + this.snapshotInterval = snapshotInterval; + return this; + } + + public Integer getApplyBatch() { + return applyBatch; + } + + public ServerRaftProperties setApplyBatch(Integer applyBatch) { + this.applyBatch = applyBatch; + return this; + } + + public Integer getMaxAppendBufferSize() { + return maxAppendBufferSize; + } + + public ServerRaftProperties setMaxAppendBufferSize(Integer maxAppendBufferSize) { + this.maxAppendBufferSize = maxAppendBufferSize; + return this; + } + + public Integer getMaxReplicatorInflightMsgs() { + return maxReplicatorInflightMsgs; + } + + public ServerRaftProperties setMaxReplicatorInflightMsgs(Integer maxReplicatorInflightMsgs) { + this.maxReplicatorInflightMsgs = maxReplicatorInflightMsgs; + return this; + } + + public Integer getDisruptorBufferSize() { + return disruptorBufferSize; + } + + public ServerRaftProperties setDisruptorBufferSize(Integer disruptorBufferSize) { + this.disruptorBufferSize = disruptorBufferSize; + return this; + } + + public Integer getElectionTimeoutMs() { + return electionTimeoutMs; + } + + public ServerRaftProperties setElectionTimeoutMs(Integer electionTimeoutMs) { + this.electionTimeoutMs = electionTimeoutMs; + return this; + } + + public boolean isReporterEnabled() { + return reporterEnabled; + } + + public ServerRaftProperties setReporterEnabled(boolean reporterEnabled) { + this.reporterEnabled = reporterEnabled; + return this; + } + + public Integer getReporterInitialDelay() { + return reporterInitialDelay; + } + + public ServerRaftProperties setReporterInitialDelay(Integer reporterInitialDelay) { + this.reporterInitialDelay = reporterInitialDelay; + return this; + } + + public Boolean getAutoJoin() { + return autoJoin; + } + + public ServerRaftProperties setAutoJoin(Boolean autoJoin) { + this.autoJoin = autoJoin; + return this; + } + + public String getSerialization() { + return serialization; + } + + public void setSerialization(String serialization) { + this.serialization = serialization; + } + + public String getCompressor() { + return compressor; + } + + public void setCompressor(String compressor) { + this.compressor = compressor; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public boolean isSync() { + return sync; + } + + public void setSync(boolean sync) { + this.sync = sync; + } + +} diff --git a/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoInnerSerializer.java b/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoInnerSerializer.java index 6d1eb2781c9..032cbf2f4d8 100644 --- a/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoInnerSerializer.java +++ b/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoInnerSerializer.java @@ -15,18 +15,18 @@ */ package io.seata.serializer.kryo; -import com.esotericsoftware.kryo.Kryo; -import com.esotericsoftware.kryo.io.Input; -import com.esotericsoftware.kryo.io.Output; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Objects; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + /** * @author jsbxyyx */ -public class KryoInnerSerializer { +public class KryoInnerSerializer implements AutoCloseable { private final Kryo kryo; @@ -53,4 +53,9 @@ public T deserialize(byte[] bytes) { return (T) kryo.readClassAndObject(input); } + @Override + public void close() { + KryoSerializerFactory.getInstance().returnKryo(this); + } + } diff --git a/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoSerializer.java b/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoSerializer.java index 1a2be6b727d..6cc58ec68a9 100644 --- a/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoSerializer.java +++ b/serializer/seata-serializer-kryo/src/main/java/io/seata/serializer/kryo/KryoSerializer.java @@ -16,8 +16,8 @@ package io.seata.serializer.kryo; import io.seata.common.loader.LoadLevel; -import io.seata.core.serializer.Serializer; import io.seata.core.protocol.AbstractMessage; +import io.seata.core.serializer.Serializer; /** * @author jsbxyyx @@ -36,6 +36,7 @@ public byte[] serialize(T t) { } finally { KryoSerializerFactory.getInstance().returnKryo(kryoSerializer); } + } @Override diff --git a/server/pom.xml b/server/pom.xml index 271ad037803..0e44ece71c4 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -232,6 +232,10 @@ com.github.danielwegener logback-kafka-appender + + com.alipay.sofa + jraft-core + org.codehaus.janino janino diff --git a/server/src/main/java/io/seata/server/AbstractTCInboundHandler.java b/server/src/main/java/io/seata/server/AbstractTCInboundHandler.java index 18a2699060b..856a5b125ba 100644 --- a/server/src/main/java/io/seata/server/AbstractTCInboundHandler.java +++ b/server/src/main/java/io/seata/server/AbstractTCInboundHandler.java @@ -342,4 +342,5 @@ private void checkTransactionStatus(AbstractGlobalEndRequest request, AbstractGl LOGGER.error("check transaction status error,{}]", exx.getMessage()); } } + } diff --git a/server/src/main/java/io/seata/server/Server.java b/server/src/main/java/io/seata/server/Server.java index 30c73c36c71..650c57a6442 100644 --- a/server/src/main/java/io/seata/server/Server.java +++ b/server/src/main/java/io/seata/server/Server.java @@ -18,8 +18,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; - import io.seata.common.XID; +import io.seata.common.holder.ObjectHolder; import io.seata.common.thread.NamedThreadFactory; import io.seata.common.util.NetUtil; import io.seata.common.util.StringUtils; @@ -30,7 +30,12 @@ import io.seata.server.lock.LockerManagerFactory; import io.seata.server.metrics.MetricsManager; import io.seata.server.session.SessionHolder; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.web.context.support.GenericWebApplicationContext; + +import static io.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGEX_SPLIT_CHAR; import static io.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFERED_NETWORKS; @@ -70,14 +75,22 @@ public static void start(String[] args) { XID.setIpAddress(NetUtil.getLocalIp()); } } - NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads); XID.setPort(nettyRemotingServer.getListenPort()); UUIDGenerator.init(parameterParser.getServerNode()); + ConfigurableListableBeanFactory beanFactory = + ((GenericWebApplicationContext)ObjectHolder.INSTANCE + .getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)).getBeanFactory(); + DefaultCoordinator coordinator = DefaultCoordinator.getInstance(nettyRemotingServer); + if (coordinator instanceof ApplicationListener) { + beanFactory.registerSingleton(NettyRemotingServer.class.getName(), nettyRemotingServer); + beanFactory.registerSingleton(DefaultCoordinator.class.getName(), coordinator); + ((GenericWebApplicationContext)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .addApplicationListener((ApplicationListener)coordinator); + } //log store mode : file, db, redis SessionHolder.init(); LockerManagerFactory.init(); - DefaultCoordinator coordinator = DefaultCoordinator.getInstance(nettyRemotingServer); coordinator.init(); nettyRemotingServer.setHandler(coordinator); diff --git a/server/src/main/java/io/seata/server/cluster/listener/ClusterChangeEvent.java b/server/src/main/java/io/seata/server/cluster/listener/ClusterChangeEvent.java new file mode 100644 index 00000000000..ab49f465e4a --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/listener/ClusterChangeEvent.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.listener; + +import java.time.Clock; +import org.springframework.context.ApplicationEvent; + +/** + * @author funkye + */ +public class ClusterChangeEvent extends ApplicationEvent { + + private String group; + + private boolean leader; + + private long term; + + public ClusterChangeEvent(Object source, String group, long term, boolean leader) { + super(source); + this.group = group; + this.term = term; + this.leader = leader; + } + + public ClusterChangeEvent(Object source, String group) { + super(source); + this.group = group; + } + + public ClusterChangeEvent(Object source, Clock clock) { + super(source, clock); + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public long getTerm() { + return term; + } + + public void setTerm(long term) { + this.term = term; + } + + public boolean isLeader() { + return leader; + } + + public void setLeader(boolean leader) { + this.leader = leader; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java b/server/src/main/java/io/seata/server/cluster/listener/ClusterChangeListener.java similarity index 73% rename from saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java rename to server/src/main/java/io/seata/server/cluster/listener/ClusterChangeListener.java index 8fc530a9bbe..bc6a432ebee 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java +++ b/server/src/main/java/io/seata/server/cluster/listener/ClusterChangeListener.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.saga.engine.evaluation; - -import java.util.Map; +package io.seata.server.cluster.listener; /** - * Evaluator - * - * @author lorne.cl + * @author funkye */ -public interface Evaluator { +public interface ClusterChangeListener { - boolean evaluate(Map variables); -} \ No newline at end of file + /** + * cluster change event + * @param event event + */ + void onChangeEvent(ClusterChangeEvent event); +} diff --git a/server/src/main/java/io/seata/server/cluster/manager/ClusterWatcherManager.java b/server/src/main/java/io/seata/server/cluster/manager/ClusterWatcherManager.java new file mode 100644 index 00000000000..86d7fe3be8a --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/manager/ClusterWatcherManager.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.manager; + +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletResponse; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.server.cluster.listener.ClusterChangeEvent; +import io.seata.server.cluster.listener.ClusterChangeListener; +import io.seata.server.cluster.watch.Watcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * @author funkye + */ +@Component +public class ClusterWatcherManager implements ClusterChangeListener { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final Map>> WATCHERS = new ConcurrentHashMap<>(); + + private static final Map GROUP_UPDATE_TIME = new ConcurrentHashMap<>(); + + private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("long-polling", 1)); + + @PostConstruct + public void init() { + // Responds to monitors that time out + scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { + for (String group : WATCHERS.keySet()) { + Optional.ofNullable(WATCHERS.remove(group)) + .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { + if (System.currentTimeMillis() >= watcher.getTimeout()) { + HttpServletResponse httpServletResponse = + (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); + watcher.setDone(true); + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + ((AsyncContext)watcher.getAsyncContext()).complete(); + } + if (!watcher.isDone()) { + // Re-register + registryWatcher(watcher); + } + })); + } + }, 1, 1, TimeUnit.SECONDS); + } + + @Override + @EventListener + @Async + public void onChangeEvent(ClusterChangeEvent event) { + if (event.getTerm() > 0) { + GROUP_UPDATE_TIME.put(event.getGroup(), event.getTerm()); + // Notifications are made of changes in cluster information + Optional.ofNullable(WATCHERS.remove(event.getGroup())) + .ifPresent(watchers -> watchers.parallelStream().forEach(this::notify)); + } + } + + private void notify(Watcher watcher) { + AsyncContext asyncContext = (AsyncContext)watcher.getAsyncContext(); + HttpServletResponse httpServletResponse = (HttpServletResponse)asyncContext.getResponse(); + watcher.setDone(true); + if (logger.isDebugEnabled()) { + logger.debug("notify cluster change event to: {}", asyncContext.getRequest().getRemoteAddr()); + } + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + asyncContext.complete(); + } + + public void registryWatcher(Watcher watcher) { + String group = watcher.getGroup(); + Long term = GROUP_UPDATE_TIME.get(group); + if (term == null || watcher.getTerm() >= term) { + WATCHERS.computeIfAbsent(group, value -> new ConcurrentLinkedQueue<>()).add(watcher); + } else { + notify(watcher); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/RaftServer.java b/server/src/main/java/io/seata/server/cluster/raft/RaftServer.java new file mode 100644 index 00000000000..20d1cf41a03 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/RaftServer.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RaftGroupService; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.codahale.metrics.Slf4jReporter; +import io.seata.config.ConfigurationFactory; +import io.seata.core.rpc.Disposable; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_ENABLED; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_INITIAL_DELAY; + +/** + * @author funkye + */ +public class RaftServer implements Disposable, Closeable { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final RaftStateMachine raftStateMachine; + private final String groupId; + private final String groupPath; + private final NodeOptions nodeOptions; + private final PeerId serverId; + private final RpcServer rpcServer; + private RaftGroupService raftGroupService; + private Node node; + + public RaftServer(final String dataPath, final String groupId, final PeerId serverId, final NodeOptions nodeOptions, final RpcServer rpcServer) + throws IOException { + this.groupId = groupId; + this.groupPath = dataPath + File.separator + groupId; + // Initialize the state machine + this.raftStateMachine = new RaftStateMachine(groupId); + this.nodeOptions = nodeOptions; + this.serverId = serverId; + this.rpcServer = rpcServer; + } + + public void start() throws IOException { + // Initialization path + FileUtils.forceMkdir(new File(groupPath)); + // Set the state machine to startup parameters + nodeOptions.setFsm(this.raftStateMachine); + // Set the storage path + // Log, must + nodeOptions.setLogUri(groupPath + File.separator + "log"); + // Meta information, must + nodeOptions.setRaftMetaUri(groupPath + File.separator + "raft_meta"); + // Snapshot, optional, is generally recommended + nodeOptions.setSnapshotUri(groupPath + File.separator + "snapshot"); + boolean reporterEnabled = ConfigurationFactory.getInstance().getBoolean(SERVER_RAFT_REPORTER_ENABLED, false); + nodeOptions.setEnableMetrics(reporterEnabled); + // Initialize the raft Group service framework + this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); + this.node = this.raftGroupService.start(false); + RouteTable.getInstance().updateConfiguration(groupId, node.getOptions().getInitialConf()); + if (reporterEnabled) { + final Slf4jReporter reporter = Slf4jReporter.forRegistry(node.getNodeMetrics().getMetricRegistry()) + .outputTo(logger).convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS).build(); + reporter.start(ConfigurationFactory.getInstance().getInt(SERVER_RAFT_REPORTER_INITIAL_DELAY, 60), + TimeUnit.MINUTES); + } + } + + public Node getNode() { + return this.node; + } + + + public RaftStateMachine getRaftStateMachine() { + return raftStateMachine; + } + + @Override + public void close() { + destroy(); + } + + @Override + public void destroy() { + if (raftGroupService != null) { + raftGroupService.shutdown(); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/RaftServerFactory.java b/server/src/main/java/io/seata/server/cluster/raft/RaftServerFactory.java new file mode 100644 index 00000000000..076b67f573d --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/RaftServerFactory.java @@ -0,0 +1,221 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.RaftServiceFactory; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.CliOptions; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.rpc.CliClientService; +import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import io.seata.common.ConfigurationKeys; +import io.seata.common.XID; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.FileRegistryServiceImpl; +import io.seata.discovery.registry.MultiRegistryFactory; +import io.seata.discovery.registry.RegistryService; +import io.seata.server.store.StoreConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_PORT_CAMEL; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_SYNC; +import static io.seata.common.DefaultValues.DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS; +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_APPLY_BATCH; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_DISRUPTOR_BUFFER_SIZE; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_ELECTION_TIMEOUT_MS; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_APPEND_BUFFER_SIZE; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS; +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_SNAPSHOT_INTERVAL; +import static java.io.File.separator; + +/** + * @author funkye + */ +public class RaftServerFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftServerFactory.class); + + private static final Map RAFT_SERVER_MAP = new HashMap<>(); + + private Boolean raftMode = false; + + private RpcServer rpcServer; + + private static final io.seata.config.Configuration CONFIG = ConfigurationFactory.getInstance(); + + public static RaftServerFactory getInstance() { + return SingletonHandler.INSTANCE; + } + + public static CliService getCliServiceInstance() { + return SingletonHandler.CLI_SERVICE; + } + + public static CliClientService getCliClientServiceInstance() { + return SingletonHandler.CLI_CLIENT_SERVICE; + } + + public void init() { + String initConfStr = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + StoreConfig.SessionMode storeMode = StoreConfig.getSessionMode(); + if (storeMode.equals(StoreConfig.SessionMode.RAFT)) { + for (RegistryService instance : MultiRegistryFactory.getInstances()) { + if (!(instance instanceof FileRegistryServiceImpl)) { + throw new IllegalArgumentException("Raft store mode not support other Registration Center"); + } + } + raftMode = true; + } + if (StringUtils.isBlank(initConfStr)) { + if (raftMode) { + throw new IllegalArgumentException( + "Raft store mode must config: " + ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + } + return; + } else { + LOGGER.warn("raft mode and raft cluster is an experimental feature"); + } + final Configuration initConf = new Configuration(); + if (!initConf.parse(initConfStr)) { + throw new IllegalArgumentException("fail to parse initConf:" + initConfStr); + } + int port = Integer.parseInt(System.getProperty(SERVER_RAFT_PORT_CAMEL, "0")); + PeerId serverId = null; + String host = XID.getIpAddress(); + if (port <= 0) { + // Highly available deployments require different nodes + for (PeerId peer : initConf.getPeers()) { + if (StringUtils.equals(peer.getIp(), host)) { + if (serverId != null) { + throw new IllegalArgumentException( + "server.raft.cluster has duplicate ip, For local debugging, use -Dserver.raftPort to specify the raft port"); + } + serverId = peer; + } + } + } else { + // Local debugging use + serverId = new PeerId(host, port); + } + final String dataPath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR) + + separator + "raft" + separator + serverId.getPort(); + String group = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + try { + // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this separately + this.rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); + RaftServer raftServer = new RaftServer(dataPath, group, serverId, initNodeOptions(initConf), this.rpcServer); + // as the foundation for multi raft group in the future + RAFT_SERVER_MAP.put(group, raftServer); + } catch (IOException e) { + throw new IllegalArgumentException("fail init raft cluster:" + e.getMessage(), e); + } + } + + public void start() { + RAFT_SERVER_MAP.forEach((group, raftServer) -> { + try { + raftServer.start(); + } catch (IOException e) { + LOGGER.error("start seata server raft cluster error, group: {} ", group, e); + throw new RuntimeException(e); + } + LOGGER.info("started seata server raft cluster, group: {} ", group); + }); + if (!this.rpcServer.init(null)) { + throw new RuntimeException("start raft node fail!"); + } + } + + public RaftServer getRaftServer(String group) { + return RAFT_SERVER_MAP.get(group); + } + + public Collection getRaftServers() { + return RAFT_SERVER_MAP.values(); + } + + public Boolean isLeader(String group) { + AtomicReference stateMachine = new AtomicReference<>(); + Optional.ofNullable(RAFT_SERVER_MAP.get(group)).ifPresent(raftServer -> { + stateMachine.set(raftServer.getRaftStateMachine()); + }); + RaftStateMachine raftStateMachine = stateMachine.get(); + return !isRaftMode() && RAFT_SERVER_MAP.isEmpty() || (raftStateMachine != null && raftStateMachine.isLeader()); + } + + public Boolean isRaftMode() { + return raftMode; + } + + private RaftOptions initRaftOptions() { + RaftOptions raftOptions = new RaftOptions(); + raftOptions.setApplyBatch(CONFIG.getInt(SERVER_RAFT_APPLY_BATCH, raftOptions.getApplyBatch())); + raftOptions.setMaxAppendBufferSize( + CONFIG.getInt(SERVER_RAFT_MAX_APPEND_BUFFER_SIZE, raftOptions.getMaxAppendBufferSize())); + raftOptions.setDisruptorBufferSize( + CONFIG.getInt(SERVER_RAFT_DISRUPTOR_BUFFER_SIZE, raftOptions.getDisruptorBufferSize())); + raftOptions.setMaxReplicatorInflightMsgs( + CONFIG.getInt(SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS, raftOptions.getMaxReplicatorInflightMsgs())); + raftOptions.setSync(CONFIG.getBoolean(SERVER_RAFT_SYNC, raftOptions.isSync())); + return raftOptions; + } + + private NodeOptions initNodeOptions(Configuration initConf) { + NodeOptions nodeOptions = new NodeOptions(); + // enable the CLI service. + nodeOptions.setDisableCli(false); + // snapshot should be made every 600 seconds + int snapshotInterval = CONFIG.getInt(SERVER_RAFT_SNAPSHOT_INTERVAL, 60 * 10); + nodeOptions.setSnapshotIntervalSecs(snapshotInterval); + nodeOptions.setRaftOptions(initRaftOptions()); + // set the election timeout to 1 second + nodeOptions + .setElectionTimeoutMs(CONFIG.getInt(SERVER_RAFT_ELECTION_TIMEOUT_MS, DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS)); + // set up the initial cluster configuration + nodeOptions.setInitialConf(initConf); + return nodeOptions; + } + + public static Set groups() { + return RAFT_SERVER_MAP.keySet(); + } + + private static class SingletonHandler { + private static final RaftServerFactory INSTANCE = new RaftServerFactory(); + private static final CliService CLI_SERVICE = RaftServiceFactory.createAndInitCliService(new CliOptions()); + private static final CliClientService CLI_CLIENT_SERVICE = new CliClientServiceImpl(); + static { + CLI_CLIENT_SERVICE.init(new CliOptions()); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/RaftStateMachine.java b/server/src/main/java/io/seata/server/cluster/raft/RaftStateMachine.java new file mode 100644 index 00000000000..d99c75b2646 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/RaftStateMachine.java @@ -0,0 +1,328 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import io.seata.common.XID; +import io.seata.common.holder.ObjectHolder; +import io.seata.common.metadata.ClusterRole; +import io.seata.common.metadata.Node; +import io.seata.common.store.StoreMode; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.common.ConfigurationKeys; +import io.seata.server.cluster.raft.context.SeataClusterContext; +import io.seata.server.cluster.raft.snapshot.metadata.LeaderMetadataSnapshotFile; +import io.seata.server.cluster.raft.snapshot.session.SessionSnapshotFile; +import io.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import io.seata.server.cluster.raft.execute.RaftMsgExecute; +import io.seata.server.cluster.raft.execute.branch.AddBranchSessionExecute; +import io.seata.server.cluster.raft.execute.branch.RemoveBranchSessionExecute; +import io.seata.server.cluster.raft.execute.branch.UpdateBranchSessionExecute; +import io.seata.server.cluster.raft.execute.global.AddGlobalSessionExecute; +import io.seata.server.cluster.raft.execute.global.RemoveGlobalSessionExecute; +import io.seata.server.cluster.raft.execute.global.UpdateGlobalSessionExecute; +import io.seata.server.cluster.raft.execute.lock.BranchReleaseLockExecute; +import io.seata.server.cluster.raft.execute.lock.GlobalReleaseLockExecute; +import io.seata.server.cluster.listener.ClusterChangeEvent; +import io.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType; +import io.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import io.seata.server.cluster.raft.util.RaftTaskUtil; +import io.seata.server.session.SessionHolder; +import io.seata.server.store.StoreConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.env.Environment; + +import static io.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; +import static io.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; +import static io.seata.common.DefaultValues.SERVICE_OFFSET_SPRING_BOOT; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_BRANCH_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_GLOBAL_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REFRESH_CLUSTER_METADATA; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.RELEASE_BRANCH_SESSION_LOCK; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.RELEASE_GLOBAL_SESSION_LOCK; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REMOVE_BRANCH_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REMOVE_GLOBAL_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.UPDATE_BRANCH_SESSION_STATUS; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.UPDATE_GLOBAL_SESSION_STATUS; + +/** + * @author funkye + */ +public class RaftStateMachine extends StateMachineAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftStateMachine.class); + + private final String mode; + + private final String group; + + private final List snapshotFiles = new ArrayList<>(); + + private static final Map> EXECUTES = new HashMap<>(); + + private volatile RaftClusterMetadata raftClusterMetadata; + + /** + * Leader term + */ + private final AtomicLong leaderTerm = new AtomicLong(-1); + + /** + * current term + */ + private final AtomicLong currentTerm = new AtomicLong(-1); + + public boolean isLeader() { + return this.leaderTerm.get() > 0; + } + + public RaftStateMachine(String group) { + this.group = group; + mode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE); + EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { + refreshClusterMetadata(syncMsg); + return null; + }); + registryStoreSnapshotFile(new LeaderMetadataSnapshotFile(group)); + if (StoreMode.RAFT.getName().equalsIgnoreCase(mode)) { + registryStoreSnapshotFile(new SessionSnapshotFile(group)); + EXECUTES.put(ADD_GLOBAL_SESSION, new AddGlobalSessionExecute()); + EXECUTES.put(ADD_BRANCH_SESSION, new AddBranchSessionExecute()); + EXECUTES.put(REMOVE_BRANCH_SESSION, new RemoveBranchSessionExecute()); + EXECUTES.put(UPDATE_GLOBAL_SESSION_STATUS, new UpdateGlobalSessionExecute()); + EXECUTES.put(RELEASE_GLOBAL_SESSION_LOCK, new GlobalReleaseLockExecute()); + EXECUTES.put(REMOVE_GLOBAL_SESSION, new RemoveGlobalSessionExecute()); + EXECUTES.put(UPDATE_BRANCH_SESSION_STATUS, new UpdateBranchSessionExecute()); + EXECUTES.put(RELEASE_BRANCH_SESSION_LOCK, new BranchReleaseLockExecute()); + } + } + + @Override + public void onApply(Iterator iterator) { + while (iterator.hasNext()) { + Closure done = iterator.done(); + if (done != null) { + // leader does not need to be serialized, just execute the task directly + done.run(Status.OK()); + } else { + ByteBuffer byteBuffer = iterator.getData(); + // if data is empty, it is only a heartbeat event and can be ignored + if (byteBuffer != null && byteBuffer.hasRemaining()) { + RaftBaseMsg msg = (RaftBaseMsg)RaftSyncMessageSerializer.decode(byteBuffer.array()).getBody(); + // follower executes the corresponding task + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sync msg: {}", msg); + } + onExecuteRaft(msg); + } + } + iterator.next(); + } + } + + @Override + public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { + if (!StringUtils.equals(StoreConfig.SessionMode.RAFT.getName(), mode)) { + done.run(Status.OK()); + return; + } + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + Status status = snapshotFile.save(writer); + if (!status.isOk()) { + done.run(status); + return; + } + } + LOGGER.info("groupId: {}, onSnapshotSave cost: {} ms.", group, System.currentTimeMillis() - current); + done.run(Status.OK()); + } + + @Override + public boolean onSnapshotLoad(final SnapshotReader reader) { + if (!StringUtils.equals(StoreConfig.SessionMode.RAFT.getName(), mode)) { + return true; + } + if (isLeader()) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Leader is not supposed to load snapshot"); + } + return false; + } + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + if (!snapshotFile.load(reader)) { + return false; + } + } + LOGGER.info("groupId: {}, onSnapshotLoad cost: {} ms.", group, System.currentTimeMillis() - current); + return true; + } + + @Override + public void onLeaderStart(final long term) { + boolean leader = isLeader(); + this.leaderTerm.set(term); + LOGGER.info("groupId: {}, onLeaderStart: term={}.", group, term); + this.currentTerm.set(term); + SeataClusterContext.bindGroup(group); + syncMetadata(); + if (!leader && RaftServerFactory.getInstance().isRaftMode()) { + CompletableFuture.runAsync(() -> { + LOGGER.info("reload session, groupId: {}, session map size: {} ", group, + SessionHolder.getRootSessionManager().allSessions().size()); + SeataClusterContext.bindGroup(group); + try { + // become the leader again,reloading global session + SessionHolder.reload(SessionHolder.getRootSessionManager().allSessions(), + StoreConfig.SessionMode.RAFT, false); + } finally { + SeataClusterContext.unbindGroup(); + } + }); + } + } + + @Override + public void onLeaderStop(final Status status) { + this.leaderTerm.set(-1); + LOGGER.info("groupId: {}, onLeaderStop: status={}.", group, status); + } + + @Override + public void onStopFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStopFollowing: {}.", group, ctx); + } + + @Override + public void onStartFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStartFollowing: {}.", group, ctx); + this.currentTerm.set(ctx.getTerm()); + } + + @Override + public void onConfigurationCommitted(Configuration conf) { + LOGGER.info("groupId: {}, onConfigurationCommitted: {}.", group, conf); + syncMetadata(); + RouteTable.getInstance().updateConfiguration(group, conf); + } + + private void syncMetadata() { + if (isLeader()) { + SeataClusterContext.bindGroup(group); + try { + RaftClusterMetadataMsg raftClusterMetadataMsg = + new RaftClusterMetadataMsg(createNewRaftClusterMetadata()); + RaftTaskUtil.createTask(status -> refreshClusterMetadata(raftClusterMetadataMsg), + raftClusterMetadataMsg, null); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } finally { + SeataClusterContext.unbindGroup(); + } + } + } + + private void onExecuteRaft(RaftBaseMsg msg) { + RaftMsgExecute execute = EXECUTES.get(msg.getMsgType()); + if (execute == null) { + throw new RuntimeException( + "the state machine does not allow events that cannot be executed, please feedback the information to the Seata community !!! msg: " + + msg); + } + try { + execute.execute(msg); + } catch (Throwable e) { + LOGGER.error("Message synchronization failure: {}, msgType: {}", e.getMessage(), msg.getMsgType(), e); + throw new RuntimeException(e); + } + } + + public AtomicLong getCurrentTerm() { + return currentTerm; + } + + public void registryStoreSnapshotFile(StoreSnapshotFile storeSnapshotFile) { + snapshotFiles.add(storeSnapshotFile); + } + + public RaftClusterMetadata getRaftLeaderMetadata() { + return raftClusterMetadata; + } + + public void setRaftLeaderMetadata(RaftClusterMetadata raftClusterMetadata) { + this.raftClusterMetadata = raftClusterMetadata; + } + + public RaftClusterMetadata createNewRaftClusterMetadata() { + RaftClusterMetadata metadata = new RaftClusterMetadata(this.currentTerm.get()); + Node leader = metadata.createNode(XID.getIpAddress(), XID.getPort(), + Integer.parseInt(((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) + .getProperty("server.port", String.valueOf(8088))), + group, Collections.emptyMap()); + leader.setRole(ClusterRole.LEADER); + metadata.setLeader(leader); + Configuration configuration = RouteTable.getInstance().getConfiguration(this.group); + List learners = configuration.getLearners().stream().map(learner -> { + int nettyPort = learner.getPort() - SERVICE_OFFSET_SPRING_BOOT; + Node learnerNode = metadata.createNode(learner.getIp(), nettyPort, nettyPort - SERVICE_OFFSET_SPRING_BOOT, + this.group, Collections.emptyMap()); + learnerNode.setRole(ClusterRole.LEARNER); + return learnerNode; + }).collect(Collectors.toList()); + metadata.setLearner(learners); + List followers = configuration.getPeers().stream().map(follower -> { + int nettyPort = follower.getPort() - SERVICE_OFFSET_SPRING_BOOT; + Node followerNode = metadata.createNode(follower.getIp(), nettyPort, nettyPort - SERVICE_OFFSET_SPRING_BOOT, + this.group, Collections.emptyMap()); + followerNode.setRole(ClusterRole.FOLLOWER); + return followerNode; + }).collect(Collectors.toList()); + metadata.setFollowers(followers); + return metadata; + } + + public void refreshClusterMetadata(RaftBaseMsg syncMsg) { + raftClusterMetadata = ((RaftClusterMetadataMsg)syncMsg).getRaftClusterMetadata(); + ((ApplicationEventPublisher)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterChangeEvent(this, group, raftClusterMetadata.getTerm(), this.isLeader())); + LOGGER.info("groupId: {}, refresh cluster metadata: {}", group, raftClusterMetadata); + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/context/SeataClusterContext.java b/server/src/main/java/io/seata/server/cluster/raft/context/SeataClusterContext.java new file mode 100644 index 00000000000..d6f7f585623 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/context/SeataClusterContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.context; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.seata.common.ConfigurationKeys; +import io.seata.config.ConfigurationFactory; +import io.seata.core.context.ContextCore; +import io.seata.core.context.ContextCoreLoader; + +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +/** + * @author funkye + */ +public class SeataClusterContext { + + private static final String GROUP = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + + private SeataClusterContext() { + } + + /** + * The constant KEY_GROUP. + */ + public static final String KEY_GROUP = "TX_GROUP"; + + private static ContextCore CONTEXT_HOLDER = ContextCoreLoader.load(); + + /** + * Bind group. + * + * @param group the group + */ + public static void bindGroup(@Nonnull String group) { + CONTEXT_HOLDER.put(KEY_GROUP, group); + } + + /** + * Bind group. + * + */ + public static String bindGroup() { + CONTEXT_HOLDER.put(KEY_GROUP, GROUP); + return GROUP; + } + + /** + * Unbind group. + */ + public static void unbindGroup() { + CONTEXT_HOLDER.remove(KEY_GROUP); + } + + @Nullable + public static String getGroup() { + return (String) CONTEXT_HOLDER.get(KEY_GROUP); + } + + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/AbstractRaftMsgExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/AbstractRaftMsgExecute.java new file mode 100644 index 00000000000..ad24b314f1a --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/AbstractRaftMsgExecute.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.storage.raft.lock.RaftLockManager; + +/** + * @author jianbin.chen + */ +public abstract class AbstractRaftMsgExecute implements RaftMsgExecute { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + protected RaftLockManager raftLockManager = (RaftLockManager)LockerManagerFactory.getLockManager(); + +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java b/server/src/main/java/io/seata/server/cluster/raft/execute/RaftMsgExecute.java similarity index 65% rename from saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java rename to server/src/main/java/io/seata/server/cluster/raft/execute/RaftMsgExecute.java index fa23a374e65..78eacdb56d3 100644 --- a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/RaftMsgExecute.java @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.saga.engine.evaluation; +package io.seata.server.cluster.raft.execute; + +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; /** - * Evaluator Factory - * - * @author lorne.cl - * @see Evaluator + * @author jianbin.chen */ -public interface EvaluatorFactory { +public interface RaftMsgExecute { /** - * create evaluator + * Execute t. * - * @param expressionString the expression - * @return the evaluator + * @param syncMsg the sessionSyncMsg + * @return the t + * @throws Throwable the throwable */ - Evaluator createEvaluator(String expressionString); -} \ No newline at end of file + T execute(RaftBaseMsg syncMsg) throws Throwable; + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java new file mode 100644 index 00000000000..5cca618a283 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.branch; + +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.SessionConverter; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class AddBranchSessionExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; + RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + BranchTransactionDTO branchTransactionDTO = sessionSyncMsg.getBranchSession(); + GlobalSession globalSession = raftSessionManager.findGlobalSession(branchTransactionDTO.getXid()); + BranchSession branchSession = SessionConverter.convertBranchSession(branchTransactionDTO); + branchSession.lock(); + globalSession.add(branchSession); + if (logger.isDebugEnabled()) { + logger.debug("addBranch xid: {},branchId: {}", branchTransactionDTO.getXid(), + branchTransactionDTO.getBranchId()); + } + return true; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/branch/RemoveBranchSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/RemoveBranchSessionExecute.java new file mode 100644 index 00000000000..6dece9960e6 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/RemoveBranchSessionExecute.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.branch; + +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class RemoveBranchSessionExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; + RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + GlobalSession globalSession = raftSessionManager.findGlobalSession(sessionSyncMsg.getBranchSession().getXid()); + if (globalSession != null) { + BranchSession branchSession = globalSession.getBranch(sessionSyncMsg.getBranchSession().getBranchId()); + if (branchSession != null) { + raftLockManager.localReleaseLock(branchSession); + globalSession.remove(branchSession); + } + if (logger.isDebugEnabled()) { + logger.debug("removeBranch xid: {},branchId: {}", globalSession.getXid(), + sessionSyncMsg.getBranchSession().getBranchId()); + } + } + return true; + } +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java new file mode 100644 index 00000000000..3608dd2681d --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.branch; + +import io.seata.core.model.BranchStatus; +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class UpdateBranchSessionExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; + RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + GlobalSession globalSession = raftSessionManager.findGlobalSession(sessionSyncMsg.getBranchSession().getXid()); + BranchSession branchSession = globalSession.getBranch(sessionSyncMsg.getBranchSession().getBranchId()); + BranchStatus status = BranchStatus.get(sessionSyncMsg.getBranchSession().getStatus()); + branchSession.setStatus(status); + if (logger.isDebugEnabled()) { + logger.debug("update branch: {} , status: {}", branchSession.getBranchId(), branchSession.getStatus()); + } + return true; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/global/AddGlobalSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/global/AddGlobalSessionExecute.java new file mode 100644 index 00000000000..7b8d50dd6a6 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/global/AddGlobalSessionExecute.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.global; + +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.SessionConverter; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class AddGlobalSessionExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftGlobalSessionSyncMsg sessionSyncMsg = (RaftGlobalSessionSyncMsg)syncMsg; + RaftSessionManager raftSessionManager = + (RaftSessionManager)SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + GlobalSession globalSession = SessionConverter.convertGlobalSession(sessionSyncMsg.getGlobalSession()); + raftSessionManager.addGlobalSession(globalSession); + if (logger.isDebugEnabled()) { + logger.debug("add session xid: {}", globalSession.getXid()); + } + return true; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/global/RemoveGlobalSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/global/RemoveGlobalSessionExecute.java new file mode 100644 index 00000000000..46b2640381a --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/global/RemoveGlobalSessionExecute.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.global; + +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.core.exception.TransactionException; +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class RemoveGlobalSessionExecute extends AbstractRaftMsgExecute { + + private static final ThreadPoolExecutor EXECUTOR = + new ThreadPoolExecutor(1, 1, Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(2048), + new NamedThreadFactory("RemoveGlobalSessionExecute", 1), new ThreadPoolExecutor.CallerRunsPolicy()); + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftGlobalSessionSyncMsg sessionSyncMsg = (RaftGlobalSessionSyncMsg)syncMsg; + // when the global transaction needs to be deleted, it does not affect any consistency issues, and can be + // deleted in an asynchronous thread to improve the throughput of the state machine + RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + Optional.ofNullable(raftSessionManager.findGlobalSession(sessionSyncMsg.getGlobalSession().getXid())) + .ifPresent(globalSession -> { + try { + raftLockManager.localReleaseGlobalSessionLock(globalSession); + EXECUTOR.execute(() -> { + try { + raftSessionManager.removeGlobalSession(globalSession); + if (logger.isDebugEnabled()) { + logger.debug("remove session xid: {}", globalSession.getXid()); + } + } catch (TransactionException e) { + logger.error("remove global fail error:{}", e.getMessage()); + } + }); + } catch (TransactionException e) { + logger.error(e.getMessage(), e); + } + }); + return true; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/global/UpdateGlobalSessionExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/global/UpdateGlobalSessionExecute.java new file mode 100644 index 00000000000..dce38897013 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/global/UpdateGlobalSessionExecute.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.global; + +import io.seata.core.model.GlobalStatus; +import io.seata.core.model.LockStatus; +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.raft.session.RaftSessionManager; + +/** + * @author jianbin.chen + */ +public class UpdateGlobalSessionExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftGlobalSessionSyncMsg sessionSyncMsg = (RaftGlobalSessionSyncMsg)syncMsg; + RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); + GlobalTransactionDTO globalTransactionDTO = sessionSyncMsg.getGlobalSession(); + GlobalSession globalSession = raftSessionManager.findGlobalSession(globalTransactionDTO.getXid()); + if (globalSession != null) { + globalSession.setStatus(GlobalStatus.get(globalTransactionDTO.getStatus())); + if (GlobalStatus.RollbackRetrying.equals(globalSession.getStatus()) + || GlobalStatus.Rollbacking.equals(globalSession.getStatus()) + || GlobalStatus.TimeoutRollbacking.equals(globalSession.getStatus())) { + globalSession.getBranchSessions().parallelStream() + .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); + } + if (logger.isDebugEnabled()) { + logger.debug("xid: {}, change status: {}", globalSession.getXid(), globalSession.getStatus()); + } + } + return true; + } +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java new file mode 100644 index 00000000000..e52a9aaaf3b --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.lock; + +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; + +/** + * @author jianbin.chen + */ +public class BranchReleaseLockExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; + GlobalSession globalSession = + SessionHolder.getRootSessionManager().findGlobalSession(sessionSyncMsg.getBranchSession().getXid()); + BranchSession branchSession = globalSession.getBranch(sessionSyncMsg.getBranchSession().getBranchId()); + if (branchSession != null) { + if (logger.isDebugEnabled()) { + logger.debug("releaseBranchSessionLock xid: {}", globalSession.getXid()); + } + return raftLockManager.localReleaseLock(branchSession); + } + return false; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/execute/lock/GlobalReleaseLockExecute.java b/server/src/main/java/io/seata/server/cluster/raft/execute/lock/GlobalReleaseLockExecute.java new file mode 100644 index 00000000000..061ab461a76 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/execute/lock/GlobalReleaseLockExecute.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.execute.lock; + +import io.seata.server.cluster.raft.execute.AbstractRaftMsgExecute; +import io.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; + +/** + * @author jianbin.chen + */ +public class GlobalReleaseLockExecute extends AbstractRaftMsgExecute { + + @Override + public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { + RaftGlobalSessionSyncMsg sessionSyncMsg = (RaftGlobalSessionSyncMsg)syncMsg; + GlobalSession globalSession = + SessionHolder.getRootSessionManager().findGlobalSession(sessionSyncMsg.getGlobalSession().getXid()); + if (globalSession != null) { + if (logger.isDebugEnabled()) { + logger.debug("releaseGlobalSessionLock xid: {}", globalSession.getXid()); + } + globalSession.setActive(false); + return raftLockManager.localReleaseGlobalSessionLock(globalSession); + } + return false; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/serializer/JacksonSerializer.java b/server/src/main/java/io/seata/server/cluster/raft/serializer/JacksonSerializer.java new file mode 100644 index 00000000000..ffdc82dbf13 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/serializer/JacksonSerializer.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.serializer; + +import java.io.IOException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.seata.common.loader.LoadLevel; +import io.seata.core.serializer.Serializer; + +/** + * @author jianbin.chen + */ +@LoadLevel(name = "JACKSON") +public class JacksonSerializer implements Serializer { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public byte[] serialize(T t) { + try { + JsonInfo jsonInfo = new JsonInfo(objectMapper.writeValueAsBytes(t), t.getClass()); + return objectMapper.writeValueAsBytes(jsonInfo); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public T deserialize(byte[] bytes) { + try { + JsonInfo jsonInfo = objectMapper.readValue(bytes, JsonInfo.class); + return (T)objectMapper.readValue(jsonInfo.getObj(), jsonInfo.getClz()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class JsonInfo { + + byte[] obj; + + Class clz; + + public JsonInfo() {} + + public JsonInfo(byte[] obj, Class clz) { + this.obj = obj; + this.clz = clz; + } + + public byte[] getObj() { + return obj; + } + + public void setObj(byte[] obj) { + this.obj = obj; + } + + public Class getClz() { + return clz; + } + + public void setClz(Class clz) { + this.clz = clz; + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshot.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshot.java new file mode 100644 index 00000000000..483805f87f1 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshot.java @@ -0,0 +1,144 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot; + +import java.io.Serializable; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.compressor.CompressorType; +import io.seata.core.protocol.Version; +import io.seata.core.serializer.SerializerType; + +import static io.seata.common.ConfigurationKeys.SERVER_RAFT_COMPRESSOR; +import static io.seata.common.DefaultValues.DEFAULT_RAFT_COMPRESSOR; +import static io.seata.common.DefaultValues.DEFAULT_RAFT_SERIALIZATION; + +/** + * @author funkye + */ +public class RaftSnapshot implements Serializable { + + private byte codec = SerializerType.getByName(DEFAULT_RAFT_SERIALIZATION).getCode(); + + private byte compressor = CompressorType + .getByName(ConfigurationFactory.getInstance().getConfig(SERVER_RAFT_COMPRESSOR, DEFAULT_RAFT_COMPRESSOR)) + .getCode(); + + private Object body; + + private String version = Version.getCurrent(); + + private SnapshotType type; + + /** + * Gets body. + * + * @return the body + */ + public Object getBody() { + return body; + } + + /** + * Sets body. + * + * @param body the body + */ + public void setBody(Object body) { + this.body = body; + } + + /** + * Gets codec. + * + * @return the codec + */ + public byte getCodec() { + return codec; + } + + /** + * Sets codec. + * + * @param codec the codec + * @return the codec + */ + public RaftSnapshot setCodec(byte codec) { + this.codec = codec; + return this; + } + + /** + * Gets compressor. + * + * @return the compressor + */ + public byte getCompressor() { + return compressor; + } + + /** + * Sets compressor. + * + * @param compressor the compressor + * @return the compressor + */ + public RaftSnapshot setCompressor(byte compressor) { + this.compressor = compressor; + return this; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public SnapshotType getType() { + return type; + } + + public void setType(SnapshotType type) { + this.type = type; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + + public enum SnapshotType { + + /** + * session snapshot + */ + session("session"), + /** + * leader metadata snapshot + */ + leader_metadata("leader_metadata"); + + final String type; + + SnapshotType(String type) { + this.type = type; + } + + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java new file mode 100644 index 00000000000..90b5f570326 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.core.compressor.CompressorFactory; +import io.seata.core.serializer.Serializer; +import io.seata.core.serializer.SerializerType; + +/** + * @author funkye + */ +public class RaftSnapshotSerializer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftSnapshotSerializer.class); + + public static byte[] encode(RaftSnapshot raftSnapshot) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + Serializer serializer = + EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSnapshot.getCodec()).name()); + Optional.ofNullable(raftSnapshot.getBody()).ifPresent(value -> raftSnapshot.setBody( + CompressorFactory.getCompressor(raftSnapshot.getCompressor()).compress(serializer.serialize(value)))); + oos.writeObject(raftSnapshot); + return bos.toByteArray(); + } + } + + public static RaftSnapshot decode(byte[] raftSnapshotByte) throws IOException { + try (ByteArrayInputStream bin = new ByteArrayInputStream(raftSnapshotByte); + ObjectInputStream ois = new ObjectInputStream(bin)) { + RaftSnapshot raftSnapshot = (RaftSnapshot)ois.readObject(); + Serializer serializer = + EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSnapshot.getCodec()).name()); + Optional.ofNullable(raftSnapshot.getBody()) + .ifPresent(value -> raftSnapshot.setBody(serializer.deserialize(CompressorFactory + .getCompressor(raftSnapshot.getCompressor()).decompress((byte[])raftSnapshot.getBody())))); + return raftSnapshot; + } catch (ClassNotFoundException e) { + LOGGER.info("Failed to read raft snapshot: {}", e.getMessage(), e); + throw new IOException(e); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/StoreSnapshotFile.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/StoreSnapshotFile.java new file mode 100644 index 00000000000..a2d82eb9a57 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/StoreSnapshotFile.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot; + +import java.io.File; +import java.io.IOException; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.commons.io.FileUtils; + +/** + * @author funkye + */ +public interface StoreSnapshotFile { + + /** + * Save a snapshot . + * + * @param writer snapshot writer + * @return true if save succeed + */ + Status save(final SnapshotWriter writer); + + /** + * Load snapshot for the specified region. + * + * @param reader snapshot reader + * @return true if load succeed + */ + boolean load(final SnapshotReader reader); + + default boolean save(final RaftSnapshot value, String path) throws IOException { + FileUtils.writeByteArrayToFile(new File(path), RaftSnapshotSerializer.encode(value)); + return true; + } + + /** + * Save value to snapshot file. + */ + default Object load(String path) throws IOException { + RaftSnapshot raftSnapshot = RaftSnapshotSerializer.decode(FileUtils.readFileToByteArray(new File(path))); + return raftSnapshot.getBody(); + } + + + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java new file mode 100644 index 00000000000..85c37fecdcb --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot.metadata; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import io.seata.server.cluster.raft.RaftServerFactory; +import io.seata.server.cluster.raft.snapshot.RaftSnapshot; +import io.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import io.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jianbin.chen + */ +public class LeaderMetadataSnapshotFile implements Serializable, StoreSnapshotFile { + private static final long serialVersionUID = 78637164618855724L; + + private static final Logger LOGGER = LoggerFactory.getLogger(LeaderMetadataSnapshotFile.class); + + private final String group; + + private final String fileName = "leader_metadata"; + + public LeaderMetadataSnapshotFile(String group) { + this.group = group; + } + + @Override + public Status save(SnapshotWriter writer) { + RaftSnapshot raftSnapshot = new RaftSnapshot(); + RaftClusterMetadata raftClusterMetadata = + RaftServerFactory.getInstance().getRaftServer(group).getRaftStateMachine().getRaftLeaderMetadata(); + raftSnapshot.setBody(raftClusterMetadata); + raftSnapshot.setType(RaftSnapshot.SnapshotType.leader_metadata); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + RaftClusterMetadata raftClusterMetadata = (RaftClusterMetadata)load(path); + RaftServerFactory.getInstance().getRaftServer(group).getRaftStateMachine() + .setRaftLeaderMetadata(raftClusterMetadata); + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/RaftSessionSnapshot.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/RaftSessionSnapshot.java new file mode 100644 index 00000000000..5d975ea4538 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/RaftSessionSnapshot.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot.session; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import io.seata.common.util.CollectionUtils; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.GlobalStatus; +import io.seata.core.model.LockStatus; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RaftSessionSnapshot implements java.io.Serializable { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftSessionSnapshot.class); + + private static final long serialVersionUID = -2257327786007900291L; + + private Map/*branch sessions*/> globalsessions = new ConcurrentHashMap<>(); + + public Map> getGlobalsessions() { + return globalsessions; + } + + public void setGlobalsessions(Map> globalsessions) { + this.globalsessions = globalsessions; + } + + public Map convert2GlobalSession() { + Map sessionMap = new HashMap<>(); + globalsessions.forEach((globalSessionByte, branchSessionBytes) -> { + GlobalSession globalSession = new GlobalSession(); + globalSession.decode(globalSessionByte); + branchSessionBytes.forEach(branch -> { + BranchSession branchSession = new BranchSession(); + branchSession.decode(branch); + if (globalSession.isActive()) { + try { + branchSession.lock(); + } catch (TransactionException e) { + LOGGER.error(e.getMessage()); + } + } + globalSession.add(branchSession); + }); + if (GlobalStatus.Rollbacking.equals(globalSession.getStatus()) + || GlobalStatus.TimeoutRollbacking.equals(globalSession.getStatus())) { + globalSession.getBranchSessions().parallelStream() + .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); + } + sessionMap.put(globalSession.getXid(), globalSession); + }); + return sessionMap; + } + + public void convert2GlobalSessionByte(GlobalSession globalSession) { + byte[] globalSessionByte = globalSession.encode(); + if (CollectionUtils.isEmpty(globalSession.getBranchSessions())) { + globalsessions.put(globalSessionByte, Collections.emptyList()); + } else { + globalsessions.put(globalSessionByte, globalSession.getBranchSessions().parallelStream() + .map(branch -> branch.encode()).collect(Collectors.toList())); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/SessionSnapshotFile.java b/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/SessionSnapshotFile.java new file mode 100644 index 00000000000..12504a8eabf --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/snapshot/session/SessionSnapshotFile.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.snapshot.session; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import io.seata.server.cluster.raft.snapshot.RaftSnapshot; +import io.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHolder; +import io.seata.server.storage.raft.session.RaftSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author funkye + */ +public class SessionSnapshotFile implements Serializable,StoreSnapshotFile { + + private static final Logger LOGGER = LoggerFactory.getLogger(SessionSnapshotFile.class); + + private static final long serialVersionUID = 7942307427240595916L; + + String group; + + String fileName = "session"; + + public SessionSnapshotFile(String group) { + this.group = group; + } + + @Override + public Status save(SnapshotWriter writer) { + RaftSessionManager raftSessionManager = (RaftSessionManager)SessionHolder.getRootSessionManager(group); + Map sessionMap = raftSessionManager.getSessionMap(); + RaftSessionSnapshot sessionSnapshot = new RaftSessionSnapshot(); + sessionMap.forEach((xid, session) -> sessionSnapshot.convert2GlobalSessionByte(session)); + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(sessionSnapshot); + raftSnapshot.setType(RaftSnapshot.SnapshotType.session); + LOGGER.info("groupId: {}, global session size: {}", group, sessionSnapshot.getGlobalsessions().size()); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + LOGGER.info("on snapshot load start index: {}", reader.load().getLastIncludedIndex()); + RaftSessionSnapshot sessionSnapshot = (RaftSessionSnapshot)load(path); + RaftSessionManager raftSessionManager = (RaftSessionManager)SessionHolder.getRootSessionManager(group); + Map rootSessionMap = raftSessionManager.getSessionMap(); + // be sure to clear the data before loading it, because this is a full overwrite update + LockerManagerFactory.getLockManager().cleanAllLocks(); + rootSessionMap.clear(); + rootSessionMap.putAll(sessionSnapshot.convert2GlobalSession()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("on snapshot load end index: {}", reader.load().getLastIncludedIndex()); + } + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/RaftSyncMessageSerializer.java b/server/src/main/java/io/seata/server/cluster/raft/sync/RaftSyncMessageSerializer.java new file mode 100644 index 00000000000..87ac596cf9d --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/RaftSyncMessageSerializer.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.core.compressor.CompressorFactory; +import io.seata.core.serializer.Serializer; +import io.seata.core.serializer.SerializerType; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author funkye + */ +public class RaftSyncMessageSerializer { + + private static final Logger LOGGER = LoggerFactory.getLogger(RaftSyncMessageSerializer.class); + + public static byte[] encode(RaftSyncMessage raftSyncMessage) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + Serializer serializer = + EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSyncMessage.getCodec()).name()); + Optional.ofNullable(raftSyncMessage.getBody()).ifPresent(value -> raftSyncMessage.setBody( + CompressorFactory.getCompressor(raftSyncMessage.getCompressor()).compress(serializer.serialize(value)))); + oos.writeObject(raftSyncMessage); + return bos.toByteArray(); + } + } + + public static RaftSyncMessage decode(byte[] raftSyncMsgByte) { + try (ByteArrayInputStream bin = new ByteArrayInputStream(raftSyncMsgByte); + ObjectInputStream ois = new ObjectInputStream(bin)) { + RaftSyncMessage raftSyncMessage = (RaftSyncMessage)ois.readObject(); + Serializer serializer = + EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSyncMessage.getCodec()).name()); + Optional.ofNullable(raftSyncMessage.getBody()) + .ifPresent(value -> raftSyncMessage.setBody(serializer.deserialize(CompressorFactory + .getCompressor(raftSyncMessage.getCompressor()).decompress((byte[]) raftSyncMessage.getBody())))); + return raftSyncMessage; + } catch (ClassNotFoundException | IOException e) { + LOGGER.info("Failed to read raft synchronization log: {}", e.getMessage(), e); + throw new RuntimeException(e); + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBaseMsg.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBaseMsg.java new file mode 100644 index 00000000000..9cc6c2491bd --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBaseMsg.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +/** + * @author jianbin.chen + */ +public class RaftBaseMsg implements java.io.Serializable { + + private static final long serialVersionUID = -1439073440621259777L; + + protected RaftSyncMsgType msgType; + + protected String group = DEFAULT_SEATA_GROUP; + + public RaftSyncMsgType getMsgType() { + return msgType; + } + + public void setMsgType(RaftSyncMsgType msgType) { + this.msgType = msgType; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBranchSessionSyncMsg.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBranchSessionSyncMsg.java new file mode 100644 index 00000000000..f81d6310492 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftBranchSessionSyncMsg.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +import io.seata.common.util.StringUtils; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; + +/** + * @author funkye + */ +public class RaftBranchSessionSyncMsg extends RaftBaseMsg { + + private static final long serialVersionUID = -8577994371969898054L; + + private BranchTransactionDTO branchSession; + + private String group = DEFAULT_SEATA_GROUP; + + public RaftBranchSessionSyncMsg(RaftSyncMsgType msgType, BranchTransactionDTO branchSession) { + this.msgType = msgType; + this.branchSession = branchSession; + } + + public RaftBranchSessionSyncMsg() {} + + public BranchTransactionDTO getBranchSession() { + return branchSession; + } + + public void setBranchSession(BranchTransactionDTO branchSession) { + this.branchSession = branchSession; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftClusterMetadataMsg.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftClusterMetadataMsg.java new file mode 100644 index 00000000000..349c7e756af --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftClusterMetadataMsg.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +import io.seata.common.util.StringUtils; +import io.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; + +/** + * @author jianbin.chen + */ +public class RaftClusterMetadataMsg extends RaftBaseMsg { + + private static final long serialVersionUID = 6208583637662412658L; + + private RaftClusterMetadata raftClusterMetadata; + + public RaftClusterMetadataMsg(RaftClusterMetadata raftClusterMetadata) { + this.msgType = RaftSyncMsgType.REFRESH_CLUSTER_METADATA; + this.raftClusterMetadata = raftClusterMetadata; + } + + public RaftClusterMetadataMsg() { + } + + public RaftClusterMetadata getRaftClusterMetadata() { + return raftClusterMetadata; + } + + public void setRaftClusterMetadata(RaftClusterMetadata raftClusterMetadata) { + this.raftClusterMetadata = raftClusterMetadata; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftGlobalSessionSyncMsg.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftGlobalSessionSyncMsg.java new file mode 100644 index 00000000000..4a30eaa5c0c --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftGlobalSessionSyncMsg.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +import io.seata.common.util.StringUtils; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; + +/** + * @author funkye + */ +public class RaftGlobalSessionSyncMsg extends RaftBaseMsg { + + private static final long serialVersionUID = -8577994371969898054L; + private GlobalTransactionDTO globalSession; + + public RaftGlobalSessionSyncMsg(RaftSyncMsgType msgType, GlobalTransactionDTO globalSession) { + this.msgType = msgType; + this.globalSession = globalSession; + } + + public RaftGlobalSessionSyncMsg() { + } + + public GlobalTransactionDTO getGlobalSession() { + return globalSession; + } + + public void setGlobalSession(GlobalTransactionDTO globalSession) { + this.globalSession = globalSession; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMessage.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMessage.java new file mode 100644 index 00000000000..3683d390874 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMessage.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.compressor.CompressorType; +import io.seata.core.protocol.Version; +import io.seata.core.serializer.SerializerType; + +import static io.seata.common.DefaultValues.DEFAULT_RAFT_COMPRESSOR; +import static io.seata.common.DefaultValues.DEFAULT_RAFT_SERIALIZATION; +import static io.seata.core.constants.ConfigurationKeys.SERVER_RAFT_COMPRESSOR; + +/** + * @author funkye + */ +public class RaftSyncMessage implements java.io.Serializable { + + private static final long serialVersionUID = 8225279734319945365L; + private byte codec = SerializerType.getByName(DEFAULT_RAFT_SERIALIZATION).getCode(); + + private byte compressor = CompressorType + .getByName(ConfigurationFactory.getInstance().getConfig(SERVER_RAFT_COMPRESSOR, DEFAULT_RAFT_COMPRESSOR)) + .getCode(); + + private Object body; + + private String version = Version.getCurrent(); + + /** + * Gets body. + * + * @return the body + */ + public Object getBody() { + return body; + } + + /** + * Sets body. + * + * @param body the body + */ + public void setBody(Object body) { + this.body = body; + } + + /** + * Gets codec. + * + * @return the codec + */ + public byte getCodec() { + return codec; + } + + /** + * Sets codec. + * + * @param codec the codec + * @return the codec + */ + public RaftSyncMessage setCodec(byte codec) { + this.codec = codec; + return this; + } + + /** + * Gets compressor. + * + * @return the compressor + */ + public byte getCompressor() { + return compressor; + } + + /** + * Sets compressor. + * + * @param compressor the compressor + * @return the compressor + */ + public RaftSyncMessage setCompressor(byte compressor) { + this.compressor = compressor; + return this; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java new file mode 100644 index 00000000000..4be80e69b5e --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg; + +/** + * @author jianbin.chen + */ +public enum RaftSyncMsgType { + + /** + * addGlobalSession + */ + ADD_GLOBAL_SESSION, + /** + * removeGlobalSession + */ + REMOVE_GLOBAL_SESSION, + /** + * + */ + ADD_BRANCH_SESSION, + /** + * addBranchSession + */ + REMOVE_BRANCH_SESSION, + /** + * updateGlobalSessionStatus + */ + UPDATE_GLOBAL_SESSION_STATUS, + /** + * updateBranchSessionStatus + */ + UPDATE_BRANCH_SESSION_STATUS, + /** + * releaseGlobalSessionLock + */ + RELEASE_GLOBAL_SESSION_LOCK, + /** + * releaseBranchSessionLock + */ + RELEASE_BRANCH_SESSION_LOCK, + /** + * refresh cluster metadata + */ + REFRESH_CLUSTER_METADATA; +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/BranchTransactionDTO.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/BranchTransactionDTO.java new file mode 100644 index 00000000000..b465a4c0824 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/BranchTransactionDTO.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg.dto; + +import io.seata.core.store.BranchTransactionDO; + +/** + * @author jianbin.chen + */ +public class BranchTransactionDTO extends BranchTransactionDO { + + private static final long serialVersionUID = 4550610938263777969L; + private String lockKey; + + public BranchTransactionDTO(String xid, long branchId) { + super(xid, branchId); + } + + public BranchTransactionDTO() { + super(); + } + + public String getLockKey() { + return lockKey; + } + + public void setLockKey(String lockKey) { + this.lockKey = lockKey; + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/GlobalTransactionDTO.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/GlobalTransactionDTO.java new file mode 100644 index 00000000000..4d1a0e3a470 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/GlobalTransactionDTO.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg.dto; + +import io.seata.core.store.GlobalTransactionDO; + +/** + * @author jianbin.chen + */ +public class GlobalTransactionDTO extends GlobalTransactionDO { + private static final long serialVersionUID = 8402806824435215696L; + + public GlobalTransactionDTO(String xid) { + super(xid); + } + + public GlobalTransactionDTO() { + super(); + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/RaftClusterMetadata.java b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/RaftClusterMetadata.java new file mode 100644 index 00000000000..94fd74628f4 --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/sync/msg/dto/RaftClusterMetadata.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.sync.msg.dto; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import io.seata.common.metadata.Node; +import io.seata.common.util.StringUtils; + +/** + * @author jianbin.chen + */ +public class RaftClusterMetadata implements Serializable { + + private static final long serialVersionUID = 6208583637662412658L; + + private Node leader; + + private List followers; + + private List learner; + + private long term; + + public RaftClusterMetadata() { + } + + public RaftClusterMetadata(long term) { + this.term = term; + } + + public Node createNode(String host, int txPort, int controlPort, String group, Map metadata) { + Node node = new Node(); + node.setTransaction(node.createEndpoint(host, txPort, "seata")); + node.setControl(node.createEndpoint(host, controlPort, "http")); + node.setGroup(group); + Optional.ofNullable(metadata).ifPresent(node::setMetadata); + return node; + } + + public Node getLeader() { + return leader; + } + + public void setLeader(Node leader) { + this.leader = leader; + } + + public long getTerm() { + return term; + } + + public List getFollowers() { + return followers; + } + + public void setFollowers(List followers) { + this.followers = followers; + } + + public List getLearner() { + return learner; + } + + public void setLearner(List learner) { + this.learner = learner; + } + + public void setTerm(long term) { + this.term = term; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} diff --git a/server/src/main/java/io/seata/server/cluster/raft/util/RaftTaskUtil.java b/server/src/main/java/io/seata/server/cluster/raft/util/RaftTaskUtil.java new file mode 100644 index 00000000000..484ab803d2c --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/raft/util/RaftTaskUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.raft.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.entity.Task; +import io.seata.core.exception.GlobalTransactionException; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.server.cluster.raft.RaftServerFactory; +import io.seata.server.cluster.raft.context.SeataClusterContext; +import io.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMessage; + +/** + * @author funkye + */ +public class RaftTaskUtil { + + public static boolean createTask(Closure done, Object data, CompletableFuture completableFuture) + throws TransactionException { + final Task task = new Task(); + if (data != null) { + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + raftSyncMessage.setBody(data); + try { + task.setData(ByteBuffer.wrap(RaftSyncMessageSerializer.encode(raftSyncMessage))); + } catch (IOException e) { + throw new TransactionException(e); + } + } + task.setDone(done == null ? status -> { + } : done); + RaftServerFactory.getInstance().getRaftServer(SeataClusterContext.getGroup()).getNode().apply(task); + if (completableFuture != null) { + return futureGet(completableFuture); + } + return true; + } + + public static boolean createTask(Closure done, CompletableFuture completableFuture) + throws TransactionException { + return createTask(done, null, completableFuture); + } + + public static boolean futureGet(CompletableFuture completableFuture) throws TransactionException { + try { + return completableFuture.get(); + } catch (InterruptedException e) { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } catch (ExecutionException e) { + if (e.getCause() instanceof TransactionException) { + throw (TransactionException)e.getCause(); + } else { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } + } + } + +} diff --git a/server/src/main/java/io/seata/server/cluster/watch/Watcher.java b/server/src/main/java/io/seata/server/cluster/watch/Watcher.java new file mode 100644 index 00000000000..dc7a2023c3a --- /dev/null +++ b/server/src/main/java/io/seata/server/cluster/watch/Watcher.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.cluster.watch; + +import static io.seata.server.cluster.watch.Watcher.Protocol.HTTP; + +/** + * @author funkye + */ +public class Watcher { + + private String group; + + private volatile boolean done = false; + + private T asyncContext; + + private long timeout; + + private long term; + + private String protocol = HTTP; + + public Watcher(String group, T asyncContext, int timeout, long term) { + this.group = group; + this.asyncContext = asyncContext; + this.timeout = System.currentTimeMillis() + timeout; + this.term = term; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public boolean isDone() { + return done; + } + + public void setDone(boolean done) { + this.done = done; + } + + public T getAsyncContext() { + return asyncContext; + } + + public void setAsyncContext(T asyncContext) { + this.asyncContext = asyncContext; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public long getTerm() { + return term; + } + + public void setTerm(long term) { + this.term = term; + } + + public interface Protocol { + String GRPC = "grpc"; + String HTTP = "http"; + } + +} diff --git a/server/src/main/java/io/seata/server/console/impl/raft/BranchSessionRaftServiceImpl.java b/server/src/main/java/io/seata/server/console/impl/raft/BranchSessionRaftServiceImpl.java new file mode 100644 index 00000000000..7fa33a14538 --- /dev/null +++ b/server/src/main/java/io/seata/server/console/impl/raft/BranchSessionRaftServiceImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.console.impl.raft; + +import io.seata.server.console.impl.file.BranchSessionFileServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Branch Session File ServiceImpl + * + * @author: zhongxiang.wang + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'raft'.equals('${sessionMode}')}") +public class BranchSessionRaftServiceImpl extends BranchSessionFileServiceImpl { +} diff --git a/server/src/main/java/io/seata/server/console/impl/raft/GlobalLockRaftServiceImpl.java b/server/src/main/java/io/seata/server/console/impl/raft/GlobalLockRaftServiceImpl.java new file mode 100644 index 00000000000..c1754a86604 --- /dev/null +++ b/server/src/main/java/io/seata/server/console/impl/raft/GlobalLockRaftServiceImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.console.impl.raft; + +import io.seata.server.console.impl.file.GlobalLockFileServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Global Lock File ServiceImpl + * + * @author: zhongxiang.wang + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'raft'.equals('${lockMode}')}") +public class GlobalLockRaftServiceImpl extends GlobalLockFileServiceImpl { +} diff --git a/server/src/main/java/io/seata/server/console/impl/raft/GlobalSessionRaftServiceImpl.java b/server/src/main/java/io/seata/server/console/impl/raft/GlobalSessionRaftServiceImpl.java new file mode 100644 index 00000000000..d625e64ff07 --- /dev/null +++ b/server/src/main/java/io/seata/server/console/impl/raft/GlobalSessionRaftServiceImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.console.impl.raft; + +import io.seata.server.console.impl.file.GlobalSessionFileServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * Global Session File ServiceImpl + * + * @author: zhongxiang.wang + */ +@Component +@org.springframework.context.annotation.Configuration +@ConditionalOnExpression("#{'raft'.equals('${sessionMode}')}") +public class GlobalSessionRaftServiceImpl extends GlobalSessionFileServiceImpl { +} diff --git a/server/src/main/java/io/seata/server/controller/ClusterController.java b/server/src/main/java/io/seata/server/controller/ClusterController.java new file mode 100644 index 00000000000..74a19ff22d0 --- /dev/null +++ b/server/src/main/java/io/seata/server/controller/ClusterController.java @@ -0,0 +1,137 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.controller; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletRequest; + +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import io.seata.common.ConfigurationKeys; +import io.seata.common.metadata.MetadataResponse; +import io.seata.common.metadata.Node; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.console.result.Result; +import io.seata.server.cluster.manager.ClusterWatcherManager; +import io.seata.server.cluster.raft.RaftServer; +import io.seata.server.cluster.raft.RaftServerFactory; +import io.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import io.seata.server.cluster.watch.Watcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static io.seata.common.ConfigurationKeys.STORE_MODE; +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +/** + * @author funkye + */ +@RestController +@RequestMapping("/metadata/v1") +public class ClusterController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClusterController.class); + + @Resource + private ClusterWatcherManager clusterWatcherManager; + + private ServerProperties serverProperties; + + @Resource + ApplicationContext applicationContext; + + @PostConstruct + private void init() { + this.serverProperties = applicationContext.getBean(ServerProperties.class); + } + + @PostMapping("/changeCluster") + public Result changeCluster(@RequestParam String raftClusterStr) { + Result result = new Result<>(); + final Configuration newConf = new Configuration(); + if (!newConf.parse(raftClusterStr)) { + result.setMessage("fail to parse initConf:" + raftClusterStr); + } else { + RaftServerFactory.groups().forEach(group -> { + RaftServerFactory.getCliServiceInstance().changePeers(group, + RouteTable.getInstance().getConfiguration(group), newConf); + RouteTable.getInstance().updateConfiguration(group, newConf); + }); + } + return result; + } + + @GetMapping("/cluster") + public MetadataResponse cluster(String group) { + MetadataResponse metadataResponse = new MetadataResponse(); + if (StringUtils.isBlank(group)) { + group = + ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + } + RaftServer raftServer = RaftServerFactory.getInstance().getRaftServer(group); + if (raftServer != null) { + String mode = ConfigurationFactory.getInstance().getConfig(STORE_MODE); + metadataResponse.setStoreMode(mode); + RouteTable routeTable = RouteTable.getInstance(); + try { + routeTable.refreshLeader(RaftServerFactory.getCliClientServiceInstance(), group, 1000); + PeerId leader = routeTable.selectLeader(group); + if (leader != null) { + Set nodes = new HashSet<>(); + RaftClusterMetadata raftClusterMetadata = raftServer.getRaftStateMachine().getRaftLeaderMetadata(); + Node leaderNode = raftServer.getRaftStateMachine().getRaftLeaderMetadata().getLeader(); + leaderNode.setGroup(group); + nodes.add(leaderNode); + nodes.addAll(raftClusterMetadata.getLearner()); + nodes.addAll(raftClusterMetadata.getFollowers()); + metadataResponse.setTerm(raftClusterMetadata.getTerm()); + metadataResponse.setNodes(new ArrayList<>(nodes)); + } + } catch (Exception e) { + LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); + } + } + return metadataResponse; + } + + @PostMapping("/watch") + public void watch(HttpServletRequest request, @RequestParam Map groupTerms, + @RequestParam(defaultValue = "28000") int timeout) { + AsyncContext context = request.startAsync(); + context.setTimeout(0L); + groupTerms.forEach((group, term) -> { + Watcher watcher = + new Watcher<>(group, context, timeout, Long.parseLong(String.valueOf(term))); + clusterWatcherManager.registryWatcher(watcher); + }); + } + +} diff --git a/server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java b/server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java index ad9bb30b0ad..90c11bb724b 100644 --- a/server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java +++ b/server/src/main/java/io/seata/server/coordinator/DefaultCoordinator.java @@ -17,6 +17,7 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; @@ -90,7 +91,7 @@ */ public class DefaultCoordinator extends AbstractTCInboundHandler implements TransactionMessageHandler, Disposable { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCoordinator.class); + protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultCoordinator.class); private static final int TIMED_TASK_SHUTDOWN_MAX_WAIT_MILLS = 5000; @@ -185,7 +186,7 @@ public class DefaultCoordinator extends AbstractTCInboundHandler implements Tran * * @param remotingServer the remoting server */ - private DefaultCoordinator(RemotingServer remotingServer) { + protected DefaultCoordinator(RemotingServer remotingServer) { if (remotingServer == null) { throw new IllegalArgumentException("RemotingServer not allowed be null."); } @@ -210,7 +211,9 @@ public static DefaultCoordinator getInstance(RemotingServer remotingServer) { if (null == instance) { synchronized (DefaultCoordinator.class) { if (null == instance) { - instance = new DefaultCoordinator(remotingServer); + StoreConfig.SessionMode storeMode = StoreConfig.getSessionMode(); + instance = Objects.equals(StoreConfig.SessionMode.RAFT, storeMode) + ? new RaftCoordinator(remotingServer) : new DefaultCoordinator(remotingServer); } } } diff --git a/server/src/main/java/io/seata/server/coordinator/DefaultCore.java b/server/src/main/java/io/seata/server/coordinator/DefaultCore.java index 9ddecea433d..19540af9bc2 100644 --- a/server/src/main/java/io/seata/server/coordinator/DefaultCore.java +++ b/server/src/main/java/io/seata/server/coordinator/DefaultCore.java @@ -269,10 +269,6 @@ public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) thr // if it succeeds and there is no branch, retrying=true is the asynchronous state when retrying. EndCommitted is // executed to improve concurrency performance, and the global transaction ends.. if (success && globalSession.getBranchSessions().isEmpty()) { - if (!retrying) { - //contains not AT branch - globalSession.setStatus(GlobalStatus.Committed); - } SessionHelper.endCommitted(globalSession, retrying); LOGGER.info("Committing global transaction is successfully done, xid = {}.", globalSession.getXid()); } diff --git a/server/src/main/java/io/seata/server/coordinator/RaftCoordinator.java b/server/src/main/java/io/seata/server/coordinator/RaftCoordinator.java new file mode 100644 index 00000000000..a205bbaad1b --- /dev/null +++ b/server/src/main/java/io/seata/server/coordinator/RaftCoordinator.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.coordinator; + +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.protocol.transaction.AbstractTransactionRequest; +import io.seata.core.protocol.transaction.AbstractTransactionResponse; +import io.seata.core.rpc.RemotingServer; +import io.seata.server.cluster.listener.ClusterChangeEvent; +import io.seata.server.cluster.raft.context.SeataClusterContext; +import io.seata.server.store.StoreConfig; +import org.springframework.context.ApplicationListener; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The type raft tx coordinator. + * @author funkye + */ +public class RaftCoordinator extends DefaultCoordinator implements ApplicationListener { + + protected static final Map GROUP_PREVENT = new ConcurrentHashMap<>(); + + public RaftCoordinator(RemotingServer remotingServer) { + super(remotingServer); + } + + @Override + public void exceptionHandleTemplate(Callback callback, T request, S response) { + String group = SeataClusterContext.bindGroup(); + try { + if (!isPass(group)) { + throw new TransactionException(TransactionExceptionCode.NotRaftLeader, + " The current TC is not a leader node, interrupt processing !"); + } + super.exceptionHandleTemplate(callback,request,response); + } catch (TransactionException tex) { + LOGGER.error("Catch TransactionException while do RPC, request: {}", request, tex); + callback.onTransactionException(request, response, tex); + } finally { + SeataClusterContext.unbindGroup(); + } + } + + private boolean isPass(String group) { + // Non-raft mode always allows requests + return Optional.ofNullable(GROUP_PREVENT.get(group)).orElse(false); + } + + public static void setPrevent(String group, boolean prevent) { + if (StoreConfig.getSessionMode() == StoreConfig.SessionMode.RAFT) { + GROUP_PREVENT.put(group, prevent); + } + } + + + @Override + public void onApplicationEvent(ClusterChangeEvent event) { + setPrevent(event.getGroup(), event.isLeader()); + } + +} diff --git a/server/src/main/java/io/seata/server/lock/LockerManagerFactory.java b/server/src/main/java/io/seata/server/lock/LockerManagerFactory.java index d7858237102..bca894c59e9 100644 --- a/server/src/main/java/io/seata/server/lock/LockerManagerFactory.java +++ b/server/src/main/java/io/seata/server/lock/LockerManagerFactory.java @@ -52,6 +52,7 @@ public static void init() { init(null); } + public static void init(LockMode lockMode) { if (LOCK_MANAGER == null) { synchronized (LockerManagerFactory.class) { diff --git a/server/src/main/java/io/seata/server/session/AbstractSessionManager.java b/server/src/main/java/io/seata/server/session/AbstractSessionManager.java index b1ad0f660f9..dc2f6a506d0 100644 --- a/server/src/main/java/io/seata/server/session/AbstractSessionManager.java +++ b/server/src/main/java/io/seata/server/session/AbstractSessionManager.java @@ -156,7 +156,7 @@ public void onSuccessEnd(GlobalSession globalSession) throws TransactionExceptio @Override public void onFailEnd(GlobalSession globalSession) throws TransactionException { - LOGGER.info("xid:{} fail end, transaction:{}",globalSession.getXid(),globalSession.toString()); + LOGGER.info("xid:{} fail end, transaction:{}", globalSession.getXid(), globalSession); } private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException { diff --git a/server/src/main/java/io/seata/server/session/GlobalSession.java b/server/src/main/java/io/seata/server/session/GlobalSession.java index a8ece6165bb..79cea914e5e 100644 --- a/server/src/main/java/io/seata/server/session/GlobalSession.java +++ b/server/src/main/java/io/seata/server/session/GlobalSession.java @@ -41,11 +41,13 @@ import io.seata.core.model.LockStatus; import io.seata.server.UUIDGenerator; import io.seata.server.lock.LockerManagerFactory; +import io.seata.server.cluster.raft.RaftServerFactory; import io.seata.server.store.SessionStorable; import io.seata.server.store.StoreConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import static io.seata.core.model.GlobalStatus.AsyncCommitting; import static io.seata.core.model.GlobalStatus.CommitRetrying; import static io.seata.core.model.GlobalStatus.Committing; @@ -103,6 +105,7 @@ public class GlobalSession implements SessionLifecycle, SessionStorable { private GlobalSessionLock globalSessionLock = new GlobalSessionLock(); + private Set lifecycleListeners = new HashSet<>(2); /** * Add boolean. @@ -131,7 +134,15 @@ public boolean remove(BranchSession branchSession) { } } - private Set lifecycleListeners = new HashSet<>(); + /** + * Remove boolean. + * + * @param branchId the long + * @return the boolean + */ + public boolean remove(Long branchId) { + return this.remove(this.getBranch(branchId)); + } /** * Can be committed async boolean. @@ -211,7 +222,7 @@ public void changeGlobalStatus(GlobalStatus status) throws TransactionException if (GlobalStatus.Rollbacking == status || GlobalStatus.TimeoutRollbacking == status) { LockerManagerFactory.getLockManager().updateLockStatus(xid, LockStatus.Rollbacking); } - SessionHolder.getRootSessionManager().updateGlobalSessionStatus(this, status); + SessionHolder.getRootSessionManager().onStatusChange(this, status); // set session status after update successfully this.status = status; for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { @@ -220,8 +231,7 @@ public void changeGlobalStatus(GlobalStatus status) throws TransactionException } @Override - public void changeBranchStatus(BranchSession branchSession, BranchStatus status) - throws TransactionException { + public void changeBranchStatus(BranchSession branchSession, BranchStatus status) throws TransactionException { SessionHolder.getRootSessionManager().onBranchStatusChange(this, branchSession, status); for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { lifecycleListener.onBranchStatusChange(this, branchSession, status); @@ -303,8 +313,9 @@ public void addBranch(BranchSession branchSession) throws TransactionException { for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { lifecycleListener.onAddBranch(this, branchSession); } - branchSession.setStatus(BranchStatus.Registered); - add(branchSession); + if (!RaftServerFactory.getInstance().isRaftMode()) { + add(branchSession); + } } public void loadBranchs() { @@ -323,9 +334,10 @@ public void loadBranchs() { public void unlockBranch(BranchSession branchSession) throws TransactionException { // do not unlock if global status in (Committing, CommitRetrying, AsyncCommitting), // because it's already unlocked in 'DefaultCore.commit()' - if (status != Committing && status != CommitRetrying && status != AsyncCommitting) { + if (this.status != Committing && this.status != CommitRetrying && this.status != AsyncCommitting) { if (!branchSession.unlock()) { - throw new TransactionException("Unlock branch lock failed, xid = " + this.xid + ", branchId = " + branchSession.getBranchId()); + throw new TransactionException( + "Unlock branch lock failed, xid = " + this.xid + ", branchId = " + branchSession.getBranchId()); } } } @@ -336,7 +348,11 @@ public void removeBranch(BranchSession branchSession) throws TransactionExceptio for (SessionLifecycleListener lifecycleListener : lifecycleListeners) { lifecycleListener.onRemoveBranch(this, branchSession); } - remove(branchSession); + + if (!RaftServerFactory.getInstance().isRaftMode()) { + this.remove(branchSession); + } + } @Override @@ -635,7 +651,6 @@ public byte[] encode() { } else { byteBuffer.putInt(0); } - byteBuffer.putLong(beginTime); byteBuffer.put((byte)status.getCode()); byteBuffer.flip(); @@ -646,7 +661,7 @@ public byte[] encode() { private int calGlobalSessionSize(byte[] byApplicationIdBytes, byte[] byServiceGroupBytes, byte[] byTxNameBytes, byte[] xidBytes, byte[] applicationDataBytes) { - final int size = 8 // transactionId + return 8 // transactionId + 4 // timeout + 2 // byApplicationIdBytes.length + 2 // byServiceGroupBytes.length @@ -660,7 +675,6 @@ private int calGlobalSessionSize(byte[] byApplicationIdBytes, byte[] byServiceGr + (byTxNameBytes == null ? 0 : byTxNameBytes.length) + (xidBytes == null ? 0 : xidBytes.length) + (applicationDataBytes == null ? 0 : applicationDataBytes.length); - return size; } @Override @@ -761,13 +775,11 @@ public List getBranchSessions() { } public void asyncCommit() throws TransactionException { - // [optimize-session-manager] add--> root manager.update - SessionHolder.getRootSessionManager().updateGlobalSessionStatus(this, GlobalStatus.AsyncCommitting); + changeGlobalStatus(GlobalStatus.AsyncCommitting); } public void queueToRetryCommit() throws TransactionException { - // [optimize-session-manager] add--> root manager.update - SessionHolder.getRootSessionManager().updateGlobalSessionStatus(this,GlobalStatus.CommitRetrying); + changeGlobalStatus(GlobalStatus.CommitRetrying); } public void queueToRetryRollback() throws TransactionException { @@ -778,8 +790,7 @@ public void queueToRetryRollback() throws TransactionException { } else { newStatus = GlobalStatus.RollbackRetrying; } - // [optimize-session-manager] add--> root manager.update - SessionHolder.getRootSessionManager().updateGlobalSessionStatus(this, newStatus); + changeGlobalStatus(newStatus); } public void setExpectedStatusFromCurrent() { diff --git a/server/src/main/java/io/seata/server/session/SessionHelper.java b/server/src/main/java/io/seata/server/session/SessionHelper.java index 511fa9a08d0..a798411ff25 100644 --- a/server/src/main/java/io/seata/server/session/SessionHelper.java +++ b/server/src/main/java/io/seata/server/session/SessionHelper.java @@ -26,16 +26,18 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import io.seata.common.ConfigurationKeys; import io.seata.common.util.CollectionUtils; import io.seata.config.Configuration; import io.seata.config.ConfigurationFactory; -import io.seata.core.constants.ConfigurationKeys; import io.seata.core.context.RootContext; import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; import io.seata.core.model.BranchType; import io.seata.core.model.GlobalStatus; import io.seata.metrics.IdConstants; import io.seata.server.UUIDGenerator; +import io.seata.server.cluster.raft.context.SeataClusterContext; import io.seata.server.coordinator.DefaultCoordinator; import io.seata.server.metrics.MetricsPublisher; import io.seata.server.store.StoreConfig; @@ -45,6 +47,7 @@ import org.slf4j.MDC; import static io.seata.common.DefaultValues.DEFAULT_ENABLE_BRANCH_ASYNC_REMOVE; +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** * The type Session helper. @@ -52,6 +55,7 @@ * @author sharajava */ public class SessionHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(SessionHelper.class); /** @@ -62,12 +66,16 @@ public class SessionHelper { private static final Boolean ENABLE_BRANCH_ASYNC_REMOVE = CONFIG.getBoolean( ConfigurationKeys.ENABLE_BRANCH_ASYNC_REMOVE, DEFAULT_ENABLE_BRANCH_ASYNC_REMOVE); + private static final String GROUP = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + /** * The instance of DefaultCoordinator */ private static final DefaultCoordinator COORDINATOR = DefaultCoordinator.getInstance(); - private static final boolean DELAY_HANDLE_SESSION = StoreConfig.getSessionMode() != SessionMode.FILE; + private static final boolean DELAY_HANDLE_SESSION = !(Objects.equals(StoreConfig.getSessionMode(), SessionMode.FILE) + || Objects.equals(StoreConfig.getSessionMode(), SessionMode.RAFT)); + private SessionHelper() { } @@ -97,6 +105,7 @@ public static BranchSession newBranchByGlobal(GlobalSession globalSession, Branc branchSession.setLockKey(lockKeys); branchSession.setClientId(clientId); branchSession.setApplicationData(applicationData); + branchSession.setStatus(BranchStatus.Registered); return branchSession; } @@ -132,8 +141,10 @@ public static void endCommitted(GlobalSession globalSession, boolean retryGlobal if (retryGlobal || !DELAY_HANDLE_SESSION) { long beginTime = System.currentTimeMillis(); boolean retryBranch = globalSession.getStatus() == GlobalStatus.CommitRetrying; - // TODO: If the globalSession status in the database is Committed, don't set status again - globalSession.changeGlobalStatus(GlobalStatus.Committed); + if (!globalSession.getStatus().equals(GlobalStatus.Committed)) { + // TODO: If the globalSession status in the database is Committed, don't set status again + globalSession.changeGlobalStatus(GlobalStatus.Committed); + } globalSession.end(); if (!DELAY_HANDLE_SESSION) { MetricsPublisher.postSessionDoneEvent(globalSession, false, false); @@ -141,8 +152,8 @@ public static void endCommitted(GlobalSession globalSession, boolean retryGlobal MetricsPublisher.postSessionDoneEvent(globalSession, IdConstants.STATUS_VALUE_AFTER_COMMITTED_KEY, true, beginTime, retryBranch); } else { + globalSession.setStatus(GlobalStatus.Committed); if (globalSession.isSaga()) { - globalSession.setStatus(GlobalStatus.Committed); globalSession.end(); } MetricsPublisher.postSessionDoneEvent(globalSession, false, false); @@ -200,9 +211,10 @@ public static void endRollbacked(GlobalSession globalSession, boolean retryGloba } boolean retryBranch = currentStatus == GlobalStatus.TimeoutRollbackRetrying || currentStatus == GlobalStatus.RollbackRetrying; - if (SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) { + if (!currentStatus.equals(GlobalStatus.TimeoutRollbacked) + && SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) { globalSession.changeGlobalStatus(GlobalStatus.TimeoutRollbacked); - } else { + } else if (!globalSession.getStatus().equals(GlobalStatus.Rollbacked)) { globalSession.changeGlobalStatus(GlobalStatus.Rollbacked); } globalSession.end(); @@ -284,14 +296,17 @@ public static void forEach(Collection sessions, GlobalSessionHand if (CollectionUtils.isEmpty(sessions)) { return; } + Stream stream = StreamSupport.stream(sessions.spliterator(), parallel); stream.forEach(globalSession -> { + SeataClusterContext.bindGroup(GROUP); try { MDC.put(RootContext.MDC_KEY_XID, globalSession.getXid()); handler.handle(globalSession); } catch (Throwable th) { LOGGER.error("handle global session failed: {}", globalSession.getXid(), th); } finally { + SeataClusterContext.unbindGroup(); MDC.remove(RootContext.MDC_KEY_XID); } }); diff --git a/server/src/main/java/io/seata/server/session/SessionHolder.java b/server/src/main/java/io/seata/server/session/SessionHolder.java index 7491f2664a0..2ebc3b5e44d 100644 --- a/server/src/main/java/io/seata/server/session/SessionHolder.java +++ b/server/src/main/java/io/seata/server/session/SessionHolder.java @@ -17,13 +17,17 @@ import java.io.IOException; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import io.seata.common.ConfigurationKeys; +import io.seata.common.exception.StoreException; +import io.seata.core.model.LockStatus; import io.seata.common.XID; import io.seata.common.exception.ShouldNeverHappenException; -import io.seata.common.exception.StoreException; import io.seata.common.loader.EnhancedServiceLoader; import io.seata.common.util.CollectionUtils; import io.seata.common.util.StringUtils; @@ -31,16 +35,22 @@ import io.seata.config.ConfigurationFactory; import io.seata.core.exception.TransactionException; import io.seata.core.model.GlobalStatus; -import io.seata.core.model.LockStatus; import io.seata.core.store.DistributedLockDO; import io.seata.core.store.DistributedLocker; +import io.seata.server.cluster.raft.context.SeataClusterContext; import io.seata.server.lock.distributed.DistributedLockerFactory; +import io.seata.server.cluster.raft.RaftServer; +import io.seata.server.cluster.raft.RaftServerFactory; import io.seata.server.store.StoreConfig; import io.seata.server.store.StoreConfig.SessionMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; +import static java.io.File.separator; import static io.seata.common.DefaultValues.DEFAULT_DISTRIBUTED_LOCK_EXPIRE_TIME; +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; +import static io.seata.core.constants.ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL; /** * The type Session holder. @@ -55,27 +65,11 @@ public class SessionHolder { * The constant CONFIG. */ protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); + /** * The constant ROOT_SESSION_MANAGER_NAME. */ public static final String ROOT_SESSION_MANAGER_NAME = "root.data"; - /** - * The constant ASYNC_COMMITTING_SESSION_MANAGER_NAME. - */ - public static final String ASYNC_COMMITTING_SESSION_MANAGER_NAME = "async.commit.data"; - /** - * The constant RETRY_COMMITTING_SESSION_MANAGER_NAME. - */ - public static final String RETRY_COMMITTING_SESSION_MANAGER_NAME = "retry.commit.data"; - /** - * The constant RETRY_ROLLBACKING_SESSION_MANAGER_NAME. - */ - public static final String RETRY_ROLLBACKING_SESSION_MANAGER_NAME = "retry.rollback.data"; - - /** - * The default session store dir - */ - public static final String DEFAULT_SESSION_STORE_FILE_DIR = "sessionStore"; /** * The redis distributed lock expire time @@ -83,6 +77,7 @@ public class SessionHolder { private static long DISTRIBUTED_LOCK_EXPIRE_TIME = CONFIG.getLong(ConfigurationKeys.DISTRIBUTED_LOCK_EXPIRE_TIME, DEFAULT_DISTRIBUTED_LOCK_EXPIRE_TIME); private static SessionManager ROOT_SESSION_MANAGER; + private static volatile Map SESSION_MANAGER_MAP; private static DistributedLocker DISTRIBUTED_LOCKER; @@ -100,83 +95,136 @@ public static void init(SessionMode sessionMode) { sessionMode = StoreConfig.getSessionMode(); } LOGGER.info("use session store mode: {}", sessionMode.getName()); + DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(sessionMode.getName()); if (SessionMode.DB.equals(sessionMode)) { ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, SessionMode.DB.getName()); - - DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(SessionMode.DB.getName()); - } else if (SessionMode.FILE.equals(sessionMode)) { - String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, - DEFAULT_SESSION_STORE_FILE_DIR); - if (StringUtils.isBlank(sessionStorePath)) { - throw new StoreException("the {store.file.dir} is empty."); + reload(sessionMode); + } else if (SessionMode.RAFT.equals(sessionMode) || SessionMode.FILE.equals(sessionMode)) { + RaftServerFactory.getInstance().init(); + if (CollectionUtils.isNotEmpty(RaftServerFactory.getInstance().getRaftServers())) { + sessionMode = SessionMode.RAFT; + } + if (SessionMode.RAFT.equals(sessionMode)) { + String group = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, SessionMode.RAFT.getName(), + new Object[] {ROOT_SESSION_MANAGER_NAME}); + SESSION_MANAGER_MAP = new HashMap<>(); + SESSION_MANAGER_MAP.put(group, ROOT_SESSION_MANAGER); + RaftServerFactory.getInstance().start(); + } else { + String sessionStorePath = + CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR) + separator + + System.getProperty(SERVER_SERVICE_PORT_CAMEL); + if (StringUtils.isBlank(sessionStorePath)) { + throw new StoreException("the {store.file.dir} is empty."); + } + ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, SessionMode.FILE.getName(), + new Object[] {ROOT_SESSION_MANAGER_NAME, sessionStorePath}); + reload(sessionMode); } - ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, SessionMode.FILE.getName(), - new Object[]{ROOT_SESSION_MANAGER_NAME, sessionStorePath}); - - DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(SessionMode.FILE.getName()); } else if (SessionMode.REDIS.equals(sessionMode)) { ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, SessionMode.REDIS.getName()); - - DISTRIBUTED_LOCKER = DistributedLockerFactory.getDistributedLocker(SessionMode.REDIS.getName()); + reload(sessionMode); } else { // unknown store throw new IllegalArgumentException("unknown store mode:" + sessionMode.getName()); } - reload(sessionMode); } - //region reload - /** * Reload. * * @param sessionMode the mode of store */ protected static void reload(SessionMode sessionMode) { - - if (ROOT_SESSION_MANAGER instanceof Reloadable) { - ((Reloadable) ROOT_SESSION_MANAGER).reload(); + if (sessionMode == SessionMode.FILE) { + ((Reloadable)ROOT_SESSION_MANAGER).reload(); + reload(ROOT_SESSION_MANAGER.allSessions(), sessionMode); + } else { + reload(null, sessionMode); } + } + + public static void reload(Collection allSessions, SessionMode storeMode) { + reload(allSessions, storeMode, true); + } - if (SessionMode.FILE.equals(sessionMode)) { - Collection allSessions = ROOT_SESSION_MANAGER.allSessions(); - if (CollectionUtils.isNotEmpty(allSessions)) { - for (GlobalSession globalSession : allSessions) { - GlobalStatus globalStatus = globalSession.getStatus(); - switch (globalStatus) { - case UnKnown: - case Committed: - case CommitFailed: - case Rollbacked: - case RollbackFailed: - case TimeoutRollbacked: - case TimeoutRollbackFailed: - case Finished: - removeInErrorState(globalSession); - break; - case AsyncCommitting: - case Committing: - case CommitRetrying: - break; - default: { + public static void reload(Collection allSessions, SessionMode storeMode, boolean acquireLock) { + if ((SessionMode.FILE == storeMode || SessionMode.RAFT == storeMode) + && CollectionUtils.isNotEmpty(allSessions)) { + for (GlobalSession globalSession : allSessions) { + GlobalStatus globalStatus = globalSession.getStatus(); + switch (globalStatus) { + case TimeoutRollbacked: + case Rollbacked: + try { + SessionHelper.endRollbacked(globalSession, true); + } catch (TransactionException e) { + LOGGER.error("Could not handle the global session, xid: {},error: {}", + globalSession.getXid(), e.getMessage()); + } + break; + case Committed: + try { + SessionHelper.endCommitted(globalSession, true); + } catch (TransactionException e) { + LOGGER.error("Could not handle the global session, xid: {},error: {}", + globalSession.getXid(), e.getMessage()); + } + break; + case Finished: + case UnKnown: + case CommitFailed: + case RollbackFailed: + case TimeoutRollbackFailed: + removeInErrorState(globalSession); + break; + case AsyncCommitting: + case Committing: + case CommitRetrying: + if (Objects.equals(SessionMode.RAFT, storeMode)) { + // When a state change occurs, re-electing the leader may result in the lock not being unlocked yet + // so a COMMIT unlock operation needs to be performed at the time of re-election + try { + globalSession.clean(); + } catch (TransactionException e) { + throw new RuntimeException(e); + } + } + break; + default: { + if (acquireLock) { lockBranchSessions(globalSession.getSortedBranches()); - switch (globalStatus) { - case Rollbacking: - case RollbackRetrying: - case TimeoutRollbacking: - case TimeoutRollbackRetrying: - globalSession.getBranchSessions().parallelStream() - .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); - break; - case Begin: - globalSession.setActive(true); - break; - default: - LOGGER.error("Could not handle the global session, xid: {}", globalSession.getXid()); - throw new ShouldNeverHappenException("NOT properly handled " + globalStatus); + if (GlobalStatus.Rollbacking.equals(globalSession.getStatus()) + || GlobalStatus.TimeoutRollbacking.equals(globalSession.getStatus())) { + globalSession.getBranchSessions().parallelStream() + .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); } - break; } + switch (globalStatus) { + case Rollbacking: + case RollbackRetrying: + case TimeoutRollbacking: + case TimeoutRollbackRetrying: + break; + case Begin: + if (Objects.equals(storeMode, SessionMode.RAFT)) { + try { + globalSession.changeGlobalStatus(GlobalStatus.RollbackRetrying); + LOGGER.info("change global status: {}, xid: {}", globalSession.getStatus(), + globalSession.getXid()); + } catch (TransactionException e) { + LOGGER.error("change global status fail: {}", e.getMessage(), e); + } + } else { + globalSession.setActive(true); + } + break; + default: + LOGGER.error("Could not handle the global session, xid: {}", globalSession.getXid()); + throw new ShouldNeverHappenException("NOT properly handled " + globalStatus); + } + break; } } } @@ -209,7 +257,7 @@ protected static void reload(SessionMode sessionMode) { private static void removeInErrorState(GlobalSession globalSession) { try { LOGGER.warn("The global session should NOT be {}, remove it. xid = {}", globalSession.getStatus(), globalSession.getXid()); - ROOT_SESSION_MANAGER.removeGlobalSession(globalSession); + getRootSessionManager().removeGlobalSession(globalSession); if (LOGGER.isInfoEnabled()) { LOGGER.info("Remove global session succeed, xid = {}, status = {}", globalSession.getXid(), globalSession.getStatus()); } @@ -241,10 +289,13 @@ private static void lockBranchSessions(List branchSessions) { * @return the root session manager */ public static SessionManager getRootSessionManager() { - if (ROOT_SESSION_MANAGER == null) { - throw new ShouldNeverHappenException("SessionManager is NOT init!"); - } - return ROOT_SESSION_MANAGER; + String group = SeataClusterContext.getGroup(); + return getRootSessionManager(group); + } + + public static SessionManager getRootSessionManager(String group) { + return StringUtils.isNotBlank(group) && SESSION_MANAGER_MAP != null + ? SESSION_MANAGER_MAP.computeIfAbsent(group, k -> ROOT_SESSION_MANAGER) : ROOT_SESSION_MANAGER; } //endregion @@ -329,6 +380,14 @@ public static boolean distributedLockAndExecute(String key, NoArgsFunc func) { } public static void destroy() { + Collection raftServers = RaftServerFactory.getInstance().getRaftServers(); + if (raftServers != null) { + try { + raftServers.forEach(RaftServer::close); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } if (ROOT_SESSION_MANAGER != null) { ROOT_SESSION_MANAGER.destroy(); } diff --git a/server/src/main/java/io/seata/server/session/SessionLifecycleListener.java b/server/src/main/java/io/seata/server/session/SessionLifecycleListener.java index 693d5b695b0..156f09df564 100644 --- a/server/src/main/java/io/seata/server/session/SessionLifecycleListener.java +++ b/server/src/main/java/io/seata/server/session/SessionLifecycleListener.java @@ -95,4 +95,5 @@ void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSessi * @throws TransactionException the transaction exception */ void onFailEnd(GlobalSession globalSession) throws TransactionException; + } diff --git a/server/src/main/java/io/seata/server/spring/listener/ServerApplicationListener.java b/server/src/main/java/io/seata/server/spring/listener/ServerApplicationListener.java index f590b66b43b..0ee1b9588d1 100644 --- a/server/src/main/java/io/seata/server/spring/listener/ServerApplicationListener.java +++ b/server/src/main/java/io/seata/server/spring/listener/ServerApplicationListener.java @@ -105,7 +105,6 @@ public void onApplicationEvent(ApplicationEvent event) { private void setTargetPort(ConfigurableEnvironment environment, String port, boolean needAddPropertySource) { // get rpc port first, use to logback-spring.xml, @see the class named `SystemPropertyLoggerContextListener` System.setProperty(SERVER_SERVICE_PORT_CAMEL, port); - if (needAddPropertySource) { // add property source to the first position Properties pro = new Properties(); diff --git a/server/src/main/java/io/seata/server/storage/SessionConverter.java b/server/src/main/java/io/seata/server/storage/SessionConverter.java index 7c302c4a4f6..030b1c64c07 100644 --- a/server/src/main/java/io/seata/server/storage/SessionConverter.java +++ b/server/src/main/java/io/seata/server/storage/SessionConverter.java @@ -22,6 +22,7 @@ import java.util.Collections; import io.seata.common.util.CollectionUtils; import io.seata.common.util.StringUtils; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; import io.seata.server.console.vo.BranchSessionVO; import io.seata.server.console.vo.GlobalSessionVO; import io.seata.core.model.BranchStatus; @@ -76,17 +77,25 @@ public static BranchSession convertBranchSession(BranchTransactionDO branchTrans branchSession.setClientId(branchTransactionDO.getClientId()); branchSession.setResourceGroupId(branchTransactionDO.getResourceGroupId()); branchSession.setStatus(BranchStatus.get(branchTransactionDO.getStatus())); + if (branchTransactionDO instanceof BranchTransactionDTO) { + branchSession.setLockKey(((BranchTransactionDTO)branchTransactionDO).getLockKey()); + } return branchSession; } public static GlobalTransactionDO convertGlobalTransactionDO(SessionStorable session) { + GlobalTransactionDO globalTransactionDO = new GlobalTransactionDO(); + convertGlobalTransactionDO(globalTransactionDO, session); + return globalTransactionDO; + } + + public static void convertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO, + SessionStorable session) { if (!(session instanceof GlobalSession)) { throw new IllegalArgumentException( - "The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session)); + "The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session)); } GlobalSession globalSession = (GlobalSession)session; - - GlobalTransactionDO globalTransactionDO = new GlobalTransactionDO(); globalTransactionDO.setXid(globalSession.getXid()); globalTransactionDO.setStatus(globalSession.getStatus().getCode()); globalTransactionDO.setApplicationId(globalSession.getApplicationId()); @@ -96,7 +105,6 @@ public static GlobalTransactionDO convertGlobalTransactionDO(SessionStorable ses globalTransactionDO.setTransactionName(globalSession.getTransactionName()); globalTransactionDO.setTransactionServiceGroup(globalSession.getTransactionServiceGroup()); globalTransactionDO.setApplicationData(globalSession.getApplicationData()); - return globalTransactionDO; } public static BranchTransactionDO convertBranchTransactionDO(SessionStorable session) { @@ -104,8 +112,23 @@ public static BranchTransactionDO convertBranchTransactionDO(SessionStorable ses throw new IllegalArgumentException( "The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session)); } - BranchSession branchSession = (BranchSession)session; BranchTransactionDO branchTransactionDO = new BranchTransactionDO(); + convertBranchTransaction(branchTransactionDO, session); + return branchTransactionDO; + } + + public static BranchTransactionDTO convertBranchTransactionDTO(SessionStorable session) { + if (!(session instanceof BranchSession)) { + throw new IllegalArgumentException( + "The parameter of SessionStorable is not available, SessionStorable:" + StringUtils.toString(session)); + } + BranchTransactionDTO branchTransactionDTO = new BranchTransactionDTO(); + convertBranchTransaction(branchTransactionDTO, session); + return branchTransactionDTO; + } + + public static void convertBranchTransaction(BranchTransactionDO branchTransactionDO, SessionStorable session) { + BranchSession branchSession = (BranchSession)session; branchTransactionDO.setXid(branchSession.getXid()); branchTransactionDO.setBranchId(branchSession.getBranchId()); branchTransactionDO.setBranchType(branchSession.getBranchType().name()); @@ -115,7 +138,9 @@ public static BranchTransactionDO convertBranchTransactionDO(SessionStorable ses branchTransactionDO.setApplicationData(branchSession.getApplicationData()); branchTransactionDO.setResourceId(branchSession.getResourceId()); branchTransactionDO.setStatus(branchSession.getStatus().getCode()); - return branchTransactionDO; + if (branchTransactionDO instanceof BranchTransactionDTO) { + ((BranchTransactionDTO)branchTransactionDO).setLockKey(branchSession.getLockKey()); + } } public static void convertToGlobalSessionVo(List result, List globalSessions) { diff --git a/server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java b/server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java index 37f8e2df071..8b8e4e7a307 100644 --- a/server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java +++ b/server/src/main/java/io/seata/server/storage/file/lock/FileLockManager.java @@ -23,6 +23,7 @@ import io.seata.server.lock.AbstractLockManager; import io.seata.server.session.BranchSession; import io.seata.server.session.GlobalSession; +import io.seata.server.storage.raft.lock.RaftLockManager; import org.slf4j.MDC; import static io.seata.core.context.RootContext.MDC_KEY_BRANCH_ID; @@ -47,9 +48,8 @@ public boolean releaseGlobalSessionLock(GlobalSession globalSession) throws Tran for (BranchSession branchSession : branchSessions) { try { MDC.put(MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId())); - if (!this.releaseLock(branchSession)) { - releaseLockResult = false; - } + releaseLockResult = this instanceof RaftLockManager ? super.releaseLock(branchSession) + : this.releaseLock(branchSession); } finally { MDC.remove(MDC_KEY_BRANCH_ID); } diff --git a/server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java b/server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java index 5e28d3af0f4..ebf41517176 100644 --- a/server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java +++ b/server/src/main/java/io/seata/server/storage/file/lock/FileLocker.java @@ -31,7 +31,6 @@ import io.seata.core.model.LockStatus; import io.seata.server.session.BranchSession; - import static io.seata.core.exception.TransactionExceptionCode.LockKeyConflictFailFast; /** diff --git a/server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java b/server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java index 7a95f70bd77..453a62e5cb1 100644 --- a/server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java +++ b/server/src/main/java/io/seata/server/storage/file/session/FileSessionManager.java @@ -17,7 +17,6 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -27,6 +26,7 @@ import java.util.Arrays; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import io.seata.common.exception.ShouldNeverHappenException; import io.seata.common.loader.LoadLevel; @@ -62,10 +62,27 @@ public class FileSessionManager extends AbstractSessionManager implements Reload private static final int READ_SIZE = ConfigurationFactory.getInstance().getInt( ConfigurationKeys.SERVICE_SESSION_RELOAD_READ_SIZE, DEFAULT_SERVICE_SESSION_RELOAD_READ_SIZE); + /** * The Session map. */ - private Map sessionMap = new ConcurrentHashMap<>(); + private Map sessionMap = new ConcurrentHashMap<>(64); + + + /** + * Instantiates a new File based session manager. + * + * @param name the name + */ + public FileSessionManager(String name) { + super(name); + transactionStoreManager = new AbstractTransactionStoreManager() { + @Override + public boolean writeSession(LogOperation logOperation, SessionStorable session) { + return true; + } + }; + } /** * Instantiates a new File based session manager. @@ -77,8 +94,8 @@ public class FileSessionManager extends AbstractSessionManager implements Reload public FileSessionManager(String name, String sessionStoreFilePath) throws IOException { super(name); if (StringUtils.isNotBlank(sessionStoreFilePath)) { - transactionStoreManager = new FileTransactionStoreManager( - sessionStoreFilePath + File.separator + name, this); + transactionStoreManager = + new FileTransactionStoreManager(sessionStoreFilePath + File.separator + name, this); } else { transactionStoreManager = new AbstractTransactionStoreManager() { @Override @@ -131,49 +148,36 @@ public Collection allSessions() { @Override public List findGlobalSessions(SessionCondition condition) { - List found = new ArrayList<>(); - List globalStatuses = null; if (null != condition.getStatuses() && condition.getStatuses().length > 0) { globalStatuses = Arrays.asList(condition.getStatuses()); } - for (GlobalSession globalSession : sessionMap.values()) { + Collection list = sessionMap.values(); + List finalGlobalStatuses = globalStatuses; + return list.parallelStream().filter(globalSession -> { + if (null != condition.getOverTimeAliveMills() && condition.getOverTimeAliveMills() > 0) { if (System.currentTimeMillis() - globalSession.getBeginTime() <= condition.getOverTimeAliveMills()) { - continue; + return false; } } if (!StringUtils.isEmpty(condition.getXid())) { - if (Objects.equals(condition.getXid(), globalSession.getXid())) { - // Only one will be found, just add and return - found.add(globalSession); - return found; - } else { - continue; - } + // Only one will be found, just add and return + return Objects.equals(condition.getXid(), globalSession.getXid()); } if (null != condition.getTransactionId() && condition.getTransactionId() > 0) { - if (Objects.equals(condition.getTransactionId(), globalSession.getTransactionId())) { - // Only one will be found, just add and return - found.add(globalSession); - return found; - } else { - continue; - } + // Only one will be found, just add and return + return Objects.equals(condition.getTransactionId(), globalSession.getTransactionId()); } - if (null != globalStatuses) { - if (!globalStatuses.contains(globalSession.getStatus())) { - continue; - } + if (null != finalGlobalStatuses) { + return finalGlobalStatuses.contains(globalSession.getStatus()); } - // All test pass, add to resp - found.add(globalSession); - } - return found; + return true; + }).collect(Collectors.toList()); } @Override @@ -374,6 +378,14 @@ private void restore(List stores, Set removedGlob } + public Map getSessionMap() { + return sessionMap; + } + + public void setSessionMap(Map sessionMap) { + this.sessionMap = sessionMap; + } + @Override public void destroy() { transactionStoreManager.shutdown(); diff --git a/server/src/main/java/io/seata/server/storage/raft/lock/RaftDistributedLocker.java b/server/src/main/java/io/seata/server/storage/raft/lock/RaftDistributedLocker.java new file mode 100644 index 00000000000..2ffd3b91917 --- /dev/null +++ b/server/src/main/java/io/seata/server/storage/raft/lock/RaftDistributedLocker.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.storage.raft.lock; + +import io.seata.common.ConfigurationKeys; +import io.seata.common.loader.LoadLevel; +import io.seata.config.ConfigurationFactory; +import io.seata.server.cluster.raft.RaftServerFactory; +import io.seata.core.store.DistributedLockDO; +import io.seata.core.store.DistributedLocker; +import io.seata.server.storage.redis.lock.RedisDistributedLocker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +/** + * @description raft distributed lock + * @author funkye + */ +@LoadLevel(name = "raft") +public class RaftDistributedLocker implements DistributedLocker { + + protected static final Logger LOGGER = LoggerFactory.getLogger( + RedisDistributedLocker.class); + + private final String group = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + + /** + * Acquire the distributed lock + * + * @param distributedLockDO distributedLockDO + * @return boolean + */ + @Override + public boolean acquireLock(DistributedLockDO distributedLockDO) { + return RaftServerFactory.getInstance().isLeader(group); + } + + /** + * Release the distributed lock + * + * @param distributedLockDO distributedLockDO + * @return boolean + */ + @Override + public boolean releaseLock(DistributedLockDO distributedLockDO) { + return true; + } + +} diff --git a/server/src/main/java/io/seata/server/storage/raft/lock/RaftLockManager.java b/server/src/main/java/io/seata/server/storage/raft/lock/RaftLockManager.java new file mode 100644 index 00000000000..605ebdb0c32 --- /dev/null +++ b/server/src/main/java/io/seata/server/storage/raft/lock/RaftLockManager.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.storage.raft.lock; + +import java.util.concurrent.CompletableFuture; +import com.alipay.sofa.jraft.Closure; +import io.seata.common.loader.LoadLevel; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.storage.file.lock.FileLockManager; +import io.seata.server.cluster.raft.util.RaftTaskUtil; + +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.RELEASE_BRANCH_SESSION_LOCK; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.RELEASE_GLOBAL_SESSION_LOCK; +/** + * @author funkye + */ +@LoadLevel(name = "raft") +public class RaftLockManager extends FileLockManager { + + @Override + public boolean releaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException { + GlobalTransactionDTO globalTransactionDTO = new GlobalTransactionDTO(); + globalTransactionDTO.setXid(globalSession.getXid()); + RaftGlobalSessionSyncMsg raftSyncMsg = new RaftGlobalSessionSyncMsg(RELEASE_GLOBAL_SESSION_LOCK, globalTransactionDTO); + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = status -> { + if (status.isOk()) { + try { + completableFuture.complete(this.localReleaseGlobalSessionLock(globalSession)); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } else { + completableFuture.completeExceptionally(new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + status.getErrorMsg())); + } + }; + return RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public boolean releaseLock(BranchSession branchSession) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + BranchTransactionDTO branchTransactionDTO = new BranchTransactionDTO(); + branchTransactionDTO.setBranchId(branchSession.getBranchId()); + branchTransactionDTO.setXid(branchSession.getXid()); + RaftBranchSessionSyncMsg raftSyncMsg = new RaftBranchSessionSyncMsg(RELEASE_BRANCH_SESSION_LOCK, branchTransactionDTO); + Closure closure = status -> { + if (status.isOk()) { + try { + // ensure consistency through state machine reading + completableFuture.complete(super.releaseLock(branchSession)); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } else { + completableFuture.completeExceptionally(new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + status.getErrorMsg())); + } + }; + return RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + public boolean localReleaseGlobalSessionLock(GlobalSession globalSession) throws TransactionException { + return super.releaseGlobalSessionLock(globalSession); + } + + public boolean localReleaseLock(BranchSession branchSession) throws TransactionException { + return super.releaseLock(branchSession); + } + +} diff --git a/server/src/main/java/io/seata/server/storage/raft/session/RaftSessionManager.java b/server/src/main/java/io/seata/server/storage/raft/session/RaftSessionManager.java new file mode 100644 index 00000000000..c20f6fd8d08 --- /dev/null +++ b/server/src/main/java/io/seata/server/storage/raft/session/RaftSessionManager.java @@ -0,0 +1,226 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.storage.raft.session; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import com.alipay.sofa.jraft.Closure; +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.GlobalStatus; +import io.seata.core.model.LockStatus; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; +import io.seata.server.session.BranchSession; +import io.seata.server.session.GlobalSession; +import io.seata.server.storage.SessionConverter; +import io.seata.server.storage.file.session.FileSessionManager; +import io.seata.server.cluster.raft.util.RaftTaskUtil; + +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_BRANCH_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_GLOBAL_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REMOVE_BRANCH_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REMOVE_GLOBAL_SESSION; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.UPDATE_BRANCH_SESSION_STATUS; +import static io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.UPDATE_GLOBAL_SESSION_STATUS; + +/** + * @author funkye + */ +@LoadLevel(name = "raft", scope = Scope.PROTOTYPE) +public class RaftSessionManager extends FileSessionManager { + + public RaftSessionManager(String name) throws IOException { + super(name); + } + + @Override + public void addGlobalSession(GlobalSession globalSession) throws TransactionException { + super.addGlobalSession(globalSession); + } + + @Override + public GlobalSession findGlobalSession(String xid) { + return super.findGlobalSession(xid); + } + + @Override + public void onBegin(GlobalSession globalSession) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = status -> { + if (status.isOk()) { + try { + super.addGlobalSession(globalSession); + completableFuture.complete(true); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } else { + try { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + status.getErrorMsg())); + } finally { + try { + super.removeGlobalSession(globalSession); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } + } + }; + GlobalTransactionDTO globalTransactionDTO = new GlobalTransactionDTO(); + SessionConverter.convertGlobalTransactionDO(globalTransactionDTO, globalSession); + RaftGlobalSessionSyncMsg raftSyncMsg = new RaftGlobalSessionSyncMsg(ADD_GLOBAL_SESSION, globalTransactionDTO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onStatusChange(GlobalSession globalSession, GlobalStatus globalStatus) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = closureStatus -> { + if (closureStatus.isOk()) { + globalSession.setStatus(globalStatus); + if (GlobalStatus.RollbackRetrying.equals(globalSession.getStatus()) + || GlobalStatus.Rollbacking.equals(globalSession.getStatus()) + || GlobalStatus.TimeoutRollbacking.equals(globalSession.getStatus())) { + globalSession.getBranchSessions().parallelStream() + .forEach(branchSession -> branchSession.setLockStatus(LockStatus.Rollbacking)); + } + completableFuture.complete(true); + } else { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + closureStatus.getErrorMsg())); + } + }; + GlobalTransactionDTO globalTransactionDO = new GlobalTransactionDTO(globalSession.getXid()); + globalTransactionDO.setStatus(globalStatus.getCode()); + RaftGlobalSessionSyncMsg raftSyncMsg = + new RaftGlobalSessionSyncMsg(UPDATE_GLOBAL_SESSION_STATUS, globalTransactionDO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onBranchStatusChange(GlobalSession globalSession, BranchSession branchSession, + BranchStatus branchStatus) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = closureStatus -> { + if (closureStatus.isOk()) { + branchSession.setStatus(branchStatus); + completableFuture.complete(true); + } else { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + closureStatus.getErrorMsg())); + } + }; + BranchTransactionDTO branchTransactionDO = new BranchTransactionDTO(globalSession.getXid(), branchSession.getBranchId()); + branchTransactionDO.setStatus(branchStatus.getCode()); + RaftBranchSessionSyncMsg raftSyncMsg = + new RaftBranchSessionSyncMsg(UPDATE_BRANCH_SESSION_STATUS, branchTransactionDO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onAddBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + branchSession.setStatus(BranchStatus.Registered); + Closure closure = status -> { + if (status.isOk()) { + completableFuture.complete(globalSession.add(branchSession)); + } else { + try { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + status.getErrorMsg())); + } finally { + try { + globalSession.removeBranch(branchSession); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } + } + }; + BranchTransactionDTO branchTransactionDTO = new BranchTransactionDTO(); + SessionConverter.convertBranchTransaction(branchTransactionDTO, branchSession); + RaftBranchSessionSyncMsg raftSyncMsg = new RaftBranchSessionSyncMsg(ADD_BRANCH_SESSION, branchTransactionDTO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onRemoveBranch(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = closureStatus -> { + if (closureStatus.isOk()) { + completableFuture.complete(globalSession.remove(branchSession)); + } else { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + closureStatus.getErrorMsg())); + } + }; + BranchTransactionDTO branchTransactionDO = + new BranchTransactionDTO(globalSession.getXid(), branchSession.getBranchId()); + RaftBranchSessionSyncMsg raftSyncMsg = new RaftBranchSessionSyncMsg(REMOVE_BRANCH_SESSION, branchTransactionDO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onSuccessEnd(GlobalSession globalSession) throws TransactionException { + CompletableFuture completableFuture = new CompletableFuture<>(); + Closure closure = status -> { + if (status.isOk()) { + try { + super.removeGlobalSession(globalSession); + completableFuture.complete(true); + } catch (TransactionException e) { + completableFuture.completeExceptionally(e); + } + } else { + completableFuture.completeExceptionally( + new TransactionException(TransactionExceptionCode.NotRaftLeader, + "seata raft state machine exception: " + status.getErrorMsg())); + } + }; + GlobalTransactionDTO globalTransactionDO = new GlobalTransactionDTO(globalSession.getXid()); + RaftGlobalSessionSyncMsg raftSyncMsg = new RaftGlobalSessionSyncMsg(REMOVE_GLOBAL_SESSION, globalTransactionDO); + RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); + } + + @Override + public void onFailEnd(GlobalSession globalSession) throws TransactionException { + super.onFailEnd(globalSession); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public void destroy() {} + +} diff --git a/server/src/main/java/io/seata/server/store/StoreConfig.java b/server/src/main/java/io/seata/server/store/StoreConfig.java index 362c7992e1d..c125cc17c33 100644 --- a/server/src/main/java/io/seata/server/store/StoreConfig.java +++ b/server/src/main/java/io/seata/server/store/StoreConfig.java @@ -155,7 +155,11 @@ public enum StoreMode { /** * The Redis store mode. */ - REDIS("redis"); + REDIS("redis"), + /** + * The Raft store mode. + */ + RAFT("raft"); private String name; @@ -163,10 +167,6 @@ public enum StoreMode { this.name = name; } - public String getName() { - return name; - } - public static StoreMode get(String name) { for (StoreMode mode : StoreMode.values()) { if (mode.getName().equalsIgnoreCase(name)) { @@ -175,6 +175,10 @@ public static StoreMode get(String name) { } throw new IllegalArgumentException("unknown store mode:" + name); } + + public String getName() { + return name; + } } public enum SessionMode { @@ -189,7 +193,11 @@ public enum SessionMode { /** * The Redis store mode. */ - REDIS("redis"); + REDIS("redis"), + /** + * raft store + */ + RAFT("raft"); private String name; @@ -197,10 +205,6 @@ public enum SessionMode { this.name = name; } - public String getName() { - return name; - } - public static SessionMode get(String name) { for (SessionMode mode : SessionMode.values()) { if (mode.getName().equalsIgnoreCase(name)) { @@ -209,6 +213,10 @@ public static SessionMode get(String name) { } throw new IllegalArgumentException("unknown session mode:" + name); } + + public String getName() { + return name; + } } public enum LockMode { @@ -223,7 +231,11 @@ public enum LockMode { /** * The Redis store mode. */ - REDIS("redis"); + REDIS("redis"), + /** + * raft store + */ + RAFT("raft"); private String name; @@ -231,10 +243,6 @@ public enum LockMode { this.name = name; } - public String getName() { - return name; - } - public static LockMode get(String name) { for (LockMode mode : LockMode.values()) { if (mode.getName().equalsIgnoreCase(name)) { @@ -243,6 +251,10 @@ public static LockMode get(String name) { } throw new IllegalArgumentException("unknown lock mode:" + name); } + + public String getName() { + return name; + } } } diff --git a/server/src/main/resources/META-INF/services/io.seata.core.serializer.Serializer b/server/src/main/resources/META-INF/services/io.seata.core.serializer.Serializer new file mode 100644 index 00000000000..750cbc7d7dd --- /dev/null +++ b/server/src/main/resources/META-INF/services/io.seata.core.serializer.Serializer @@ -0,0 +1 @@ +io.seata.server.cluster.raft.serializer.JacksonSerializer \ No newline at end of file diff --git a/server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker b/server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker index 874e8b91c31..8ce77186f8b 100644 --- a/server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker +++ b/server/src/main/resources/META-INF/services/io.seata.core.store.DistributedLocker @@ -1,2 +1,3 @@ io.seata.server.storage.redis.lock.RedisDistributedLocker -io.seata.server.storage.db.lock.DataBaseDistributedLocker \ No newline at end of file +io.seata.server.storage.db.lock.DataBaseDistributedLocker +io.seata.server.storage.raft.lock.RaftDistributedLocker \ No newline at end of file diff --git a/server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager b/server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager index bca40c8591b..2a1cfb1acce 100644 --- a/server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager +++ b/server/src/main/resources/META-INF/services/io.seata.server.lock.LockManager @@ -1,3 +1,4 @@ io.seata.server.storage.db.lock.DataBaseLockManager io.seata.server.storage.file.lock.FileLockManager -io.seata.server.storage.redis.lock.RedisLockManager \ No newline at end of file +io.seata.server.storage.redis.lock.RedisLockManager +io.seata.server.storage.raft.lock.RaftLockManager \ No newline at end of file diff --git a/server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager b/server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager index f2e82316792..13b37775e38 100644 --- a/server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager +++ b/server/src/main/resources/META-INF/services/io.seata.server.session.SessionManager @@ -1,3 +1,4 @@ io.seata.server.storage.file.session.FileSessionManager io.seata.server.storage.db.session.DataBaseSessionManager -io.seata.server.storage.redis.session.RedisSessionManager \ No newline at end of file +io.seata.server.storage.redis.session.RedisSessionManager +io.seata.server.storage.raft.session.RaftSessionManager \ No newline at end of file diff --git a/server/src/main/resources/application.example.yml b/server/src/main/resources/application.example.yml index 08705e7a0bd..58c6c11880d 100644 --- a/server/src/main/resources/application.example.yml +++ b/server/src/main/resources/application.example.yml @@ -116,6 +116,20 @@ seata: address-wait-time: 3000 server: + raft: + group: default + cluster: + snapshot-interval: 600 + apply-batch: 32 + max-append-bufferSize: 262144 + max-replicator-inflight-msgs: 256 + disruptor-buffer-size: 16384 + election-timeout-ms: 1000 + reporter-enabled: false + reporter-initial-delay: 60 + serialization: jackson + compressor: none + sync: true # sync log&snapshot to disk service-port: 8091 #If not configured, the default is '${server.port} + 1000' max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 @@ -138,7 +152,7 @@ seata: branch-async-queue-size: 5000 #branch async remove queue size enable-branch-async-remove: false #enable to asynchronous remove branchSession store: - # support: file 、 db 、 redis + # support: file 、 db 、 redis 、 raft mode: file session: mode: file diff --git a/server/src/main/resources/application.raft.example.yml b/server/src/main/resources/application.raft.example.yml new file mode 100644 index 00000000000..5d07b8b2797 --- /dev/null +++ b/server/src/main/resources/application.raft.example.yml @@ -0,0 +1,132 @@ +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +server: + port: 7091 + +spring: + application: + name: seata-server + +logging: + config: classpath:logback-spring.xml + file: + path: ${log.home:${user.home}/logs/seata} + extend: + logstash-appender: + destination: 127.0.0.1:4560 + kafka-appender: + bootstrap-servers: 127.0.0.1:9092 + topic: logback_to_logstash + +seata: + config: + # support: nacos 、 consul 、 apollo 、 zk 、 etcd3 + type: file + nacos: + server-addr: 127.0.0.1:8848 + namespace: + group: SEATA_GROUP + username: + password: + context-path: + ##if use MSE Nacos with auth, mutex with username/password attribute + #access-key: + #secret-key: + data-id: seataServer.properties + consul: + server-addr: 127.0.0.1:8500 + acl-token: + key: seata.properties + apollo: + appId: seata-server + apollo-meta: http://192.168.1.204:8801 + apollo-config-service: http://192.168.1.204:8080 + namespace: application + apollo-access-key-secret: + cluster: seata + zk: + server-addr: 127.0.0.1:2181 + session-timeout: 6000 + connect-timeout: 2000 + username: + password: + node-path: /seata/seata.properties + etcd3: + server-addr: http://localhost:2379 + key: seata.properties + registry: + # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa + type: file + preferred-networks: 30.240.* + server: + raft: + group: default + server-addr: + snapshot-interval: 600 + apply-batch: 32 + max-append-bufferSize: 262144 + max-replicator-inflight-msgs: 256 + disruptor-buffer-size: 16384 + election-timeout-ms: 1000 + reporter-enabled: false + reporter-initial-delay: 60 + serialization: jackson + compressor: none + sync: true # sync log&snapshot to disk + service-port: 8091 #If not configured, the default is '${server.port} + 1000' + max-commit-retry-timeout: -1 + max-rollback-retry-timeout: -1 + rollback-retry-timeout-unlock-enable: false + enable-check-auth: true + enable-parallel-request-handle: true + enable-parallel-handle-branch: false + retry-dead-threshold: 130000 + xaer-nota-retry-timeout: 60000 + enableParallelRequestHandle: true + recovery: + committing-retry-period: 1000 + async-committing-retry-period: 1000 + rollbacking-retry-period: 1000 + timeout-retry-period: 1000 + undo: + log-save-days: 7 + log-delete-period: 86400000 + session: + branch-async-queue-size: 5000 #branch async remove queue size + enable-branch-async-remove: false #enable to asynchronous remove branchSession + store: + # support: file + mode: file + file: + dir: sessionStore + max-branch-session-size: 16384 + max-global-session-size: 512 + file-write-buffer-cache-size: 16384 + session-reload-read-size: 100 + flush-disk-mode: async + metrics: + enabled: false + registry-type: compact + exporter-list: prometheus + exporter-prometheus-port: 9898 + transport: + rpc-tc-request-timeout: 15000 + enable-tc-server-batch-send-response: false + shutdown: + wait: 3 + thread-factory: + boss-thread-prefix: NettyBoss + worker-thread-prefix: NettyServerNIOWorker + boss-thread-size: 1 diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 5ffc5ed8c3d..37cee3e20bf 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -42,7 +42,7 @@ seata: # support: nacos, eureka, redis, zk, consul, etcd3, sofa type: file store: - # support: file 、 db 、 redis + # support: file 、 db 、 redis 、 raft mode: file # server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' @@ -50,4 +50,4 @@ seata: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: - urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login + urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/** diff --git a/server/src/test/java/io/seata/server/coordinator/DefaultCoordinatorTest.java b/server/src/test/java/io/seata/server/coordinator/DefaultCoordinatorTest.java index 0f737529952..19b9fab39e6 100644 --- a/server/src/test/java/io/seata/server/coordinator/DefaultCoordinatorTest.java +++ b/server/src/test/java/io/seata/server/coordinator/DefaultCoordinatorTest.java @@ -97,7 +97,7 @@ public static void beforeClass(ApplicationContext context) throws Exception { EnhancedServiceLoader.unload(AbstractCore.class); XID.setIpAddress(NetUtil.getLocalIp()); RemotingServer remotingServer = new MockServerMessageSender(); - defaultCoordinator =DefaultCoordinator.getInstance(remotingServer); + defaultCoordinator = DefaultCoordinator.getInstance(remotingServer); defaultCoordinator.setRemotingServer(remotingServer); core = new DefaultCore(remotingServer); } @@ -284,4 +284,4 @@ public void registerProcessor(int messageType, RemotingProcessor processor, Exec } } -} \ No newline at end of file +} diff --git a/server/src/test/java/io/seata/server/lock/LockManagerTest.java b/server/src/test/java/io/seata/server/lock/LockManagerTest.java index 9f10ad5512e..d661455298f 100644 --- a/server/src/test/java/io/seata/server/lock/LockManagerTest.java +++ b/server/src/test/java/io/seata/server/lock/LockManagerTest.java @@ -236,8 +236,8 @@ public void lockQueryTest(GlobalSession globalSessions1, GlobalSession globalSes // wrong pageSize or pageNum Assertions.assertThrows( - IllegalArgumentException.class, - () -> globalLockService.query(param) + IllegalArgumentException.class, + () -> globalLockService.query(param) ); LockManager lockManager = new FileLockManagerForTest(); diff --git a/server/src/test/java/io/seata/server/raft/RaftSyncMessageTest.java b/server/src/test/java/io/seata/server/raft/RaftSyncMessageTest.java new file mode 100644 index 00000000000..608dc773222 --- /dev/null +++ b/server/src/test/java/io/seata/server/raft/RaftSyncMessageTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.raft; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchType; +import io.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMessage; +import io.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import io.seata.server.cluster.raft.snapshot.RaftSnapshot; +import io.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; +import io.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; +import io.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; +import io.seata.server.session.GlobalSession; +import io.seata.server.session.SessionHelper; +import io.seata.server.session.SessionHolder; +import io.seata.server.store.StoreConfig; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +/** + * @author jianbin.chen + */ +@SpringBootTest +public class RaftSyncMessageTest { + + @BeforeAll + public static void setUp(ApplicationContext context){ + SessionHolder.init(StoreConfig.SessionMode.FILE); + } + + @AfterAll + public static void destroy(){ + SessionHolder.destroy(); + } + @Test + public void testMsgSerialize() throws IOException { + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + RaftGlobalSessionSyncMsg raftSessionSyncMsg = new RaftGlobalSessionSyncMsg(); + RaftBranchSessionSyncMsg raftBranchSessionMsg = new RaftBranchSessionSyncMsg(); + raftBranchSessionMsg.setBranchSession(new BranchTransactionDTO("123:123", 1234)); + raftSessionSyncMsg.setGlobalSession(new GlobalTransactionDTO("123:123")); + raftSyncMessage.setBody(raftSessionSyncMsg); + byte[] msg = RaftSyncMessageSerializer.encode(raftSyncMessage); + RaftSyncMessage raftSyncMessage1 = RaftSyncMessageSerializer.decode(msg); + RaftSyncMessage raftSyncMessage2 = new RaftSyncMessage(); + raftSyncMessage2.setBody(raftBranchSessionMsg); + byte[] msg2 = RaftSyncMessageSerializer.encode(raftSyncMessage2); + RaftSyncMessage raftSyncMessageByBranch = RaftSyncMessageSerializer.decode(msg2); + Assertions.assertEquals("123:123", ((RaftBranchSessionSyncMsg) raftSyncMessageByBranch.getBody()).getBranchSession().getXid()); + Assertions.assertEquals("123:123", ((RaftGlobalSessionSyncMsg) raftSyncMessage1.getBody()).getGlobalSession().getXid()); + Assertions.assertEquals(1234, ((RaftBranchSessionSyncMsg) raftSyncMessageByBranch.getBody()).getBranchSession().getBranchId()); + } + + @Test + public void testSnapshotSerialize() throws IOException, TransactionException { + Map sessionMap = new HashMap<>(); + GlobalSession globalSession = GlobalSession.createGlobalSession("123", "123", "123", 11111); + sessionMap.put(globalSession.getXid(), globalSession); + globalSession + .addBranch(SessionHelper.newBranchByGlobal(globalSession, BranchType.AT, "!23", null, "123", "123")); + RaftSessionSnapshot sessionSnapshot = new RaftSessionSnapshot(); + sessionMap.forEach((xid, session) -> sessionSnapshot.convert2GlobalSessionByte(session)); + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(sessionSnapshot); + byte[] msg = RaftSnapshotSerializer.encode(raftSnapshot); + RaftSnapshot raftSnapshot1 = RaftSnapshotSerializer.decode(msg); + RaftSessionSnapshot sessionSnapshot2 = (RaftSessionSnapshot)raftSnapshot1.getBody(); + Map map = sessionSnapshot2.convert2GlobalSession(); + Assertions.assertEquals(1, map.size()); + Assertions.assertNotNull(map.get(globalSession.getXid())); + Assertions.assertEquals(1, map.get(globalSession.getXid()).getBranchSessions().size()); + } + +} diff --git a/server/src/test/java/io/seata/server/session/FileSessionManagerTest.java b/server/src/test/java/io/seata/server/session/FileSessionManagerTest.java index f752482c7ad..bdd246fdb5f 100644 --- a/server/src/test/java/io/seata/server/session/FileSessionManagerTest.java +++ b/server/src/test/java/io/seata/server/session/FileSessionManagerTest.java @@ -49,9 +49,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; + +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; import static io.seata.common.DefaultValues.DEFAULT_TX_GROUP; import static io.seata.server.session.SessionHolder.CONFIG; -import static io.seata.server.session.SessionHolder.DEFAULT_SESSION_STORE_FILE_DIR; /** * The type File based session manager test. * @@ -67,9 +68,6 @@ public class FileSessionManagerTest { @Resource(type = GlobalSessionService.class) private GlobalSessionService globalSessionService; - private static String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, - DEFAULT_SESSION_STORE_FILE_DIR); - @BeforeAll public static void setUp(ApplicationContext context) { StoreUtil.deleteDataFile(); diff --git a/server/src/test/java/io/seata/server/session/GlobalSessionTest.java b/server/src/test/java/io/seata/server/session/GlobalSessionTest.java index b631d413a30..4ed88665833 100644 --- a/server/src/test/java/io/seata/server/session/GlobalSessionTest.java +++ b/server/src/test/java/io/seata/server/session/GlobalSessionTest.java @@ -169,6 +169,7 @@ public void codecTest(GlobalSession globalSession) { Assertions.assertEquals(expected.getApplicationId(), globalSession.getApplicationId()); Assertions.assertEquals(expected.getTransactionServiceGroup(), globalSession.getTransactionServiceGroup()); Assertions.assertEquals(expected.getTransactionName(), globalSession.getTransactionName()); + Assertions.assertTrue(expected.isActive()); } /** diff --git a/server/src/test/java/io/seata/server/session/SessionHolderTest.java b/server/src/test/java/io/seata/server/session/SessionHolderTest.java index 1cd553823c3..99070bd6bfd 100644 --- a/server/src/test/java/io/seata/server/session/SessionHolderTest.java +++ b/server/src/test/java/io/seata/server/session/SessionHolderTest.java @@ -17,7 +17,7 @@ import java.io.File; import java.io.IOException; - +import io.seata.common.XID; import io.seata.core.constants.ConfigurationKeys; import io.seata.server.store.StoreConfig.SessionMode; import org.junit.jupiter.api.AfterEach; @@ -34,7 +34,9 @@ import static io.seata.common.Constants.RETRY_ROLLBACKING; import static io.seata.common.Constants.TX_TIMEOUT_CHECK; import static io.seata.common.Constants.UNDOLOG_DELETE; +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; import static io.seata.server.session.SessionHolder.ROOT_SESSION_MANAGER_NAME; +import static java.io.File.separator; /** * The type Session holder test. @@ -48,7 +50,9 @@ public class SessionHolderTest { @BeforeEach public void before() { - String sessionStorePath = SessionHolder.CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR); + String sessionStorePath = + SessionHolder.CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR) + separator + + XID.getPort(); //delete file previously created pathname = sessionStorePath + File.separator + ROOT_SESSION_MANAGER_NAME; // SessionHolder.init(StoreMode.REDIS.getName()); diff --git a/server/src/test/java/io/seata/server/session/redis/RedisSessionManagerTest.java b/server/src/test/java/io/seata/server/session/redis/RedisSessionManagerTest.java index 883d749688b..60f769edb08 100644 --- a/server/src/test/java/io/seata/server/session/redis/RedisSessionManagerTest.java +++ b/server/src/test/java/io/seata/server/session/redis/RedisSessionManagerTest.java @@ -139,7 +139,7 @@ public void test_addBranchSession() throws TransactionException { branchSession.setBranchType(BranchType.AT); branchSession.setApplicationData("{\"data\":\"test\"}"); branchSession.setClientId("storage-server:192.168.158.80:11934"); - sessionManager.addBranchSession(globalSession, branchSession); + sessionManager.addBranchSession(globalSession,branchSession); sessionManager.removeBranchSession(globalSession, branchSession); sessionManager.removeGlobalSession(globalSession); diff --git a/server/src/test/java/io/seata/server/store/RaftSyncMessageSerializerTest.java b/server/src/test/java/io/seata/server/store/RaftSyncMessageSerializerTest.java new file mode 100644 index 00000000000..9369d710ec8 --- /dev/null +++ b/server/src/test/java/io/seata/server/store/RaftSyncMessageSerializerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.server.store; + +import io.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMsgType; +import io.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +import io.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import io.seata.server.cluster.raft.sync.msg.RaftSyncMessage; +import io.seata.server.session.GlobalSession; +import io.seata.server.storage.SessionConverter; + +/** + * @author funkye + */ +@SpringBootTest +public class RaftSyncMessageSerializerTest { + @BeforeAll + public static void setUp(ApplicationContext context) { + + } + + @Test + public void testSerializerTest() throws Exception { + RaftGlobalSessionSyncMsg raftSessionSyncMsg = new RaftGlobalSessionSyncMsg(); + raftSessionSyncMsg.setMsgType(RaftSyncMsgType.ADD_GLOBAL_SESSION); + GlobalSession session = GlobalSession.createGlobalSession("test", "test", "test123", 100); + GlobalTransactionDTO globalTransactionDTO = new GlobalTransactionDTO(); + SessionConverter.convertGlobalTransactionDO(globalTransactionDTO,session); + raftSessionSyncMsg.setGlobalSession(globalTransactionDTO); + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + raftSyncMessage.setBody(raftSessionSyncMsg); + byte[] bytes = RaftSyncMessageSerializer.encode(raftSyncMessage); + RaftSyncMessage raftSyncMessage2 = RaftSyncMessageSerializer.decode(bytes); + RaftGlobalSessionSyncMsg raftSessionSyncMsg2 = (RaftGlobalSessionSyncMsg) raftSyncMessage2.getBody(); + Assertions.assertTrue(raftSessionSyncMsg2.getGlobalSession().getXid().equals(session.getXid())); + Assertions.assertTrue(raftSessionSyncMsg.getMsgType().equals(raftSessionSyncMsg2.getMsgType())); + } + +} diff --git a/server/src/test/java/io/seata/server/store/SessionStoreTest.java b/server/src/test/java/io/seata/server/store/SessionStoreTest.java index 312d90cc7ed..04b2de8baee 100644 --- a/server/src/test/java/io/seata/server/store/SessionStoreTest.java +++ b/server/src/test/java/io/seata/server/store/SessionStoreTest.java @@ -38,6 +38,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; +import static java.io.File.separator; import static io.seata.common.DefaultValues.DEFAULT_TX_GROUP; /** @@ -65,10 +67,10 @@ public static void setUp(ApplicationContext context) { */ @BeforeEach public void clean() throws Exception { - String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR); - File rootDataFile = new File(sessionStorePath + File.separator + SessionHolder.ROOT_SESSION_MANAGER_NAME); - File rootDataFileHis = new File( - sessionStorePath + File.separator + SessionHolder.ROOT_SESSION_MANAGER_NAME + ".1"); + String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR) + + separator + XID.getPort(); + File rootDataFile = new File(sessionStorePath + separator + SessionHolder.ROOT_SESSION_MANAGER_NAME); + File rootDataFileHis = new File(sessionStorePath + separator + SessionHolder.ROOT_SESSION_MANAGER_NAME + ".1"); if (rootDataFile.exists()) { rootDataFile.delete(); @@ -195,7 +197,6 @@ public void testRestoredFromFileAsyncCommitting() throws Exception { // Re-init SessionHolder: restore sessions from file SessionHolder.init(SessionMode.FILE); - long tid = globalSession.getTransactionId(); GlobalSession reloadSession = SessionHolder.findGlobalSession(globalSession.getXid()); Assertions.assertEquals(reloadSession.getStatus(), GlobalStatus.AsyncCommitting); diff --git a/server/src/test/java/io/seata/server/util/StoreUtil.java b/server/src/test/java/io/seata/server/util/StoreUtil.java index 9f3f88b56b4..8f59da908f9 100644 --- a/server/src/test/java/io/seata/server/util/StoreUtil.java +++ b/server/src/test/java/io/seata/server/util/StoreUtil.java @@ -19,15 +19,21 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import io.seata.common.XID; import io.seata.config.ConfigurationFactory; import io.seata.core.constants.ConfigurationKeys; -import static io.seata.server.session.SessionHolder.DEFAULT_SESSION_STORE_FILE_DIR; +import static io.seata.common.DefaultValues.DEFAULT_SESSION_STORE_FILE_DIR; +import static java.io.File.separator; +/** + * @author funkye + */ public class StoreUtil { private static String sessionStorePath = - ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR); + ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_FILE_DIR, DEFAULT_SESSION_STORE_FILE_DIR) + + separator + XID.getPort(); public static void deleteDataFile() { try { diff --git a/test/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java b/test/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java index a2ac66f3a2f..2447ba0d272 100644 --- a/test/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java +++ b/test/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java @@ -33,6 +33,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static io.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + /** * @author slievrly */