RateLimiter是guava中concurrent包下的一個限流工具類,使用了令牌桶算法。下面簡單的介紹一下。
一、RateLimiter API
修飾符 |
方法 |
描述 |
double |
acquire() |
從RateLimiter獲取一個許可,該方法會被阻塞知道獲取到請求,阻塞動作,返回的double是從阻塞到獲取到令牌等待的時間。 |
double |
acquire(int permits) |
從RateLimiter獲取指定許可數,該方法會被阻塞知道獲取到請求 |
static RateLimiter |
create(double permitsPerSecond) |
根據指定的穩定吞吐率創建RateLimiter,這裏的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少查詢) |
static RateLimiter |
create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) |
根據指定的穩定吞吐率和預熱期來創建RateLimiter,這裏的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少個請求量),在這段預熱時間內,RateLimiter每秒分配的許可數回平穩的增長知道預熱期結束時達到其最大速率。(只要存在足夠請求數來使其飽和) |
double |
getRate() |
返回RateLimiter配置中的穩定速率,該速率單位是每秒多少許可數 |
void |
setRate(double permitsPerSecond) |
更新RateLimite的穩定速率,參數permit是PerSecond由構造RateLimiter的工廠方法提供。 |
String |
toString() |
返回對象的字符表現形式 |
boolean |
tryAcquire() |
從RateLimiter獲取許可,如果該許可在無延遲下的情況下立即獲取得到的話 |
boolean |
tryAcquire(int permits) |
從RateLimiter獲取許可數,如果該許可數可以在無延遲的情況下立即獲取得到的話 |
boolean |
tryAcquire(int permits, long timeout, TimeUnit unit) |
從RateLimiter獲取指定許可數如果該許可數可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout過期之前獲取得到許可數的話,那麼立即返回false(無需等待) |
boolean |
tryAcquire(long timeout, TimeUnit unit) |
從RateLimiter獲取許可如果該許可可以不超過timeout的時間內獲取得到的話,或者如果無法在timeout過期之前獲取得到許可的話,那麼立即返回false(無需等待) |
二、應用示例
需求:我們需要將流量限制在5QPS範圍內,爲了這個目的我們來看看該怎麼弄。
定義一個interface,AccessLimitService:
public interface AccessLimitService {
boolean tryAcquire();
double getRate();
}
定義實現類AccessLimitServiceImpl:
@Service
public class AccessLimitServiceImpl implements AccessLimitService {
//每秒發出5個令牌
private RateLimiter rateLimiter = RateLimiter.create(5.0);
/**
* 嘗試獲取令牌
* @Return
*/
public boolean tryAcquire() {
//這裏使用的是tryAcquire而不是acquire方法,快速失敗避免耗盡所有資源
return rateLimiter.tryAcquire();
}
public double getRate() {
return rateLimiter.getRate();
}
}
接口類:
@Slf4j
@RestController
@RequestMapping("/flow/limit")
public class LimitController extends BaseController {
@Autowired
private AccessLimitService accessLimitService;
@GetMapping(value = "/get")
public String getInfo() {
return "ok";
}
@GetMapping(value = "/access")
public String access() {
//嘗試獲取令牌,如果沒有請求到立即返回false
if (accessLimitService.tryAcquire()) {
return "access success [" + System.currentTimeMillis() + "]";
} else {
System.out.println(accessLimitService.getRate());
return "access limit [" + System.currentTimeMillis() + "]";
}
}
}
單元測試:
import com.google.common.util.concurrent.RateLimiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FlowLimitTest {
private RestTemplate restTemplate;
private String ip_dev = "localhost:8101";
private RateLimiter rateLimiter;
ExecutorService fixedThreadPool;
@Before
public void setup() {
restTemplate = new RestTemplate();
((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setConnectTimeout(60000);
((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setReadTimeout(60000);
}
@After
public void destroy() {
}
@Test
public void flowLimitTest() throws Exception {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
/**
* access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]
*/
for (int i = 0; i < 10; i++) {
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(requestApi());
}
});
}
fixedThreadPool.shutdown();
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
private String requestApi() {
StringBuilder url = new StringBuilder();
url.append("http://").append(ip_dev).append("/flow/limit/access");
HttpEntity<String> httpEntity = new HttpEntity<String>(null, null);
ResponseEntity<String> result = null;
try {
result = restTemplate.exchange(url.toString(), HttpMethod.GET, httpEntity, String.class);
if (result.getStatusCode().equals(HttpStatus.OK)) {
return result.getBody();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
可以看到結果,10個請求,6個通過,4個被限制,通過進一步測試發現create大小爲n的流量,但是在併發的過程中實際可以通過n+1個請求,其他的則被限制:
access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]
RateLimiter是比較常用的限流的服務,但是在微服務的場景中只有限流是不夠的,同時還要保證在下游系統不可用的時,本身系統的可用性,那麼就要考慮到熔斷和降級了,後面將說到這些。
Author:憶之獨秀
Email:[email protected]
註明出處:https://blog.csdn.net/lavorange/article/details/95742831