Redis实战篇——黑马点评【异步秒杀优化】

目录

异步秒杀优化

一、回顾同步下单流程

二、同步下单秒杀测试

(一)在测试类中添加如下方法,生成1000个用户的token

(二)接下来,我们通过jemeter进行测试

三、改进秒杀业务,提高并发性能

 (一)优化方案

(二)操作实现

(三)优化压力测试

四、小总结


异步秒杀优化

一、回顾同步下单流程

当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤

1、查询优惠卷

2、判断秒杀库存是否足够

3、查询订单

4、校验是否是一人一单

5、扣减库存

6、创建订单

流程图如下所示:

由这个图可以看出在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行, 这样就会导致我们的程序执行的很慢

二、同步下单秒杀测试

(一)在测试类中添加如下方法,生成1000个用户的token

@Test
    public void generateToken() throws Exception{
        //数据库查询1000个用户的信息
        List<User> list = userService.list(new QueryWrapper<User>().last("limit 1000"));
        //创建字符输入流准备写入token到文件
        BufferedWriter br = new BufferedWriter(new FileWriter("D:\\JavaProject\\IdeaProject\\hm-dianping\\hm-dianping\\src\\main\\resources\\Tokens.txt"));
        for (User user : list) {
            //随机生成token作为登录令牌
            String token = UUID.randomUUID().toString(true);
            //将User对象转为HashMap存储到Redis中
            UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
            Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                    CopyOptions.create()
                            .setIgnoreNullValue(true)
                            .setFieldValueEditor((fieldName, fieldValue) ->
                                    fieldValue.toString()));
            //保存用户信息到Redis中
            String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
            stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
            stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
            //写入token到文件
            br.write(token);
            br.newLine();
            br.flush();
        }

    }

其中文件写入地址可以放在Resource目录下,地址更换为自己的位置

执行后的结果如下:

清空订单表,更改秒杀库存为200

(二)接下来,我们通过jemeter进行测试

1.Jmeter中线程数设为1000,执行时间为1秒,模拟1000个用户高并发访问秒杀业务

2.添加http信息头管理器,值修改如图所示

3.添加csv

4.进行如下配置,文件名换成自己的token文件的地址

5.测试秒杀id为14的优惠券接口

6.查看聚合报告

由于JMeter模拟发送请求不是同时发送,是又少到多的请求,所以响应时间最小值是198毫秒,最大值是3088毫秒,平均响应时间是957毫秒,吞吐量为323.2/sec(随着并发量增加,吞吐量减少)

7.查看数据库,正常被扣减

三、改进秒杀业务,提高并发性能

 (一)优化方案

我们将耗时比较短的逻辑判断放入到redis中,比如是否库存足够,比如是否一人一单,这样的操作,只要这种逻辑可以完成,就意味着我们是一定可以下单完成的,我们只需要进行快速的逻辑判断,根本就不用等下单逻辑走完,我们直接给用户返回成功, 再在后台开一个线程,后台线程慢慢的去执行queue里边的消息,这样程序不就超级快了吗?(简单来说就是让redis在前面先提前都把东西卖好快速响应给客户,我们再在后台慢慢的将订单写入数据库)

当然这里边有两个难点:

第一个难点是我们怎么在redis中去快速校验一人一单,还有库存判断

第二个难点是由于我们校验和tomct下单是两个线程,那么我们如何知道到底哪个单他最后是否成功,或者是下单完成,为了完成这件事我们在redis操作完之后,我们会将一些信息返回给前端,同时也会把这些信息丢到异步queue中去,后续操作中,可以通过这个id来查询我们tomcat中的下单逻辑是否完成了。

流程可以优化成这样:

  1. 请求转发
    用户请求先经 Nginx 转发,进入系统处理链路。

  2. Redis 核心校验

  • 秒杀库存判断:在 Redis 中校验秒杀库存(如通过键值对快速判断库存是否充足),若库存不足,直接结束异常流程;若充足,进入下一步。
  • 一人一单校验:通过 Redis 校验用户是否已下单(如检查用户 ID 是否在特定的 Set 集合中),确保同一用户只能参与一次秒杀。
  1. 队列存储与异步处理
    Redis 校验通过后,将优惠券 ID、用户 ID、订单 ID 等信息存入 阻塞队列。后台异步线程读取队列数据,触发后续下单操作。

  2. Tomcat 服务端处理

  • 先查询优惠券详情,再二次校验秒杀库存(双重校验保障准确性)。
  • 查询用户订单记录,再次校验一人一单规则。
  • 确认无误后,执行减库存操作,最后在 MySQL 集群中创建订单,完成数据持久化。
  1. 结果返回
    系统将订单 ID 返回给用户,用户可通过该 ID 查询下单状态,实现前端与后端的状态交互。

