电竞比分网-中国电竞赛事及体育赛事平台

分享

什么是接口的冪等性,如何實現(xiàn)接口冪等性?

 邸彥強 2020-11-22

什么是接口的冪等性,如何實現(xiàn)接口冪等性?

(一)冪等性概念

冪等性原本是數(shù)學(xué)上的概念,用在接口上就可以理解為:同一個接口,多次發(fā)出同一個請求,必須保證操作只執(zhí)行一次。 調(diào)用接口發(fā)生異常并且重復(fù)嘗試時,總是會造成系統(tǒng)所無法承受的損失,所以必須阻止這種現(xiàn)象的發(fā)生。 比如下面這些情況,如果沒有實現(xiàn)接口冪等性會有很嚴(yán)重的后果: 支付接口,重復(fù)支付會導(dǎo)致多次扣錢 訂單接口,同一個訂單可能會多次創(chuàng)建。

(二)冪等性的解決方案

唯一索引 使用唯一索引可以避免臟數(shù)據(jù)的添加,當(dāng)插入重復(fù)數(shù)據(jù)時數(shù)據(jù)庫會拋異常,保證了數(shù)據(jù)的唯一性。

樂觀鎖 這里的樂觀鎖指的是用樂觀鎖的原理去實現(xiàn),為數(shù)據(jù)字段增加一個version字段,當(dāng)數(shù)據(jù)需要更新時,先去數(shù)據(jù)庫里獲取此時的version版本號

select version from tablename where xxx

更新數(shù)據(jù)時首先和版本號作對比,如果不相等說明已經(jīng)有其他的請求去更新數(shù)據(jù)了,提示更新失敗。

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

悲觀鎖 樂觀鎖可以實現(xiàn)的往往用悲觀鎖也能實現(xiàn),在獲取數(shù)據(jù)時進(jìn)行加鎖,當(dāng)同時有多個重復(fù)請求時其他請求都無法進(jìn)行操作

分布式鎖 冪等的本質(zhì)是分布式鎖的問題,分布式鎖正常可以通過redis或zookeeper實現(xiàn);在分布式環(huán)境下,鎖定全局唯一資源,使請求串行化,實際表現(xiàn)為互斥鎖,防止重復(fù),解決冪等。

token機(jī)制 token機(jī)制的核心思想是為每一次操作生成一個唯一性的憑證,也就是token。一個token在操作的每一個階段只有一次執(zhí)行權(quán),一旦執(zhí)行成功則保存執(zhí)行結(jié)果。對重復(fù)的請求,返回同一個結(jié)果。token機(jī)制的應(yīng)用十分廣泛。

(三)token機(jī)制的實現(xiàn)

這里展示通過token機(jī)制實現(xiàn)接口冪等性的案例:github文末自取 首先引入需要的依賴:

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-lang3</artifactId>    <version>3.4</version></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>

3.1、配置請求的方法體和枚舉類

首先配置一下通用的請求返回體

public class Response {    private int status;    private String msg;    private Object data;    //省略get、set、toString、無參有參構(gòu)造方法}

以及返回code

public enum ResponseCode {    // 通用模塊 1xxxx    ILLEGAL_ARGUMENT(10000'參數(shù)不合法'),    REPETITIVE_OPERATION(10001'請勿重復(fù)操作'),    ;    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以及構(gòu)造方法}

配置全局異常捕獲器

@ControllerAdvicepublic 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 編寫創(chuàng)建Token和驗證Token的接口以及實現(xiàn)類

@Servicepublic interface TokenService {    public Response createToken();    public Response checkToken(HttpServletRequest request);}

具體實現(xiàn)類,核心的業(yè)務(wù)邏輯都寫在注釋中了

@Servicepublic class TokenServiceImpl implements TokenService {    @Autowired    private RedisTemplate redisTemplate;    @Override    public Response createToken() {        //生成uuid當(dāng)作token        String token = UUID.randomUUID().toString().replaceAll('-','');        //將生成的token存入redis中        redisTemplate.opsForValue().set(token,token);        //返回正確的結(jié)果信息        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為空就從參數(shù)中獲取            token=request.getParameter('token');            //如果都為空拋出參數(shù)異常的錯誤            if (StringUtils.isBlank(token)){                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getCode().toString(),ResponseCode.ILLEGAL_ARGUMENT.getMsg());            }        }        //如果redis中不包含該token,說明token已經(jīng)被刪除了,拋出請求重復(fù)異常        if (!redisTemplate.hasKey(token)){            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());        }        //刪除token        Boolean del=redisTemplate.delete(token);        //如果刪除不成功(已經(jīng)被其他請求刪除),拋出請求重復(fù)異常        if (!del){            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());        }        return new Response(0,'校驗成功',null);    }}

3.4 配置自定義注解

這是比較重要的一步,通過自定義注解在需要實現(xiàn)接口冪等性的方法上添加此注解,實現(xiàn)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){            // 校驗通過放行,校驗不通過全局異常捕獲后輸出返回結(jié)果            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,添加攔截器

@Configurationpublic class WebConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(apiIdempotentInterceptor());    }    @Bean    public ApiIdempotentInterceptor apiIdempotentInterceptor() {        return new ApiIdempotentInterceptor();    }}

配置redis,使得中文可以正常傳輸

@Configurationpublic class RedisConfig {    //自定義的redistemplate    @Bean(name = 'redisTemplate')    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){        //創(chuàng)建一個RedisTemplate對象,為了方便返回key為string,value為Object        RedisTemplate<String,Object> template = new RedisTemplate<>();        template.setConnectionFactory(factory);        //設(shè)置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);    }}

(四)結(jié)果驗證

首先通過token接口創(chuàng)建一個token出來,此時redis中也存在了該token

什么是接口的冪等性,如何實現(xiàn)接口冪等性?

在jmeter中同時運行50個請求,我們可以觀察到,只有第一個請求校驗成功,后續(xù)的請求均提示請勿重復(fù)操作。

什么是接口的冪等性,如何實現(xiàn)接口冪等性?
什么是接口的冪等性,如何實現(xiàn)接口冪等性?

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多