我负责的是秒杀模块和网关认证授权功能,秒杀模块其实是促销模块的一个子功能,由于我们的项目老开发人员比较急,为了优先实现功能,多个功能模块只是多模块开发,模块之间调用需要通过httpClient,并没有引入RPC框架;而且各模块的数据库的表也比较混乱没有分库,功能逻辑耦合度也比较大,而且并发能力也比较弱只能达到几百,我们做秒杀功能模块其实是为了后期把服务都迁移到新框架中。
我这次负责的是促销系统的秒杀功能,因为要抗击高并发,所以单独创建独立微服务,独立数据库。大佬把旧项目各个模块的数据库也进行了分库,然后做了微服务适配。我创建秒杀模块创建成独立服务,引入微服务网关进行认证和授权。我对系统安全方面和应对高并发方面比较了解。
.html
风控系统比较简单,是通过设定业务规则,限定一个用户只能参加一个活动,而且只能抢购有限数量的商品
后台订单系统和前端订单系统展示的信息相对应,包括订单列表以及订单详情的展示。
我们系统是微服务架构,分布式部署,我们可以将秒杀也单独做一个服务。
单独给秒杀服务创建一个秒杀库:秒杀订单,秒杀商品
单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他服务。
秒杀前按钮都是置灰的,秒杀时间到了才能点击。
按钮点击之后也给它置灰几秒,防止重复点击。
验证码登陆验证。
秒杀的本质,就是对库存的抢夺。通过redis预扣库存的实现,避免了到底是支付减库存、下单减库存两种方案的选择,因为其各有利弊。而且还将验库存和减库存通过mq进行了解耦,极大的提升系统并发度和响应时间。使得系统从qps不到1000提升到上万的能力。
每个秒杀用户都去数据库查询库存效验库存,然后扣减库存,所有的操作都在数据库,会导致数据库顶不住
秒杀前通过定时任务将库存加载到redis中去,让效验库存的操作在redis中进行,然后发mq同步redis和数据库的数据。
在高并发场景下,多个线程同时效验该商品库存满足条件,然后同时扣减库存导致超卖问题发生。
Lua脚本功能时Redis2.6版本最大的亮点,通过对Lua脚本的支持,Redis解决了长久以来不能高效处理CAS命令的缺点,并且可以通过组合多个redis命令,轻松实现以前很难实现或者不能高效实现的模式。
Lua脚本实现了redis事务操作,我们将判断扣减库存的操作 和 预减库存的操作写到一个Lua脚本,返回扣减后的库存数量,如果库存数量<0则判断为发生超卖,将之前扣减的数目再加回去;如果库存数量>0,则发一个订单消息,然后订单模块生成订单,库存模块扣减库存,支付模块进行支付;支付成功后发送一个支付成功消息,然后优惠券模块扣减优惠券,积分模块增加积分,最后向用户发送短信通知。
JSONObject.parseObject(data);
库存=配额
key=手机号/微信ID+活动ID value=订单数据
kafka的key=redis的key value=orderId
通过Lua脚本实现抢红包功能,很优秀
## 尝试获得红包,如果成功,则返回json字符串,如果不成功,则返回空
## eval函数(脚本名称,参数个数,keys1,keys2,keys3,keys4)
## keys1:预生成的红包队列 keys2: 已消费的红包队列 keys3: 去重map keys4:用户idstatic String tryGetHongBaoScript = "if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 thenn" //如果用户已经抢过红包,则返回nill+ "return niln" + "elsen" + "local hongBao = redis.call('rpop', KEYS[1]);n" //取出一个小红包+ "if hongBao thenn" + "local x = cjson.decode(hongBao);n" + "x['userId'] = KEYS[4];n" //加入用户信息+ "local re = de(x);n" + "redis.call('hset', KEYS[3], KEYS[4], KEYS[4]);n" //将用户放到去重的set中去,防止多次抢红包+ "redis.call('lpush', KEYS[2], re);n" //将红包放入以消费队列中+ "return re;n" + "endn" + "endn" + "return nil"; static public void testTryGetHongBao() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(threadCount); long startTime = System.currentTimeMillis();println("start:" + startTime); for(int i = 0; i < threadCount; ++i) { final int temp = i; Thread thread = new Thread() { public void run() { Jedis jedis = new Jedis(host, port); String sha = jedis.scriptLoad(tryGetHongBaoScript); int j = honBaoCount/threadCount * temp; while(true) { //抢红包方法Object object = jedis.eval(tryGetHongBaoScript, 4, hongBaoList/*预生成的红包队列*/, hongBaoConsumedList, /*已经消费的红包队列*/hongBaoConsumedMap, /*去重的map*/"" + j /*用户id*/); j++; if (object != null) { //// System.out.println("get hongBao:" + object); }else { //已经取完了 if(jedis.llen(hongBaoList) == 0) break; } } untDown(); } }; thread.start(); }
刚开始我们使用的是付款减库存,防止恶意买家大量下单。
微信支付回调会返回微信生成的订单号以及我们自己生成的订单号
a) 设置的秒杀活动的库存,总是莫名其妙的减少了。分析发现是因为我们把减库存放在微信支付的成功回调里面的,而微信会回调这个url8次,导致多次减库存。最后我们通过接口幂等进行了解决
b) 用户下单显示的不是最新的数据库,支付时用户经常由于库存不足而支付失败,这导致用户体验十分不好。
备用库存:商品库用完之后,如果还有用户支付,则直接扣减备用库
优点:缓解部门用户支付失败问题
缺点:不能从根本解决问题,若并发量很大,还是会出现大量用户下单成功缺库存不足而支付失败的问题
微信支付成功后,微信支付平台会发送8次回调地址,这样就得做接口幂等
链接要是提前暴露出去可能有人直接访问url就提前秒杀了
刚开始我们想到做个秒杀开始、截止时间,但是这种方案解决不了通过程序进行抢购。
将URL动态化,通过MD5之类的加密随机的字符串去做url,然后通过前端代码获取url后台效验才能通过
redis集群,主从同步,读写分离。
开启持久化保证高可用
Nginx是高性能web服务器,并发轻松上万并发,但普通Tomcat只能顶住几百的并发。
买流量机
几万并发——>Nginx——>Tomcat集群
保持商品库存数量的准确性是库存系统最根本的功能
本文发布于:2024-02-01 09:44:38,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170675188235751.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |