11 Star 100 Fork 53

文攀 / basis-enhance

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 12.96 KB
一键复制 编辑 原始数据 按行查看 历史

一、功能简介

  • 基于幂等表思想实现了一款较为通用的幂等组件,可适用于大多数case
  • 同一个项目中可同时使用多种幂等方案,比如:
    • 接口A 对数据非常敏感,要求 100% 不能丢数据,那么他可以使用使用基于MySQL的幂等实现方案
    • 接口B对数据不是很敏感,但对性能要求非常高,非常极端的情况下可以容忍一部分error,那么可以采用基于Redis的幂等实现方案
  • 使用依赖倒置,将幂等逻辑核心实现和幂等数据持久化进行分离,可方便的基于核心幂等模块(enhance-boot-idempotent)进行自定义扩展(可基于IdempotentEntity自由的实现扩展)
  • 提供了灵活的API模式供使用者灵活使用幂等功能实现,也提供了基于【注解 + spel】的幂等功能实现,使用者可快速简单的使用注解实现业务幂等
  • 目前提供基于MySQL和Redis的两种存储介质实现幂等功能,使用者可以方便的动态选择(当然也可以自定义实现,仅需实现一个接口即可实现自定义)
    • 基于Redis的幂等实现最大的优点是效率高,能承载更高的并发,当然由于Redis本身的特性,在极端情况下有丢数据的可能(这是选择时需要容忍的)
    • 基于MySQL的幂等实现优点是不会丢失数据,但是效率上要低于Redis
    • 以上两种实现可以按照实际情况选择!!!当然也可以两种方案同时使用,不同的接口可以选择不同的幂等实现
  • 提供可扩展幂等异常处理接口(IdempotentExceptionEventHandler)以供使用方对极端情况下存在的幂等异常进行自定义兜底
  • 无锁实现并且不依赖于事务,可方便灵活进行拓展,幂等数据存储介质不会强依赖某个介质

二、如何引入

1、下载源码并打包到自己的maven仓库

由于项目暂时没有发布到中央maven仓库,所以需要自己mvn install 到自己本地maven仓库

2、pom中引入依赖坐标

  • 下面的配置中引入了基于MySQL和基于Redis的两种幂等实现所需的所有依赖,可以根据自己需要只引入对应的依赖即可
  • 当然如果你项目中已经有了Redis或mysql相关依赖,那么仅需要引入enhance-idempotent-adapter-dbenhance-idempotent-adapter-redis即可
  • 特别说明:如果选择基于MySQL的方案,那么需要引入依赖mybatis(因为暂时使用的是mybatis对幂等记录进行增删改查)
<dependencies>
    <!--web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--==================================使用基于MySQL的幂等组件实现时才引入如下依赖==========================-->
    <!--基于db实现幂等-->
    <dependency>
        <groupId>org.basis.enhance</groupId>
        <artifactId>enhance-idempotent-adapter-db</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--如果项目里已经有了MySQL和mybatis相关依赖和配置,则可以不引入如下配置-->
    <!--mysql相关依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>

    <!--mybatis依赖-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.1</version>
    </dependency>
    <!--==================================使用基于MySQL的幂等组件实现时才引入如上依赖==========================-->

    <!--==================================使用基于redis的幂等组件实现时才引入如下依赖==========================-->
    <!--基于redis实现幂等-->
    <dependency>
        <groupId>org.basis.enhance</groupId>
        <artifactId>enhance-idempotent-adapter-redis</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--如果项目里已经有了redis相关依赖和配置,则可以不引入如下配置-->
    <!--redis核心依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--Apache的 common-pool2 提供连接池,供redis客户端使用-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <!--==================================使用基于redis的幂等组件实现时才引入如上依赖==========================-->
</dependencies>

3、配置application.yml

  • 下面配置了基于MySQL和Redis的两种幂等实现的所有配置,按照你选择的实现选择对应的配置即可,当然你也可以两种实现同时使用,多个实现同时使用时需要指定哪个实现作为primaryRepository
server:
  port: 12345

