针对 SAAS 私有化部署,如何优雅合并微服务

需求背景

在 SAAS 系统中,一个非常经典、同时也非常"现实"的部署场景是:

客户要求提供私有云部署方案

但是对于微服务架构场景,私有云部署的成本是非常大的,例如:对于有些大型SAAS系统来说,一个系统会有几百个、上千个 Docker 容器需要部署启动到客户的私有云环境中,即便是做好了通用的部署方案,部署的成本也是非常高的,无论是人力,还是服务器资源。

所以很多 SAAS 厂商针对于这种私有云部署场景,会考虑:将微服务进行合并。假设你的系统中微服务容器有300个,通过合并可能只需要部署60个服务,差别非常大。

在不推翻现有微服务架构的前提下,对微服务进行"部署维度"的合并。

为什么不能"简单粗暴"地合并

最直观、也最容易被想到的方式是:直接把多个微服务的代码复制(CV)到一个工程里,做成一个大单体。但在真实项目中,这种方式几乎不可行,尤其是大型系统:

❌ 改造成本极高

❌ 风险不可控

❌ 回退成本巨大

❌ 公有云 SAAS 架构会被直接破坏

一旦合并完成,系统将:

❌ 很难再拆回微服务

❌ 形成私有云与公有云两套代码分支

❌ 后续维护成本指数级上升

因此,我们在实际项目中采用了一种 改造成本极低、可随时回退、对原架构几乎零侵入 的方案。这里就介绍一个我们真实落地的微服务合并方案:聚合启动

聚合启动(All‑in‑One)

Maven import 多个微服务 → 上层 All-in-One 聚合服务 → 一个启动类统一启动

原本的微服务项目不做任何改动,新创建一个All-In-One服务作为聚合工程,然后将想要合并的微服务放到该工程下,这样 All In One 服务有一个启动类,作为该工程下所有微服务统一启动的入口,原本的微服务代码不需要做任何的调整,依旧提供原本的能力(公有云),项目结构大概如下:

java 复制代码
all-in-one
 ├── all-in-one-starter (SpringBootApplication 启动类)
 ├── user-service
 ├── order-service
 ├── auth-service
 ├── pay-service

不需要推翻原本的微服务架构,公有云 SAAS 依旧按照以前的微服务方式进行部署,不会对之前的架构造成任何影响,改造成本极低。

本方案并不是将系统从微服务退化为单体,而是在部署维度对微服务进行收敛,在代码与架构维度保持微服务形态不变

这种结构的最大优势在于:

✅ 公有云 SAAS 架构 完全不受影响

✅ 私有云方案 仅新增一个工程

✅ 微服务依然可以独立开发、测试、发布

注意点

Maven 依赖

所有微服务都会通过依赖绑定到父工程中,例如:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.xxx</groupId>
        <artifactId>user-service</artifactId>
    </dependency>
    <dependency>
        <groupId>com.xxx</groupId>
        <artifactId>order-service</artifactId>
    </dependency>
    <dependency>
        <groupId>com.xxx</groupId>
        <artifactId>auth-service</artifactId>
    </dependency>
</dependencies>

聚合启动类

All‑in‑One 工程提供唯一的启动类,通过 @ComponentScan 扫描所有微服务包:

java 复制代码
// 关键:通过 @ComponentScan 扫描所有微服务包,确保 Bean 被加载
@SpringBootApplication(
    scanBasePackages = {
        "com.xxx.user",
        "com.xxx.order",
        "com.xxx.hr",
        "com.xxx.auth"
    }
)
public class AllInOneApplication {
    public static void main(String[] args) {
        SpringApplication.run(AllInOneApplication.class, args);
    }
}

服务调用

原本的微服务之间通讯用的是 Feign,我们的 Feign URL 都在配置文件中维护好了,那么合并之后,直接就可以重写 ALL IN ONE 聚合工程中的配置,将 Feign URL 调整为本地,这样Feign注解就可以将请求路由到当前服务中了,例如:

java 复制代码
@FeignClient(name = "user-service", url = "http://127.0.0.1:8080")

还有一个办法,在 All‑in‑One 工程中,对原有 Feign Client 进行本地实现覆盖:

java 复制代码
@Primary
@Component
public class LocalUserClient implements UserClient {
    @Autowired
    private UserService userService;

    @Override
    public UserDTO getById(Long id) {
        return userService.getById(id);
    }
}

数据库

在私有云场景下,我们采用了相对务实的方案:统一数据源,所有业务表放在同一个数据库中。原因在于:

私有云客户的数据规模和并发模型通常是可预期的

客户更关心系统是否稳定、是否易维护

多数据源反而会增加部署和排查成本

在这种背景下,统一数据源往往是 性价比最高 的选择。

私有云不需要考虑那么多,什么性能问题、数据量等等,很少会成为私有化部署的瓶颈。私有云场景下,数据规模和并发模型通常是可预期的,因此我们选择了更简单、可维护性更高的方案。

相关推荐
sunny_10 小时前
⚡️ vite-plugin-oxc:从 Babel 到 Oxc,我为 Vite 写了一个高性能编译插件
前端·webpack·架构
兆子龙14 小时前
模块联邦(Module Federation)详解:从概念到手把手 Demo
前端·架构
程序员清风15 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
Bigger16 小时前
告别版本焦虑:如何为 Hugo 项目定制专属构建环境
前端·架构·go
皮皮林55116 小时前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
狗哥哥20 小时前
微前端架构下的平台级公共组件资源体系设计
前端·架构
两万五千个小时20 小时前
落地实现 Anthropic Multi-Agent Research System
人工智能·python·架构
Mintopia21 小时前
思想长期停在事物表面的深层原因:认知机制、环境结构与技术化治理
架构
兆子龙21 小时前
React Compiler 来了:少写 useMemo,照样稳
前端·架构
华仔啊1 天前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端