← 返回博客
2026-06-24 10:30:00

Spring Cloud 组件替换实战:每一层都有更好的选择

Spring Cloud 组件替换实战:每一层都有更好的选择

2026-06-24 10:00:00 · 标签:Spring Cloud、微服务、组件选型、Kafka、Kong、APISIX、教程

引言

上一篇文章《Spring Cloud Alibaba 微服务实战》用全套 Alibaba 技术栈搭了一遍完整系统。但现实中的技术选型从来不是"全家桶一把梭"——不同的业务场景、团队背景、成本预算,对应的最佳组件往往不同。

本文逐一拆解微服务架构的每一层,给出可替换方案 + 实际切换代码,让你能根据自己项目的实际情况灵活组合。


一、注册中心:Nacos 的替代方案

1.1 选型对比

维度NacosConsulEurekaZookeeper
CAPAP / 可切换 CPCPAPCP
配置中心内置内置 KV
健康检查TCP/HTTP/MySQLTCP/HTTP/gRPCHTTPTCP
一致性Raft (CP模式)Raft无(最终一致)ZAB
多数据中心支持原生支持不支持不支持
运维复杂度
社区活跃度高(国内)中(国际)低(已停维)

1.2 切换到 Consul

Docker 启动 Consul

docker run -d --name consul \
  -p 8500:8500 \
  consul:1.18 agent -server -bootstrap -ui -client=0.0.0.0

替换依赖

<!-- 移除 Nacos -->
<!--
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
-->

<!-- 引入 Consul -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

修改配置(仅改 bootstrap.yml,业务代码零改动):

# 改前:Nacos
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

# 改后:Consul
spring:
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
        instance-id: ${spring.application.name}:${server.port}
        health-check-path: /actuator/health
        health-check-interval: 10s

关键点:Spring Cloud 的服务发现抽象层(DiscoveryClient)屏蔽了底层实现差异。只要你的代码是通过 @FeignClient(name = "shop-order") 做调用,底层注册中心从 Nacos 切成 Consul,Feign 依然正常工作——它只认服务名,不关心注册中心的品牌。

1.3 切换到 Eureka(不推荐,但历史项目多)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
spring:
  application:
    name: eureka-server
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

客户端配置:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

注意:Eureka 2.x 已停止维护。如果是从零开始的新项目,不建议选它。但如果你接手的是一个老项目,至少知道它的替换点在哪。


二、网关:Gateway 的替代方案

2.1 选型对比

维度Spring Cloud GatewayKongAPISIXZuul
性能中(WebFlux)高(OpenResty)高(etcd + Radixtree)低(Servlet 阻塞)
插件生态丰富(200+)丰富(100+)
动态路由需配合 Nacos原生 Admin API原生 Admin API需重启
鉴权手写 Filter内置 JWT/OAuth2/Keycloak内置 JWT/OAuth2手写 Filter
限流需配合 Sentinel内置多种算法内置多种算法
控制面板Kong Manager 可视化APISIX Dashboard
学习成本低(Spring 生态)中(Lua 插件开发)

2.2 切换到 Kong

Docker 部署

# 使用 Docker Compose 部署 Kong + Postgres
# 官方提供完整 compose 文件,此处简化为关键步骤
docker network create kong-net

docker run -d --name kong-db --network kong-net \
  -e POSTGRES_DB=kong -e POSTGRES_USER=kong -e POSTGRES_PASSWORD=kong \
  postgres:15

docker run --rm --network kong-net \
  -e KONG_DATABASE=postgres -e KONG_PG_HOST=kong-db \
  kong:3.7 kong migrations bootstrap

docker run -d --name kong --network kong-net \
  -p 8000:8000 -p 8443:8443 -p 8001:8001 \
  -e KONG_DATABASE=postgres -e KONG_PG_HOST=kong-db \
  -e KONG_ADMIN_LISTEN=0.0.0.0:8001 \
  kong:3.7

通过 Admin API 配置路由(替代 Gateway 的 yaml 配置):

# 注册上游服务(对应 Nacos 中的服务)
curl -X POST http://localhost:8001/upstreams \
  -d name=shop-user.upstream

curl -X POST http://localhost:8001/upstreams/shop-user.upstream/targets \
  -d target=192.168.1.10:8081 -d weight=100