优化流程图如下:

Redis 优化秒杀全流程详细解析:
1. Redis 数据结构基础

  • 库存校验:通过键值对存储库存,例如stock:vid:7的 VALUE 为 100,直接判断该值是否大于 0,快速校验库存是否充足。
  • 用户下单记录:利用 Set 集合(如order:vid:7存储用户 ID:1、2、3 等),校验用户是否已参与秒杀,实现 “一人一单” 限制。

2. Lua 脚本原子化核心逻辑

  • 开始执行
    • 库存判断:先校验stock:vid:7的 VALUE,若库存不足,直接返回 1,流程结束。
    • 用户下单判断:若库存充足,检查用户 ID 是否在order:vid:7的 Set 集合中。
      • 若已存在(已下单),返回 2,流程结束;
      • 若不存在,执行扣减库存操作,同时将用户 ID 存入 Set 集合,最后返回 0。

3. 后续处理流程

  • 结果判断:系统判断 Lua 脚本返回值,若为 0,说明校验通过可下单。
  • 入队列与异步处理:将优惠券 ID、用户 ID、订单 ID 存入阻塞队列,后台异步线程读取队列信息,完成数据库下单操作。
  • 前端交互:返回订单 ID 给前端,用户可通过该 ID 查询订单状态,确认下单是否成功。

整个方案通过 Redis 快速校验、Lua 保证原子性操作、队列异步处理,高效解决秒杀场景下的库存校验、用户限制及高并发下单问题。

(二)操作实现

1.新增秒杀优惠券的同时,将优惠券信息保存到Redis

①在VoucherServiceImpl类中添加以下方法

@Transactional
    public void addSeckillVoucher(Voucher voucher) {
        // 保存优惠券
        save(voucher);
        // 保存秒杀信息
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucher.getId());
        seckillVoucher.setStock(voucher.getStock());
        seckillVoucher.setBeginTime(voucher.getBeginTime());
        seckillVoucher.setEndTime(voucher.getEndTime());
        seckillVoucherService.save(seckillVoucher);
        //新增时将秒杀券保存到Redis当中,不设置过期时间,秒杀到期后需要手动删除缓存
        stringRedisTemplate.opsForValue().set(RedisConstants.SECKILL_STOCK_KEY+voucher.getId(),voucher.getStock().toString());
    }

  ②在apifox中进行添加秒杀券(我这里添加的是14号秒杀券)

{
"shopId":2,
"title": "100元代金券",
"subTitle" : "周一至周五均可使用",
"rules" : "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
"payValue" : 8000,
"actualValue" : 10000,
"type" : 1 , 
"stock" : 100,
"beginTime" : "2022-01-25T10:09:17",
"endTime" : "2030-01-26T12:09:04"
}

2.基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功

-- 参数列表
local voucherId=ARGV[1]   -- 优惠券id(用于判断库存是否充足)
local userId=ARGV[2]   -- 用户id(用于判断用户是否下过单)

-- 构造缓存数据Key
local stockKey ='seckill:stock:' .. voucherId   --库存key
local orderKey ='seckill:order' .. voucherId   --订单key

-- 脚本业务
-- 判断库存是否充足
if tonumber(redis.call('get',stockKey))<=0 then
    -- 库存不足,返回1
    return 1
end

-- -- 判断用户是否下过单 SISMEMBER orderKey userId,SISMEMBER:判断Set集合中是否存在某个元素,存在返回1,不存在放回0
if redis.call('sismember',orderKey,userId)==1 then
    -- 存在,说明用户已经下单,返回2
    return 2
end

-- 缓存中预先扣减库存incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 下单(保存用户) sadd orderKey userId
redis.call('sadd',orderKey,userId)
-- 有下单资格,允许下单,返回0
return 0;

以下操作都在VoucherOrderServiceImpl类中实现

3.在Java中执行lua脚本

