Spring Cloud Alibaba
https://spring.io/projects/spring-cloud-alibaba
https://github.com/alibaba/spring-cloud-alibaba
https://github.com/alibaba/spring-cloud-alibaba/wiki
https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
Spring Boot -> Spring Cloud -> Spring Cloud Alibaba
1,服务治理
服务注册、服务发现
Nacos 根据版本关系表格,采用1.2.1版本
nacos快速开始 https://nacos.io/zh-cn/docs/quick-start.html
源码及bin https://github.com/alibaba/nacos
https://github.com/alibaba/nacos/releases/tag/1.2.1
root/senrsl/b
[senrsl@localhost ~]$ tar -zxvf nacos-server-1.2.1.tar.gz
nacos/LICENSE
nacos/NOTICE
nacos/target/nacos-server.jar
nacos/conf/
nacos/conf/schema.sql
nacos/conf/nacos-mysql.sql
nacos/conf/application.properties.example
nacos/conf/nacos-logback.xml
nacos/conf/cluster.conf.example
nacos/conf/application.properties
nacos/bin/startup.sh
nacos/bin/startup.cmd
nacos/bin/shutdown.sh
nacos/bin/shutdown.cmd
[senrsl@localhost ~]$
需要java环境
[senrsl@localhost ~]$ java -version
openjdk version "1.8.0_332"
OpenJDK Runtime Environment (build 1.8.0_332-b09)
OpenJDK 64-Bit Server VM (build 25.332-b09, mixed mode)
[senrsl@localhost ~]$ cd
启动
[senrsl@localhost bin]$ ./startup.sh
which: no javac in
(/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/senrsl/.local/bin:/home/senrsl/bin)
readlink: 缺少操作数
Try 'readlink --help' for more information.
dirname: 缺少操作数
Try 'dirname --help' for more information.
ERROR: Please set the JAVA_HOME variable in your environment, We
need java(x64)! jdk8 or later is better! !!
[senrsl@localhost bin]$
安装的版本没有javac
[senrsl@localhost ~]$ sudo yum install -y
java-1.8.0-openjdk-devel.x86_64
[senrsl@localhost ~]$ ll
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.332.b09-1.el7_9.x86_64/
总用量 184
-rw-r--r--. 1 root root 1522 5月 10 22:33 ASSEMBLY_EXCEPTION
drwxr-xr-x. 2 root root 4096 6月 11 00:28 bin
drwxr-xr-x. 3 root root 132 6月 11 00:28 include
drwxr-xr-x. 4 root root 95 6月 10 22:31 jre
drwxr-xr-x. 3 root root 144 6月 11 00:28 lib
-rw-r--r--. 1 root root 19274 5月 10 22:33 LICENSE
drwxr-xr-x. 2 root root 204 6月 11 00:28 tapset
-rw-r--r--. 1 root root 157063 5月 10 22:33 THIRD_PARTY_README
[senrsl@localhost ~]$
启动,必须要加 -standalone,不然会报找不到jdbc driver.....
[senrsl@localhost bin]$ sh startup.sh -m standalone
查看启动结果
[senrsl@localhost bin]$ cat ../logs/start.out
访问即可 http://172.16.5.131:8848/nacos/index.html
用户名密码nacos/nacos
启动时,修改端口并run setting 勾选allow parellen run 可以直接启动多个。
配图1
服务注册完成。
Invalid packaging for parent POM
dc.test:TestSpringCloudAlibaba:0.0.1-SNAPSHOT
(/Users/senrsl/temp/TestSpringCloudAlibaba/pom.xml), must be "pom"
but is "jar"
根pom version上面加入packaging 标签内容为pom.
服务消费者也需要注册到nacos中,不写默认注册到localhost:8848地址。
通过DiscoveryClient可以发现获取服务实例
配图2
2,Ribbon
@LoadBalanced
restTemplate.getForObject("http://nacos-provider/port",
String.class);
默认采用轮询算法,可以改随机算法、券种算法等。
3,Sentinel
服务限流降级
雪崩效应
雪崩的时候每一片雪花都在勇闯天涯
解决雪崩效应
1) 设置线程超时
2)设置限流,服务流量到达上限进行限制
3)熔断器 Sentinel(alibaba)、Hystrix(spring cloud)
断路器关闭、断路器打开、断路器半开(自我修复)
降级 部分功能可用
限流 控制流量
熔断 不再请求
sentinel sent(ə)nəl 哨兵
actuator ˈak(t)SHəˌwādər
执行器
sentinel 按照版本介绍,需要找1.7.1版本
https://github.com/alibaba/Sentinel/releases/tag/1.7.1
默认8080端口,使用9080端口启动
[senrsl@localhost sentinel]$ java -Dserver.port=9080
-Dcsp.sentinel.dashboard.server=localhost:8080
-Dproject.name=sentinel-dashboard -jar
sentinel-dashboard-1.7.1.jar
访问 http://172.16.5.131:9080/
用户名密码sentinel
图标不显示 DevTools 无法加载来源映射:无法加载
http://172.16.5.131:9080/lib/js/jquery.min.map 的内容:HTTP 错误:状态代码
404,net::ERR_HTTP_RESPONSE_CODE_FAILURE
流控规则
直接 直接对controller限流,保护controller
关联 当controller1超出流控时,对controller2进行限流。售票处忙不过来限流排队入口。
链路 控制controller到Service链路,保护Service
快速失败 抛异常
Warm UP 预热时间内阈值低,时间后正常
排队等待 失败重试
降级规则
RT 单个请求响应时间超过阈值,则进入准降级状态。接下来1S内连续5个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。
异常比例
每秒异常比例超过阈值降级。
异常数
数值比对降级。
热点规则
对第index个参数进行限流
例外项:第index个参数值为例外值时,使用例外的QPS
授权规则
配置参数值的黑白名单
4,RocketMQ
文档 https://github.com/alibaba/spring-cloud-alibaba/wiki/RocketMQ
[senrsl@localhost rocket]$ sudo yum install -y unzip
[senrsl@localhost rocket]$ unzip rocketmq-all-4.7.1-bin-release.zip
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup ./bin/mqnamesrv &
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ sudo yum install net-tools
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ netstat -an | grep 9876
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ cd bin/
[senrsl@localhost bin]$ vi runserver.shJAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
[senrsl@localhost bin]$ vi runbroker.sh
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
[senrsl@localhost bin]$ nohup ./mqbroker -n localhost:9876 &
[1] 14942
[senrsl@localhost bin]$ nohup: 忽略输入并把输出追加到"nohup.out"
[senrsl@localhost bin]$ tail -ff ~/logs/rocketmqlogs/broker.log
2022-06-11 06:56:52 INFO BrokerControllerScheduledThread1 - Slave fall behind master: 0 bytes
2022-06-11 06:56:53 WARN brokerOutApi_thread_2 - registerBroker Exception, localhost:9876
org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to localhost:9876 failed
at org.apache.rocketmq.remoting.netty.NettyRemotingClient.invokeSync(NettyRemotingClient.java:394) ~[rocketmq-remoting-4.7.1.jar:4.7.1]
at org.apache.rocketmq.broker.out.BrokerOuterAPI.registerBroker(BrokerOuterAPI.java:194) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at org.apache.rocketmq.broker.out.BrokerOuterAPI.access$000(BrokerOuterAPI.java:61) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at org.apache.rocketmq.broker.out.BrokerOuterAPI$1.run(BrokerOuterAPI.java:150) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_332]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_332]
at java.lang.Thread.run(Thread.java:750) [na:1.8.0_332]
重新启动
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqnamesrv &
[2] 15013
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup: 忽略输入并把输出追加到"nohup.out"
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ tail -f ~/logs/rocketmqlogs/namesrv.log
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, SCHEDULE_TOPIC_XXXX QueueData [brokerName=localhost.localdomain, readQueueNums=18, writeQueueNums=18, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, RMQ_SYS_TRANS_HALF_TOPIC QueueData [brokerName=localhost.localdomain, readQueueNums=1, writeQueueNums=1, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, DefaultCluster_REPLY_TOPIC QueueData [brokerName=localhost.localdomain, readQueueNums=1, writeQueueNums=1, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, BenchmarkTest QueueData [brokerName=localhost.localdomain, readQueueNums=1024, writeQueueNums=1024, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, OFFSET_MOVED_EVENT QueueData [brokerName=localhost.localdomain, readQueueNums=1, writeQueueNums=1, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, TBW102 QueueData [brokerName=localhost.localdomain, readQueueNums=8, writeQueueNums=8, perm=7, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, localhost.localdomain QueueData [brokerName=localhost.localdomain, readQueueNums=1, writeQueueNums=1, perm=7, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, SELF_TEST_TOPIC QueueData [brokerName=localhost.localdomain, readQueueNums=1, writeQueueNums=1, perm=6, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new topic registered, DefaultCluster QueueData [brokerName=localhost.localdomain, readQueueNums=16, writeQueueNums=16, perm=7, topicSynFlag=0]
2022-06-11 06:59:23 INFO RemotingExecutorThread_1 - new broker registered, 172.16.5.131:10911 HAServer: 172.16.5.131:10912
^C
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ tail -f ~/logs/rocketmqlogs/broker.log
at org.apache.rocketmq.broker.out.BrokerOuterAPI.registerBroker(BrokerOuterAPI.java:194) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at org.apache.rocketmq.broker.out.BrokerOuterAPI.access$000(BrokerOuterAPI.java:61) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at org.apache.rocketmq.broker.out.BrokerOuterAPI$1.run(BrokerOuterAPI.java:150) ~[rocketmq-broker-4.7.1.jar:4.7.1]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_332]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_332]
at java.lang.Thread.run(Thread.java:750) [na:1.8.0_332]
2022-06-11 06:59:23 INFO brokerOutApi_thread_4 - register broker[0]to name server localhost:9876 OK
2022-06-11 06:59:52 INFO BrokerControllerScheduledThread1 - dispatch behind commit log 0 bytes
2022-06-11 06:59:52 INFO BrokerControllerScheduledThread1 - Slave fall behind master: 0 bytes
2022-06-11 06:59:53 INFO brokerOutApi_thread_1 - register broker[0]to name server localhost:9876 OK
^C
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$
shutdown
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ sh bin/mqshutdown broker
The mqbroker(14950) is running...
Send shutdown request to mqbroker(14950) OK
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ sh bin/mqshutdown namesrv
The mqnamesrv(15020) is running...
Send shutdown request to mqnamesrv(15020) OK
[1]- 退出 143 nohup ./mqbroker -n localhost:9876(工作目录:~/rocket/rocketmq-all-4.7.1-bin-release/bin)
(当前工作目录:~/rocket/rocketmq-all-4.7.1-bin-release)
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ sh bin/mqshutdown namesrv
No mqnamesrv running.
[2]+ 退出 143 nohup sh bin/mqnamesrv
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ netstat -an | grep 9876
tcp6 0 0 127.0.0.1:50682 127.0.0.1:9876 TIME_WAIT
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ cd bin/
[senrsl@localhost bin]$ sh mqshutdown namesrv
No mqnamesrv running.
[senrsl@localhost rocket]$
测试
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqnamesrv &
[1] 15182
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup: 忽略输入并把输出追加到"nohup.out"
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ tail -f ~/logs/rocketmqlogs/namesrv.log
2022-06-11 07:08:30 INFO main - tls.client.keyPassword = null
2022-06-11 07:08:30 INFO main - tls.client.certPath = null
2022-06-11 07:08:30 INFO main - tls.client.authServer = false
2022-06-11 07:08:30 INFO main - tls.client.trustCertPath = null
2022-06-11 07:08:30 INFO main - Using OpenSSL provider
2022-06-11 07:08:30 INFO main - SSLContext created for server
2022-06-11 07:08:30 INFO main - Try to start service thread:FileWatchService started:false lastThread:null
2022-06-11 07:08:30 INFO NettyEventExecutor - NettyEventExecutor service started
2022-06-11 07:08:30 INFO main - The Name Server boot success. serializeType=JSON
2022-06-11 07:08:30 INFO FileWatchService - FileWatchService service started
^C
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup sh bin/mqbroker -n localhost:9876 &
[2] 15208
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ nohup: 忽略输入并把输出追加到"nohup.out"
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$ tail -f ~/logs/rocketmqlogs/broker.log
2022-06-11 07:09:12 INFO main - Try to start service thread:StoreStatsService started:false lastThread:null
2022-06-11 07:09:12 INFO main - Try to start service thread:FileWatchService started:false lastThread:null
2022-06-11 07:09:12 INFO main - Try to start service thread:PullRequestHoldService started:false lastThread:null
2022-06-11 07:09:12 INFO main - Try to start service thread:TransactionalMessageCheckService started:false lastThread:null
2022-06-11 07:09:12 INFO PullRequestHoldService - PullRequestHoldService service started
2022-06-11 07:09:12 INFO FileWatchService - FileWatchService service started
2022-06-11 07:09:13 INFO brokerOutApi_thread_1 - register broker[0]to name server localhost:9876 OK
2022-06-11 07:09:13 INFO main - The broker[localhost.localdomain, 172.16.5.131:10911] boot success. serializeType=JSON and name server is localhost:9876
2022-06-11 07:09:22 INFO BrokerControllerScheduledThread1 - dispatch behind commit log 0 bytes
2022-06-11 07:09:22 INFO BrokerControllerScheduledThread1 - Slave fall behind master: 0 bytes
2022-06-11 07:09:23 INFO brokerOutApi_thread_2 - register broker[0]to name server localhost:9876 OK
[senrsl@localhost rocketmq-all-4.7.1-bin-release]$
测试示例
[senrsl@localhost bin]export NAMESRV_ADDR=localhost:9876
发送
[senrsl@localhost bin]./tools.sh org.apache.rocketmq.example.quickstart.Producer
接收
[senrsl@localhost bin]./tools.sh org.apache.rocketmq.example.quickstart.Consumer
4.1) RocketMQ Console
https://github.com/apache/rocketmq-dashboard
SENRSL:docker senrsl$ docker pull apacherocketmq/rocketmq-dashboard:latest
SENRSL:docker senrsl$ docker run -d --name rocketmq-dashboard -e "JAVA_OPTS=-Drocketmq.namesrv.addr=172.16.5.131:9876" -p 9877:8080 -t apacherocketmq/rocketmq-dashboard:latest
访问 http://localhost:9877/
配图3
org.apache.rocketmq.client.exception.MQBrokerException: CODE: 1
DESC: the producer group[] not exist BROKER: 172.16.5.131:10911
For more information, please visit the url,
4.2) java发送接收消息
4.3)rocketclient发送接收消息
5,网关
Zuul、
Spring Cloud Gateway 基于Netty,与Servlet不兼容。
Could not autowire. No beans of 'ServerCodecConfigurer' type
found.
6,分布式事务
seata https://seata.io/zh-cn/
6.1)安mysql
https://hub.docker.com/_/mysql
SENRSL:docker senrsl$ docker pull mysql
SENRSL:docker senrsl$ docker run -itd --name mysql01 -p 3306:3306
-e MYSQL_ROOT_PASSWORD=012345 mysql:latest
a00fe42d229633dc539bdf2cf3608c85e9dd9b506d6308475a9f06dc19f6335b
SENRSL:docker senrsl$ docker ps
SENRSL:docker senrsl$ docker exec -it a00fe42d2296 /bin/bash
root@a00fe42d2296:/# ls
bin boot dev docker-entrypoint-initdb.d entrypoint.sh
etc home lib lib64 media mnt opt proc root run sbin
srv sys tmp usr var
root@a00fe42d2296:/# mysql -h localhost -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.0.29 MySQL Community Server - GPL
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current
input statement.
mysql>
然后用idea自带的sql管理就可以,基本操作够用了
配图4 建库
配图5 建表
然后去模拟业务,插入数据
2) 模拟两个微服务插入联合插入数据
3) seata
3.1)下载 https://seata.io/zh-cn/blog/download.html
需要查看上面版本对应表
3.2)修改配置
config/registry.conf
修改register和config为 nacos
config/nacos-config.txt
修改
service.vgroup_mapping.my_test_tx_group=default
为
service.vgroup_mapping.seata_order=default
service.vgroup_mapping.seata_pay=default
3.3)启动nacos,导入seata配置
[senrsl@localhost conf]$ sh nacos-config.sh 127.0.0.1
刷新nacos,查看导入结果
配图6
查询改的key,确认导入成功
配图7
3.4)启动seata server
[senrsl@localhost bin]$ sh seata-server.sh -p 8090 -m file
服务列表中,确认已注册进nacos
配图8
3.5)数据库中添加undo表
config/db_undo_log.sql 内容,在seata_order 和seata_pay中执行
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
执行后两个库中都有了undo表
配图9
3.6)给两个项目添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.1.RELEASE</version> </dependency>
3.7)两个项目都添加jdbcTemplate代理
@Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ return new JdbcTemplate(new DataSourceProxy(dataSource)); }
3.8)将conf/registery.conf复制到两个工程resource下
3.9)给两个工程添加bootstrip.yml读取nacos配置
如pay下 resource/bootstrap.yml
spring: application: name: seata_pay cloud: nacos: config: server-addr: 172.16.5.131:8848 namespace: public group: SEATA_GROUP alibaba: seata: tx-service-group: ${spring.application.name}
3.10)在seata_order.save()方法加入注解 @GlobalTransactional
3.11)测试
可以看到在插入第一个库时,会在第一个库的undo表中插入一条记录
2,2108675952,172.16.5.131:8090:2108675951,serializer=jackson,"{""@class"":""io.seata.rm.datasource.undo.BranchUndoLog"",""xid"":""172.16.5.131:8090:2108675951"",""branchId"":2108675952,""sqlUndoLogs"":[""java.util.ArrayList"",[{""@class"":""io.seata.rm.datasource.undo.SQLUndoLog"",""sqlType"":""INSERT"",""tableName"":""orders"",""beforeImage"":{""@class"":""io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords"",""tableName"":""orders"",""rows"":[""java.util.ArrayList"",[]]},""afterImage"":{""@class"":""io.seata.rm.datasource.sql.struct.TableRecords"",""tableName"":""orders"",""rows"":[""java.util.ArrayList"",[{""@class"":""io.seata.rm.datasource.sql.struct.Row"",""fields"":[""java.util.ArrayList"",[{""@class"":""io.seata.rm.datasource.sql.struct.Field"",""name"":""id"",""keyType"":""PrimaryKey"",""type"":4,""value"":10},{""@class"":""io.seata.rm.datasource.sql.struct.Field"",""name"":""name"",""keyType"":""NULL"",""type"":12,""value"":""李四""},{""@class"":""io.seata.rm.datasource.sql.struct.Field"",""name"":""createTime"",""keyType"":""NULL"",""type"":93,""value"":[""java.sql.Timestamp"",[1656042658000,0]]}]]}]]}}]]}",0,2022-06-24 03:50:59,2022-06-24 03:50:59,
在失败后,会根据这条记录回滚第一个库的写入信息。
7,简单梳理
这么看的话SpringCloudAlibaba总共改了几处地方
使用Nacos做服务治理
使用Ribbon做服务间负载均衡
使用Sentnel做服务限流降级
使用RocketMQ做队列消息传递
使用SpringCloudGateway做网关
使用Seata解决分布式事务。
senRsl
2022年06月21日14:43:16
没有评论 :
发表评论