curl -X POST http://localhost:8001/upstreams/shop-user.upstream/targets \
  -d target=192.168.1.11:8081 -d weight=100

# 创建 Service
curl -X POST http://localhost:8001/services \
  -d name=shop-user \
  -d url=http://shop-user.upstream

# 创建 Route
curl -X POST http://localhost:8001/routes \
  -d name=user-route \
  -d paths[]=/api/user \
  -d strip_path=true \
  -d service.name=shop-user

JWT 鉴权插件(替代 Gateway 的 AuthGlobalFilter):

# 全局启用 JWT 插件
curl -X POST http://localhost:8001/plugins \
  -d name=jwt \
  -d config.claims_to_verify=exp

# 创建消费者和凭证
curl -X POST http://localhost:8001/consumers \
  -d username=app-user

curl -X POST http://localhost:8001/consumers/app-user/jwt \
  -d algorithm=HS256 \
  -d key=your-app-key \
  -d secret=your-256-bit-secret

对比总结:Kong 的鉴权、限流、日志全是插件化配置,不需要写代码。代价是你需要学习它的 Admin API 和 Lua 插件体系。如果团队以运维为主、不想写网关层 Java 代码,Kong 是更好的选择。

2.3 切换到 APISIX

# APISIX 比 Kong 更轻量,依赖 etcd 而非 Postgres
docker run -d --name apisix \
  -p 9080:9080 -p 9443:9443 -p 9180:9180 \
  -e APISIX_STAND_ALONE=true \
  apache/apisix:3.10
# 创建路由(同样通过 Admin API)
curl -X PUT http://localhost:9180/apisix/admin/routes/1 \
  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
  -d '{
    "uri": "/api/user/*",
    "plugins": {
        "jwt-auth": {},
        "limit-req": {
            "rate": 100,
            "burst": 50,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "192.168.1.10:8081": 1,
            "192.168.1.11:8081": 1
        }
    }
}'

APISIX 比 Kong 的优势:无需数据库(只需 etcd),路由匹配性能更高(Radixtree 算法),配置更简洁。


三、服务调用:OpenFeign 的替代方案

3.1 选型对比

维度OpenFeignDubbogRPCRestTemplate
协议HTTP/1.1TCP 自定义 + HTTP/2HTTP/2 (Protobuf)HTTP/1.1
序列化JSONHessian2 / ProtobufProtobufJSON
性能
跨语言天然支持有限天然支持天然支持
服务治理依赖 Sentinel内置丰富需外挂
Spring 集成原生spring-cloud-dubbogrpc-spring-boot原生
适用场景常规微服务高性能 Java 内部调用多语言异构系统简单调用

3.2 切换到 Dubbo(高性能 Java 内部调用)

<!-- 替换 Feign -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-registry-nacos</artifactId>
    <version>3.3.0</version>
</dependency>

定义服务接口(需要抽取公共 API 模块,这是 Dubbo 和 Feign 的最大差异):

// shop-api 模块中的接口
public interface OrderService {
    Result<OrderVO> createOrder(CreateOrderDTO dto);
    Result<OrderVO> getOrder(Long orderId);
}

// shop-order 中的实现
@DubboService(version = "1.0.0")
public class OrderServiceImpl implements OrderService {
    // 业务逻辑
}

// shop-user 中调用
@DubboReference(version = "1.0.0", timeout = 3000, retries = 0)
private OrderService orderService;

Dubbo vs Feign 的核心区别

3.3 切换到 gRPC(多语言异构系统)

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>

定义 Protobuf(gRPC 的核心差异点——先写 .proto 文件):

// order.proto
syntax = "proto3";
package shop;

service OrderService {
  rpc CreateOrder (CreateOrderReq) returns (OrderResp);
  rpc GetOrder (GetOrderReq) returns (OrderResp);
}

message CreateOrderReq {
  int64 user_id = 1;
  int64 goods_id = 2;
  int32 quantity = 3;
}

message OrderResp {
  int32 code = 1;
  string message = 2;
  OrderData data = 3;
}

Java 服务端实现

