高并发设计
一思路
缓存、降级和限流。
1. 缓存
提升系统访问速度和增大系统能处理的容量。
2. 降级
当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开
3. 限流
用来处理前面2种不能解决的情况,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。
##二限流
操作
###1. 操作
通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。
###2. 常见的限流
1. 限制总并发数(比如数据库连接池、线程池)
2. 限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)
3. 限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
4. 其他还有如限制远程接口调用速率、限制MQ的消费速率。
5. 根据网络连接数、网络流量、CPU或内存负载等来限流。
###3. 注意
1. 好处:不用担心瞬间流量导致系统挂掉或雪崩,最终做到有损服务而不是不服务;
2. 缺陷: 限流需要评估好,不可乱用,否则会正常流量出现一些奇怪的问题而导致用户抱怨。
###4限流算法
常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。
##三令牌桶算法
令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。
令牌桶算法的描述如下:
1. 假设按照100毫秒的固定速率往桶中添加令牌;
2. 桶中最多存放1000个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
3. 当一个5个字节大小的数据包(5个请求)到达,将从桶中拿走5个令牌,接着数据包被发送到网络上;
4. 如果桶中的令牌不足5个,数据包大于等于5个,且该数据包将被限流,因为没有足够的令牌(要么丢弃,要么缓冲区等待)。
##四漏桶算法
可以用于流量整形和流量控制。
漏桶算法的描述如下:
1. 一个固定容量的漏桶,按照常量固定速率流出水滴;
2. 如果桶是空的,则不流出水滴;
3. 可以以任意速率流入水滴到漏桶;
4. 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。
##五漏桶算法与令牌桶算法比较
- 流入
- 令牌桶:固定
- 按照固定速率往桶中添加令牌,请求是否被处理要看桶中令牌是否足够,当令牌数不够则拒绝新的请求
- 允许一定程度的突发:一下子要处理很多请求
- 漏桶:任意
- 令牌桶:固定
- 流出
- 令牌桶:任意
- 允许突发请求,只要有令牌就可以处理,支持拿多个令牌,并允许一定程度突发流量;
- 漏桶:固定
- 按照固定速率流出:按照固定的速度来处理
- 平滑突发流入速率:就是让高并发的请求按照固定的速度来处理
- 令牌桶:任意
##六在我们项目中的应用
###1项目的环境
当前我们项目,当用户登录的时候,会触发10多个接口同时请求,当我们服务端向客户端推送消息后(参与活动有奖励),大量的用户(30W)在短时间内登录我们服务,会造成很大的数据库和接口请求压力。当然我们有做了优化。
- 一般固定不变的数据预先准备好
- 固定数据放缓存
- 特性数据(用户个性化推送数据),预先处理,定时刷新
###2使用借鉴漏桶算法 - 我们请求来源终端操作,且请求速度是任意的,为了让请求不丢失,我们一定要处理来只请求的所有数据,怎么让所有的请求不丢失呢,我们先把所有的请求放到阻塞队列中(桶),开启多个线程(线程池管理)执行队列中的任务(固定速度(一个时刻有几个线程处理任务)处理任务),并返回响应结果。
- 后期优化,原先我们处理请求的线程数是固定的,后来我们做了优化,我们对请求的数量做了一个梯度,当超过某个梯度的时候,我们会自动增加线程的数量,当然线程的数量不是可以无限增加的,最大的线程数和cpu,内存有关,当超过我们能处理的最大线程数的时候,我们开启等待队列,将所有新的请求放到等待队列中,同时开启请求优化服务,将那些请求能个短时间返回结果的请求优先执行,那些要花大量时间的请求,我们对响应的接口做了服务降级。