①初始化lua脚本

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    // 静态代码块初始化加载lua脚本
    static {
        SECKILL_SCRIPT=new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

②创建阻塞队列

    //创建阻塞队列
   private BlockingQueue<VoucherOrder> orderTasks=new ArrayBlockingQueue<>(1024*1024);

③执行lua脚本,如果抢购成功,将优惠券id和用户id封装后存入阻塞队列

/**
     * 秒杀优惠券
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
       //1.获取用户id
        Long userId = UserHolder.getUser().getId();
        //2.执行Lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString()
        );
        //3.判断结果是否为0
        int r=result.intValue();
        if(r!=0){
            //3.1不为0,表示没有购买资格
            return Result.fail(r==1?"库存不足": "不能重复下单");
        }
        //3.2为0,表示有购买资格,把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        //4.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        //5.保存在阻塞队列中
        orderTasks.add(voucherOrder);
        //6.获取代理对象
        proxy=(IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

 执行部分和保存部分如图所示:


  
4.开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能

①创建一个线程来获取阻塞队列中的任务,进行下单

    //创建一个线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();

    //在类创建的之后就立马开启任务
    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }

    private class VoucherOrderHandler implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //2.进行下单
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("处理订单异常",e);
                }
            }
        }
    }

②异步线程进行下单并创建订单存入数据库

IVoucherOrderService proxy;

    /**
     * 进行下单
     * @param voucherOrder
     */
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        // 从订单信息里获取用户id(从线程池中取出的是一个全新线程,不是主线程,所以不能从BaseContext中获取用户信息)
        Long userId = voucherOrder.getUserId();
        //创建锁对象(可重入),指定锁的名称
        RLock redisLock = redissonClient.getLock("lock:order:" + userId);
        //尝试获取锁对象
        boolean isLock = redisLock.tryLock();
        //判断是否获取锁成功
        if (!isLock){
            //获取锁失败,直接返回失败或者重试
            log.error("不允许重复下单!");
            return;
        }
        try{
            //注意:由于是spring的事务是放在threadLocal中,此时的是多线程,事务会失效
            proxy.createVoucherOrder(voucherOrder);
        }finally {
            //释放锁
            redisLock.unlock();
        }
    }

/**
     * 创建订单存入数据库
     * @param voucherOrder
     */
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        //查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        //判断是否存在
        if(count>0){
            //用户已经购买过了
            log.error("用户已经购买过了");
            return;
        }

        //扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock=stock-1")
                .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
                .update();

        if (!success){
            //扣减失败
            log.error("库存不足");
            return;
        }
        save(voucherOrder);

    }
  • 注意:AopContext.currentProxy()底层也是利用ThreadLocal获取的,所以异步线程中也无法使用。解决方案就是提升代理对象的作用域,放到成员变量位置,在主线程中初始化,或者在主线程中创建后作为方法参数一起传递给阻塞队列。

如图所示提升了代理对象的作用域:

在主线程中初始化:

完整代码展示:

VoucherOrderServiceImpl类
package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Autowired
    private SeckillVoucherServiceImpl seckillVoucherService;
    @Autowired
    private RedisIdWorker redisIdWorker;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    // 静态代码块初始化加载lua脚本
    static {
        SECKILL_SCRIPT=new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
    //创建一个线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();

    //在类创建的之后就立马开启任务
    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }

    private class VoucherOrderHandler implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //2.进行下单
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("处理订单异常",e);
                }
            }
        }
    }

    IVoucherOrderService proxy;

    /**
     * 进行下单
     * @param voucherOrder
     */
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        // 从订单信息里获取用户id(从线程池中取出的是一个全新线程,不是主线程,所以不能从BaseContext中获取用户信息)
        Long userId = voucherOrder.getUserId();
        //创建锁对象(可重入),指定锁的名称
        RLock redisLock = redissonClient.getLock("lock:order:" + userId);
        //尝试获取锁对象
        boolean isLock = redisLock.tryLock();
        //判断是否获取锁成功
        if (!isLock){
            //获取锁失败,直接返回失败或者重试
            log.error("不允许重复下单!");
            return;
        }
        try{
            //注意:由于是spring的事务是放在threadLocal中,此时的是多线程,事务会失效
            proxy.createVoucherOrder(voucherOrder);
        }finally {
            //释放锁
            redisLock.unlock();
        }
    }


    //创建阻塞队列
    private BlockingQueue<VoucherOrder> orderTasks=new ArrayBlockingQueue<>(1024*1024);

    /**
     * 秒杀优惠券
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
       //1.获取用户id
        Long userId = UserHolder.getUser().getId();
        //2.执行Lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString()
        );
        //3.判断结果是否为0
        int r=result.intValue();
        if(r!=0){
            //3.1不为0,表示没有购买资格
            return Result.fail(r==1?"库存不足": "不能重复下单");
        }
        //3.2为0,表示有购买资格,把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        //4.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        //5.保存在阻塞队列中
        orderTasks.add(voucherOrder);
        //6.获取代理对象
        proxy=(IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

    /**
     * 创建订单存入数据库
     * @param voucherOrder
     */
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        //查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        //判断是否存在
        if(count>0){
            //用户已经购买过了
            log.error("用户已经购买过了");
            return;
        }

        //扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock=stock-1")
                .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
                .update();

        if (!success){
            //扣减失败
            log.error("库存不足");
            return;
        }
        save(voucherOrder);

    }


