一、前言
1、什么是服务网关?
服务网关也就是API网关,服务网关可以作为服务的统一入口,提供身份校验、动态路由、负载均衡、安全管理、统计、监控、流量管理、灰度发布、压力测试等功能
服务网关/API网关并不是微服务体系所特有的,而是微服务流行起来之后,服务网关基本上成了微服务架构的标配。服务网关通常用于向客户端或者合作伙伴应用提供统一的服务接入方式,例如:App网关、开放平台(OpenAPI)等等。
2、什么是Zuul?
Zuul是Netflix开源的服务网关/API网关,提供动态路由、监控、弹性、安全性等功能。
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, security, and more. Please view the wiki for usage, information, HOWTO, etc https://github.com/Netflix/zuul/wiki
Zuul+Eureka交互示意图
这里只列举了单个网关集群,实际上互联网公司通常会有多个网关集群,App网关、HTML5网关、OpenAPI网关等等
3、本篇环境信息
框架 | 版本 |
---|---|
Spring Boot | 2.0.0.RELEASE |
Spring Cloud | Finchley.RELEASE |
Zuul | 1.3.1 (Spring Cloud-Finchley还没整合2.x版本) |
JDK | 1.8.x |
4、准备工作
参考上一篇:https://ken.io/note/spring-cloud-hystrix-dashboard-turbine-quickstart
基于源码:https://github.com/ken-io/springcloud-course/tree/master/chapter-06
- 准备Eureka Server、服务提供者
启动Eureka Server: http://localhost:8800
启动Test Service:http://localhost:8602,http://localhost:8603
二、服务网关:Zuul
1、创建Zuul项目
- 创建项目
按照惯例,使用maven-archtype-quickstart模板创建项目
项 | 说明 |
---|---|
GroupId | io.ken.springcloud.zuul |
ArtifactId | zuul |
- 修改pom.xml 引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.ken.springcloud.zuul</groupId>
<artifactId>zuul</artifactId>
<version>1.0-SNAPSHOT</version>
<name>zuul</name>
<url>http://ken.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2、配置Zuul启动类
修改\src\main\java\io\ken\springcloud\zuul\App.java
@EnableZuulProxy:启用Zuul
package io.ken.springcloud.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3、配置Zuul
在\src\main下创建文件夹resources文件夹并设置为Resources Root
在resources文件夹下创建application.yml文件并配置Zuul
server:
port: 8888
spring:
application:
name: zuul
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8800/eureka/
4、Zuul访问测试
启动zuul项目后,访问:http://localhost:8888/testservice
会交替显示以下信息
{
"code": 0,
"message": "hello",
"content": null,
"serviceName": "testservice",
"host": "localhost:8602"
}
/*********分割线*********/
{
"code": 0,
"message": "hello",
"content": null,
"serviceName": "testservice",
"host": "localhost:8603"
}
- 解析
通过配置文件我们知道,Spring Cloud Zuul 将自己作为一个服务注册到了Eureka。这也就意味着Zuul可以拿到所有注册到Eureka的其他服务的信息。Zuul为这些服务创建了默认的路由规则:/{servicename}/**
所以,我们访问 http://localhost:8888/testservice
就相当于访问:(testservice)http://localhost:8602
由于我们注册了两个testservice的实例,而且Zuul本身具备负载均衡的功能。所以会交替显示不同实例的响应内容。
5、FeignClient访问Zuul
根据上一篇FeignClient项目代码调整
在\src\main\java\io\ken\springcloud\feignclient\service
增加TestServiceZuul.java
package io.ken.springcloud.feignclient.service;
import io.ken.springcloud.feignclient.model.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "zuul")
public interface TestServiceZuul {
@RequestMapping(value = "/testservice/", method = RequestMethod.GET)
String indexService();
@RequestMapping(value = "/testservice/plus", method = RequestMethod.GET)
Result plusService(@RequestParam(name = "numA") int numA, @RequestParam(name = "numB") int numB);
}
简单来说,就是将直接ServiceName配置为zuul,请求的url前增加目标服务的ServiceName即可。
引用示例:
@RestController
public class TestController {
@Autowired
private TestServiceZuul testServiceZuul;
@RequestMapping("/ti-zuul")
public Object ti_zuul() {
return testServiceZuul.indexService();
}
}
三、路由配置
1、路由配置参数说明
- 路由配置基础参数说明
配置项 | ken.io 的说明 |
---|---|
zuul.routes.{routename} | 路由名称,自定义,支持小写字母、- |
zuul.routes.{routename}.path | 要路由的路径,支持通配符:?、 、* |
zuul.routes.{routename}.serviceId | 注册在Eureka的ServiceName |
zuul.routes.{routename}.url | 如果应用没有注册在Eureka,也可以通过指定Url来路由 |
zuul.ignored-services | 忽略指定的服务,可以配置多个,以,间隔 |
zuul.ignored-patterns | 忽略指定的路径,可以配置多个,以,间隔。同样支持通配符 |
- path通配符说明
通配符 | 说明 | path举例 | 匹配示例 |
---|---|---|---|
? | 匹配单个任意字符 | /ts/? | /ts/a、/ts/b |
* | 匹配任意字符 | ts/* | /ts/a、/ts/b、/ts/ab |
** | 匹配任意字符且支持多级目录 | ts/** | /ts/a、/ts/b、/ts/ab、/ts/ab/c |
对于通配符来说,没有特定的需求,直接用**就好
2、路由配置示例
- 路由到注册到Eureka的服务:testservice
zuul:
routes:
ts:
path: /ts/**
serviceId: testservice
通常适用于:一个服务随着业务的发展不断扩大需要拆分,这时候我们可以通过api兼容+路由配置来适配,可以对上游无感知。例如用户服务(userservice)有三个核心模块,auth、info、safe,经过拆分拆出来两个服务:authservice、safeservice。那么拆分后的路由配置:
zuul:
routes:
user-auth:
path: /user/auth/**
serviceId: authservice
user-safe:
path: /user/safe/**
serviceId: safeservice
user:
path: /user/**
serviceId: userservice
因为路由规则匹配顺序是按配置顺序来的,所以未拆出去的配置在最后。
- 路由到指定站点
当一个应用并没有注册到服务注册中心(Eureka),我们又需要将它挂到网关上可以通过指定url的这种方式。
zuul:
routes:
ken-io:
path: /ken/**
url: https://ken.io/
- 忽略指定路径
通常适用于某些通用的接口不暴露给外部,例如每个应用都会有一个健康检查入口,但是这个入口是不应该暴露给外部的,就可以通过忽略规则屏蔽掉。
zuul:
ignored-patterns: /**/hs,/**/health
- 忽略指定服务
zuul:
ignored-services: aservice,bservice
四、Zuul过滤器
1、身份校验过滤器
- 创建过滤器
在src\main\java\io\ken\springcloud\zuul创建package:filter
然后创建Filter类:AuthFilter.java
实现ZuulFilter并标记为Component
package io.ken.springcloud.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.logging.Logger;
@Component
public class AuthFilter extends ZuulFilter {
private static Logger log = Logger.getLogger(AuthFilter.class.toString());
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("header-token:%s,param-token:%s", request.getHeader("token"), request.getParameter("token")));
String token_header = request.getHeader("token") == null ? "" : request.getHeader("token");
String token_param = request.getParameter("token") == null ? "" : request.getParameter("token");
if (token_header.equals("") && token_param.equals("")) {
try {
ctx.setSendZuulResponse(false);
ctx.getResponse().getWriter().write("{\"code\": 9999,\"message\": \"token is empty.\"}");
} catch (Exception e) {
log.warning("system error");
}
} else if (!token_header.equals("")) {
log.warning(String.format("token is %s", token_header));
} else if (!token_param.equals("")) {
log.warning(String.format("token is %s", token_param));
}
return null;
}
}
重新启动Zuul,然后访问:http://localhost:8888/testservice
由于缺少token,会返回如下信息:
{
"code": 9999,
"message": "token is empty."
}
这里只是做一个示例,需要完成身份校验,还要完善代码。
2、ZuulFilter使用说明
- ZuulFilter方法说明
方法名 | 说明 |
---|---|
filterType() | 过滤器类型:pre、routing、post、error |
filterOrder | 过滤器顺序,用于指定过滤器执行顺序 |
shouldFilter | 是否要过滤,可以根据当前请求信息判断是否过滤,也可以默认返回true |
run | 过滤器执行逻辑,执行具体的过滤操作。 |
- FilterType说明
type | 说明 |
---|---|
pre | 在路由之前执行过滤 |
routing | 在路由时执行过滤 |
post | 在路由之后执行过滤 |
error | 在发生错误时执行过滤 |
五、备注
- 本篇代码示例
https://github.com/ken-io/springcloud-course/tree/master/chapter-07
- 本篇参考
https://eacdy.gitbooks.io/spring-cloud-book/content/2%20Spring%20Cloud/2.6%20API%20Gateway.html