Springboot注解,SpringCloud(6):Feign與Zuul詳解

 2023-11-23 阅读 31 评论 0

摘要:1 Feign的使用 案例接著 Hystrix案例 Springboot注解、在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠程調用時的代碼: String baseUrl = "http://user-service/user/"; User user = this.restTemplate.getForObject(base

1 Feign的使用

案例接著 Hystrix案例

Springboot注解、在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠程調用時的代碼:

String baseUrl = "http://user-service/user/";
User user = this.restTemplate.getForObject(baseUrl + id, User.class)

需要4點:提供服務的地址http://user-service/user/ ,參數id,請求方式get,返回類型User

如果就學到這里,你可能以后需要編寫類似的大量重復代碼,格式基本相同,無非參數不一樣。有沒有更優雅的方式,來對這些代碼再次優化呢?這就是我們接下來要學的Feign的功能了。
Feign可以把Rest的請求進行隱藏,偽裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數等等操作,一切都交給Feign去做。

在服務調用者user-consumer中操作

1)添加依賴

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

2)修改啟動類

@EnableFeignClients //開啟feign功能
@SpringCloudApplication  //點進去看看,抵三個注解
public class ConsumerApplicationStarter {public static void main(String[] args) {SpringApplication.run(ConsumerApplicationStarter.class);}
}

3)編寫UserClient接口

//去eureka拉取服務,使用負載均衡來挑選
//支持hystrix,但是默認是關閉的   寫法比較麻煩可以不使用
@FeignClient("user-service")
public interface UserClient {@GetMapping("user/{id}")User findUserById(@PathVariable("id") Long id);
}

這與上面的四點剛好相對應

  • 首先這是一個接口,Feign會通過動態代理,幫我們生成實現類。這點跟mybatis的mapper很像
  • @FeignClient,聲明這是一個Feign客戶端,類似@Mapper注解。同時通過value屬性指定服務名稱
  • 接口中的定義方法,完全采用SpringMVC的注解,Feign會根據注解幫我們生成URL,并訪問獲取結果

4)修改ConsumerController

@RestController
@RequestMapping("consumer")
public class ConsumerController {@Autowiredprivate UserClient userClient;@GetMapping("{id}")public User queryById(@PathVariable("id") Long id) {return userClient.findUserById(id);}
}

RestTemplate的注冊被我刪除了。Feign中已經自動集成了Ribbon負載均衡,因此我們不需要自己定義RestTemplate了

補充: Feign默認也有對Hystix的集成,只不過,默認情況下是關閉的。我們需要通過下面的參數來開啟:

feign:hystrix:enabled: true  #feign  開啟熔斷 默認關閉
ribbon:ConnectionTimeout: 500 #Feign的負載均衡時長   拋出異常ReadTimeout: 2000 #超過兩秒沒讀取到數據  拋出異常

配置會比較麻煩,先要修改UserClient,添加fallBack屬性

@FeignClient(value = "user-service",fallback = UserClientFallback.class)
public interface UserClient {@GetMapping("user/{id}")User findUserById(@PathVariable("id") Long id);
}

添加UserClient 的實現類,實現findUserById方法

@Component
public class UserClientFallback implements UserClient {@Overridepublic User findUserById(Long id) {User user = new User();user.setUsername("用戶未知");return user;}
}

既然能自己寫Hystrix那就可以不使用Feign的Hystrix了。

主要功能還是簡化遠程調用,其他的小功能不再贅述。

2 網關Zuul

2.1 Zuul的功能介紹

服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。

在這里插入圖片描述

Zuul的官網

2.2 加入Zuul后的架構

在這里插入圖片描述

不管是來自于客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然后再由網關來實現 鑒權、動態路由等等操作。Zuul就是我們服務的統一入口。

從圖中能夠看出Zuul具有的功能有,最基本的路由,權限校驗,負載均衡,下面將一一來介紹與使用。

2.3 Zuul快速入門

創建gateway模塊

1)添加依賴 pom.xml內容如下

<parent><artifactId>spring-cloud-demo</artifactId><groupId>com.scu</groupId><version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion><artifactId>gateway</artifactId>
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>
</dependencies>

2)創建啟動類

@SpringBootApplication
@EnableZuulProxy  //啟用zuul
public class ZuulApplication {public static void main(String[] args) {SpringApplication.run(ZuulApplication.class);}
}

3)添加配置文件 application.yml

server:port: 10086   #端口號
spring:application:name: gateway  #服務名稱
zuul:routes:xixi:  #路徑id  隨意寫path: /user-service/**   #映射路徑url: http://127.0.0.1:8083  #映射路徑對應的url地址

啟動項目后,當頁面訪問http://localhost:10086/user-service/user/1,之后路由地址為http://127.0.0.1:8083/user/1,即:將符合path 規則的一切請求,都代理到 url參數指定的地址,本例中,我們將 /user-service/**開頭的請求,代理到http://127.0.0.1:8083

在這里插入圖片描述

2.4 Zuul面向服務的路由

在剛才的路由規則中,我們把路徑對應的服務地址寫死了!如果同一服務有多個實例的話,這樣做顯然就不合理了。我們應該根據服務的名稱,去Eureka注冊中心查找 服務對應的所有實例列表,然后進行動態路由才對!

