Docker deploy Seata with Nacos

使用Docker部署Seata,同时使用Nacos作为配置中心,如果在你了解了Nacos和Seata之后开始捣鼓整一个Demo来学习一下,那么你一定是看了Seata的官网的,而且还很有可能是看了Github上的seata和seata-samples项目的,然后开始在本机上把 springcloud-nacos-seata 这个项目尝试跑起来,很遗憾你没能轻易如愿。我之前也是一样,现在把项目真正跑起来的过程整理一遍。

我们就以Seata官网的 一个微服务示例 架构图

然后参照 seata-samples项目的 springcloud-nacos-seata实例代码,做一个梳理。

目标:实现SpringCloud + Seata + Nacos + Mybatis + Druid + Mysql 整合。

说明:
(1)其中 Seata 和 Nacos 均采用Docker 部署,且是单机模式,准确来说是docker-compose部署。
(2)Seata 和 Nacos 版本均是最新版,Seata-1.2.0, Nacos-1.3.0.
Mysql 是5.7,我的用户名/密码:root/123456.
(3)Seata和Nacos是部署在两个不同的容器中,所以不要想着你的localhost,而要用你的宿主机ip (我的IP是192.168.1.2)

具体步骤:
(1)由于使用Nacos作为配置中心,所以第一步是启动nacos这个比较容易,直接使用下面standalone-derby.yaml文件,这个文件你最好不要直接copy,因为你会发现它挂载了其它文件。它来源于
https://github.com/nacos-group/nacos-docker example文件夹,所以你需要先下载这个仓库。至于下面的prometheus和grafana你可以暂且不管,如果你之前学习Nacos就知道了就更好。

version: "2"
services:
  nacos:
    image: nacos/nacos-server:latest
    container_name: nacos-standalone
    environment:
    - PREFER_HOST_MODE=hostname
    - MODE=standalone
    volumes:
    - ./standalone-logs/:/home/nacos/logs
    - ./init.d/custom.properties:/home/nacos/init.d/custom.properties
    ports:
    - "8848:8848"

  prometheus:
    container_name: prometheus
    image: prom/prometheus:latest
    volumes:
      - ./prometheus/prometheus-standalone.yaml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    depends_on:
      - nacos
    restart: on-failure

  grafana:
    container_name: grafana
    image: grafana/grafana:latest
    ports:
      - 3000:3000
    restart: on-failure

Nacos启动之后你就可以验证了:
http://localhost:8848/nacos/ 登录用户名和密码均是nacos

(2)由于Seata采用Nacos作为注册中心,所以在你启动Seata之前,你需要把配置写入到Nacos,当然Seata给你提供了一个shell脚本———nacos-config.sh,它来源于 https://github.com/seata/seata script文件夹,你只需要先配置好config.txt文件,然后一行命令就可以搞定。注意:config.txt和nacos-config.sh在同一目录下。原始的脚本在windows平台下可能需要修改一下,我会上传修改后的。

配置完成之后你可以在Nacos中验证了:找到->配置管理 -> 配置列表

当然上面是最后配置的效果,在此之前,我希望关注一下config.txt文件本身

transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=192.168.1.2:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
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=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.logTable=undo_log
client.log.exceptionRate=100
store.mode=db
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
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.1.2:3306/seata?useUnicode=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
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.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9090

上面的配置是基于我的环境的,你可能需要做一些调整,我想强调几行分别是

(一)service.vgroupMapping.my_test_tx_group=default
说明my_test_tx_group 需要和代码中的tx-service-group一致

(二)store.mode=db
store.db.datasource=druid
store.db.url=jdbc:mysql://192.168.1.2:3306/seata?useUnicode=true
store.db.user=root
store.db.password=123456
说明上面采用db模式,Seata默认采用file模式,所以我们写db来覆盖,同时我们注意到url中有一个Schema是seata,没错我们一会需要在数据库中建好这个Schema同时我们注意到下面还有三张Table,我们一会一同建好。
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.lockTable=lock_table

(三) 既然说到了数据库,那么我们顺便把实例微服务中的数据库也建好,主要是三个Schema,每一个Schema中都有一张业务表和undo_log表。可以先看一下

上面数据库的sql文件其实分为两个部分,一部分是Seata Server作为db模式所需要的seata库(branch_table,global_table,lock_table)及Seata Client (分别是account,order,storage)各自业务表及一张undo_log表。第一部分sql语句可以在Seata的下载文件中找到

第二部分可以在
https://github.com/seata/seata-samples/tree/master/springcloud-nacos-seata

(3)在数据库准备工作完毕后,我们使用docker-compose.yml启动Seata服务器

version: "3.1"

services:

  seata-server:
    image: seataio/seata-server:latest
    hostname: seata-server
    ports:
      - 8091:8091
    volumes:
      - ./config:/root/seata-config
    environment:
      - SEATA_PORT=8091
      - SEATA_IP=192.168.1.2
      - SEATA_CONFIG_NAME=file:/root/seata-config/registry

上面的配置中我们重点关注最后两行,一个是指定了IP,一个是指定了配置文件的位置。然后我们看一下配置文件——registry.conf。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "192.168.1.2"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "192.168.1.2"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

上面配置中
registry.type=nacos,
config.type=nacos,
nacos.serverAddr=”192.168.1.2″
nacos.group= “SEATA_GROUP” (这个和Nacos中相匹配)

(4)我们首先来看一下项目结构。

重点说明: 
(一)讲道理我们也是需要把account服务加上去的,但是用来验证分布式事务Seata的使用情况下,我们用两个就可以了。
(二)上面最主要的registry.conf文件和bootstrap.yml中spring.cloud.alibaba.seata.tx-service-group: ‘my_test_tx_group’(要和前面config.txt中 service.vgroupMapping.my_test_tx_group=default 相呼应)。
(三)再看registry.conf中最重要的部分:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "192.168.1.2"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  # 为了节省空间,其余type 省略
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "192.168.1.2"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
  # 为了节省空间,其余type 省略
}

上面的application = “seata-server”和Nacos配置列表中保持一致

5.启动项目开始测试

(1)分布式事务成功,模拟正常下单、扣库存
localhost:9091/order/placeOrder/commit

(2)分布式事务失败,模拟下单成功、扣库存失败,最终同时回滚
localhost:9091/order/placeOrder/rollback

后记:这次整合Seata和Nacos的确花了一些时间,主要的原因是因为问题>人,自己能量级不够,所以这个问题才是问题,根本原因是因为没有理清思路,在众多配置文件和纷繁的零碎资料中走进了“问题森林”,会重点去查看一棵树,比如调试本地启动脚本jvm参数导致问题,配置文件中某项具体选项同时变化多个等等细节,而忘了初心是找到一个方向迅速走出这片森林。理思路,找方向,这件事一定要在头脑清晰的时候去做,头脑不清晰的时候只能算是“糊涂探索”。

最后希望这篇文章对你有帮助,我会把相关的脚本上传到我的github,欢迎clone.