微服务深入浅出(7)-- 网关路由Zuul
Zuul用于构建边界服务,致力于动态路由,过滤,监控,弹性伸缩和安全等方向。
1、Zuul+Ribbon+Eureka结合,可以实现智能路由和负载均衡
2、网关将所有服务的API接口统一聚合统一暴露
3、网关统一爆率接口后,可以做身份和权限认证
4、实现监控功能,实时日志输出
5、流量监控,实现降级和限流
6、方便测试
1、网关存在的必要性
不同的微服务有不同的请求地址,如果一个客户端需要访问多个接口才能完成一个业务需求的话,可能存在以下问题:
# 客户端会多次请求不同微服务,增加客户端的复杂性
# 存在跨域请求,在一定场景下处理相对复杂
# 认证复杂,每一个服务都需要独立认证
# 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通信,那么重构会难以实施
# 某些微服务可能使用了其他协议,直接访问有一定困难

而微服务网关可以很好的解决这个问题:

这样客户端只需要和网关交互,而无需直接调用特定微服务的接口,而且方便监控,易于认证,减少客户端和各个微服务之间的交互次数。
2、主流解决方案
# Spring Cloud Gateway
# Zuul
Zuul基于 servlet 2.5(使用3.x),使用阻塞API。 它不支持任何 长连接 ,如 web sockets。而Gateway建立在Spring Framework 5,Project Reactor和Spring Boot 2之上,使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的开发体验。
参考:https://juejin.im/post/5aa4eacbf265da237a4ca36f
3、模拟场景
客户端请求后端服务,网关提供后端服务的统一入口。后端的服务都注册到Zookeeper、Consul或者Eureka (服务发现、配置管理中心服务)。网关通过负载均衡。转发到具体的后端服务。
4、Zuul
Zuul 提供了四种过滤器的 API,动态读取、编译和运行这些过滤器。过滤器之间不能相互通讯,只能通过RequestContext对象共享数据。
# 前置(Pre)鉴权、请求转发、增加请求参数等行为
一般来说整个服务的鉴权逻辑可以很复杂。
- 客户端:App、Web、Backend
- 权限组:用户、后台人员、其他开发者
- 实现:OAuth、JWT
- 使用方式:Token、Cookie、SSO
而对于后端应用来说,它们其实只需要知道请求属于谁,而不需要知道为什么,所以 Gateway 可以友善的帮助后端应用完成鉴权这个行为,并将用户的唯一标示透传到后端,而不需要、甚至不应该将身份信息也传递给后端,防止某些应用利用这些敏感信息做错误的事情。Zuul 默认情况下在处理后会删除请求的 Authorization 头和 Set-Cookie 头,也算是贯彻了这个原则。
# 后置(Post)统计返回值和调用时间、记录日志、增加跨域头等行为
使用 Gateway 做跨域相比应用本身或是 Nginx 的好处是规则可以配置的更加灵活。例如一个常见的规则。
对于任意的 AJAX 请求,返回
Access-Control-Allow-Origin为*,且Access-Control-Allow-Credentials为true,这是一个常用的允许任意源跨域的配置,但是不允许请求携带任何 Cookie如果一个被信任的请求者需要携带 Cookie,那么将它的
Origin增加到白名单中。对于白名单中的请求,返回Access-Control-Allow-Origin为该域名,且Access-Control-Allow-Credentials为true,这样请求者可以正常的请求接口,同时可以在请求接口时携带 Cookie对于 302 的请求,即使在白名单内也必须要设置
Access-Control-Allow-Origin为*,否则重定向后的请求携带的Origin会为null,有可能会导致 iOS 低版本的某些兼容问题
Gateway 可以统一收集所有应用请求的记录,并写入日志文件或是发到监控系统,相比 Nginx 的 access log,好处主要也是二次开发比较方便,比如可以关注一些业务相关的 HTTP 头,或是将请求参数和返回值都保存为日志打入消息队列中,便于线上故障调试。也可以收集一些性能指标发送到类似 Statsd 这样的监控平台。
# 路由(Route)一般只需要选择 Zuul 中内置的即可
#错误(Error)一般只需要一个,这样可以在 Gateway 遇到错误逻辑时直接抛出异常中断流程,并直接统一处理返回结果
错误过滤器的主要用法就像是 Jersey 中的 ExceptionMapper 或是 Spring MVC 中的 @ExceptionHandler 一样,在处理流程中认为有问题时,直接抛出统一的异常,错误过滤器捕获到这个异常后,就可以统一的进行返回值的封装,并直接结束该请求。
总结关键特性:
1、Type,规定类型
2、Execution Order,规定执行顺序,Order值越小越优先
3、Criteria,规定执行所需要的条件
4、Action,如果符合条件,则执行Action

