redis队列的使用

本文介绍了如何使用RedisTemplate结合CompletableFuture处理并发下单流程,通过线程编排实现异步操作。在号码下单场景中,利用Redis的Set存储中间状态,避免异常数据,使用ThreadLocal保存执行结果以兼容旧流程。另外,利用ZSet作为延时队列处理订单状态变更,确保数据一致性。
摘要由CSDN通过智能技术生成

一、redisTemplate对于set集合的使用

1、需求背景

在做号码下单的时候,我需要去调运营商的接口,查询号码、去对客户的身份信息校验、然后去下预约单、号码锁定、正式单...

首先,号码锁定需要预约单返回的订单号,正式单需要号码锁定返回的seq与预约单返回的订单号

2、解决方案

接口的调用顺序   这里,我可以使用一个线程编排去处理这个事情

首先,号码查询与客户信息校验可以同步去进行。

然后根据上一步的结果,去进行预约单

根据预约单结果去号码锁定

根据预约单结果与号码锁定结果去进行正式单

看一下代码

void testCompletableFuture(){

        //1 查询订单信息
        CompletableFuture<Object> numberInfos = CompletableFuture.supplyAsync(() -> {
            System.err.println("查询订单信息");
            return new Object();
        });
        //1 校验用户信息
        CompletableFuture<Object> checkUserInfo = CompletableFuture.supplyAsync(() -> {
            System.err.println("校验用户信息");
            return new Object();
        });

        //2 预约单  combone将第一步的两个结果都拿到,然后再去执行
        CompletableFuture<Object> preOrder = numberInfos.thenCombine(checkUserInfo, (numberInfo, checkUser) -> {
            System.err.println("下预约单");
            return new Object();
        });

        //3 号码预占
        CompletableFuture<Object> lockNumber = preOrder.thenApply(result -> {
            System.err.println("号码预占");
            return new Object();
        });

        //4 正式单
        CompletableFuture<Object> order = preOrder.thenCombine(lockNumber, (pre, lock) -> {
            System.err.println("正式单");
            return new Object();
        });

        //等待任务完成~~~
        CompletableFuture<String> handle = CompletableFuture.allOf(order).handle((result, exception) -> {
            if (exception != null) {
                //异常处理
                return "fail";
            } else {
                return "success";
            }
        });
        
        try {
            //获取结果~~
            String result = handle.get();
        } catch (Exception e) {
            e.printStackTrace();
        } 

    }

3、其他问题

但是往往结果并不是想象的那么顺利。有时候,中间返回异常,因为某些号码还需要更多信息,那么就得重新填写扩展信息,重新请求。

而这部分数据肯定就会在我们拿到某一步的结果的时候就得知。  那么这个号码,我会将其保存在redis中,那么下一次对号码下单的话,我就可以从redis查看是否有这个号码,有的话,返回错误,告知就让其填扩展信息,没有就填基本信息就可以了。

并且在每一个号码成功下单完成之后,需要从redis中删除这个号码。因为你不知道redis中是否有这个号码,所以删除操作一定得进行

将号码添加在redis的set中

redisTemplate.opsForSet().add(redisKey, number);

在下单成功之后,将其从redis中删除

redisTemplate.opsForSet().remove(redisKey, number);

到了这里基本就结束了。

4、代码兼容

但是我的项目要兼容以前的下单流程,而不可能说你直接新建一个流程,按照你的异步编排去走。而以前的流程是是直接一个请求过来,然后依次去调第三方的信息校验、预约单、锁号码、正式单~~~。那么我们上面所有的异步都没办法用到。

所以每一次执行的结果怎么在下一次去拿到?  放在数据库,没问题  。 代价有点大。  放在redis,也没问题。  但是redis是一个基于内存的高性能的缓存数据库,我们需要在里面存频繁需要查询的,并且不是经常更改的数据,或者热点数据。

而我们每一次的执行结果,显然不符合这个条件。使用redis就是大材小用了。  而有一个更加适合的东西去用-----ThreadLocal。

上面也说了,我们是一个请求,需要拿到每一次的结果,那么ThreadLocal就是为这个而出现的。它的线程安全性,并且线程执行完之后,保存的数据也就会销毁,占用的内存就会释放,在这里再合适不过了。所以我就使用了threadLocal来保存每一步执行的结果。

到了这里,需求也基本完美解决了~~~(不要忘记出现异常或者流程结束的时候,去清空这个threadlocal!!!)

二、redisTemplate对于zset集合的使用

1、需求背景

还是下单。在下单完之后,我需要保存这笔订单状态等相关的数据。同时,如果订单状态有变,那么运营商那边会来通知我去更改订单状态。

2、问题的出现

下单完成之后,我需要保存这笔订单的seq(我们这边的订单号),orderNo(第三方订单号),number等,orderChannel(号码下单的运营商)。以后运营商在订单状态更改之后,会给我传过来orderNo,number。所以我要用这两个去查seq与orderchannel,然后新增一条订单状态更改的记录。因为我们这边所有的数据都是跟seq关联的,所以我必须在每一次的运营商回调的时候去查出seq。当然也可以不查seq,在查订单状态的时候,根据seq查到number与orderNo,然后再用这两个去查运营商给我们通知的订单状态。但是系统查订单都是统一的流程,这种特殊情况的处理代码越多,导致后续其他人接手的难度也越大。

在运营商通知到的时候,我去查seq发现并不存在,因为我的订单结果在运营商给我回调之后才返回。所以导致订单状态已经变了,我却还没收到订单成功的通知。这条数据插入收seq为空,而收到订单成功之后,再插入seq,那么表中就存在异常数据。对于异常数据的修复又得去做~~~

所以我使用了zset延时队列,对于运营商的通知订单状态更改并不是需要实时,所以我将信息存起来,等订单结果返回之后再去处理运营商的回调。

3、解决方法

入队列  将信息保存在redis中

private static ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().build();

value = mapper.writeValueAsString(param);
            redisTemplate.opsForZSet().add(OrderStatusConfig.ORDER_STATUS_DELAY_KEY, value, System.currentTimeMillis() + 300000);

信息消费并删除

@Scheduled(cron = "0  0/30 * * * ?")
    public void synchronizationOrderStatus(){
        log.info("订单状态信息同步~~~  now:{}", new Date());
        Set set = redisTemplate.opsForZSet().rangeByScore(OrderStatusConfig.ORDER_STATUS_DELAY_KEY, 0, System.currentTimeMillis());
        if(!set.isEmpty()){
            set.stream().forEach(e ->{
                try{
                    Map map = JSON.parseObject((String)e, Map.class);
                    log.info("消费解析出来的map!:{}", map);
                    orderService.syncHuakaOrder(map);
                    redisTemplate.opsForZSet().remove(OrderStatusConfig.ORDER_STATUS_DELAY_KEY, e);
                }catch (Exception exception){
                    log.error("订单信息同步异常!原始数据:{},  error:{}", e, JSONObject.toJSONString(exception));
                }
            });
        }
    }

将数据存入zset的时候,给定score为当前时间戳+30分钟,那么取得时候,只取score表示得时间在当前时间之前的数据,取处理这些数据并且删除就ok了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值