
⾕粒商城:秒杀系统设计与编写
1.秒杀系统设计
秒杀(⾼并发)系统关注的问题
1、服务单独职责+独⽴部署
秒杀系统为单独的服务,即使⾃⼰扛不住压⼒挂掉,也不要影响其他服务
2、秒杀链接加密
防⽌恶意攻击,模拟秒杀请求,1000次/s的攻击;防⽌链接暴露,⾃⼰⼯作⼈员,提前秒杀商品;我们使⽤了带uuid随机码的机制;
3、库存预热+快速扣减
秒杀读多写少,⽆需每次实时校验库存,我们库存预热,放到redis中,信号量控制进来秒杀的请求;为了保证redis可以保证千万并发,可以
给redis做集群,做成分⽚⾼可⽤;我们是⽤定时任务提取三天写到缓存中;
4、动静分离
Nginx做好动静分离,保证秒杀和商品详情页的动态请求才打到后端的服务集群,使⽤CDN⽹络,分担本集群的压⼒;⽐如访问静态资源,
阿⾥云CDN会在最快的节点返回静态资源;
5、恶意请求拦截
识别⾮法攻击请求并进⾏拦截(⽹关层),⽐如伪造的请求没带令牌;保证能放到后端的请求都是正常⾏为;
6、流量错峰
使⽤各种⼿段,将流量分担到更⼤宽度的时间点。⽐如验证码(⼩⽶商城)、加⼊购物车(结账,锁库存还有⼀段时间);
7、限流&熔断&降级(必须)
前端限流+后端限流(限流把不合理的去除掉,如:⼀秒发送1w次的请求;就算合理的,次数太多也应该限制起来);限制次数,限制总
量,快速失败降级运⾏(⼀部分流量引导到降级页⾯),熔断隔离防⽌雪崩;
8、队列削峰
1万个商品,每个1000件秒杀,双11;所有秒杀成功的请求,进⼊队列,慢慢创建订单,扣减库存即可;
2.秒杀核⼼流程
秒杀流程+消息队列监听流程
3.秒杀系统编写
秒杀请求
@Autowired
privateSeckillServiceckillService;
@GetMapping("/kill")
publicStringcKill(@RequestParam("killId")StringkillId,//ssion_skuID
@RequestParam("key")Stringkey,
@RequestParam("num")Integernum,Modelmodel){
StringorderSn=(killId,key,num);
//1.判断是否登录
ribute("orderSn",orderSn);
return"success";
}
登录验证:编写拦截器
@Component
publicclassLoginUrInterceptorimplementsHandlerInterceptor{
publicstaticThreadLocal
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponrespon,Objecthandler)throwsException{
Stringuri=uestURI();
//这个请求直接放⾏
booleanmatch=newAntPathMatcher().match("/kill",uri);
//如果是秒杀请求,才做这⼀系列的登录验证
if(!match){
HttpSessionssion=sion();
MemberRespVoMemberRespVo=(MemberRespVo)ribute(_USER);
if(MemberRespVo!=null){
(MemberRespVo);
returntrue;
}el{
//没登陆就去登录
ribute("msg",_LOGIN);
direct("/");
returnfal;
}
}
returntrue;
}
}
将登录验证拦截器添加到webmvc的配置中
@Configuration
publicclassSeckillWebConfigimplementsWebMvcConfigurer{
@Autowired
privateLoginUrInterceptorloginUrInterceptor;
@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
erceptor(loginUrInterceptor).addPathPatterns("/**");
}
}
流量削峰
配置rabbitmq
ipAddr:"192.168.56.10"
spring:
rabbitmq:
virtual-host:/
host:${ipAddr}
引⼊rabbitmq配置类,使⽤JSON序列化器,进⾏消息转换
@Configuration
publicclassMyRabbitConfig{
@Bean
publicMessageConvertermessageConverter(){
returnnewJackson2JsonMessageConverter();
}
}
设置交换机和队列的名字常量
package;
publicclassRabbitInfo{
publicstaticclassOrder{
//其实⼚⾥应该⼤写,但是我们为了区分,这⾥也不改了
publicstaticfinalStringexchange="order-event-exchange";
publicstaticfinalStringdelayQueue="";
publicstaticfinalStringdelayRoutingKey="";
publicstaticfinalStringreleaQueue="";
publicstaticfinalStringreleaRoutingKey="e";
//其他路由key也是跳到releaQueue
publicstaticfinalStringbaRoutingKey="order.#";
publicstaticfinalintttl=900000;
}
publicstaticclassStock{
publicstaticfinalStringexchange="stock-event-exchange";
publicstaticfinalStringdelayQueue="";
publicstaticfinalStringdelayRoutingKey="";
publicstaticfinalStringreleaQueue="";
publicstaticfinalStringreleaRoutingKey="";
publicstaticfinalStringbaRoutingKey="stock.#";
publicstaticfinalintttl=900000;
}
publicstaticclassSecKill{
publicstaticfinalStringexchange="ckill-event-exchange";
publicstaticfinalStringdelayQueue="";
publicstaticfinalStringdelayRoutingKey="";
publicstaticfinalStringreleaQueue="";
publicstaticfinalStringreleaRoutingKey="";
publicstaticfinalintttl=900000;
}
}
给交换机发请求
//+skuId在redis中标识买过商品
StringredisKey=()+"-"+skuId;
//让数据⾃动过期
longttl=Time()-rtTime();
//SETNX,也就是不存在的时候才占位,如果能占位成功,说明这个⼈没买过
BooleanaBoolean=Value()
.tIfAbnt(redisKey,
ng(),
ttl<0?0:ttl,
ECONDS);
if(aBoolean){
//占位成功说明从来没买过
RSemaphoremaphore=aphore(SKUSTOCK_SEMAPHONE+randomCode);
//使⽤tryAcquire()⽅法,因为acquire()是阻塞的
booleanacquire=uire(num);
if(acquire){
//秒杀成功
//快速下单发送MQ
/**⽣成订单号,这样数据库通过消息队列保存后,订单⽀付页⾯也知道保存的id是多少*/
StringorderSn=eId()+UUID().toString().replace("-","").substring(7,8);
SecKillOrderToorderTo=newSecKillOrderTo();
erSn(orderSn);
berId(());
(num);
Id(Id());
killPrice(killPrice());
motionSessionId(motionSessionId());
tAndSend(ge,
outingKey,orderTo);
//返回订单号
returnorderSn;
}
监听秒杀单的队列
package;
@RabbitListener(queues=ueue)
@Component
publicclassOrderSecKillListener{
@Autowired
privateOrderServiceorderService;
@RabbitHandler
publicvoidlistener(SecKillOrderTocKillOrderTo,Channelchannel,Messagemessage)throwsIOException{
try{
//秒杀的时候没有订单,这时候才创建订单
SecKillOrder(cKillOrderTo);
//⼿动ack确认消费
ck(sageProperties().getDeliveryTag(),fal);
}catch(Exceptione){
eject(sageProperties().getDeliveryTag(),true);
}
}
}
本⽂完
本文发布于:2023-03-13 14:24:01,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/zhishi/a/16786886419418.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:谷粒网.doc
本文 PDF 下载地址:谷粒网.pdf
| 留言与评论(共有 0 条评论) |