SpringCloud中如何使用Zuul路由网关,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
一、Zuul的作用
1. 网关功能
网关可以将所有的api统一暴露,保护其他服务api接口和信息,以防止被外界直接调用
网关可以做身份认证和权限认证,防止非法请求操作API,保护服务
网关可以实现监控功能,实时日志输出,对请求进行记录
网关可以实现流量监控,便于在高流量情况下对服务进行降级
2. 智能路由
二、Zuul的工作原理
Zuul基于Servlet实现,通过自定义的ZuulServlet来对请求进行控制,Zuul中有一系列的过滤器,这些过滤器是同样是Zuul的实现关键,请求发起和响应期间,通过这些过滤器实现Zuul的功能。具体有以下四个:
PRE过滤器:在请求路由到具体的服务之前执行,用途:安全验证(身份校验,参数校验、ip黑白名单);
ROUTING过滤器 :在请求的服务到具体的微服务实例时执行,用途:进行网络请求(默认使用HttpClient);
POST过滤器:在请求路由到微服务之后执行,用途:统计信息,回传响应;
ERROR过滤器:在其他过滤器发生错误的时候执行,用途:保证请求能够正确响应;
ZuulFilter中的方法有以下四个,继承ZuulFilter并且重写以下四个方法即可实现一个过滤器。
public String filterType(); 返回该Filter的类型,即如上四种过滤器。
public int filterOrder(); 返回该过滤器的执行顺序。
public boolean shouldFilter(); 返回该过滤器是否需要执行。
public Object run(); 执行具体的过滤逻辑。
ZuulServlet
是Zuul的核心Servlet,负责初始化ZuulFilter
并且编排这些过滤器,具体代码在service()
方法中。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.route();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
RequestContext
是ZuulFilter
中负责上下文衔接的角色,其本身是一个ConcurrentHashMap
,包含Request和Response、routeType、routeHost等上下文需要的对象。
三、项目实战
(1)项目搭建
<groupId>com.calvin.zuul</groupId>
<artifactId>zuul-test</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eureka-server</module>
<module>user-service</module>
<module>zuul-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<parent>
<artifactId>zuul-test</artifactId>
<groupId>com.calvin.zuul</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zuul-service</artifactId>
<dependencies>
<!-- Springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringCloud支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
/**
* <p>
* 启动类
* </p>
* @author Calvin
* @date 2019/10/25
* @since 1.0
*/
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
spring:
application:
name: zuul-service
server:
port: 8030
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
hostname: localhost
zuul:
routes:
user-api:
path: /user/**
serviceId: user-service
<parent>
<artifactId>zuul-test</artifactId>
<groupId>com.calvin.zuul</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
spring:
application:
name: user-service
server:
port: 8020
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
hostname: localhost
/**
* <p>
* 启动类
* </p>
* @author Calvin
* @date 2019/10/25
* @since 1.0
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
/**
* <p> </p>
*
* @author Calvin
* @date 2019/10/25
* @since
*/
@RestController
public class UserController {
/**
* 简单构建一个User
*/
@GetMapping("/userDetail/{userId}")
public SysUser getUserDetail(@PathVariable("userId") String userId){
SysUser sysUser = new SysUser();
sysUser.setId(userId);
sysUser.setAge(20);
sysUser.setPassword(MD5Utils.digestAsHex("123456"));
sysUser.setUsername("Calvin");
//图片来自百度
sysUser.setHeadImg("https://b-ssl.duitang.com/uploads/item/201510/17/20151017181645_c5hWE.thumb.700_0.jpeg");
return sysUser;
}
}
(2)智能路由
依次启动eureka-server, user-server, zuul-server
浏览器调用 http://localhost:8030/user/userDetail/1 
从调用结果中可以看到我们从zuul-service中调用了user-service的方法,并且调用成功。从而证明路由配置可用;
如需配置版本号,我们只需要咱zuul-service/application.yml中添加配置:zuul.prefix=v1
(3)故障熔断
如果需要在Zuul中实现服务熔断,只需要实现ZuulFallbackProvider
接口,重写其中两个方法,通过getRoute()
方法返回我们需要熔断的路由,通过fallbackResponse()
方法来重写熔断时执行的逻辑。
如下,我们实现第一个user-service的熔断器
/**
* <p>
* user-service熔断器
* </p>
*
* @author Calvin
* @date 2019/10/27
* @since
*/
@Component
public class UserServiceCallback implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "user-service";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new CommonClientResponse();
}
}
贴上CommonClientResponse
的代码,就是针对ClientHttpResponse接口的封装
/**
* <p>封装的通用返回类 </p>
*
* @author Calvin
* @date 2019/10/27
* @since
*/
public class CommonClientResponse implements ClientHttpResponse {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 0;
}
@Override
public String getStatusText() throws IOException {
return "success";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("hello , this is zuul fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
}
接下来做个测试,我们停掉user-service服务,然后再访问 http://localhost:8030/user/userDetail/1 
结果当然是我们定义的熔断器中返回的内容了!
如果需要对其他服务使用同一个熔断器,只需要在getRoute()
方法中返回通配符 return "*"
就可以了
网关测试
ZuulFilter
是Zuul实现过滤和网关的关键,此类有四个枚举值,分别代表Zuul中的过滤器类型。如果需要实现过滤,只需要继承ZuulFilter
,并且指定其过滤器类型,枚举值为:
/**
* {@link ZuulFilter#filterType()} error type.
*/
String ERROR_TYPE = "error";
/**
* {@link ZuulFilter#filterType()} post type.
*/
String POST_TYPE = "post";
/**
* {@link ZuulFilter#filterType()} pre type.
*/
String PRE_TYPE = "pre";
/**
* {@link ZuulFilter#filterType()} route type.
*/
String ROUTE_TYPE = "route";
简单实现一个过滤器
/**
* <p> header过滤器 </p>
*
* @author Calvin
* @date 2019/10/27
* @since
*/
@Component
public class TokenFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
logger.info("request_url : {}, request_headers: {}",request.getRequestURI(), JSON.toJSON(request.getHeaderNames()).toString());
return null;
}
}
重新启动zuul-service,调用服务控制台已经可以输出如下内容:
2019-11-12 22:04:36.726 INFO 58984 --- [nio-8030-exec-4] com.calvin.zuul.filter.TokenFilter : request_url : /user/userDetail/1, request_headers: ["host","connection","cache-control","upgrade-insecure-requests","user-agent","sec-fetch-user","accept","sec-fetch-site","sec-fetch-mode","accept-encoding","accept-language","cookie"]
若需要拦截请求,或者设置白名单等,在RequestContext中设置好自己的statusCode等,就可以了
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(401);
ctx.getResponse().getWriter().write("there is no token found,please relogin!")
看完上述内容,你们掌握SpringCloud中如何使用Zuul路由网关的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注天达云行业资讯频道,感谢各位的阅读!