Spring-Cloud组件之API网关Zuul
什么是Zuul
Zuul是spring全家桶中的微服务API网关。
主要功能是路由和过滤两个主要的功能。
路由就是把外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。
过滤呢就是对请求的处理过程进行干预(比如说认证资源,不符合的不让访问,还可以做性能监测等),是实现请求校验,服务聚合等功能的基础。
不得不说的spring全家桶是真的厉害,这个zuul和eureka整合后,也就是将zuul服务注册为eureka服务治理下的应用,zuul就可以或得其他微服务的信息,然后就可以通过zuul访问到其他微服务了。
路由
简单的说就是将不同的请求地址映射到不同的微服务上去,比如/api/user/*,那我们就讲这类请求映射到user微服务上,/api/power/*就映射到power微服务上去。
还是来写代码吧。新建一个module取名zuul9001。
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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zuul9001</artifactId>
<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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
application.yml
server:
port: 9001
eureka:
client:
service-url:
defaultZone: http://eureka3000.com:3000/eureka #Eureka服务端提供的注册地址
instance:
instance-id: server-zuul1 #此实例注册到Eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP
lease-renewal-interval-in-seconds: 10 #Eureka 客户端需要多长时间发送心跳给Eyreka服务器,表明他还活着 ,默认为30 秒 (与下面配置的单位都是秒)
lease-expiration-duration-in-seconds: 30 #Eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
spring:
application:
name: server-zuul #此实例注册到Eureka服务端的name
编写启动类Zuul9001Application
@SpringBootApplication
@EnableZuulProxy
public class Zuul9001Application {
public static void main(String[] args) {
SpringApplication.run(Zuul9001Application.class);
}
}
好了,启动一下 eureka3000,power,zuul9001三个项目。
我们访问 http://localhost:9001/server-power/getPower.do 然后就可以顺利返回
这说明zuul已经成功路由了。
但是我们实际项目中,可不会访问/server-power。感觉不太好看啊,所以改一下配置文件:在最后面加上这个:
zuul:
routes:
power:
serviceId: server-power #eureika注册的名字
path: /power/** #转换访问路径
然后重启一下zuul9001。访问http://localhost:9001/server-power/getPower.do 和http://localhost:9001/power/getPower.do
是不是都可以正常放回了。
但是我们为了方位双通道访问,保证微服务的安全性,所以我们就应该只允许一个通道入口,既然如此,我们禁用一下/server-power/*访问吧。
改一下配置文件:
zuul:
ignored-services: server-power #禁用微服务名称直接访问 改为 "*" 则是禁用所有
routes:
power:
serviceId: server-power #eureika注册的名字
path: /power/** #转换访问路径
我们可能会存在一个问题,就是路径冲突,什么意思呢,就是可能本项目和其他微服务有相同的访问路径,那如果我们这样访问,到底是访问微服务呢还是访问本项目的?所以我们给微服务和本项目区分一下,就是访问微服务的加一个路径。
改一下配置文件:
zuul:
ignored-services: server-power #禁用微服务名称直接访问 改为 "*" 则是禁用所有
prefix: /api
# strip-prefix: false #防止路径被截断,比如我们power那边是 /api/getPower.do 写上这个就是防止 截断/api
routes:
power:
serviceId: server-power #eureika注册的名字
path: /power/** #转换访问路径
重启一下,是不是前面两个路径都不能访问了,这是因为我们给微服务做了一下区分,在路径前面必须加上/api才能访问了,现在应该访问http://localhost:9001/api/power/getPower.do
其实可能你们觉得这里我比较啰嗦了,我也只是想让大家理解一下每一个配置都是代表什么意思。
好吧,路由就差不多是这样,接下来我们再看看过滤吧。
过滤器
过滤器(filter)是zuul的核心组件 zuul大部分功能都是通过过滤器来实现的。
zuul中定义了4种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
1.PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在 集群中选择请求的微服务、记录调试信息等。
2.ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服 务的请求,并使用 Apache HttpCIient或 Netfilx Ribbon请求微服务。
3.POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准 的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
4.ERROR:在其他阶段发生错误时执行该过滤器。
用代码的方式看看。
新建一个LogFilter类,表示我们需要操作日志:
@Component
public class LogFilter extends ZuulFilter {
/**
* 拦截器类型
* 1.PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在 集群中选择请求的微服务、记录调试信息等。
* 2.ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服 务的请求,并使用 Apache HttpCIient或 Netfilx Ribbon请求微服务
* 3.POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准 的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
* 4.ERROR:在其他阶段发生错误时执行该过滤器。
* @return
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 拦截器优先级,数字越小优先级越高。这里只是针对ZuulFilter的拦截器优先级,我们可能会定义几个ZuulFilter
*
* @return
*/
@Override
public int filterOrder() {
//FilterConstants.PRE_DECORATION_FILTER_ORDER是ZuulFilter默认的拦截器。我们一般定义是在他之前或者在他之后,看实际情况来决定先后
//+1表示在他之后
return FilterConstants.PRE_DECORATION_FILTER_ORDER+1;
}
/**
* 当前拦截器是否启动
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 实际的拦截需要执行的代码
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String s = ctx.get(FilterConstants.REQUEST_URI_KEY).toString();
System.out.println(request.getRemoteAddr()+"访问了:"+request.getRequestURI()+"路由后的链接为:"+s);
return null;
}
}
这里看一下,我们实现了ZuulFilter接口。然后实现的几个接口分别代表什么就看一下注释,我们这里做的过滤比较简单,就是为了看一下 是谁访问的,然后访问的哪个接口,最后又被zuul路由到哪里了。
重启一下,然后看看控制台打印
这个ip有点问题,应该都知道的,本机ip需要转一下的,这里我就不演示了。
这个就证明了我这个过滤器生效了,其实这里只是简单的看一下,实际上这里可以做很多事情的,比如认证,性能监测,负载削减,压力测试,安全,静态响应处理等等事情。
容错与回退
zuul默认是整合了hystrix和ribbon的, 提供降级回退
新建一个FallBackProvider类实现FallbackProvider接口:
/**
* zuul容错与回退
*/
@Component
public class FallBackProvider implements FallbackProvider {
/**
* 对哪个微服务做降级回退,"*"代表对所有微服务做降级回退
* @return
*/
@Override
public String getRoute() {
return "server-power";
}
/**
*
* @param route 出错了的微服务名称
* @param cause 这个是出错的异常
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
//判断异常属于什么异常
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
/**
* 这个是错误信息的枚举信息
* @return
* @throws IOException
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
/**
* 这个就是状态码 比如 404 500 等
* @return
* @throws IOException
*/
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
/**
* 这个是错误信息的实际信息
* @return
* @throws IOException
*/
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
/**
* 在getBody响应完后调用
*/
@Override
public void close() {
}
/**
* 返回具体的信息
* @return
* @throws IOException
*/
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("系统异常".getBytes());
}
/**
* 定义头部信息
* @return
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
这个也得仔细看看注释咯,解释都写在注释上面的,这里的代码是去官网拷贝的。这个就是微服务的容错也回退,我们可以这样,把power服务停掉,然后重启一下zuul9001,这样访问http://localhost:9001/api/power/getPower.do
然后就可以看到返回的是
这是我们自定义的错误信息,返回这个比返回一堆看不懂的英文应该要好很多吧。
Zuul高可用-----集群
拷贝一个zuul9001取名zuul9002然后改一下端口为9002。这个就不贴代码了吧。
然后新建一个项目叫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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zuul</artifactId>
<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>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
application.yml
server:
port: 9000
eureka:
client:
service-url:
defaultZone: http://eureka3000.com:3000/eureka #Eureka服务端提供的注册地址
instance:
instance-id: server-zuul0 #此实例注册到Eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP
lease-renewal-interval-in-seconds: 10 #Eureka 客户端需要多长时间发送心跳给Eyreka服务器,表明他还活着 ,默认为30 秒 (与下面配置的单位都是秒)
lease-expiration-duration-in-seconds: 30 #Eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
spring:
application:
name: zuul #此实例注册到Eureka服务端的name
zuul:
ignored-services: server-power #禁用微服务名称直接访问 改为 "*" 则是禁用所有
prefix: /api
# strip-prefix: false #防止路径被截断,比如我们power那边是 /api/getPower.do 写上这个就是防止 截断/api
routes:
zuul:
serviceId: server-zuul #eureika注册的名字
path: /zuul/** #转换访问路径
这里就是将zuul其他两个项目通过eureka路由一下。
编写启动类:
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
启动eureka3000,power,zuul9001,zuul9002,zuul五个项目,然后通过
http://localhost:9000/api/zuul/api/power/getPower.do 访问一下,在看看zuul9001和zuul9002的控制台,基本上是一边打印一次(这个不是轮询,我的意思是不会同时在两边打印)
这个就是zuul的高可用了啊,这里还是有一点小问题,如果这样部署集群,必须要给zuul也要部署集群,不然不行的。当然这个还可以通过nginx,feign来弄的,不过我就不演示了啊。有兴趣自己玩玩吧。
好了,到此为止了啊。马上进行下一篇。