@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {

    @Override
    public void createOrder(CreateOrderReq request,
                            StreamObserver<OrderResp> responseObserver) {
        // 业务逻辑
        OrderResp resp = OrderResp.newBuilder()
            .setCode(200)
            .setMessage("success")
            .build();
        responseObserver.onNext(resp);
        responseObserver.onCompleted();
    }
}

适用场景总结


四、熔断限流:Sentinel 的替代方案

4.1 选型对比

维度SentinelResilience4jHystrix
限流内置需配合 RateLimiter
熔断慢调用/异常比例/异常数慢调用/异常比例异常比例
控制台有(功能完整)无(需自建或配合 Prometheus)Dashboard(功能弱)
规则动态下发支持(Nacos/Apollo)支持(Config Server)支持(Archaius)
线程池隔离支持支持(Bulkhead)支持
维护状态活跃活跃停维
Spring 集成Alibaba 原生Spring Cloud Circuit Breaker 抽象层Spring Cloud Netflix

4.2 切换到 Resilience4j

替换依赖(关键变更——通过 Spring Cloud Circuit Breaker 抽象层):

<!-- 移除 Sentinel -->
<!--
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
-->

<!-- 引入 Resilience4j -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

配置

resilience4j:
  circuitbreaker:
    configs:
      default:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 100
        failure-rate-threshold: 50         # 50% 失败则熔断
        wait-duration-in-open-state: 10s   # 熔断后 10 秒尝试半开
        permitted-number-of-calls-in-half-open-state: 5
        slow-call-rate-threshold: 50       # 50% 慢调用则熔断
        slow-call-duration-threshold: 2s   # 超过 2 秒算慢调用
    instances:
      createOrder:
        base-config: default

  timelimiter:
    configs:
      default:
        timeout-duration: 5s               # 超时 5 秒

代码层面——零侵入切换

@Service
public class OrderService {

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private CircuitBreakerFactory circuitBreakerFactory;

    public Result<Void> deductStockWithCircuitBreaker(StockDeductDTO dto) {
        // 通过 CircuitBreaker 抽象层,底层可以任意切换 Sentinel / Resilience4j
        CircuitBreaker cb = circuitBreakerFactory.create("deductStock");
        return cb.run(
            () -> goodsClient.deductStock(dto),                    // 正常调用
            throwable -> {
                log.error("库存扣减熔断", throwable);
                return Result.fail("库存服务繁忙,请稍后重试");     // 降级返回
            }
        );
    }
}

核心价值spring-cloud-starter-circuitbreaker-resilience4j 实现了 Spring Cloud Circuit Breaker 抽象层。你的业务代码只依赖这个抽象层,底层换成 Sentinel 或 Resilience4j 只改依赖和配置文件,业务代码不用动。

4.3 RateLimiter 替换 Sentinel 限流

Resilience4j 自身不带限流。如果需要,用 Guava RateLimiter 补充:

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    private final RateLimiter rateLimiter = RateLimiter.create(200.0); // 每秒 200

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        if (!rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
            response.setStatus(429);
            response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁\"}");
            return false;
        }
        return true;
    }
}

注意:Guava RateLimiter 是单机限流,分布式场景需要换成 Redis 令牌桶或 Sentinel 集群限流。


五、消息队列:RocketMQ 的替代方案

5.1 选型对比

维度RocketMQKafkaRabbitMQPulsar
吞吐量十万级百万级万级十万级
延迟毫秒级毫秒级微秒级毫秒级
消息顺序严格顺序(队列级)分区有序队列有序分区有序
事务消息原生支持需外挂(KIP-98)插件原生支持
延迟消息18 个等级需外挂插件原生支持
流处理有限Kafka Streams / ksqlDB有限Pulsar Functions
运维复杂度
社区国内活跃全球活跃全球活跃增长中

5.2 切换到 Kafka

场景:需要百万级吞吐 + 流处理 + 大数据生态打通。

<!-- 替换 RocketMQ -->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

生产者

@Service
public class OrderKafkaProducer {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void sendOrderCreated(OrderCreatedEvent event) {
        String json = JSON.toJSONString(event);
        // key 用 userId,保证同一用户的消息进入同一分区,顺序处理
        kafkaTemplate.send("order-created-topic",
                String.valueOf(event.getUserId()), json)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("Kafka 发送失败 orderId={}", event.getOrderId(), ex);
                    // 写入本地消息表
                    messageLogMapper.insert(new MessageLog(event, 0));
                }
            });
    }
}

