自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一步一步教你:用 Docker Compose 完成 Seata 的整合部署

開發(fā)
本文中我們將深入探索基于 Docker Compose 整合 Seata 的全過程,不僅會詳細(xì)介紹每一個(gè)步驟,還會剖析其中的關(guān)鍵要點(diǎn)和潛在問題。

在當(dāng)今分布式系統(tǒng)大行其道的時(shí)代,確保數(shù)據(jù)在不同服務(wù)間的一致性和完整性,已然成為開發(fā)者們面臨的關(guān)鍵挑戰(zhàn)之一。分布式事務(wù)管理因此成為了構(gòu)建可靠、高效分布式應(yīng)用不可或缺的一部分。

Seata,作為一款優(yōu)秀的分布式事務(wù)解決方案,為解決分布式環(huán)境下的數(shù)據(jù)一致性問題提供了強(qiáng)大的支持。它致力于提供高性能且易于使用的分布式事務(wù)服務(wù),幫助開發(fā)者輕松應(yīng)對復(fù)雜的業(yè)務(wù)場景。 而 Docker Compose,無疑是容器編排領(lǐng)域的得力助手。它能夠讓我們通過一個(gè)簡單的配置文件,便捷地定義和運(yùn)行多個(gè)相互關(guān)聯(lián)的 Docker 容器,極大地提升了開發(fā)和部署的效率,簡化了環(huán)境管理的復(fù)雜度。 那么,當(dāng)功能強(qiáng)大的 Seata 遇上便捷高效的 Docker Compose,會碰撞出怎樣的火花呢?

在本文中,我們將深入探索基于 Docker Compose 整合 Seata 的全過程,不僅會詳細(xì)介紹每一個(gè)步驟,還會剖析其中的關(guān)鍵要點(diǎn)和潛在問題。無論你是分布式系統(tǒng)開發(fā)的新手,渴望深入了解分布式事務(wù)管理,還是經(jīng)驗(yàn)豐富的開發(fā)者,希望借助容器技術(shù)優(yōu)化現(xiàn)有架構(gòu),相信本文都能為你帶來有價(jià)值的參考和啟發(fā)。

詳解docker-compose整合seata步驟

1. 實(shí)踐案例描述

我們用一共比較簡單的購物下單的實(shí)例介紹一下本文示例的業(yè)務(wù),當(dāng)前業(yè)務(wù)用戶通過接口提交訂單請求,對應(yīng)參數(shù)為如下,分別是用戶賬號id、產(chǎn)品id、購買的產(chǎn)品數(shù)量:

{
    "accountCode": "0932897",
    "productCode": "P001",
    "count": 1
}

然后執(zhí)行如下步驟:

  • 基于傳入的商品id和數(shù)量計(jì)算售價(jià)。
  • 基于用戶id和商品id、數(shù)量生成訂單信息。
  • 基于售價(jià)信息到用戶賬戶上進(jìn)行扣減。
  • 基于傳入的數(shù)量和商品id進(jìn)行庫存扣減。

需要注意的是上述的創(chuàng)建訂單、余額扣減、庫存扣減分別對應(yīng)3個(gè)服務(wù),這也就意味著一個(gè)原子業(yè)務(wù)需要在分布式環(huán)境下保證如下幾個(gè)業(yè)務(wù)合理性:

  • 用戶余額不足扣款直接回滾,將生成的訂單信息銷毀。
  • 商品庫存不足同樣回滾訂單和庫存扣減。

所以我們需要借助seata來實(shí)現(xiàn)分布式事務(wù)以保證分布式事務(wù)的ACID:

這里我們先給出對應(yīng)的業(yè)務(wù)代碼,后續(xù)我們將通過seata保證這塊分布式事務(wù)的ACID:

public void createOrder(OrderDto orderDTO) {
        Order order = new Order();
        BeanUtils.copyProperties(orderDTO, order);
        //調(diào)用產(chǎn)品服務(wù)獲取商品詳情
        ResultData<ProductDTO> productInfo = productFeign.getByCode(orderDTO.getProductCode());
        //計(jì)算總金額
        BigDecimal total = productInfo.getData().getPrice().multiply(new BigDecimal(order.getCount()));
        order.setAmount(total);
        //創(chuàng)建訂單
        save(order);
        //扣減金額
        accountFeign.reduceAccount(orderDTO.getAccountCode(), order.getAmount());
        //扣減商品
        productFeign.deduct(orderDTO.getProductCode(), orderDTO.getCount());

    }

2. 容器編排和基礎(chǔ)環(huán)境配置

既然要用到docker-compose,所以我們就需要創(chuàng)建一個(gè)yml文件,以筆者為例創(chuàng)建一個(gè)名為seata-compose.yaml的文件,筆者都已給出注釋,內(nèi)容如下:

