当前位置: 首页 > news >正文

Java面试题021:一文深入了解微服务之网关Zuul

欢迎大家关注我的JAVA面试题专栏,该专栏会持续更新(第一目标100节),从原理角度覆盖Java知识体系的方方面面。

一文吃透JAVA知识体系(面试题)https://blog.csdn.net/wuxinyan123/category_7521898.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=7521898&sharerefer=PC&sharesource=wuxinyan123&sharefrom=from_link

1、Zuul简介 

        Spring Cloud Zuul是Netflix开源的一款提供动态路由、监控、弹性、安全等网关服务的框架。它作为一个微服务的入口点,通过路由配置,将不同路径的请求转发到不同的服务,同时,Zuul也支持动态路由配置,能够在运行时根据需要动态更新路由信息,而不影响系统的可用性。

        使用Zuul,一般在微服务数量较多(多于10个)的时候推荐使用,对服务的管理有严格要求的时候推荐使用,当微服务权限要求严格的时候推荐使用。

网关有以下几个作用:

  • 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
  • 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
  • 动态路由:动态的将请求路由到不同的后端集群中。
  • 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。

2、动态路由

        在微服务架构中,动态路由是指在运行时根据特定的规则或条件动态地将外部请求转发到相应的微服务实例。

        动态路由的实现依赖于几个关键组件:路由配置中心、路由发现机制、以及路由规则的动态更新。

  • 路由配置中心是存储路由规则的中央存储,可以是数据库、文件或专用的配置服务器。
  •  路由发现机制则用于监控微服务实例的状态,并将最新的服务实例信息推送到路由配置中心。
  • 动态更新路由规则依赖于配置中心与服务网关的实时同步能力。

        Spring Cloud Zuul动态路由的实现依赖于服务发现组件(例如Eureka),当服务实例发生变化时,Zuul会订阅服务注册中心的通知,并根据最新的实例信息动态更新路由表,从而达到动态路由的效果。

(1)基于规则的路由配置

        Zuul中的路由匹配规则使用了Ant风格定义,一共有三种不同的通配符:

通配符含义解释
?匹配任意单个字符匹配/feign-consumer/a,/feign-consumer/b,/feign-consumer/c等
*匹配任意数量的字符匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,无法匹配/feign-consumer/a/b/c
**匹配任意数量的字符匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,也可以匹配/feign-consumer/a/b/c
zuul:routes:myservice:path: /my-service/**

        所有匹配 /my-service/** 路径的请求都会被转发到服务ID为 my-service 的服务上。这种基于路径前缀的路由规则是实现动态路由的一种简单方式。

单实例 serviceId 映射详细配置

### 网关配置
zuul:# 路由信息配置routes:# 针对某个服务的配置,可自定义demo-order://访问的路径,此处要以 '/do/' 开头path: /do/**//后端服务的实例 Id。以 '/do/' 开头的请求,都会向后端服务 'demo-order' 进行转发serviceId: demo-order//剥夺前缀,此配置是针对上方 'path' 配置的项//为 true 的情况下:向后端转发之后是不会携带 '/do' 的。为 false 则相反stripPrefix: true//不向后端服务传递的敏感头信息sensitiveHeaders: Cookie,Set-Cookie,Authorization

