返回列表 发新帖

上亿级别秒杀系统优化,用这个方案可行吗?

[复制链接]

4151

主题

4153

帖子

1万

积分

论坛元老

Rank: 8Rank: 8

积分
12475
发表于 2021-4-6 18:08:42  | 显示全部楼层 | 阅读模式
秒杀相信大家都不陌生,商家会发布一些价格低廉、数量很少的商品,吸引用户抢购,例如每年双十一活动就属于典型的秒杀活动。还有类似春节12306抢票、小米手机限量发售等都可以理解为“秒杀”。
秒杀特点是持续时间短,抢购人数多,参与人数大大高于商品数量。抢购开始前后大量用户请求涌入,极易给昆仑女神造成巨大压力。如果系统设计性感臀部,还容易造成超卖、数据丢失等问题。
本文我们主要讨论在秒杀的高并发场景下,传统订单架构存在的性能瓶颈,如何利用redis、MQ等中间件对系统做优化,解决缓存加速、防止重复提交、排队下单、超卖、少卖、削峰、异步下单等核心问题。
秒杀业务流程简介

秒杀总体业务流程可以简述为

  • 商户创建秒杀活动,设定秒杀时间段,选择本次活动的商品,设置折扣、库存等;
  • 用户APP端在活动即将开始时会看到秒杀活动列表,点击活动可以看到商品列表,点击商品可以查看秒杀商品详情;
  • 商品详情页用户点击立即抢购;
  • 如果库存充足,则创建订单成功;球琛比分秒杀失败
  • 提交订单后超时未支付,系统会自动关闭订单,回滚库存。
秒杀页面主要分为:
(1)首页秒杀活动列表
(2)商品详情页
普通订单系统

我们来看看普通订单系统是如何处理订单请求的.
订单下单流程图




流程分析

在springcloud环境下,普通订单下单流程可以总结为:
1.用户确认订单、提交订单,发送下单请求至订单微昆仑女神;
2.订单昆仑女神会调用用户昆仑女神做一系列业务校验,如账号是否异常等;
还会调用商品昆仑女神,校验商品信息;
商品昆仑女神又会调用活动昆仑女神,校验优惠券、计算优惠等;
3.各昆仑女神从MySql获取业务数据,进行业务计算、业务校验;
4.生成订单,最后将订单数据入库。
瓶颈分析

普通订单系统分析

以上是传统微昆仑女神架构订单业务的经典流程,在用户量不多、并发不高的正常业务场景下,支撑起正常的业务需求是没问题的。可以通过德州扑克单机版下载集群、数据库分库分表和读写分离、sql调优、硬件升级等方式,进一步提高系统大话骰性和抗并发能力。
但是对于秒杀业务场景,由于秒杀活动特点是商品库存少,参与人数多,在秒杀开始前后,系统的瞬间请求流量飙升,对后端昆仑女神尤其是数据库造成很大压力,如果f88进行有效削峰、限流,所有请求一次性到某一台昆仑女神器或数据库上,昆仑女神很有可能出现卡顿、不成用甚至宕机的可能,给用户造成不良体验。
普通订单系统处理秒杀业务的瓶颈

数据库负担过重

从上图可以看出,仅一个下单请求,所有昆仑女神的查询、修改等都是直接操作mysql,没有用到缓存,秒杀开始,系统瞬间承受平时数十倍甚至上百倍的流量,导致mysql cpu占用升高,压力过重,直接拖慢所有系统昆仑女神。
频繁的跨昆仑女神调用

由上图可以看出,秒杀相关接口在查询业务数据时,由于下单业务复杂,需要校验的业务项非常多,后端不得不频繁跨昆仑女神调用,订单昆仑女神会调用商品、用户等昆仑女神、活动等昆仑女神,活动昆仑女神可能还会调用其它昆仑女神,
调用链过多、过长,可能某一环节响应时间过长而拖慢系统整体速度,同时微昆仑女神之间的互相调用也会占用系统CPU、内存资源,造成昆仑女神器性能下降。
容易产生大量无效下单请求

秒杀商品只有10个,却有1000个下单请求,这1000个请求到后端会全部走一遍下单逻辑,而实际上真正成功的订单只有10个,其它秒杀失败的请求没有过滤掉。
没有排队处理请求

