在带领团队完成多个千万级 DAU 项目后,我深刻体会到架构设计并非一蹴而就,而是持续演进的过程:
TL-Java 架构第七期的课程正是围绕这三个层次展开,将理论(领域驱动设计、CAP 定理、最终一致性)与实战(Spring Cloud Alibaba、Seata、Sentinel、Micrometer)深度结合。本文截取其中最具代表性的五个实战模块,用代码还原架构师的设计决策。
很多团队拆分微服务时,习惯按“技术层”拆分(Controller-Service-DAO 各成一个服务),导致服务内聚性差、改动扩散。正确的做法是按业务能力(Bounded Context)拆分。
以电商订单为例,我们将“订单创建”和“库存扣减”拆分为两个独立服务,通过领域事件解耦。以下是一个典型的订单聚合根代码(使用 Spring Boot + JPA):
// Order.java - 聚合根
package com.tl.architecture.order.domain;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
private OrderStatus status;
private LocalDateTime createdTime;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "order_id")
private List<OrderItem> items;
// 领域行为:创建订单(工厂方法)
public static Order create(Long userId, List<OrderItem> items) {
Order order = new Order();
order.orderNo = "ORD" + System.currentTimeMillis();
order.userId = userId;
order.items = items;
order.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
order.status = OrderStatus.CREATED;
order.createdTime = LocalDateTime.now();
return order;
}
// 领域行为:取消订单
public void cancel() {
if (this.status == OrderStatus.PAID) {
throw new IllegalStateException("已支付订单无法取消");
}
this.status = OrderStatus.CANCELLED;
}
}
// OrderStatus 枚举
enum OrderStatus {
CREATED, PAID, SHIPPED, COMPLETED, CANCELLED
}使用 Repository 模式隔离数据访问,领域服务编排跨聚合的操作:
// OrderRepository.java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findByOrderNo(String orderNo);
}
// OrderDomainService.java - 领域服务
@Service
@Transactional
public class OrderDomainService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private DomainEventPublisher eventPublisher;
public Order createOrder(Long userId, List<OrderItem> items) {
Order order = Order.create(userId, items);
Order saved = orderRepository.save(order);
// 发布订单创建事件,由库存服务订阅
eventPublisher.publish(new OrderCreatedEvent(saved.getId(), saved.getUserId(), items));
return saved;
}
}架构决策点:订单服务不直接调用库存服务,而是通过 消息队列(RocketMQ) 发送事件,实现最终一致性。这避免了分布式事务的刚性开销。
在涉及资金、库存等强一致性场景时,事件驱动的最终一致性可能不够。此时采用 TCC(Try-Confirm-Cancel) 模式,Seata 框架提供了完善的支持。
// InventoryTccAction.java
@LocalTCC
public interface InventoryTccAction {
@TwoPhaseBusinessAction(name = "deductInventory", commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeductInventory(
@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "quantity") Integer quantity,
BusinessActionContext context
);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}@Service
public class InventoryTccActionImpl implements InventoryTccAction {
@Autowired
private InventoryRepository inventoryRepo;
@Override
public boolean tryDeductInventory(Long productId, Integer quantity, BusinessActionContext context) {
// Try阶段:预扣库存,将库存状态置为“冻结”
Inventory inventory = inventoryRepo.findByProductId(productId);
if (inventory.getAvailableQty() < quantity) {
return false; // 冻结失败,触发Cancel
}
inventory.setFrozenQty(inventory.getFrozenQty() + quantity);
inventory.setAvailableQty(inventory.getAvailableQty() - quantity);
inventoryRepo.save(inventory);
// 将业务数据存入上下文,供Confirm/Cancel使用
context.setContext("productId", String.valueOf(productId));
context.setContext("quantity", String.valueOf(quantity));
return true;
}
@Override
public boolean confirm(BusinessActionContext context) {
Long productId = Long.valueOf(context.getContext("productId"));
Integer quantity = Integer.valueOf(context.getContext("quantity"));
// Confirm阶段:扣减冻结库存,即真正减少库存
Inventory inventory = inventoryRepo.findByProductId(productId);
inventory.setFrozenQty(inventory.getFrozenQty() - quantity);
// 实际可用库存已在Try阶段扣除,此处无需再减
inventoryRepo.save(inventory);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
Long productId = Long.valueOf(context.getContext("productId"));
Integer quantity = Integer.valueOf(context.getContext("quantity"));
// Cancel阶段:释放冻结库存
Inventory inventory = inventoryRepo.findByProductId(productId);
inventory.setFrozenQty(inventory.getFrozenQty() - quantity);
inventory.setAvailableQty(inventory.getAvailableQty() + quantity);
inventoryRepo.save(inventory);
return true;
}
}@Service
public class OrderTccService {
@Autowired
private InventoryTccAction inventoryTcc;
@GlobalTransactional // Seata 全局事务注解
public void createOrderWithTcc(OrderDTO dto) {
// 1. 创建订单(本地事务)
Order order = orderService.createLocalOrder(dto);
// 2. 调用库存服务的TCC接口
boolean result = inventoryTcc.tryDeductInventory(dto.getProductId(), dto.getQuantity(), null);
if (!result) {
throw new RuntimeException("库存不足");
}
// 3. 其他业务...
// 如果这里抛出异常,Seata会自动触发所有TCC的Cancel
}
}架构决策点:TCC 适用于短事务、高一致性要求场景,但需注意空回滚和悬挂问题,Seata 已内置防悬挂机制。对于长事务,考虑 SAGA 模式。
网关是流量的入口,除了路由转发,还需承载认证、限流、日志等横切关注点。我们基于 Spring Cloud Gateway 实现一个灰度发布过滤器,根据请求头中的 version 动态路由到不同版本的服务。
// GrayReleaseFilter.java
@Component
public class GrayReleaseFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String version = request.getHeaders().getFirst("X-version");
if (StringUtils.hasText(version)) {
// 将版本号存入路由属性,供RoutePredicate使用
exchange.getAttributes().put("version", version);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1; // 高优先级
}
}
// 自定义路由断言工厂(仅部分代码)
@Component
public class VersionRoutePredicateFactory extends AbstractRoutePredicateFactory<VersionRoutePredicateFactory.Config> {
public VersionRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String version = exchange.getAttribute("version");
if (version == null) return config.getDefaultVersion().equals("v1");
return version.equals(config.getVersion());
};
}
public static class Config {
private String version;
private String defaultVersion = "v1";
// getter/setter 省略
}
}在 application.yml 中配置两条路由:
spring:
cloud:
gateway:
routes:
- id: order-service-v1
uri: lb://order-service-v1
predicates:
- Path=/order/**
- Version= v1
- id: order-service-v2
uri: lb://order-service-v2
predicates:
- Path=/order/**
- Version= v2架构决策点:灰度发布是微服务架构中降低上线风险的重要手段,结合注册中心的元数据(如 eureka.instance.metadata-map.version),可以实现平滑切流。
排查线上故障时,只依赖日志远远不够。我们引入 Micrometer Tracing + Zipkin,实现请求的全链路追踪,并在关键节点添加业务自定义 Span,以记录订单支付、库存扣减等关键步骤的耗时与状态。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>// TraceAnnotation.java - 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceAnnotation {
String value() default "";
}
// TracingAspect.java
@Aspect
@Component
public class TracingAspect {
@Autowired
private Tracer tracer; // Brave Tracer
@Around("@annotation(trace)")
public Object traceMethod(ProceedingJoinPoint pjp, TraceAnnotation trace) throws Throwable {
String spanName = trace.value().isEmpty() ? pjp.getSignature().getName() : trace.value();
Span span = tracer.nextSpan().name(spanName).start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 添加业务标签
span.tag("class", pjp.getTarget().getClass().getSimpleName());
Object result = pjp.proceed();
span.tag("result", "success");
return result;
} catch (Throwable t) {
span.tag("error", t.getMessage());
span.error(t);
throw t;
} finally {
span.finish();
}
}
}
// 业务代码中使用
@Service
public class PaymentService {
@TraceAnnotation("支付流程")
public void processPayment(PaymentRequest request) {
// 业务逻辑
}
}架构决策点:可观测性不仅是 APM 工具,更是业务监控的基础。通过自定义 Span,我们能够将业务指标(如支付成功率、平均响应时间)与系统指标(GC、CPU)关联分析,快速定位到底是代码慢还是资源争抢。
压测是架构演进的重要验证手段。我们通过 JMeter 模拟混合流量,结合 Prometheus + Grafana 监控系统瓶颈,并根据压测结果配置 Sentinel 降级规则。
// 在 JMeter 中模拟用户登录 token
import org.apache.commons.codec.digest.DigestUtils;
String userId = vars.get("userId");
String timestamp = String.valueOf(System.currentTimeMillis());
String sign = DigestUtils.md5Hex(userId + timestamp + "secret");
vars.put("sign", sign);
vars.put("timestamp", timestamp);假设压测发现,当 /pay 接口 QPS 超过 5000 时,响应时间从 50ms 飙升到 2000ms,且 CPU 达到 80%。我们据此在 Sentinel 控制台配置慢调用比例降级:
// 代码方式配置(也可在控制台动态配置)
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("pay");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setCount(200); // 响应时间阈值 200ms
rule.setTimeWindow(10); // 降级持续时间 10s
rule.setMinRequestAmount(20);
rule.setStatIntervalMs(1000);
rule.setSlowRatioThreshold(0.3); // 30% 请求超过阈值则触发降级
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}同时,在业务代码中添加熔断后的降级处理:
@Service
public class PayService {
@SentinelResource(value = "pay", fallback = "payFallback")
public PayResult pay(PayRequest req) {
// 实际支付逻辑
}
public PayResult payFallback(PayRequest req, Throwable ex) {
// 降级返回快速失败
return PayResult.fail("系统繁忙,请稍后重试");
}
}架构决策点:压测不是一次性的,而是伴随架构演进的常态化活动。每一次大版本上线前,都应执行容量评估,并根据评估结果调整弹性伸缩策略(如 HPA 阈值)。
TL-Java 架构第七期的课程精华,在于将架构理论与工业级代码紧密结合。本文的五个模块分别对应了:
架构师的价值,正是通过“预判”和“设计”,将这些关键技术点有机串联,形成一套可演进、可运维的系统。如果本文的代码让你有所收获,不妨在自己的项目中尝试落地,并持续关注性能数据和业务反馈——架构没有终点,只有持续优化。
免责声明:本文涉及 Seata、Sentinel 等开源组件,版本可能迭代,请以官方最新文档为准。生产环境使用前务必进行充分测试。
关于作者:TL-Java 架构课程讲师,前互联网大厂架构师,专注于分布式系统与云原生技术。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。