秒杀架构设计问题以及思考

秒杀注意事项以及总体简略设计前端

1.如何解决卖超问题

 

--在sql加上判断防止数据边为负数
--数据库加惟一索引防止用户重复购买
--redis预减库存减小数据库访问 内存标记减小redis访问 请求先入队列缓冲,异步下单,加强用户体验redis

注册功能 -- 若是有前端的牛人加入修改几个页面那是再好不过了哈哈哈
全局异常处理拦截
1.定义全局的异常拦截器
2.定义了全局异常类型
3.只返回和业务有关的
4.详情请看GlobleException算法

页面级缓存thymeleafViewResolver

spring

对象级缓存redis

redis永久缓存对象减小压力
redis预减库存减小数据库访
内存标记方法减小redis访问sql

订单处理队列rabbitmq


请求先入队缓冲,异步下单,加强用户体验
请求出队,生成订单,减小库存
客户端定时轮询检查是否秒杀成功 数据库

解决分布式session


--生成随机的uuid做为cookie返回并redis内存写入
--拦截器每次拦截方法,来从新获根据cookie获取对象
--下一个页面拿到key从新获取对象
--HandlerMethodArgumentResolver 方法 supportsParameter 若是为true 执行 resolveArgument 方法获取miaoshauser对象
--若是有缓存的话 这个功能实现起来就和简单,在一个用户访问接口的时候咱们把访问次数写到缓存中,在加上一个有效期。
经过拦截器. 作一个注解 @AccessLimit 而后封装这个注解,能够有效的设置每次访问多少次,有效时间是否须要登陆!编程

秒杀安全 -- 安全性设计


秒杀接口隐藏
数字公式验证码
接口防刷限流(通用 注解,拦截器方式)后端

通用缓存key的封装采用什么设计模式
模板模式的优势
-具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法总体结构
-代码复用的基本技术,在数据库设计中尤其重要
-存在一种反向的控制结构,经过一个父类调用其子类的操做,经过子类对父类进行扩展增长新的行为,符合“开闭原则”
-缺点: 每一个不一样的实现都须要定义一个子类,会致使类的个数增长,系统更加庞大设计模式

redis的库存如何与数据库的库存保持一致


redis的数量不是库存,他的做用仅仅只是为了阻挡多余的请求透穿到DB,起到一个保护的做用
由于秒杀的商品有限,好比10个,让1万个请求区访问DB是没有意义的,由于最多也就只能10个
请求下单成功,全部这个是一个伪命题,咱们是不须要保持一致的缓存

redis 预减成功,DB扣减库存失败怎么办
-其实咱们能够不用太在乎,对用户而言,秒杀不中是正常现象,秒杀中才是意外,单个用户秒杀中
-1.原本就是小几率事件,出现这种状况对于用户而言没有任何影响
-2.对于商户而言,原本就是为了活动拉流量人气的,卖不完还能够省一部分费用,可是活动还参与了,也就没有了任何影响
-3.对网站而言,最重要的是体验,只要网站不崩溃,对用户而言没有任何影响

为何redis数量会减小为负数