1)添加eureka依賴

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

2)修改配置文件

server:port: 10086
spring:application:name: gateway
eureka:client:service-url:defaultZone: http://127.0.0.1:10001/eureka  #zuul也注冊到eurekainstance:prefer-ip-address: trueip-address: 127.0.0.1
zuul:routes:xixi:path: /user-service/**serviceId: user-service   #服務提供方Id   zuul自動負載均衡

注意:似乎啟動類上不加@EnableDiscoveryClient也行,但我還是加了,也不影響。

2.5 Zuul簡化路由配置和默認配置

如果覺得上面寫的麻煩, 那么可以進行簡化,如下

zuul:routes: #服務id: 映射路徑           默認配置也是這樣user-service: /user-service/**

在這里插入圖片描述
上面這種配置也是默認的配置,所以即使我們不配置,也能訪問到。那么我們就來試試訪問user-consumer這個微服務

在這里插入圖片描述

正常訪問,實際上我們也沒有自己去配置。zuul會根據服務id來幫助我們提供這種配置。但是如果你希望忽略掉部分 服務id: 映射路徑 那么該怎么做呢?另外希望加路由前綴怎么做呢?

zuul:routes: #服務id 映射路徑           默認配置也是這樣user-service: /user-service/**prefix: /api   #路由前綴ignored-services:   #忽略掉的服務- consumer-service

在這里插入圖片描述
注意:這里加了前綴 /api 由于忽略掉了consumer-service服務,再來訪問下

在這里插入圖片描述

2.6 Zuul過濾器

Zuul作為網關的其中一個重要功能,就是實現請求的鑒權。而這個動作我們往往是通過Zuul提供的過濾器來實現的。

2.6.1 ZuulFilter

ZuulFilter是過濾器的頂級父類。在這里我們看一下其中定義的4個最重要的方法:

public abstract ZuulFilter implements IZuulFilter{abstract public String filterType();abstract public int filterOrder();boolean shouldFilter();// 來自IZuulFilterObject run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
  • run:過濾器的具體業務邏輯。
  • filterType:返回字符串,代表過濾器的類型。包含以下4種:
    • pre:請求在被路由之前執行
    • routing:在路由請求時調用
    • post:在routing和errror過濾器之后調用
    • error:處理請求時發生錯誤調用
  • filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。

2.6.2 過濾器生命周期

在這里插入圖片描述

  • 正常流程:
    • 請求到達首先會經過pre類型過濾器,而后到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果后,會到達post過濾器。而后返回響應。
  • 異常流程:
    • 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,再error處理完畢后,會將請求交給POST過濾器,最后返回給用戶。
    • 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而后返回。
    • 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,請求不會再到達POST過濾器了。

景非常多:

  • 請求鑒權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了
  • 異常處理:一般會在error類型和post類型過濾器中結合來處理。
  • 服務調用時長統計:pre和post結合使用。

2.6.3 自定義過濾器的使用

我們來自定義一個過濾器,模擬一個登錄的校驗。基本邏輯:如果請求中有access-token參數,則認為請求有效,放行。

package com.scu.filter;import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Component
public class LoginFilter extends ZuulFilter{@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;//pre}@Overridepublic int filterOrder() {//至少要確保拿到了參數 5 - 1return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;}@Overridepublic boolean shouldFilter() {return true;//返回true 使用攔截器}@Overridepublic Object run() throws ZuulException {//獲取上下文RequestContext ctx = RequestContext.getCurrentContext();//獲取requestHttpServletRequest request = ctx.getRequest();String token = request.getParameter("access-token");if(StringUtils.isBlank(token)){//登錄校驗失敗   攔截 默認是truectx.setSendZuulResponse(false);//返回403狀態碼 禁止訪問ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());}return null;}
}

在這里插入圖片描述
那么加上access-token參數試試,訪問正常
在這里插入圖片描述

2.7 Zuul的負載均衡與熔斷

Zuul中默認就已經集成了Ribbon負載均衡和Hystix熔斷機制。但是所有的超時策略都是走的默認值,比如熔斷超時時間只有1S,很容易就觸發了。因此建議我們手動進行配置:

hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 3000
ribbon:ConnectionTimeout: 500ReadTimeout: 2000 #500*2+2000兩個加起來*2不能超過timeoutInMillisecond
#  MaxAutoRetriesNextServer: 0 #不重試

重啟后頁面訪問,查看日志,注意到warn的那行

2019-04-09 20:46:38.014  WARN 15792 --- [io-10086-exec-1] o.s.c.n.z.f.r.s.AbstractRibbonCommand    : The Hystrix timeout of 3000ms for the command user-service is set lower than the combination of the Ribbon read and connect timeout, 6000ms.

說的是hystrix的超時時間3000ms(針對user-service服務,實際上像上面那樣寫是針對所有服務的超時時間都是3000ms,如果是只針對user-service服務那么需要在之前補充user-service:后面不變),小于ribbon的讀和連接超時時間6000毫秒

所以將hystrix的超時時間設置為6000ms吧,關于這個時間怎么計算的,查看AbstractRibbonCommand類的下方代碼

			int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout",IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout",IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries",IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES);int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer",IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER);ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/187244.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息