前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot整合redis解决订单重复请求的问题

springboot整合redis解决订单重复请求的问题

作者头像
猫头虎
发布2024-04-07 20:49:49
1440
发布2024-04-07 20:49:49
举报

摘要:

本文探讨了使用Spring Boot整合Redis来解决订单重复请求问题。通过利用Redis的缓存和分布式锁特性,可以有效地防止因网络延迟或其他原因导致的重复订单请求,从而提高系统的可靠性和稳定性。

引言:

在现代的分布式系统中,订单重复请求是一个常见的问题,可能会导致不必要的资源浪费和数据不一致。为了解决这个问题,本文将介绍如何使用Spring Boot整合Redis来有效地处理订单重复请求。通过在系统中引入分布式锁机制和缓存,可以确保每个订单请求只会被处理一次,从而保证了系统的数据一致性和稳定性。

实现思路:

  1. 引入Redis依赖:在Spring Boot项目中引入Redis相关的依赖,以便使用Redis作为缓存和分布式锁的存储介质。
  2. 实现分布式锁:使用Redis的原子操作特性,实现一个分布式锁,确保同一订单的请求在同一时间内只能被处理一次。
  3. 检查订单状态:在处理订单请求之前,先检查订单的处理状态,避免已经处理过的订单再次被处理。
  4. 缓存订单信息:将已处理的订单信息缓存到Redis中,设置合适的过期时间,以避免重复请求在一段时间内被处理。

实现思路:将请求的json数据,去除一些可变字段,将key升序排序,拼接成字符串并进行md5加密,再拼接一些用户信息,这样相同的请求参数得到的加密串必然一致,将此字符串作为key,存入redis,设置过期时间为1秒,一般重复提交都是在1000ms以内; 代码部分

代码语言:javascript
复制
import com.alibaba.fastjson.JSONObject;
import com.wang.learn.cloudredis.entity.Book;
import com.wang.learn.cloudredis.utils.ReqDedupHelper;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.validation.Valid;

/**
 * ClassName RedisController
 * Description
 *

 */
@RestController
@RequestMapping("/redis")
public class RedisController {


    @Resource
    private RedisTemplate<String, Object> stringRedisTemplate;

    @RequestMapping("/repeat")
    public Object repeat(@RequestBody @Valid Book book){
        //用户
        String userId= "12345678";
        //接口名
        String method = "pay";
        //计算请求参数摘要,其中剔除里面请求时间的干扰
        String dedupMD5 = ReqDedupHelper.dedupParamMD5(JSONObject.toJSONString(book),"time");
        String KEY = "dedup:U=" + userId + "M=" + method + "P=" + dedupMD5;
        // 1000毫秒过期,1000ms内的重复请求会认为重复
        long expireTime =  1000;
        long expireAt = System.currentTimeMillis() + expireTime;

        String val = "expireAt@" + expireAt;
        // NOTE:直接SETNX不支持带过期时间,所以设置+过期不是原子操作,极端情况下可能设置了就不过期了,后面相同请求可能会误以为需要去重,所以这里使用底层API,保证SETNX+过期时间是原子操作
        Boolean firstSet = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime),
                RedisStringCommands.SetOption.SET_IF_ABSENT));

        final boolean isConsiderDup;
        if (firstSet != null && firstSet) {
            return book;
        } else {
            return "订单重复!";
        }
    }

}
代码语言:javascript
复制
import com.alibaba.fastjson.JSON;
import com.wang.learn.cloudredis.entity.Book;
import lombok.extern.slf4j.Slf4j;

import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;

 
@Slf4j
public class ReqDedupHelper {

    /**
     *
     * @param reqJSON 请求的参数,这里通常是JSON
     * @param excludeKeys 请求参数里面要去除哪些字段再求摘要(如时间戳字段)
     * @return 去除参数的MD5摘要
     */
    public static String dedupParamMD5(final String reqJSON, String... excludeKeys) {
        String decreptParam = reqJSON;

        TreeMap paramTreeMap = JSON.parseObject(decreptParam, TreeMap.class);
        if (excludeKeys!=null) {
            List<String> dedupExcludeKeys = Arrays.asList(excludeKeys);
            if (!dedupExcludeKeys.isEmpty()) {
                for (String dedupExcludeKey : dedupExcludeKeys) {
                    if(paramTreeMap.containsKey(dedupExcludeKey)){
                        paramTreeMap.remove(dedupExcludeKey);
                    }
                }
            }
        }

        String paramTreeMapJSON = JSON.toJSONString(paramTreeMap);
        String md5deDupParam = jdkMD5(paramTreeMapJSON);
        log.debug("md5deDupParam = {}, excludeKeys = {} {}", md5deDupParam, Arrays.deepToString(excludeKeys), paramTreeMapJSON);
        return md5deDupParam;
    }

    private static String jdkMD5(String src) {
        String res = null;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] mdBytes = messageDigest.digest(src.getBytes());
            res = DatatypeConverter.printHexBinary(mdBytes);
        } catch (Exception e) {
            log.error("",e);
        }
        return res;
    }

    public static void main(String[] args) {
        Book book = new Book();
        book.setId(1);
        book.setCount(2);
        book.setName("java编程思想");
        book.setTime(System.currentTimeMillis());

        String key = dedupParamMD5(JSON.toJSONString(book), "time", "456");
        System.out.println(key);


    }

}

解决方法:

通过上述实现思路,可以有效地解决订单重复请求问题。当一个订单请求到达系统时,首先尝试获取对应订单的分布式锁,如果成功获取锁,则处理订单并缓存订单信息,最后释放锁。如果获取锁失败,则表示该订单正在被处理,无需再次处理。

总结:

通过Spring Boot整合Redis,我们成功地解决了订单重复请求的问题。引入分布式锁和缓存机制,保证了系统对于同一订单的幂等性处理,从而提高了系统的可靠性和性能。

参考资料:

  1. “Spring Data Redis” documentation: [链接]
  2. “Redis in Action” by Josiah L. Carlson, Manning Publications, 2013.
  3. “Mastering Spring Boot 2.0” by Koushik Kothagal, Packt Publishing, 2017.
  4. “Distributed Systems for Practitioners” by Arnon Rotem-Gal-Oz, O’Reilly Media, 2018.
  5. “Redis: Understanding Cache in Spring Boot” tutorial: [链接]
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-08-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要:
  • 引言:
  • 实现思路:
  • 解决方法:
  • 总结:
  • 参考资料:
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档