在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一,秒杀需要具备的功能:秒杀通常是电商中用到的吸引流量的促销活动方式 搭建秒杀系统,需要具备以下几点: 1,限制每个用户购买的商品数量,(秒杀价格为吸引流量一般会订的很低,不能让一个用户全部抢购到手) 2,处理速度要快,避免在高并发的情况下发生堵塞 3,高并发情况下,不能出现库存超卖的情况 因为redis中对lua脚本执行的原子性,不会出现因高并发而导致数据查询的延迟 所以我们选择使用redis+lua来实现秒杀的功能 例子:如果同一个秒杀活动中有多件商品,而有人用软件刷接口的方式来下单, 这时就需要有针对当前活动的购买数量限制
说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest 对应的源码可以访问这里获取: https://github.com/liuhongdi/ 说明:作者:刘宏缔 邮箱: [email protected]
二,本演示项目的相关信息1,项目地址: https://github.com/liuhongdi/seconddemo 2,项目原理: 在秒杀项目开始前,要把sku及其库存数同步到redis中, 有秒杀请求时,判断商品库存数, 判断用户已购买的同一sku数量, 判断用户已购买的同一秒杀活动中的商品数量, 如果以上两个数量大于0时,需要进行限制 如有问题时,返回秒杀失败 都没有问题时,减库存,返回秒杀成功
要注意的地方: 秒杀前要获取此活动中的对购买活动/sku的数量限制 秒杀成功后,如果用户未支付导致订单过期恢复库存时,redis中的库存数也要同步
3,项目结构:
三,lua代码说明1,second.lua local userId = KEYS[1] local buyNum = tonumber(KEYS[2]) local skuId = KEYS[3] local perSkuLim = tonumber(KEYS[4]) local actId = KEYS[5] local perActLim = tonumber(KEYS[6]) local orderTime = KEYS[7] --用到的各个hash local user_sku_hash = 'sec_'..actId..'_u_sku_hash' local user_act_hash = 'sec_'..actId..'_u_act_hash' local sku_amount_hash = 'sec_'..actId..'_sku_amount_hash' local second_log_hash = 'sec_'..actId..'_log_hash' --当前sku是否还有库存 local skuAmountStr = redis.call('hget',sku_amount_hash,skuId) if skuAmountStr == false then --redis.log(redis.LOG_NOTICE,'skuAmountStr is nil ') return '-3' end; local skuAmount = tonumber(skuAmountStr) --redis.log(redis.LOG_NOTICE,'sku:'..skuId..';skuAmount:'..skuAmount) if skuAmount <= 0 then return '0' end redis.log(redis.LOG_NOTICE,'perActLim:'..perActLim) local userActKey = userId..'_'..actId --当前用户已购买此活动多少件 if perActLim > 0 then local userActNumInt = 0 local userActNum = redis.call('hget',user_act_hash,userActKey) if userActNum == false then --redis.log(redis.LOG_NOTICE,'userActKey:'..userActKey..' is nil') userActNumInt = buyNum else --redis.log(redis.LOG_NOTICE,userActKey..':userActNum:'..userActNum..';perActLim:'..perActLim) local curUserActNumInt = tonumber(userActNum) userActNumInt = curUserActNumInt+buyNum end if userActNumInt > perActLim then return '-2' end end local goodsUserKey = userId..'_'..skuId --redis.log(redis.LOG_NOTICE,'perSkuLim:'..perSkuLim) --当前用户已购买此sku多少件 if perSkuLim > 0 then local goodsUserNum = redis.call('hget',user_sku_hash,goodsUserKey) local goodsUserNumint = 0 if goodsUserNum == false then --redis.log(redis.LOG_NOTICE,'goodsUserNum is nil') goodsUserNumint = buyNum else --redis.log(redis.LOG_NOTICE,'goodsUserNum:'..goodsUserNum..';perSkuLim:'..perSkuLim) local curSkuUserNumint = tonumber(goodsUserNum) goodsUserNumint = curSkuUserNumint+buyNum end --redis.log(redis.LOG_NOTICE,'------goodsUserNumint:'..goodsUserNumint..';perSkuLim:'..perSkuLim) if goodsUserNumint > perSkuLim then return '-1' end end --判断是否还有库存满足当前秒杀数量 if skuAmount >= buyNum then local decrNum = 0-buyNum redis.call('hincrby',sku_amount_hash,skuId,decrNum) --redis.log(redis.LOG_NOTICE,'second success:'..skuId..'-'..buyNum) if perSkuLim > 0 then redis.call('hincrby',user_sku_hash,goodsUserKey,buyNum) end if perActLim > 0 then redis.call('hincrby',user_act_hash,userActKey,buyNum) end local orderKey = userId..'_'..skuId..'_'..buyNum..'_'..orderTime local orderStr = '1' redis.call('hset',second_log_hash,orderKey,orderStr) return orderKey else return '0' end 2,功能说明: --用到的各个参数 local userId 用户id local buyNum 用户购买的数量 local skuId 用户购买的sku local perSkuLim 每人购买此sku的数量限制 local actId 活动id local perActLim 此活动中商品每人购买数量的限制 local orderTime 下订单的时间 --用到的各个hash 判断的流程: 判断商品库存数, 判断用户已购买的同一sku数量, 判断用户已购买的同一秒杀活动中的商品数量
四,java代码说明:1,SecondServiceImpl.java 功能:传递参数,执行秒杀功能 /* * 秒杀功能, * 调用second.lua脚本 * actId:活动id * userId:用户id * buyNum:购买数量 * skuId:sku的id * perSkuLim:每个用户购买当前sku的个数限制 * perActLim:每个用户购买当前活动内所有sku的总数量限制 * 返回: * 秒杀的结果 * * */ @Override public String skuSecond(String actId,String userId,int buyNum,String skuId,int perSkuLim,int perActLim) { //时间字串,用来区分秒杀成功的订单 int START = 100000; int END = 900000; int rand_num = ThreadLocalRandom.current().nextInt(END - START + 1) + START; String order_time = TimeUtil.getTimeNowStr()+"-"+rand_num; List<String> keyList = new ArrayList(); keyList.add(userId); keyList.add(String.valueOf(buyNum)); keyList.add(skuId); keyList.add(String.valueOf(perSkuLim)); keyList.add(actId); keyList.add(String.valueOf(perActLim)); keyList.add(order_time); String result = redisLuaUtil.runLuaScript("second.lua",keyList); System.out.println("------------------lua result:"+result); return result; }
2,RedisLuaUtil.java 功能:负责调用lua脚本的类 @Service public class RedisLuaUtil { @Resource private StringRedisTemplate stringRedisTemplate; private static final Logger logger = LogManager.getLogger("bussniesslog"); /* run a lua script luaFileName: lua file name,no path keyList: list for redis key return other: fail 1: success */ public String runLuaScript(String luaFileName,List<String> keyList) { DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName))); redisScript.setResultType(String.class); String result = ""; String argsone = "none"; //logger.error("开始执行lua"); try { result = stringRedisTemplate.execute(redisScript, keyList,argsone); } catch (Exception e) { logger.error("发生异常",e); } return result; } }
五,测试秒杀的效果1,访问:http://127.0.0.1:8080/second/index 添加库存 如图:
2,配置jmeter开始测试: 参见这一篇: https://www.cnblogs.com/architectforest/p/13087798.html 定义测试用到的变量: 定义线程组数量为100 定义http请求: 在查看结果树中查看结果: 3,查看代码中的输出: ------------------lua result:u3_cpugreen_1_20200611162435-487367 ------------------lua result:-2 ------------------lua result:u1_cpugreen_2_20200611162435-644085 ------------------lua result:u3_cpugreen_1_20200611162435-209653 ------------------lua result:-1 ------------------lua result:u2_cpugreen_1_20200611162434-333603 ------------------lua result:-1 ------------------lua result:-2 ------------------lua result:-1 ------------------lua result:u2_cpugreen_1_20200611162434-220636 ------------------lua result:-2 ------------------lua result:-1 ... 每个用户的购买数量均未超过2单,秒杀的限制成功
六,查看spring boot的版本:. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.0.RELEASE)
|
请发表评论