Zuul
概述
Zuul组件可以提供对请求的路由和过滤两个最主要的功能。
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
依赖引入
因为需要借助eureka注册,所以还是需要引入eureka的依赖,和其他服务一样,也是属于springboot项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
路由功能
Zuul组件的路由功能可以实现访问路径的映射,不过如果需要使用Zuul需要创建一个Zuul服务器,并把此服务注册到Eureka中去。
而且Zuul的路由只会对Eureka中已经注册的服务起作用,Zuul服务和普通的服务有所区别的就是需要加上Zuul的注解和添加相关映射配置。
启动注解
需要在springboot的启动类上添加@EnableZuulProxy,不过不需要再加上Eureka客户端的注解了,因为@EnableZuulProxy继承自@EnableEurekaClient
@SpringBootApplication
@EnableZuulProxy
public class ZuulStart {
public static void main(String[] args) {
SpringApplication.run(ZuulStart.class);
}
}
eureka配置
配置信息同其他eureka服务一样
server:
port: 4500
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://eureka3001.com:3001/eureka,http://eureka3002.com:3002/eureka #eureka服务端提供的注册地址 参考服务端配置的这个路径
instance:
instance-id: zuul-1 #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
spring:
application:
name: server-zuul #此实例注册到eureka服务 端的name
这样就可以启动Zuul项目了
不过想要想要实现路由功能需要配置相关参数
路由配置
路径映射
在实际开发当中我们肯定不会是这样通过微服务调用,比如我要调用power可能只要一个/power就好了而不是/server-power。
在yml文件中加入以下配置
zuul:
routes:
mypower:
serviceId: server-power
path: /power/**
myorder:
serviceId: server-order
path: /order/**
routes节点下的my***是自定义的名字,相当于路由策略的id,至于serviceId则是eureka中的服务名,这个可以从注册中心中找到,至于path则是需要映射的路径,如果设置为/power/**
,那么访问/power下的任何路径就相当于在server-power下的路径。
注意/**
代表是所有层级,/*
是代表一层。 如果是/*
的话/power/admin/getUser.do
就不会被路由。
而且添加上Zuul后就可以直接通过服务名访问其他服务的controller
加上映射后就可以通过相应的规则访问
禁用服务名
这时候我们能通过我们自定义的规则来访问了,但是还有一个问题,就是我们现在依然能用之前的微服务名调用,这样子是不合理的,第一是有多重地址了, 第二,一般微服务名这种最好不要暴露在外。
所以我们一般会禁用微服务名方式调用,只需要加上如下配置
ignored-services: server-power
这样就无法使用服务名访问服务了
不过这个配置如果一个一个通过微服务名来配置难免有点复杂,所以一般这样配置来禁用所有
ignored-services: "*"
添加前缀
可能有时候我们的接口调用需要一定的规范,譬如调用微服务的API URL前缀需要加上/api 对于这种情况, zuul也考虑到了并给出了解决方案,配置prefix属性
zuul:
prefix: /api
ignored-services: "*"
routes:
mypower:
serviceId: server-power
path: /power/**
myorder:
serviceId: server-order
path: /order/**
加上一个prefix 即定义好了一个前缀, 那么我们每次需要路由的时候需要加上一个/api的前缀
但是 这样有一个问题,就是这个/api前缀 会不会出现在我们路由后的IP地址中呢?因为有可能我们微服务提供的接口也是含有/api前缀的,答案是不会的。 但是可以进行配置,配置strip-prefix属性值为false
zuul:
prefix: /api
strip-prefix: false
ignored-services: "*"
routes:
mypower:
serviceId: server-power
path: /power/**
myorder:
serviceId: server-order
path: /order/**
过滤器
过滤器(filter)是zuul的核心组件 zuul大部分功能都是通过过滤器来实现的。 zuul中定义了4种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
- PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在 集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服 务的请求,并使用 Apache HttpCIient或 Netfilx Ribbon请求微服务 。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准 的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。
执行顺序
这4中过滤器的执行顺序也不一样,也决定了每种过滤器的功能的不同,这样它们处理的请求路径也是不尽相同
一个简单过滤器实现
需要继承ZuulFilter类并实现其中的方法,其中这四个方法各有各的作用,不过真正过滤器的回调方法是ZuulException
方法
public class MyFilter extends ZuulFilter {
/**
* 此方法决定了该过滤器的种类,可以返回四种过滤器类型
* 1. FilterConstants.PRE_TYPE
* 2. FilterConstants.ROUTE_TYPE
* 3. FilterConstants.POST_TYPE
* 4. FilterConstants.ERROR_TYPE
* @return
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 这个方法的返回值决定了此过滤器的执行顺序,返回的越小越先执行
* @return
*/
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER;
}
/**
* 此方法会判断是否需要执行此过滤器逻辑
* @return
*/
@Override
public boolean shouldFilter() {
// 返回true为执行过滤器
return true;
}
/**
* 此方法中是在过滤器调用时执行的回调方法
* 里面就是过滤器的逻辑代码
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
// 拿到RequestContext上下文对象
RequestContext currentContext = RequestContext.getCurrentContext();
// 拿到此时的request请求对象
HttpServletRequest request = currentContext.getRequest();
// 拿到请求的路径
String remoteAddr = request.getRemoteAddr();
System.out.println("访问者IP:"+remoteAddr+"访问地址:"+request.getRequestURI());
return null;
}
}
filterType
:返回过滤器的类型。有 pre、 route、 post、 error等几种取值,分别对应上文的几种过滤器。详细可以参考 com.netflix.zuul.ZuulFilter.filterType()中的注释。filter0rder
:返回一个 int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。shouldFilter
:返回一个 boolean值来判断该过滤器是否要执行, true表示执行, false表示不执行。run
:过滤器的具体逻辑。
禁用zuul过滤器 Spring Cloud默认为Zuul编写并启用了一些过滤器,例如DebugFilter、 FormBodyWrapperFilter等,这些过滤器都存放在spring-cloud-netflix-core这个jar包 里,一些场景下,想要禁用掉部分过滤器,该怎么办呢? 只需在application.yml里设置zuul…disable=true 例如,要禁用上面我们写的过滤器,这样配置就行了:zuul.LogFilter.pre.disable=true
容错和降级
zuul默认是整合了hystrix和ribbon的, 提供降级回退,那如何来使用hystrix呢?
我们自行写一个类,继承FallbackProvider类,然后重写里面的方法
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return null;
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return null;
}
}
这里会发现有这2个方法需要重写, 那么如何来写呢? 我们可以查阅官方文档
下面的官方提供的demo
class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//制定为哪个微服务提供回退(这里写微服务名 写*代表所有微服务)
return "*";
}
//此方法需要返回一个ClientHttpResponse对象 ClientHttpResponse是一个接口,具体的回退逻辑要实现此接口
//route:出错的微服务名 cause:出错的异常对象
@Override
public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
//这里可以判断根据不同的异常来做不同的处理, 也可以不判断
//完了之后调用response方法并根据异常类型传入HttpStatus
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
//这里返回一个ClientHttpResponse对象 并实现其中的方法,关于回退逻辑的详细,便在下面的方法中
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
//返回一个HttpStatus对象 这个对象是个枚举对象, 里面包含了一个status code 和reasonPhrase信息
return status;
}
@Override
public int getRawStatusCode() throws IOException {
//返回status的code 比如 404,500等
return status.value();
}
@Override
public String getStatusText() throws IOException {
//返回一个HttpStatus对象的reasonPhrase信息
return status.getReasonPhrase();
}
@Override
public void close() {
//close的时候调用的方法, 讲白了就是当降级信息全部响应完了之后调用的方法
}
@Override
public InputStream getBody() throws IOException {
//吧降级信息响应回前端
return new ByteArrayInputStream("降级信息".getBytes());
}
@Override
public HttpHeaders getHeaders() {
//需要对响应报头设置的话可以在此设置
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
Zuul集群
为了实现Zuul路由器的容灾,需要考虑使用Zuul集群来解决这个问题,这里的解决方案有很多,比如可以用zuul、ribbon、nginx。
这里只介绍使用zuul自身做集群,这里先创建多个zuul服务组建zuul集群,其中每个zuul服务的配置都是一样的,和eureka的其他服务一样,除了instance-id不一样,其他的配置都一样。
接着需要创建一个主zuul服务器使我们的路径路由到这些集群中的zuul,配置文件如下,需要注意的使eureka.instance.instance-id和spring.application.name都必须和其他zuul服务不一样。
同时创建映射到其他zuul服务的配置,需要注意如果这些集群zuul服务加上了prefix属性的前缀,那么这个转发的zuul服务的配置中的前缀就不能忽略,需要设置strip-prefix: false
server:
port: 4500
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://eureka3001.com:3001/eureka,http://eureka3002.com:3002/eureka #eureka服务端提供的注册地址 参考服务端配置的这个路径
instance:
instance-id: zuul #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
spring:
application:
name: zuul #此实例注册到eureka服务 端的name
zuul:
prefix: /api
strip-prefix: false
ignored-services: *
routes:
myzuul:
serviceId: server-zuul
path: /zuul/**
访问时需要加上转发的zuul服务路径和需要调用的服务的路径