# Table of Contents
- [架构](#架构)
- [秒杀业务分析](#秒杀业务分析)
- [业务特点](#业务特点)
- [性能瓶颈](#性能瓶颈)
- [数据库锁竞争](#数据库锁竞争)
- [秒杀读多写少的特性](#秒杀读多写少的特性)
- [技术难点](#技术难点)
- [秒杀流程](#秒杀流程)
- [数据库表设计](#数据库表设计)
- [重要基础功能开发](#重要基础功能开发)
- [result结果集封装](#result结果集封装)
- [redis通用缓存Key封装](#redis通用缓存key封装)
- [明文密码两次MD5处理](#明文密码两次md5处理)
- [第一次的目的](#第一次的目的)
- [第二次的目的](#第二次的目的)
- [全局异常处理器](#全局异常处理器)
- [异常抛出的流程](#异常抛出的流程)
- [全局异常处理器处理思路:](#全局异常处理器处理思路)
- [实现](#实现)
- [分布式Session--SSO系统](#分布式session--sso系统)
- [秒杀系统优化](#秒杀系统优化)
- [高并发-访问拦截](#高并发-访问拦截)
- [动静分离与反向代理层访问拦截](#动静分离与反向代理层访问拦截)
- [Web层和Service层访问拦截](#web层和service层访问拦截)
- [高并发-分流](#高并发-分流)
- [高并发-限流](#高并发-限流)
- [计数器--redis预减库存](#计数器--redis预减库存)
- [异步化--消息队列](#异步化--消息队列)
- [数学公式验证码](#数学公式验证码)
- [正确性-超卖问题](#正确性-超卖问题)
- [原因](#原因)
- [解决重复下单](#解决重复下单)
- [防作弊--安全性](#防作弊--安全性)
- [秒杀接口地址隐藏](#秒杀接口地址隐藏)
- [限流](#限流)
- [其它优化](#其它优化)
- [先下单再减库存](#先下单再减库存)
- [网站设计概述](#网站设计概述)
- [参考资料](#参考资料)
# 架构
![](./pic/搜狗截图20190623095123.jpg)
![](./pic/搜狗截图20190623095948.jpg)
# 秒杀业务分析
1. 正常电子商务流程(1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;(6)卖家发货
2. 秒杀业务的特性(1)低廉价格;(2)大幅推广;(3)瞬时售空;(4)一般是定时上架;(5)时间短、瞬时并发量高;
## 业务特点
![](./pic/0b850c409bb72d6a8e1f9265442e224a.png)
## 性能瓶颈
### 数据库
[数据库性能对比](https://cloud.tencent.com/developer/article/1005399)
I: 首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,**MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点**,之后一路下降,最终甚至会比单thread的性能还要差。
II: 其次,超卖的根结在于**减库存操作是一个事务操作**,需要先select,然后insert,最后update -1。最后这个-1操作
是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。
III:最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢**InnoDB行锁**的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。
- Mysql执行单条的SQL语句其实是非常快的
- 主要是**行级锁事务的等待**,网络的延迟和GC回收!
update number set x=x-1 where (x -1 ) >= 0;
这个语句在innodb,设置了事务和RR可重复读隔离级别下,并且使用到了索引,是会加上行锁(X锁)的,可以直接解决超卖问题,但是高并发扛不住,行锁竞争严重。所以使用消息队列,做一个缓冲,减轻mysql行锁竞争的压力。
消息队列的作用:1. 减轻mysql行锁的竞争 2. 异步下单
### 秒杀读多写少的特性
使用缓存
## 技术难点
秒杀系统主要解决三大问题:
一、**瞬时的高并发访问**。抢购和普通的电商销售有所不同,普通的电商销售,流量是比较平均的,虽然有波峰波谷,但不会特别突出。而抢购是在特定时间点进行的推销活动,抢购开始前,用户不断刷新页面,以获得购买按钮;抢购开始的一瞬间,集中并发购买。
二、**数据正确性**。抢购毕竟是一种购买行为,需要购买、扣减库存、支付等复杂的流程,在此过程中,要保证数据的正确性,防止**超卖(卖出量超过库存)**的发生。
三、**防作弊**。无论是火车票的购买,还是低价商品的促销,肯定不希望某些客户买到所有的商品,应尽量保证公平性。通过购票插件购买火车票,阿里巴巴抢月饼事件等,需要限制技术性用户绕过网站的限制,通过技术手段获得不良收益。
解决高并发的问题,主要有如下的三个思路:**访问拦截,分流,限流**。
主流的Web站点采用分层的架构设计,如果你的应用还没有采用分层的架构,那么先做分层设计吧。一般来说,浏览器采用了html/js/css技术,负责数据的展示;反向代理一般采用nginx,负责负载均衡;Web层是指Php,Tomcat等应用服务器,负责用户状态的维护,http协议处理等;service层一般是rpc调用,当然也有用http的,例如spring cloud;数据库存储一般是mongodb,mysql等持久化数据方案。用户的一次数据访问,例如查询商品库存,数据是从上层依次调用到DB,逐层返回数据。
## 秒杀流程
登录
![](./pic/phaa6c15if.jpeg)
准备进入秒杀
![](./pic/6gi0nqp5k9.jpeg)
**开始秒杀**
![秒杀流程](./pic/秒杀流程图.jpg)
整体流程
![](./pic/搜狗截图20190623104731.jpg)
## 数据库表设计
商品表 秒杀商品表
订单表 秒杀订单表
如果直接在商品表里面添加一个字段表明是否是秒杀商品,会让商品表难以维护
# 重要基础功能开发
## result结果集封装
暴露接口,而不是直接去创建对象,面向接口编程,进行解耦合
由于错误有多种原因,所以使用CodeMsg封装code,msg
```java
return Result.success("hello");
return new Result<String>(50010,"success","hello"); //耦合度太高,硬编码
```
```java
public class Result<T> {
private int code;
private String msg;
private T data;
//construtor
public static <T> Result<T> success(T data) {
return new Result<T>(data);
}
public static <T> Result<T> error(CodeMsg cm) {
return new Result<T>(cm);
}
//get-set
}
```
```java
public class CodeMsg {
private int code;
private String msg;
//construtor
public static CodeMsg SUCCESS=new CodeMsg(0,"success");
public static CodeMsg SERVER_ERROR=new CodeMsg(500100,"服务端异常");
//get-set
}
```
## redis通用缓存Key封装
[电商系统,Redis做缓存时===>通用缓存key的封装](https://blog.csdn.net/tiankong_12345/article/details/86651613)
采用**字符串**的数据类型,但是不同模块的key可能会相互影响,所以应该设计这样的key "UserKey:id1","OrderKey:id1"
**模板模式:接口+抽象类+具体实现**
**优点**
具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构
**代码复用**的基本技术,在数据库设计中尤为重要
存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“**开闭原则**”
**缺点**
每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大
接口
```java
public interface KeyPrefix {
public int expireSeconds() ;
public String getPrefix() ;
}
```
**抽象类**
```java
public abstract class BasePrefix implements KeyPrefix{
private int expireSeconds;
private