(2)基于服务发现的路由配置

        基于服务发现的路由配置更为动态,它允许Zuul根据服务名来自动发现服务实例,并构建路由映射。

        Zuul默认会将通过以服务名作为ContextPath的方式创建路由映射,比如将path:/user/**的请求转发到service-id=user的服务上(根据ContextPath查找对应的服务,需要结合服务发现机制如Eureka等)

zuul:routes:myservice:serviceId: my-service

3、过滤器机制

        过滤器(Filter)是保障请求和响应数据安全、质量的重要组件。它类似于网络中路由器的功能,允许或拒绝数据包的传递,但又增加了对数据包内容的处理能力,用过滤器来控制进入系统的请求。

        Zuul提供了一个排序的机制,允许开发者在配置文件中为每个过滤器指定一个顺序值。数字越小,表示过滤器越早执行。若不指定,系统会默认给定一个顺序值。

(1)外部http请求响应流程

在Zuul中,过滤器分为两种类型:

  • PRE过滤器 :在请求被路由到后端服务之前执行,可以用于进行身份验证、权限校验、日志记录等操作。
  • POST过滤器 :在请求已经被后端服务响应之后执行,可以用来修改响应的内容,或者根据请求的响应状态进行进一步的处理。

        第一个阶段:被pre类型的过滤器进行处理。该类型过滤器的主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。

        第二个阶段:路由请求转发阶段。请求将会被routing类型的处理器处理。这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程。

        第三个阶段:被post类型的过滤器处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。

        另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端。

(2)pre过滤器

        ServletDetectionFilter:执行顺序-3,最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行的,还是通过ZuulServlet来处理运行的。它的检测结果以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,后续的过滤器,通过判断请求处理源头实现不同的处理机制。

        一般,外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/*路径访问的请求会绕过DispatcherServlet(比如之前我们说的大文件上传),被ZuulServlet处理,主要用来应对大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/*,我们可以通过zuul.servletPath参数进行修改。
        Servlet30WrapperFilter:执行顺序为-2,第二个执行,目前的实现会对所有请求生效,将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。
        FormBodyWrapperFilter:执行顺序为-1,第三个执行。该过滤器仅对两类请求生效,第一类是Context-Type为application/x-www-form-urlencoded的请求,第二类是Context-Type为multipart/form-data并且是由String的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
        DebugFilter:执行顺序为1,第四个执行,根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。它的具体操作内容是将当前请求上下文中的debugRouting和debugRequest参数设置为true。后续的各个过滤器中可利用这二个值来定义一些debug信息,可以通过参数的方式激活这些debug信息帮助分析问题。
        PreDecorationFilter:执行顺序是5,pre阶段最后被执行的过滤器,判断当前请求上下文中是否存在forward.do和serviceId参数,如果都不存在,会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这二个信息就是根据当前请求的路由信息加载进来的)。具体操作内容是为当前请求做一些预处理,比如说,进行路由规则的匹配,在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。另外,还可以对HTTP头请求进行处理,比如X-Forwarded-Host,X-Forwarded-Port。也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。

(3)route过滤器

 
        RibbonRoutingFilter:执行顺序10,route阶段第一个执行的过滤器。只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。该过滤器的执行逻辑就是面向服务路由的核心,它通过使用ribbon和hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
        SimpleHostRoutingFilter:执行顺序100,route阶段第二个执行的过滤器。该过滤器只对通过url配置路由规则的请求生效。执行逻辑就是直接向routeHost参数的物理地址发起请求,源码中是直接通过httpclient包实现的,没有用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。
        SendForwardFilter:执行顺序500,route阶段第三个执行的过滤器。只对请求上下文中存在的forward.do参数进行处理请求,即用来处理路由规则中的forward本地跳转装配。

(4)自定义过滤器

        实现的过滤器必须包含4个基本特征:过滤类型,执行顺序,执行条件,具体操作。

String filterType();
int filterOrder();
boolean shouldFilter();
Object run();

 filterType:过滤器的类型,zuul中默认定义了4个不同的生命周期过程类型,具体如下

  • pre:可以在请求被路由之前调用
  • routing: 路由请求时被调用
  • post:在routing和error过滤器之后被调用
  • error:处理请求时发生错误时被调用

filterOrder:过滤器执行顺序,数值越小优先级越高。
shouldFilter:过滤器是否执行。通过此方法来指定过滤器的有效范围。
run:过滤器的具体逻辑。可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。

基本步骤:自定义过滤器需要继承Zuul提供的抽象类 ZuulFilter ,并实现其抽象方法。

  • 创建过滤器类 :实现 ZuulFilter 抽象类并重写方法。
  • 定义过滤类型 :指定 filterType 方法返回值是 "pre" 或 "post" 。
  • 设置过滤顺序 :实现 filterOrder 方法,返回过滤器执行的顺序。
  • 定义过滤条件 :通过 shouldFilter 方法定义何时激活过滤器。
  • 执行过滤逻辑 :实现 run 方法,在该方法中编写具体的过滤逻辑。

案例用Zuul网关ZuulFilter过滤器实现登录鉴权

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;/*** 登录过滤器*/
//这个注解不要忘了,加入spring容器中
@Component
public class LoginFilter  extends ZuulFilter {/*** 过滤器类型* org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 这个类* pre:在请求被路由(转发)之前调用* route:在路由(请求)转发时被调用* error:服务网关发生异常时被调用* post:在路由(转发)请求后调用* @return*/@Overridepublic String filterType() {//前置过滤器return PRE_TYPE;}/*** 过滤器顺序,越小越先执行* @return*/@Overridepublic int filterOrder() {return 4;}/*** 过滤器是否生效* @return*/@Overridepublic boolean shouldFilter() {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();System.out.println(request.getRequestURI()); ///apigateway/product/api/v1/product/listSystem.out.println(request.getRequestURL()); //http://localhost:9000/apigateway/product/api/v1/product/list//ACL//进行拦截,就会进入下面的 run方法中if (request.getRequestURI().contains("/apigateway/orderapi/ordermapping/savedingdan")){return true;}//不拦截,放行return false;}/*** 业务逻辑* @return* @throws ZuulException*/@Overridepublic Object run() throws ZuulException {//获取上下文RequestContext requestContext =  RequestContext.getCurrentContext();//获取request对象HttpServletRequest  request = requestContext.getRequest();//token对象String token = request.getHeader("token");if(StringUtils.isBlank((token))){token  = request.getParameter("token");}//登录校验逻辑  根据公司情况自定义 JWT//token为空,就不能访问if (StringUtils.isBlank(token)) {//停止访问,并返回出错的消息requestContext.setSendZuulResponse(false);//防止中文乱码requestContext.getResponse().setContentType("text/html;charset=UTF-8");//设置返回的状态码和正文requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());requestContext.setResponseBody("userToken is null");}//正常的话,继续向下走return null;}
}

4、一些高级应用场景

(1)使用OAuth2.0进行身份验证

