当前位置:

首页 > 编程开发 > Redis优惠券秒杀功能怎么实现

Redis优惠券秒杀功能怎么实现

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

一、全局唯一ID1.全局ID生成器每个店铺都可以发布优惠券:当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:id的规律性太明显受单表数据量的限制所以tb_voucher_order表的主键不能用自增ID:createtabletb_voucher_order(idbigintnotnullcomment'主键'primarykey,user_idbigintunsignednotnullcomment'下单的

本文目录

      一、全局唯一ID

      1. 全局ID生成器

      每个店铺都可以发布优惠券:

      Redis优惠券秒杀功能怎么实现

      当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:

      • id的规律性太明显

      • 受单表数据量的限制

      所以tb_voucher_order表的主键不能用自增ID:

      create table tb_voucher_order
      (
          id          bigint                                        not null comment '主键'
              primary key,
          user_id     bigint unsigned                               not null comment '下单的用户id',
          voucher_id  bigint unsigned                               not null comment '购买的代金券id',
          pay_type    tinyint(1) unsigned default 1                 not null comment '支付方式 1:余额支付;2:支付宝;3:微信',
          status      tinyint(1) unsigned default 1                 not null comment '订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款',
          create_time timestamp           default CURRENT_TIMESTAMP not null comment '下单时间',
          pay_time    timestamp                                     null comment '支付时间',
          use_time    timestamp                                     null comment '核销时间',
          refund_time timestamp                                     null comment '退款时间',
          update_time timestamp           default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
      );

      全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:

      Redis优惠券秒杀功能怎么实现

      为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:

      Redis优惠券秒杀功能怎么实现

      D的组成部分:

      • 符号位:1bit,永远为0,表示正数

      • 时间戳:31bit,以秒为单位,可以使用69年

      • 序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID

      编写全局ID生成器代码:

      @Component
      public class RedisIdWorker {
          /**
           * 开始时间戳,以2022.1.1为基准计算时间差
           */
          private static final long BEGIN_TIMESTAMP = 1640995200L;
          /**
           * 序列号的位数
           */
          private static final int COUNT_BITS = 32;
      
          private StringRedisTemplate stringRedisTemplate;
      
          public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
              this.stringRedisTemplate = stringRedisTemplate;
          }
      
          /**
           * 生成带有业务前缀的redis自增id
           * @param keyPrefix
           * @return
           */
          public long nextId(String keyPrefix) {
              // 1.生成时间戳
              LocalDateTime now = LocalDateTime.now();
              long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
              long timestamp = nowSecond - BEGIN_TIMESTAMP;
      
              // 2.生成序列号
              // 2.1.获取当前日期,精确到天
              // 加上日期前缀,可以让存更多同一业务类型的数据,并且还能通过日期获取当天的业务数量,一举两得
              String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
              // 2.2.自增长
              long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
      
              // 3.拼接并返回
              // 用于是数字类型的拼接,所以不能像拼接字符串那样处理,而是通过位运算将高32位存 符号位+时间戳,低32位存 序列号
              return timestamp << COUNT_BITS | count;
          }
      
          public static void main(String[] args) {
              LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
              long second = time.toEpochSecond(ZoneOffset.UTC);
              System.out.println(second);// 1640995200
          }
      }

      测试全局ID生成器:

      @SpringBootTest
      class HmDianPingApplicationTests {
      	@Resource
          private RedisIdWorker redisIdWorker;
      
          private ExecutorService executorService = Executors.newFixedThreadPool(500);
      
      	@Test
          void testIdWorker() throws InterruptedException {
              CountDownLatch latch = new CountDownLatch(300);
      
              // 每个线程生成100个id
              Runnable task = () -> {
                  for (int i = 0; i < 100; i++) {
                      long id = redisIdWorker.nextId("order");
                      System.out.println("id = " + id);
                  }
                  latch.countDown();
              };
              // 300个线程
              long begin = System.currentTimeMillis();
              for (int i = 0; i < 300; i++) {
                  executorService.submit(task);
              }
              latch.await();
              long end = System.currentTimeMillis();
              System.out.println("time = " + (end - begin));
          }
      }

      测试结果:

      Redis优惠券秒杀功能怎么实现

      Redis优惠券秒杀功能怎么实现

      2. 全局唯一ID生成策略

      • UUID(不是递增的)

      • Redis自增

      • 雪花算法(snowflake)

      • 数据库自增(单独建一张表存自增id,分配到分库分表后的表中)

      3. Redis自增ID策略

      • 以日期作为前缀的key,方便统计订单量

      • 自增ID的结构:时间戳 + 计数器

      二、实现优惠券秒杀下单

      1. 添加优惠券

      每个店铺都可以发布优惠券,分为平价券和特价券。平价券可以任意购买,而特价券需要秒杀抢购:

      Redis优惠券秒杀功能怎么实现

      优惠券表信息:

      • tb_voucher:优惠券的基本信息,优惠金额、使用规则等(tb_voucher表的type字段区分是普通券还是秒杀券)

      • tb_seckill_voucher:优惠券的库存、开始抢购时间,结束抢购时间(秒杀券才需要填写这些信息),同时秒杀券拥有普通券的基本信息(秒杀券表tb_seckill_voucher的主键id绑定的是普通券表tb_voucher的id)

      create table tb_voucher
      (
          id           bigint unsigned auto_increment comment '主键'
              primary key,
          shop_id      bigint unsigned                               null comment '商铺id',
          title        varchar(255)                                  not null comment '代金券标题',
          sub_title    varchar(255)                                  null comment '副标题',
          rules        varchar(1024)                                 null comment '使用规则',
          pay_value    bigint(10) unsigned                           not null comment '支付金额,单位是分。例如200代表2元',
          actual_value bigint(10)                                    not null comment '抵扣金额,单位是分。例如200代表2元',
          type         tinyint(1) unsigned default 0                 not null comment '0,普通券;1,秒杀券',
          status       tinyint(1) unsigned default 1                 not null comment '1,上架; 2,下架; 3,过期',
          create_time  timestamp           default CURRENT_TIMESTAMP not null comment '创建时间',
          update_time  timestamp           default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
      );
      create table tb_seckill_voucher
      (
          voucher_id  bigint unsigned                     not null comment '关联的优惠券的id'
              primary key,
          stock       int(8)                              not null comment '库存',
          create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
          begin_time  timestamp default CURRENT_TIMESTAMP not null comment '生效时间',
          end_time    timestamp default CURRENT_TIMESTAMP not null comment '失效时间',
          update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
      )
          comment '秒杀优惠券表,与优惠券是一对一关系';

      2. 编写添加秒杀券的接口

      主要代码:

      @RestController
      @RequestMapping("/voucher")
      public class VoucherController {
      
          @Resource
          private IVoucherService voucherService;
      
          /**
           * 新增秒杀券
           * @param voucher 优惠券信息,包含秒杀信息
           * @return 优惠券id
           */
          @PostMapping("seckill")
          public Result addSeckillVoucher(@RequestBody Voucher voucher) {
              voucherService.addSeckillVoucher(voucher);
              return Result.ok(voucher.getId());
          }
      }
      @Service
      public class VoucherServiceImpl extends ServiceImpl implements IVoucherService {
      
          @Resource
          private ISeckillVoucherService seckillVoucherService;
      
          @Override
          @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优惠券秒杀功能怎么实现

      测试结果:

      Redis优惠券秒杀功能怎么实现

      三、实现秒杀下单

      Redis优惠券秒杀功能怎么实现

      下单时需要判断两点:

      • 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单

      • 库存是否充足,不足则无法下单

      Redis优惠券秒杀功能怎么实现

      主要代码:

      @RestController
      @RequestMapping("/voucher-order")
      public class VoucherOrderController {
          @Resource
          private IVoucherOrderService voucherOrderService;
      
          @PostMapping("seckill/{id}")
          public Result seckillVoucher(@PathVariable("id") Long voucherId) {
              return voucherOrderService.seckillVoucher(voucherId);
          }
      }
      @Service
      public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {
      
          @Resource
          private ISeckillVoucherService seckillVoucherService;
      
          @Resource
          private RedisIdWorker redisIdWorker;
      
          @Override
          public Result seckillVoucher(Long voucherId) {
              // 1.查询优惠券
              SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
              // 2.判断秒杀是否开始
              if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
                  // 尚未开始
                  return Result.fail("秒杀尚未开始!");
              }
              // 3.判断秒杀是否已经结束
              if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
                  // 尚未开始
                  return Result.fail("秒杀已经结束!");
              }
              // 4.判断库存是否充足
              if (voucher.getStock() < 1) {
                  // 库存不足
                  return Result.fail("库存不足!");
              }
              //5,扣减库存
              boolean success = seckillVoucherService.update()
                      .setSql("stock= stock -1")
                      .eq("voucher_id", voucherId).update();
              if (!success) {
                  // 扣减库存失败
                  return Result.fail("库存不足!");
              }
              // 6.创建订单
              VoucherOrder voucherOrder = new VoucherOrder();
              // 6.1.订单id
              long orderId = redisIdWorker.nextId("order");
              voucherOrder.setId(orderId);
              // 6.2.用户id
              Long userId = UserHolder.getUser().getId();
              voucherOrder.setUserId(userId);
              // 6.3.代金券id
              voucherOrder.setVoucherId(voucherId);
              save(voucherOrder);
      
              return Result.ok(orderId);
      
          }
      }

      简单测试秒杀成功:

      Redis优惠券秒杀功能怎么实现

      扣减库存成功:

      Redis优惠券秒杀功能怎么实现

      四、超卖问题

      当有大量请求同时访问时,就会出现超卖问题

      Redis优惠券秒杀功能怎么实现

      Redis优惠券秒杀功能怎么实现

      超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁:

      Redis优惠券秒杀功能怎么实现

      1. 加锁方式 - 乐观锁

      乐观锁的关键是判断之前查询得到的数据是否有被修改过,常见的方式有两种:

      (1)版本号法

      Redis优惠券秒杀功能怎么实现

      (2)CAS法

      • 用库存代替了版本号,可以少加一个字段

      • 扣库存时,与查询时的库存比较,没被修改则可以扣减库存

      Redis优惠券秒杀功能怎么实现

      2. 乐观锁解决超卖问题

      乐观锁方式,通过CAS判断前后库存是否一致,解决超卖问题:

      // 之前的代码
      boolean success = seckillVoucherService.update()
                      .setSql("stock= stock -1")
                      .eq("voucher_id", voucherId).update();
                      
      // 乐观锁方式,通过CAS判断前后库存是否一致,解决超卖问题                
      boolean success = seckillVoucherService.update()
                  .setSql("stock= stock -1") // set stock = stock -1
                  .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); // where id = ? and stock = ?

      又出现新的问题:

      • 假设100个线程同时请求,但通过CAS判断后,只有一个线程能扣减库存成功,其余99个线程全部失败

      • 此时,库存剩余99,但是实际业务可以满足其余99个线程扣减库存

      • 虽然能解决超卖问题,但是设计不合理

      所以为了解决失败率高的问题,需要进一步改进:

      通过CAS 不再 判断前后库存是否一致,而是判断库存是否大于0

      boolean success = seckillVoucherService.update()
                      .setSql("stock= stock -1")
                      .eq("voucher_id", voucherId).gt("stock",0).update(); // where id = ? and stock > 0

      3. 小结

      超卖这样的线程安全问题,解决方案有哪些?
      (1)悲观锁:添加同步锁,让线程串行执行

      • 优点:简单粗暴

      • 缺点:性能一般

      (2)乐观锁:不加锁,在更新时判断是否有其它线程在修改

      • 优点:性能相对悲观锁好(但是仍然需要同时查数据库,影响性能)

      • 缺点:存在成功率低的问题(可以采用分段锁方式提高成功率)

      五、一人一单问题

      需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单

      Redis优惠券秒杀功能怎么实现

      在扣减库存之前,加上一人一单的逻辑:

      // 5.一人一单逻辑
      Long userId = UserHolder.getUser().getId();
        // 5.1.查询订单数量
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        // 5.2.判断是否下过单
        if (count > 0) {
            // 用户已经购买过了
            return Result.fail("用户已经购买过一次!");
        }

      此处仍会出现并发问题,当同一用户模拟大量请求同时查询是否下过单时,如果正好都查询出count为0,就会跳过判断继续执行扣减库存的逻辑,此时就会出现一人下多单的问题

      解决方法:

      • 由于是判断查询的数据是否存在,而不是像之前判断查询的数据是否修改过

      • 所以这里只能加悲观锁

      1. 加锁分析

      • 首先将一人一单之后的逻辑全部加锁,所以将一人一单之后的逻辑抽取出一个方法进行加锁,public Result createVoucherOrder(Long voucherId)

      • 如果直接在方法上加锁,则锁的是this对象,锁的对象粒度过大,就算是不同的人执行都会阻塞住,影响性能,public synchronized Result createVoucherOrder(Long voucherId)

      • 所以将锁的对象改为userId,但是不能直接使用synchronized (userId),因为每次执行Long userId = UserHolder.getUser().getId();虽然值一样,但是对象不同,因此需要这样加锁 synchronized (userId.toString().intern()),intern()表示每次从字符串常量池中获取,这样值相同时,对象也相同

      • 为了防止事务还没提交就释放锁的问题,则不能将锁加在createVoucherOrder方法内部,例如:

      @Transactional
      public Result createVoucherOrder(Long voucherId) {
      	synchronized (userId.toString().intern()) {
      		。。。
      	}
      }

      而是需要等事务提交完再释放锁,例如:

      synchronized (userId.toString().intern()) {
       	// 获取代理对象(事务)
          IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
          return proxy.createVoucherOrder(voucherId);
      }

      2. 事务分析

      由于只有一人一单之后的逻辑涉及到修改数据库,所以只需对该方法加事务
      @Transactional
      public Result createVoucherOrder(Long voucherId)

      由于只对createVoucherOrder方法加了事务,而该方法是在seckillVoucher方法中被调用,seckillVoucher方法又没有加事务,为了防止事务失效,则不能直接在seckillVoucher方法调用createVoucherOrder方法,例如:

      @Override
      public Result seckillVoucher(Long voucherId) {
      	。。。。
      	synchronized (userId.toString().intern()) {
              return this.createVoucherOrder(voucherId);
          }
      }

      而是需要通过代理对象调用createVoucherOrder方法,因为@Transactional事务注解的原理是通过获取代理对象执行目标对象的方法,进行AOP操作,所以需要这样:

      @Override
      public Result seckillVoucher(Long voucherId) {
      	。。。。
      	// 获取代理对象(事务)
          IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
          return proxy.createVoucherOrder(voucherId);
      }

      并且还要引入依赖:

      
          org.aspectj
          aspectjweaver
      

      还要开启注解暴露出代理对象:

      @EnableAspectJAutoProxy(exposeProxy = true)
      @MapperScan("com.hmdp.mapper")
      @SpringBootApplication
      public class HmDianPingApplication {
          public static void main(String[] args) {
              SpringApplication.run(HmDianPingApplication.class, args);
          }
      }

      完整VoucherOrderServiceImpl代码:

      @Service
      public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {
      
          @Resource
          private ISeckillVoucherService seckillVoucherService;
      
          @Resource
          private RedisIdWorker redisIdWorker;
      
          @Override
          public Result seckillVoucher(Long voucherId) {
              // 1.查询优惠券
              SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
              // 2.判断秒杀是否开始
              if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
                  // 尚未开始
                  return Result.fail("秒杀尚未开始!");
              }
              // 3.判断秒杀是否已经结束
              if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
                  // 尚未开始
                  return Result.fail("秒杀已经结束!");
              }
              // 4.判断库存是否充足
              if (voucher.getStock() < 1) {
                  // 库存不足
                  return Result.fail("库存不足!");
              }
      
              Long userId = UserHolder.getUser().getId();
              synchronized (userId.toString().intern()) {
                  // 获取代理对象(事务)
                  IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
                  return proxy.createVoucherOrder(voucherId);
              }
              
          }
      
          @Transactional
          public Result createVoucherOrder(Long voucherId) {
      
              // 5.一人一单逻辑
              Long userId = UserHolder.getUser().getId();
              // 5.1.查询订单数量
              int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
              // 5.2.判断是否下过单
              if (count > 0) {
                  // 用户已经购买过了
                  return Result.fail("用户已经购买过一次!");
              }
      
              // 6,扣减库存
              // 乐观锁方式,通过CAS判断库存是否大于0,解决超卖问题:
              boolean success = seckillVoucherService.update()
                      .setSql("stock= stock -1")
                      .eq("voucher_id", voucherId).gt("stock",0).update(); // where id = ? and stock > 0
      
              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);
      
              // 8.返回订单id
              return Result.ok(orderId);
          }
          
      }

      六、集群模式下并发安全问题

      通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了。

      我们将服务启动两份,端口分别为8081和8082:

      Redis优惠券秒杀功能怎么实现

      然后修改nginx的conf目录下的nginx.conf文件,配置反向代理和负载均衡:

      Redis优惠券秒杀功能怎么实现

      修改完后,重新加载nginx配置文件:

      Redis优惠券秒杀功能怎么实现

      现在,用户请求会在这两个节点上负载均衡,再次测试下是否存在线程安全问题:

      访问8081端口的线程进入了synchronized中

      Redis优惠券秒杀功能怎么实现

      访问8082端口的线程也进入了synchronized中

      Redis优惠券秒杀功能怎么实现

      最终同一个用户下了2单扣了2个库存,所以在集群模式下,出现了一人多单的问题:

      Redis优惠券秒杀功能怎么实现

      分析:

      • 锁的原理是每个JVM中都有一个Monitor作为锁对象,所以当对象相同时,获取的就是同一把锁

      • 但是不同的JVM中的Monitor不同,所以获取的不是同一把锁

      • 因此集群模式下,加synchronized锁也会出现并发安全问题,需要加分布式锁

      Redis优惠券秒杀功能怎么实现

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发
      相关文章 更多
      php72w redis,docker php7安装php-redis
      php72w redis,docker php7安装php-redis

      在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

      php 如何配置 redis,php 如何设置redis
      php 如何配置 redis,php 如何设置redis

      配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

      c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
      c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

      C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

      如何在Golang架构中部署Redict作为高能数据临时存储中转站
      如何在Golang架构中部署Redict作为高能数据临时存储中转站

      Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

      TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
      TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

      在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

      如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
      如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

      在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

      TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
      TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

      先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

      TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
      TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

      高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

      Golang 实现基于 Redis 的延时任务队列
      Golang 实现基于 Redis 的延时任务队列

      基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

      RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
      RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

      操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

      即将离开本站
      您即将前往第三方网站,请确认是否继续?