秒杀避免超卖 ,兼顾效率与质量
redis的队列来实现。将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。这个操作起来很方便,而且效率极高
Redis是一个分布式key-value缓存系统,value支持多种数据结构,这里value可以选择两种类型,String(或者hash):主要用于记录商品的库存,对商品减库存。Set集合(这里不要用list集合,list集合是可重复的,set是不可重复的,可以保证一个用户只卖一次,如果一个用户可以买多次那么可以使用list集合):用于存储用户的id获取其他唯一确定一个用户的值。
在秒杀开始前:可以使用批处理,将参加秒杀的产品信息缓存到redis中。这里将产品的业务唯一字段作为key,库存作为value。这里的key要和前端缓存的key一致。
在秒杀开始时:用户大量提交。根据用户提交的产品信息,获取到redis中需要的key值,查询缓存 ,得到库存量,判断当前库存是否大于零,如果大于零,判断当前的set集合中是否用该用户ID,如果没有,减库存并且将用户的ID放入集合中,并对库存减一,如果库存为0,提示用户,商品已售完等文案信息,如果集合中已经存在该用户id,则不做任何处理,直接处理下一个请求。直到最后库存售完,上面的过程可以利用redis事务和watch功能完成对数据一致性的控制即超卖问题。
库存售完后:程序开始启动一个有个后台线程,可以阻塞等待商品库存售完的通知,在上面一步,库存一旦售完,后台进程获取set集合中的用户信息,异步处理需要操作的购买等后续操作。
package com.github.distribute.lock.redis; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; /** * redis乐观锁实例 * @author linbingwen * */ public class OptimisticLockTest { public static void main(String[] args) throws InterruptedException { long starTime=System.currentTimeMillis(); initPrduct(); initClient(); printResult(); long endTime=System.currentTimeMillis(); long Time=endTime-starTime; System.out.println("程序运行时间: "+Time+"ms"); } /** * 输出结果 */ public static void printResult() { Jedis jedis = RedisUtil.getInstance().getJedis(); Set<String> set = jedis.smembers("clientList"); int i = 1; for (String value : set) { System.out.println("第" + i++ + "个抢到商品,"+value + " "); } RedisUtil.returnResource(jedis); } /* * 初始化顾客开始抢商品 */ public static void initClient() { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); int clientNum = 10000;// 模拟客户数目 for (int i = 0; i < clientNum; i++) { cachedThreadPool.execute(new ClientThread(i)); } cachedThreadPool.shutdown(); while(true){ if(cachedThreadPool.isTerminated()){ System.out.println("所有的线程都结束了!"); break; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 初始化商品个数 */ public static void initPrduct() { int prdNum = 100;// 商品个数 String key = "prdNum_100001"; String clientList = "clientList";// 抢购到商品的顾客列表 Jedis jedis = RedisUtil.getInstance().getJedis(); if (jedis.exists(key)) { jedis.del(key); } if (jedis.exists(clientList)) { jedis.del(clientList); } jedis.set(key, String.valueOf(prdNum));// 初始化 RedisUtil.returnResource(jedis); } } /** * 顾客线程 * * @author linbingwen * */ class ClientThread implements Runnable { Jedis jedis = null; String key = "prdNum_10001";// 商品主键 String clientList = "clientList";//// 抢购到商品的顾客列表主键 String clientName; public ClientThread(int num) { clientName = "编号=" + num; } public void run() { try { Thread.sleep((int)(Math.random()*5000));// 随机睡眠一下 } catch (InterruptedException e1) { } while (true) { System.out.println("顾客:" + clientName + "开始抢商品"); jedis = RedisUtil.getInstance().getJedis(); try { jedis.watch(key); int prdNum = Integer.parseInt(jedis.get(key));// 当前商品个数 if (prdNum > 0) { Transaction transaction = jedis.multi(); transaction.set(key, String.valueOf(prdNum - 1)); List<Object> result = transaction.exec(); if (result == null || result.isEmpty()) { System.out.println("悲剧了,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回 } else { jedis.sadd(clientList, clientName);// 抢到商品记录一下 System.out.println("好高兴,顾客:" + clientName + "抢到商品"); break; } } else { System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品"); break; } } catch (Exception e) { e.printStackTrace(); } finally { jedis.unwatch(); RedisUtil.returnResource(jedis); } } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2017-07-03 PHP页面间的参数传递