//    @Transactional
//    public  Result createVoucherOrder(Long voucherId) {
//        Long userId = UserHolder.getUser().getId();
//        synchronized(userId.toString().intern()){
//            // 5.1.查询订单
//            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
//            // 5.2.判断是否存在
//            if (count > 0) {
//                // 用户已经购买过了
//                return Result.fail("用户已经购买过一次!");
//            }
//
//            // 6.扣减库存
//            boolean success = seckillVoucherService.update()
//                    .setSql("stock = stock - 1") // set stock = stock - 1
//                    .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
//                    .update();
//            if (!success) {
//                // 扣减失败
//                return Result.fail("库存不足!");
//            }
//
//            // 7.创建订单
//            VoucherOrder voucherOrder = new VoucherOrder();
//            // 7.1.订单id
//            long orderId = redisIdWorker.nextId("order");
//            voucherOrder.setId(orderId);
//            // 7.2.用户id
//            voucherOrder.setUserId(userId);
//            // 7.3.代金券id
//            voucherOrder.setVoucherId(voucherId);
//            save(voucherOrder);
//
//            // 7.返回订单id
//            return Result.ok(orderId);
//        }
//    }
//
//    /**
//     * 秒杀优惠券
//     * @param voucherId
//     * @return
//     */
//    @Override
//    public Result seckillVoucher(Long voucherId) {
//        //1. 查询优惠券信息
//        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//        //2. 判断秒杀是否开始
//        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
//            //2.1还没有开始
//            return Result.fail("秒杀尚未开始");
//        }
//        //2.2已经开始
//        //3.判断库存是否充足
//        if (voucher.getStock() < 1){
//            //3.1库存不足
//            return Result.fail("库存不足");
//        }
//        //3.2库存充足
//        //根据用户id和优惠券id查询订单
//        Long userId = UserHolder.getUser().getId();
//        //创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate,"order:" + userId);
//        RLock lock = redissonClient.getLock(RedisConstants.LOCK_KEY_PREFIX + RedisConstants.SECKILL_ORDER_KEY + userId);
//        //获取锁
//        boolean isLock = lock.tryLock();    //空参默认失败不等待,直接返回结果
//        //加锁失败
//        if(!isLock){
//            return Result.fail("不允许重复下单");
//        }
//
//        try {
//            //获取代理对象(事务)
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
//            return proxy.createVoucherOrder(voucherId);
//        } finally {
//            //释放锁
//            lock.unlock();
//        }
//    }
}
IVoucherOrderService接口
package com.hmdp.service;

import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
public interface IVoucherOrderService extends IService<VoucherOrder> {

    /**
     * 秒杀下单
     * @param voucherId
     * @return
     */
    Result seckillVoucher(Long voucherId);

    /**
     * 代理方法
     * @param voucherId
     * @return
     */
    void createVoucherOrder(VoucherOrder voucherId);

    //Result createVoucherOrder(Long voucherId);
}

(三)优化压力测试

还是同样的步骤,这里再次进行演示

1. ①数据库秒杀券设置为200

②清空数据库订单表 

③清空redis秒杀订单信息,

④ redis秒杀券设置为200

 2.Jmeter中线程数设为1000,执行时间为1秒,模拟1000个用户高并发访问秒杀业务

3.添加http信息头管理器,值修改如图所示

4.添加csv

5.进行如下配置,文件名换成自己的token文件的地址(确保token没有失效

6.测试秒杀id为14的优惠券接口

7.查看聚合报告

由于JMeter模拟发送请求不是同时发送,是又少到多的请求,所以响应时间最小值是87毫秒,最大值是1037毫秒,平均响应时间是488毫秒,吞吐量为963.4/sec(随着并发量增加,吞吐量减少),可以看出执行耗时相较上面减少,吞吐量大幅增加,提高了秒杀系统的并发性能!

8.查看数据库,正常被扣减

四、小总结

1.秒杀业务的优化思路是什么?

        ①先利用Redis完成库存余量、一人一单判断,完成抢单业务
        ② 再将下单业务放入阻塞队列,利用独立线程异步下单
2.基于阻塞队列的异步秒杀存在哪些问题?
         ① 内存限制问题(JDK的阻塞队列使用的是JVM的内存,高并发订单量可能导致内存溢出,队列大小是由我们自己指定的,可能会超出阻塞队列的上限)
         ② 数据安全问题(情况①:JVM内存是没有持久化机制的,服务重启或意外宕机时,阻塞队列中的所有任务都会丢失。情况②:当我们从阻塞队列拿到一个任务尚未处理时,如果此时发生异常,该任务也会丢失,就没有机会再次被处理了,导致数据不一致)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

让我上个超影吧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值