        OAuth2.0是一种行业标准的授权协议,允许用户提供一个令牌而不是用户名和密码来访问数据。微服务架构中,使用OAuth2.0可以确保只有经过认证的用户或服务才能访问受保护的资源。

在Zuul网关中实现OAuth2.0身份验证通常涉及以下步骤:

  • 在Zuul网关中配置一个过滤器,拦截所有进入的请求。
  • 检查请求中的 Authorization 头部是否存在有效的令牌。
  • 如果令牌有效,则将请求转发至相应的微服务;如果令牌无效或不存在,则返回认证错误。

示例代码:

public class OAuthFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {String authHeader = request.getHeader("Authorization");// 验证逻辑if (isValidToken(authHeader)) {// 令牌有效return null;} else {// 令牌无效response.setStatus(HttpStatus.FORBIDDEN.value());return null;}}private boolean isValidToken(String authHeader) {// Token验证逻辑,此示例省略return true;}@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 1;}
}

(2)利用JWT进行授权控制

        JSON Web Token (JWT) 是一个开放标准,用于在各方之间安全地传输信息。在Zuul中,可以利用JWT来传递用户身份信息和授权数据。

以下是使用JWT的基本步骤:

  • 用户登录后,服务端验证身份并生成JWT,返回给客户端。
  • 客户端将JWT存储在本地(通常是Cookie或者localStorage)。
  • 客户端发送请求到Zuul网关,网关会从请求中提取JWT。
  • Zuul网关验证JWT的有效性,并提取JWT中的授权信息。
  • 根据授权信息,Zuul决定是否将请求转发到后端服务。

实现JWT验证的Zuul过滤器代码示例:

public class JwtFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {String token = extractTokenFromRequest(request);// 验证JWT逻辑if (isValidJwt(token)) {// JWT有效return null;} else {// JWT无效response.setStatus(HttpStatus.UNAUTHORIZED.value());return null;}}private String extractTokenFromRequest(HttpServletRequest request) {// 从请求中提取JWT,此示例省略return "";}private boolean isValidJwt(String token) {// JWT验证逻辑,此示例省略return true;}@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 2;}
}

(3)流量控制

1) 基于请求大小的限制

        Zuul可以对进入的HTTP请求的大小进行控制,通过设置过滤器中的参数来限制请求的最大长度。这对于防止恶意用户上传大量数据,或者避免因大文件上传导致的内存溢出具有重要意义。

