分布式事务——Dubbo集成hmily使用

374 阅读4分钟

分布式事务——Dubbo集成hmily使用

1. 背景

在市面上分布式事务框架挺多的,但没有哪一款分布式事务框架像MybatisSpring Cloud这样市场占有率高的,因为分布式事务跟业务逻辑是比较贴近的,所以找到一款适合当前业务逻辑、符合技术生态的分布式事务框架就显得尤为重要。

经过收集和了解,决定对seataservicecomb packhmily进行使用对比,一开始使用的是servicecomb pack,但经过实验后,发现如下问题:

  1. 不支持最新的Apache Dubbo;
  2. 文档和网上资料比较少;
  3. 使用不通,报io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found: TccEventService/OnDisconnected,如果有小伙伴有解决过的,麻烦评论或私信下。

然后放弃了servicecomb pack,转向了hmily,先说使用后的结论:

  1. 分布式事务可以走通;
  2. 支持多种RPC框架,且紧跟这些RPC框架的更新,比如支持Apache Dubbo;
  3. 使用简单,且不是服务端——客户端模式,省去了部署服务端的时间和资源;
  4. 符合当前流行的技术生态。

2. 使用

1. 环境

JDK1.8

Apache Dubbo2.7.8

Spring Boot2.3.7.RELEASE

2. 项目结构

maven主要分为4个模块:

digital-api: Dubbo 接口定义

digital-provider: Dubbo提供者A

digital-order-provider: Dubbo提供者B

digital-consumer: Dubbo消费者

3. 父依赖

parent pom主要管理项目中使用到的包版本

pom.xml

<properties>
    <alibaba-spring-cloud.version>2.2.4.RELEASE</alibaba-spring-cloud.version>
    <dubbo.version>2.7.8</dubbo.version>
    <zookeeper.version>3.4.13</zookeeper.version>
    <curator.version>4.0.1</curator.version>
    <hmily.version>2.1.1</hmily.version>
    <mysql.version>5.1.47</mysql.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${alibaba-spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-bom</artifactId>
            <version>${dubbo.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
        </dependency>

        <!-- zookeeper客户端 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>${curator.version}</version>
        </dependency>

        <!-- hmily start -->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-annotation</artifactId>
            <version>${hmily.version}</version>
        </dependency>

        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-spring-boot-starter-apache-dubbo</artifactId>
            <version>${hmily.version}</version>
        </dependency>
        <!-- hmily end -->

        <!-- mysql依赖,用于hmily记录事务日志 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

4. digital-api

pom.xml: 需要用到hmily中的注解

<dependencies>
    <dependency>
        <groupId>org.dromara</groupId>
        <artifactId>hmily-annotation</artifactId>
    </dependency>
</dependencies>

定义接口:这里定义两个接口类,用于之后两个提供者实现,从而模拟两个服务提供者的分布式事务场景

/**
 * 数字藏品订单接口类
 * @author Tarzan写bug
 * @date 2022/11/28
 */
public interface DigitalOrderService {

    /**
     * 铸造
     * @param dto
     * @return
     */
    @Hmily
    boolean casting(CastingDTO dto);
}

/**
 * 数字藏品接口
 * @author Tarzan写bug
 * @date 2022/11/28
 */
public interface DigitalService {

    /**
     * 数字藏品查询
     * @return
     */
    List<CastingDTO> list();
}

官方文档是要在接口方法上标注@Hmily注解,实验过没有注解也是可以正常跑通分布式事务的,不知道是否是跟后面管理界面有关系。

5. digital-provider

pom.xml

<dependencies>
    <dependency>
        <groupId>com.mountain</groupId>
        <artifactId>digital-api</artifactId>
        <version>${project.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
    </dependency>

    <dependency>
        <groupId>org.dromara</groupId>
        <artifactId>hmily-spring-boot-starter-apache-dubbo</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.dromara</groupId>
                <artifactId>hmily-repository-mongodb</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

注意:这里排除掉hmily-repository-mongodb是为了解决下面这个报错:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    org.springframework.boot.autoconfigure.mongo.MongoClientFactorySupport.applyUuidRepresentation(MongoClientFactorySupport.java:85)

The following method did not exist:

    com.mongodb.MongoClientSettings$Builder.uuidRepresentation(Lorg/bson/UuidRepresentation;)Lcom/mongodb/MongoClientSettings$Builder;

The method's class, com.mongodb.MongoClientSettings$Builder, is available from the following locations:

    jar:file:/D:/rts/java-condition-service/apache-maven-3.6.3-bin/rts-maven-repository/org/mongodb/mongo-java-driver/3.8.0/mongo-java-driver-3.8.0.jar!/com/mongodb/MongoClientSettings$Builder.class

The class hierarchy was loaded from the following locations:

    com.mongodb.MongoClientSettings.Builder: file:/D:/rts/java-condition-service/apache-maven-3.6.3-bin/rts-maven-repository/org/mongodb/mongo-java-driver/3.8.0/mongo-java-driver-3.8.0.jar


Action:

Correct the classpath of your application so that it contains a single, compatible version of com.mongodb.MongoClientSettings$Builder

而且没有用到mongodb进行分布式事务日志记录,所以干脆直接排除依赖。

Dubbo配置

/**
 * Dubbo配置
 * @author Tarzan写bug
 * @since 2022/11/28
 */
@Configuration
public class DubboConfiguration {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("digital-provider");
        return applicationConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setId("hmily-digital-provider");
        registryConfig.setAddress("zookeeper://localhost:2181");
        registryConfig.setClient("curator");
        registryConfig.setTimeout(12000000);
        return registryConfig;
    }

    @Bean
    public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setGroup("hmily-dubbo");
        providerConfig.setTimeout(12000000);
        providerConfig.setVersion("1.0.0");
        return providerConfig;
    }

    @Bean
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setGroup("hmily-dubbo");
        consumerConfig.setTimeout(12000000);
        consumerConfig.setVersion("1.0.0");
        consumerConfig.setCheck(false);
        return consumerConfig;
    }
}

