秒杀问题:前端
1. 前端:redis
2. 后端:算法
商品超卖: 数据库乐观锁(CAS无锁)、 Redis分布式锁、MQ异步形式修改库存(用户须要等待)docker
单机压力大:单独一服务形式部署+docker。能够实现快速扩容数据库
用户操做频率块:网关限流后端
用户做弊:缓存
数据库访问压力大: 分表分库、使用MQ异步实现修改库存。相似:抢票等待30s才知道抢票结果。服务器
前端优化方案: 网络
举个例子:若是1m带宽等于128kb/s加载一个网页640kb。须要 640kb/128kb=5s. 若是秒杀时候网页加载不出来就完蛋了。并发
这个就牵涉到一个带宽入口问题,服务器生产环境买的带宽。
优化方案: 动静分离 能够经过CDN完成, 能够参考使用七牛云的配置使用
Nginx配置以下:
events {
#的最大链接数(包含全部链接数)1024
worker_connections 1024; ## Default: 1024
}
http{
# 代理缓存配置
proxy_cache_path "./toov5_cachedata" levels=1:2 keys_zone=meitecache:256m inactive=1d max_size=1000g;
server {
listen 80;
location /{
#使用缓存名称
proxy_cache toov5cache;
#对如下状态码实现缓存
proxy_cache_valid 200 206 304 301 302 1d;
#缓存的key
proxy_cache_key $request_uri;
add_header X-Cache-Status $upstream_cache_status;
#反向代理地址
proxy_pass http://127.0.0.1:9500;
}
}
}
同时在Nginx目录下建立:
toov5_cachedata
可是这样,若是商品详情页修改了,对于缓存怎么办? 能够经过Nginx+Lua+OpenResty实现优化。或者把url后面拼接时间戳。
后端:
数据库设计:
秒杀商品表:verson:乐观锁。 seckill_id 秒杀商品ID
秒杀订单记录表:
超卖问题:
超卖方案一:
修改库存时候: update toov5_seckill set inventory = inventory -1 where seckill_id = ? and inventory >0 ; 前提是库存必须大于0! 数据库自带行锁!
超卖方案二:
先查询版本号: select version from toov5_seckill where seckill_id = '10023';
修改数据时候:update toov5_seckill set inventory = inventory -1, version = version +1 where seckill_id = ? and version = ' ' and inventory > 0
至关于某个线程获取到锁,去执行。
分析:
方案一: 使用数据库自带的行锁机制,100个商品,若是200个请求,100个能够成功。
方案二: 乐观锁控制, 100个商品,若是200请求,只有一部分能够抢购成功。能防止一会儿都抢购完。
用户访问频率问题:
用户频率的限制: 不能让一个用户把库存全部的都抢购了,或者用户持续请求访问。
方案: 让用户请求间隔10s
具体: Redis,单线程,setNx 成功失败返回值。 key: phone value: seckillId expireTime: 10s
数据库高并发IO操做问题:
问题: 若是秒杀的请求过多,对数据库频繁的IO操做,可能会数据库崩溃。 即使是分库分表,读写分离也是无济于事的。
方案: MQ+令牌桶
具体: 提早生成好对应令牌,放在令牌桶。经过MQ异步发送,实现修改库存。(拿到令牌的才能够去修改库存)。
好比:100个库存,10w个请求
提早生成100个token,谁能获取到令牌,获取到令牌后,MQ异步实现库存修改。MQ也能够进行流量削峰。
只有一部分能执行上面的 update语句。
注意: 采用MQ实现秒杀接口,用户不能一次拿到秒杀结果。
具体: 1.前端调用秒杀接口,若是秒杀成功的话,返回正在排队中...
2. 前端经过定时器,使用token查询是否秒杀成功。(MQ消费速度很快的状况下)
注意生成token的Redis采用的数据类型: key:库存Id value: list. 每次从redis获取token后删除redis的token。直到没有token为止,此时表明商品抢购完了。
注意:生产者和消费者不能在同一个服务,消费者单独起一个服务。要不一个失败了都失败了。生产者挂了,不影响消费者。
限流方案:
网关层面: 经过令牌桶算法限流,每秒往令牌桶放入令牌。
限流算法:
关于令牌桶算法是市面上比较常见的方案,原理图:
拿不到令牌的场景就是,高并发请求量比较大,请求过多时候。一秒200个令牌,则一秒200个请求。
原理:
以规定的速率往令牌桶中存入Token,用户请求必须获取到令牌中的Token才能够处理 请求,若是没有从令牌桶中获取到令牌则丢失该请求。
例如:令牌桶中最多只能存放20个Token,以规定速率存入Token实如今高并发状况下 限流
优点:控制请求的速率匀速相等的 1s/10r
关于漏桶实现原理图:
漏桶算法原理:
水(请求)先进入到漏桶里,漏桶以必定的速度出水,当水流入速度过大会直接溢出,能够看出漏桶算法能强行限制数据的传输速率。
区别:
漏桶算法与令牌桶算法在表面看起来相似,很容易将二者混淆。但事实上,这二者具备大相径庭的特性,且为不一样的目的而使用。 漏桶算法与令牌桶算法的区别在于,漏桶算法可以强行限制数据的传输速率,令牌桶算法可以在限制数据的平均传输速率的同时还容许某种程度的突发传输。 须要注意的是,在某些状况下,漏桶算法不可以有效地使用网络资源,由于漏桶的漏出速率是固定的,因此即便网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。所以,漏桶算法对于存在突发特性的流量来讲缺少效率。而令牌桶算法则可以知足这些具备突发特性
RateLIimiter源码中,有个线程异步往令牌桶放入token. 能够进行配置。
问题: 若是Tomcat50个线程已经处理请求访问了,同时咱们本身的查询用户秒杀结果的接口请求。这样的话是不行的,可能致使服务雪崩效应。须要进行线程池隔离!经过Hystrix就能够。