@Component
public class RequestSizeFilter extends ZuulFilter {@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return FilterConstants.SIZE_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (request.getContentLength() > 5 * 1024 * 1024) { // 5MBctx.setSendZuulResponse(false);ctx.setResponseStatusCode(HttpStatus.BAD_REQUEST.value());ctx.setResponseBody("Request is too large.");ctx.set("error.status_code", HttpStatus.BAD_REQUEST.value());}return null;}
}

2)基于并发数的限制

@Component
public class ConcurrentRateLimitingFilter extends ZuulFilter {private ConcurrentHashMap<String, Integer> concurrentRequestsMap = new ConcurrentHashMap<>();@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return FilterConstants.SERVLETDetection_FILTER_ORDER + 1;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();String requestURI = request.getRequestURI();Integer count = concurrentRequestsMap.get(requestURI);if (count == null) {concurrentRequestsMap.put(requestURI, 1);} else if (count >= 10) { // Assuming we only allow 10 concurrent requests per URIctx.setSendZuulResponse(false);ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());ctx.setResponseBody("Too many requests");ctx.set("error.status_code", HttpStatus.TOO_MANY_REQUESTS.value());} else {concurrentRequestsMap.put(requestURI, count + 1);ctx.addZuulResponseHeader("X-Concurrent-Request", String.valueOf(count + 1));}return null;}
}

相关文章:

  • 缓存击穿,缓存穿透,缓存雪崩的原因和解决方案
  • 传智健康---十天项目总结
  • 挑战杯三个项目
  • 09 - TripletAttention模块
  • RAG数据集综述
  • 第六章 进阶19 琦琦的追求
  • Windows 文件复制利器:ROBOCOPY 拷贝命令指南
  • 全球域名WHOIS信息查询免费API接口教程
  • Tlias-web 管理系统项目知识点复盘总结
  • 高性能Tick级别高频交易引擎设计与实现
  • 6月13日day52打卡
  • 西电新增信息力学与感知学院,26考研正式招生
  • 【python深度学习】DAY 52 神经网络调参
  • 第三章支线八 ·构建之巅 · 工具链与打包炼金术
  • PHP商城源码:构建高效电商平台的利器
  • DeepSeek 引领前端开发变革:AI 助力学习与工作新路径
  • record类型-Java 16
  • 使用 PolarProxy+Proxifier 解密 TLS 流量
  • Stone 3D使用RemoteMesh组件极大的缩小工程文件尺寸
  • python实现鸟类识别系统实现方案
  • 国外开源代码网站/宁波网络推广软件
  • 乌鲁木齐新市区建设局网站/视频号直播推广二维码
  • 做电影网站用的云盘/恶意点击竞价是用的什么软件
  • 网站建设推广怎么做/如何做品牌推广方案
  • 创业做网站失败/搜索引擎技术
  • 深圳正规网站建设公司/黄页网络的推广软件