前言学过redis基础的都学的差不多了,但是没有做过具体的项目实践。可以看这篇文章做一个项目来巩固知识。相关要求及说明总体来说,秒杀系统的功能并不多,主要包括:制定秒杀计划。某天几点开始,卖什么产品,卖多少,持续多久。显示闪购列表。一般是当天陈列,有的8点卖,有的10点卖。商品详情页。下单购买。等等等等。本文主要目的是用代码实现防止商品超卖的功能,所以制定秒杀计划、展示商品等功能不会重写。也有电商产品主要加工SPU(比如iPhone12、iPhone11是两个SPU)和SKU(比如iPhone1264G白色、iPhone12128G黑色是两个SKU)。显示SPU,采购扣除库存后使用的SKU。为了方便,本文直接将其替换为product。下单也有一些前置条件,比如通过风控系统确认你是否是黄牛;营销系统,是否有相关的优惠券、虚拟货币等。下单后还有仓管、物流、积分,本文不做赘述。这篇文章不涉及数据库,一切都是在Redis上操作的,但是我还是想说一下数据库和缓存数据的一致性。如果我们系统的并发不高,数据库可以支持的话,我们可以直接操作数据库。为了防止超卖,我们可以使用:悲观锁select*fromSKUtablewheresku_id=1forupdate;乐观锁updateSKU表setstock=stock-1wheresku_id=1andupdate_version=旧版本号;如果并发度较高,比如商品详情页一般并发度最高。为了减轻数据库的压力,使用了Redis等缓存。为了保证数据库与Redis的一致性,大多采用“修改后删除”的方案。但是在这种方案并发较高的情况下,比如C10K,C10M等,在修改数据库,删除Redis内容的瞬间,大量的并发查询会传到数据库,导致在一个例外。在这种情况下,SPUdetails的接口一定不能连接到数据库。步骤应该是:B端管理系统操作数据库(这个并发不会高)。存储数据后,向MQ发送消息。相关处理程序收到订阅的MQTopic后,从数据库中取出信息放入Redis。相关服务接口只从Redis取数据。代码实现在实际项目中,建议将ToC端的秒杀产品相关接口组合成一个微服务,product-server。销售接口被组合成一个微服务,order-server。编码可以参考之前的SpringCloud系列文章。本文简单使用了一个SpringBoot项目。秒杀计划实体类省略*/privateLongprice;/***Dashlinepriceunit:cents*/privateLonglinePrice;/***stocknumber*/privateLongstock;/***用户只购买了一件商品,标识符为0No1Yes*/privateintbuyOneFlag;/***计划状态0未提交,1已提交*/privateintplanStatus;/***开始时间*/privateDatestartTime;/***结束时间*/privateDateendTime;/***创建时间*/privateDatecreateTime;}说明:如上所述,限时抢购商品应显示SPU,销售扣减库存应为SKU。为方便起见,本文仅使用product代替。用户购买闪购产品有两种方式:一个用户只能购买一件商品。用户可以多次购买多个项目。所以这个类使用buyOneFlag作为标识。planStatus表示秒杀是否真正执行。0不会显示给C端,也不会出售;1会显示到C端并出售。添加秒杀计划&查询秒杀计划@RestControllerpublicclassProductController{@ResourceprivateRedisTemplate>addSecKillPlan(@RequestParam("saledate")StringsaleDate){DateTimeFormatterdtf=DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss");Randomrand=newRandom();Gsongson=newGson();List
>(){}.getType());//设置新的库for(SecKillPlanEntity:list){if(entity.getBuyOneFlag()==1){longnewStock=redisTemplate.opsForList().size("product_one_stock_"+entity.getProductId());entity.setStock(newStock);}else{longnewStock=Long.parseLong(redisTemplate.opsForValue().get("product_stock_"+entity.getProductId()));实体。setStock(newStock);}}returnDefaultResult.success(list);}}说明:addSecKillPlan就是随机生成10个销售计划,只有卖一件或卖多件并将相关数据推送到Redis。seckill_plan_date,代表某一天的所有秒杀计划,用于列表显示。product_productID,代表一个商品信息,用于详情页。product_one_stock_ProductID,表示只卖一种商品的存货数量,取值为List,有存货多少就往里面塞多少个“1”。product_buyers_产品ID,代表只销售一种产品的买家,已经购买过的用户不允许再次购买。product_stock_productID,表示可销售的多个商品的库存数量,值为库存数量。findSecKillPlanByDate,显示某天的秒杀销售计划。从与库存相关的两个KEY中获取库存编号。LUA脚本只卖一个buyone.lua:--商品库存Keyproduct_one_stock_XXXlocalstockKey=KEYS[1]--商品购买用户记录Keyproduct_buyers_XXXlocalbuyersKey=KEYS[2]--用户IDlocaluid=KEYS[3]--查询用户是否购买localresult=redis.call("sadd",buyersKey,uid)if(tonumber(result)==1)then--没有购买,可以购买localstock=redis.call("lpop",stockKey)--除了nil和false,其他值为真(包括0)if(stock)then--有货退货1else--缺货退货-1endelse-已购买退货-3end可卖多件buymore.lua:--productKeylocalkey=KEYS[1]--购买数量localval=ARGV[1]--现有总库存localstock=redis.call("GET",key)if(tonumber(stock)<=0)then--无库存返回-1else--得到扣除后的总库存=总库存-购买数量localdecrstock=redis.call("DECRBY",key,val)if(tonumber(decrstock)>=0)then--扣除购买数量后没有超卖,返回当前库存returndecrstockelse--超卖,把扣掉的加回来redis.call("INCRBY",key,val)return-2endend注:1.只卖一件。首先使用命令“sadd”将买家ID输入product_buyers_productID。如果返回1,表示用户之前没有购买过,否则返回-3,表示已经购买过。lpopoutproduct_one_stock_productID中的值,如果还有存货,则返回1,有存货,否则为nil,无存货。2.可以卖多件。前面已经说过了,不再赘述。将这两个lua文件放在SpringBoot项目的resources目录下。销售接口@RestControllerpublicclassOrderController{@ResourceprivateRedisTemplate