//预见库存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId) ;
if(stock <0){
localOverMap.put(goodsId, true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
假如redis的数量为1,这个时候同时过来100个请求,你们一块儿执行decr数量就会减小成-99这个是正常的
进行优化后改变了sql写法和内存写法则不会出现上述问题

为何要单独维护一个秒杀结束标志
-1.前提全部的秒杀相关的接口都要加上活动是否结束的标志,若是结束就直接返回,包括轮寻的接口防止一直轮寻
-2.管理后台也能够手动的更改这个标志,防止出现活动开始之后就没办法结束这种意外的事件

rabbitmq如何作到消息不重复不丢失即便服务器重启


-1.exchange持久化
-2.queue持久化
-3.发送消息设置MessageDeliveryMode.persisent这个也是默认的行为
-4.手动确认

为何threadlocal存储user对象,原理


1.并发编程中重要的问题就是数据共享,当你在一个线程中改变任意属性时,全部的线程都会所以受到影响,同时会看到第一个线程修改后的值<br>
有时咱们但愿如此,好比:多个线程增大或减少同一个计数器变量<br>
可是,有时咱们但愿确保每一个线程,只能工做在它本身的线程实例的拷贝上,同时不会影响其余线程的数据<br>

举例: 举个例子,想象你在开发一个电子商务应用,你须要为每个控制器处理的顾客请求,生成一个惟一的事务ID,同时将其传到管理器或DAO的业务方法中,
以便记录日志。一种方案是将事务ID做为一个参数,传到全部的业务方法中。但这并非一个好的方案,它会使代码变得冗余。
你可使用ThreadLocal类型的变量解决这个问题。首先在控制器或者任意一个预处理器拦截器中生成一个事务ID
而后在ThreadLocal中 设置事务ID,最后,不论这个控制器调用什么方法,都能从threadlocal中获取事务ID
并且这个应用的控制器能够同时处理多个请求,
同时在框架 层面,由于每个请求都是在一个单独的线程中处理的,因此事务ID对于每个线程都是惟一的,并且能够从全部线程的执行路径获取
运行结果能够看出每一个线程都在维护本身的变量:
Starting Thread: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 2 : Fri Sep 21 23:05:34 CST 2018<br>
Starting Thread: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 1 : Fri Jan 02 05:36:17 CST 1970<br>
Thread Finished: 0 : Fri Sep 21 23:05:34 CST 2018<br>
Thread Finished: 2 : Fri Sep 21 23:05:34 CST 2018<br>

局部线程一般使用在这样的状况下,当你有一些对象并不知足线程安全,可是你想避免在使用synchronized关键字<br>
块时产生的同步访问,那么,让每一个线程拥有它本身的对象实例<br>
注意:局部变量是同步或局部线程的一个好的替代,它老是可以保证线程安全。惟一可能限制你这样作的是你的应用设计约束<br>
因此设计threadlocal存储user不会对对象产生影响,每次进来一个请求都会产生自身的线程变量来存储

maven 隔离


maven隔离就是在开发中,把各个环境的隔离开来,通常分为
本地(local)
开发(dev)
测试(test)
线上(prod)
在环境部署中为了防止人工修改的弊端! spring.profiles.active=@activatedProperties@

redis 分布式锁实现方法


我用了四种方法 , 分别指出了不一样版本的缺陷以及演进的过程 orderclosetask
V1---->>版本没有操做,在分布式系统中会形成同一时间,资源浪费并且很容易出现并发问题
V2--->>版本加了分布式redis锁,在访问核心方法前,加入redis锁能够阻塞其余线程访问,能够
很好的处理并发问题,可是缺陷就是若是机器忽然宕机,或者线路波动等,就会形成死锁,一直
不释放等问题
V3版本-->>很好的解决了这个问题v2的问题,就是加入时间对好比果当前时间已经大与释放锁的时间
说明已经能够释放这个锁从新在获取锁,setget方法能够把以前的锁去掉在从新获取,旧值在于以前的
值比较,若是无变化说明这个期间没有人获取或者操做这个redis锁,则能够从新获取
V4---->>采用成熟的框架redisson,封装好的方法则能够直接处理,可是waittime记住要这只为0

服务降级--服务熔断(过载保护))
自动降级: 超时.失败次数,故障,限流
人工降级:秒杀,双11

9.全部秒杀相关的接口好比:秒杀,获取秒杀地址,获取秒杀结果,获取秒杀验证码都须要加上
秒杀是否开始结束的判断

秒杀相似场景sql的写法注意事项


1.在秒杀一类的场景里面,由于数据量亿万级全部即便有的有缓存有的时候也是扛不住的,不可避免的透穿到DB
全部在写一些sql的时候就要注意:
1.必定要避免全表扫描,若是扫一张大表的数据就会形成慢查询,致使数据的链接池直接塞满,致使事故
首先考虑在where和order by 设计的列上创建索引
例如: 1. where 子句中对字段进行 null 值判断 .
2. 应尽可能避免在 where 子句中使用!=或<>操做符
3. 应尽可能避免在 where 子句中使用 or 来链接条件
4. in 和 not in 也要慎用,不然会致使全表扫描
5. select id from t where name like '%abc%' 或者
6.select id from t where name like '%abc' 或者
7. 若要提升效率,能够考虑全文检索。
8.而select id from t where name like 'abc%' 才用到索引 慢查询通常在测试环境不容易复现
9.应尽可能避免在 where 子句中对字段进行表达式操做 where num/2 num=100*2
2.合理的使用索引 索引并非越多越好,使用不当会形成性能开销
3.尽可能避免大事务操做,提升系统并发能力
4.尽可能避免象客户端返回大量数据,若是返回则要考虑是否需求合理,实在不得已则须要在设计一波了!!!!!

前端限流:

首先前端可使用一个验证码输入模式,让用户输入验证码,经过获取验证码和输入验证码得时候来减轻数据库压力,而后再进行一个防重复点击,在点击一次以后须要五秒或者十秒以后再让用户进行交易

固然这种方式对于黑客是没法行得通得.

 

后端限流:

对用户进行访问路径次数使用redis进行记录,或者使用内存threadlocal进行记录用户访问次数,若是用户超过三次就不让用户购买,这样就能够防止用户进行攻击.

至关于前端进行了两次限流同样,对用户进行了限制交易

 

redis分布式锁问题:

可使用redisson进行使用redis分布式锁,这样就能够很好避免原生得分布式锁得一些弊端,同时也能够避免多线程得安全问题就是库存变成负数得问题.

当库存为0,那么就直接返回数据给前端,当分布式线程锁住了,能够提醒用户,当前人数过多,

 

rabbitmq得可靠性投递;

能够先将消息,以及oreder实体落入数据库,当消息消费以后能够进行下一步进行手动签收,这样就能够,再开启一个task任务扫描数据库表中数据,当是下单成功以后,就表明该消息成功消费,若是没有消费成功,再进行重试.

相关文章
相关标签/搜索