抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1. 前言

本文将在Docker环境中搭建微服务环境,包含以下几个环境,均为当前最新版(2024-1-17):

  • MySQL 8
  • Redis
  • Nacos
  • Sentinel
  • Seata 2.3.0

2. 环境搭建

2.1 建立子网

未指定容器ip的情况下,docker会按启动顺序分配ip,所以可能每次启动微服务环境的时候,各容器的ip并不一致,所以需要自己建立一个子网,并分配给各个容器

初始状态下,docker有三个网络:

1
2
3
4
5
PS C:\Users\23516> docker network ls
NETWORK ID NAME DRIVER SCOPE
d71b4e8082bd bridge bridge local
cfe60e5ede6f host host local
eca3acb024ef none null local

此处我们建立一个自己的网络,名字为myNetwork,网段是172.20.0.0/16,可以分配172.20.0.0 ~ 172.20.255.255

1
docker network create --subnet=172.20.0.0/16 myNetwork

再次检查Docker网络列表,发现已经成功创建了自己的网络

1
2
3
4
5
6
PS C:\Users\23516> docker network ls
NETWORK ID NAME DRIVER SCOPE
d71b4e8082bd bridge bridge local
cfe60e5ede6f host host local
e49ab5c335a4 myNetwork bridge local
eca3acb024ef none null local

既然我们已经成功创建自己的网络,所以我们需要提前分配一下每个容器的ip,可以列一个表格:

容器 IP
MySQL 172.20.0.2
Redis 172.20.0.3
Nacos 172.20.0.4
Sentinel 172.20.0.5
Seata 172.20.0.6

2.2 MySQL

MYSQL_ROOT_PASSWORD: MySQL的密码,用户名默认为root

1
docker run -itd --name MySQL --network myNetwork --ip 172.20.0.2 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -p 33060:33060 mysql

执行后我们成功创建了一个容器名为MySQL,分配了ip并且将3306和33060端口暴露出来

当然这还没完,使用select NOW()查询后会发现时间差了8个小时,也就是存在时区问题,所以我们需要修改MySQL的时区

进入MySQL容器,理论上容器是不存在my.cnf这个文件的,所以直接执行以下命令即可。如果存在,则修改相应字段即可。

1
echo -e "[mysqld]\ndefault-time-zone = '+8:00'" > /etc/mysql/my.cnf

执行完毕后重启MySQL容器,再次执行select NOW()即可发现时间显示正常。

2.3 Redis

Redis部分就很简单,只分配一个ip即可,没什么特殊的操作

1
docker run -itd --name Redis --network myNetwork --ip 172.20.0.3 -p 6379:6379 redis

2.4 Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

MODE: 运行模式,我们选择单机模式(standalone)

Nacos 2.0之后需要额外暴露9848和9849端口

1
docker run -itd --name Nacos --network myNetwork --ip 172.20.0.4 -e MODE=standalone -p 8848:8848 -p 9848:9848 -p 9849:9849 nacos/nacos-server

现在就可以访问localhost:8848/nacos进入Nacos的后台管理页面了,用户名和密码默认都是nacos

2.5 Sentinel

Sentinel的搭建也是非常的简单,执行这条命令即可创建

1
docker run -itd --name Sentinel --network myNetwork --ip 172.20.0.5 -p 8858:8858 bladex/sentinel-dashboard

现在就可以访问localhost:8858进入Sentinel的后台管理页面了,用户名和密码默认都是sentinel

2.6 Seata

恭喜你,到了最后的一步,也是最难搭建的分布式事务容器,这里有一些坑是需要去踩的

2.6.1 创建容器

首先,你需要先查一下自己所处的局域网ip,例如我在当前的局域网中的IP是192.168.10.254,这个ip建议在路由器后台中定死。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS C:\Users\23516> ipconfig

Windows IP 配置


以太网适配器 以太网:

媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :

无线局域网适配器 WLAN:

连接特定的 DNS 后缀 . . . . . . . :
IPv6 地址 . . . . . . . . . . . . : 2409:8a34:61e:15f4:731b:41fe:57a8:a797
临时 IPv6 地址. . . . . . . . . . : 2409:8a34:61e:15f4:b44e:b797:dd16:3dcb
本地链接 IPv6 地址. . . . . . . . : fe80::ca09:8ecc:8470:a83a%13
IPv4 地址 . . . . . . . . . . . . : 192.168.10.254
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : fe80::10%13
192.168.10.1

接下来创建Seata容器,成功创建后,立马停止Seata容器,我们需要进行一些配置

这里的SEATA_IP一定一定一定要和宿主机的局域网IP一致,也就是上面提到的(192.168.10.254),不然后面项目链接Seata服务器的时候会一直报错!

1
docker run -itd --name Seata --network myNetwork --ip 172.20.0.6 -e SEATA_IP=192.168.10.254 -p 8091:8091 -p 7091:7091 seataio/seata-server

2.6.2 创建seata数据库

然后我们现在需要创建供Seata回滚事务使用的数据库,数据库名为seata(当然也可以自定义),建表语句可以参见官方地址

点击展开查看建表语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

2.6.3 在Nacos中导入Seata的配置

我们的Seata的配置是从Nacos获取的,所以我们需要事先在Nacos上配置好Seata的相关参数

点击Nacos的配置列表,选择创建配置

这里填写Data-ID和Group,填入相关的配置信息(如果提示有语法错误,请无视)

同样,配置内容可以参见官方提供的config.txt,也可以复制下面的

点击展开查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#For details about configuration items, see https://seata.apache.org/zh-cn/docs/user/configurations
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
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
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
# You can choose from the following options: fastjson, jackson, gson
tcc.contextJsonParserType=fastjson

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=file
store.lock.mode=file
store.session.mode=file
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.type=pipeline
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.sentinel.sentinelPassword=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=true
server.enableParallelHandleBranch=false
server.applicationDataLimit=64000
server.applicationDataLimitCheck=false

server.raft.server-addr=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
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

这里我们主要修改以下几个地方

1
2
3
4
5
6
7
8
9
store.mode=db
store.lock.mode=db
store.session.mode=db

store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 这里的IP是我们为MySQL容器分配的ID
store.db.url=jdbc:mysql://172.20.0.2:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456

修改完毕后点击下面的发布即可。

至此,我们在Nacos的配置已经完成,接下来的步骤就是将Seata注册到Nacos上

2.6.4 注册Seata到Nacos中

进入到Seata容器中,我们可以找到/seata-server/resources/application.yml这个文件,Seata本质上也是一个SpringBoot项目,所以可以修改application.yml改变Seata的一些参数。

/seata-server/resources/application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file
store:
# support: file 、 db 、 redis 、 raft
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login

从配置文件可以知道,Seata运行在7091端口上,控制台的用户名和密码都是seata,相关的数据都是文件形式保存在容器中的,所以现在需要修改seata.configseata.registry,使得Seata能够正确的注册到Nacos上。

1
2
3
4
5
6
7
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file

根据application.example.yml的内容来看,可以知道nacos相关的配置如下,并修改为Nacos的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 172.20.0.4:8848
namespace:
# seata.properties所属的Group的名称
group: DEFAULT_GROUP
context-path:
# 我们使用第一种鉴权方式:用户名和密码
username: nacos
password: nacos
data-id: seata.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
preferred-networks: 30.240.*
nacos:
# 在Nacos中显示Seata的名称
application: seata-server
server-addr: 172.20.0.4:8848
group: DEFAULT_GROUP
namespace:
cluster: default
context-path: /nacos
username: nacos
password: nacos

启动Seata容器,就可以看到Nacos里已经出现Seata服务了。这时候访问http://宿主IP:7091/也是可以访问到Seata的后台页面的

现在,一个最基本的微服务环境已经完成。

3. 坑点总结

启动Seata容器的时候,SEATA_IP一定是宿主机的IP,否则项目链接的时候会报连接不到Seata服务的异常

1
docker run -itd --name Seata --network myNetwork --ip 172.20.0.6 -e SEATA_IP=192.168.10.254 -p 8091:8091 -p 7091:7091 seataio/seata-server

评论