幂等性设计与实现

前言

幂等性(Idempotence)是一个在计算机科学中使用的术语。当某个操作无论进行一次或多次都产生相同的结果,我们就说这个操作是幂等的。

例如,删除文件的操作就是幂等的,因为无论你尝试删除一次还是两次,结果都是文件被删除。相对地,计数器增加操作就不是幂等的,因为每次操作,计数器的值都会改变。

在分布式系统和网络协议中,幂等性是非常重要的特性。由于网络延迟,服务端可能会接收到同一条消息的多个副本,如果此类操作是幂等的,那么就不会出现任何问题。

解决方案:

    1. 全局唯一ID :为每个请求分配一个唯一的任务 ID,并确保服务器对每个任务只执行一次。如果服务器接收到相同的任务 ID,它将忽略这个任务或返回之前任务的结果。
    1. Token 机制: 根据业务的操作和内容生成一个Token值(全局唯一ID,执行之前判断方法是否执行过。
    1. 发送确认消息:客户端在发送完请求后并不立即进行下一步操作,而是等待服务器的确认消息。如果客户端没有收到确认消息,那么它会重新发送请求。
    1. 使用分布式锁或事务:通过这种方式,可以确保一次只有一个任务在执行,避免因重复执行同一个任务而产生的问题。
    1. 使用乐观锁:乐观锁对数据进行更新时,会检查数据在此期间是否被别人修改过,如果被修改过,则拒绝更新。
    1. 使用“compare-and-swap”等原子操作:原子操作可以在不使用锁的情况下完成复杂的更新操作,这也可以保证幂等性。

幂等可以用来解决什么问题?
幂等性可以用于解决在分布式系统中的重复请求问题。在分布式系统中,由于网络等原因,可能会导致同一个请求被多次执行,从而可能会造成脏数据或资源浪费等问题。通过实现幂等性,可以确保相同的请求只被执行一次,从而防止出现重复请求的情况。这样可以提高系统的稳定性、可靠性和性能。另外,幂等性也可以用于实现事务的原子性,确保事务只被执行一次,从而防止出现数据不一致的情况。

1.全局唯一ID

全局唯一ID的生成,需要根据实际的业务需要进行生成,单机的部署一般使用UUID或者数据库自增ID就ok,分布式下现在比较流行的雪花算法生成全局唯一ID。单机也需要考虑后期业务的扩展进行使用全局唯一ID,根据阿里规范:单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表

1.1 前端防止重复提交

一般开发中,前端也会做一些简单防止重复提交操作,一般的操作就是提交请求后禁用操作。此处就不在赘述,比较简单,此做法不能解决问题,只能简单解决人为的操作。
解决不了由于网络波动,网络重试机制导致数据不一致问题。

1.2 token机制

Token的机制通过进行业务操作之前进行根据业务生成一个token,token可以使用雪花算法进行生成,进行业务操作之前先获取token,获取成功请求业务携带token,请求,后台会根据逻辑进行有效操作的判断。后端判断该 token 是否存在,如存在,则为第一次提交,放行并删除token,如无token,第二次提交,阻拦该请求。

时序图
高并发环境下还需要考虑操作的原子性。
此处使用redis来存生成的token。
安装和具体的操作参考:
centos7下安装redis以及本地连接注意事项
Springboot整合redis

简单token服务代码

/**
 * token服务
 */
@Service
public class TokenService {

    @Autowired
    RedisService redisService;

    /**
     * 获取token
     * @param bizType
     * @return
     */
    public String getToken(String bizType){
        ///业务的key
        String key = bizType+":"+UUID.randomUUID().toString();
        redisService.saveKeyValue(key,1,10);
        return key;
    }
    /**
     * 校验token
     * @param token
     * @return
     */
    public boolean verifyToken(String token){
        ///token
        String value = redisService.getValueByKey(token);
        //判断值是否存在
        if(!StringUtils.isEmpty(value)){
            //存在删除
            redisService.delete(token);
            return true;
        }
        return false;
    }

controller:

@RestController
public class TokenController {

    @Autowired
    TokenService tokenService;

    @GetMapping("/getToken/{bizType}")
    public String getToken(@PathVariable("bizType") String bizType){
      return tokenService.getToken(bizType);
    }
}

请求乱码

1.3 数据库表加唯一约束

比如用户表加上身份证号作为唯一的约束,也可以防止重复提交用户注册。对于一些业务唯一不明确的也能导致不幂等性。

2.幂等下 ABA问题 与乐观锁

ABA问题:是在并发环境中经常会遇到的问题,最典型的例子就是CAS(Compare And Swap)操作。CAS操作是一种乐观加锁机制,它执行时首先把某个内存值A读入到未提交缓冤区,在此期间可能其他线程也修改了这个内存场所的值变成了B,若这个时候重来执行CAS操作,尽管它能检测到这个内存值已经被修改过,但修改过后的这个值B又被其他线程改变成了A,这样CAS操作就可能错误地成功。这就是ABA问题。

2.1 乐观锁

乐观锁一种并发控制技术。在写入数据时,并不加锁,而是假定没有其他线程与其同时修改数据。当操作系统执行写入操作时,它会检查没有线程已经修改从开头到现在的数据。如果检测到其他线程已经修改了该数据,那么操作系统就会拒绝写入,并通知失败。需要重试直到没有其他线程修改需要写入的数据。
乐观锁主要解决了悲观锁长期占据锁而导致其他线程长时间等待锁的问题。

2.2 如何解决ABA问题?

  1. 使用版本号:在每个变量后面追加上版本号,每次修改该变量都对版本号加一,这就能解决ABA问题了。
  2. 使用原子类:Java从1.5开始提供了一个原子包(java.util.concurrent.atomic),在这个包中提供了一些原子类,其中ABA问题可以使用类AtomicStampedReference进行解决,它通过控制变量的版本解决了ABA问题。

3.分布式锁和事务

3.1 分布式锁:

其主要目的是在系统内提供一种机制,来确保在任何时刻只有一个节点可以执行某个程序区块,所以分布式锁主要目的是为了防止并发操作引发的数据不一致问题。
分布式锁通过一种协议来决定哪一个请求可以获得锁,以执行相应的操作。例如,一个典型的应用程序可能需要对数据库中的一个数据进行更新,程序在进行更新操作的时候会先尝试获取分布式锁,如果获取成功则进行操作,如果获取失败则等待或者抛出错误。

3.2. 分布式事务

事务的核心是:ACID原则(原子性、一致性、隔离性、持久性)。

在一个分布式系统中,为了保证跨多个节点的数据一致性和操作可靠性,通常需要引入一种机制——分布式事务。该机制可以确保分布式系统中多个操作要么全都成功执行,要么全都不执行(如果某处失败,其他所有成功的操作也需要回滚)。

分布式锁主要用于保证在多节点环境下,某一时刻只有一个节点能够操作某资源,用于解决并发问题;而分布式事务,主要用于确保在多节点环境下,一个业务流程中包含的多个操作要么全部成功,要么全部失败,从而保证数据的一致性。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
在Java中设计接口,可以使用自定义注解来实现。可以参考引用中的自定义注解`@Idempotent`。这个注解可以应用在方法上,用于标识该方法是否需要实现。注解中的属包括: - `isIdempotent`: 用于指示是否需要对该方法进行处理,默认为false,表示不需要处理。 - `expireTime`: 用于指定的有效期,单位为秒,默认为1秒。这个有效期要大于方法执行的时间,以确保在有效期内重复的请求不会被处理。 - `timeUnit`: 用于指定有效期的时间单位,默认为秒。 - `info`: 用于指定提示信息,可以自定义,默认为"重复请求,请稍后重试"。 - `delKey`: 用于指示在业务完成后是否删除处理所使用的key,默认为false,表示不删除。 通过在需要实现的方法上添加`@Idempotent`注解,并根据业务需求配置相应的属,可以实现对接口的处理。这样,在重复的请求中,系统可以根据的处理逻辑返回同一个结果或报错,以避免对系统资源的重复影响。123 #### 引用[.reference_title] - *1* *2* *3* [【技术应用】java接口实现方案](https://blog.csdn.net/weixin_37598243/article/details/128403043)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小刘同学要加油呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值