# 幂等组件核心配置
enhance:
  idempotent:
    # 开启幂等组件
    enable: true
    namespace: idempotent-test
    # 期望的最大执行时间是1天
    maxProcessTime: 1
    unit: DAYS
    # 开启多个实现时,指定哪个是primary
    primaryRepository: redisIdempotentRepository
    # 适配器选择(任选一种模式开启即可)
    adapter:
      redis:
        # 开启基于Redis的幂等实现
        enable: true
        # 幂等记录存放有效时间是120天
        expireTime: 120
        unit: DAYS
      mysql:
        # 开启基于MySQL的幂等实现
        enable: false

spring:
  application:
    name: idempotent-demo
  # mysql连接配置(基于MySQL实现幂等需要MySQL相关连接配置)
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: ${SPRING_DATASOURCE_URL:jdbc:mysql://ip-host:3306/xxxDB?useUnicode=true&characterEncoding=UTF-8&useSSL=false}
    username: ${SPRING_DATASOURCE_USERNAME:xxx}
    password: ${SPRING_DATASOURCE_PASSWORD:xxxx}
  # redis基础配置(基于Redis实现幂等需要Redis相关连接配置)
  redis:
    host: ${SPRING_REDIS_HOST:wenpan-host}
    port: ${SPRING_REDIS_PORT:6379}
    password: ${SPRING_REDIS_PASSWORD:xxxx}
    database: ${SPRING_REDIS_DATABASE:2}
    # 指定client类型
    client-type: lettuce
    lettuce:
      pool:
        # 资源池中最大连接数,默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整
        max-active: ${SPRING_REDIS_POOL_MAX_ACTIVE:16}
        # 资源池运行最大空闲的连接数
        # 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整,一般建议和max-active保持一致,避免资源伸缩带来的开销
        max-idle: ${SPRING_REDIS_POOL_MAX_IDLE:16}
        # 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) 默认 -1 表示永不超时,设置5秒
        max-wait: ${SPRING_REDIS_POOL_MAX_WAIT:5000}

mybatis:
  mapperLocations: classpath*:/mapper/*.xml
  configuration:
    mapUnderscoreToCamelCase: true

4、执行SQL脚本(基于MySQL实现幂等才执行)

SQL脚本位置enhance-boot-idempotent/enhance-idempotent-adapter-db/src/main/resources/sql

CREATE TABLE `business_idempotent` (
  `idempotent_id` bigint NOT NULL AUTO_INCREMENT COMMENT '幂等主键',
  `namespace` varchar(32) DEFAULT NULL COMMENT '命名空间',
  `source` varchar(64) DEFAULT NULL COMMENT '来源',
  `operation_type` varchar(64) DEFAULT NULL COMMENT '操作类型',
  `business_key` varchar(128) NOT NULL COMMENT '业务key',
  `unique_key` varchar(255) NOT NULL COMMENT '唯一键(用于幂等控制)',
  `idempotent_status` tinyint NOT NULL COMMENT '幂等状态(1为处理中,2为处理成功)',
  `object_version_number` bigint NOT NULL COMMENT '版本号',
  `response` blob COMMENT '响应数据',
  `create_date` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `last_modified_date` datetime NOT NULL COMMENT '最近修改时间',
  PRIMARY KEY (`idempotent_id`),
  UNIQUE KEY `udx_unique_key` (`unique_key`) USING BTREE COMMENT '幂等键唯一索引'
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='业务幂等表';

三、如何使用

1、使用IdempotentHelper API

@Slf4j
@RestController("TestRedisIdempotentController.v1")
@RequestMapping("/v1/idempotent")
public class TestIdempotentController {
    /**
     * 测试idempotentHelper执行有返回值的方法
     * url: localhost:12345/v1/idempotent/test-with-result?source=taobao&operationType=publish_product&businessKey=pd_20230105007&name=lisi
     */
    @GetMapping("/test-with-result")
    public String testWithResult(String source,
                                 String operationType,
                                 String businessKey,
                                 String name) {
        // 带返回值的幂等执行
        String result = idempotentHelper.invoke(source, operationType, businessKey,
                () -> testIdempotentService.testIdempotentHelper(name));
        log.info("=============>>>>>>> TestRedisIdempotentController result {}", result);
        return result;
    }
  
    /**
     * 测试idempotentHelper执行没有返回值的方法
     * url : localhost:12345/v1/idempotent/test-with-no-result?source=taobao&operationType=publish_product&businessKey=pd_20230105008&name=zhangsan
     */
    @GetMapping("/test-with-no-result")
    public String testWithNoResult(String source,
                                   String operationType,
                                   String businessKey,
                                   String name) {
        // 不带返回值的幂等执行
        idempotentHelper.invokeWithNoResult(source, operationType, businessKey,
                () -> testIdempotentService.testIdempotentWithNoResult(name));
        log.info("=============>>>>>>> TestRedisIdempotentController testWithNoResult.");
        return "success";
    }
}

