演进式架构深度解析:绞杀者模式与抽象分支模式
标签:演进式架构 绞杀者模式 抽象分支 遗留系统改造 重构 微服务迁移
摘要:本文从遗留系统改造痛点出发,深入剖析演进式架构的两大核心模式------绞杀者模式与抽象分支模式,详解其实现原理、适用场景与操作步骤,附生产级迁移案例与风险控制策略。
一、演进式架构概述
1.1 为什么需要演进式架构
┌─────────────────────────────────────────────────────────┐
│ 遗留系统改造的三大困境 │
├─────────────────────────────────────────────────────────┤
│ │
│ 困境1:大爆炸重写(Big Bang Rewrite) │
│ ───────────────────────────────────── │
│ 方案:暂停业务开发,团队全力重写新系统 │
│ 时间:预计6个月,实际18个月 │
│ 结果: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 第3个月:业务需求积压,老系统仍需维护 │ │
│ │ 第6个月:新系统完成50%,技术债务已积累 │ │
│ │ 第9个月:团队分裂,部分成员维护老系统 │ │
│ │ 第12个月:新系统架构过时,业务逻辑理解偏差 │ │
│ │ 第18个月:项目取消或交付即落后 │ │
│ └─────────────────────────────────────────────────┘ │
│ 失败率:70%以上的大爆炸重写项目失败或严重延期 │
│ │
│ 困境2:风险不可控 │
│ ───────────────── │
│ • 新系统未经验证即全量切换,故障影响所有用户 │
│ • 数据迁移一次性完成,回滚困难 │
│ • 业务逻辑理解偏差,功能缺失或错误 │
│ │
│ 困境3:机会成本 │
│ ───────────────── │
│ • 改造期间无法响应市场变化,错失商机 │
│ • 团队士气低落,核心人员流失 │
│ │
│ ═══════════════════════════════════════════════════ │
│ 演进式架构核心思想: │
│ • 增量演进:小步快跑,持续交付价值 │
│ • 风险可控:新旧系统并行,逐步切换流量 │
│ • 可回滚:任何问题可快速回退到稳定状态 │
│ • 验证驱动:每个阶段都有明确的成功标准和验证手段 │
│ ═══════════════════════════════════════════════════ │
│ │
└─────────────────────────────────────────────────────────┘
1.2 演进式架构模式族
┌─────────────────────────────────────────────────────────┐
│ 演进式架构模式全景 │
├─────────────────────────────────────────────────────────┤
│ │
│ 按改造对象分类: │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 系统级改造 │ │ 模块级改造 │ │
│ │ │ │ │ │
│ │ • 绞杀者模式 │ │ • 抽象分支模式 │ │
│ │ (Strangler │ │ (Branch by │ │
│ │ Fig Pattern) │ │ Abstraction) │ │
│ │ │ │ │ │
│ │ 场景:单体→微服务 │ │ 场景:模块内实现替换│ │
│ │ 全系统替换 │ │ 数据库迁移 │ │
│ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 按数据策略分类: │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 数据同步模式 │ │ 数据共享模式 │ │
│ │ │ │ │ │
│ │ • 数据复制 │ │ • 共享数据库 │ │
│ │ • 事件溯源同步 │ │ • API数据访问 │ │
│ │ • 双写模式 │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 按切换策略分类: │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 流量切换模式 │ │ 功能切换模式 │ │
│ │ │ │ │ │
│ │ • 蓝绿部署 │ │ • 特性开关 │ │
│ │ • 金丝雀发布 │ │ • 黑暗发布 │ │
│ │ • A/B测试 │ │ • 抽象分支 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
二、绞杀者模式(Strangler Fig Pattern)
2.1 模式起源与核心思想
模式名称来源于澳大利亚的绞杀榕(Strangler Fig)
绞杀榕种子落在宿主树上,逐渐生长根系包围宿主,最终宿主枯死,绞杀榕成为独立大树。
Martin Fowler, 2004
┌─────────────────────────────────────────────────────────┐
│ 绞杀者模式演进过程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 阶段0:遗留单体系统 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Legacy Monolith │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Order │ │ Payment │ │ Inventory│ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ │ (老旧) │ │ (老旧) │ │ (老旧) │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ └───────────┼───────────┘ │ │
│ │ │ │ │
│ │ ┌────┴────┐ │ │
│ │ │ Shared │ │ │
│ │ │ Database │ │
│ │ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段1:引入绞杀者 facade( facade 模式) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Strangler Facade │ │
│ │ (路由层,新系统的入口) │ │
│ │ │ │ │
│ │ ├──→ 100% 流量路由到 Legacy Monolith │ │
│ │ │ (所有请求走老系统) │ │
│ │ │ │ │
│ │ └──→ 新系统准备中,0%流量 │ │
│ │ │ │
│ │ 关键:Facade对客户端透明,无感知切换 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段2:逐步迁移功能(绞杀开始) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Strangler Facade │ │
│ │ │ │ │
│ │ ├──→ 80% 流量 → Legacy Monolith │ │
│ │ │ (Payment, Inventory) │ │
│ │ │ │ │
│ │ └──→ 20% 流量 → New Order Service │ │
│ │ (新微服务,已验证) │ │
│ │ │ │
│ │ 新Order Service: │
│ │ • 独立数据库(数据同步) │
│ │ • 防腐层隔离老系统依赖 │
│ │ • 功能验证通过后逐步放大流量 │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段3:持续绞杀,功能逐个迁移 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Strangler Facade │ │
│ │ │ │ │
│ │ ├──→ 30% 流量 → Legacy Monolith │ │
│ │ │ (仅剩Inventory) │ │
│ │ │ │ │
│ │ └──→ 70% 流量 → New Services │ │
│ │ (Order + Payment微服务) │ │
│ │ │ │
│ │ 数据同步策略: │
│ │ • 新Order Service写新库,同步到老库(双写) │
│ │ • 老Inventory读老库,通过事件同步到新库 │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段4:遗留系统枯萎,最终移除 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Strangler Facade │ │
│ │ │ │ │
│ │ └──→ 100% 流量 → New Microservices │ │
│ │ (Order, Payment, Inventory) │ │
│ │ │ │
│ │ Legacy Monolith:已停用,数据归档 │ │
│ │ • 保留只读访问一段时间(合规要求) │
│ │ • 最终完全下线 │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段5:绞杀者 facade 可选移除 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ API Gateway (原Facade升级) │ │
│ │ │ │ │
│ │ └──→ Order Service │ │
│ │ └──→ Payment Service │ │
│ │ └──→ Inventory Service │ │
│ │ │ │
│ │ 演进为标准的微服务网关架构 │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
2.2 绞杀者Facade实现
java
/**
* 绞杀者Facade:请求路由层
* 决定请求转发到遗留系统还是新服务
*/
@Component
public class StranglerFacade {
private final LegacySystemClient legacyClient;
private final NewOrderServiceClient newOrderClient;
private final FeatureFlagService featureFlag;
private final TrafficRouter trafficRouter;
/**
* 订单创建路由
*/
public OrderResponse createOrder(CreateOrderRequest request) {
// 策略1:特性开关控制(按功能)
if (featureFlag.isEnabled("new-order-service", request.getUserId())) {
return newOrderClient.createOrder(request);
}
// 策略2:流量百分比控制(金丝雀)
if (trafficRouter.shouldRouteToNew(request.getUserId(), 20)) { // 20%流量
return newOrderClient.createOrder(request);
}
// 默认路由到遗留系统
return legacyClient.createOrder(request);
}
/**
* 订单查询路由(读操作,可A/B测试对比结果)
*/
public OrderResponse getOrder(String orderId) {
// 双读验证:同时调用新旧系统,对比结果
if (featureFlag.isEnabled("dual-read-verify")) {
CompletableFuture<OrderResponse> legacyFuture =
CompletableFuture.supplyAsync(() -> legacyClient.getOrder(orderId));
CompletableFuture<OrderResponse> newFuture =
CompletableFuture.supplyAsync(() -> newOrderClient.getOrder(orderId));
try {
OrderResponse legacyResult = legacyFuture.get(500, TimeUnit.MILLISECONDS);
OrderResponse newResult = newFuture.get(500, TimeUnit.MILLISECONDS);
// 对比结果,记录差异
if (!resultsMatch(legacyResult, newResult)) {
log.warn("Result mismatch for order {}: legacy={}, new={}",
orderId, legacyResult, newResult);
metrics.counter("strangler.result.mismatch").increment();
}
// 返回新系统结果(已验证阶段)
return newResult;
} catch (Exception e) {
// 新系统超时或异常,降级到遗留系统
log.error("New system failed, fallback to legacy", e);
return legacyResult;
}
}
// 正常路由
return featureFlag.isEnabled("new-order-read")
? newOrderClient.getOrder(orderId)
: legacyClient.getOrder(orderId);
}
/**
* 支付路由(关键操作,保守策略)
*/
public PaymentResponse processPayment(PaymentRequest request) {
// 支付操作始终先走遗留系统,新系统并行记录用于验证
PaymentResponse response = legacyClient.processPayment(request);
// 异步同步到新系统(不阻塞主流程)
if (featureFlag.isEnabled("payment-sync-async")) {
newOrderClient.recordPaymentAsync(request, response)
.exceptionally(ex -> {
log.error("Failed to sync payment to new system", ex);
return null;
});
}
return response;
}
}
2.3 数据同步策略
java
/**
* 双写模式:新系统写入时同步到老系统
* 用于新服务为主,老系统只读的场景
*/
@Service
public class DualWriteOrderService {
private final OrderRepository newRepository; // 新系统数据库
private final LegacyOrderClient legacyClient; // 老系统API
private final EventPublisher eventPublisher;
@Transactional
public Order createOrder(CreateOrderCommand command) {
// 1. 写入新系统(主)
Order order = Order.create(command);
newRepository.save(order);
// 2. 同步到老系统(从)
try {
legacyClient.syncOrder(toLegacyFormat(order));
} catch (Exception e) {
// 记录同步失败,异步补偿
log.error("Failed to sync order {} to legacy", order.getId(), e);
eventPublisher.publish(new OrderSyncFailedEvent(order.getId(), e));
// 不抛异常,避免影响主流程
}
return order;
}
}
/**
* 事件溯源同步:通过领域事件异步同步
* 用于新老系统数据最终一致的场景
*/
@Component
public class OrderEventSyncListener {
private final LegacyOrderClient legacyClient;
private final NewOrderQueryService queryService;
@EventListener
@Async
public void onOrderCreated(OrderCreatedEvent event) {
// 新系统已处理,查询完整状态同步到老系统
OrderDTO order = queryService.getOrder(event.getOrderId());
legacyClient.syncOrder(order);
}
@EventListener
@Async
public void onOrderPaid(OrderPaidEvent event) {
legacyClient.updatePaymentStatus(event.getOrderId(), "PAID", event.getAmount());
}
}
/**
* 数据一致性校验Job
*/
@Component
public class DataConsistencyChecker {
private final OrderRepository newRepository;
private final LegacyOrderClient legacyClient;
@Scheduled(fixedRate = 3600000) // 每小时执行
public void checkConsistency() {
// 抽样最近1小时的订单
List<Order> recentOrders = newRepository.findRecent(Duration.ofHours(1));
for (Order order : recentOrders) {
try {
LegacyOrder legacyOrder = legacyClient.getOrder(order.getId());
if (!isConsistent(order, legacyOrder)) {
log.error("Data inconsistency detected: orderId={}, new={}, legacy={}",
order.getId(), order, legacyOrder);
alertService.sendAlert("Data inconsistency: " + order.getId());
// 自动修复或人工介入
reconcile(order, legacyOrder);
}
} catch (Exception e) {
log.error("Failed to check consistency for order {}", order.getId(), e);
}
}
}
}
2.4 生产级案例:某电商单体→微服务
┌─────────────────────────────────────────────────────────┐
│ 案例:某电商平台单体系统改造(2年演进历程) │
├─────────────────────────────────────────────────────────┤
│ │
│ 背景: │
│ • 10年历史Java单体,50万行代码,300+表 │
│ • 团队80人,并行开发冲突严重,季度发布 │
│ • 峰值性能不足,关键促销频繁故障 │
│ │
│ 演进路线: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Year 1 Q1: 基础设施准备 │ │
│ │ • 搭建K8s集群,CI/CD流水线 │ │
│ │ • 引入绞杀者Facade(基于Spring Cloud Gateway) │ │
│ │ • 100%流量路由到单体,Facade验证通过 │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Year 1 Q2-Q3: 商品服务绞杀 │ │
│ │ • 新商品服务开发(3个月) │ │
│ │ • 商品数据双写(新服务写主,同步到单体) │ │
│ │ • 5% → 20% → 50% → 100% 流量逐步切换 │ │
│ │ • 每个阶段观察1-2周,指标:错误率<0.1%,延迟<P99 200ms│ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Year 1 Q4 - Year 2 Q2: 订单+支付服务绞杀 │ │
│ │ • 订单服务(复杂状态机,耗时较长) │ │
│ │ • 支付服务(对接渠道多,防腐层复杂) │ │
│ │ • 关键策略:支付先走老系统,新系统并行验证 │ │
│ │ • 订单查询双读对比,差异率<0.01%后切换 │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Year 2 Q3-Q4: 库存+物流服务绞杀,单体下线 │ │
│ │ • 库存服务(高并发,引入Redis缓存) │ │
│ │ • 物流服务(外部对接多,独立演进) │ │
│ │ • 单体完全下线,仅保留只读查询(合规) │ │
│ │ • 绞杀者Facade演进为API Gateway │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 成果: │
│ • 服务拆分:1个单体 → 8个微服务 │
│ • 发布频率:季度 → 每周(甚至每日) │
│ • 故障恢复:小时级 → 分钟级 │
│ • 团队并行:10个独立小团队(2披萨团队) │
│ • 零大爆炸停机,业务连续性100% │
│ │
│ 关键成功因素: │
│ 1. 业务连续性优先,不追求技术完美 │
│ 2. 每个服务有明确的业务 owner 和成功标准 │
│ 3. 完善的监控、回滚、降级机制 │
│ 4. 数据一致性校验和自动修复 │
│ │
└─────────────────────────────────────────────────────────┘
三、抽象分支模式(Branch by Abstraction)
3.1 模式核心思想
在代码库中创建抽象层,将原有实现与新实现隔离,逐步替换实现而非一次性替换。
Paul Hammant, 2007
┌─────────────────────────────────────────────────────────┐
│ 抽象分支 vs 版本控制分支 │
├─────────────────────────────────────────────────────────┤
│ │
│ 传统方式:版本控制分支(Git Branch) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ main branch │ │
│ │ │ │ │
│ │ ├──→ feature/new-database (3个月开发) │ │
│ │ │ │ │ │
│ │ │ └── 大量冲突,合并困难 │ │
│ │ │ │ │
│ │ └──→ 其他feature分支(并行开发) │ │
│ │ │ │ │
│ │ └── 与database分支冲突 │ │
│ │ │ │
│ │ 问题: │ │
│ │ • 长期分支导致集成地狱,合并冲突频繁 │ │
│ │ • 新功能在分支上,无法及时验证 │ │
│ │ • 分支合并时风险集中,容易引入故障 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 抽象分支:代码内抽象(Trunk-based Development) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ main branch(单一主干,持续集成) │ │
│ │ │ │ │
│ │ ├──→ 引入抽象接口(1天) │ │
│ │ │ │ │ │
│ │ │ ├── DatabaseInterface │ │
│ │ │ ├── OldDatabaseImpl(原有实现) │ │
│ │ │ └── NewDatabaseImpl(新实现,开关控制) │ │
│ │ │ │ │
│ │ ├──→ 小步替换,持续验证(每周) │ │
│ │ │ │ │ │
│ │ │ ├── 10%查询走新实现 → 验证 → 50% → 100% │ │
│ │ │ ├── 任何问题立即回滚(切换开关) │ │
│ │ │ └── 老实现逐步退役 │ │
│ │ │ │ │
│ │ └──→ 其他feature并行开发,无冲突 │ │
│ │ │ │
│ │ 优势: │ │
│ │ • 持续集成,避免长期分支 │ │
│ │ • 渐进式替换,风险分散 │ │
│ │ • 随时可回滚,业务连续性保障 │ │
│ │ • 新功能及时验证,快速反馈 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
3.2 抽象分支实现步骤
java
/**
* 场景:将订单查询从MySQL迁移到Elasticsearch
* 目标:提升复杂查询性能,不影响现有功能
*/
// ========== 步骤1:引入抽象接口 ==========
/**
* 订单查询接口(抽象层)
*/
public interface OrderQueryRepository {
Optional<Order> findById(OrderId id);
List<Order> findByCustomer(CustomerId customerId, PageParam page);
List<Order> search(OrderSearchCriteria criteria);
OrderStatistics getStatistics(DateRange range);
}
// ========== 步骤2:原有实现(保持不变) ==========
@Repository
@Primary // 初始时主实现
public class MySqlOrderQueryRepository implements OrderQueryRepository {
private final JdbcTemplate jdbcTemplate;
@Override
public Optional<Order> findById(OrderId id) {
// 原有MySQL实现
String sql = "SELECT * FROM orders WHERE id = ?";
return jdbcTemplate.query(sql, new OrderMapper(), id.getValue())
.stream()
.findFirst();
}
@Override
public List<Order> search(OrderSearchCriteria criteria) {
// 复杂动态SQL,性能差
StringBuilder sql = new StringBuilder("SELECT * FROM orders WHERE 1=1");
// ... 动态拼接条件
return jdbcTemplate.query(sql.toString(), new OrderMapper());
}
// ...
}
// ========== 步骤3:新实现(特性开关控制) ==========
@Repository
@ConditionalOnProperty(name = "feature.es-order-query.enabled", havingValue = "true")
public class ElasticsearchOrderQueryRepository implements OrderQueryRepository {
private final ElasticsearchRestTemplate esTemplate;
@Override
public Optional<Order> findById(OrderId id) {
// ES实现
return Optional.ofNullable(
esTemplate.get(id.getValue(), OrderDocument.class)
).map(this::toDomain);
}
@Override
public List<Order> search(OrderSearchCriteria criteria) {
// ES原生查询,性能优化
BoolQueryBuilder query = buildQuery(criteria);
SearchHits<OrderDocument> hits = esTemplate.search(
new NativeSearchQueryBuilder()
.withQuery(query)
.withPageable(PageRequest.of(criteria.getPage(), criteria.getSize()))
.build(),
OrderDocument.class
);
return hits.getSearchHits().stream()
.map(hit -> toDomain(hit.getContent()))
.collect(Collectors.toList());
}
// ...
}
// ========== 步骤4:路由实现(渐进式切换) ==========
@Repository
@Primary
public class RoutingOrderQueryRepository implements OrderQueryRepository {
private final MySqlOrderQueryRepository mysqlRepo;
private final ElasticsearchOrderQueryRepository esRepo;
private final FeatureFlagService featureFlag;
private final MeterRegistry metrics;
// 构造器注入两个实现
@Override
public Optional<Order> findById(OrderId id) {
// 简单查询:逐步切换
if (featureFlag.isEnabled("es-query-by-id", id.getValue())) {
return executeWithFallback("findById", () -> esRepo.findById(id),
() -> mysqlRepo.findById(id));
}
return mysqlRepo.findById(id);
}
@Override
public List<Order> search(OrderSearchCriteria criteria) {
// 复杂查询:优先ES,老系统作为降级
if (featureFlag.isEnabled("es-search", criteria.getCustomerId())) {
try {
List<Order> result = esRepo.search(criteria);
metrics.counter("es.search.success").increment();
return result;
} catch (Exception e) {
log.warn("ES search failed, fallback to MySQL", e);
metrics.counter("es.search.fallback").increment();
}
}
return mysqlRepo.search(criteria);
}
/**
* 执行新实现,失败时降级到老实现
*/
private <T> T executeWithFallback(String operation,
Supplier<T> primary,
Supplier<T> fallback) {
try {
T result = primary.get();
metrics.counter(operation + ".primary.success").increment();
return result;
} catch (Exception e) {
log.warn("{} primary failed, using fallback", operation, e);
metrics.counter(operation + ".fallback").increment();
return fallback.get();
}
}
/**
* 双读验证:对比两个实现的结果(用于验证阶段)
*/
public void verifyConsistency(OrderSearchCriteria criteria) {
if (!featureFlag.isEnabled("query-verify-mode")) {
return;
}
List<Order> mysqlResult = mysqlRepo.search(criteria);
List<Order> esResult = esRepo.search(criteria);
if (!resultsEqual(mysqlResult, esResult)) {
log.error("Query result mismatch: criteria={}, mysql={}, es={}",
criteria, mysqlResult.size(), esResult.size());
metrics.counter("query.result.mismatch").increment();
}
}
}
// ========== 步骤5:数据同步(保证一致性) ==========
@Component
public class OrderIndexSyncService {
private final ElasticsearchRestTemplate esTemplate;
@EventListener
@Async
public void onOrderCreated(OrderCreatedEvent event) {
// 订单创建后同步到ES
OrderDocument doc = toDocument(event);
esTemplate.save(doc);
}
@EventListener
@Async
public void onOrderUpdated(OrderUpdatedEvent event) {
// 更新ES索引
esTemplate.update(updateQuery(event));
}
/**
* 全量重建索引(初始迁移或修复)
*/
public void rebuildIndex() {
// 1. 创建新索引(带版本号)
String newIndex = "orders_v2_" + System.currentTimeMillis();
createIndex(newIndex);
// 2. 批量导入数据
orderRepository.findAll()
.map(this::toDocument)
.buffer(1000)
.flatMap(batch -> esTemplate.save(batch, newIndex))
.subscribe();
// 3. 切换别名(原子操作)
switchAlias("orders", newIndex);
}
}
3.3 抽象分支的变体模式
┌─────────────────────────────────────────────────────────┐
│ 抽象分支的四种变体 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 特性开关分支(Feature Toggle Branch) │
│ ───────────────────────────────────── │
│ 场景:新功能开发中,需要频繁集成但不想暴露给用户 │
│ 实现: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ if (featureFlag.isEnabled("new-checkout-flow")) { │ │
│ │ newCheckoutFlow.execute(); │ │
│ │ } else { │ │
│ │ oldCheckoutFlow.execute(); │ │
│ │ } │ │
│ └─────────────────────────────────────────────────┘ │
│ 开关控制:用户白名单 → 百分比 → 全量 │
│ │
│ 2. 数据库抽象分支(Database Branch) │
│ ───────────────────────────────── │
│ 场景:数据库迁移(MySQL → PostgreSQL, 或分库分表) │
│ 实现: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ RepositoryInterface │ │
│ │ ├── JdbcOrderRepository(旧) │ │
│ │ └── JpaOrderRepository(新) │ │
│ │ │ │
│ │ 双写阶段:写入新旧两个库,读取旧库 │ │
│ │ 验证阶段:对比新旧库数据一致性 │ │
│ │ 切换阶段:读取切换到新库,旧库只写(可回滚) │ │
│ │ 退役阶段:停止写入旧库 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 3. API抽象分支(API Branch) │
│ ─────────────────────────── │
│ 场景:外部API升级(支付渠道SDK升级) │
│ 实现: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ PaymentGateway(接口) │ │
│ │ ├── AlipaySdkV1Gateway(旧版SDK) │ │
│ │ └── AlipaySdkV2Gateway(新版SDK) │ │
│ │ │ │
│ │ 灰度策略:按商户ID逐步切换,异常自动降级 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 4. 架构抽象分支(Architecture Branch) │
│ ─────────────────────────────────── │
│ 场景:架构模式升级(同步 → 异步,阻塞 → 响应式) │
│ 实现: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ OrderProcessor(接口) │ │
│ │ ├── SyncOrderProcessor(同步处理) │ │
│ │ └── AsyncOrderProcessor(异步消息) │ │
│ │ │ │
│ │ 逐步迁移:非关键路径先切换,关键路径保留回滚能力 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
3.4 生产级案例:数据库分库分表迁移
┌─────────────────────────────────────────────────────────┐
│ 案例:订单表分库分表改造(10亿级数据) │
├─────────────────────────────────────────────────────────┤
│ │
│ 背景: │
│ • 单表10亿条,查询性能下降,维护困难 │
│ • 目标:按用户ID分16库,每库64表 │
│ │
│ 抽象分支演进: │
│ │
│ 阶段1:引入Sharding抽象(2周) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ OrderRepository(接口) │ │
│ │ ├── SingleTableOrderRepository(原有) │ │
│ │ └── ShardingOrderRepository(新实现,开关控制)│ │
│ │ │ │
│ │ 开关:sharding.enabled=false(默认关闭) │ │
│ │ 验证:单元测试通过,代码审查通过 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段2:双写阶段(4周) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 写入:应用双写到单表和分表(事务保证) │ │
│ │ 读取:从单表读取(主),分表读取验证(异步对比) │ │
│ │ │ │
│ │ 监控: │ │
│ │ • 双写延迟 < 10ms │ │
│ │ • 数据一致性差异率 < 0.001% │ │
│ │ • 分表查询性能提升 5x(压测验证) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段3:灰度读切换(4周) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 按用户ID尾号逐步切换: │ │
│ │ Week1: 尾号0-1 → 10%流量读分表 │ │
│ │ Week2: 尾号0-3 → 25%流量读分表 │ │
│ │ Week3: 尾号0-7 → 50%流量读分表 │ │
│ │ Week4: 尾号0-F → 100%流量读分表 │ │
│ │ │ │
│ │ 回滚策略:任何异常立即切回单表读取 │ │
│ │ 验证:对比单表和分表查询结果,差异告警 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段4:单表写入退役(2周) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 停止写入单表,仅保留读取(应急回滚用) │ │
│ │ 监控单表查询量,确认无遗漏 │ │
│ │ 数据归档策略制定(冷数据迁移到OSS) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 阶段5:单表完全下线(1周后) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 删除SingleTableOrderRepository实现 │ │
│ │ 单表数据备份后删除(释放存储) │ │
│ │ 监控和回滚代码清理 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 关键指标: │
│ • 整个过程零停机,业务无感知 │
│ • 数据零丢失(双写+校验) │
│ • 回滚时间 < 5分钟(开关切换) │
│ • 查询性能提升:P99从2s降到200ms │
│ │
└─────────────────────────────────────────────────────────┘
四、两种模式对比与选型
┌─────────────────────────────────────────────────────────┐
│ 绞杀者模式 vs 抽象分支模式 │
├─────────────────────────────────────────────────────────┤
│ │
│ 维度 绞杀者模式 抽象分支模式 │
│ ───────────────────────────────────────────────────── │
│ │
│ 改造范围 系统级(单体→微服务) 模块级(数据库、组件) │
│ │
│ 架构变化 物理架构变化(新服务) 逻辑架构变化(代码内) │
│ │
│ 部署方式 新旧系统并行部署 单一系统,内部切换 │
│ │
│ 数据策略 数据迁移、同步、双写 数据抽象、路由、切换 │
│ │
│ 技术栈 可以完全不同(Java→Go) 通常相同技术栈 │
│ │
│ 团队组织 可独立团队并行开发 同一团队协作 │
│ │
│ 适用场景 遗留系统全面改造 技术债务局部重构 │
│ │
│ 典型周期 6个月-2年 2周-3个月 │
│ │
│ 风险控制 流量控制、灰度发布 特性开关、快速回滚 │
│ │
│ ═══════════════════════════════════════════════════ │
│ 组合使用场景: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 大型遗留系统改造: │ │
│ │ • 先用绞杀者模式拆分出独立服务(系统级) │ │
│ │ • 服务内部用抽象分支模式重构模块(模块级) │ │
│ │ │ │
│ │ 例如:订单服务绞杀出来后,内部用抽象分支替换数据库实现 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
五、演进式架构的成功要素
| 要素 | 说明 | 实践 |
|---|---|---|
| 自动化测试 | 快速验证变更正确性 | 单元测试>80%,集成测试覆盖核心流程 |
| 监控告警 | 及时发现问题 | 业务指标、技术指标、数据一致性三重监控 |
| 特性开关 | 控制功能曝光 | LaunchDarkly/Unleash/自研开关平台 |
| 数据一致性 | 保证迁移过程数据正确 | 双写校验、对账Job、差异修复 |
| 快速回滚 | 降低故障影响 | 开关切换<5分钟,数据库回滚<30分钟 |
| 团队共识 | 理解演进价值 | 定期复盘,分享成功案例,建立信心 |
六、总结
| 模式 | 核心思想 | 关键实践 | 风险控制 |
|---|---|---|---|
| 绞杀者模式 | 逐步替换整个系统 | Facade路由、数据同步、流量切换 | 灰度发布、双读验证、快速回滚 |
| 抽象分支模式 | 代码内渐进替换 | 抽象接口、特性开关、双实现并存 | 开关控制、对比验证、降级策略 |
演进式架构的本质:
- 不是不犯错,而是犯错后能快速恢复
- 不是追求完美设计,而是持续改进设计
- 不是大爆炸替换,而是小步快跑验证
参考文档:
- Patterns of Enterprise Application Architecture - Martin Fowler
- Continuous Delivery - Jez Humble
- Refactoring Databases - Scott Ambler