/* * Copyright 2008-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.hasor.rsf.address.route.flowcontrol.speed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicInteger; /** * 描述:线程安全的令牌桶限流器,时间窗刷新误差为毫秒级。 定义有效的限流器需要满足: * <ol> * <li>速率rate > 0,单位为次数/秒 * <li>时间窗timeWindow >= 1,单位为毫秒 * <li>峰值peak >= rate * timeWindow / 1000.0 * </ol> */ public class QoSBucket { protected Logger logger = LoggerFactory.getLogger(getClass()); private static final int DEFAULT_RATE = 50; private static final int DEFAULT_PEAK = 100; private static final int DEFAULT_TIMEWINDOW = 1000; private int rate; // 稳态中,每秒允许的调用次数 private int peak; // 突发调用峰值的上限,即令牌桶容量 private int timeWindow; // 令牌桶刷新最小间隔,单位毫秒 private AtomicInteger tokens; // 当前可用令牌数量 private volatile long lastRefreshTime; // 下一次刷新令牌桶的时间 private volatile double leftDouble; // // public QoSBucket() { this(DEFAULT_RATE, DEFAULT_PEAK, DEFAULT_TIMEWINDOW); } public QoSBucket(int rate, int peak, int timeWindow) { this.rate = rate; this.peak = peak; this.timeWindow = timeWindow; double initialToken = rate * timeWindow / 1000d; //初始的token为零不合理, 改为1。 this.tokens = initialToken >= 1 ? new AtomicInteger((int) initialToken) : new AtomicInteger(1); //增加此保存值,是为了double转int时候的不精确;如果不累及这个误差,累计的接过会非常大。 this.leftDouble = initialToken - Math.floor(initialToken); this.lastRefreshTime = System.currentTimeMillis(); } /** 检查令牌前,首先更新令牌数量 */ public boolean check() { long now = System.currentTimeMillis(); if (now > lastRefreshTime + timeWindow) {// 尝试更新令牌数量 int currentValue = tokens.get(); double interval = (now - lastRefreshTime) / 1000d; double addedDouble = interval * rate; int added = (int) addedDouble; // 最大值为Integer.MAX_VALUE if (added > 0) { double addedPlusDouble = leftDouble + (addedDouble - added); int addPlus = (int) addedPlusDouble; added += addPlus; int newValue = currentValue + added; newValue = (newValue > currentValue && newValue < peak) ? newValue : peak; if (tokens.compareAndSet(currentValue, newValue)) { lastRefreshTime = now;// 更新成功后,设置新的刷新时间 leftDouble = addedPlusDouble - addPlus; if (logger.isDebugEnabled()) { logger.debug("[QoSBucket] Updated done: [{}] -> [{}], refresh time: {}.", currentValue, newValue, now); } } } } int value = tokens.get();// 尝试获得一个令牌 boolean flag = false; // 是否获得到一个令牌 while (value > 0 && !flag) { flag = tokens.compareAndSet(value, value - 1); value = tokens.get(); } if (logger.isDebugEnabled() && !flag) { logger.debug("QoSBucket: get token failed, tokens[" + tokens.get() + "]"); } return flag; } @Override public String toString() { return "QoSBucket [tokens=" + tokens + ", rate=" + rate + ", peak=" + peak + ", timeWindow=" + timeWindow + "]"; } /** * 限流器有效性验证。限流器的配置必须满足以下条件: * <ol> * <li>速率rate、峰值peak配置为大于0 * <li>时间窗timeWindow不小于1 * <li>峰值不小于速率与时间窗的乘积 * </ol> * @return true/false */ public boolean validate() { if (rate <= 0 || peak <= 0 || timeWindow < 1) { return false; } if (peak < (rate * timeWindow / 1000F)) { return false; } return true; } }