消费者

@Component
public class OrderKafkaConsumer {

    @KafkaListener(topics = "order-created-topic", groupId = "sms-notify-group")
    public void onMessage(ConsumerRecord<String, String> record) {
        OrderCreatedEvent event = JSON.parseObject(record.value(), OrderCreatedEvent.class);
        log.info("收到订单创建事件 orderId={} partition={} offset={}",
            event.getOrderId(), record.partition(), record.offset());

        // 发送短信通知...
    }
}

Kafka vs RocketMQ 的核心差异

RocketMQKafka
消息模型:Topic > Queue > MessageTopic > Partition > Message
同一个 Queue 内严格有序同一个 Partition 内严格有序
消费失败有重试机制(内置)需手动实现重试(或 Seek 回退)
长轮询拉取(消费者无感知)拉模式(可配长轮询),需关注 poll 间隔
事务消息:二阶段提交事务消息:KIP-98 较新,稳定性待验证

什么时候选 Kafka

什么时候选 RocketMQ

5.3 切换到 RabbitMQ

场景:低延迟 + 灵活路由 + 低运维成本。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
// 生产者
rabbitTemplate.convertAndSend("order.exchange", "order.created", event);

// 消费者
@RabbitListener(queues = "order.created.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
    // 业务处理
}

RabbitMQ 的交换机类型(Direct/Topic/Fanout/Headers)比 Kafka 和 RocketMQ 的路由更灵活,适合需要复杂消息路由的场景。


六、分布式事务:Seata 的替代方案

6.1 选型对比

方案一致性性能复杂度业务侵入
Seata AT强一致(最终)极低(一个注解)
Seata TCC强一致需实现 try/confirm/cancel
RocketMQ 事务消息最终一致
Saga 编排最终一致需实现补偿逻辑
本地消息表最终一致

6.2 用 RocketMQ 事务消息替代 Seata

核心思想:不用全局事务,改为"先发半消息 → 执行本地事务 → 提交/回滚消息"的模式。

@Service
public class OrderServiceWithTransactionMQ {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private GoodsClient goodsClient;

    /**
     * 下单:事务消息保证"订单创建"与"库存扣减通知"同时成功或同时失败
     */
    public Result<OrderVO> createOrder(CreateOrderDTO dto) {

        // 1. 检查库存(预校验,不扣减)
        Result<GoodsVO> goodsResult = goodsClient.getGoodsById(dto.getGoodsId());
        if (goodsResult.getCode() != 200) {
            return Result.fail("商品不存在");
        }

        // 2. 创建订单(本地事务)
        Order order = buildOrder(dto);
        orderMapper.insert(order);

        // 3. 发送事务消息——通知 inventory-service 扣减库存
        //    如果这步失败,上面的订单创建不受影响(因为还没提交消息)
        //    如果消息提交成功,inventory-service 消费后扣库存
        //    如果 inventory-service 消费失败,会触发重试或补偿
        String json = JSON.toJSONString(
            new StockDeductMsg(dto.getGoodsId(), dto.getQuantity(), order.getId())
        );
        rocketMQTemplate.asyncSend("stock-deduct-topic", json);

        log.info("订单创建成功 orderId={}", order.getId());
        return Result.ok(OrderVO.from(order));
    }
}

库存服务的消费者(负责扣库存,失败了走补单逻辑)

@Component
@RocketMQMessageListener(
    topic = "stock-deduct-topic",
    consumerGroup = "stock-deduct-group"
)
public class StockDeductConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        StockDeductMsg msg = JSON.parseObject(message, StockDeductMsg.class);

        try {
            // 扣减库存(本地事务)
            goodsStockMapper.deduct(msg.getGoodsId(), msg.getQuantity());
            log.info("库存扣减成功 orderId={}", msg.getOrderId());
        } catch (Exception e) {
            log.error("库存扣减失败 orderId={}", msg.getOrderId(), e);
            // 补偿逻辑:标记订单为"库存不足",通知用户退款
            orderService.markStockFailed(msg.getOrderId());
            // 同时通知客服介入
        }
    }
}

