【Spring Cloud 系列 六】Zuul 构建微服务网关
一 为什么需要使用微服务网关
当使用微服务架构时,我们会将单体应用拆分成很多小的服务,每个服务一般都会有不同的网络地址,而客户端可能需要调用多个服务接口才能完成一个业务需求,这样客户端就会直接与各个服务进行通信,造成以下可能出现的问题:
- 客户端多次请求不同的微服务,增加了客户端的复杂性
- 存在跨域请求,在一定场景下处理相对复杂
- 认证复杂,每个服务都需要独立认证
- 难以重构,随着项目的迭代,可能需要重新划分微服务,那么客户端如果直接和服务进行通信,那么将会很难进行重构
以上问题都可以通过加入网关来解决,网关是介于客户端和微服务端之间的中间层,所有的外部请求都需要先经过微服务网关,然后由网关在进行进一步的处理。封装了应用程序的内部结构,客户端只需要与网关打交道。
二 Zuul 简介
Zuul 是 Netflix 开源的微服务网关,可以和 Eureka、Ribbon、Hystrix 等组件配合使用。Zuul 的核心是提供了一些列的过滤器,完成下列功能:
- 身份认证与安全:识别每个资源的验证要求,并拒绝那些不符合要求的请求
- 审查与监控:在边缘位置追踪有意义的数据和统计结果
- 动态路由
- 压力测试:为每一种负载类型分配相应容量,并启用超出限定值的请求
- 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群
Spring Cloud 对 Zuul 进行了整合与增强,Zuul 目前使用的默认HTTP 客户端是 Apache HTTP client,也可以设置使用 RestClient 或者 okhttp3.OKHttpClient,只需要设置对应的配置ribbon.restclient.enabled=true
、ribbon.okhttp.enabled=true
三 编写 Zuul 微服务网关
1 创建一个新项目(microservice-gateway-zuul
),并添加相关依赖
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-zuul', version:'1.4.0.RELEASE'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client', version:'1.4.0.RELEASE'
2 在启动类上添加注解@EnableZuulProxy
,声明一个代理,该代理同时还整合了 Ribbon 和 Hystrix
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
3 编写配置文件application.yml
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
4 启动相关的服务
- microservice-discovery-eureka
- mircroservice-provider-user
- microservice-consumer-movie-ribbon
- microservice-gateway-zuul
当访问 http://localhost:8040/microservice-consumer-movie/user/1
时,请求会被转发到 mocroservice-consumer-movie-ribbon
服务中的 /user/1
接口。
四 路由配置详解
在上个demo中我们对不同的service的 application name
进行了代理,我们还可以使用 Zull 代理部分服务,或者对 URL 进行更加准确的控制
自定义指定微服务的访问路径
配置 zuul.routes.「指定微服务的 serviceId」= 指定路径,例如
zuul:
routes:
microservice-provider-user: /user/**
通配符 | 含义 | 举例 | 解释 |
---|---|---|---|
? | 匹配任意单个字符 | /feign-consumer/? | 匹配/feign-consumer/a,/feign-consumer/b,/feign-consumer/c等 |
* | 匹配任意数量的字符 | /feign-consumer/* | 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,无法匹配/feign-consumer/a/b/c |
** | 匹配任意数量的字符 | /feign-consumer/* | 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,也可以匹配/feign-consumer/a/b/c |
忽略指定微服务
zuul:
ignored-services: microservice-provider-user,microservice-consumer-movie // 多个服务使用 , 分割
忽略所有服务,只路由指定的服务
zuul:
ignored-services: '*' # 使用'*'可忽略所有微服务
routes:
microservice-provider-user: /user/**
同时指定服务的 serviceId 和 对应路径
zuul:
routes:
user-route: # 该配置方式中,user-route只是给路由一个名称,可以任意起名。
service-id: microservice-provider-user
path: /user/** # service-id对应的路径
同时指定 path 和 URL
zuul:
routes:
user-route: # 该配置方式中,user-route只是给路由一个名称,可以任意起名。
url: http://localhost:8000/ # 指定的url
path: /user/** # url对应的路径。
这种配置方式的路由 Hystrix
和 Ribbon
都不会工作
同时指定 URL 和 path,并且 Hystrix
和 Ribbon
可以正常工作
zuul:
routes:
user-route:
path: /user/**
service-id: microservice-provider-user
ribbon:
eureka:
enabled: false # 禁用掉ribbon的eureka使用。详见:http://cloud.spring.io/spring-cloud-static/Camden.SR3/#_example_disable_eureka_use_in_ribbon
microservice-provider-user:
ribbon:
listOfServers: localhost:8000,localhost:8001
路由前缀
zuul:
prefix: /api
strip-prefix: false
routes:
microservice-provider-user: /user/**
当访问 Zuul 的 /api/mocroservice-provide-user/1
时会被转发到 microservice-provider-user
的 /api/1
忽略某些路径
zuul:
ignoredPatterns: /**/admin/** # 忽略所有包括/admin/的路径
routes:
microservice-provider-user: /user/**
五 Zuul 的安全与 Header
敏感 Header 的设置
不同服务之间通信时会共享 Header,可以对敏感的 Header 进行设置,避免外泄
zuul:
routes:
user-route:
path: /user/**
service-id: microservice-provider-user
sensitive-headers: Cookie, Set-Cookie ...
忽略 Header
zuul:
ignored-headers: Header 1, header 2
默认情况下,zuul.ignored-headers 是空值,但如果项目中存在 Spring security
的依赖,那么 zuul.ignored-headers 就会存在一些header,如果我们需要使用下游服务中的 header 时,需要将 zuul.ingoredSecurity-Headers
设置为 false
六 Zuul 的容错与回退
在 Spring Cloud 中,Zuul 已经默认整合了 hystrix。想要为 Zuul 添加回退,需要实现 ZuulFallbackProvider
接口,在实现类中,指定为哪个服务提供回退,并提供一个 ClientResponse
作为回退响应,例如:
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
// 表明是为哪个微服务提供回退,*表示为所有微服务提供回退
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return this.fallbackResponse();
}
}
@Override
public ClientHttpResponse fallbackResponse() {
return this.response(HttpStatus.INTERNAL_SERVER_ERROR);
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.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;
}
};
}
}