在微服务架构中,后端的服务会存在多个。如果没有API Gateway就会存在以下的问题:
- 客户端需要知道每个每个微服务的存在
- 一次业务场景的交互需要发多次请求到多个微服务
- 不同的微服务调用协议有可能是不同的
- 每个微服务都需要进行权限校验
针对以上的需求,我们引入API Gateway。这里介绍基于Zuul的APIGateway的配置。
在gradle中添加依赖
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 1.8
ext {
springCloudVersion = 'Finchley.SR1' #注意cloud的版本
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
compile 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
compile('org.springframework.boot:spring-boot-starter')
}
在Application中添加@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
添加Zuul路由配置
application.yml文件的配置:
server:
port: 8900
zuul:
strip-prefix: true #是否在转发时去掉路由前缀
prefix: /api #全局到为路由规则增加前缀
sensitive-headers: Cookie, Set-Cookie #默认情况下Zuul在路由请求时会将Cookie、Set-Cookie和Authorization给过滤调。由于下游服务可能需要Authorization信息,所以在这里进行自定义不过滤掉Authorization。
ignored-headers: g1 #转发时忽略的hearder
ignored-patterns: /**/goods/user #配置不希望API网关进行路由的路径
routes:
goods:
path: /goods/** #路径为goods的都会路由到goods服务中
# serviceId: mst-goods-service #如果有服务注册中心可以使用serviceId。goods服务在Consul注册的service name
url: http://127.0.0.1:8902
stripPrefix: false #不移除路径前缀,即https://localhost:8900/goods/会被路由到goods服务到/goods路径,如果为true,则会被路由到goods服务到/(根路径)路径中
host:
connect-timeout-millis: 20000
socket-timeout-millis: 20000
semaphore:
maxSemaphores: 100 # 指任意时间点允许的并发数。当请求达到或超过该设置值后,其其余就会被拒绝。默认值是100。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 20000 #当路由转发当请求命令的时间超过该值后Hystrix会将该执行命令标记为超时并抛出异常。
ribbon:
ServerListRefreshInterval: 1000 #设置多久刷新一次server list
ReadTimeout: 20000 #用来这时请求连接建立之后的处理超时时间。
ConnectTimeout: 20000 #用来设置路由转发请求的时候创建请求连接的超时时间
超时设置
在上述的配置中有三种配置超时的方式,zull.host、hystrix和ribbon。
zull.host和ribbon都是配超时的。区别在于,如果路由方式是serviceId的方式,那么ribbon的生效,如果是url的方式,则zuul.host开头的生效。也就是说使用serviceId路由和url路由是不一样的超时策略。
###stripPrex介绍
在这里再次介绍下stripPrefix,stripPrex为true时表示是否在转发时将匹配的前缀移除。比如:
zuul:
routes:
goods:
path: /goods/**
url: http://127.0.0.1:8902
stripPrefix: false
prefix: /api
strip-prefix: true
如果请求:https://localhost:8900/api/goods/user 路由到goods服务的goods/user
zuul:
routes:
goods:
path: /goods/**
url: http://127.0.0.1:8902
stripPrefix: true
prefix: /api
strip-prefix: true
如果请求:https://localhost:8900/api/goods/user 路由到goods服务的/user
zuul:
routes:
goods:
path: /goods/**
url: http://127.0.0.1:8902
stripPrefix: true
prefix: /api
strip-prefix: false
如果请求:https://localhost:8900/api/goods/user 路由到goods服务的/api/user
zuul:
routes:
goods:
path: /goods/**
url: http://127.0.0.1:8902
stripPrefix: false
prefix: /api
strip-prefix: false
如果请求:https://localhost:8900/api/goods/user 路由到goods服务的/api/goods/user
Filters
Zuul提供了如下几种类型的过滤器:
- PRE:这种过滤器在请求被路由之前被调用。在该过滤器中可以做一些比如身份验证的事情。
- ROUTING:这种过滤器将请求发送给最终的服务。通常来说该类过滤器使用默认的即可。
- POST:这种过滤器在用户请求从Origin Server返回以后执行。比如在返回的response上面加response header。
- ERROR:在其他阶段发生错误时执行该过滤器。该类过滤器定义一个即可。
请求到来时会先按照指定的顺序通过所有的前置过滤器,然后经由ROUTING路由过滤器转发给最终的服务,在获取到相应后会通过所有所有的后置过滤器。在
这些过程中出现任何问题都会跳转到错误过滤器中。在这些过滤器中通过一个RequestContext的静态类来进行数据传递的。RequestContext类中会记录和
request、response相关的数据。
RequestContext
- getRequest: 获取HttpServletRequest
- getResponse: 获取HttpServletResponse
- setSendZuulResponse: 如果设置为false,则不需要进行路由,也就是不会调用api服务提供者。如果在pre filter中身份校验失败,则可以设置false
并自定义response。 - setResponseBody: 设置返回给客户端的response
- setResponseStatusCode: 设置状态码
pre filter
pre filter使用如下:
@Component
public class RequestFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientHandler.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
HttpServletRequest httpServletRequest = getHttpServletRequest();
String requestURI = httpServletRequest.getRequestURI();
return requestURI.contains("goods");
}
private HttpServletRequest getHttpServletRequest() {
RequestContext currentContext = RequestContext.getCurrentContext();
return currentContext.getRequest();
}
@Override
public Object run() throws ZuulException {
HttpServletRequest httpServletRequest = getHttpServletRequest();
logger.info("request method {} URI {}", httpServletRequest.getMethod(),httpServletRequest.getRequestURI());
return null;
}
}
- filterOrder: 指定filter被执行的顺序,值越小越先被执行。
- shouldFilter: 指定该filter是否会被执行,为true时会执行run方法指定的逻辑,为false时跳过该filter,不执行run方法。
- run: 该filter的核心逻辑,可以在该filter中执行比如身份校验的工作。
post filter
post filter使用如下:
@Component
public class ResponseFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientHandler.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = currentContext.getResponse();
logger.info("Zuul get response, http status is: [{}]", servletResponse.getStatus());
return null;
}
}
在该filter中可以获取请求的返回信息,比如状态码、response等。