1.为什么要使用微服务网关
在前文内容的基础上,我们已经可以通过Spring Cloud集成的一系列组件(Eureka、Ribbon、Feign、Hystrix)构建初步的微服务系统了,但目前构建的微服务系统还不够完善。
1.1 客户端直接访问多个微服务
通常客户端(尤其是外部客户端,如手机APP)需要调用多个微服务才能完成一个业务需求,客户端需要与多个微服务通信,会存在多个问题。
- 客户端请求不同的微服务,增加复杂度;
- 可能存在跨域请求,增加处理难度;
- 每个微服务都需要单独认证;
- 微服务重构(如微服务合并或拆分)困难;
- 需要保证客户端与所有微服务的网络连通性。
![](https://img.haomeiwen.com/i1643904/98520745d85bfd10.png)
1.2 使用微服务网关
使用微服务网关封装微服务应用,客户端仅跟网关交互,无需直接请求微服务接口,开发和维护都会得到简化。
- 易监控,可以在网关收集监控数据,还可以集中推送至外部系统进行分析;
- 易认证,可以统一在网关实现认证,各个微服务无须单独认证;
- 简化网络要求,仅需要保证客户端与网关的连通性,及网关与各个微服务的连通性,微服务不需要暴露在公网之下。
![](https://img.haomeiwen.com/i1643904/2c877ee66d078202.png)
2.Zuul
Spring Cloud Netflix中的Zuul通过服务网关统一对外提供REST API,具备服务路由、均衡负载、权限控制等功能,使得服务集群主体能够具备更高的可复用性和可测试性,并且为微服务架构提供了前置的保护和控制能力。
2.1 默认Zuul网关
只需要简单几步,zuul就能集成feign构建服务网关,无需配置就具有默认的路由功能。
Step 1:在之前的spring-cloud-hystrix项目基础上稍作修改,使用Spring Initializr创建一个新的modulezuul-server
整个项目下的module结构如下
spring-cloud-demo/
├── eureka-client-consumer
├── eureka-client-provider
├── eureka-server
├── hystrix-dashboard
└── zuul-server
Step 2:添加eureka及zuul依赖
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Finchley.RELEASE'
}
}
dependencies {
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
compile('org.springframework.boot:spring-boot-starter')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Step 3:修改Spring Boot启动类,添加@EnableEurekaClient及@EnableZuulProxy注解
@SpringBootApplication
@EnableZuulProxy
class ZuulApplication
fun main(args: Array<String>) {
runApplication<ZuulApplication>(*args)
}
Step 4:在application.yml中添加相关属性
server:
port: 8080
spring:
application:
name: zuul-server
eureka:
client:
service-url:
defaultZone: http://localhost:10001/eureka/
# 以下超时时间必须设置(默认时间太短,会导致第一次请求超时)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
ribbon:
ConnectTimeout: 10000
ReadTimeout: 10000
Step 5:依次启动以下应用
eureka-server:10001
eureka-client-provider:20001
eureka-client-provider:20002
zuul-server:8080
hystrix-dashboard:9090
Step 6:测试验证
- 1.请求http://localhost:8080/eureka-client-provider/test,被转发到http://localhost:20001/test;
- 2.多次请求,会交替地转发至http://localhost:20001/test及http://localhost:20002/test;
- 3.访问http://localhost:9090/hystrix-dashboard/, 输入http://localhost:9090/hystrix.stream,能够进入Hystrix Dashboard的监控页面,页面以服务为粒度显示监控数据。
Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: eureka-client-provider
2.2 Zuul的基本功能
-
路由规则
默认情况下,Zuul会代理所有注册到Eureka Server的微服务,且使用路由规则:http://ZUUL_HOST:ZUUL_PORT/serviceId/**会被转发到serviceId对应的微服务
-
负载均衡
Zuul整合了Ribbon,实现负载均衡。 -
Hystrix容错与监控
Zuul整合了Hystrix,实现与监控。
2.3 Zuul路由配置
2.3.1 查看路由
当Zuul与Spring Boot Actuator结合使用时,Zuul会暴露一个路由管理endpoint(名为/routes),我们可以通过该endpoint方便地查看Zuul的路由映射。
Step 1:添加Spring Boot Actuator依赖
dependencies {
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
compile('org.springframework.boot:spring-boot-starter-actuator')
}
Step 2:禁用security(否则会无法访问/routes)
说明:也可以添加Spring Security的依赖,通过账号、密码访问/routes
management:
security:
enabled: false
Step 3:GET方式请求http://localhost:8080/routes,会返回如下结果
{"/eureka-client-provider/**":"eureka-client-provider"}
说明:POST方式请求/routes会刷新Zuul当前映射的路由列表
2.3.2 自定义路由
2.3.2.1 指定微服务的访问路径
如:指定通过/provider/**访问eureka-client-provider微服务下的所有请求。
zuul:
routes:
eureka-client-provider: /provider/**
还可以用如下形式指定(其中my-route只是一个自定义的路由名称,可以是其它名称)
zuul:
routes:
my-route:
service-id: eureka-client-provider
path: /provider/**
2.3.2.2 忽略指定微服务
如有多个微服务用,分隔,'*'可以忽略所有微服务。
如:设置Zuul网关不代理eureka-client-provider微服务。
zuul:
ignored-services: eureka-client-provider
2.3.2.3 路由前缀
zuul.prefix可以为所有路由指定一个前缀,zuul.strip-prefix指定路由转发是是否移除前缀
如:将请求http://localhost:8080/common/provider/test路由到eureka-client-provider微服务的/test
zuul:
routes:
eureka-client-provider: /provider/**
prefix: /common
strip-prefix: true
2.3.2.4 忽略某些路径
Zuul可以忽略指定的微服务,还支持忽略指定的路径,实现更小粒度的路由控制。zuul.ignorePatterns可以以正则的形式指定忽略的路径。
zuul:
routes:
eureka-client-provider: /provider/**
ignored-patterns: /**/test/**
如果希望跟踪Zuul的路由转发细节,可以将com.netflix包的日志级别设为DEBUG。
logging:
level:
com.netflix: debug
2.4 Zuul安全与Header
2.4.1 敏感Header
通常情况下,可以在同一个系统之间共享Header,不过应尽量防止一些敏感Header外泄。Zuul中可以设置敏感Header,这些header不会传播到下游微服务。
为指定微服务设置敏感Header
zuul:
route:
<指定微服务>:
sensitive-headers: header1,header2,header3
设置全局敏感Header
zuul:
sensitive-headers: header1,header2,header3
2.4.2 忽略Header
通过设置zuul.ignored-headers属性,可以丢弃一些header,这些header就不会传播到其它微服务。作用与上面的敏感Header差不多,实际上sensitive-headers会被添加到ignored-headers中。
zuul:
ignored-headers: header1,header2,header3
zuul.ignored-headers属性默认值为空,但如果引入了Spring Security,zuul.ignored-headers属性默认值就是Pragma、Cache-Control、X-Frame-Options、X-Content-Type-Options、X-XSS-Protection、Expires,此时如果需要将这些Header传递到下游微服务可以设置zuul.ignoreSecurityHeaders=false
。
2.5 Zuul上传文件
通过Zuul上传小文件,无须任何特殊处理。而通过Zuul上传10M以上的大文件,则需要在上传路径前加上/zuul前缀,也可以使用zuul.servlet-path设置自定义前缀。
另外,spring.http.multipart.max-file-size
和spring.http.multipart.max-request-size
可以设置文件上传大小限制。
2.6 Zuul过滤器
2.6.1 过滤器类型
Zuul中有4种默认类型的过滤器:
- PRE:请求路由前调用该过滤器;
- ROUTING:该过滤器将请求路由到微服务;
- POST:请求路由到微服务后执行该过滤器;
- ERROR:发生错误时执行该过滤器。
除了默认的过滤类型,Zuul还允许创建自定义的过滤类型。
2.6.2 自定义过滤器
Step 1:创建自定义的过滤器类,继承ZuulFilter
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";//可选值:pre、route、post、error
}
@Override
public int filterOrder() {
return 0;//返回int值指定过滤器的执行顺序,不同过滤器允许相同值
}
@Override
public boolean shouldFilter() {
return true;//返回boolean值判断是否执行该过滤器
}
@Override
public Object run() {
//过滤器的具体逻辑
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest httpServletRequest = requestContext.getRequest();
System.out.println("===========Current Request:"+httpServletRequest.getMethod()+", Request Method:"+httpServletRequest.getRequestURL().toString());
return null;
}
}
Step 2:修改启动类,添加初始化对象
@Bean
public MyFilter myFilter(){
return new MyFilter();
}
Step 3:重启zuul-server后,访问任意请求,都会执行run方法中的逻辑
===========Current Request:GET, Request Method:http://localhost:8080/common/provider/test
2.6.3 禁用过滤器
Spring Cloud默认为Zuul提供并启用了一些过滤器,如DebugFilter、FormBodyWrapperFilter、PreDecorationFilter等。如有特殊场景需要禁用部分过滤器,可以通过设置zuul.<ClassName>.<filterType>.disable=true禁用ClassName对应的过滤器。
如:禁用上面自定义的过滤器,仅需要设置zuul.MyFilter.pre.disable=true
即可。
2.7 Zuul容错与回退
Zuul集成了Hystrix,可以实现服务容错处理。但我们还记得Spring Cloud Hystrix的回退处理是在客户端实现的,现在请求经过Zuul网关,要实现回退处理的话也需要在Zuul网关完成。
Zuul网关以微服务为粒度,为该微服务的所有请求做回退处理。
Zuul为我们提供了FallbackProvider接口,实现该接口,构建自定义的响应即可。
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
return this.fallbackResponse();
}
@Override
public String getRoute() {
// 指定为哪个微服务提供回退,* 可以为所有微服务提供回退
return "*";
}
@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("{\"ret\":1,\"msg\":\"服务不可用\"}".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mediaType);
return headers;
}
};
}
}
重启zuul-server后,停止eureka-client-provider的所有服务,再次访问http://localhost:8080/common/provider/test,会返回我们自定义的响应结果{"ret":1,"msg":"服务不可用"}
2.8 Zuul高可用
2.8.1 客户端注册到Eureka Server
如果客户端也注册到Eureka Server上,那么请求至Zuul网关就跟请求至其它微服务一样,支持Ribbon负载均衡。
![](https://img.haomeiwen.com/i1643904/2aee73c10d03bb61.png)
2.8.2 客户端未注册到Eureka Server
实际上,更多的场景是这种情况,通常更多的是外部系统(如:手机APP、前端页面)访问微服务网关,不可能让这些系统也注册到Eureka Server。这种情况下,可以使用额外的负载均衡器(如:Nginx、HAProxy、F5等)。
![](https://img.haomeiwen.com/i1643904/8a897d4622af625a.png)
3.示例代码
git clone https://github.com/yuanzicheng/spring-cloud-zuul.git
网友评论