限流实现方案


一.总体介绍

很多做服务接口的人或多或少的遇到这样的场景,由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统。

    也就是面对大流量时,如何进行流量控制?

    服务接口的流量控制策略:分流、降级、限流等。本文讨论  限流策略,虽然降低了服务接口的访问频率和并发量,却换取服务接口和业务应用系统的高可用。

 实际场景中常用的限流策略:

    1.Nginx前端限流
         按照一定的规则如帐号、IP、系统调用逻辑等在Nginx层面做限流

    2.业务应用系统限流
        1、客户端限流
        2、服务端限流

    3.数据库限流
        红线区,力保数据库


二.常用的限流算法

常见的限流算法有:令牌桶、漏桶。 计数器也可以进行粗暴限流实现。

2.1 令牌桶(单机)

大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小


流程:
1.所有的流量在放行之前需要获取一定量的 token;
2.所有的 token 存放在一个 bucket(桶)当中,每 1/r 秒,都会往这个 bucket 当中加入一个 token;
3.bucket 有最大容量(capacity or limit),在 bucket 中的 token 数量等于最大容量,而且没有 token 消耗时,新的额外的 token 会被抛弃。

这种实现方法有几个优势:
1.避免了给每一个 Bucket 设置一个定时器这种笨办法,
2.数据结构需要的内存量很小,只需要储存 Bucket 中剩余的 Token 量以及上次补充 Token 的时间戳就可以了;
3.只有在用户访问的时候,才会计算 Token 补充量,对于系统的计算资源占用量也较小。

Guava 库当中也有一个 RateLimiter,其作用也是 用来进行限流,于是阅读了 RateLimiter 的源代码,查看一些 Google 的人是如何实现 Token Bucket 算法的。

private void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    if (nowMicros > nextFreeTicketMicros) {
      storedPermits = min(maxPermits,
          storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
      nextFreeTicketMicros = nowMicros;
    }
}

在 resync 方法中的这句代码 storedPermits = min(maxPermits, storedPermits+ (nowMicros - nextFreeTicketMicros)/stableIntervalMicros); 就是 RateLimiter 中计算 Token 数量的方法。没有使用计时器,而是使用时间戳的方式计算。这个做法给足了 信息。我们可以在 Bucket 中存放现在的 Token 数量,然后存储上一次补充 Token 的时间戳,当用户下一次请求获取一个 Token 的时候, 根据此时的时间戳,计算从上一个时间戳开始,到现在的这个时间点所补充的所有 Token 数量,加入到 Bucket 当中。


通过使用RateLimiter简单模拟一个实现:

package com.niepeng.goldcode.common.ratelimit;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.RateLimiter;
import com.niepeng.goldcode.util.DateUtil;

/**
 * 介绍文档:google的ratelimiter文档翻译
 * http://ifeve.com/guava-ratelimiter/
 * 
 * @author niepeng
 *
 */
public class ApiCallDemo {

	private int permitsPerSecond = 10; // 每秒10个许可
	private int threadNum = 3;

	public static void main(String[] args) {
		new ApiCallDemo().call();
	}

	private void call() {
		ExecutorService executor = Executors.newFixedThreadPool(threadNum);
		final RateLimiter rateLimiter = RateLimi
  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值