在《[[SaaS部署标准化与定制化]]一》中,落地实践中总体原则:构建时用 Maven 控制依赖是否打进包;运行时用 Spring 条件装配隔离缺失的类
看似很简单的措施,其实底层映射出不少经典设计思想:面向接口编程、SOLID中的DIP原则、DDD供应商模式
下面再谈另一种现实场景:微服务整合部署
标准化
在公有云上,我们部署的是标准化SaaS,按客户价值分类,这些服务客户是我们的 “现金奶牛“。
这类客户应该覆盖大部分的用户群体,比如 GitHub 免费用户,或是 Salesforce 标准版这样的用户。对于这类客户,目标就是降低成运营成本。比如通过自动化脚本实现低成本运维。再比如,通过自助式服务来解决服务开通等问题。
这类客户没有特殊需求,都是产品标准功能。标准化能把高昂的研发成本摊薄到海量用户上,就可以降低边际成本,实现规模化盈利。
此时,我们需要的客户数量越多越好,而从架构角度,稳定性就是关键因素。至少保证“4个9的高可用性”。
为了主业务的高可用、高性能。我们通常使用拆分、隔离原则。
在DDD拆分域中,通常分为:核心域、支撑域、通用域。
对应组织层面,就会有各个业务团队,各个中台团队,基础架构团队。把一个服务,拆分成上百个微服务来支撑整个系统的良好运行。
定制化
标准客户是”现金奶牛“,定制化就是服务于高价值客户。
如果不是高价值客户,也没必要折腾定制化。高价值也并不单单是价格,也可能是市场标杆价值
标准化部署时,为了高可用,核心业务与非核心业务要隔离,每个服务都至少需要2个pod,
为了应对突发高峰,还有冗余资源
形象一点,米国入侵战争时,别看深入敌后的就一支海豹突击队,其实后面可能有一个航母编队在支撑。
这些成本通过规模化都能拉平。而在定制化部署时,云资源是客户提供的,每次部署前,都要提前申请,而且申请是可能会被拒绝的,并且在招标时,这也是一个中标重要因素。
在公有云,如果有100个微服务,至少需要200个pod,1个pod如果至少需要1核 CPU,那至少需要200核。
这时应对方案
1、压缩单个pod资源,比如低流量服务只配置0.5核
2、减少pod数量,比如只要50个微服务,把为了灵活性,运维性的服务减掉,像配置中心服务
而业务微服务,把多个微服务整合到一个微服务中,使用本地调用平替掉远程调用,达到减少微服务部署的要求
在 Spring Boot 中实现本地调用与 Feign 远程调用的无缝切换,核心思路是面向接口编程,并结合 Spring 的条件装配机制(Conditional)。通过读取配置文件中的属性,让 Spring 容器在启动时自动决定注入本地实现还是远程代理。
我们假设项目的 GroupId 为 com.example,整体分为三个核心模块:product-api、product-core 和 order-service(消费方)。
1. 模块一:product-api(轻量级契约模块)
职责:仅存放对外暴露的接口定义、DTO/POJO 实体类、Feign 客户端接口。
依赖:极轻量,只依赖 Web 和 OpenFeign。
1 2 3 4 5 6 7 8 9 10 11
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
|
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface ProductApi { @GetMapping("/product/{id}") ProductInfo getProductById(@PathVariable("id") Integer id); }
@FeignClient(name = "product-service") public interface ProductFeignClient extends ProductApi { }
@Data public class ProductInfo { private Integer id; private String name; }
|
2. 模块二:product-core(核心业务模块)
职责:存放真正的业务逻辑、数据访问层。
依赖:依赖 product-api、MyBatis、数据库驱动等。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>product-api</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> </dependencies>
|
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Service public class ProductCoreServiceImpl implements ProductApi { @Autowired private ProductMapper productMapper;
@Override public ProductInfo getProductById(Integer id) { return productMapper.selectById(id); } }
@Mapper public interface ProductMapper { @Select("SELECT * FROM product WHERE id = #{id}") ProductInfo selectById(Integer id); }
|
3. 服务提供方:product-service(独立部署的微服务)
职责:作为独立服务运行,对外暴露 HTTP 接口。
依赖:依赖 product-core、Spring Boot Web。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RestController public class ProductController implements ProductApi { @Autowired private ProductCoreServiceImpl productCoreService;
@Override public ProductInfo getProductById(Integer id) { return productCoreService.getProductById(id); } }
@SpringBootApplication @MapperScan("com.example.product.core.mapper") public class ProductServiceApplication { public static void main(String[] args) { SpringApplication.run(ProductServiceApplication.class, args); } }
|
4. 消费方:order-service(核心亮点:无缝切换)
职责:调用商品服务,支持通过配置一键切换本地/远程调用。
依赖:
- 始终依赖
product-api(为了获取 ProductApi 接口和 ProductFeignClient)。
- 按需依赖
product-core(如果配置为 local,则必须引入;如果仅用 remote,可不引入)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>product-api</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>product-core</artifactId> <version>1.0.0</version> </dependency> </dependencies>
|
核心代码(条件装配配置类):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration @EnableFeignClients(basePackages = "com.example.product.api") public class ProductClientConfig {
@Bean @ConditionalOnProperty(name = "service.product.mode", havingValue = "remote") public ProductApi remoteProductApi(ProductFeignClient feignClient) { return feignClient; }
@Bean @ConditionalOnProperty(name = "service.product.mode", havingValue = "local", matchIfMissing = true) public ProductApi localProductApi(ProductCoreServiceImpl coreService) { return coreService; } }
|
消费方业务代码(完全解耦):
1 2 3 4 5 6 7 8 9 10 11
| @Service public class OrderService { @Autowired private ProductApi productApi;
public void createOrder(Integer productId) { ProductInfo product = productApi.getProductById(productId); System.out.println("订单创建成功,商品名称:" + product.getName()); } }
|
消费方配置文件:
1 2 3 4
| service: product: mode: local
|
这套架构是目前微服务演进中最标准的“SDK 抽取模式”,既保留了微服务的灵活性,又兼顾了单体的高性能。