人生必须的知识就是引人向光明方面的明灯。这篇文章主要讲述微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#相关的知识,希望能为你提供帮助。
@[TOC](6.1 使用 Zuul 进行服务路由)
前言Zuul 是 Netflix 开源的一个 API Gateway 服务器, 本质上是一个 Web servlet 应用;
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门;
1. Zuul 基础知识
1.1 Zuul 是什么
- Zuul 是一种提供动态路由、监视、弹性、安全性等功能的边缘服务;
- Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器;
- 将应用程序中的所有服务的路由映射到一个 URL,即所有客户端调用都经过这个路口;
- 构建可以对通过网关的请求进行检查和操作的过滤器;
- 简单来说就是:代理、路由、过滤;
- 前置过滤器:在请求被路由到目标服务前执行;
- 用于:统一消息格式、用户验证和授权、打印日志等;
- 路由过滤器:在请求被路由到目标服务时执行;
- 用于:确定是否需要进行某些级别的动态路由;(灰度发布等)
- 后置过滤器:在请求被路由到目标服务后执行;
- 用于:记录响应信息、给目标服务的响应添加头信息、收集统计数据;
文章图片
2. 构建 Zuul 网关服务 2.1 引入 pom.xml 依赖
<
!-- zuul相关依赖jar包 -->
<
dependency>
<
groupId>
org.springframework.cloud<
/groupId>
<
artifactId>
spring-cloud-starter-zuul<
/artifactId>
<
/dependency>
<
!-- 监控相关,用于查看路由信息相关 -->
<
dependency>
<
groupId>
org.springframework.boot<
/groupId>
<
artifactId>
spring-boot-starter-actuator<
/artifactId>
<
/dependency>
2.2 修改 bootstrap.yml 配置文件
- 使用自动映射路由不需要做其他特殊配置;
- 其他特殊配置参考本篇《2.4 配置路由》;
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/#开启查看路由的端点
management:
endpoints:
web:
exposure:
include: routes
2.3 在主启动类上添加注解
- @EnableZuulProxy:表示该服务为一个 Zuul 服务器(常用);
- 开发人员需要使用 Zuul 与 Eureka 等服务注册中心(如 Consul )进行集成的时使用;
- @EnableZuulServer:也是表示该服务为一个 Zuul 服务器(不加载任何 Zuul 反向代理过滤器,也不使用 Nettlix Eureka 进行服务发现);
- 开发人员想要构建自己的路由服务,而不使用任何 Zuul 预置的功能时使用;
- 不需要做额外配置;
- 访问
http://localhost:zuul-port/routes
; - 通过 Zuul 注册的服务映射展示在左边,路由映射到实际的 Eureka 服务 ID 展示在右边;
- Zuul 将基于 Eureka 服务的 ID 来公开服务,如果服务的实例没有在运行 , Zuul 将不会公开该服务的路由;
- 【微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#】需要在 application.yml 里进行配置:
zuul: routes: xxxservice: /xxx/**
- 配置前原接口:
/xxxservice/**
; - 配置后的接口:
/xxx/**
; - 可以配置不存在的服务路由,但当尝试为不存在的服务调用路由时会返回 500 错误;
- 上述路由将包括自动路由和手动路由两种路由规则,如果要排除自动路由需要添加
ignored-services
属性:
zuul: ignored-services: xxxservice routes: xxxservice: /xxx/**
- ignored-services: * 将排除所有自动映射路由;
- 还可以给给路由添加前缀,用来区分 API 路由与内容路由:
zuul: ignored-services: xxxservice prefix: /api routes: xxxservice: /xxx/**
- 上述配置后,路由接口将变为:
/api/xxxservice/**
;
- 用来路由那些不受 Eureka 管理的服务,可以建立 Zuul 直接路由到一个静态定义的 URL;
zuul: routes: yyy:#Zuul 用于在内部识别服务的关键字 path: /yyy/**#许可证服务的静态路由 url: http://yyyservice-ip:yyyservice-port#已建立许可证服务的实例,它将被直接调用,而不是由 Zuul 通过 Eureka 调用
- 由于不使用 Eureka,因此需要手动配置 Zuul 来禁用 Ribbon 与 Eureka 集成,列出 Ribbon 将进行负载均衡的各个服务实例。配置如下:
zuul: routes: yyy: path: /yyy/** serviceId: yyy#定义一个服务 ID,该 ID 将用于在 Ribbon 中查找服务 ribbon: eureka: enabled: false#在 Ribbon 中禁用 Eureka 支持 yyy: ribbon: listOfServers: http://yyyservice-ip-1:yyyservice-port-1, http://yyyservice-ip-2:yyyservice-port-2#指定请求会路由到服务器列表
- 为什么要使用 Ribbon:支持 Ribbon 的服务客户端会在本地缓存服务实例的位置,不需要每次解析服务位置时都调用 Eureka;
- 其允许在不回收 Zuul 服务器的情况下更改路由的映射。现有的路由可以被快速修改,以及添加新的路由;
- 动态配置相关需要结合《2.1 使用 Spring Cloud Config 管理服务配置项》篇里的使用 git 外部化微服务配置;
- 将配置文件交给 git 后,我们直接修改配置文件即可,不需要做额外配置;
- Zuul 使用 Netflix 的 Hystrix 和 Ribbon 库;
- Hystrix 会对需要超过 1s 的服务请求会返回一个 HTTP 500 错误;
- Ribbon 则会处理超过 5s 的服务请求;
- 可以使用如下配置进行覆盖:
- 修改所有服务的 Hystrix 默认超时为 2.5s:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500
; - 修改特定服务(xxx)的 Hystrix 默认超时为 2.5s:
hystrix.command.xxx.execution.isolation.thread.timeoutInMilliseconds: 2500
; - 修改特定服务(xxx)的 Ribbon 默认超时为 7s:
xxx.ribbon.ReadTimeout: 7000
;
- 修改所有服务的 Hystrix 默认超时为 2.5s:
//所有 Zuul 过滤器必须扩展 ZuulFilter 类,并覆盖四个方法:filterType()、filterOrder()、shouldFilter() 和 run()
@Component
public class TrackingFilter extends ZuulFilter
private static final int FILTER_ORDER =1;
private static final boolean SHOULD_FILTER=true;
private static final Logger logger = LoggerFactory.getLogger(TrackingFilter.class);
//所有过滤器的常用方法都封装进 FilterUtils 类中
@Autowired
FilterUtils filterUtils;
//告诉的是那种类型过滤器
@Override
public String filterType()
return FilterUtils.PRE_FILTER_TYPE;
//返回整数值,指示不同类型的过滤器执行顺序
@Override
public int filterOrder()
return FILTER_ORDER;
//是否执行过滤器
@Override
public boolean shouldFilter()
return SHOULD_FILTER;
//每次服务通过过滤器时执行的方法
@Override
public Object run()
...
- 其他两种 Zuul 过滤器与示例类似;
文章图片
- 需要将 50% 的用户请求路由到新服务,50% 的用户请求路由到旧服务。即 A/B 测试;
- 本例中先需要查询目标服务是否存在,只有存在才进行 A/B 测试;
- 注意区分目标服务、新服务和旧服务的概念:
- 这里的目标服务指该次请求既定的归属地,用来判断 Zuul 是否能路由过去;
- 只有判断有目标服务后,才根据一定策略决定路由到新服务还是旧服务;
- 路由到新服务这里称转发路由;
- 与本篇《3.1 前置过滤器 TrackingFilter 示例模板》一样,需要扩展 ZuulFilter 类,并覆盖 4 个方法;
- 其中重点在
run()
方法;
- 主要的路由逻辑,先查询目标服务是否存在,再根据返回值确定路由到新版本还是旧版本;
@Override
public Object run()
RequestContext ctx = RequestContext.getCurrentContext();
//确定目标服务是否存在
AbTestingRoute abTestRoute = getAbRoutingInfo( filterUtils.getServiceId() );
//接受路径权重 abTestRoute,生成随机数,确定是否路由到新服务
if (abTestRoute!=null &
&
useNewRoute(abTestRoute))
//构建新服务的路由路径
String route = buildRouteString(ctx.getRequest().getRequestURI(),
abTestRoute.getEndpoint(),
ctx.get("serviceId").toString());
//完成转发到新版本服务的工作
forwardToNewRoute(route);
return null;
4.3 getAbRoutingInfo() 查找目标服务
- 该方法的作用是:确定目标服务是否存在;
private AbTestingRoute getAbRoutingInfo(String serviceName)
ResponseEntity<
AbTestingRoute>
restExchange = null;
try
//尝试调用目标服务
restExchange = restTemplate.exchange(
"http://newroutesservice/v1/route/abtesting/serviceName",
HttpMethod.GET,
null, AbTestingRoute.class, serviceName);
catch(HttpClientErrorException ex)
//如果没有找到目标服务,将报错 HTTP 404,并返回空值
if (ex.getStatusCode()== HttpStatus.NOT_FOUND) return null;
throw ex;
return restExchange.getBody();
4.4 useNewRoute() 将决定是否路由到新服务
- 该方法的作用是:使用随机数确定调用到新版本还是旧版本;
- 返回 true 说明路由到新服务,反之不转发;
public boolean useNewRoute(AbTestingRoute testRoute) Random random = new Random(); //检查路由是否为活跃状态 if (testRoute.getActive().equals("N")) return false; //使用随机数确定调用到新版本还是旧版本 int value = https://www.songbingjia.com/android/random.nextInt((10 - 1) + 1) + 1; if (testRoute.getWeight()< value) return true; return false;
- 在目标服务存在,且实施路由到新服务的策略后,最后执行的路由到新服务的逻辑;
//Spring Cloud 提供,用于代理服务请求的辅助方法
private ProxyRequestHelper helper = new ProxyRequestHelper();
private void forwardToNewRoute(String route)
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
//创建发送到服务的所有 HTTP 请求首部的副本
MultiValueMap<
String, String>
headers = this.helper.buildZuulRequestHeaders(request);
//创建所有 HTTP 请求参数的副本
MultiValueMap<
String, String>
params = this.helper.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
//创建新服务的 HTTP 主体的副本
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() <
0)
context.setChunkedRequestBody();
this.helper.addIgnoredHeaders();
CloseableHttpClient httpClient = null;
HttpResponse response = null;
try
httpClient= HttpClients.createDefault();
//使用 forward() 方法调用新服务
response = forward(httpClient, verb, route, request, headers,params, requestEntity);
//将服务调用的结果保存会 Zuul 服务器
setResponse(response);
catch (Exception ex )
ex.printStackTrace();
finally
try
httpClient.close();
catch(IOException ex)
最后::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
文章图片
:::
推荐阅读
- RT-Thread快速入门-中断管理
- 微服务架构 | 8.1 使用 Spring Cloud Stream 整合 Apache kafka #yyds干货盘点#
- Java之集合
- 预处理器
- 如何利用MHA+ProxySQL实现读写分离和负载均衡
- Flink处理函数实战之四(窗口处理)
- Java多线程与线程池技术
- MapReduce编程模型和计算框架
- 三高Mysql - Mysql索引和查询优化(偏实战部分)