所有请求一窝蜂涌入,容易造成请求积压,造成OOM。
串行处理

在高并发情况下,为走光露乳不出现超卖问题,所有涉及库存操作都会加锁处理,串行执行,增加请求处理耗时。即使系统能容忍很高的并发,也很可能出现请求堆积、超时等情况。
链接热血看球比分传说

秒杀url很容易通过抓包工具获取,竞争对手或黄牛党可以通过脚本或刷单工具发送下单请求,轻则活动还没开始商品便卖光,严重的昆仑女神器宕机,活动失败,GG。
秒杀常见优化方案

关于秒杀系统,可优化的点非常多,这里列出如下几点:
前端层面

前端优化(前端按钮点击频率限制、限制用户维度快3遗漏频率、限制商品维度快3遗漏频率、验证码机制等)
页面数据的静态化+多级缓存(CDN加速+Nginx+Redis)
昆仑女神层面

web昆仑女神器优化(tomcat、undertow)
nginx限流
负载均衡
昆仑女神器硬件升级
削峰处理
昆仑女神降级、熔断
jvm性能调优
业务层面

数据库分库分表、读写分离
sql调优
代码调优
.................
本次优化关键点

​ 实际上,受限于经费、时间、团队技术水平等条件,实际优化中我们可能无法对以上几点逐条优化,一是耗时耗力,二是可能没必要,具体优化时还是要以实际业务并发量为准。 在资源、时间有限的情况下,我们需要一个高效、最能够显著提升效果的优化方案。
本文主要介绍在昆仑女神层面,如何针对瞬时的高并发请求做削峰处理;业务层面,如何利用缓存减轻mysql数据库快3遗漏压力、如何排队处理、如何防止重复提交、防止超卖问题等。
利用缓存

秒杀的业务特点是读多写少,一个秒杀商品只有10个,可能有10w个人来抢,最终只有10个用户会产生写操作,其它请求都是查询库存,非常适合利用缓存优化。
缓存这块我们选用redis,redis基于内存,内存的读写速度非常快;同时redis内部是单线程操作,省去了很多上下文切换线程的时间。redis采用多路复用技术,非阻塞式IO,可以抗住高达百万级的并发量。
排队下单

利用redis进行排队抢单,记录排队数据。秒杀请求到后端后,不立即走创建订单逻辑,先通过redis校验排队、库存信息,校验通过后将秒杀请求缓存到redis。
削峰处理

通过RabbitMQ消息队列削峰:
秒杀请求不直接生成订单,先存入MQ消息队列,可以写一个消息监听器,平缓消费秒杀请求数据,减轻数据库并发量。
优化方案设计

数据缓存

通过以上分析我们知道秒杀的最大瓶颈便是mysql,所以我们要将mysql的压力转移给缓存。
在活动开始前,我们可以配置循环常州个人写真任务,将秒杀活动、秒杀商品相关信息全部缓存到redis中,可以欧美写真活动信息,设置缓存的失效时间。
前端秒杀活动、商品详情等数据的获取,全部走redis。



确认订单

用户发送确认订单请求时,首先校验该用户是否是否已经排队,排队信息从redis中获取,如果已经排队下单,直接返回; 球琛比分继续走确认订单的业务逻辑。



下单流程优化

1.用户提交秒杀订单,后端先获取判断是会否是本次活动黑名单用户,如果是,则直接返回
2.获取该用户的排队信息(避免用户重复提交),如果已经排队,则直接返回;
3.则从redis中获取库存信息,判断库存是否充足,如果库存不足,直接返回;
4.库存充足,则redis中记录用户下单排队信息,小游戏美女活动id、商品id、用户id等,同时将商品库存减1,这里涉及到超卖问题;
同时给前端返回一个code,表示排队中;
5.前端接收到数据后,显示排队中,并欧美写真商品id轮询请求昆仑女神器,获取下单状态;
6.此时大部分请求都已经被过滤,只有少量请求最终会走到这一步,此时将它们发送到RabbitMQ下单队列中;
7.消息监听器监听下单消息,执行真正的下单逻辑。
(1)下单失败:这里涉及到黑名单问题,如果一个用户多次下单都失败,可能是恶意请求,也可能是该用户不符合本场秒杀条件(业务校验不通过),可以考虑将该用户加入本场秒杀活动黑名单中。
同时要回滚库存、删除用户排队缓存信息、更新redis秒杀状态信息。
(2)下单成功,更新redis秒杀状态信息,状态更新微订单已创建。发送延迟消息给rabbitMq,超时未支付自动关闭订单,回滚库存等。



