IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Redis+token实现接口幂等性 -> 正文阅读

[大数据]Redis+token实现接口幂等性

概述

在秒杀场景或情况下,用户多次发送请求会导致订单表中有多条数据的情况,为此要实现的功能是接口幂等,用户无论发多少次请求都只能创建一个单子

思路

时序图
在这里插入图片描述

首先客户端请求token接口,获取到token,服务端生成token并在redis中存一份,每次请求的时候,客户端将token带过来,由拦截器检验token,token存在redis中则说明是第一次请求,将数据写入数据库中,并删除redis中的token,第二次客户端再携带token时,去redis中查,如果redis中没有,那么说明是第二次请求了返回重复操作提示

demo

首先定义一个幂等性注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}

幂等性拦截器

public class IdempotentTokenInterceptor 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;
        //幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
        ApiIdempotent apiIdempotent = handlerMethod.getMethod().getAnnotation(ApiIdempotent.class);
        if (apiIdempotent != 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 httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }

}

拦截器配置类

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册接口幂等性拦截器
        registry.addInterceptor(idempotentTokenInterceptor());
    }

    @Bean
    public IdempotentTokenInterceptor idempotentTokenInterceptor() {
        return new IdempotentTokenInterceptor();
    }

    /**
     * 跨域
     *
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }

}

token Service类

public interface TokenService {

    String createToken();

    void checkToken(HttpServletRequest request) throws Exception;

}

token ServiceImpl类

@Service
public class TokenServiceImpl implements TokenService {

    private static final String TOKEN_NAME = "token";

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public String createToken() {
        //通过UUID来生成token
        String tokenValue = "idempotent:token:" + UUID.randomUUID().toString();
        //将token放入redis中,设置有效期为60S
        stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);
        return tokenValue;
    }

    /**
     * @param request
     */
    @Override
    public void checkToken(HttpServletRequest request) throws Exception {
        String token = request.getHeader(TOKEN_NAME);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(TOKEN_NAME);
            if (StringUtils.isBlank(token)) {
                //没有携带token,抛异常,这里的异常需要全局捕获
                throw new Exception("非法参数");
            }
        }
        //token不存在,说明token已经被其他请求删除或者是非法的token
        if (!stringRedisTemplate.hasKey(token)) {
            throw new Exception("请勿重复操作");
        }
        boolean del = stringRedisTemplate.delete(token);
        if (!del) {
            //token删除失败,说明token已经被其他请求删除
            throw new Exception("请勿重复操作");
        }
    }

}

测试

这里我们用Jmeter和postfox来做测试,Jmeter做用户并发请求,postfox获取token

首先测试计划中添加变量
在这里插入图片描述

接着在Jmeter线程组中右键添加事务控制器
在这里插入图片描述

在事务控制器中添加HTTP信息头管理器,设置token,token通过apifox拿
在这里插入图片描述
在添加一个HTTP请求,设置好相应的数据参数
在这里插入图片描述

最后在线程组处添加一个查看结果树

在这里插入图片描述

这里模拟的是一个用户请求n的并发情况,本来想模拟n个用户m次请求,但是疏于对jmeter的使用,为此暂且先考虑1个用户n次请求的情况接下来启动看看

在这里插入图片描述

可以看到请求了n次,只有一个数据被写入的状况,也就大体实现了幂等性,往后看看模拟n个用户m次请求的情况,是否是满足的,我想应该是没用问题的
在这里插入图片描述
补充几个StringRedisTemplate的使用方法

第一个参数是key,第二个参数是value,第三个参数时间,第四个参数是时间单位,通常为TimeUnit中的枚举类

stringRedisTemplate.opsForValue().set(tokenValue, "0", 60, TimeUnit.SECONDS);

在这里插入图片描述
stringRedisTemplate.hasKey:判断相应的key是否在Redis中
在这里插入图片描述
stringRedisTemplate.delete(key):在Redis中删除相应的key值,删除成功返回true,否则返回false,如此
在这里插入图片描述

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-16 21:45:45  更:2022-06-16 21:47:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:01:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码