对比 Seata

维度Seata ATRocketMQ 事务消息
一致性强一致(回滚保证)最终一致(需写补偿代码)
性能中(undo_log 有开销)
代码侵入低(一个注解)中(需写生产者和消费者)
适用场景不容忍中间态的业务可容忍短时间不一致的业务

七、容器编排:Kubernetes 的轻量替代

7.1 什么时候不需要 K8s

场景建议
1-3 台服务器Docker Compose
3-10 台服务器 + 简单部署Docker Swarm
不需要自动扩缩容Compose / Swarm
不需要服务网格Compose / Swarm
开发/测试环境Compose

7.2 切换到 Docker Swarm

# 初始化 Swarm 集群(第一台机器)
docker swarm init --advertise-addr 192.168.1.10

# 其他节点加入
docker swarm join --token SWMTKN-xxx 192.168.1.10:2377

部署服务栈(一个 compose 文件部署到整个集群):

# docker-stack.yml —— 和 Docker Compose 几乎一样,只多了 deploy 配置
version: '3.8'
services:
  shop-user:
    image: registry.yourdomain.com/shop/shop-user:latest
    ports:
      - "8081:8080"
    environment:
      - NACOS_ADDR=nacos:8848
    deploy:
      replicas: 3
      update_config:
        parallelism: 1          # 每次更新 1 个副本
        delay: 10s
        order: start-first
      rollback_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
    networks:
      - shop-net

networks:
  shop-net:
    driver: overlay               # 跨主机网络
# 部署到 Swarm 集群
docker stack deploy -c docker-stack.yml shop

# 查看运行状态
docker stack ps shop

# 扩容
docker service scale shop_shop-user=5

# 滚动更新
docker service update --image registry.yourdomain.com/shop/shop-user:1.2.4 shop_shop-user

Swarm vs K8s

Docker SwarmKubernetes
学习曲线1 天1-2 周
配置复杂度极低(Compose 文件即可)高(Deployment + Service + Ingress + ...)
扩缩容docker service scaleHPA 自动 + 手动
滚动更新内置,简单精细控制,复杂
服务发现内置 DNS需 Service + CoreDNS
社区/生态统治级

八、日志系统:ELK 的轻量替代

8.1 Loki + Grafana(轻量级日志方案)

ELK 太重——ES 吃内存、Logstash 吃 CPU。对于小团队,Loki + Grafana 的方案成本低得多。

# docker-compose-loki.yml
version: '3.8'
services:
  loki:
    image: grafana/loki:3.1
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml

  grafana:
    image: grafana/grafana:11.0
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true

  promtail:
    image: grafana/promtail:3.1
    volumes:
      - /var/log:/var/log
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
# promtail 配置 —— 采集容器日志
scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        target_label: 'container'

在 Grafana 中配置 Loki 为数据源,查询语法 LogQL:

{container=~"shop-.*"} |= "ERROR"
{app="shop-order"} | json | level="ERROR" | line_format "{{.message}}"

什么时候选 Loki 而不是 ELK


九、CI/CD:GitHub Actions 的替代方案

9.1 选型对比

维度GitHub ActionsGitLab CIJenkinsTeamCityTekton
部署方式SaaSSaaS/自建自建自建K8s 原生
配置语言YAMLYAMLGroovy / Pipeline DSLKotlin DSL / UIYAML + K8s CRD
构建环境GitHub runner / 自建GitLab runnerJenkins agentBuild AgentK8s Pod
插件生态Actions Marketplace极丰富丰富K8s 生态
学习成本
免费额度2000 分钟/月(公开仓库无限)400 分钟/月无限制(自建)3 Build Agent 免费无限制(自建)
适合团队开源 + 中小团队自建 GitLab大团队自定义Java/.NET 团队K8s 原生团队

9.2 切换到 Jenkins(自建灵活度最高)

docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts

Jenkinsfile(声明式流水线)

