什么是API网关
在微服务架构中,通常会有多个服务提供者。设想一个电商系统,可能会有商品、订单、支付、用户等多个类型的服务,而每个类型的服务数量也会随着整个系统体量的增大也会随之增长和变更。作为UI端,在展示页面时可能需要从多个微服务中聚合数据,而且服务的划分位置结构可能会有所改变。网关就可以**对外暴露聚合API,屏蔽内部微服务的微小变动,**保持整个系统的稳定性。
当然这只是网关众多功能中的一部分,它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。
zuul是什么
Zuul包含了对请求的路由和过滤两个最主要的功能:
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合,认证和安全,性能监测,压力测试等功能的基础.
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
所以Zuul的功能是:代理+路由+过滤
zuul路由的基本配置
pom.xml导入依赖
<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>
配置文件部署到eureka
server:
port: 9527
spring:
application:
name: microservicecloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka,http://localhost::7002/eureka
instance:
instance-id: gateway-9527.com
prefer-ip-address: true
主启动类
@SpringBootApplication
@EnableZuulProxy
public class Zuul_9527_StartSpringCloudApp
{
public static void main(String[] args)
{
SpringApplication.run(Zuul_9527_StartSpringCloudApp.class, args);
}
}
启动后你可以测试一下使用路由和不适用路由两种方式
不使用路由就是直接调用子服务的URI
使用路由就是调用路由的项目+子服务的instantId+URI
http://localhost:8001/dept/get/2
http://localhost:9527/microservicecloud-dept/dept/get/2
zuul路由访问映射规则
可以通过配置路由,实现代理名称
zuul:
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**
routes下直接定义microservicecloud-dept 的叫mydept,只要是/mydept/这个路径下的都能访问到这个服务,最终效果两个都可以访问
http://myzuul.com:9527/mydept/dept/get/1
http://myzuul.com:9527/microservicecloud-dept/dept/get/2
如果想要忽略掉原真实服务名,可以用这个配置ignored-services
zuul:
ignored-services: microservicecloud-dept
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**
忽略多个可以直接用"*"
zuul:
ignored-services: "*"
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**
如果你希望在路由接口前面有一个统一公共前缀,可以使用prefix属性
zuul:
prefix: /gateway
ignored-services: "*"
routes:
mydept.serviceId: microservicecloud-dept
mydept.path: /mydept/**
http://myzuul.com:9527/gateway/mydept/dept/get/1
zuul的Filter
Zuul是围绕一系列Filter展开的,这些Filter在整个HTTP请求过程中执行一连串的操作,各个Filter间没有直接联系,但是都通过RequestContext共享一些状态数据。可以对过滤器进行动态的加载,编译,运行。
Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用。
Zuul Filter有以下几个特征:
- Type:用以表示路由过程中的阶段(内置包含PRE、ROUTING、POST和ERROR)
- Execution Order:表示相同Type的Filter的执行顺序
- Criteria:执行条件
- Action:执行体
Filter Types
以下提供几种标准的Filter类型及其在请求生命周期中所处的位置:
- PRE Filter:在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录。
- ROUTING Filter:处理目标请求。这里使用Apache HttpClient或Netflix Ribbon构造对目标的HTTP请求。这类filter可能做的事:真正的向service的一台server(这台server是pre filter选出来的)发请求。
- POST Filter:在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
- ERROR Filter:整个流程某块出错时执行。
- CUSTOM Filters:沿着默认的filter流,zuul允许我们创建一些自定义的Filter type,并且准确的执行他们。
例如:我们自定义一个STATIC type的filter,用于从zuul直接产生响应,而不是从后边的
除了上述默认的四种Filter类型外,Zuul还允许自定义Filter类型并显示执行。例如,我们定义一个STATIC类型的Filter,它直接在Zuul中生成一个响应,而非将请求在转发到目标。
Zuul请求生命周期
添加一个PreRequestLogFilter:
public class PreRequestLogFilter extends ZuulFilter{
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
System.out.print(String.format("send %s request to %s",request.getMethod(),request.getRequestURL()));
return null;
}
}
配置类将filter注入
@Bean
public PreRequestLogFilter preRequestLogFilter(){
return new PreRequestLogFilter();
zuul的两种隔离机制
总概里说了zuul允许使用信号量隔离(semaphore)和线程池隔离(ThreadPool)
为什么要进行隔离
服务调用方隔离方式(也可以叫资源隔离)
针对于服务调用方,如果不做隔离会出现以下情况。
一个请求过来,占用支付服务中的Tomcat的一个线程。然后,该线程去顺序调用订单服务和库存服务!如果库存服务出现问题,Tomcat的线程就一直卡在那,无法返回!与此同时,页面上源源不断的有请求过来,会把Tomcat里头的线程池资源全部消耗完毕!对于后面的请求,Tomcat就无法响应!
因此,如果不针对被调服务做服务隔离,一个被调服务出问题,就将导致调用方服务不可用!
避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上,导致调用方服务不可用,崩溃,蔓延。
而针对于这种调用方隔离的方式有两种,多个依赖服务的调用分别隔离到各自自己的资源池内。一种是线程池隔离,一种是信号量隔离。
线程池隔离(ThreadPool)
给每个微服务都初始化出一个线程池。
如下图所示,给订单服务和库存服务都初始化出一个线程池,不使用Tomcat线程池中的线程直接调用,而是用相应线程池中的线程去调用!如果库存服务不可用了,库存服务线程池会被迅速塞满,此时后面进来的新请求发现库存服务线程池满啦,就不去调库存服务,直接返回!
信号量隔离(semaphore)
适应非网络请求,因为是同步的请求,无法支持超时,只能依靠协议本身
每次调用线程,当前请求通过计数信号量进行限制,当信号大于了最大请求数(maxConcurrentRequests)时,进行限制,调用fallback接口快速返回。信号量的调用是同步的,所有的请求都将经过信号队列的计数器。
每次调用都得阻塞调用方的线程,直到结果返回。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)
两种分别的应用场景
线程池:适合绝大多数的场景,线程池资源隔离一般用于对依赖服务的网络请求的调用和访问,timeout这种问题。每个command运行在一个线程中,限流是通过线程池的大小进行控制的。
信号量:适合对内部的一些比较复杂的业务逻辑的访问,但是像这种访问,系统内部的代码其实不涉及任何的网络请求。那么只要做信号量的普通限流就可以了,因为不需要去捕获timeout类似的问题,算法+数据结构的效率不是太高,并发量突然太高,因为这里稍微耗时一些,导致很多线程卡在这里的话,不太好,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率的代码,导致大量的线程被hang住。command是运行在调用线程中,但是通过信号量的容量来进行限流。
zuul的隔离
默认的隔离级别是信号量,默认最大隔离信号量是100
zuul默认对每个路由直接用了信号量隔离,默认值是100,当一个路由请求的信号量高于100那么就拒绝服务了,返回500。zuul里隔离是按服务隔离的,也就是1个服务1个信号量,非接口级别的.
zuul:
routes:
linkflow:
path: /api1/**
serviceId: lf
semaphore:
maxSemaphores: 2000
- semaphore.maxSemaphores信号隔离的默认隔离大小
- zuul.ribbonIsolationStrategy: SEMAPHORE隔离级别指定
- zuul.eureka.serviceId.semaphore.maxSemaphores = 20指定服务的信号隔离级别大小
请注意,这三个配置会覆盖hystrix原生的配置,即hytrix.command.default.execution.isolation.strategy和maxConcurrentRequests