参考github wiki:https://github.com/Netflix/zuul/wiki
一、什么是Zuul
Zuul是从设备和网站到Netflix流应用程序后端的所有请求的前门。作为边缘服务应用程序,Zuul旨在实现动态路由,监控,弹性和安全性。
特点
- 身份验证和安全性
- 观察与监控
- 动态路由
- 压力测试
- 负载
- 静态响应处理
- 多区域弹性
二、架构原理
-
工作原理
Zuul的中心是一系列的过滤器,能够在请求和响应的路由过程中执行一系列操作。
Zuul提供了一个支持动态读取、编译和运行过滤器的框架。且过滤器之间不互相通信,而是通过RequestContext共享状态,且RequestContext 对于每个请求都是唯一的(ThreadLocal)。
-
核心流程
-
核心功能
-
服务发现
- 支持与 Eureka 无缝集成
- 支持静态服务类别以发现服务
-
负载均衡
-
连接池
-
状态类别
-
重试
-
申请护照
-
申请重试
-
原始并发保护
-
相互TLS
-
代理协议
-
GZip压缩
-
-
内置Filter说明
Zuul内置了四种不同生命周期的过滤器类型,且过滤器之间不“直接”互相通信,而是通过RequestContext共享状态。开发人员可以通过使用zuul来创建各种校验规则的过滤器。
Zuul RequestContext
为了在过滤器之间传递信息,Zuul使用了
RequestContext
。其数据保存在ThreadLocal
每个请求的特定数据中过滤器类型如下
- pre-filter(s):在请求被路由之前调用
- route-filter(s):在路由请求时调用
- error-filter(s):在处理请求发生错误时调用
- post-filter(s):在route和error过滤器被调用之后调用
Zuul请求的一个生命周期如下图
二、Zuul-Instance-Demo说明
1. 简介
Demo只有服务注册中心、网关服务、服务提供方
网关的配置
spring:
application:
name: zuul-api-gateway
redis:
cluster:
nodes: 192.168.0.201:7000
max-redirects: 3
password: 123456
jedis:
pool:
max-idle: 10
max-active: 500
max-wait: 1000
server:
port: 1001
eureka:
client:
service-url:
defaultZone: http://localhost:1001/eureka/
# 单实例配置:zuul.routes..path与zuul.routes..serviceId 参数对的方式配置
zuul:
# # 过滤客户端附带的headers
# sensitive-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
# # 过滤网关内服务之间通信所附带的headers
# ignored-headers: Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials,Access-Control-Allow-Headers,Access-Control-Expose-Headers,Access-Control-Max-Age
# 限流設置
ratelimit:
enabled: true
# 配置60秒内请求超3次,网关则抛异常,且60s后可恢复正常请求
default-policy-list:
- limit: 3
quota: 2
refresh-interval: 60
repository: Redis
# 路由设置
routes:
# 将对符合/api/** 规则的请求路径转发到服务名为eureka-provider的服务实例上
service:
path: /api/**
serviceId: eureka-provider
# 当访问格式如:http://localhost:port/服务名/请求路径 时,若遇到服务名太长,可如下做修改
eureka-provider: /p/**
# 前缀,所有服务调用需在方法路径前加/api
# prefix:/api
# 排除服务
ignored-services: eureka-provider1
# 设置超时时间,ribbon和hystrix能够同时生效,且取两者的最小值
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
ribbon:
# 该参数用来设置路由转发请求的超时时间
ReadTimeout: 4000
# 该参数用来设置路由转发请求的时候,创建请求连接的超时时间
ConnectTimeout: 4000
# 最大自动重试次数
MaxAutoRetries: 1
# 最大自动重试下一个服务的次数
MaxAutoRetriesNextServer: 1
eureka:
enabled: true
2. 准备
-
服务端口及参数定义
启动服务 端口 运行参数 eureka-server 1000 无 zuul-api-gateway 1001 无 eureka-provider 1101 server.port=1101
thread.sleep-ms=500eureka-provider 1102 server.port=1102
thread.sleep-ms=1500eureka-provider 1103 server.port=1103
thread.sleep-ms=10000
eureka.instance.metadata-map.publish=gray注意:1103的实例配置的休眠时间是10秒,网关配置的超时时间是4秒必定会超时
-
配置服务提供方
以IDEA为例,打开Maven面板,依次打开
eureka-provider——Plugins——spring-boot
,右击选择spring-boot:run
项
依次添加三个provider实例,按以上表格的运行参数分别配置
3. 运行
- 启动注册中心
- 启动网关
- 分别运行第二步配置的三个provider实例
- 运行网关下的测试类TestZuul.java
三、使用开发
1. 引入依赖
org.springframework.boot
spring-boot-starter-parent
2.1.5.RELEASE
1.0.0
2.1.2.RELEASE
2.1.5.RELEASE
org.springframework.cloud
spring-cloud-context
${cloud.version}
org.springframework.cloud
spring-cloud-starter-netflix-zuul
${cloud.version}
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
${cloud.version}
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
${cloud.version}
org.springframework.cloud
spring-cloud-dependencies
Greenwich.RELEASE
pom
import
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
UTF-8
1.8
org.apache.maven.plugins
maven-surefire-plugin
true
2. zuul配置
-
步驟一:创建微服务网关:zuul-api-gateway
这里默认已有eureka注册中心
eureka-server
和服务提供方eureka-provider
。服务提供方的某个controller
@RestController @RequestMapping("/test/") public class TestController { @GetMapping("hello/{name}") public String hello(@PathVariable String name) throws InterruptedException { Thread.sleep(5* 1000); return "hello " + name; } }
-
步骤二:配置文件
spring: application: name: zuul-api-gateway server: port: 1000 eureka: client: service-url: defaultZone: http://localhost:1001/eureka/ # 单实例配置:zuul.routes.
.path与zuul.routes. .serviceId 参数对的方式配置 zuul: routes: # 将对符合/api/** 规则的请求路径转发到服务名为eureka-provider的服务实例上 service: path: /api/** serviceId: eureka-provider # 当访问格式如:http://localhost:port/服务名/请求路径 时,若遇到服务名太长,可如下做修改 eureka-provider: /p/** # 前缀,所有服务调用需在方法路径前加/api # prefix:/api # 排除服务 ignored-services: eureka-provider1 -
步骤三:添加启动类
@SpringCloudApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
这里解释以下
@EnableZuulServer
和@EnableZuulProxy
的区别-
@EnableZuulServer
普通版Zuul Server, 支持基本的router和filter功能
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ZuulServerMarkerConfiguration.class}) public @interface EnableZuulServer { }
-
@EnableZuulProxy
增强版Zuul Server,在普通版的基础上,结合eureka+ribbon+增加服务发现与熔断等功能
@EnableCircuitBreaker @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Import({ZuulProxyMarkerConfiguration.class}) public @interface EnableZuulProxy { }
详细可查看ZuulProxyConfiguration
-
-
步骤四:启动
访问eureka注册中心后台:http://localhost:1001/,可以看到eureka上面注册了服务提供方和zuul网关
访问方式:
- http://localhost:1000/eureka-provider/test/hello/jerry
- http://localhost:1000/api/test/hello/jerry
正常即可转发到服务提供方eureka-provider
。
3. 设置超时时间
Zuul 内部使用了 Ribbon 做负载均衡,它的默认超时时间是1s,当执行一些比较长的请求,会被当做超时处理,返回504
在配置文件内添加如下配置
# 设置超时时间,ribbon和hystrix能够同时生效,且取两者的最小值
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
ribbon:
ReadTimeout: 30000
ConnectTimeout: 30000
4. 自定义熔断降级策略
当Zuul中给定的路径发生错误时,可以通过创建自定义FallbackProvider来提供熔断响应,而Zuul默认的响应不太友好(比如上个point的超时错误返回504)。
-
自定义熔断处理类
@Component public class TestFallbackProvider implements FallbackProvider { /** * 指定为哪个微服务提供回退功能,*表示所有微服务 * @return */ @Override public String getRoute() { return "eureka-provider"; } /** * 返回体 * @param route * @param cause * @return */ @Override public ClientHttpResponse fallbackResponse(String route, 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() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("服务暂时不可用,要不等一会再试试?".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
5. 自定义Filter
通过继承ZuulFilter,即可实现过滤器机制。
以下自定义过滤器用于校验接口是否传递了token
@Slf4j
@Component
public class AccessFilter extends ZuulFilter {
/**
* 四种不同生命周期的过滤器类型
* 1. pre:在请求被路由之前调用
* 2. route:在路由请求时被调用
* 3. post:在route和error过滤器之后被调用
* 4. error:处理请求时发生错误时被调用·
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤的优先级,数字越大,优先级越低。
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* @return 该过滤器是否需要被执行
*/
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");
if(Objects.isNull(token)) {
log.warn("token is empty");
// 让zuul过滤该请求,不对其进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info("token is ok");
return null;
}
}
访问方式:
- http://localhost:1000/eureka-provider/test/hello/jerry?token=111
- http://localhost:1000/api/test/hello/jerry?token=111
正常即可转发到服务提供方eureka-provider
。
6. 限流
-
引入依赖包
spring-cloud-zuul-ratelimit
,支持与zuul整合提供分布式限流策略、github地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
com.marcosbarbero.cloud spring-cloud-zuul-ratelimit 2.2.4.RELEASE -
限流粒度:
粗粒度:
- 网关限流
- 单个服务限流
细粒度:
- url:对请求的目标url进行限流
- origin:对请求来源ip进行限流
- user:对特定用户(比如系统的非vip用户)进行限流
- serviceId:对特定服务id进行限流
-
限流统计数据存储
ConsulRateLimiter:
Consul
RedisRateLimiter:
Redis
org.springframework.boot spring-boot-starter-data-redis SpringDataRateLimiter:
Spring Data
Bucket4jJCacheRateLimiter:
Bucket4j
Bucket4jHazelcastRateLimiter:
Bucket4j
Bucket4jIgniteRateLimiter:
Bucket4j
Bucket4jInfinispanRateLimiter:
Bucket4j
-
限流配置模板
zuul: ratelimit: key-prefix: your-prefix # 开启限流 enabled: true # 存储类型,用于存储统计信息(对于不同的存储类型,需要在pom添加不同的依赖) repository: REDIS behind-proxy: true add-response-headers: true default-policy-list: #optional 全局配置 - limit: 10 #optional - 单位时间内窗口的请求数限制 quota: 1000 #optional - 单位时间内窗口的请求总时间限制 refresh-interval: 60 # 单位时间设置 type: #optional - user # 通过登录用户区分 - origin # 通过请求ip区分 - url # 通过请求路径区分 - httpmethod # 通过请求类型区分 ################################################################# policy-list: # 局部配置(对特定的服务id进行限流) myServiceId: - limit: 10 #optional - request number limit per refresh interval window quota: 1000 #optional - request time limit per refresh interval window (in seconds) refresh-interval: 60 #default value (in seconds) type: #optional - user - origin - url - type: #optional value for each type - user=anonymous - origin=somemachine.com - url=/api #url prefix - role=user - httpmethod=get #case insensitive
-
-
测试
这里选用redis作为网关的数据存储,需要引入redis的依赖包,并且配置redis和ratelimit
spring: application: name: zuul-api-gateway redis: cluster: nodes: 192.168.0.201:7000 max-redirects: 3 password: 123456 jedis: pool: max-idle: 10 max-active: 500 max-wait: 1000 zuul: # 开启全局配置限流 ratelimit: enabled: true # 配置60秒内请求超3次,网关则抛异常,且60s后可恢复正常请求 default-policy-list: - limit: 3 quota: 2 refresh-interval: 60 repository: Redis
结果如下:
4. 负载均衡
5. 路由重试
引入依赖
org.springframework.retry
spring-retry
配置 zuul.retryable=true
zuul-demo-provider: #指定服务
ribbon:
MaxAutoRetries: 0 #本服务重试次数
MaxAutoRetriesNextServer: 1 #重试下一个服务个数
ReadTimeout: 1000
ConnectTimeout: 3000
6. 权限集成
参考自定义Filter的实现方式。
7. 灰度发布
8. 开启跨域
注意:当网关配置了跨域处理后,内部服务则不需要配置
配置过滤请求头
zuul:
# 过滤客户端附带的headers
sensitive-headers: Access-Control-Allow-Origin
# 过滤网关内服务之间通信所附带的headers
ignored-headers: Access-Control-Allow-Origin
配置解決跨域访问问题
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setMaxAge(18000L);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
感谢阅读,Ending...