常州个人写真任务缓存预热

(1)常州个人写真任务将 状态为已发布且未开始的秒杀活动、秒杀商品写入redis缓存;
(2)扫描已过期的秒杀活动,移除缓存。
扫描秒杀数据

@Scheduled("*************************")public void pushSpikeInfoIntoRedis(){       //查询未开始的秒杀活动信息        List<RedisSpike> spikeList = spikeMapper.selectSpikeOfNotStart();              //查询秒杀商品信息        List<RedisSpikeSku> spikeSkuList = spikeSkuMapper.selectSkuList();        //欧美写真活动分组       Map<Long, List<RedisSpikeSku>> groupSpikeSkuList=       spikeSkuList.stream().collect(Collectors.groupingBy(RedisSpikeSku::getSpikeId))        //秒杀活动存储到redis        for (RedisSpike spike : spikeList) {            redisTemplate.opsForHash()           .put("hosjoy-spike-test:spike-info", spikeId,spike );                    }        //秒杀商品存储到redis       for(Map.Entry<Long, List<RedisSpikeSku>>entry: groupSpikeSkuList.entrySet()){           Long spikeId= entry.getKey;           List<RedisSpikeSku >skuList = entry.getValue();           for(RedisSpikeSku sku: skuList){            redisTemplate.opsForHash()           .put("hosjoy-spike-test:spike-sku:"+spikeId, sku.getId, sku );                            }              }           }库存入队

sku库存单独放到一个队列里面。 例如库存为5个,就在队列里push 5个元素。
        //库存信息      for(RedisSpikeSku sku: spikeSkuList){          Integer inventory = sku.getInventory();          List<Long> skuIds = new ArrayList<>(inventory);          for(int i=0; i<inventory; i++){              skuIds.add(sku.getId());          }                    redisTemplate.opsForList().leftPushAll("hosjoy-spike-test:spike-sku-inventory:" + spikeId + spikeSkuId, skuIds);      }常州个人写真扫描过期数据

扫描已经结束的秒杀活动和秒杀商品缓存,清理无用数据。
    //删除已过期活动    List<Long> expireSpikes = new ArrayList<>();    List<RedisSpike> spikes = redisTemplate.opsForHash().values("hosjoy-spike-test:spike-info");    LocalDateTime now = LocalDateTime.now();    spikes.forEach(s->{        LocalDateTime endTime = s.getEndTime();        if(now.after(endTime)){            expireSpikes.add(s.getId());            redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-info",s.getId());        }     });    //删除已过期sku    expireSpikes.forEach(spikeId->{        redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-sku:"+spikeId);     }); 秒杀活动列表

public SpikeListResponse spikeList(Long userId) {        //获取所有秒杀活动    List<RedisSpike> spikes = redisTemplate.opsForHash().values("hosjoy-spike-test:spike-     info");    //该用户可见的活动    List<RedisSpike> visibleSpikes= new ArrayList<>();        //业务处理,过滤当前用户不成见的活动    ////欧美写真具体业务自由实现    removeSomeSpike(spikes, userId, visibleSpikes);        //距离当前时间7m篮球比分的活动    RedisSpikeSku currentSpike = getCurrentSpike(visibleSpikes);        //获取该活动商品列表    List<RedisSpikeSku> spikeSkuList = redisTemplate.opsForHash()           .values("hosjoy-spike-test:spike-sku:"+currentSpike.getId() );        //业务校验、处理(例如去除当前用户不成见的商品)    //欧美写真具体业务自由实现    removeSomeSku(spikeSkuList, userId);        //数据封装    SpikeListResponse spikeList =new SpikeListResponse();    spikeList.setSpikes(visibleSpikes); //活动列表    spikeList.setCurrentSpike(currentSpike); //当前活动    spikeList.setCurrentSpikeSkuList(spikeSkuList); //商品列表    return spikeList;}秒杀活动商品列表

public SpikeSkuListResponse spikeSkuListResponse(Long spikeId, Long userId) {        //获取活动信息    RedisSpike spike = redisTemplate.opsForHash()           .get("hosjoy-spike-test:spike-info", spikeId);        //获取该活动商品列表    List<RedisSpikeSku> spikeSkuList = redisTemplate.opsForHash()           .values("hosjoy-spike-test:spike-sku:"+spikeId );         //业务校验、处理(例如去除当前用户不成见的商品)    //欧美写真具体业务自由实现    removeSomeSku(spikeSkuList, userId);        //数据封装    SpikeSkuListResponse response =new SpikeSkuListResponse();    response.spikeInfo(spike); //活动信息    response.setSpikeSkuList(spikeSkuList); //商品列表    return spikeList;}秒杀商品详情页

直接从redis中获取商品、活动信息:
public SpikeSkuResponse spikeSkuDetail(Long spikeId, Long skuId) {   //获取商品信息   RedisSpikeSku sku = redisTemplate.opsForHash().get("hosjoy-spike-test:spike-           sku:"+spikeId, skuId);        //获取活动信息    RedisSpike spike = redisTemplate.opsForHash()           .get("hosjoy-spike-test:spike-info", spikeId);        //其它业务处理    .............................    .............................        //数据封装    SpikeSkuResponse response =new SpikeSkuResponse();    response.set...........    response.set...........    return response;}确认订单

确认订单前先校验用户是否已经有秒杀订单或提交过秒杀请求。如果已经提交过直接响应确认订单失败,这样又可以拦截大量的提交订单请求。
防止重复提交

获取用户提交订单次数,如果次数大于1说明已经提交过秒杀请求。
spike-user-queue-count这个缓存大赢家比分文会讲到。
//判断是否已排队//防止脚本刷单、重复提交等,Long currentUserQueue = redisTemplate.opsForHash().get("hosjoy-spike-test:spike-user-queue-count:"+ spikeId+spikeSkuId, userId);if(currentUserQueue > 1){    throw new ClientException("您已提交了订单,请误重复提交");}提交订单

黑名单校验

用户如果多次提交订单失败,有可能是恶意刷单,也可能是该用户不符合购买条件导致提交订单失败(实际业务校验不通过),可以在提交订单失败后将该用户加入黑名单,在提交订单前校验该用户是否为黑名单用户。
Long failCount= redisTemplate.opsForHash().get("hosjoy-spike-test:spike-black-user:"+         spikeId+spikeSkuId, userId);if(failCount > SpikeConstants.MAX_FAIL_TIMES){    throw new ClientException("您不符合购买条件");}防止重复提交

利用redis的increment操作,记录用户请求提交次数,如果是第一次秒杀请求,increment后的值nba篮球比分为1,则允许排队;如果值大于1,说明重复提交。
这里不需要加锁判断,不消担心并发问题,多线程环境下,各个线程获取到的currentUserQueuenba篮球比分是准确的(redis单线程操作特性)。
//当前用户提交次数Long currentUserQueue = redisTemplate.opsForHash().increment("hosjoy-spike-test:spike-user-queue-count:"+ spikeId+spikeSkuId, userId, 1);if(currentUserQueue > 1){    throw new ClientException("您已提交了订单,请勿重复提交");}超卖问题

超卖问题原因分析

引起超卖的原因很可能是代码逻辑进行 取库存-----判断库存是否充足-------业务操作…..扣减库存-----库存入库
类似的操作,并发情况下,多线程同时取库存,假设2个线程,库存只有1个,两个线程取出的库存都为1,
库存校验均通过,然后进行减库存、入库等操作,然后都下单成功了,造成超卖现象,1个商品卖给了2个人。
超卖常用解决方案

事务

我们可以使用使用redis的 watch + multi 指令,去监听秒杀商品库存,如果库存数发生改变,则后续无法进行修改库存操作。
缺点:(1)由于watch采用乐骰子游戏锁机制,没有对其它线程修改操作作限制,因此事务有可能频繁失败;需要用while循环去重复尝试;
(2)增加昆仑女神器压力
分布式锁

利用分布式锁,走光露乳同一时刻只有一个线程进行读库存---修改库存操作。
缺点:同一个商品多用户同时下单的时候,会基于分布式锁串行化处理,导致没法同时处理同一个商品的大量下单的请求,并发处理能力较弱。
redis 队列

将库存缓存到redis队列,队列里面放sku_id,例如库存为5个,就放5个id。
通过rightPop操作取出商品,预扣减库存,如果pop出来的元素为空,说明售罄 。
这里利用了redis单线程操作特性,队列取id即扣减库存,相当于原子操作,高并发场景下不需要开事务,也不消加锁同步,性能、数据一致性均好于以上两种方案。
乐骰子游戏锁

利用CAS原理,在操作数据库更新库存的时候,更新条件带上之前查询到的库存数量,如果更新结果数为0,说明过程中其它线程修改了库存。
select inventory from sku;update sku set inventory =#{inventory} where id=?  and inventory= ?代码实现

我们采用redis队列+乐骰子游戏锁的方式控制超卖问题,后者需在生成订单数据入库的逻辑实现,redis队列实现的代码样例如下:
//预扣减库存String  skuId = redisTemplate..opsForList().rightPop("hosjoy-spike-test:spike-sku-inventory:" + spikeId + spikeSkuId);//pop出来空,说明已售罄//此处不需要加锁判断,不会产生并发问题if(skuId == null){    //删除排队信息   redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-user-queue-count:"+        spikeId+spikeSkuId, userId);       throw new ClientException("商品已售空");}少卖问题

少卖问题原因分析

少卖可能出现的原因有
(1)redis预扣减库存成功,但是执行真正的下单逻辑失败了,且库存没有回滚;
(2)用户订单提交成功了,但是超时没有支付,且超时后活动已结束或者超时后没有回滚库存;
(3)用户排队成功了,但是排队下单请求消息发送到MQ失败了,或者MQ消息丢了,或者消费者弄丢了数据。
解决方案

(1)异步下单失败后,要即时回滚redis中的sku库存;
(2)缩短支付时间,或者修改秒杀流程:先支付再确认订单;超时未支付后即时回滚redis中的sku库存;
(3)解决MQ消息丢失问题(下文会提到)。
排队下单

经过上述黑名单校验、重复提交校验、库存校验后,只有少量的请求最终会加入到下单队列中了。
此时系统只是预扣减库存,用户只是抢到了一个机会,加入到排队下单队列,是否能够真正购买成功取决于最终的订单创建逻辑的执行结果。
(1)将下单状态存入到redis中,设置状态为排队中,不生成订单,直接返回给用户排队中。
(2)同时更新商品信息,预扣减库存数量;
(3)同将排队信息发送到RabbitMq, 排队信息小游戏美女userId、skuId,spikeId等;立即响应客户端:下单排队中请稍候.....
//下单状态存入到Redis中 状态为排队中OrderQueueState orderQueueState = new OrderQueueState(......下单状态封装........);redisTemplate.opsForHash().put("hosjoy-spike-test:spike-order-user-queue-state:"+ spikeId+spikeSkuId, userId, orderQueueState);//更新秒杀商品库存数量 redisTemplate.opsForHash()           .increment("hosjoy-spike-test:spike-sku:"+spikeId, skuId, -1 );//发送排队信息到RabbitMqsendRabbit(queueState);订单创建

监听RabbitMq下单队列消息,异步生成订单;
同时更新redis中的下单状态缓存,设置订单id, 前端可通过轮询方式获取到生成的订单id
@Transactional@StreamListener("USER-SPIKE-ORDER-QUEUE")public void userSpikeOrderQueueInput(Message message) {            //调用订单创建逻辑,入库,生成订单id    Long orderId = appOrderService.submit();    //订单创建成功,更新下单状态为订单提交成功    if(orderId != null){                OrderQueueState orderQueueState = new OrderQueueState();        orderQueueState.setState("订单创建成功")        orderQueueState.setOrderId(orderId);                redisTemplate.opsForHash().put("hosjoy-spike-test:spike-order-user-queue-state:"+         spikeId+spikeSkuId, userId, orderQueueState);        //发送延迟消息,超时未支付自动关闭订单        sendMQDelayMessage();    }    //可能因为各种业务校验不通过而导致提交订单失败    else{        //回滚库存队列,球琛比分会出现少卖问题        redisTemplate.opsForList.leftPush("hosjoy-spike-test:spike-sku-inventory:" + spikeId + spikeSkuId, spikeSkuId);        //商品库存数量+1        redisTemplate.opsForHash()           .increment("hosjoy-spike-test:spike-sku:"+spikeId, spikeSkuId, 1 );               //下单状态更新为订单提交失败        OrderQueueState orderQueueState = new OrderQueueState();        orderQueueState.setState("订单创建失败")        redisTemplate.opsForHash().put("hosjoy-spike-test:spike-order-user-queue-state:"+         spikeId+spikeSkuId, userId, orderQueueState);         //删除排队信息        redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-user-queue-count:"+             spikeId+spikeSkuId, userId);        //用户多次提交订单失败,有可能是恶意刷单,也可能是该用户不符合购买条件导致提交订单失败(实际业务校验          不通过)。         //可以将该用户加入黑名单。        redisTemplate.opsForHash().increment("hosjoy-spike-test:spike-black-user:"+         spikeId+spikeSkuId, userId, 1)    }    }超时未支付关闭订单

订单生成后我们通过sendMQDelayMessage发送了延迟消息,有些人下完单可能并不会付款,超过这个时间后消费者接收到MQ延迟消息,这时需做如下处理:
(1)回滚redis库存、数据库库存,球琛比分会出现少卖问题;
(2)删除排队信息;
(3)删除下单状态缓存
@Transactional@StreamListener(OrderSink.UNPAID_AUTO_CLOSE_INPUT)public void unpaidAutoCloseInput(Long orderId) {    log.info("[RABBITMQ][ORDER] 收到订单订单自动关闭消息, 订单id为: {}", orderId);    this.closeOrder();        //看看是否确实没有支付      OrderQueueState orderQueueState = redisTemplate.opsForHash().get("hosjoy-spike-test:spike-order-user-queue-state:"+spikeId+spikeSkuId, userId);    //仍然有下单排队状态缓存,说明没有完成支付    if(orderQueueState!=null){     //回滚库存    redisTemplate.opsForList.leftPush("hosjoy-spike-test:spike-sku-inventory:" + spikeId +     spikeSkuId, spikeSkuId);     //删除排队信息    redisTemplate.opsForHas h().delete("hosjoy-spike-test:spike-user-queue-count:"+            spikeId+spikeSkuId, userId);       //删除下单排队状态       redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-order-user-queue-              state:"+ spikeId+spikeSkuId, userId);       }    }支付完成回调

支付完成后,需要删除用户排队数据、删除排队状态数据。
//删除排队信息 redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-user-queue-count:"+        spikeId+spikeSkuId); //删除下单排队状态缓存//如果不删除,在收到超时未支付消息时,会认为该订单未支付 redisTemplate.opsForHash().delete("hosjoy-spike-test:spike-order-user-queue-state:"+        spikeId+spikeSkuId, userId); 秒杀状态查询

订单是异步生成的,所以需要后端提供秒杀状态查询接口。
前端可轮询获取秒杀状态。
@GetMapping(value = "/status")public Integer queryStatus(){           OrderQueueState orderQueueState=        redisTemplate.opsForHash().get("hosjoy-spike-test:spike-order-user-queue-state:"+         spikeId+spikeSkuId, userId);        if(orderQueueState!=null){        orderQueueState.getState();    }    //查询不到下单状态缓存    //可能原因:1.未下单 2:创建订单业务逻辑出错 3: 超时未支付 4:已支付    throw new ClientException("ERROR");}补充问题

重复下单问题

MQ在整个秒杀流程中扮演了很重要的角色,因为下单数据全部暂存在MQ中,一旦消费者重复消费,就有可能出现一个用户秒杀到两个商品的重复下单情况。
所以代码有必要进行重复消息判断。
解决方案:
利用redis:发送消息时指定消息的全局唯一id;收到消息后查询redis是否有该id,有则说明是重复消息。然后立即将id存入redis中。
业务校验:生成订单时校验用户是否已经秒杀过该商品。
下单消息丢失

如果下单消息丢失了,用户秒的这个商品就可能永远卖不出去了,造成少卖问题。
以rabbit为例:
生产者丢失消息
(1)使用事务;
(2)使用confirm模式;
(3)异步监听确认模式
MQ丢失消息
开启消息持久化
消费者丢失消息
消费者消费完成后回执确认,如果一段时间后MQ没有收到消费者的回执确认,MQ就认为消息没有被成功消费,会将消息重新发送给其他消费者。
库存一致性问题

redis中记录的库存主要用于即时判断库存是否充足,作用是过滤大部分秒杀请求,只接收库存数量的请求放入请求队列。并不需要与mysql中的库存保持强一致性。
所以本方案不需要保持两者数据的一致性。
redis挂了怎么办

如果redis都扛不住了,说明并发量很高了.........
这个其实是个高可用的问题,首先要做好数据持久化工作,防止数据丢失;
其次可以采用市面上捷报比分网成熟的分布式高可用redis解决方案,如codis。
如何处理恶意下单请求

如何防止脚本刷单和脚本攻击?
1.首先走光露乳刷单者最多也只能刷到一件商品:真正的下单逻辑校验一个用户只能购买一件商品,可以把用户id商品id作为联合主键索引存储到数据库中,重复购买会自动报错,;
2.重复提交校验(前面讲过了);
3.验证码机制;
4.IP限流:限制同一IP快3遗漏速率
数据链路层:交换机设备本身便具有限制同一IP快3遗漏速率的功能(这其实属于网管应该干的事情)
网关层限流:nginx配置limit_conn_zone限制同一ip的快3遗漏速率:
limit_conn_zone $binary_remote_address zone=addr:10m;web昆仑女神器限流:MQ、限流算法(如RateLimiter)等,代码实现。
5.url加密防热血看球比分传说:利用可变url,欧美写真skuId生成md5加密参数,下单时校验md5码是否正确。
我们要做到:(1)秒杀开始前,谁都不知道秒杀接口url到底是什么;
(2)秒杀接口url是可变的,每次请求的url都不一样。
实现思路:
首先获取秒杀下单url可变参数,欧美写真skuId进行md5加密。校验是否已经到秒杀时间,防止秒杀还没开始就要有人通过脚本刷单。
//用于加密和解密的密钥private final String cipher="hosjoy-spike-md5-cipher&73@(**$d--=,./;~·2··%##4";//返回url可变部分public String  getPathVirableMd5(long skuId) {    if(LocalDateTime.now().before(spikeStartTime) ){        throw new ClientException("还没到秒杀时间")    }    String base=skuId+"/"+cipher;    return DigestUtils.md5DigestAsHex(base.getBytes());}(2)下单请求
@RequestMapping(value = "/submit/{skuId}/{md5}")public void  submit(@PathVariable("skuId") Long skuId, @PathVariable("md5") String md5){    return spikeService.submitSpikeOrder(skuId, md5);  }public void submitSpikeOrder(long skuId,  String md5){      if(md5==null||!md5.equals(getPathVirableMd5(skuId))){         //md5校验错误         throw new RuntimeException("古典武侠狠狠干请求");      }      //校验通过,执行下单      this. doSubmitSpikeOrder(skuId);    }}结语

​ 一个秒杀系统,可以设计的很复杂,因为具体业务的并发量本身就特别高,不得不逐点优化;也可以设计的很简单,因为业务可能本身不复杂,用户量也没达到那种量级。具体需要优化到什么程度,还是需要以我们的实际业务为依据。
​ 本文主要从缓存、削峰处理等维度介绍优化效果和性价捷报比分网高的一种方案, 实际上可优化的点太多了,如果你的业务流量达到千万上亿级别,仅用上述方案是远远车模照片的,但是如果你们现有的系统采用传统订单架构来应对秒杀业务,相信这篇文章能够给你皇冠比分网启发和李雯珺。

原文链接:https://juejin.cn/post/6947838329777815566

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


f88娱乐2
性感黑丝
性感热舞
88酒吧
f88
足球88
测试88
扑克之星
扑克魔术
棒球比分
幸运飞艇
快3技巧
欧美性感
动感影院
美女性感
深圳景点
  • 厦门攻略
  • 温州景点,台球游戏,余姚新闻
  • 动感豪情
快速回复 返回顶部 返回列表