WebFlux vs MVC:Gateway集成若依框架的技术选型之争

摘要

在微服务架构体系里,API网关作为整个系统的统一入口,肩负着路由转发、鉴权认证、流量控制等诸多关键职责。本文详细记录了在Spring Cloud Gateway(基于WebFlux)中集成若依框架(基于MVC)鉴权功能时所经历的技术选型过程,深入剖析了WebFlux与MVC的技术差异,并阐释了最终选择HTTP远程鉴权方案的决策依据。

目录

  1. 背景
    • [1.1 Gateway需要集成若依鉴权](#1.1 Gateway需要集成若依鉴权)
    • [1.2 Gateway基于WebFlux](#1.2 Gateway基于WebFlux)
    • [1.3 若依框架基于MVC](#1.3 若依框架基于MVC)
  2. [WebFlux vs MVC对比](#WebFlux vs MVC对比)
    • [2.1 编程模型差异](#2.1 编程模型差异)
    • [2.2 线程模型差异](#2.2 线程模型差异)
    • [2.3 性能特性差异](#2.3 性能特性差异)
    • [2.4 生态成熟度对比](#2.4 生态成熟度对比)
  3. 技术决策分析
    • [3.1 方案一:改造若依支持WebFlux](#3.1 方案一:改造若依支持WebFlux)
    • [3.2 方案二:本地鉴权](#3.2 方案二:本地鉴权)
    • [3.3 方案三:远程鉴权中心](#3.3 方案三:远程鉴权中心)
    • [3.4 各方案优劣对比](#3.4 各方案优劣对比)
  4. 放弃WebFlux改造的原因
    • [4.1 项目其他模块都是MVC](#4.1 项目其他模块都是MVC)
    • [4.2 WebFlux生态不熟悉](#4.2 WebFlux生态不熟悉)
    • [4.3 异步结构Debug困难](#4.3 异步结构Debug困难)
    • [4.4 可能引入雪崩风险](#4.4 可能引入雪崩风险)
  5. 最终选型:远程鉴权中心
    • [5.1 HTTP方案(选择)](#5.1 HTTP方案(选择))
    • [5.2 gRPC方案(备选但放弃)](#5.2 gRPC方案(备选但放弃))
    • [5.3 选择理由](#5.3 选择理由)
  6. 双环境适配实现
    • [6.1 Gateway WebFlux环境](#6.1 Gateway WebFlux环境)
    • [6.2 其他服务MVC环境](#6.2 其他服务MVC环境)
    • [6.3 统一抽象层设计](#6.3 统一抽象层设计)
  7. 经验总结
    • [7.1 技术选型考虑因素](#7.1 技术选型考虑因素)
    • [7.2 WebFlux适用场景](#7.2 WebFlux适用场景)
    • [7.3 项目一致性重要性](#7.3 项目一致性重要性)

一、背景

1.1 Gateway需要集成若依鉴权

在合同审查平台的架构设计工作中,我们选定Spring Cloud Gateway作为API网关,以此来统一管理所有微服务的入口。同时,引入若依框架作为认证授权的基础架构,目的是让Gateway能够复用若依的权限体系。这就如同在一个大型商场中,Gateway是所有用户进入各个店铺(微服务)的大门,而若依框架则是负责检查用户是否有资格进入特定店铺(访问特定权限资源)的安保系统。

1.2 Gateway基于WebFlux

这实际上是一种"被动选择"。并非我们主动挑选WebFlux,而是因为Spring Cloud Gateway的路由功能自身便是基于WebFlux实现的。

Spring Cloud Gateway作为Spring官方力荐的下一代API网关(用以替代第一代的Zuul),其底层架构完全构建于Spring WebFlux之上。这就意味着:

  • 若要运用Spring Cloud Gateway强大的路由功能,就不得不接纳WebFlux。
  • 无法在Gateway中使用传统的Spring MVC组件。
  • 这是技术框架所施加的强制性约束,并非我们自主的技术选型决策。

查看contract - gatewaypom.xml文件,能清晰看到WebFlux的依赖配置:

xml 复制代码
<!-- Spring Cloud Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

<!-- Spring Boot WebFlux -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

上述代码中,通过spring - cloud - starter - gateway - server - webfluxspring - boot - starter - webflux这两个依赖,引入了Spring Cloud Gateway和Spring Boot WebFlux相关的功能,表明该项目基于WebFlux构建。

1.3 若依框架基于MVC

若依框架作为一款成熟的开源权限管理系统,其核心是搭建在传统的Spring MVC之上,借助Servlet容器来处理请求。查看RuoYi - Vue的pom.xml可以发现:

xml 复制代码
<!-- SpringBoot的依赖配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>3.5.8</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

这段配置体现了若依框架对Spring Boot相关依赖的引入,基于此构建其基于Spring MVC的功能体系,也由此造成了与WebFlux在编程模型上的本质区别。

二、WebFlux vs MVC对比

2.1 编程模型差异

维度 Spring MVC Spring WebFlux
编程范式 命令式编程 响应式编程
返回类型 直接返回对象 Mono/Flux
阻塞特性 阻塞I/O 非阻塞I/O
代码风格 同步调用 链式调用 + Lambda
学习曲线 平缓 较陡

例如,对于同样获取用户信息并关联角色的业务逻辑,两种框架的写法差异显著:

java 复制代码
// MVC风格(阻塞)
public User getUserById(Long id) {
    User user = userRepository.findById(id);
    List<Role> roles = roleRepository.findByUserId(id);
    user.setRoles(roles);
    return user;
}

// WebFlux风格(非阻塞)
public Mono<User> getUserById(Long id) {
    return userRepository.findById(id)
       .flatMap(user -> roleRepository.findByUserId(user.getId())
           .collectList()
           .doOnNext(user::setRoles)
           .thenReturn(user));
}

在上述代码示例中,MVC风格采用传统的同步方式,按照顺序依次执行数据库查询和对象赋值操作。而WebFlux风格则运用响应式编程,通过Mono和链式调用,以非阻塞的方式处理异步操作。这就好比MVC是按照顺序一项一项完成任务,而WebFlux则可以同时处理多个任务,并在合适的时机组合结果。

2.2 线程模型差异

Spring MVC采用传统的Thread - per - Request模型,即每个请求都会占用一个独立线程,直至响应完成。而WebFlux则利用少量固定线程来应对大量并发请求:

复制代码
MVC线程模型:
请求1 → 线程1 → [阻塞等待DB] → 返回
请求2 → 线程2 → [阻塞等待DB] → 返回
请求3 → 线程3 → [阻塞等待DB] → 返回

WebFlux线程模型:
请求1 ┐
请求2 ├→ 少量事件循环线程 → 非阻塞I/O ─→ 返回
请求3 ┘

可以将MVC线程模型想象成每个用户(请求)都有一个专属的服务员(线程)全程陪同,在等待上菜(数据库查询等操作)时,服务员只能干等。而WebFlux的线程模型则像是几个服务员同时服务众多用户,在等待上菜时,服务员可以去处理其他用户的需求。

2.3 性能特性差异

WebFlux在以下场景中优势凸显:

  • 高并发I/O密集型操作,例如在一个大型文件下载网站,同时有大量用户请求下载文件,WebFlux能够高效处理这些I/O操作。
  • 需要处理大量长连接,如实时聊天应用,众多用户保持长连接与服务器通信,WebFlux可以更好地管理这些连接。
  • 流式数据处理,比如实时数据监控系统,持续接收并处理数据流。

然而,在若依鉴权这类场景下,其优势并不突出。因为权限验证主要依赖数据库查询,依然是I/O等待过程,就如同在一个用户数量相对稳定的小餐馆,即使采用了大型餐厅的高效服务模式(WebFlux),也难以体现出明显优势。

2.4 生态成熟度对比

方面 Spring MVC Spring WebFlux
历史 2004年 2017年
社区成熟度 非常成熟 持续发展
第三方库兼容 广泛兼容 部分库不支持
调试便利性 堆栈清晰 异步堆栈复杂
资料丰富度 极其丰富 相对较少

Spring MVC由于诞生时间较早,经过多年发展,积累了丰富的社区资源、大量兼容的第三方库以及清晰的调试方法,就如同是一座发展成熟、设施完备的城市。而Spring WebFlux作为较新的技术,虽然在不断发展,但在这些方面相对较弱,类似于正在建设中的新兴城市。

三、技术决策分析

3.1 方案一:改造若依支持WebFlux

思路:将若依框架的核心模块全面改造为WebFlux响应式编程模型。

优势

  • 技术栈实现统一,全部采用响应式编程,使得整个系统在编程风格和技术体系上更加一致,就像一个团队都使用同一种语言交流,沟通更顺畅。
  • 理论上性能最优,能够充分发挥WebFlux的优势,应对高并发场景。

劣势

  • 改造工作量巨大,需要对核心逻辑进行重写,这就好比要把一座已经建好的大楼推倒重新按照新的设计方案建造。
  • MyBatis等组件需要迁移到R2DBC,涉及到数据库访问方式的重大改变。
  • 若依框架生态丰富,改造后可能无法复用社区资源,就像原本有很多好用的工具,改造后却不能用了。

3.2 方案二:本地鉴权

思路:在Gateway内部直接实现权限验证逻辑,独立维护一套用户权限数据。

优势

  • 避免远程调用,性能最好,减少了网络通信带来的开销,就像在自己家里就能完成所有事情,不需要跑到外面去。
  • 架构简单,不依赖外部服务,降低了系统的复杂性。

劣势

  • 数据冗余,需要同步用户权限数据,这就好比一份文件在多个地方都要保存一份,增加了管理成本。
  • 两套代码维护成本高,不仅要维护Gateway中的权限验证代码,还要维护若依框架原有的相关代码。
  • 若依框架的功能无法复用,浪费了若依框架已有的成熟功能。

3.3 方案三:远程鉴权中心

思路:保持若依框架不变,Gateway通过Feign客户端调用若依的鉴权接口。

优势

  • 若依框架无需改动,最大程度地保留了其原有功能和生态,就像一辆运行良好的汽车,不需要对其进行大修。
  • 复用成熟的权限功能,利用若依框架已经完善的权限管理能力。
  • 数据来源唯一,避免了数据不一致的问题,所有权限验证都基于若依框架中的数据。

劣势

  • 增加网络调用开销,每次权限验证都需要通过网络请求若依框架的鉴权接口,就像每次做事都要跑到别的地方去问一下。
  • 需要处理服务降级,以防若依鉴权服务出现故障时,系统仍能保持一定的可用性。

3.4 各方案优劣对比

评估维度 方案一:改造若依 方案二:本地鉴权 方案三:远程鉴权
开发成本 极高 中等
维护成本 低(若统一) 高(双维护)
性能 最优 最优 略有损耗
数据一致性 强一致 需要同步 强一致
技术风险
复用性 -

四、放弃WebFlux改造的原因

4.1 项目其他模块都是MVC

查看项目结构,会发现除了Gateway采用WebFlux外,其他核心服务如contract - management、contract - review - engine等均使用传统的Spring MVC:

xml 复制代码
<!-- contract - gateway采用WebFlux -->
<dependency>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- 其他服务采用MVC -->
<dependency>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

如果仅仅为了Gateway而改造若依,会致使项目技术栈分裂更为严重,就像一个团队里,大部分人说一种语言,只有少数人说另一种语言,交流和协作会变得困难。

4.2 WebFlux生态不熟悉

团队对Spring MVC拥有丰富的开发经验,但对响应式编程相对陌生。这意味着学习成本和调试难度都会增加,就像要去一个陌生的地方,不仅要学习当地的语言和文化,遇到问题时解决起来也更困难。

4.3 异步结构Debug困难

WebFlux的响应式编程堆栈复杂,当出现问题时,定位和排查的难度远高于同步代码。这就好比在一个迷宫中寻找出路,同步代码的迷宫可能路线比较清晰,而异步结构的迷宫则充满了错综复杂的路径,更难找到问题所在。

4.4 可能引入雪崩风险

将成熟的若依框架改造为WebFlux,可能会引入未知的问题和风险,进而影响系统的稳定性。就像对一座原本坚固的桥梁进行大规模改造,可能会破坏其原有的稳定性,带来意想不到的危险。

五、最终选型:远程鉴权中心

5.1 HTTP方案(选择)

最终确定通过HTTP协议调用若依鉴权服务的方案,使用Feign客户端进行远程调用。

核心配置如下:

java 复制代码
@FeignClient(name = "contract - security - ruoyi",
             contextId = "remoteAuthFeignService",
             path = "/contract - security - ruoyi/")
public interface RemoteAuthFeignService {

    /**
     * 远程权限验证
     */
    @PostMapping("/remote/auth/validate")
    AjaxResult validatePermission(@RequestBody AuthValidateRequest request);
}

上述代码定义了一个Feign客户端接口RemoteAuthFeignService,通过@FeignClient注解指定了服务名称、上下文ID和路径。validatePermission方法用于远程调用若依鉴权服务的权限验证接口。

5.2 gRPC方案(备选但放弃)

虽然gRPC在性能上优于HTTP,但考虑到:

  • 若依框架没有gRPC支持,需要额外开发适配功能。
  • 团队对gRPC不熟悉,增加了开发和维护的难度。
  • 调试工具相对缺乏,不利于问题排查和解决。

最终没有选择gRPC方案。这就好比有一辆速度更快的车(gRPC),但需要特定的道路(若依框架支持),且司机(团队)不熟悉驾驶方法,维修工具(调试工具)也不好找,所以最终选择了更熟悉的交通工具(HTTP)。

5.3 选择理由

  1. 技术栈复用:Feign客户端是Spring Cloud生态的标准组件,团队熟悉,能够充分利用已有的技术知识,减少学习成本。
  2. 开发效率高:只需定义接口,无需处理底层通信,就像只需要告诉别人你要什么,而不需要关心怎么去拿,大大提高了开发速度。
  3. 调试便利:可以使用常规的HTTP工具进行调试,降低了调试门槛,更容易发现和解决问题。
  4. 渐进式演进:后续如果需要优化性能,可以逐步调整,为系统的发展提供了灵活性。

六、双环境适配实现

6.1 Gateway WebFlux环境

针对WebFlux环境,ruoyi - feign模块提供了RemoteAuthWebFilter

java 复制代码
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RemoteAuthWebFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 1. 获取HandlerMethod
        Method handlerMethod = WebFluxRouteUtil.getHandlerMethod(exchange);

        // 2. 检查注解
        RemotePreAuthorize annotation = getRemotePreAuthorizeAnnotation(handlerMethod);

        // 3. 提取token
        String token = extractToken(exchange.getRequest());

        // 4. 异步验证权限
        return validateRemotePermission(token, annotation.value())
           .flatMap(has
相关推荐
独自归家的兔2 小时前
Java反射之根:Class类生成机制深度剖析与最佳实践
java·开发语言
请叫我聪明鸭2 小时前
基于 marked.js 的扩展机制,创建一个自定义的块级容器扩展,让内容渲染为<div>标签而非默认的<p>标签
开发语言·前端·javascript·vue.js·ecmascript·marked·marked.js插件
悟能不能悟2 小时前
Gson bean getxxx,怎么才能返回给前端
java·前端
Apex Predator2 小时前
本地库导入到nexus
java·服务器·前端
仍然.2 小时前
Java---反射、枚举、lambda表达式 和 泛型进阶
java·开发语言
Zsy_0510032 小时前
【C++】类和对象(二)
开发语言·c++
Duang007_2 小时前
【万字学习总结】API设计与接口开发实战指南
开发语言·javascript·人工智能·python·学习
小北方城市网2 小时前
JVM 调优实战指南:从问题排查到参数优化
java·spring boot·python·rabbitmq·java-rabbitmq·数据库架构
一叶星殇2 小时前
C# .NET 如何解决跨域(CORS)
开发语言·前端·c#·.net