pipeline {
    agent any

    environment {
        REGISTRY = 'registry.yourdomain.com'
        IMAGE = 'shop/shop-order'
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/yourcompany/shop-cloud.git'
            }
        }

        stage('Build & Test') {
            steps {
                sh 'mvn clean verify -B'
            }
            post {
                success {
                    junit '**/target/surefire-reports/*.xml'
                }
            }
        }

        stage('Docker Build & Push') {
            steps {
                script {
                    def tag = "${env.BUILD_ID}"
                    docker.build("${REGISTRY}/${IMAGE}:${tag}")
                    docker.withRegistry("https://${REGISTRY}",
                        'registry-credentials') {
                        docker.image("${REGISTRY}/${IMAGE}:${tag}").push()
                        docker.image("${REGISTRY}/${IMAGE}:${tag}")
                              .push('latest')
                    }
                }
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'main'
            }
            steps {
                sh 'kubectl set image deployment/shop-order ' +
                   "shop-order=${REGISTRY}/${IMAGE}:${env.BUILD_ID} " +
                   '-n staging'
            }
        }

        stage('Approve Production') {
            when { branch 'main' }
            steps {
                input message: 'Deploy to production?', ok: 'Deploy'
            }
        }

        stage('Deploy to Production') {
            when { branch 'main' }
            steps {
                sh 'kubectl set image deployment/shop-order ' +
                   "shop-order=${REGISTRY}/${IMAGE}:${env.BUILD_ID} " +
                   '-n production'
            }
        }
    }

    post {
        failure {
            // 发钉钉/企业微信通知
            sh 'curl -X POST https://hooks.dingtalk.com/xxx -d \'{"msg":"Build failed"}\''
        }
    }
}

9.3 切换到 GitLab CI

# .gitlab-ci.yml
stages:
  - build
  - docker
  - deploy

variables:
  REGISTRY: registry.yourdomain.com
  IMAGE: shop/shop-order

build:
  stage: build
  image: maven:3.9-eclipse-temurin-21
  script:
    - mvn clean verify -B
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 hour

docker:
  stage: docker
  image: docker:27
  services:
    - docker:27-dind
  script:
    - docker build -t $REGISTRY/$IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $REGISTRY/$IMAGE:$CI_COMMIT_SHORT_SHA
    - docker tag $REGISTRY/$IMAGE:$CI_COMMIT_SHORT_SHA $REGISTRY/$IMAGE:latest
    - docker push $REGISTRY/$IMAGE:latest

deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/shop-order
      shop-order=$REGISTRY/$IMAGE:$CI_COMMIT_SHORT_SHA -n production
  only:
    - main

9.4 切换到 TeamCity(Java/.NET 团队首选)

TeamCity 是 JetBrains 出品的 CI/CD 服务器,与 IntelliJ IDEA 深度集成,Kotlin DSL 定义流水线,对 Java 生态支持最好。

# Docker 部署 TeamCity Server
docker run -d --name teamcity-server \
  -p 8111:8111 \
  -v teamcity_data:/data/teamcity_server/datadir \
  -v teamcity_logs:/opt/teamcity/logs \
  jetbrains/teamcity-server:2024.07

# 另外需要在构建机器上部署 Build Agent
docker run -d --name teamcity-agent \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e SERVER_URL=http://192.168.1.100:8111 \
  jetbrains/teamcity-agent:2024.07

Kotlin DSL 定义流水线.teamcity/pom.xml 同级目录下):

// .teamcity/settings.kts
package _Self.buildTypes

import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.buildSteps.maven
import jetbrains.buildServer.configs.kotlin.buildSteps.dockerCommand
import jetbrains.buildServer.configs.kotlin.buildSteps.script

