SaaS 流量治理应用与 Spring Boot 请求转发实现

SaaS 应用与数据可分离原因分析

回到这个遗留系统,应用效果得到公司内外认可后,得到了大量复用。复用就面临一个问题,如何快速部署并演示应用效果。在政企行业很多用户看来是不能用设计稿来演示的(可交互设计稿也是不行的),应用又需要大量的数据治理工作,用上线标准满足阶段性(也可能是一次性)演示要求从成本上考量不划算。

梳理一下业务背景:

  • 基于真实 SaaS 应用(数据结构保持一致)满足用户演示需要
  • 不依赖数据服务能力,快速呈现应用效果
  • 没有流量治理服务支撑

在 SaaS 没有微服务治理能力支撑下,考虑一种轻量级演示方案,应用交互与数据服务分离,实现流量转发就有了实现价值。

这背后深层理解是,应用交互是需要真实的,它体现了业务流程、领域知识、行业规则,传统行业用户更愿意相信眼见为实,而数据真实性是可以脱敏的,也就是构造逻辑自洽的数据不影响应用的演示效果。演示不同于实战,对数据要求反而更低一些。

流量治理与 Mock Server

流量治理是 SaaS 核心之一,负载均衡、流量调度、限流、熔断、分流等,诞生了服务路由、网关、熔断器、分布式追踪等一批中间件。不仅直接影响业务流程,也是研发体系不可缺少的一环,比如全链路压测。

全链路压测简单来说就是构造压测流量,推入到生产服务中,通过监控系统运行指标找到系统性能瓶颈。

全链路压测要求压测流量与真实流量使用同样的应用服务,压测流量不能影响生产服务与真实流量。 流量构造基于系统架构设计,流量标识要能让系统识别出分发链路,实现流量隔离。

这里又会引入 Mock Server,用于隔离外部服务,以及代理暂时无法实现流量隔离的组件。

回到上面演示需要,应用与数据分离方案就可以通过 Mock Server 实现。只不过范围从系统变成了组件,数据服务通过 Mock Server 代理,应用增加请求拦截,转发到 Mock Server 上。这样,实施工作量就从数据服务治理大幅缩短为演示数据构造。

请求转发实现

Spring Boot HTTP 请求拦截方法

分析之后,解决方案就迁移到了已有知识体系上。Spring Boot 对于 HTTP 请求有三种常见拦截方法,Filter,Interceptor 和 Controller Advice。

Filter 是 Servlet 容器提供的拦截器,可实现所有 HTTP 请求与返回结果的控制;Interceptor 是 Spring 框架提供的仅限于 Controller 的前置后置处理;Controller Advice 借助 Spring AOP 能力实现的切面处理方法。

这里就选用 Filter 作为拦截器,管理与扩展 HTTP 生命周期更合适。

Mock Server

因为要 mock 后端服务,与 SpringBoot 有良好支持,这里选择 moco 作为 Mock Server。

moco 支持 API、shell、standalone 等多种方式,底层使用 netty 性能优异,同时支持热部署,HTTP 服务所见即所得。

Spring Boot 实现

创建一个类 WebMockServerManager 用于管理拦截器创建条件,添加 @Component 注解。

创建一个内部类 MockServerFilter 实现 Filter 接口并实现 doFilter 方法。

java 复制代码
private static class MockServerFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ...
    }
}

将 ServletRequest 和 ServletResponse 转成 Http Servlet。然后替换请求路径,转发到 Mock Server 上。

ini 复制代码
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
​
// 拦截所有前端请求,转发到 Mock Server
String originalUri = request.getRequestURI();
String targetServletPath = originalUri.replaceFirst(servletPath, servletMockPath);
String targetUrl = servletHost + targetServletPath;

Mock Server 如果是独立进程,则向 Mock Server 发起 HTTP 请求,请求参数与原始请求保持一致。

arduino 复制代码
HttpEntity<?> httpEntity = new HttpEntity<>(new ServletServerHttpRequest(request).getBody());
​
ResponseEntity<String> forwardedResponse = restTemplate.exchange(URI.create(targetUrl),
    HttpMethod.valueOf(request.getMethod()),
    httpEntity,
    String.class);

将请求再转发回去,这要求 Mock Server 按照接口同样实现。

scss 复制代码
response.setStatus(forwardedResponse.getStatusCodeValue());
forwardedResponse.getHeaders().forEach((headerName, headerValues) -> {
    for (String headerValue : headerValues) {
        response.addHeader(headerName, headerValue);
    }
});
​
response.getWriter().write(forwardedResponse.getBody());

另外,可以添加条件注解,在必要时候启动拦截器。比如使用基于外部化配置的 @ConditionalOnProperty,Spring 容器启动时判断 server.servlet.mock-condition 为 true 时才注册这个拦截器,使之生效。

less 复制代码
@Bean
@ConditionalOnProperty(prefix = "server.servlet", name = "mock-condition", havingValue = "true")
MockServerFilter filter() {
    return new MockServerFilter();
}
相关推荐
乌啼霜满天24919 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn24 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
Grey_fantasy35 分钟前
高级编程之结构化代码
java·spring boot·spring cloud
苹果酱05671 小时前
前端面试vue篇:Vue2 和 Vue3 在设计和性能上有显著区别
java·spring boot·毕业设计·layui·课程设计
刘大浪2 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
一只爱撸猫的程序猿2 小时前
简单实现一个系统升级过程中的数据平滑迁移的场景实例
数据库·spring boot·程序员
攻心的子乐2 小时前
shell脚本启动springboot项目
spring boot
程序媛-徐师姐3 小时前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
bjzhang754 小时前
SpringBoot开发——Maven多模块工程最佳实践及详细示例
spring boot·maven·maven多模块工程