Zuul提供了服务网关的功能,可以实现
负载均衡
反向代理
动态路由
请求转发等功能
Zuul大部分功能是通过过滤器实现的,除了标准的四种过滤器类型,还支持自定义过滤器。
使用@EnableZuulProxy注解,Spring容器初始化时,会将Zuul的相关配置初始化,其中包含一个Spring Boot的Bean:ServletRegistrationBean,该类主要用于注册Servlet。
在Servlet的service方法中,执行各种Zuul过滤器。下图为HTTP请求在ZuulServlet中的生命周期。
这里我们延续前面工程的工程继续往下写,没看到前面的可以在SpringCloud专辑
https://blog.csdn.net/suxiexingchen/category_11853792.html中查看前面的工程详情。
1.首先创建一个SpringBoot工程,并建立Eureka客户端,pom文件中加入zuul的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.application.yml中做如下配置,这样就将访问地址转到了设置的URL
eureka:
client:
serviceUrl: #注册中心的地址
defaultZone: http://localhost:8761/eureka/
server:
port: 8765 #服务端口号
spring:
application:
name: Eureka-client-zuul #服务名称
zuul:
routes:
a: #路由的一个名称,可以任意起名
url: http://localhost:8764 #跳转的URL
3.最后在启动类上,要加入@EnableZuulProxy注解
package com.studycloud.eurekaclientproducer;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringCloudApplication
@EnableZuulProxy
public class EurekaClientProducerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientProducerApplication.class, args);
}
}
4.启动前面的所有服务,并在Eureka管理端查看
5.我们这里访问 http://localhost:8765/a/test 就访问到了服务。
6.可是我们的服务都注册到了Eureka啊,所以我们将配置文件再来改造一下,这样我们就可以访问我们注册服务的服务名称了。这才显得Eureka啊
eureka:
client:
serviceUrl: #注册中心的地址
defaultZone: http://localhost:8761/eureka/
server:
port: 8765 #服务端口号
spring:
application:
name: Eureka-client-zuul #服务名称
zuul:
sensitive-headers: #敏感header的设置,此处为空,默认Authorization不被转发
# ignored-services: '*' #该配置关闭,则zuul会默认代理所有注册在Eureka上的微服务
routes:
admin: #路由的一个名称,可以任意起名
path: /admin/** #拦截到的路径
serviceId: eureka-service-consumerA #模块的服务名
user:
path: /user/**
serviceId: eureka-service-provider
7.因为设置了path所以我们需要将所有请求都加上我们设置的前缀例如
【http://localhost:8765/admin/test】
总结:
使用url的话那么你规定的路由名称相当于所有请求的前缀
使用serviceId的话就可以使用注册的模块,但是要设置好path哦,按照path设置的路径来访问
8.其实还没有完,前面我们使用了Hystrix熔断器,好现在将服务B停掉看看熔断器起作用了没,出现了错误,其实是因为Zuul中需要自己实现断路器
504错误代表网关超时 (Gateway timeout),是指服务器作为网关或代理,但是没有及时从上游服务器收到请求
9.Zuul中实现Hystrix断路器
- Zuul网关中实现服务降级,只需要在Zuul网关的服务中,编写实现ZuulFallbackProvider接口的java类即可
- getRoute方法是执行要为哪个服务配置降级处理
- fallbackResponse方法是进行返回托底信息的,
因为这个问题比较特殊,特殊倒不是代码问题,而是全局观。所以这里我再交代一下前面的结构
9.1首先是这个现有的几个功能和作用
- Eureka-Server:Eureka的服务端
- Eureka-client-producerA:Eureka的客户端-提供者A(就是做具体的业务)
- Eureka-client-producerB:Eureka的客户端-提供者B
- Eureka-client-consumerA:Eureka的客户端-消费者A(集成Feign进行接口调用,并使用Hystrix熔断器)
- Eureka-client-zuul:作为网关使用(功能很纯粹就是网关)
9.2前面的其他代码可以在专辑中查看,这里写Zuul和Hystrix整合使用,交代一下这里使用的SpringBOOT版本
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
9.3首先看pom文件中需要依赖,因为是Eureka,Zuul所以
<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>
9.4接着是配置文件application.yml
解释一下下面Zuul的配置吧,拦截 /admin的路径到 eureka注册名为eureka-service-consumerA的工程
eureka:
client:
serviceUrl: #注册中心的地址
defaultZone: http://localhost:8761/eureka/
server:
port: 8765 #服务端口号
spring:
application:
name: Eureka-client-zuul #服务名称
zuul:
sensitive-headers: #敏感header的设置,此处为空,默认Authorization不被转发
# ignored-services: '*' #该配置关闭,则zuul会默认代理所有注册在Eureka上的微服务
routes:
admin: #路由的一个名称,可以任意起名
path: /admin/** #拦截到的路径
serviceId: eureka-service-consumerA #模块的服务名
user:
path: /user/**
serviceId: eureka-service-consumerB
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
这个路由一定要好好理解,否则总找不到404
9.5接下来需要实现接口 ProductFallback
解释一下下面的配置getRoute中返回*,代表支持所有的服务都快速失败,都可以走fallbackResponse我们自己设定的错误,是不是像极了以前的全局错误。大包大揽兜底用。
package com.studycloud.eurekaclientproducer;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
@Component
public class ProductFallback implements FallbackProvider {
/**
* 指定需要托底处理的服务名
* 推荐 - 为指定的服务定义特性化的fallback逻辑
* 推荐 - 提供一个处理所有服务的fallback逻辑
* 好处 - 某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑还有个通用的
*
* 返回fallback处理哪个服务,返回的是服务名称
* 如果支持所有的服务都快速失败,则返回null或者*即可
* @return java.lang.String 返回 fallback 处理哪一个服务,返回服务名称
*/
@Override
public String getRoute() {
return "*";
}
/**
* 服务容错处理
* 服务无法使用时,返回的托底信息
* @param route 容错服务名称
* @param cause 服务器异常信息
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("路由地址为:"+route);
return new ClientHttpResponse() {
/**
* ClientHttpResponse 的 fallback 的状态码 返回HttpStatus
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
/**
* ClientHttpResponse 的 fallback 的状态码 返回 int
*/
@Override
public int getRawStatusCode() throws IOException {
return getStatusCode().value();
}
/**
* ClientHttpResponse 的 fallback 的状态码 返回 String
*/
@Override
public String getStatusText() throws IOException {
return getStatusCode().getReasonPhrase();
}
/**
* 设置响应体
* Zuul会将本方法返回的输入流数据读取,并且通过HttpServletResponse的输出流输出到客户端
*/
@Override
public InputStream getBody() throws IOException {
String msg = "{\"message\":\"服务不可用,请稍后重试\"}";
return new ByteArrayInputStream(msg.getBytes());
}
/**
* 设置响应的头信息
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders= new HttpHeaders();
MediaType mediaType = new MediaType("application","json", Charset.forName("utf-8"));
httpHeaders.setContentType(mediaType);
return httpHeaders;
}
@Override
public void close() {
}
};
}
}
ProductFallback 中实现方法 getRoute 一定要好好理解,我看了网上很多的说的都不清楚,让人高的糊里糊涂的,其实就一句话,不是他们说的什么消费者,提供者。
返回值就是 【返回 fallback 处理哪一个服务,返回服务名称,如果支持所有的服务都快速失败,则返回null或者*即可】
还有一处容易犯错的错误,这里是JSON因为你已经规定了application为json,如果写普通字符串会报JSON错误,所以注意一下了。
9.6最后在启动类上加上@EnableZuulProxy注解
package com.studycloud.eurekaclientproducer;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringCloudApplication
@EnableZuulProxy
public class EurekaClientProducerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientProducerApplication.class, args);
}
}
所有启动以后
9.7,好了梳理一下思绪,启动项目,首先我们调用网关 localhost:8765/admin/test
成功获取到项目A
成功获取到项目B,并且是轮询方式
9.8接着我们将Eureka-client-producerA提供者A停掉,猜想一下,服务A停用熔断器生效
这里一定要强调一下,如果你的熔断器没起作用,我猜想大多数是因为超时没有设置的问题,循着这个方向去吧。
再次运行就只有Eureka-client-producerB还在运行了。
9.9现在再将Eureka-client-consumerA消费者整个停掉,按照道理应该Zuul的容错处理起作用了,事实正如所料,超时后显示出了我们预期的JSON
10.到这里已经不是一个简单Zuul的使用,而是整体的套用情况,所以心中要有一个全局观才好