秒杀问题:
1. 前端:
- 突然增加网络访问带宽
- 用户可能存在重复提交
2. 后端:
商品超卖: 数据库乐观锁(CAS无锁)、 Redis分布式锁、MQ异步形式修改库存(用户需要等待)
单机压力大:单独一服务形式部署+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为止,此时代表商品抢购完了。
注意:生产者和消费者不能在同一个服务,消费者单独起一个服务。要不一个失败了都失败了。生产者挂了,不影响消费者。
关于限流:
限流方案:
- Nginx+Lua
- Hystrix,熔断,达到阈值拒绝之。
- Guava
网关层面: 通过令牌桶算法限流,每秒往令牌桶放入令牌。
限流算法:
- 令牌桶算法
- 漏桶算法
关于令牌桶算法是市面上比较常见的方案,原理图:
拿不到令牌的场景就是,高并发请求量比较大,请求过多时候。一秒200个令牌,则一秒200个请求。
原理:
以规定的速率往令牌桶中存入Token,用户请求必须获取到令牌中的Token才可以处理 请求,如果没有从令牌桶中获取到令牌则丢失该请求。
例如:令牌桶中最多只能存放20个Token,以规定速率存入Token实现在高并发情况下 限流
优势:控制请求的速率匀速相等的 1s/10r
关于漏桶实现原理图:
漏桶算法原理:
水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
区别:
漏桶算法与令牌桶算法在表面看起来类似,很容易将两者混淆。但事实上,这两者具有截然不同的特性,且为不同的目的而使用。 漏桶算法与令牌桶算法的区别在于,漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。 需要注意的是,在某些情况下,漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性
RateLIimiter源码中,有个线程异步往令牌桶放入token. 可以进行配置。
问题: 如果Tomcat50个线程已经处理请求访问了,同时我们自己的查询用户秒杀结果的接口请求。这样的话是不行的,可能导致服务雪崩效应。需要进行线程池隔离!通过Hystrix就可以。