微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#
Posted 多氯环己烷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务架构 | 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 路由和服务端的负载均衡器;
1.2 Zuul 提供的功能
- 将应用程序中的所有服务的路由映射到一个 URL,即所有客户端调用都经过这个路口;
- 构建可以对通过网关的请求进行检查和操作的过滤器;
- 简单来说就是:代理、路由、过滤;
1.3 Zuul 支持的三种类型过滤器
- 前置过滤器:在请求被路由到目标服务前执行;
- 用于:统一消息格式、用户验证和授权、打印日志等;
- 路由过滤器:在请求被路由到目标服务时执行;
- 用于:确定是否需要进行某些级别的动态路由;(灰度发布等)
- 后置过滤器:在请求被路由到目标服务后执行;
- 用于:记录响应信息、给目标服务的响应添加头信息、收集统计数据;
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 预置的功能时使用;
2.4 配置路由
2.4.1 自动映射路由
- 不需要做额外配置;
- 访问
http://localhost:zuul-port/routes
; - 通过 Zuul 注册的服务映射展示在左边,路由映射到实际的 Eureka 服务 ID 展示在右边;
- Zuul 将基于 Eureka 服务的 ID 来公开服务,如果服务的实例没有在运行 , Zuul 将不会公开该服务的路由;
2.4.2 手动映射路由
-
需要在 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/**
;
2.4.3 使用静态 URL 手动映射路由
-
用来路由那些不受 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;
2.4.4 动态重新加载路由配置
- 其允许在不回收 Zuul 服务器的情况下更改路由的映射。现有的路由可以被快速修改,以及添加新的路由;
- 动态配置相关需要结合《2.1 使用 Spring Cloud Config 管理服务配置项》篇里的使用 git 外部化微服务配置;
- 将配置文件交给 git 后,我们直接修改配置文件即可,不需要做额外配置;
2.4.5 配置服务超时
- 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:
3. 使用 Zuul 的三种类型过滤器示例
3.1 前置过滤器 TrackingFilter 示例模板
//所有 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 过滤器与示例类似;
4. 使用 Zuul 的路由过滤器实现灰度发布示例
- 需要将 50% 的用户请求路由到新服务,50% 的用户请求路由到旧服务。即 A/B 测试;
- 本例中先需要查询目标服务是否存在,只有存在才进行 A/B 测试;
- 注意区分目标服务、新服务和旧服务的概念:
- 这里的目标服务指该次请求既定的归属地,用来判断 Zuul 是否能路由过去;
- 只有判断有目标服务后,才根据一定策略决定路由到新服务还是旧服务;
- 路由到新服务这里称转发路由;
4.1 扩展 ZuulFilter 类
- 与本篇《3.1 前置过滤器 TrackingFilter 示例模板》一样,需要扩展 ZuulFilter 类,并覆盖 4 个方法;
- 其中重点在
run()
方法;
4.2 自定义 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 = random.nextInt((10 - 1) + 1) + 1; if (testRoute.getWeight()<value) return true; return false;
4.5 转发路由的逻辑
- 在目标服务存在,且实施路由到新服务的策略后,最后执行的路由到新服务的逻辑;
//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
:::
以上是关于微服务架构 | 6.1 使用 Zuul 进行服务路由 #yyds干货盘点#的主要内容,如果未能解决你的问题,请参考以下文章
springcloud+zuul+swagger2之微服务通过网关进行分发
spring cloud 入门系列六:使用Zuul 实现API网关服务
微服务架构SpringCloud之Eureka(服务注册和服务发现基础篇)