微服务网关Zuul
服务网关的概念:
API网关是一个服务器,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供一个定制的API。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
作用和应用场景:
网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。
微服务网关Zuul:
ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。
Spring Cloud对Zuul进行了整合和增强。
Zuul加入后的架构:
搭建Zuul网关服务器:
1.创建工程导入依赖
在IDEA中创建ZUUL网关工程 zuul_server ,并引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <version>2.1.0.RELEASE</version> </dependency>
2.编写启动类
@EnableZuulProxy : 通过 @EnableZuulProxy 注解开启Zuul网管功能
@SpringBootApplication // 开启zuul网关功能 @EnableZuulProxy public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
3.编写配置
创建配置文件 application.yml ,并添加相应配置
server: port: 8080 spring: application: name: zuul-server #服务名称
Zuul 中的路由转发:
最直观的理解:“路由”是指根据请求URL,将请求分配到对应的处理程序。在微服务体系中,Zuul负责接收所有的请求。根据不同的URL匹配规则,将不同的请求转发到不同的微服务处理。
只需要在 application.yml文件中配置路由规则即可:
server:
port: 8080
spring:
application:
name: zuul-server #服务名称
##路由配置
zull:
routes:
#以商品微服务为例
product-server: #路由id,随便写
path: /service-product/** #映射路径
url: http://localhost:9011 #映射路径对应的实际url地址
sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取
#消默认的黑名单,如果设置了具体的头信息则不会传到下游服务
启动服务,在浏览器中输入 http://localhost:8080/service-product/product/1 ,即可访问到商品微服务。
面向服务的路由:
微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。
Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。
1.添加Eureka客户端依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2.开启Eureka客户端发现功能(从Spring Cloud Edgware版本开始, @EnableDiscoveryClient 或 @EnableEurekaClient 可省略。)
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) // 开启zuul网关功能 @EnableZuulProxy @EnableDiscoveryClient public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
3.添加Eureka配置,获取服务信息
##配置eureka eureka: client: serviceUrl: defaultZone: http://127.0.0.1:9000/eureka/ instance: preferIpAddress: true
4.修改映射配置,通过服务名称获取
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。
##路由配置 zuul: routes: #以商品微服务为例 product-server: #路由id,随便写 path: /service-product/** #映射路径 #url: http://127.0.0.1:9011 #映射路径对应的实际url地址 serviceId: service-product #配置转发的微服务名称
5.重启服务,在浏览器上通过访问 http://localhost:8080/service-product/product/1 查看效果(访问路径中必须包含路由配置中的 path 部分)
简化的路由配置:
在刚才的配置中,我们的规则是这样的
zuul.routes.<route>.path=/xxx/** : 指定映射路径。 <route> 是自定义的路由名
zuul.routes.<route>.serviceId=/service - product:指定服务名。
而大多数情况下,我们的 <route> 路由名称(product-server)往往和服务名(service-product)会写成一样的。因此Zuul就提供了一种简化的配置语法:
zuul.routes.<serviceId>=<path>
上面的配置可以简化为一条:
##路由配置 zuul: routes: service-product: /service-product/**
默认的路由规则:
在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。
因此Zuul就指定了默认的路由规则:
默认情况下,一切服务的映射路径就是服务名本身。
例如服务名为: service-product ,则默认的映射路径就是: /service-product/**
添加固定路由前缀:
zuul.prefix: /api
访问的路径:http://localhost:8080/api/order-product/order/buy/1
Zuul 中的过滤器:
Zuul包含了两个核心功能:对请求的路由和过滤。其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。其实,路由功能在真正运行时,它的路由映射和请求转发同样也由几个不同的过滤器完成的。所以,过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
ZuulFilter简介:
Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。
1. PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
2. ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
3. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
4. ERROR:在其他阶段发生错误时执行该过滤器。
ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:
public abstract ZuulFilter implements IZuulFilter{ abstract public String filterType(); abstract public int filterOrder(); boolean shouldFilter();// 来自IZuulFilter Object run() throws ZuulException;// IZuulFilter }
shouldFilter :返回一个 Boolean 值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
run :过滤器的具体业务逻辑。
filterType :返回字符串,代表过滤器的类型。包含以下4种:
pre :请求在被路由之前执行
routing :在路由请求时调用
post :在routing和errror过滤器之后调用
error :处理请求时发生错误调用
filterOrder :通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
过滤器执行生命周期:
正常流程:
-
-
-
请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
-
-
异常流程:
-
-
-
整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
-
如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。
-
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。
-
-
自定义过滤器:
模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
/** * 自定义的zuul过滤器 * 继承抽象父类 */ @Component public class LoginFilter extends ZuulFilter { /** * 定义过滤器类型 * pre * routing * post * error */ public String filterType() { return "pre"; } /** * 指定过滤器的执行顺序 * 返回值越小,执行顺序越高 */ public int filterOrder() { return 1; } /** * 当前过滤器是否生效 * true : 使用此过滤器 * flase : 不使用此过滤器 */ public boolean shouldFilter() { return true; } /** * 指定过滤器中的业务逻辑 * 身份认证: * 1.所有的请求需要携带一个参数 : access-token * 2.获取request请求 * 3.通过request获取参数access-token * 4.判断token是否为空 * 4.1 token==null : 身份验证失败 * 4.2 token!=null : 执行后续操作 * 在zuul网关中,通过RequestContext的上下问对象,可以获取对象request对象 */ public Object run() throws ZuulException { //System.out.println("执行了过滤器"); //1.获取zuul提供的上下文对象RequestContext RequestContext ctx = RequestContext.getCurrentContext(); //2.从RequestContext中获取request HttpServletRequest request = ctx.getRequest(); //3.获取请求参数access-token String token = request.getParameter("access-token"); //4.判断 if (token == null) { //4.1 如果token==null ,拦截请求,返回认证失败 ctx.setSendZuulResponse(false); // 拦截请求 // 响应状态码,401-身份未认证 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } //4.2 如果token!=null ,继续后续操作 return null; } }
RequestContext :用于在过滤器之间传递消息。它的数据保存在每个请求的ThreadLocal中。它用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse都存储在RequestContext中。RequestContext扩展了ConcurrentHashMap,所以,任何数据都可以存储在上下文中
在自定义的过滤器类中需要加上@Component注解,启动测试 ........
访问成功
访问出错