什么是接口幂等性?_第3页回答

2021-02-24 18:21发布

25条回答
梦到内河_
2楼 · 2021-02-26 16:11

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

请叫我雷锋叔叔啊
3楼 · 2021-02-26 17:34

  接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

猫的想法不敢猜
4楼 · 2021-02-28 12:43

 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

kitidog2016
5楼 · 2021-03-05 10:08

 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性

天才小馒头
6楼 · 2021-03-05 15:42

 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

来几粒速效枸杞
7楼 · 2021-03-21 20:28

接口幂等性就是用户对同一操作发起了一次或多次请求的对数据的影响是一致不变的,不会因为多次的请求而产生副作用。 副作用:可以认为多次请求操作,每一次对数据状态都会产生影响 。

pipi雪
8楼 · 2021-04-27 10:05

接口调用存在的问题

 现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果是否要统一呢?那是肯定的!尤其在支付场景。

什么是接口幂等性

 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性

什么情况下需要保证接口的幂等性

 在增删改查4个操作中,尤为注意就是增加或者修改,


我的网名不再改
9楼 · 2021-05-14 16:32

微信搜索《Java鱼仔》,每天一个知识点不错过


每天一个知识点

什么是接口的幂等性,如何实现接口幂等性?


(一)幂等性概念

幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。

调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。

比如下面这些情况,如果没有实现接口幂等性会有很严重的后果:

支付接口,重复支付会导致多次扣钱

订单接口,同一个订单可能会多次创建。


(二)幂等性的解决方案

唯一索引

使用唯一索引可以避免脏数据的添加,当插入重复数据时数据库会抛异常,保证了数据的唯一性。


乐观锁

这里的乐观锁指的是用乐观锁的原理去实现,为数据字段增加一个version字段,当数据需要更新时,先去数据库里获取此时的version版本号


select version from tablename where xxx

1

更新数据时首先和版本号作对比,如果不相等说明已经有其他的请求去更新数据了,提示更新失败。


update tablename set count=count+1,version=version+1 where version=#{version}

1

悲观锁

乐观锁可以实现的往往用悲观锁也能实现,在获取数据时进行加锁,当同时有多个重复请求时其他请求都无法进行操作


分布式锁

幂等的本质是分布式锁的问题,分布式锁正常可以通过redis或zookeeper实现;在分布式环境下,锁定全局唯一资源,使请求串行化,实际表现为互斥锁,防止重复,解决幂等。


token机制

token机制的核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。token机制的应用十分广泛。


(三)token机制的实现

这里展示通过token机制实现接口幂等性的案例:github文末自取

首先引入需要的依赖:


    org.springframework.boot

    spring-boot-starter-data-redis

    org.apache.commons

    commons-lang3

    3.4

    org.springframework.boot

    spring-boot-starter-web


3.1、配置请求的方法体和枚举类

首先配置一下通用的请求返回体


public class Response {

    private int status;

    private String msg;

    private Object data;

    //省略get、set、toString、无参有参构造方法

}


以及返回code


public enum ResponseCode {

    // 通用模块 1xxxx

    ILLEGAL_ARGUMENT(10000, "参数不合法"),

    REPETITIVE_OPERATION(10001, "请勿重复操作"),

    ;

    ResponseCode(Integer code, String msg) {

        this.code = code;

        this.msg = msg;

    }

    private Integer code;

    private String msg;

    public Integer getCode() {

        return code;

    }

    public void setCode(Integer code) {

        this.code = code;

    }

    public String getMsg() {

        return msg;

    }

    public void setMsg(String msg) {

        this.msg = msg;

    }

}


3.2 自定义异常以及配置全局异常类

public class ServiceException extends RuntimeException{

    private String code;

    private String msg;

    //省略get、set、toString以及构造方法

}


配置全局异常捕获器


@ControllerAdvice

public class MyControllerAdvice {

    @ResponseBody

    @ExceptionHandler(ServiceException.class)

    public Response serviceExceptionHandler(ServiceException exception){

        Response response=new Response(Integer.valueOf(exception.getCode()),exception.getMsg(),null);

        return response;

    }

}


3.3 编写创建Token和验证Token的接口以及实现类

@Service

public interface TokenService {

    public Response createToken();

    public Response checkToken(HttpServletRequest request);

}


具体实现类,核心的业务逻辑都写在注释中了


@Service

public class TokenServiceImpl implements TokenService {


    @Autowired

    private RedisTemplate redisTemplate;


    @Override

    public Response createToken() {

        //生成uuid当作token

        String token = UUID.randomUUID().toString().replaceAll("-","");

        //将生成的token存入redis中

        redisTemplate.opsForValue().set(token,token);

        //返回正确的结果信息

        Response response=new Response(0,token.toString(),null);

        return response;

    }


    @Override

    public Response checkToken(HttpServletRequest request) {

        //从请求头中获取token

        String token=request.getHeader("token");

        if (StringUtils.isBlank(token)){

            //如果请求头token为空就从参数中获取

            token=request.getParameter("token");

            //如果都为空抛出参数异常的错误

            if (StringUtils.isBlank(token)){

                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getCode().toString(),ResponseCode.ILLEGAL_ARGUMENT.getMsg());

            }

        }

        //如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常

        if (!redisTemplate.hasKey(token)){

            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());

        }

        //删除token

        Boolean del=redisTemplate.delete(token);

        //如果删除不成功(已经被其他请求删除),抛出请求重复异常

        if (!del){

            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());

        }

        return new Response(0,"校验成功",null);

    }

}


3.4 配置自定义注解

这是比较重要的一步,通过自定义注解在需要实现接口幂等性的方法上添加此注解,实现token验证


