秒杀系统,库存优化
直接上代码
1 public class RedisShop { 2 3 private final static String PRODUCT_STOCK_KEY = "productStockKey"; 4 private static ConcurrentHashMap<Long, Boolean> productSoldOutMap = new ConcurrentHashMap<>(); 5 private static ConcurrentHashMap<Long, Boolean> getProductSoldOutMap() { 6 return productSoldOutMap; 7 } 8 9 @Autowired 10 private ProductService productService; 11 @Autowired 12 private StringRedisTemplate stringRedisTemplate; 13 @Autowired 14 private Zookeeper zookeeper; 15 16 /** 17 * 将数据添加至redis缓存 18 */ 19 public void init() { 20 21 List<PoductInfo> products = productService.getProductList(); 22 23 for (PoductInfo productInfo : products) { 24 25 stringRedisTemplate.opsForValue().set(PRODUCT_STOCK_KEY + productInfo.getProductId, productInfo.getStock()); 26 } 27 28 } 29 30 public String soleProduct(Long productId) { 31 32 //优化3:创建一个ConcurrentHashMap集合(Jvm线程同步),不等于空==已售完 33 if (productSoldOutMap.get(productId) != null) { 34 35 return "商品已售完"; 36 } 37 38 //优化2:将redis缓存中的数据-1,并返回减1后的值 39 Long stock = stringRedisTemplate.opsForValue().decrement(PRODUCT_STOCK_KEY + productId); 40 if (stock < 0) { 41 42 //优化3:库存不足时,向ConcurrentHashMap添加值 43 getProductSoldOutMap().put(productId, true); 44 45 //优化2:缓存数据+1(这时stock=-1,还原为0,需要+1) 46 stringRedisTemplate.opsForValue().increment(PRODUCT_STOCK_KEY + productId); 47 48 //优化4:ConcurrentHashMap是jvm线程同步,如果是多集群分布式服务,还是会造成并发,可以用消息中间件或zooKeeper解决 49 //获取productId在Zookeeper中的路径 50 String zkSoldProductPath = Constants.getZkSoldProductPath(productId); 51 //如果不存在值为true的zookeeper,则添加一个 52 if (zookeeper.exists(zkSoldProductPath, ture) == null) { 53 zookeeper.create(zkSoldProductPath, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode); 54 } 55 56 return "商品已售完"; 57 } 58 59 try { 60 61 //业务逻辑 62 63 //优化1:操作数据库,-1操作在sql中执行,防止并发(数据库乐观锁) 64 65 } catch (Exception ex) { 66 67 //优化2:秒杀失败,还原stock,需要+1 68 stringRedisTemplate.opsForValue().increment(PRODUCT_STOCK_KEY + productId); 69 70 //优化3:秒杀失败,这时库存至少为1,移除ConcurrentHashMap值 71 if (productSoldOutMap.get(productId) != null) { 72 productSoldOutMap.remove(productId); 73 } 74 75 //优化4:修改zk的商品售完标记为false 76 if (zookeeper.exists(zkSoldProductPath, ture) != null) { 77 zookeeper.setData(Constants.getZkSoldProductPath(productId), "false".getBytes(), -1); 78 } 79 80 return "创单失败"; 81 } 82 83 return ""; 84 85 }
优化一共分为四个方面:
1.数据库优化:对库存的数据库操作,直接在sql中进行-1,mySql数据库自带乐观锁。(缺点:并发在500-1000左右,最大并发连接数是16384。)
2.redis缓存:将要对数据库操作的数据放入redis缓存中,先判断缓存中数据是否满足,不行直接return,大大减少了数据库压力。(并发数能达到十万左右,缺点:要考虑redis缓存和mysql数据的一致性)
3.ConCorrectHashMap集合:线程同步的集合,库存不足时向ConCorrectHashMap集合中添加一个值,先检查ConCorrectHashMap集合是否有值,再检查stock。(解决了redis和mysql数据的一致性,缺点:JVM级别的线程同步,在多集群分布式服务中并不适用)
4.消息中间件或ZooKeeper:库存不足时向zookeeper中添加一个值,客户端进行监听数据