version: "3"
services:
  seata-server:
    image: seataio/seata-server:1.4.2
    ports:
    # 內(nèi)外部端口映射
    - "8091:8091"
    environment:
    # 端口號和seata的ip地址
    - SEATA_PORT=8091
    - SEATA_IP=x.x.x.x
    volumes:
      # 宿主和容器之間registry.conf文件映射地址
    - "/usr/local/seata/seata-config/registry.conf:/seata-server/resources/registry.conf"
    # 宿主和容器之間file.conf文件映射地址
    - "/usr/local/seata/seata-config/file.conf:/seata-server/resources/file.conf"
    expose:
      # 暴露端口號
    - 8091
    # 容器名稱
    container_name: seata-server

可以看到筆者上文配置中registry.conf宿主存放位置在/usr/local/seata/seata-config/,所以我們需要在這個(gè)位置創(chuàng)建registry.conf,以筆者為例,這個(gè)registry.conf內(nèi)容如下,可以看到筆者指明了注冊中心的地址、命名空間id以及分組名。

這里唯一需要的注意的就是registry 上指明的cluster 集群節(jié)點(diǎn)名稱,該配置會將seata綁定到nacos對應(yīng)的default節(jié)點(diǎn)上:

registry {
  # 將seata注冊到nacos上
  type = "nacos"
  nacos {
  # nacos地址
    serverAddr = "ip:8848"
    # 命名空間id
    namespace = "63f0dbe6-ac91-4a2e-a88e-82b76f8187b6"
    # 組名
    group = "DEFAULT_GROUP"
    # 集群節(jié)點(diǎn)名稱
    cluster = "default"
  }
}
config {
  # 通過nacos獲取配置
  type = "nacos"
  nacos {
    serverAddr = "ip:8848"
    namespace = "63f0dbe6-ac91-4a2e-a88e-82b76f8187b6"
    group = "DEFAULT_GROUP"
  }
}

3. 將seata常規(guī)配置存到nacos中

完成上述步驟后,我們的seata已經(jīng)可以注冊到nacos上了。只不過我們還需要在上述的命名空間(63f0dbe6-ac91-4a2e-a88e-82b76f8187b6)創(chuàng)建一個(gè)seataServer.properties的配置文件,將seata存儲設(shè)置為MySQL存儲,對應(yīng)的配置如下注釋所示:

store.mode=db
#-----db-----
store.db.datasource=druid
store.db.dbType=mysql
# 需要根據(jù)mysql的版本調(diào)整driverClassName
# mysql8及以上版本對應(yīng)的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://ip:3306/seata?useUnicode=true&characterEncoding=utf8&cnotallow=1000&socketTimeout=3000&autoRecnotallow=true&useSSL=false
store.db.user= 用戶
store.db.password= 數(shù)據(jù)庫密碼
# 數(shù)據(jù)庫初始連接數(shù)
store.db.minCnotallow=1
# 數(shù)據(jù)庫最大連接數(shù)
store.db.maxCnotallow=20
# 獲取連接時(shí)最大等待時(shí)間 默認(rèn)5000,單位毫秒
store.db.maxWait=5000
# 全局事務(wù)表名 默認(rèn)global_table
store.db.globalTable=global_table
# 分支事務(wù)表名 默認(rèn)branch_table
store.db.branchTable=branch_table
# 全局鎖表名 默認(rèn)lock_table
store.db.lockTable=lock_table
# 查詢?nèi)质聞?wù)一次的最大條數(shù) 默認(rèn)100
store.db.queryLimit=100


# undo保留天數(shù) 默認(rèn)7天,log_status=1(附錄3)和未正常清理的undo
server.undo.logSaveDays=7
# undo清理線程間隔時(shí)間 默認(rèn)86400000,單位毫秒
server.undo.logDeletePeriod=86400000
# 二階段提交重試超時(shí)時(shí)長 單位ms,s,m,h,d,對應(yīng)毫秒,秒,分,小時(shí),天,默認(rèn)毫秒。默認(rèn)值-1表示無限重試
# 公式: timeout>=now-globalTransactionBeginTime,true表示超時(shí)則不再重試
# 注: 達(dá)到超時(shí)時(shí)間后將不會做任何重試,有數(shù)據(jù)不一致風(fēng)險(xiǎn),除非業(yè)務(wù)自行可校準(zhǔn)數(shù)據(jù),否者慎用
server.maxCommitRetryTimeout=-1
# 二階段回滾重試超時(shí)時(shí)長
server.maxRollbackRetryTimeout=-1
# 二階段提交未完成狀態(tài)全局事務(wù)重試提交線程間隔時(shí)間 默認(rèn)1000,單位毫秒
server.recovery.committingRetryPeriod=1000
# 二階段異步提交狀態(tài)重試提交線程間隔時(shí)間 默認(rèn)1000,單位毫秒
server.recovery.asynCommittingRetryPeriod=1000
# 二階段回滾狀態(tài)重試回滾線程間隔時(shí)間  默認(rèn)1000,單位毫秒
server.recovery.rollbackingRetryPeriod=1000
# 超時(shí)狀態(tài)檢測重試線程間隔時(shí)間 默認(rèn)1000,單位毫秒,檢測出超時(shí)將全局事務(wù)置入回滾會話管理器
server.recovery.timeoutRetryPeriod=1000

