一. 为什么需要网关呢?
第一,我们知道我们要进入一个服务本身,很明显我们没有特别好的办法,直接输入IP地址+端口号,我们知道这样的做法很糟糕的,这样的做法大有问题,首先暴露了我们实体机器的IP地址,别人一看你的IP地址就知道服务部署在哪里,让别人很方便的进行攻击操作。
第二,我们这么多服务,我们是不是要挨个调用它呀,我们这里假设做了个权限认证,我们每一个客户访问的都是跑在不同机器上的不同的JVM上的服务程序,我们每一个服务都需要一个服务认证,这样做烦不烦呀,明显是很烦的。
那么我们这时候面临着这两个极其重要的问题,这时我们就需要一个办法解决它们。首先,我们看IP地址的暴露和IP地址写死后带来的单点问题,我是不是对这么服务本身我也要动态的维护它服务的列表呀,我需要调用这服务本身,是不是也要一个负载均衡一样的玩意,
还有关于IP地址暴露的玩意,我是不是需要做一个代理呀,像Nginx的反向代理一样的东西,还有这玩意上部署公共的模块,比如所有入口的权限校验的东西。因此我们现在需要Zuul API网关。它就解决了上面的问题,你想调用某个服务,它会给你映射,把你服务的IP地址映射成
某个路径,你输入该路径,它匹配到了,它就去替你访问这个服务,它会有个请求转发的过程,像Nginx一样,服务机器的具体实例,它不会直接去访问IP,它会去Eureka注册中心拿到服务的实例ID,即服务的名字。我再次使用客户端的负载均衡ribbon访问其中服务实例中的一台。
API网关主要为了服务本身对外的调用该怎么调用来解决的,还有解决权限校验的问题,你可以在这里整合调用一系列过滤器的,例如整合shiro,springsecurity之类的东西。
Zuul的原理图如下:
二 应用场景
说了这么多,我们来总结一下网关的应用场景:
(1)黑白名单:实现通过IP地址控制禁止访问网关功能,此功能是应用层面控制实现,再往前也可以通过网络传输方面进行控制访问。
(2)日志:实现访问日志的记录,可用于分析访问、处理性能指标,同时将分析结果支持其他模块功能应用。
(3)协议适配:实现通信协议校验、适配转换的功能。
(4)身份认证:负责网关访问身份认证验证,此模块与“访问认证中心”通信,实际认证业务逻辑交移“访问认证中心”处理。
(5)计流限流:实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。
(6)路由:路由是API网关很核心的模块功能,此模块实现根据请求,锁定目标微服务并将请求进行转发。此模块需要与“服务发布管理中心”通信。“服务发布管理中心”实现微服务发布注册管理功能,与其通信获得目标微服务信息。
三 zuul和feign的应用场景和区别
(1)、zuul作为整个应用的流量入口,接收所有的请求,如app、网页等,并且将不同的请求转发至不同的处理微服务模块,其作用可视为nginx。
(2)、feign则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用。
(3)、两者的应用层次以及原理均不相同。
(4)、zuul也含有hystrix和ribbon,基于http通讯的,可以直接代理服务就行。在它和服务间增加feign只会增加通讯消耗,没有特别的意义。feign在服务互相调用的时候用就行了,可以仿rpc通讯。
(5)、Feign主要作客户端流控,Feign的负载均衡是基于Eureka实现的,Zuul主要作服务端流控,并且Zuul的负载均衡结合Eureka实现易用性较好,并且Zuul我一般用在对第三方提供访问接口。
四 具体案例
在以前案例的基础上,我们保持注册中心和服务提供者不变,新建zuul maven工程,如下图:
Pom文件代码如下,重点是引入了spring-cloud-starter-zuul依赖:
<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>com.wx</groupId>
<artifactId>cloud_zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<!-- 使用dependencyManagement进行版本管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 引入eureka依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 引入zuul依赖,它本身又依赖了spring-boot-starter-actuator/spring-boot-starter-hystrix/spring-boot-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 设置通过jdk1.8编译项目 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!-- 拷贝resouce资源到output directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 可通过maven命令运行项目和打包运行 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
</project>
新建ZuulApp启动类,加上@EnableZuulProxy注解,用来启动zuul服务网关,如图所示:
配置文件如图所示:
这里通过zuul.routes.<route>.path和zuul.routes.<route>.url进行配置,可以配置出各种地址访问规则,跟nginx类似,这里就不详述了,一般的项目也没必要有那么复杂的限制。感兴趣可以自行百度。我们一般使用面向服务的路由,我们可以让路由的path不是映射具体的url,而是具体的某个服务,而服务的url则交给Eureka服务发现机制自动维护,这类路由就是面向服务的路由,如上图所示就是配置的面向服务的路由。
依次启动服务:
(1)启动注册中心,端口号911
(2)启动服务提供者1,端口号2221
(3)启动服务提供者2,端口号2222
(4)启动zuul服务,端口号1111
然后随便调用服务提供者中的接口[http://localhost:1111/cartServ/flow/cart/details.do?id=2020211479],运行结果如下:
刷新后:
实际上,zuul帮我们默认做了如下配置:
zuul.routes.<服务名>.path=/<服务名>/**
zuul.routes.<服务名>.serviceId=<服务名>
所有我们上面的配置可以简化成:
接下来我们访问如下地址【http://localhost:1111/cart-service/flow/cart/details.do?id=2020211479】得到的结果跟上面一样。
五、请求过滤
为了在API网关中实现对客户端请求的校验,我们可以通过过滤器来实现对请求的拦截和过滤,实现方法比较简单,只需要继承ZuulFilter抽象类并实现其四个方法就行了。
修改cloud_zuul:
1.新增过滤器类
代码:
package com.wx.filters;
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* 继承ZuulFilter,并且实现其4个接口,用来进行请求过滤
* @author wangxiang
* @date 2020年9月18日
*/
public class AccessFilter extends ZuulFilter{
/*
* shouldFilter 判断该过滤器是否需要被执行
* 这里直接返回true,表示该过滤器对所有请求都会生效。
* 实际运用中我们可以利用该函数指定过滤器的有效范围
*/
@Override
public boolean shouldFilter() {
return true;
}
/*
* 过滤器的具体逻辑
* 这里我们通过ctx.setSendZuulResponse(false)让zuul过来请求,不对其进行路由
* 然后通过ctx.setResponseStatusCode(401)设置了返回的错误码
*/
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
Object accessToken = request.getParameter("token");
System.out.println("当前请求===>"+request.getRequestURL().toString());
if(accessToken == null) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(401);
context.setResponseBody("unAuthrized");
}
return null;
}
/* filterType 返回过滤器类型
* 他决定了过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由前执行。
* pre:请求执行之前filter
* route: 处理请求,进行路由
* post: 请求处理完成后执行的filter
* error:出现错误时执行的filter
*/
@Override
public String filterType() {
return "pre";
}
/*
* filterOrder 返回过滤器的执行顺序
* 当请求在一个阶段有多个过滤器是,需要根据该方法的返回值来依次执行
*/
@Override
public int filterOrder() {
return 0;
}
}
2. 在启动类上把过滤器交给spring管理
3. 运行,输入地址:http://localhost:1111/cart-service/flow/cart/details.do?id=2020211479,结果如图:
输入地址:http://localhost:1111/cart-service/flow/cart/details.do?id=2020211479&token=aa,结果正常
4. Zuul中默认实现的Filter
类型 |
顺序 |
过滤器 |
功能 |
pre |
-3 |
ServletDetectionFilter |
标记处理Servlet的类型 |
pre |
-2 |
Servlet30WrapperFilter |
包装HttpServletRequest请求 |
pre |
-1 |
FormBodyWrapperFilter |
包装请求体 |
route |
1 |
DebugFilter |
标记调试标志 |
route |
5 |
PreDecorationFilter |
处理请求上下文供后续使用 |
route |
10 |
RibbonRoutingFilter |
serviceId请求转发 |
route |
100 |
SimpleHostRoutingFilter |
url请求转发 |
route |
500 |
SendForwardFilter |
forward请求转发 |
post |
0 |
SendErrorFilter |
处理有错误的请求响应 |
post |
1000 |
SendResponseFilter |
处理正常的请求响应 |