javascript
Spring Cloud限流详解(附源码)
在高并發(fā)的應(yīng)用中,限流往往是一個繞不開的話題。本文詳細探討在Spring Cloud中如何實現(xiàn)限流。
在?Zuul?上實現(xiàn)限流是個不錯的選擇,只需要編寫一個過濾器就可以了,關(guān)鍵在于如何實現(xiàn)限流的算法。常見的限流算法有漏桶算法以及令牌桶算法。這個可參考?https://www.cnblogs.com/LBSer/p/4083131.html?,寫得通俗易懂,你值得擁有,我就不拽文了。
GoogleGuava?為我們提供了限流工具類?RateLimiter?,于是乎,我們可以擼代碼了。
?
代碼示例
@Component public class RateLimitZuulFilter extends ZuulFilter {private final RateLimiter rateLimiter = RateLimiter.create(1000.0);@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return Ordered.HIGHEST_PRECEDENCE;}@Overridepublic boolean shouldFilter() {// 這里可以考慮弄個限流開啟的開關(guān),開啟限流返回true,關(guān)閉限流返回false,你懂的。return true;}@Overridepublic Object run() {try {RequestContext currentContext = RequestContext.getCurrentContext();HttpServletResponse response = currentContext.getResponse();if (!rateLimiter.tryAcquire()) {HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS;response.setContentType(MediaType.TEXT_PLAIN_VALUE);response.setStatus(httpStatus.value());response.getWriter().append(httpStatus.getReasonPhrase());currentContext.setSendZuulResponse(false);throw new ZuulException(httpStatus.getReasonPhrase(),httpStatus.value(),httpStatus.getReasonPhrase());}} catch (Exception e) {ReflectionUtils.rethrowRuntimeException(e);}return null;}}如上,我們編寫了一個?pre?類型的過濾器。對Zuul過濾器有疑問的可參考我的博客:
- Spring Cloud內(nèi)置的Zuul過濾器詳解:http://www.itmuch.com/spring-cloud/zuul/zuul-filter-in-spring-cloud
- Spring Cloud Zuul過濾器詳解:http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter
在過濾器中,我們使用?GuavaRateLimiter?實現(xiàn)限流,如果已經(jīng)達到最大流量,就拋異常。
?
分布式場景下的限流
以上單節(jié)點Zuul下的限流,但在生產(chǎn)中,我們往往會有多個Zuul實例。對于這種場景如何限流呢?我們可以借助Redis實現(xiàn)限流。
使用redis實現(xiàn),存儲兩個key,一個用于計時,一個用于計數(shù)。請求每調(diào)用一次,計數(shù)器增加1,若在計時器時間內(nèi)計數(shù)器未超過閾值,則可以處理任務(wù)
?if(!cacheDao.hasKey(TIME_KEY)) {cacheDao.putToValue(TIME_KEY, 0, 1, TimeUnit.SECONDS); } ? ? ? if(cacheDao.hasKey(TIME_KEY) && cacheDao.incrBy(COUNTER_KEY, 1) > 400) {// 拋個異常什么的}實現(xiàn)微服務(wù)級別的限流
一些場景下,我們可能還需要實現(xiàn)微服務(wù)粒度的限流。此時可以有兩種方案:
方式一:在微服務(wù)本身實現(xiàn)限流。
和在Zuul上實現(xiàn)限流類似,只需編寫一個過濾器或者攔截器即可,比較簡單,不作贅述。個人不太喜歡這種方式,因為每個微服務(wù)都得編碼,感覺成本很高啊。
加班那么多,作為程序猿的我們,應(yīng)該學(xué)會偷懶,這樣才可能有時間孝順父母、抱老婆、逗兒子、遛狗養(yǎng)鳥、聊天打屁、追求人生信仰。好了不扯淡了,看方法二吧。
方法二:在Zuul上實現(xiàn)微服務(wù)粒度的限流。
在講解之前,我們不妨模擬兩個路由規(guī)則,兩種路由規(guī)則分別代表Zuul的兩種路由方式。
?zuul:routes:microservice-provider-user: /user/**user2:url: http://localhost:8000/path: /user2/**如配置所示,在這里,我們定義了兩個路由規(guī)則,?microservice-provider-user?以及?user2?,其中?microservice-provider-user?這個路由規(guī)則使用到Ribbon + Hystrix,走的是?RibbonRoutingFilter?;而?user2?這個路由用不上Ribbon也用不上Hystrix,走的是?SipleRoutingFilter?。如果你搞不清楚這點,請參閱我的博客:
-
Spring Cloud內(nèi)置的Zuul過濾器詳解:http://www.itmuch.com/spring-cloud/zuul/zuul-filter-in-spring-cloud
-
Spring Cloud Zuul過濾器詳解:http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter
搞清楚這點之后,我們就可以擼代碼了:
@Component public class RateLimitZuulFilter extends ZuulFilter {private Map<String, RateLimiter> map = Maps.newConcurrentMap();@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {// 這邊的order一定要大于org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter的order// 也就是要大于5// 否則,RequestContext.getCurrentContext()里拿不到serviceId等數(shù)據(jù)。return Ordered.LOWEST_PRECEDENCE;}@Overridepublic boolean shouldFilter() {// 這里可以考慮弄個限流開啟的開關(guān),開啟限流返回true,關(guān)閉限流返回false,你懂的。return true;}@Overridepublic Object run() {try {RequestContext context = RequestContext.getCurrentContext();HttpServletResponse response = context.getResponse();String key = null;// 對于service格式的路由,走RibbonRoutingFilterString serviceId = (String) context.get(SERVICE_ID_KEY);if (serviceId != null) {key = serviceId;map.putIfAbsent(serviceId, RateLimiter.create(1000.0));}// 如果壓根不走RibbonRoutingFilter,則認為是URL格式的路由else {// 對于URL格式的路由,走SimpleHostRoutingFilterURL routeHost = context.getRouteHost();if (routeHost != null) {String url = routeHost.toString();key = url;map.putIfAbsent(url, RateLimiter.create(2000.0));}}RateLimiter rateLimiter = map.get(key);if (!rateLimiter.tryAcquire()) {HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS;response.setContentType(MediaType.TEXT_PLAIN_VALUE);response.setStatus(httpStatus.value());response.getWriter().append(httpStatus.getReasonPhrase());context.setSendZuulResponse(false);throw new ZuulException(httpStatus.getReasonPhrase(),httpStatus.value(),httpStatus.getReasonPhrase());}} catch (Exception e) {ReflectionUtils.rethrowRuntimeException(e);}return null;}}簡單講解一下這段代碼:
對于?microservice-provider-user?這個路由,我們可以用?context.get(SERVICE_ID_KEY);?獲取到serviceId,獲取出來就是?microservice-provider-user;
而對于?user2?這個路由,我們使用?context.get(SERVICE_ID_KEY);?獲得是null,但是呢,可以用?context.getRouteHost()?獲得路由到的地址,獲取出來就是?http://localhost:8000/?。接下來的事情,你們懂的。
?
改進與提升
實際項目中,除以上實現(xiàn)的限流方式,還可能會:
一、在上文的基礎(chǔ)上,增加配置項,控制每個路由的限流指標,并實現(xiàn)動態(tài)刷新,從而實現(xiàn)更加靈活的管理
二、基于CPU、內(nèi)存、數(shù)據(jù)庫等壓力限流(感謝平安常浩智)提出。。
下面,筆者借助Spring Boot Actuator提供的?Metrics?能力進行實現(xiàn)基于內(nèi)存壓力的限流——當(dāng)可用內(nèi)存低于某個閾值就開啟限流,否則不開啟限流。
@Component public class RateLimitZuulFilter extends ZuulFilter {@Autowiredprivate SystemPublicMetrics systemPublicMetrics;@Overridepublic boolean shouldFilter() {// 這里可以考慮弄個限流開啟的開關(guān),開啟限流返回true,關(guān)閉限流返回false,你懂的。Collection<Metric<?>> metrics = systemPublicMetrics.metrics();Optional<Metric<?>> freeMemoryMetric = metrics.stream().filter(t -> "mem.free".equals(t.getName())).findFirst();// 如果不存在這個指標,穩(wěn)妥起見,返回true,開啟限流if (!freeMemoryMetric.isPresent()) {return true;}long freeMemory = freeMemoryMetric.get().getValue().longValue();// 如果可用內(nèi)存小于1000000KB,開啟流控return freeMemory < 1000000L;}// 省略其他方法}三、實現(xiàn)不同維度的限流,例如:
-
對請求的目標URL進行限流(例如:某個URL每分鐘只允許調(diào)用多少次)
-
對客戶端的訪問IP進行限流(例如:某個IP每分鐘只允許請求多少次)
-
對某些特定用戶或者用戶組進行限流(例如:非VIP用戶限制每分鐘只允許調(diào)用100次某個API等)
-
多維度混合的限流。此時,就需要實現(xiàn)一些限流規(guī)則的編排機制。與、或、非等關(guān)系。
?
參考文檔
-
分布式環(huán)境下限流方案的實現(xiàn):http://blog.csdn.net/Justnow_/article/details/53055299
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud限流详解(附源码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring框架中的设计模式(五)
- 下一篇: 《Spring Cloud Netfli