mirror of
https://github.com/qiurunze123/miaosha.git
synced 2023-11-19 22:41:03 +08:00
提交README
This commit is contained in:
parent
9c20da3197
commit
2a6c22d510
205
README.md
205
README.md
|
@ -1,115 +1,78 @@
|
||||||
### 前言
|
# 互联网 Java 秒杀系统设计与架构
|
||||||
大家好,之前在公司自己设计并开发了一套完整的秒杀系统,自己构建了一个小的demo,希望和大家分享一下,希望大家能从中收益,如果有意见和好的想法请加我!
|
|
||||||
QQ:3341386488
|
|
||||||
邮箱:QiuRunZe_key@163.com
|
|
||||||
|
|
||||||
我会不断完善,希望大家有好的想法拉一个分支提高,一起合作!
|
> 邮箱 : [QiuRunZe_key@163.com](QiuRunZe_key@163.com)
|
||||||
|
|
||||||
|
> Github : [https://github.com/qiurunze123](https://github.com/qiurunze123)
|
||||||
|
|
||||||
|
> QQ : [3341386488](3341386488)
|
||||||
|
|
||||||
|
[![GQ Welcome](https://raw.githubusercontent.com/qiurunze123/imageall/master/2018.png)](https://github.com/qiurunze123)<br>
|
||||||
|
高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。本GitHub还有许多其他的知识,随时欢迎探讨与骚扰!
|
||||||
|
|
||||||
|
一点小建议:学习本系列知识之前,如果你完全没接触过 `MQ`、`SpringBoot`、`Redis`、`Dubbo`、`ZK` 等,那么我建议你可以先在网上搜一下每一块知识的快速入门,也可以下载本项目边做边学习,然后再开始每一块知识的学习。这样效果更好噢~
|
||||||
|
|
||||||
|
|
||||||
觉得不错对您有帮助,麻烦右上角点下star以示鼓励!长期维护不易 多次想放弃 坚持是一种信仰 专注是一种态度!
|
### 秒杀高并发架构 -- 架构图
|
||||||
|
|
||||||
|
|
||||||
## 秒杀设计整体流程
|
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha.png)
|
||||||
|
|
||||||
![整体流程](http://i2.bvimg.com/601558/886c867d6488dfc2.png)
|
### [秒杀注意事项]()
|
||||||
|
#### [1.如何解决卖超问题]()
|
||||||
|
--在sql加上判断防止数据边为负数
|
||||||
|
--数据库加唯一索引防止用户重复购买
|
||||||
#### 需注意几点:
|
--redis预减库存减少数据库访问 内存标记减少redis访问 请求先入队列缓冲,异步下单,增强用户体验
|
||||||
### <font color=#0099ff size=3 >1.如何解决卖超问题<br></font><br>
|
#### [解决分布式session]()
|
||||||
① sql加上判断如果防止数据变为负数<br>
|
--生成一个随机的uuid一类的写回到cookie中
|
||||||
② 数据库加唯一索引防止用户重复购买<br>
|
--redis 内存写入
|
||||||
③ redis预减库存减少数据库访问 内存标记减少redis访问 请求先入队列缓冲,异步下单,增强用户体验
|
--下一个页面拿到uuid 内存取对象
|
||||||
|
--如果有缓存的话 这个功能实现起来就和简单,在一个用户访问接口的时候我们把访问次数写到缓存中,在加上一个有效期。
|
||||||
### <font color=#0099ff size=3 >如何解决分布式session<br></font><br>
|
通过拦截器. 做一个注解 @AccessLimit 然后封装这个注解,可以有效的设置每次访问多少次,有效时间是否需要登录!
|
||||||
|
#### [通用缓存key的封装采用什么设计模式]()
|
||||||
① 生成一个随机的uuid一类的写回到cookie中<br>
|
模板模式的优点
|
||||||
② redis 内存写入<br>
|
-具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构
|
||||||
③ 下一个页面拿到uuid 内存取对象
|
-代码复用的基本技术,在数据库设计中尤为重要
|
||||||
### <font color=#0099ff size=3 >3.如何优雅解决接口防刷限流<br></font><br>
|
-存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”
|
||||||
|
-缺点: 每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大
|
||||||
如果有缓存的话 这个功能实现起来就和简单,在一个用户访问接口的时候我们把访问次数写到缓存中,在加上一个有效期。
|
#### [redis的库存如何与数据库的库存保持一致]()
|
||||||
通过拦截器. 做一个注解 @AccessLimit 然后封装这个注解,可以有效的设置每次访问多少次,有效时间是否需要登录!
|
redis的数量不是库存,他的作用仅仅只是为了阻挡多余的请求透穿到DB,起到一个保护的作用
|
||||||
|
因为秒杀的商品有限,比如10个,让1万个请求区访问DB是没有意义的,因为最多也就只能10个
|
||||||
|
请求下单成功,所有这个是一个伪命题,我们是不需要保持一致的
|
||||||
### <font color=#0099ff size=3 >4.通用缓存key的封装采用什么设计模式?<br></font><br>
|
#### [redis 预减成功,DB扣减库存失败怎么办]()
|
||||||
|
|
||||||
模板模式的优点<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负载均衡
|
|
||||||
|
|
||||||
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha2.png)
|
|
||||||
|
|
||||||
|
|
||||||
### <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>
|
|
||||||
|
|
||||||
|
-其实我们可以不用太在意,对用户而言,秒杀不中是正常现象,秒杀中才是意外,单个用户秒杀中
|
||||||
|
-1.本来就是小概率事件,出现这种情况对于用户而言没有任何影响
|
||||||
|
-2.对于商户而言,本来就是为了活动拉流量人气的,卖不完还可以省一部分费用,但是活动还参与了,也就没有了任何影响
|
||||||
|
-3.对网站而言,最重要的是体验,只要网站不崩溃,对用户而言没有任何影响
|
||||||
|
#### [为什么redis数量会减少为负数]()
|
||||||
//预见库存
|
//预见库存
|
||||||
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId) ;
|
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock,""+goodsId) ;
|
||||||
if(stock <0){
|
if(stock <0){
|
||||||
localOverMap.put(goodsId, true);
|
localOverMap.put(goodsId, true);
|
||||||
return Result.error(CodeMsg.MIAO_SHA_OVER);
|
return Result.error(CodeMsg.MIAO_SHA_OVER);
|
||||||
|
}
|
||||||
|
假如redis的数量为1,这个时候同时过来100个请求,大家一起执行decr数量就会减少成-99这个是正常的
|
||||||
|
#### [为什么要单独维护一个秒杀结束标志]()
|
||||||
|
-1.前提所有的秒杀相关的接口都要加上活动是否结束的标志,如果结束就直接返回,包括轮寻的接口防止一直轮寻
|
||||||
|
-2.管理后台也可以手动的更改这个标志,防止出现活动开始以后就没办法结束这种意外的事件
|
||||||
|
|
||||||
|
#### [rabbitmq如何做到消息不重复不丢失即使服务器重启]()
|
||||||
|
-1.exchange持久化
|
||||||
|
-2.queue持久化
|
||||||
|
-3.发送消息设置MessageDeliveryMode.persisent这个也是默认的行为
|
||||||
|
-4.手动确认
|
||||||
|
#### [为什么threadlocal存储user对象,原理]()
|
||||||
|
1.并发编程中重要的问题就是数据共享,当你在一个线程中改变任意属性时,所有的线程都会因此受到影响,同时会看到第一个线程修改后的值<br>
|
||||||
|
有时我们希望如此,比如:多个线程增大或减小同一个计数器变量<br>
|
||||||
|
但是,有时我们希望确保每个线程,只能工作在它自己的线程实例的拷贝上,同时不会影响其他线程的数据<br>
|
||||||
|
|
||||||
假如redis的数量为1,这个时候同时过来100个请求,大家一起执行decr数量就会减少成-99这个是正常的
|
举例: 举个例子,想象你在开发一个电子商务应用,你需要为每一个控制器处理的顾客请求,生成一个唯一的事务ID,同时将其传到管理器或DAO的业务方法中,
|
||||||
|
以便记录日志。一种方案是将事务ID作为一个参数,传到所有的业务方法中。但这并不是一个好的方案,它会使代码变得冗余。
|
||||||
### <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>
|
|
||||||
|
|
||||||
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha1.png)
|
|
||||||
|
|
||||||
1.并发编程中重要的问题就是数据共享,当你在一个线程中改变任意属性时,所有的线程都会因此受到影响,同时会看到第一个线程修改后的值<br>
|
|
||||||
有时我们希望如此,比如:多个线程增大或减小同一个计数器变量<br>
|
|
||||||
但是,有时我们希望确保每个线程,只能工作在它自己的线程实例的拷贝上,同时不会影响其他线程的数据<br>
|
|
||||||
|
|
||||||
举例: 举个例子,想象你在开发一个电子商务应用,你需要为每一个控制器处理的顾客请求,生成一个唯一的事务ID,同时将其传到管理器或DAO的业务方法中,以便记录日志。一种方案是将事务ID作为一个参数,传到所有的业务方法中。但这并不是一个好的方案,它会使代码变得冗余。
|
|
||||||
|
|
||||||
你可以使用ThreadLocal类型的变量解决这个问题。首先在控制器或者任意一个预处理器拦截器中生成一个事务ID
|
你可以使用ThreadLocal类型的变量解决这个问题。首先在控制器或者任意一个预处理器拦截器中生成一个事务ID
|
||||||
然后在ThreadLocal中 设置事务ID,最后,不论这个控制器调用什么方法,都能从threadlocal中获取事务ID
|
然后在ThreadLocal中 设置事务ID,最后,不论这个控制器调用什么方法,都能从threadlocal中获取事务ID
|
||||||
而且这个应用的控制器可以同时处理多个请求,
|
而且这个应用的控制器可以同时处理多个请求,
|
||||||
同时在框架 层面,因为每一个请求都是在一个单独的线程中处理的,所以事务ID对于每一个线程都是唯一的,而且可以从所有线程的执行路径获取
|
同时在框架 层面,因为每一个请求都是在一个单独的线程中处理的,所以事务ID对于每一个线程都是唯一的,而且可以从所有线程的执行路径获取
|
||||||
运行结果可以看出每个线程都在维护自己的变量:
|
运行结果可以看出每个线程都在维护自己的变量:
|
||||||
Starting Thread: 0 : Fri Sep 21 23:05:34 CST 2018<br>
|
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: 2 : Fri Sep 21 23:05:34 CST 2018<br>
|
||||||
Starting Thread: 1 : Fri Jan 02 05:36:17 CST 1970<br>
|
Starting Thread: 1 : Fri Jan 02 05:36:17 CST 1970<br>
|
||||||
|
@ -120,28 +83,17 @@ redis的数量不是库存,他的作用仅仅只是为了阻挡多余的请求
|
||||||
局部线程通常使用在这样的情况下,当你有一些对象并不满足线程安全,但是你想避免在使用synchronized关键字<br>
|
局部线程通常使用在这样的情况下,当你有一些对象并不满足线程安全,但是你想避免在使用synchronized关键字<br>
|
||||||
块时产生的同步访问,那么,让每个线程拥有它自己的对象实例<br>
|
块时产生的同步访问,那么,让每个线程拥有它自己的对象实例<br>
|
||||||
注意:局部变量是同步或局部线程的一个好的替代,它总是能够保证线程安全。唯一可能限制你这样做的是你的应用设计约束<br>
|
注意:局部变量是同步或局部线程的一个好的替代,它总是能够保证线程安全。唯一可能限制你这样做的是你的应用设计约束<br>
|
||||||
|
|
||||||
所以设计threadlocal存储user不会对对象产生影响,每次进来一个请求都会产生自身的线程变量来存储
|
所以设计threadlocal存储user不会对对象产生影响,每次进来一个请求都会产生自身的线程变量来存储
|
||||||
|
#### [maven 隔离]()
|
||||||
### <font color=#0099ff size=3 >15.mybatis如何使用注解与xml配置?</font><br>
|
|
||||||
本文使用的是注解方法开发所以不做过多解释<br>
|
|
||||||
下面仔细讲解以下如何详细使用xml开发在目录里面有一个与本文无挂的类似于微信自动回复的功能br>
|
|
||||||
里面有mybatis的全部解析和用法,大家可以简单的当做一个demo来使用<br>
|
|
||||||
|
|
||||||
### maven 隔离
|
|
||||||
|
|
||||||
maven隔离就是在开发中,把各个环境的隔离开来,一般分为
|
maven隔离就是在开发中,把各个环境的隔离开来,一般分为
|
||||||
本地(local)
|
本地(local)
|
||||||
开发(dev)
|
开发(dev)
|
||||||
测试(test)
|
测试(test)
|
||||||
线上(prod)
|
线上(prod)
|
||||||
在环境部署中为了防止人工修改的弊端! spring.profiles.active=@activatedProperties@
|
在环境部署中为了防止人工修改的弊端! spring.profiles.active=@activatedProperties@
|
||||||
|
#### [redis 分布式锁实现方法]()
|
||||||
### redis 分布式锁实现方法
|
|
||||||
|
|
||||||
我用了四种方法 , 分别指出了不同版本的缺陷以及演进的过程 orderclosetask
|
我用了四种方法 , 分别指出了不同版本的缺陷以及演进的过程 orderclosetask
|
||||||
|
V1---->>版本没有操作,在分布式系统中会造成同一时间,资源浪费而且很容易出现并发问题
|
||||||
v1---->>版本没有操作,在分布式系统中会造成同一时间,资源浪费而且很容易出现并发问题
|
|
||||||
V2--->>版本加了分布式redis锁,在访问核心方法前,加入redis锁可以阻塞其他线程访问,可以
|
V2--->>版本加了分布式redis锁,在访问核心方法前,加入redis锁可以阻塞其他线程访问,可以
|
||||||
很好的处理并发问题,但是缺陷就是如果机器突然宕机,或者线路波动等,就会造成死锁,一直
|
很好的处理并发问题,但是缺陷就是如果机器突然宕机,或者线路波动等,就会造成死锁,一直
|
||||||
不释放等问题
|
不释放等问题
|
||||||
|
@ -149,3 +101,40 @@ redis的数量不是库存,他的作用仅仅只是为了阻挡多余的请求
|
||||||
说明已经可以释放这个锁重新在获取锁,setget方法可以把之前的锁去掉在重新获取,旧值在于之前的
|
说明已经可以释放这个锁重新在获取锁,setget方法可以把之前的锁去掉在重新获取,旧值在于之前的
|
||||||
值比较,如果无变化说明这个期间没有人获取或者操作这个redis锁,则可以重新获取
|
值比较,如果无变化说明这个期间没有人获取或者操作这个redis锁,则可以重新获取
|
||||||
V4---->>采用成熟的框架redisson,封装好的方法则可以直接处理,但是waittime记住要这只为0
|
V4---->>采用成熟的框架redisson,封装好的方法则可以直接处理,但是waittime记住要这只为0
|
||||||
|
#### [服务降级--服务熔断(过载保护))]()
|
||||||
|
自动降级: 超时.失败次数,故障,限流<br>
|
||||||
|
人工降级:秒杀,双11<br>
|
||||||
|
|
||||||
|
9.所有秒杀相关的接口比如:秒杀,获取秒杀地址,获取秒杀结果,获取秒杀验证码都需要加上<br>
|
||||||
|
秒杀是否开始结束的判断
|
||||||
|
#### [Nginx优化(前端缓存)]()
|
||||||
|
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha2.png)
|
||||||
|
### [缓存](/docs/high-concurrency/why-cache.md)
|
||||||
|
- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md)
|
||||||
|
- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md)
|
||||||
|
- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md)
|
||||||
|
- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||||
|
- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||||
|
- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md)
|
||||||
|
- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md)
|
||||||
|
- [了解什么是 Redis 的雪崩和穿透?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||||
|
- [如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md)
|
||||||
|
- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md)
|
||||||
|
- [生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md)
|
||||||
|
|
||||||
|
## 高可用架构
|
||||||
|
- [Hystrix 介绍](/docs/high-availability/hystrix-introduction.md)
|
||||||
|
- [电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md)
|
||||||
|
|
||||||
|
### 高可用系统
|
||||||
|
- 如何设计一个高可用系统?
|
||||||
|
|
||||||
|
### 限流
|
||||||
|
- 如何限流?在工作中是怎么做的?说一下具体的实现?
|
||||||
|
|
||||||
|
### 熔断
|
||||||
|
- 如何进行熔断?
|
||||||
|
- 熔断框架都有哪些?具体实现原理知道吗?
|
||||||
|
|
||||||
|
### 降级
|
||||||
|
- 如何进行降级?
|
151
README1.md
Normal file
151
README1.md
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
### 前言
|
||||||
|
大家好,之前在公司自己设计并开发了一套完整的秒杀系统,自己构建了一个小的demo,希望和大家分享一下,希望大家能从中收益,如果有意见和好的想法请加我!
|
||||||
|
QQ: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负载均衡
|
||||||
|
|
||||||
|
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha2.png)
|
||||||
|
|
||||||
|
|
||||||
|
### <font color=#0099ff size=3 >8.服务降级--服务熔断(过载保护)</fontfenbushi><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>
|
||||||
|
|
||||||
|
![整体流程](https://raw.githubusercontent.com/qiurunze123/imageall/master/miaosha1.png)
|
||||||
|
|
||||||
|
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
|
|
@ -10,6 +10,10 @@ import java.util.Map;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MQConfig {
|
public class MQConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /usr/sbin/rabbitmq-plugins enable rabbitmq_management
|
||||||
|
* mq页面
|
||||||
|
*/
|
||||||
public static final String MIAOSHA_QUEUE = "miaosha.queue";
|
public static final String MIAOSHA_QUEUE = "miaosha.queue";
|
||||||
public static final String QUEUE = "queue";
|
public static final String QUEUE = "queue";
|
||||||
public static final String TOPIC_QUEUE1 = "topic.queue1";
|
public static final String TOPIC_QUEUE1 = "topic.queue1";
|
||||||
|
|
|
@ -122,7 +122,8 @@ public class MiaoshaService {
|
||||||
try {
|
try {
|
||||||
ScriptEngineManager manager = new ScriptEngineManager();
|
ScriptEngineManager manager = new ScriptEngineManager();
|
||||||
ScriptEngine engine = manager.getEngineByName("JavaScript");
|
ScriptEngine engine = manager.getEngineByName("JavaScript");
|
||||||
return (Integer)engine.eval(exp);
|
Double catch1 = (Double)engine.eval(exp);
|
||||||
|
return catch1.intValue();
|
||||||
}catch(Exception e) {
|
}catch(Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -16,9 +16,9 @@ mybatis.mapperLocations=classpath:mybatis/mapper/*.xml
|
||||||
#add mybatis
|
#add mybatis
|
||||||
mybatis.config-locations=classpath:mybatis/config.xml
|
mybatis.config-locations=classpath:mybatis/config.xml
|
||||||
#datasource
|
#datasource
|
||||||
spring.datasource.url=jdbc:mysql://10.4.34.24:30006/miaosha?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
spring.datasource.url=jdbc:mysql://39.107.245.253:3306/miaosha?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
||||||
spring.datasource.username=root
|
spring.datasource.username=root
|
||||||
spring.datasource.password=123qwe!@#
|
spring.datasource.password=root
|
||||||
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||||
#druid
|
#druid
|
||||||
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
|
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
|
||||||
|
@ -44,20 +44,20 @@ spring.resources.chain.gzipped=true
|
||||||
spring.resources.chain.html-application-cache=true
|
spring.resources.chain.html-application-cache=true
|
||||||
spring.resources.static-locations=classpath:/static/
|
spring.resources.static-locations=classpath:/static/
|
||||||
#redis
|
#redis
|
||||||
redis.host=10.4.34.24
|
redis.host=39.107.245.253
|
||||||
redis.port=30009
|
redis.port=6379
|
||||||
redis.timeout=100
|
redis.timeout=100
|
||||||
redis.password=qazwsx.com
|
redis.password=123456
|
||||||
redis.poolMaxTotal=1000
|
redis.poolMaxTotal=1000
|
||||||
redis.poolMaxIdle=500
|
redis.poolMaxIdle=500
|
||||||
redis.poolMaxWait=500
|
redis.poolMaxWait=500
|
||||||
#server.port=8003
|
#server.port=8003
|
||||||
|
|
||||||
#rabbitmq
|
#rabbitmq
|
||||||
spring.rabbitmq.host=10.4.34.24
|
spring.rabbitmq.host=39.107.245.253
|
||||||
spring.rabbitmq.port=30672
|
spring.rabbitmq.port=5672
|
||||||
spring.rabbitmq.username=liudz
|
spring.rabbitmq.username=mqadmin
|
||||||
spring.rabbitmq.password=qazwsx.c0m
|
spring.rabbitmq.password=mqadmin
|
||||||
spring.rabbitmq.virtual-host=/
|
spring.rabbitmq.virtual-host=/
|
||||||
spring.rabbitmq.listener.simple.concurrency= 10
|
spring.rabbitmq.listener.simple.concurrency= 10
|
||||||
spring.rabbitmq.listener.simple.max-concurrency= 10
|
spring.rabbitmq.listener.simple.max-concurrency= 10
|
||||||
|
|
Loading…
Reference in New Issue
Block a user