springcloud-zuul

一、簡介

Zuul 是一個基於JVM路由和服務的負載均衡器,提供路由、監控、安全等方面的服務框架。Zuul能夠與Eureka、Ribbon、Hystrix等組件配合使用。

Zuul的核心是過濾器,通過這些過濾器我們可以擴展出很多功能,例如:

  • 動態路由:動態的將客戶端的請求路由到後端不同的服務,做一些邏輯處理,比如聚合多個服務的數據返回。
  • 請求監控:可以對整個系統的請求進行監控,記錄詳細的請求響應日誌,可以試試統計出當前系統的訪問量以及監控狀態。
  • 認證鑑權:對每一個訪問的請求做認證。拒絕非法請求,保護好後端服務。
  • 壓力測試:通過Zuul可以動態的將請求轉發到後端服務的集羣中,還可以識別測試流量和真實流量,從而做一些特殊處理。
  • 灰度發佈:灰度帆布可以保證整體系統的穩定,在初始灰度時就可以發現,調整問題,以保證其影響度。

二、簡單使用

1. 引用依賴

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

2. 在配置 文件中添加配置信息

spring.application.name=zuul-demo
server.port=2103
#通過zuul.routes配置路由轉發 testzuul是自定義的名稱,當訪問 /testzuul/**地址時就會跳轉到http://baidu.com/
zuul.routes.testzuul.path=/testzuul/**
zuul.routes.testzuul.url=http://baidu.com/

3. 在啓動類加入 @EnableZuulProxy 註解

啓動應用後 訪問 http://localhost:2103/testzuul 就會跳轉到 百度。

三、 集成 Eureka

1. 引入依賴

     <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

2. 在配置文件中添加配置

eureka.client.serviceUrl.defaultZone=XXXX

注意:因爲 @EnableZuulProxy 中已經自帶了 @EnableDiscoveryClient 所以在啓動類中不用再加Eureka 的註解。

重啓服務,我們可以通過默認的轉發規則來訪問Eureka中的服務,訪問規則是:“API網關地址+訪問的服務名稱+接口URI”,例如 :http://localhost:2103/ribbon/test/fegin?userName=113

四、Zuul路由配置

Zuul的默認的轉發規則 “API網關地址+訪問的服務名稱+接口URI”,在給服務制定名稱時應該儘量短點,這樣就可以使用默認的路由會澤進行請求,不需要爲每個服務都定一個路由規則,默認規則舉例:

API網關地址:http://localhost:2103

用戶服務名稱:ribbon

用戶接口:/test/fegin?userName=113

那麼通過Zuul的訪問登錄接口的規則就是:http://localhost:2103/ribbon/test/fegin?userName=113

1. 指定具體服務路由:

zuul.routes.ribbon.path=/user-service/**

這裏將服務名稱爲 ribbon的服務配置爲user-service,也就是說想訪問 ribbon服務的接口要通過 user-service訪問。

 注意:配置文件中的 /user-services/** 這裏一定要是兩個 *  ,如果配置一個 * 就只能轉發一級,兩個表示可以轉發任意層級的URL  

 2. 配置路由前綴

zuul.prefix=/rest

配置了路由前綴後要訪問前加你配置的val,例如我配置的rest,例如:http://localhost:2103/rest/user-service/test/fegin?userName=%E2%80%981111%E2%80%99

3. 本地跳轉

zuul.routes.local.path=/api/**
zuul.routes.local.url=forward:/local

配置後,重啓服務訪問 http://localhost:2103/rest/api/1 自動跳轉本地的local的接口中。

五、Zuul過濾器

1. 過濾器的類型

  • pre: 可以在請求被路由之前調用,適用於身份認證的場景,認證通過後再繼續執行下面的流程。
  • route:在路由請求時被調用,適用於灰度發佈場景,在將要路由的時候可以做一些自定義的邏輯。
  • post:在route 個error 過濾之後被調用,這種過濾器將請求路由到達具體的服務之後執行,適用於需要添加響應頭、記錄響應日誌等應用場景。
  • error:處理請求時發生錯誤時被調用,在執行過程中發送錯誤時會進入error過濾器,可以用來統一記錄錯誤信息。

整個執行順序是,請求發過來首先到 pre 過濾器,然後到 routing 過濾器 最後到post 過濾器,任何一個過濾器有異常都會進入到 error過濾器。

2. 過濾器的使用

過濾器的創建

public class IpFilter extends ZuulFilter {

    private List<String> blackIpList = Arrays.asList("127.0.0.1");

    public IpFilter() {
        super();
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    //true爲過期生效 false 爲過濾器不執行
    @Override
    public boolean shouldFilter() {
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        String ip = getIpAddr(ctx.getRequest());
        if(StringUtils.isNotBlank(ip) && blackIpList.contains(ip)){
            //禁止轉發
            ctx.setSendZuulResponse(false);
            ctx.setResponseBody("非法請求");
            //禁止本地轉發
            ctx.set("sendForwardFilter.ran",true);
            ctx.getResponse().setContentType("application/json;charset=utf-8");
            //過濾器中傳遞數據
            ctx.set("msg","hello---------");
            ctx.set("isSuccess" ,true);
            return null;
        }
        return null;
    }

}

說明:

  • shouldFilter:是否執行該過濾器,true 爲執行,false 爲不執行,這個也可以利用配置中心的配置來實現,達到動態的開啓和關閉過濾器。filterType:過濾器類型,可選值有pre、route、post、error。
  • filterOrder:過濾器執行的順序,數值越小,優先級越高。
  • run:執行自己的業務邏輯。

過濾器定義完成之後需要配置過濾器才能生效。

@Configuration
public class FilterConfig {

    @Bean
    public IpFilter ipFilter(){
        return new IpFilter();
    }

}

過濾器中的異常處理

ublic class ErrorFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(ErrorFilter.class);


    public ErrorFilter() {
        super();
    }

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        Throwable throwable = ctx.getThrowable();
        logger.error("Filter Error :{}",throwable.getCause().getMessage());
        return null;
    }
}
@Configuration
public class FilterConfig {

    @Bean
    public ErrorFilter errorFilter(){
        return new ErrorFilter();
    }
}

再創建一個error過濾器之後,我們需要在其他額過濾器中製造一個異常,我選擇在 上面的pre的過濾器中 加入下面的代碼製造一個異常

 System.out.println(2/0);

重啓應用訪問接口時會出現下圖所示的界面

 我們可以自定義異常的返回格式

@RestController
public class ErrorHandlerController implements ErrorController {

    private ErrorAttributes errorAttributes;

    @Autowired
    public ErrorHandlerController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }


    @Override
    public String getErrorPath() {
        return "/error";
    }

    @GetMapping("/error")
    public String error(HttpServletRequest request){
        Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
        String message = (String) errorAttributes.get("message");
        String trace = (String) errorAttributes.get("trace");
        if(StringUtils.isNotBlank(trace)){
            message += String.format("and trace %s",trace);
        }
        return message;
    }
}

再次重啓應用,訪問同樣的接口,返回的格式如下:

Zuul的容錯機制

 引入依賴

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

配置文件添加如下配置:

#開啓重試
zuul.retryable=true
#請求連接的超時時間
ribbon.connectTimeOut=500
#請求處理的超時時間
ribbon.readTimeOut=5000
#對當前實例的重試次數
ribbon.maxAutoRetries=1
#切換實例的最大重試次數
#ribbon.maxAutoRetriesNextServer=3
#對所有操作請求都進行重試
ribbon.okToRetryOnAllOperations=true
#對制定的Http響應碼進行重試
ribbon.retryableStatusCode=500,404,502

Zuul回退機制

在springcloud中Zuul默認整合了Hystrix,當後端服務異常時可以爲 Zuul 添加回退功能,返回默認的數據給客戶端。

實現回退機制需要實現  FallbackProvider 接口,具體代碼如下:

@Component
public class ServiceConsumerFallbackProvider implements FallbackProvider {

    private Logger logger = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                if(null != cause){
                    logger.error("",cause.getCause());
                }
                RequestContext ctx = RequestContext.getCurrentContext();
                return new ByteArrayInputStream("服務器內部錯誤".getBytes());
            }

            @Override
            public HttpHeaders  getHeaders() {
                MediaType mediaType = new MediaType("application","json", Charset.forName("UTF-8"));
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }
        };
    }
}

getRoute 方法中法返回 * 代表對所有服務進行回退操作,如果只想對某個服務進行回退操作,那麼就返回需要回退的服務名稱,這個名稱一定要是註冊到 Eureka 中的名稱。

通過 ClientHttpResponse 構造回退的內容,通過getStatusCode 返回響應的狀態碼,通過getStatusText 返回響應狀態碼對應的文本,通過getBody返回回退的內容。通過getHeaders 返回響應的請求頭信息。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章