@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface ApiIdempotent {

}


接口拦截器


public class ApiIdempotentInterceptor implements HandlerInterceptor {

    @Autowired

    private TokenService tokenService;


    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {

            return true;

        }

        HandlerMethod handlerMethod= (HandlerMethod) handler;

        Method method=handlerMethod.getMethod();

        ApiIdempotent methodAnnotation=method.getAnnotation(ApiIdempotent.class);

        if (methodAnnotation != null){

            // 校验通过放行,校验不通过全局异常捕获后输出返回结果

            tokenService.checkToken(request);

        }

        return true;

    }


    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }


    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}


3.5 配置拦截器以及redis

配置webConfig,添加拦截器


@Configuration

public class WebConfig implements WebMvcConfigurer {

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(apiIdempotentInterceptor());

    }


    @Bean

    public ApiIdempotentInterceptor apiIdempotentInterceptor() {

        return new ApiIdempotentInterceptor();

    }

}


配置redis,使得中文可以正常传输


@Configuration

public class RedisConfig {

    //自定义的redistemplate

    @Bean(name = "redisTemplate")

    public RedisTemplate redisTemplate(RedisConnectionFactory factory){

        //创建一个RedisTemplate对象,为了方便返回key为string,value为Object

        RedisTemplate template = new RedisTemplate<>();

        template.setConnectionFactory(factory);

        //设置json序列化配置

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new

                Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper=new ObjectMapper();

        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);

        //string的序列化

        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();

        //key采用string的序列化方式

        template.setKeySerializer(stringRedisSerializer);

        //value采用jackson的序列化方式

        template.setValueSerializer(jackson2JsonRedisSerializer);

        //hashkey采用string的序列化方式

        template.setHashKeySerializer(stringRedisSerializer);

        //hashvalue采用jackson的序列化方式

        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;

    }

}


最后是controller


@RestController

@RequestMapping("/token")

public class TokenController {

    @Autowired

    private TokenService tokenService;


    @GetMapping

    public Response token(){

        return tokenService.createToken();

    }


    @PostMapping("checktoken")

    public Response checktoken(HttpServletRequest request){

        return tokenService.checkToken(request);

    }

}


其余代码在文末github链接上自取


(四)结果验证

首先通过token接口创建一个token出来,此时redis中也存在了改token

在这里插入图片描述



在jmeter中同时运行50个请求,我们可以观察到,只有第一个请求校验成功,后续的请求均提示请勿重复操作。

在这里插入图片描述

在这里插入图片描述




jmeter压测文件(Token Plan.jmx)和代码自取:github自取



相关问题推荐

  • 回答 36

    看军事新闻的同学应该都知道,一艘航空母舰作战能力虽然很强,但是弱点太明显,就是防御能力太差,单艘的航空母舰很少单独行动,通常航空母舰战斗群才是主要军事力量,你可以把单艘航母理解为的单体应用(防御差,机动性不好),把航母战斗群(调度复杂,维护...

  • 回答 31

    初始化过程细节:首先进行的就是将服务装载到容器中,然后准备注册服务。和Spring中启动过程类似,Spring启动时,将bean装载进容器中的时候,首先要解析bean。所以dubbo也是先读配置文件解析服务。解析服务:基于dubbo.jar内的META-INF/spring.handlers配置,...

  • 回答 27

    分布式事务 指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上 。换成比较容易理解的话,就是多个事务之间再保持事务的特性,也就是多个事务之间保证结果的一致性。分布式事务解决方案1、基于XA协议的两阶...

  • 回答 21

    前提是另外一个bean在bean容器中能找到

  • 回答 22

    1.View——表示层1.1准备数据实现方式:struts,servlet等1.2显示数据实现方式:extjs,jsp,jquery,html等2.Service——业务层实现方式:drools等3.Dao——数据访问层实现方式:hibernate、mybatis等...

  • SpringCloud Netflix和Sprin2020-11-12 09:41
    回答 8
    已采纳

    SpringCloud Netflix和SpringCloud Alibaba的区别如下图:

  • 回答 16

    哨兵(Sentinel)是 redis 的高可用性解决方案,前面我们讲的主从复制它是高可用的基础,需要人工介入才能完成故障转移,哨兵可以解决这个问题,在主从复制情况下,当主节点发生故障时,哨兵可以自动的发现故障并且完成故障转移,实现真正的 redis 高可用。在...

  • 回答 8

    redis为什么会有高并发问题redis的出身决定Redis是一种单线程机制的nosql数据库,基于key-value,数据可持久化落盘。由于单线程所以redis本身并没有锁的概念,多个客户端连接并不存在竞争关系,但是利用jedis等客户端对redis进行并发访问时会出现问题。发生连...

  • 回答 8

    用dubbo是想利用分布式集群的形式来提高服务的并发量,适用与大型项目.如果不用它还想提高并发另一个解决方案是springCloud+微服务.适合大中小型项目.当前对并发要求的项目还是很多的,所以dubbo用的也相对较多.建议如果是初学者可以跳过dubbo直接学习第二个方...

  • 回答 4

    不需要。一般是service分出去。然后其它放在web层即一个jar为service业务处理,一个为web层war包

  • 回答 7

    在回答这个问题之前,我们先回答一下什么是锁。普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问的变量或代码片段叫做临界区域,我们需要控制线程一个一个的顺序执行,否则会出现并发问题。如何控制呢?就是设置一个各个线...

  • 回答 6

     springmvc位于表现层,主要与浏览器进行交互(接收和响应浏览器请求)。springmvc采用MVC设计模型,模型由model、view和controller组成。         model(模型):对应JavaBean         view(视图):对应JSP         controller(控制器)...

没有解决我的问题,去提问