开篇
上篇学习了限流的基本知识(传送门),接下来学习一下应用限流的方法
应用级限流
针对容器限制总并发/连接/请求数
通过对容器进行配置,限制TPS/QPS阀值,防止大量请求涌入击垮系统。
如果你使用过Tomcat,其Connector 其中一种配置有如下几个参数:
acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;
maxConnections:瞬时最大连接数,超出的会排队等待;
maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死
详细的配置请参考官方文档。另外如MySQL(如max_connections)、Redis(如tcp-backlog)都会有类似的限制连接数的配置
针对接口进行并发控制
如果担心接口某个时刻并发量过大了,可以细粒度地限制每个接口的总并发/请求数
可以使用Java得Atomic来实现
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
结果
可以看到,程序通过requestCount
把并发数量控制在10,当有50个线程并发去访问时,就会出现有得线程能竞争资源成功,有的线程竞争资源失败
这种并发控制方式比较简单粗暴,没有平滑处理,应用的时候要根据场景使用
针对时间窗口进行并发控制
限制住一个窗口时间内接口的请求量,例如某个基础服务调用量很大,怕被突然的大流量打挂,下面是一种实现窗口时间并发控制的方法
我们使用Guava的Cache来存储计数器,利用秒数作为Key,Value代表这一秒有多少个请求,这样就限制了一秒内的并发数。另外过期时间设置为两秒,保证一秒内的数据是存在的。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
结果
限流数为5,并发数为10,从结果上看,当一秒内请求数到了5之后,接下来的请求都会别拒绝
平滑限流接口请求数
之前的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。
这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。Guava框架提供了令牌桶算法实现,可直接拿来使用。
基本使用-1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
结果
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
如果令牌桶里有令牌的话,acquire返回0。如果没令牌,则等待一段时间,当有令牌的时候消费令牌并则返回等待令牌所消耗的时间。
基本使用-2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
结果
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
令牌桶允许一定程度的透支,不过接下来的请求需要等透支完的补充回来后才能继续执行。
基本使用-3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
结果
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
没有获取到令牌的请求将会被拒绝,这里可以添加一些额外的处理,例如增加异步操作,把处理不过来的请求加入队列,待处理完之后发消息通知用户处理结果。
模拟请求
模拟一个TPS为10的接口
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
测试
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
结果
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
上面模拟了一个TPS为10的接口,同时一秒内20个并发请求,观察发现一半的请求下单成功,而另一半的需要稍后再试,符合我们的预期
总结
上面的是应用层的限流策略,有基于Atomic的请求数限流,有基于GuavaCache的针对时间窗口请求数限流,也有基于令牌桶的平滑限流。Atomic和GuavaCache这两种方法比较粗粒度,简单的场景下可以使用。
令牌桶的方法比较细粒度,使用上更灵活,适合复杂一点的场景。
不过以上的方式只是对单应用进行了限流,在分布式和流行的微服务的场景下有心无力,接下来将会继续学习在分布式和微服务的场景下如何限流。