# 指定SeaTa的命名空間
seata.config.nacos.namespace=63f0dbe6-ac91-4a2e-a88e-82b76f8187b6

4. 創(chuàng)建seata配置庫存儲分支事務(wù)和全局事務(wù)表

上文配置中我們指明一個(gè)名為seata_config的數(shù)據(jù)庫,所以我們就需要到創(chuàng)建一個(gè)名為seata_config的數(shù)據(jù)庫并刷入分支事務(wù)和全局事務(wù)表以及l(fā)ock_table表:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- 分支事務(wù)表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime(6) NULL DEFAULT NULL,
  `gmt_modified` datetime(6) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- 全局事務(wù)表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` int(11) NULL DEFAULT NULL,
  `begin_time` bigint(20) NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;




CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `branch_id` bigint NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

SET FOREIGN_KEY_CHECKS = 1;

5. 創(chuàng)建undo.log表

上述步驟我們完成了seata相關(guān)的功能維護(hù)的配置,以本文的AT模式為例,為保證每個(gè)分支事務(wù)在回滾時(shí)都能準(zhǔn)確還原,seata參照MySQL的mvcc設(shè)計(jì)思想提出undo.log的概念,如果需要實(shí)現(xiàn)AT模式,我們需要針對每一個(gè)分支事務(wù)的數(shù)據(jù)庫刷入下面這張undo_log表:

-- 日志文件表--
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    ) ENGINE = INNODB
    AUTO_INCREMENT = 1
    DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

6. 啟動seata將其注冊到nacos中

完成這些步驟之后,我們就可以啟動seata容器查看是否注冊到容器中,我們在seata-compose.yaml文件所在的路徑鍵入這條命令:

docker-compose -f seata-compose.yaml up -d

完成這條命令后,我們通過docker ps獲取到seata的id值,以筆者為例,容器的id為8c48c75d07ad,所以我們鍵入:

docker logs 8c48c75d07ad

如下圖所示,如果正常讀取到registry.conf文件以及輸出端口號,就說明啟動成功了。

查看nacos對應(yīng)命名空間的服務(wù)列表,可以看到seata-server已經(jīng)成功注冊了,自此我們的seata就已經(jīng)部署成功了。

7. 服務(wù)注冊到seata

完成這些步驟后,我們就可以將本地服務(wù)注冊到seata中,首先服務(wù)必須引入依賴seata-spring-boot-starter,只有引入這個(gè)依賴才會自動裝配seata相關(guān)組件確保服務(wù)可以注冊到seata中。

<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!--版本較低,1.3.0,因此排除-->
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--版本在父工程中配置,seata starter 采用1.4.2版本-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>

然后修改每個(gè)服務(wù)的yml文件配置,如下所示,這里需要注意一點(diǎn),因?yàn)楣P者在上文seataServer.properties指定事務(wù)分組名稱為seata-demo,所以我們這里的tx-service-group也是seata-demo。然后vgroup-mapping也指明seata-demo和我們nacos集群(筆者在上文registry.conf將cluster配置為default)的映射關(guān)系。

具體配置如下所示:

seata:
  # TC服務(wù)注冊中心的配置,微服務(wù)根據(jù)這些信息去注冊中心獲取tc服務(wù)地址
  registry:
    # 注冊中心類型 nacos
    type: nacos
    nacos:
      # nacos地址
      server-addr: ip:8848
      # namespace,默認(rèn)為空
      namespace: 63f0dbe6-ac91-4a2e-a88e-82b76f8187b6
      # 配置組
      group: DEFAULT_GROUP
      # seata服務(wù)名稱
      application: seata-server
      username: nacos
      password: 密碼
  config:
    type: nacos
    nacos:
      server-addr: ip:8848
      group : "DEFAULT_GROUP"
      namespace: "63f0dbe6-ac91-4a2e-a88e-82b76f8187b6"
      dataId: "seataServer.properties"
      username: "nacos"
      password: "密碼"
    # 事務(wù)組名稱
  tx-service-group: seata-demo
  service:
    vgroup-mapping: # 事務(wù)組與cluster的映射關(guān)系
      seata-demo: default
    grouplist.seata-server: ip:8091
  data-source-proxy-mode: AT

注意yml文件對縮進(jìn)的格式要求很高,讀者可以參考筆者的配置進(jìn)行修改,筆者本次部署時(shí)遇到服務(wù)始終無法注冊到seata中,控制臺持續(xù)輸出can not get cluster name in registry config xxxx, please make sure registry config correct

經(jīng)過查閱源碼NettyClientChannelManager的代碼段,大抵推測yml配置沒有生效,排查半天得出yml縮進(jìn)有問題。

//筆者這里debug進(jìn)去發(fā)現(xiàn)group取的SEATA-GROUP和我們的指定的DEFAULT-GROUP不一樣
 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;
            }

筆者查閱github一些issue發(fā)現(xiàn),上面這個(gè)問題可能還需要補(bǔ)充這樣一個(gè)步驟:

在上文配置的命名空間中增加一條配置,data-id為service.vgroupMapping.事務(wù)分組名稱,以筆者為例就是service.vgroupMapping.seata-demo,內(nèi)容為default

8. 啟動服務(wù)將其注冊到seata中

完成后啟動服務(wù),以筆者的order-service為例,啟動后如果seata日志中輸出這樣一段話,則說明啟動成功了。

16:31:15.596  INFO --- [rverHandlerThread_1_1_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://ip:3306/cloud_alibaba', applicatinotallow='order-service', transactinotallow='seata-demo'},channel:[id: 0x25ec75ef, L:/172.23.0.5:8091 - R:/220.200.39.1:30735],client version:1.4.2
16:31:15.732  INFO --- [rverHandlerThread_1_2_500] i.s.c.r.processor.server.RegRmProcessor  : RM register success,message:RegisterRMRequest{resourceIds='jdbc:mysql://ip:3306/cloud_alibaba', applicatinotallow='order-service', transactinotallow='seata-demo'},channel:[id: 0x25ec75ef, L:/172.23.0.5:8091 - R:/220.200.39.1:30735],client version:1.4.2

其他服務(wù)同理,都完成后,我們的下單服務(wù)代碼加一個(gè)GlobalTransactional注解,即可完成分布式事務(wù)了,感興趣的讀者可以在扣款或者庫存扣減調(diào)用上設(shè)置一個(gè)錯(cuò)誤的調(diào)用,最終都會看到訂單回滾且當(dāng)前用戶的余額和庫存都不會有扣減:

@Override
    @GlobalTransactional
    @Transactional(rollbackFor = RuntimeException.class)
    public void createOrder(OrderDto orderDTO) {
        Order order = new Order();
        BeanUtils.copyProperties(orderDTO, order);
        //調(diào)用產(chǎn)品服務(wù)獲取商品詳情
        ResultData<ProductDTO> productInfo = productFeign.getByCode(orderDTO.getProductCode());
        //計(jì)算總金額
        BigDecimal total = productInfo.getData().getPrice().multiply(new BigDecimal(order.getCount()));
        order.setAmount(total);
        //創(chuàng)建訂單
        save(order);
        //扣減金額
        accountFeign.reduceAccount(orderDTO.getAccountCode(), order.getAmount());
        //扣減商品
        productFeign.deduct(orderDTO.getProductCode(), orderDTO.getCount());

    }
責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2009-07-06 19:29:37

云計(jì)算私有云服務(wù)器虛擬化

2024-12-02 14:48:30

Docker鏡像文件

2024-09-13 15:20:46

2018-03-07 15:24:41

PythonMySQL

2009-12-17 08:57:28

Windows 7磁盤分區(qū)

2024-07-22 11:43:28

LVMPnetLab網(wǎng)絡(luò)

2022-08-29 15:19:09

CSS煙花動畫

2024-11-01 11:40:11

2020-10-28 15:03:25

C+代碼開發(fā)

2013-11-11 13:24:21

cocos2dx 2.Android編譯

2013-03-18 16:09:27

JavaEEOpenfire

2019-09-18 07:20:34

深度學(xué)習(xí)人臉識別人工智能

2012-03-22 10:33:33

思杰XenDesktop

2015-07-30 09:35:24

滑動返回代碼

2011-06-07 16:03:48

匿名SQL Server

2013-01-06 09:43:35

MySQLMySQL遷移Redis

2022-09-30 15:37:19

Web網(wǎng)站服務(wù)器

2019-03-05 14:09:27

Docker存儲容器

2019-07-09 15:23:22

Docker存儲驅(qū)動

2010-10-08 14:48:32

TFSVisual Stud
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號