一个请求会先按顺序通过所有的前置过滤器,之后在路由过滤器中转发给后端应用,得到响应后又会通过所有的后置过滤器,最后响应给客户端。在整个流程中如果发生了异常则会跳转到错误过滤器中。
5、注解配置
/**
* 这个接口需要鉴权,鉴权方式是 OAuth
*/
@Authorization(OAuth)
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
public void del(@PathVariable int id) {
//...
} /**
* 这个接口可以缓存,并且每个 IP/User 每秒最多请求 10 次
*/
@Cacheable
@RateLimiting(limit = "10/1s", scope = {IP, USER})
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public void info(@PathVariable int id) {
//...
}
6、稳定性
# 隔离机制
在 Zuul 中,每一个后端应用都称为一个 Route,为了避免一个 Route 抢占了太多资源影响到其他 Route 的情况出现,Zuul 使用 Hystrix 对每一个 Route 都做了隔离和限流。
Hystrix 的隔离策略有两种,基于线程或是基于信号量。Zuul 默认的是基于线程的隔离机制,这意味着每一个 Route 的请求都会在一个固定大小且独立的线程池中执行,这样即使其中一个 Route 出现了问题,也只会是某一个线程池发生了阻塞,其他 Route 不会受到影响。一般使用 Hystrix 时,只有调用量巨大会受到线程开销影响时才会使用信号量进行隔离策略,对于 Zuul 这种网络请求的用途使用线程隔离更加稳妥。
# 重试机制
Zuul 的路由主要有 Eureka 和 Ribbon 两种方式,简单介绍下 Ribbon 支持哪些容错配置。
重试的场景分为三种:
okToRetryOnConnectErrors:只重试网络错误okToRetryOnAllErrors:重试所有错误OkToRetryOnAllOperations:重试所有操作(这里不太理解,猜测是 GET/POST 等请求都会重试)
重试的次数有两种:
MaxAutoRetries:每个节点的最大重试次数MaxAutoRetriesNextServer:更换节点重试的最大次数
一般来说我们希望只在网络连接失败时进行重试、或是对 5XX 的 GET 请求进行重试(不推荐对 POST 请求进行重试,无法保证幂等性会造成数据不一致)。单台的重试次数可以尽量小一些,重试的节点数尽量多一些,整体效果会更好。
如果有更加复杂的重试场景,例如需要对特定的某些 API、特定的返回值进行重试,那么也可以通过实现 RequestSpecificRetryHandler 定制逻辑(不建议直接使用 RetryHandler,因为这个子类可以使用很多已有的功能)。
7、Tomcat
Tomcat的最大并发数是可以配置的,实际运用中,最大并发数与硬件性能和CPU数量都有很大关系的。更好的硬件,更多的处理器都会使Tomcat支持更多的并发。
Tomcat 默认的HTTP实现是采用阻塞式的Socket通信,每个请求都需要创建一个线程处理,当一个进程有500个线程在跑的话,那性能已经是很低很低了。Tomcat默认配置的最大请求数是150,也就是说同时支持150个并发。具体能承载多少并发,需要看硬件的配置,CPU越多性能越高,分配给JVM的内存越多性能也就越高,但也会加重GC的负担。当某个应用拥有 250个以上并发的时候,应考虑应用服务器的集群。操作系统对于进程中的线程数有一定的限制:
Windows 每个进程中的线程数不允许超过 2000
Linux 每个进程中的线程数不允许超过 1000
在Java中每开启一个线程需要耗用1MB的JVM内存空间用于作为线程栈之用,此处也应考虑。
8、实际应用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动类开启zuul代理:
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class DemoApplication { public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置文件配置路由信息:
server:
port: 9009
spring:
application:
name: zuul-client
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka/
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: hi-service
访问:http://localhost:9009/hiapi/hi,如果hi-service部署了多个实例,那么zuul在路由转发就做了负载均衡。
当然也可以使用url属性代替serviceId属性,通过指定ip+port的方式的url地址来直接访问(当然这种情况很少出现)
如果想自己维护负载均衡的服务列表,可以使用如下方式:
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: hiapi-v1
ribbon:
eureka:
enabled: false
hiapi-v1:
ribbon:
listOfServers: http://localhost:9007,http://localhost:9008,http://localhost:9009/hiapi/hi
配置API接口的版本号:
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: hi-service
prefix: v1
那么访问路径将变为:http://localhost:9009/v1/hiapi/hi
集成Hystrix实现熔断器:
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "hi-service"; // 应用名称或者serviceId,或者是正则表达式,如*
} @Override
public ClientHttpResponse fallbackResponse(String route, final 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("fallback".getBytes());
} @Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
在Zuul中使用过滤器:
@Component
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; // 前置过滤器
}
@Override
public int filterOrder() {
return 0; // 优先级为0,数字越大,优先级越低
}
@Override
public boolean shouldFilter() {
return true; // 是否执行该过滤器,此处为true,说明需要过滤
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
if (StringUtils.isBlank(token)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
} catch (IOException e) { }
}
return null;
}
}
微服务深入浅出(7)-- 网关路由Zuul的更多相关文章
- .net core 微服务之Api网关(Api Gateway)
原文:.net core 微服务之Api网关(Api Gateway) 微服务网关目录 1. 微服务引子 2.使用Nginx作为api网关 3.自创api网关(重复轮子) 3.1.构建初始化 3.2. ...
- SOA与ESB,微服务与API网关
SOA与ESB,微服务与API网关 SOA: ESB: 微服务: API网关: 参考资料: 1.漫画微服务,http://www.sohu.com/a/221400925_100039689 2.SO ...
- 微服务·API网关
阅文时长 | 3.52分钟 字数统计 | 1232字符 主要内容 | 1.什么是API网关 2.微服务中的API网关 3.几种部署策略 『微服务·API网关』 编写人 | SCscHero 编写时间 ...
- AspNetCore微服务下的网关-Kong(一)
Kong是Mashape开源的高性能高可用API网关和API服务管理层.它基于OpenResty,进行API管理,并提供了插件实现API的AOP.Kong在Mashape 管理了超过15,000 个A ...
- 一站式入口服务|爱奇艺微服务平台 API 网关实战 原创 弹性计算团队 爱奇艺技术产品团队
一站式入口服务|爱奇艺微服务平台 API 网关实战 原创 弹性计算团队 爱奇艺技术产品团队
- .NET Core微服务之基于Steeltoe集成Zuul实现统一API网关
Tip: 此篇已加入.NET Core微服务基础系列文章索引,本篇接上一篇<基于Steeltoe使用Eureka实现服务注册与发现>,所演示的示例也是基于上一篇的基础上而扩展的. => ...
- 基于spring-cloud的微服务(4)API网关zuul
API网关是微服务架构中的很重要的一个部分,内部有多个不同的服务提供给外部来使用,API网关可以对外做统一的入口,也可以在网关上做协议转换,权限控制和请求统计和限流等其他的工作 spring-clou ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_6-06 zuul微服务网关集群搭建
笔记 6.Zuul微服务网关集群搭建 简介:微服务网关Zull集群搭建 1.nginx+lvs+keepalive https://www.cnblogs.com/liuyisai/ ...
- Net分布式系统之六:微服务之API网关
本人建立了个人技术.工作经验的分享微信号,计划后续公众号同步更新分享,比在此更多具体.欢迎有兴趣的同学一起加入相互学习.基于上篇微服务架构分享,今天分享其中一个重要的基础组件“API网关”. 一.引言 ...
随机推荐
- 如何获取app的activity
(一) 本机安装appium的环境后,在cmd中输入:adb logcat>D:/log.log (二)真题连接电脑或在虚拟机中启动被测试app (三)直接查看d盘的log.log即可从来里面找 ...
- 性能分析_linux服务器CPU_中断
中断 1. 指标范围 1.1 Interrupt rate 应该与cpu利用率结合分析,如果cpu利用率在合理范围内,大量的中断也是可以接受的.一个巨大的中断值,同时伴随着缓慢的系统性能表现,指示 ...
- pygame学习笔记(2)——从画点到动画
转载请注明:@小五义 http://www.cnblogs.com/xiaowuyi 1.单个像素(画点)利用pygame画点主要有三种方法:方法一:画长宽为1个像素的正方形 #@小五义 http:/ ...
- 每秒更新时间 v-text的应用 (解决闪现{}问题)
有闪现<div id="app"> {{date}}</div> 无闪现<div id="app" v-text:date=&qu ...
- TCP/IP协议三次握手和四次挥手大白话解说
前言 昨天晚上被一位师傅问到了TCP/IP的工作机制,心里很清楚三次握手,然而对于四次挥手却忘了,这是大学习里学过的,奋而翻阅书籍和网络对之前所学的做一个温顾,算是夯实自我吧. TCP(Transmi ...
- 关于request对象的parameter和attribute
request对象的parameter相关method用于浏览器和服务之间传递数据,且是单向的,只能由浏览器写数据,request读数据,所以只有 String getParameter(String ...
- CSS实现点击改变元素背景色
可通过使用css伪类实现点击元素变色的效果,两个伪类是:active, :focus :active :active选择器用于选择活动链接.当在一个链接上点击时,它就会成为活动的(激活的),:acti ...
- Redis:主从复制
前言 在前面的两篇文章中,分别介绍了Redis的内存模型和Redis的持久化. 在Redis的持久化中曾提到,Redis高可用的方案包括持久化.主从复制(及读写分离).哨兵和集群.其中持久化侧重解决的 ...
- 【Django】用pycharm初学习使用Django
开发框架流程 M V C(99%的开发都是这种流程.) 1.URL控制器 2.Views 视图 3.models 库 1.首先创建一个Django 2.创建成功后里面几个模块的功能 用它来 ...
- 对HashMap的理解(三):ConcurrentHashMap
HashMap不是线程安全的.在并发插入元素的时候,有可能出现环链表,让下一次读操作出现死循环.避免HashMap的线程安全问题有很多方法,比如改用HashTable或Collections.sync ...