八、Zuul構建微服務閘道器
1、為什麼要使用微服務閘道器
1.1、沒有閘道器的話,存在的問題
- 客戶端要多次請求不同微服務,增加客戶端複雜性
- 存在跨域請求問題
- 認證複雜,每個專案都要單獨一套認證
- 難以重構,如果微服務做修改,所有呼叫的客戶端都要改一遍
- 某些微服務使用防火牆/不友好協議,直接訪問比較困難
使用微服務閘道器就可以解決以上問題
1.2、使用微服務閘道器的有點
- 易於監控
- 易於認證,不需要每個微服務都進行認證
- 減少客戶端與微服務端互動次數
2、Zuul簡介
Zuul是微服務閘道器外掛,本身已經集成了Ribbon和Hystrix,並且可以和Eureka配合使用。Zuul的核心是一系列過濾器,這些過濾器可以完成以下功能:
- 身份認證與安全
- 審查和監控
- 負載分配和動態路由:動態分配到不同的後端叢集
- 靜態響應處理
3、編寫Zuul微服務閘道器
3.1、新增依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
3.2、啟動類添加註解@EnableZuulProxy
宣告Zuul代理,已經整合Ribbon定位微服務,整合Hystrix實現容錯
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
3.3、編寫配置檔案application.yml
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
4、Zuul的路由端點
@EnableZuulProxy與SpringBootActuator配合使用時,Zuul會暴露一個路由管理端點/routes。直觀地檢視及管理Zuul的路由。(zuul裡面已經整合了actuator,所以不需要再增加依賴)
訪問http://localhost:8040/routes,即可獲得如下結果
{
"/microservice-provider-user/**" : "microservice-provider-user",
"/microservice-consumer-movie/**": "microservice-consumer-movie"
}
5、路徑配置詳解
5.1、自定義指定微服務的訪問路徑
zuul:
routes:
microservice-provider-user: /user/**
只要是/user/**的請求都會自動在microservice-provider-user對應的服務中請求
5.2、忽略指定微服務
zuul:
ignored-services: microservice-provider-user,microservice-consumer-movie
#zuul:
# ignored-services: '*' #忽略所有,只路由指定微服務
# routes:
# microservice-provider-user: /user/**
5.3、同時指定微服務的serviceId和對應路徑
zuul:
routes:
user-route: #任意起的名字,作為該配置的標識
service-id: provider-microservice-user
path: /user/**
本例配置效果同5.1
5.4、同時指定path和URL
zuul:
routes:
user-route: #任意起的名字,作為該配置的標識
url: http://localhost:8000/
path: /user/**
6、Zuul的安全與Header
7、Zuul上傳檔案
8、Zuul的過濾器
過濾器是Zuul的核心元件
8.1、過濾器的型別與請求生命週期
- pre:這種過濾器在請求路由之前被呼叫,可做身份驗證
- routing:將請求路由到微服務,用於構建傳送給微服務的請求,使用HttpClient/Ribbon
- post:在路由到微服務以後執行,可為響應新增指定內容
- error:在其他階段發生錯誤時執行該過濾器
8.2、編寫Zuul過濾器
8.2.1、過濾器類程式碼
public class PreRequestLogFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);
private static int n = 1;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; //過濾器型別
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;//過濾器執行順序
}
@Override
public boolean shouldFilter() {
return true; //過濾器是否執行
}
@Override
public Object run() { //過濾器業務邏輯,該例列印了請求的HTTP方法及請求地址
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
PreRequestLogFilter.LOGGER.info("——————————————————————攔截到第"+(n++)+"次請求!");
return null;
}
}
- filterType:過濾器型別——pre、route、post、error
- filterOrder:返回int值來指定過濾器的執行順序,不同過濾器可相同數字
- shouldFilter:返回boolean確定該過濾器是否執行
- run:過濾器具體的業務邏輯
8.2.2、啟動類新增
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
public PreRequestLogFilter preRequestLogFilter() {
return new PreRequestLogFilter();
}
}
8.3、禁用過濾器
zuul.<SimpleClassName>.<filterType>.disable=true
例如:zuul.PreRequestLogFilter.pre.disable=true
9、Zuul的容錯與回退
Zuul中已經預設整合了Hystrix
9.1、編寫Zuul的回退類
@Component
public class UserFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
// 表明是為哪個微服務提供回退
return "microservice-provider-user";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// fallback時的狀態碼
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
// 數字型別的狀態碼,本例返回的其實就是200,詳見HttpStatus
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
// 狀態文字,本例返回的其實就是OK,詳見HttpStatus
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 響應體
return new ByteArrayInputStream("使用者微服務不可用,請稍後再試。".getBytes());
}
@Override
public HttpHeaders getHeaders() {
// headers設定
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}
之後,當microservice-provider-user微服務無法正常響應時,將返回——“使用者微服務不可用,請稍後再試”
10、Zuul的高可用
啟動多個Zuul的閘道器服務,均註冊到EurekaServer中即可。然後前置nginx將請求分發到各個閘道器即可!
11、使用Sidecar整合非JVM微服務
12、使用zuul聚合微服務
如果客戶端需要訪問多個微服務,如果通過閘道器分別呼叫、分別返回的話,網路開銷、耗時、流量消耗都會很不好。這時我們可以在Zuul整合客戶端的請求,客戶端只給Zuul閘道器傳送一個請求,閘道器中分別請求各個微服務,然後給客戶端返回一個最終結果!
12.1、啟動類程式碼
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
12.2、Service層程式碼
@Service
public class AggregationService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public Observable<User> getUserById(Long id) {
// 建立一個被觀察者
return Observable.create(observer -> {
// 請求使用者微服務的/{id}端點
User user = restTemplate.getForObject("http://microservice-provider-user/{id}", User.class, id);
observer.onNext(user);
observer.onCompleted();
});
}
@HystrixCommand(fallbackMethod = "fallback")
public Observable<User> getMovieUserByUserId(Long id) {
return Observable.create(observer -> {
// 請求電影微服務的/user/{id}端點
User movieUser = restTemplate.getForObject("http://microservice-consumer-movie/user/{id}", User.class, id);
observer.onNext(movieUser);
observer.onCompleted();
});
}
public User fallback(Long id) {
User user = new User();
user.setId(-1L);
return user;
}
}
12.3、Controller層程式碼
@RestController
public class AggregationController {
public static final Logger LOGGER = LoggerFactory.getLogger(ZuulApplication.class);
@Autowired
private AggregationService aggregationService;
@GetMapping("/aggregate/{id}")
public DeferredResult<HashMap<String, User>> aggregate(@PathVariable Long id) {
Observable<HashMap<String, User>> result = this.aggregateObservable(id);
return this.toDeferredResult(result);
}
public Observable<HashMap<String, User>> aggregateObservable(Long id) {
// 合併兩個或者多個Observables發射出的資料項,根據指定的函式變換它們
return Observable.zip(
this.aggregationService.getUserById(id),
this.aggregationService.getMovieUserByUserId(id),
(user, movieUser) -> {
HashMap<String, User> map = Maps.newHashMap();
map.put("user", user);
map.put("movieUser", movieUser);
return map;
}
);
}
public DeferredResult<HashMap<String, User>> toDeferredResult(Observable<HashMap<String, User>> details) {
DeferredResult<HashMap<String, User>> result = new DeferredResult<>();
// 訂閱
details.subscribe(new Observer<HashMap<String, User>>() {
@Override
public void onCompleted() {
LOGGER.info("完成...");
}
@Override
public void onError(Throwable throwable) {
LOGGER.error("發生錯誤...", throwable);
}
@Override
public void onNext(HashMap<String, User> movieDetails) {
result.setResult(movieDetails);
}
});
return result;
}
}