miaosha/README.md

152 lines
9.3 KiB
Markdown
Raw Normal View History

2018-12-04 12:45:29 +08:00
### 前言
大家好,之前在公司自己设计并开发了一套完整的秒杀系统自己构建了一个小的demo希望和大家分享一下希望大家能从中收益如果有意见和好的想法请加我
:3341386488
邮箱QiuRunZe_key@163.com
我会不断完善,希望大家有好的想法拉一个分支提高,一起合作!
觉得不错对您有帮助麻烦右上角点下star以示鼓励长期维护不易 多次想放弃 坚持是一种信仰 专注是一种态度!
## 秒杀设计整体流程
![整体流程](http://i2.bvimg.com/601558/886c867d6488dfc2.png)
#### 需注意几点:
### <font color=#0099ff size=3 >1.如何解决卖超问题<br></font><br>
① sql加上判断如果防止数据变为负数<br>
② 数据库加唯一索引防止用户重复购买<br>
③ redis预减库存减少数据库访问 内存标记减少redis访问 请求先入队列缓冲异步下单增强用户体验
### <font color=#0099ff size=3 >如何解决分布式session<br></font><br>
① 生成一个随机的uuid一类的写回到cookie中<br>
② redis 内存写入<br>
③ 下一个页面拿到uuid 内存取对象
### <font color=#0099ff size=3 >3.如何优雅解决接口防刷限流<br></font><br>
如果有缓存的话 这个功能实现起来就和简单,在一个用户访问接口的时候我们把访问次数写到缓存中,在加上一个有效期。
通过拦截器. 做一个注解 @AccessLimit 然后封装这个注解,可以有效的设置每次访问多少次,有效时间是否需要登录!
### <font color=#0099ff size=3 >4.通用缓存key的封装采用什么设计模式<br></font><br>
模板模式的优点<br>
①具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。<br>
②代码复用的基本技术,在数据库设计中尤为重要。<br>
③存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。<br>
缺点: 每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大
### <font color=#0099ff size=3 >5.LVS , tomcat(apr) , keepalive 高可用 --待更新</font><br>
### <font color=#0099ff size=3 >6.限流算法 令牌桶,漏桶算法--待更新</font><br>
### <font color=#0099ff size=3 >7.Nginx优化前端缓存</font><br>
①并发优化<br>
②keepAlive长链接nginx,tomcat默认没有配置长链接<br>
③压缩优化.配置缓存<br>
监控工具:1.nginx_status并发统计,Ngxtop缓存统计
### nginx负载均衡
Merge branch 'master' of https://github.com/qiurunze123/miaosha into github-master # Conflicts: # .gitignore # README.md # pom.xml # src/main/java/com/geekq/miaosha/GeekQMainApplication.java # src/main/java/com/geekq/miaosha/access/AccessInterceptor.java # src/main/java/com/geekq/miaosha/access/AccessKey.java # src/main/java/com/geekq/miaosha/access/AccessLimit.java # src/main/java/com/geekq/miaosha/access/UserContext.java # src/main/java/com/geekq/miaosha/config/UserArgumentResolver.java # src/main/java/com/geekq/miaosha/config/WebConfig.java # src/main/java/com/geekq/miaosha/controller/DemoController.java # src/main/java/com/geekq/miaosha/controller/MiaoshaController.java # src/main/java/com/geekq/miaosha/controller/OrderController.java # src/main/java/com/geekq/miaosha/dao/DruidConfig.java # src/main/java/com/geekq/miaosha/dao/GoodsDao.java # src/main/java/com/geekq/miaosha/dao/OrderDao.java # src/main/java/com/geekq/miaosha/dao/UserDao.java # src/main/java/com/geekq/miaosha/domain/Goods.java # src/main/java/com/geekq/miaosha/domain/MiaoshaGoods.java # src/main/java/com/geekq/miaosha/domain/MiaoshaOrder.java # src/main/java/com/geekq/miaosha/domain/MiaoshaUser.java # src/main/java/com/geekq/miaosha/domain/OrderInfo.java # src/main/java/com/geekq/miaosha/domain/User.java # src/main/java/com/geekq/miaosha/mybatis/README.md # src/main/java/com/geekq/miaosha/rabbitmq/MQConfig.java # src/main/java/com/geekq/miaosha/rabbitmq/MQReceiver.java # src/main/java/com/geekq/miaosha/rabbitmq/MQSender.java # src/main/java/com/geekq/miaosha/rabbitmq/MiaoshaMessage.java # src/main/java/com/geekq/miaosha/redis/GoodsKey.java # src/main/java/com/geekq/miaosha/redis/MiaoshaKey.java # src/main/java/com/geekq/miaosha/redis/RedisPoolFactory.java # src/main/java/com/geekq/miaosha/redis/RedisService.java # src/main/java/com/geekq/miaosha/redis/RedissonAutoConfiguration.java # src/main/java/com/geekq/miaosha/redis/RedissonDistributedLocker.java # src/main/java/com/geekq/miaosha/redis/RedissonProperties.java # src/main/java/com/geekq/miaosha/redis/RedissonService.java # src/main/java/com/geekq/miaosha/result/CodeMsg.java # src/main/java/com/geekq/miaosha/service/GoodsService.java # src/main/java/com/geekq/miaosha/service/MiaoShaUserService.java # src/main/java/com/geekq/miaosha/service/MiaoshaService.java # src/main/java/com/geekq/miaosha/service/OrderService.java # src/main/java/com/geekq/miaosha/service/UserService.java # src/main/java/com/geekq/miaosha/timeTask/OrderCloseTask.java # src/main/java/com/geekq/miaosha/utils/ValidatorUtil.java # src/main/java/com/geekq/miaosha/vo/GoodsDetailVo.java # src/main/java/com/geekq/miaosha/vo/GoodsVo.java # src/main/java/com/geekq/miaosha/vo/OrderDetailVo.java # src/main/resources/application-dev.properties # src/main/resources/application-prod.properties # src/main/resources/application-test.properties # src/main/resources/application.properties # src/main/resources/config/application-test.properties # src/main/resources/dubbo/springmvc.xml # src/main/resources/static/goods_detail.htm # src/main/resources/static/js/common.js # src/main/resources/static/js/jquery.min.js # src/main/resources/static/order_detail.htm # src/main/resources/templates/goods_detail.html # src/main/resources/templates/goods_list.html # src/main/resources/templates/hello.html # src/main/resources/templates/login.html # src/main/resources/templates/miaosha_fail.html # src/main/resources/templates/order_detail.html
2018-12-05 13:52:55 +08:00
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha2.png)
2018-12-04 12:45:29 +08:00
### <font color=#0099ff size=3 >8.服务降级--服务熔断(过载保护)</font><br>
自动降级: 超时.失败次数,故障,限流<br>
人工降级秒杀双11<br>
9.所有秒杀相关的接口比如:秒杀,获取秒杀地址,获取秒杀结果,获取秒杀验证码都需要加上<br>
秒杀是否开始结束的判断
### <font color=#0099ff size=3 >10.redis的库存如何与数据库的库存保持一致?</font><br>
redis的数量不是库存,他的作用仅仅只是为了阻挡多余的请求透穿到DB起到一个保护的作用<br>因为秒杀的商品有限比如10个让1万个请求区访问DB是没有意义的因为最多也就只能10<br>
个请求下单成功,所有这个是一个伪命题,我们是不需要保持一致的。<br>
### <font color=#0099ff size=3 >11.redis 预减成功DB扣减库存失败怎么办</font><br>
其实我们可以不用太在意,对用户而言,秒杀不中是正常现象,秒杀中才是意外,单个用户秒杀中<br>
本来就是小概率事件,出现这种情况对于用户而言没有任何影响<br>
2.对于商户而言,本来就是为了活动拉流量人气的,卖不完还可以省一部分费用,但是活动还参与了,也就没有了任何影响<br>
3.对网站而言,最重要的是体验,只要网站不崩溃,对用户而言没有任何影响<br>
### <font color=#0099ff size=3 >12.为什么redis数量会减少为负数</font><br>
//预见库存
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这个是正常的
### <font color=#0099ff size=3 >13.为什么要单独维护一个秒杀结束标志?</font><br>
1.前提所有的秒杀相关的接口都要加上活动是否结束的标志,如果结束就直接返回,包括轮寻的接口防止一直轮寻<br>
2.管理后台也可以手动的更改这个标志,防止出现活动开始以后就没办法结束这种意外的事件
### <font color=#0099ff size=3 >14.redis挂掉了怎么办</font><br>
1.具体我会有时间更新关于redis的知识
### <font color=#0099ff size=3 >15.rabbitmq如何做到消息不重复不丢失即使服务器重启</font><br>
1.exchange持久化2.queue持久化3.发送消息设置MessageDeliveryMode.persisent这个也是默认的行为4.手动确认
### <font color=#0099ff size=3 >15.为什么threadlocal存储user对象原理</font><br>
Merge branch 'master' of https://github.com/qiurunze123/miaosha into github-master # Conflicts: # .gitignore # README.md # pom.xml # src/main/java/com/geekq/miaosha/GeekQMainApplication.java # src/main/java/com/geekq/miaosha/access/AccessInterceptor.java # src/main/java/com/geekq/miaosha/access/AccessKey.java # src/main/java/com/geekq/miaosha/access/AccessLimit.java # src/main/java/com/geekq/miaosha/access/UserContext.java # src/main/java/com/geekq/miaosha/config/UserArgumentResolver.java # src/main/java/com/geekq/miaosha/config/WebConfig.java # src/main/java/com/geekq/miaosha/controller/DemoController.java # src/main/java/com/geekq/miaosha/controller/MiaoshaController.java # src/main/java/com/geekq/miaosha/controller/OrderController.java # src/main/java/com/geekq/miaosha/dao/DruidConfig.java # src/main/java/com/geekq/miaosha/dao/GoodsDao.java # src/main/java/com/geekq/miaosha/dao/OrderDao.java # src/main/java/com/geekq/miaosha/dao/UserDao.java # src/main/java/com/geekq/miaosha/domain/Goods.java # src/main/java/com/geekq/miaosha/domain/MiaoshaGoods.java # src/main/java/com/geekq/miaosha/domain/MiaoshaOrder.java # src/main/java/com/geekq/miaosha/domain/MiaoshaUser.java # src/main/java/com/geekq/miaosha/domain/OrderInfo.java # src/main/java/com/geekq/miaosha/domain/User.java # src/main/java/com/geekq/miaosha/mybatis/README.md # src/main/java/com/geekq/miaosha/rabbitmq/MQConfig.java # src/main/java/com/geekq/miaosha/rabbitmq/MQReceiver.java # src/main/java/com/geekq/miaosha/rabbitmq/MQSender.java # src/main/java/com/geekq/miaosha/rabbitmq/MiaoshaMessage.java # src/main/java/com/geekq/miaosha/redis/GoodsKey.java # src/main/java/com/geekq/miaosha/redis/MiaoshaKey.java # src/main/java/com/geekq/miaosha/redis/RedisPoolFactory.java # src/main/java/com/geekq/miaosha/redis/RedisService.java # src/main/java/com/geekq/miaosha/redis/RedissonAutoConfiguration.java # src/main/java/com/geekq/miaosha/redis/RedissonDistributedLocker.java # src/main/java/com/geekq/miaosha/redis/RedissonProperties.java # src/main/java/com/geekq/miaosha/redis/RedissonService.java # src/main/java/com/geekq/miaosha/result/CodeMsg.java # src/main/java/com/geekq/miaosha/service/GoodsService.java # src/main/java/com/geekq/miaosha/service/MiaoShaUserService.java # src/main/java/com/geekq/miaosha/service/MiaoshaService.java # src/main/java/com/geekq/miaosha/service/OrderService.java # src/main/java/com/geekq/miaosha/service/UserService.java # src/main/java/com/geekq/miaosha/timeTask/OrderCloseTask.java # src/main/java/com/geekq/miaosha/utils/ValidatorUtil.java # src/main/java/com/geekq/miaosha/vo/GoodsDetailVo.java # src/main/java/com/geekq/miaosha/vo/GoodsVo.java # src/main/java/com/geekq/miaosha/vo/OrderDetailVo.java # src/main/resources/application-dev.properties # src/main/resources/application-prod.properties # src/main/resources/application-test.properties # src/main/resources/application.properties # src/main/resources/config/application-test.properties # src/main/resources/dubbo/springmvc.xml # src/main/resources/static/goods_detail.htm # src/main/resources/static/js/common.js # src/main/resources/static/js/jquery.min.js # src/main/resources/static/order_detail.htm # src/main/resources/templates/goods_detail.html # src/main/resources/templates/goods_list.html # src/main/resources/templates/hello.html # src/main/resources/templates/login.html # src/main/resources/templates/miaosha_fail.html # src/main/resources/templates/order_detail.html
2018-12-05 13:52:55 +08:00
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha1.png)
2018-12-04 12:45:29 +08:00
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不会对对象产生影响每次进来一个请求都会产生自身的线程变量来存储
### <font color=#0099ff size=3 >15.mybatis如何使用注解与xml配置</font><br>
本文使用的是注解方法开发所以不做过多解释<br>
下面仔细讲解以下如何详细使用xml开发在目录里面有一个与本文无挂的类似于微信自动回复的功能br>
里面有mybatis的全部解析和用法,大家可以简单的当做一个demo来使用<br>
### maven 隔离
maven隔离就是在开发中把各个环境的隔离开来一般分为
本地local
开发(dev)
测试(test)
线上(prod)
在环境部署中为了防止人工修改的弊端! spring.profiles.active=@activatedProperties@
### redis 分布式锁实现方法
我用了四种方法 分别指出了不同版本的缺陷以及演进的过程 orderclosetask
v1---->>版本没有操作,在分布式系统中会造成同一时间,资源浪费而且很容易出现并发问题
V2--->>版本加了分布式redis锁在访问核心方法前加入redis锁可以阻塞其他线程访问,可以
很好的处理并发问题,但是缺陷就是如果机器突然宕机,或者线路波动等,就会造成死锁,一直
不释放等问题
V3版本-->>很好的解决了这个问题v2的问题就是加入时间对比如果当前时间已经大与释放锁的时间
说明已经可以释放这个锁重新在获取锁setget方法可以把之前的锁去掉在重新获取,旧值在于之前的
值比较如果无变化说明这个期间没有人获取或者操作这个redis锁则可以重新获取
V4---->>采用成熟的框架redisson,封装好的方法则可以直接处理但是waittime记住要这只为0