object BuildAndDeploy : BuildType({
    name = "Build & Deploy Shop-Order"
    description = "编译、镜像构建、部署到 K8s"

    params {
        param("env.REGISTRY", "registry.yourdomain.com")
        param("env.IMAGE", "shop/shop-order")
    }

    vcs {
        root(AbsoluteId("ShopCloudGit"))  // 在 TeamCity UI 中配置的 VCS Root
    }

    steps {
        // Step 1: Maven 编译 + 测试
        maven {
            name = "Maven Build"
            goals = "clean verify -B"
            runnerArgs = "-Dmaven.test.failure.ignore=true"
        }

        // Step 2: Docker 镜像构建 + 推送
        dockerCommand {
            name = "Docker Build & Push"
            commandType = build {
                source = "Dockerfile"
                namesAndTags = "%env.REGISTRY%/%env.IMAGE%:%build.number%"
            }
        }

        // Step 3: 部署到 K8s Staging
        script {
            name = "Deploy to Staging"
            scriptContent = """
                kubectl set image deployment/shop-order \
                  shop-order=%env.REGISTRY%/%env.IMAGE%:%build.number% \
                  -n staging
            """.trimIndent()
        }

        // Step 4: 部署到 Production(需手动审批)
        script {
            name = "Deploy to Production"
            executionMode = BuildStep.ExecutionMode.RUN_ON_SUCCESS
            scriptContent = """
                kubectl set image deployment/shop-order \
                  shop-order=%env.REGISTRY%/%env.IMAGE%:%build.number% \
                  -n production
            """.trimIndent()
        }
    }

    triggers {
        vcs {
            // Git Push 自动触发
            branchFilter = "+:main"
        }
    }

    failureConditions {
        // 测试失败不阻塞(用 Test Report Tab 查看)
        testFailure = false
    }
})

TeamCity 的独有优势

特性说明
IntelliJ 集成在 IDE 中直接查看构建日志、运行个人构建
构建历史对比两次构建的 diff、测试结果变化一目了然
Kotlin DSL类型安全 + IDE 自动补全,比 Groovy/YAML 更可靠
预测试提交push 前在远端先跑构建,通过后才合入(比 PR 更轻量)
内建代码覆盖率与 IntelliJ 的覆盖率工具共享引擎
构建链多个构建类型串联(Build Chain),上下游自动触发

什么时候选 TeamCity

对比 Jenkins


十一、组合推荐:三套典型方案

方案 A:极致简单(小团队 3-5 人)

注册中心:  Nacos(单体模式)
网关:      Spring Cloud Gateway(Java 代码统一管理)
服务调用:  OpenFeign
熔断限流:  Sentinel
分布式事务: RocketMQ 事务消息(不引入 Seata)
消息队列:  RocketMQ
部署:      Docker Compose(不引入 K8s)
日志:      Loki + Grafana(不引入 ELK)
CI/CD:     GitHub Actions / TeamCity(3 Agent 免费)

方案 B:高性价比(中型团队 10-20 人)

注册中心:  Nacos(集群模式)
网关:      APISIX(插件化,运维配置路由)
服务调用:  OpenFeign + 少量 Dubbo(热点路径用 Dubbo 提性能)
熔断限流:  Sentinel(集群限流)
分布式事务: Seata AT(高价值业务)+ RocketMQ(普通业务)
消息队列:  Kafka(大数据流)+ RocketMQ(业务事务消息)
部署:      K8s(核心服务)+ Docker Compose(内部工具)
日志:      ELK(业务日志)+ Loki(系统日志)
CI/CD:     TeamCity / GitLab CI + ArgoCD

方案 C:成本不敏感(大流量系统)

注册中心:  Nacos(多数据中心)
网关:      Kong(企业版,含 SLA)
服务调用:  gRPC(核心链路)+ Dubbo(一般链路)
熔断限流:  Sentinel(规则全自动压测+调参)
分布式事务: Seata TCC(高价值)+ Saga 编排(长流程)
消息队列:  Kafka(数据管道)+ Pulsar(多租户)
部署:      K8s(全量服务)+ Service Mesh(Istio)
日志:      ELK + 全链路追踪(SkyWalking)
CI/CD:     Tekton + ArgoCD(全 GitOps)

总结

微服务架构中没有银弹。本文梳理了每一层的替代方案,核心思想只有一个:

根据你的团队规模、业务阶段和预算,选择刚好够用、运维成本最低的组合。 不要求全求大,每一层都有"够用就好"的选项。

组件替换的关键原则:

  1. 优先用 Spring Cloud 抽象层(如 CircuitBreakerFactory),换底层不改代码
  2. 新组件先用非关键链路验证,跑稳了再切核心链路
  3. 替换成本 = 代码改动 + 学习时间 + 运维新增成本,三者加起来才是真实成本

写于 2026 年 6 月 24 日。架构就像乐高,重要的不是拥有多少块积木,而是知道每一块该放在哪里。