2、使用@Idempotent注解

@Slf4j
@Service
public class TestIdempotentAnnotationServiceImpl implements TestIdempotentAnnotationService {

    /**
     * 用orderEntity对象里的orderNumber作为业务幂等键
     */
    @Idempotent(operationType = "'addOrder'", source = "#orderEntity.platformCode", businessKey = "#orderEntity.orderNumber")
    @Override
    public OrderEntity addOrder(OrderEntity orderEntity) {
        log.info("TestIdempotentAnnotationServiceImpl#addOrder orderEntity is : {}", JSON.toJSONString(orderEntity));
        // 模拟修改了订单一些信息
        orderEntity.setCreateDate(LocalDateTime.now());
        orderEntity.setLastModifyDate(LocalDateTime.now());
        orderEntity.setOrderStatus(orderEntity.getOrderStatus() + 1);
        // 新增订单逻辑....

        return orderEntity;
    }

    /**
     * 用orderEntity对象里的orderLineEntityList集合里的第一个元素的skuCode作为业务幂等键
     */
    @Idempotent(operationType = "'modifyOrder'",
            source = "#orderEntity.platformCode",
            businessKey = "#orderEntity.orderLineEntityList[0].skuCode")
    @Override
    public OrderEntity modifyOrder(OrderEntity orderEntity) {
        log.info("TestIdempotentAnnotationServiceImpl#modifyOrder orderEntity is : {}", JSON.toJSONString(orderEntity));
        // 模拟修改了订单一些信息
        orderEntity.setCreateDate(LocalDateTime.now());
        orderEntity.setLastModifyDate(LocalDateTime.now());
        orderEntity.setOrderStatus(orderEntity.getOrderStatus() + 1);
        // 修改订单逻辑....

        return orderEntity;
    }
}

详细案例参考org.enhance.idempotent.demo.api.controller.TestIdempotentControllerorg.enhance.idempotent.demo.app.service.impl.TestIdempotentAnnotationServiceImpl

四、扩展

1、扩展幂等实现

  • 目前只提供了两种实现,一种是基于Redis的幂等实现,一种是基于MySQL的幂等实现
  • 如果想自己基于其他存储介质来实现幂等逻辑(比如:基于MongoDB、Oracle、PG来做幂等实现),那么只需要实现IdempotentRepository接口,实现对应的操作幂等记录的方法,然后注入容器即可!

2、极端情况自定义兜底逻辑扩展

  • 对于极端的幂等异常情况,目前提供了默认的DefaultIdempotentExceptionEventHandler来处理,处理方式是提交到线程池进行日志输出(也可以自己定制线程池覆盖DefaultIdempotentExceptionEventHandler所使用的线程池)
  • 如果要自定义极端情况下的兜底逻辑,那么仅需要实现org.enhance.idempotent.core.handler.IdempotentExceptionEventHandler接口并注入容器即可。

五、期望

Java
1
https://gitee.com/mr_wenpan/basis-enhance.git
git@gitee.com:mr_wenpan/basis-enhance.git
mr_wenpan
basis-enhance
basis-enhance
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891