接口实现

/**
 * 数字藏品订单实现类
 * @author Tarzan写bug
 * @since 2022/11/28
 */
@DubboService(interfaceClass = DigitalOrderService.class)
public class DigitalOrderServiceImpl implements DigitalOrderService {

    private static final Logger logger = LoggerFactory.getLogger(DigitalOrderServiceImpl.class);

    @DubboReference
    private DigitalService digitalService;

    @HmilyTCC(confirmMethod = "confirm", cancelMethod = "cancel")
    @Override
    public boolean casting(CastingDTO dto) {
//        int num = 1/0;
        digitalService.list();
        return false;
    }

    public boolean confirm(CastingDTO dto) {
        logger.info("DigitalOrderServiceImpl confirm()");
        return true;
    }

    public boolean cancel(CastingDTO dto) {
        logger.info("DigitalOrderServiceImpl cancel()");
        return false;
    }
}

在实现方法上标注@HmilyTCC注解,并定义提交方法回滚方法,这三个方法的参数和返回值都是要一样。

hmily.yml: hmily配置,文件放置在resources目录下,内容如下:

hmily:
  server:
    configMode: local
    appName: digital-provider
  config:
    appName: digital-provider
    serializer: kryo
    contextTransmittalMode: threadLocal
    scheduledThreadMax: 16
    scheduledRecoveryDelay: 60
    scheduledCleanDelay: 60
    scheduledPhyDeletedDelay: 600
    scheduledInitDelay: 30
    recoverDelayTime: 60
    cleanDelayTime: 180
    limit: 200
    retryMax: 10
    bufferSize: 8192
    consumerThreads: 16
    asyncRepository: true
    autoSql: true
    phyDeleted: true
    storeDays: 3
    repository: mysql
  support:
    rpc:
      annotation: true

repository:
  database:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3307/hmily?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: root
    maxActive: 20
    minIdle: 10
    connectionTimeout: 30000
    idleTimeout: 600000
    maxLifetime: 1800000

hmily.server.configMode=local表示读取本地文件配置,还可以使用NacosZookeeper等配置中心的配置,所以这也反映了hmily对当前主流的技术生态的支持。这里使用mysql记录事务日志,也可以使用mongodbredis等存储。

6. digital-order-provider

digital-provider一样

7. digital-consumer

普通Dubbo消费者配置,没有hmily依赖

8. hmily sql

当使用mysql记录事务日志时,需要初始化表,sql文件在hmily源码的hmily-repository\hmily-repository-database\hmily-repository-database-mysql\src\main\resources\mysql目录下。

3. 总结

使用hmily后的第一印象就是简单,而且能用。后面将对hmily的原理进行分析。上述项目代码收录于https://gitee.com/ouwenrts/hmily-demo.git.



世界那么大,感谢遇见,未来可期...

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug

公众号二维码.jpg

淘宝店:提供一元解决Java问题和其他方面的解决方案,欢迎咨询

淘宝店铺.png