在上一章節中,我們做了網關的日誌請求及全局異常,本章節我們將會介紹限流及斷路。
對於很多同學來說,限流跟斷路應該都是經常遇見的,但是有很多新同學並不知道,這裏先介紹一下限流跟斷路。
限流:限制流量,舉個例子,我提供了一個接口出去,限定一秒內訪問的次數只能是100次,這就是限流。本文采用谷歌的RateLimiter來實現
斷路:斷路也叫斷路器,當一個請求一直在請求中,超過一定時間後,就會觸發斷路器,直接給請求返回一個異常,告知例如請求超時之類的提示。本文的斷路器採用hystrix來實現,斷路器面板採用dashboard
整體的項目結構如下:新增了兩個類,斷路器攔截,斷路器配置類,限流器
項目結構圖
接下里我們直接開幹,首先是pom文件引入,增加以下包
<!-- 斷路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 斷路器面板 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
新增斷路器類FallbackFilter
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.common.filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import com.platform.gateway.common.utils.MsgUtils;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.filters
* @className: FallbackFilter.java
* @description: 路由斷路器
* @author: OprCalf
* @date: 2020年3月4日
*/
@Component
public class FallbackFilter implements FallbackProvider {
@Override
public String getRoute() {
// 表明爲哪個微服務提供回退,return "*"代表爲所有微服務提供回退
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// 獲取狀態碼(200,OK)
return HttpStatus.BAD_GATEWAY;
}
@Override
public int getRawStatusCode() throws IOException {
// 返回數字狀態碼
return HttpStatus.BAD_GATEWAY.value();
}
@Override
public String getStatusText() throws IOException {
// 返回字母狀態碼
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 返回的內容
return new ByteArrayInputStream(MsgUtils.buildFailureMsg("找不到服務,請稍後再試").toString().getBytes("utf-8"));
}
@SuppressWarnings("deprecation")
@Override
public HttpHeaders getHeaders() {
final HttpHeaders headers = new HttpHeaders();
// 返回時的Header體的設置
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}
新增限流器類LimitFilter
這裏限制每秒只能有100個請求,超過的話,會自動提示錯誤信息
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.common.filter;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.platform.gateway.common.utils.MsgUtils;
import lombok.extern.slf4j.Slf4j;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.filters
* @className: LimitFilter.java
* @description: 限流過濾
* @author: OprCalf
* @date: 2020年3月4日
*/
@Component
@Slf4j
public class LimitFilter extends ZuulFilter {
// 每秒產生100個令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return Integer.MIN_VALUE + 2;
}
@Override
public boolean shouldFilter() {
final RequestContext ctx = RequestContext.getCurrentContext();
// 當發生異常的時候,不是進行取值
if (ctx.getThrowable() != null) {
log.error("{}", ctx.getThrowable().fillInStackTrace());
if (ctx.sendZuulResponse()) {
return true;
} else {
return false;
}
} else {
return true;
}
}
@Override
public Object run() throws ZuulException {
final RequestContext requestContext = RequestContext.getCurrentContext();
// 就相當於每調用一次tryAcquire()方法,令牌數量減1,當1000個用完後,那麼後面進來的用戶無法訪問上面接口
// 當然這裏只寫類上面一個接口,可以這麼寫,實際可以在這裏要加一層接口判斷。
if (!RATE_LIMITER.tryAcquire()) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
final HttpServletResponse response = requestContext.getResponse();
response.setContentType("application/json;charset=UTF-8");
try {
response.getOutputStream()
.write(MsgUtils.buildFailureMsg("訪問量巨大,稍後再試").toString().getBytes("utf-8"));
}
catch (final IOException e) {
e.printStackTrace();
}
}
return null;
}
}
新增HystrixConfig斷路器的面板類
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.common.config;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.configuration
* @className: HystrixConfiguration.java
* @description: 斷路器信息控制面板數據
* @author: OprCalf
* @date: 2020年3月5日
*/
@Configuration
public class HystrixConfig {
/**
* 普通斷路器信息
* @author OprCalf
* @return ServletRegistrationBean<HystrixMetricsStreamServlet>
*/
@Bean(name = "hystrixRegistrationBean")
public ServletRegistrationBean<HystrixMetricsStreamServlet> servletRegistrationBean() {
final ServletRegistrationBean<HystrixMetricsStreamServlet> registration = new ServletRegistrationBean<>(
new HystrixMetricsStreamServlet(), "/hystrix.stream");
registration.setName("hystrixServlet");
registration.setLoadOnStartup(1);
return registration;
}
/**
* 給turbine使用的斷路器信息,跟上面一樣的
* @author OprCalf
* @return ServletRegistrationBean<HystrixMetricsStreamServlet>
*/
@Bean(name = "hystrixForTurbineRegistrationBean")
public ServletRegistrationBean<HystrixMetricsStreamServlet> servletTurbineRegistrationBean() {
final ServletRegistrationBean<HystrixMetricsStreamServlet> registration = new ServletRegistrationBean<>(
new HystrixMetricsStreamServlet(), "/actuator/hystrix.stream");
registration.setName("hystrixForTurbineServlet");
registration.setLoadOnStartup(1);
return registration;
}
}
在啓動類
PlatformGatewayApplication新增註解@EnableHystrixDashboard
這個註解是啓動斷路器的監控面板,待會我們可以看到斷路器的工作情況。
最後,我們在配置文件application.yml上,添加斷路器及監控的一些信息
######健康監控配置###################################
#開放所有頁面節點 默認只開啓了health、info兩個節點
management.endpoints.web.exposure.include: "hystrix.stream,health,info,routes"
#顯示健康具體信息 默認不會顯示詳細信息
management.endpoint.health.show-details: always
######斷路器設置###################################
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000
#設置Hystrix隔離策略爲線程池
zuul.ribbonIsolationStrategy: thread
#每個路由使用獨立的線程池
zuul.threadPool.UseSeparateThreadPools: true
至此,項目完成,我們來繼續我們的實驗。
限流測試:啓用200個線程來請求,如果沒效果,調成300
限流測試
從圖中可以看到,當超過一定限制的時候,會直接返回我們的“訪問量巨大”的提示,證明限流器起作用了。
斷路測試:把後端的原子服務停掉
斷路測試
從圖片中,能直接看到,後端服務停掉的時候,直接返回“找不到服務”的信息。斷路器起作用了。
斷路器監控:訪問localhost:8012/hystrix,並輸入
localhost:8012/hystrix.stream
斷路器監控面板
服務斷路面板
這樣就可以直接看到服務的調用情況。
備註:實際中這個很少用到,一般只用到他的接口做定製化的斷路監控,畢竟,太醜了些。
最後,我們做個總結:zuul的斷路及限流,限流這裏可以結合redis來做,但是如果是小項目的話,並且不需要對網關做高可用的話,直接用谷歌的限流器來做也行的,要因地制宜,切記不要生搬硬套,不要人云亦云。
最後,謝謝觀賞,覺得好的話,點個贊,有什麼問題可以留言溝通,麼麼噠。