35岁老码农的Spring Cloud实战手记:从零搭起微服务

递归到天亮
2026-01-12 22:59
阅读 465

凌晨两点,窗外北京五环外的夜色沉得像我的黑眼圈。工位上那杯凉透的速溶咖啡还没收拾,IDEA里刚跑通的注册中心日志终于不再刷“Connection refused”。我揉了揉发酸的颈椎——没错,这就是一个奔四程序员的日常:白天在公司被产品经理追着改需求,晚上回家还得和Eureka死磕。

去年双11前,我们团队接了个新项目:重构老旧的单体电商系统。领导拍板要用微服务架构,理由很充分:“隔壁组用了Spring Cloud,扛住了百万级流量。” 但没人告诉我,隔壁组有三个资深架构师+两个运维专家,而我们只有三个后端(其中俩刚毕业)+一个兼职DevOps。更惨的是,Deadline就在三个月后。

没办法,硬着头皮上吧。这篇不是教科书式的理论堆砌,而是我踩坑、熬夜、查文档、翻GitHub Issues后总结的实战指南。如果你也正被微服务折磨,希望它能帮你少熬几个通宵。

为什么选Spring Cloud?(而不是Python?)

先说清楚立场:我在家折腾过FastAPI+Consul的组合,Python写起来确实爽,异步处理也优雅。但生产环境求稳啊兄弟们!我们系统要对接银行支付、库存同步、物流追踪,任何一个环节崩了都是P0事故。Spring Cloud生态成熟、社区庞大、监控工具链完整,加上团队Java基础扎实,选它是最稳妥的“不性感但可靠”的选择。

面试题挑战
“微服务一定要用Java吗?”
我的回答:技术栈服务于业务。如果你团队90%是Python工程师,且业务对强事务要求不高(比如内容平台),那FastAPI+gRPC完全OK。但金融、电商这类强一致性场景,Java的生态护城河还是难以替代。

第一步:拆!但别乱拆

很多新手一上来就狂拆十几个服务,结果发现服务间调用比数据库JOIN还慢。我们吸取教训,按业务边界+数据一致性来划分:

服务名 职责 是否独立数据库
user-service 用户注册/登录/信息管理
order-service 订单创建/状态流转
inventory-service 库存扣减/回滚
gateway 统一入口、鉴权、限流

关键原则:一个服务只操作自己的数据库。跨服务事务用Saga模式(后面详述),绝不搞分布式事务(除非你想半夜被PagerDuty叫醒)。

搭建注册中心:Eureka vs Nacos?

最初我选了Eureka——毕竟Spring Cloud Netflix全家桶嘛。但配置完发现:服务下线延迟高达90秒!测试时kill掉一个实例,gateway还在往它发请求,直接502。查文档才知道Eureka默认心跳90秒超时。

后来换成Nacos,真香!秒级服务发现,控制台还能看服务拓扑图。配置也简单,application.yml 三行搞定:

spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos地址

血泪教训:别迷信Netflix套件!2018年后很多组件已停止维护。Nacos + Sentinel + Seata 这套国产组合拳,在阿里系验证过,更适合国内网络环境。

服务调用:Feign比RestTemplate香多了

早期用RestTemplate手写URL调用,代码丑得像我的发际线:

// 别这么干!
String url = "http://inventory-service/api/stock?skuId=" + skuId;
ResponseEntity<Stock> response = restTemplate.getForEntity(url, Stock.class);

换成Feign后,接口声明式调用,清爽如德芙:

@FeignClient(name = "inventory-service")
public interface InventoryClient {
    @GetMapping("/api/stock")
    Stock getStock(@RequestParam("skuId") String skuId);
}

// 使用处
@Autowired
private InventoryClient inventoryClient;

public Order createOrder(String skuId) {
    Stock stock = inventoryClient.getStock(skuId); // 像调本地方法一样
    // ...
}

注意:Feign默认没超时控制!线上曾因库存服务卡住,导致订单服务线程池耗尽。务必加配置:

feign:
  client:
    config:
      default:
        connectTimeout: 2000   # 连接超时
        readTimeout: 5000      # 读取超时

配置中心:告别“改配置重启服务”

以前改个数据库密码,得逐个服务重启。现在用Nacos Config,动态刷新:

@RestController
@RefreshScope // 关键注解!配置变更时自动刷新
public class UserController {
    @Value("${user.max-login-attempts:5}")
    private int maxLoginAttempts; // 从Nacos配置中心读取
}

在Nacos控制台修改user-service.properties,服务秒级生效。再也不用求运维大哥半夜上线了!

网关:Zuul太重,Gateway真轻量

我们试过Zuul,但内存占用高得离谱。换成Spring Cloud Gateway后,QPS提升3倍,配置也直观:

spring:
  cloud:
    gateway:
      routes:
        - id: user_route
          uri: lb://user-service # lb表示负载均衡
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1 # 去掉/api前缀

配合Sentinel做限流,防止单个服务拖垮整个系统:

// 限流规则:user-service每秒最多100个请求
GatewayRuleManager.loadRules(Set.of(
    new GatewayFlowRule("user_route")
        .setCount(100)
        .setIntervalSec(1)
));

数据一致性:Saga模式救我狗命

最头疼的是下单扣库存。传统方案用分布式事务(2PC),但性能差到哭。我们用Saga模式

  1. 创建订单(状态=待支付)
  2. 调用库存服务扣减
  3. 若扣减失败,发送“取消订单”消息
  4. 订单服务收到后,把状态改为“已取消”

用RocketMQ保证消息可靠:

// order-service中
@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);
    // 发送“扣库存”消息
    rocketMQTemplate.convertAndSend("inventory-topic", 
        new InventoryDeductCmd(order.getSkuId(), order.getCount()));
}

// 监听库存扣减结果
@RocketMQMessageListener(topic = "inventory-result")
public class InventoryResultConsumer implements RocketMQListener<InventoryResult> {
    public void onMessage(InventoryResult result) {
        if (!result.isSuccess()) {
            // 发送补偿消息:取消订单
            rocketMQTemplate.convertAndSend("order-cancel-topic", result.getOrderId());
        }
    }
}

线上事故复盘
有次MQ集群故障,补偿消息丢失,导致库存超卖。后来加了定时对账任务:每天凌晨扫描“待支付”订单,检查库存是否一致。技术没有银弹,兜底方案必须有!

Python能掺和进来吗?

当然可以!我们有个AI推荐模块用Python写的(TensorFlow模型),通过gRPC暴露服务:

# ai-recommendation/main.py
class RecommendationService(recommend_pb2_grpc.RecommendServiceServicer):
    def GetRecommendations(self, request, context):
        # 调用模型...
        return recommend_pb2.RecommendResponse(items=items)

# 启动gRPC服务器
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
recommend_pb2_grpc.add_RecommendServiceServicer_to_server(RecommendationService(), server)
server.add_insecure_port('[::]:50051')
server.start()

Java服务用grpc-spring-boot-starter调用:

@GrpcClient("ai-recommendation")
private RecommendServiceGrpc.RecommendServiceBlockingStub aiStub;

public List<Item> getRecommendations(String userId) {
    RecommendResponse response = aiStub.getRecommendations(
        RecommendRequest.newBuilder().setUserId(userId).build()
    );
    return response.getItemsList();
}

关键点:gRPC比HTTP快3-5倍,特别适合内部高频调用。但调试麻烦,建议用BloomRPC这类GUI工具。

运维经验:监控不上,迟早火葬场

微服务不监控等于裸奔。我们搭了三件套:

  1. Prometheus + Grafana:监控JVM、HTTP状态码、接口延迟
  2. SkyWalking:分布式链路追踪,定位慢请求
  3. ELK:集中日志,关键词告警(比如"OutOfMemoryError")

Grafana看板截图(文字描述):

[服务健康度]  
user-service: ✅ (99.95% uptime)  
order-service: ⚠️ (5xx errors ↑ 2%)  

[接口延迟 P99]  
/createOrder: 120ms  
/getUser: 45ms  

上周五晚上,order-service延迟突然飙升到2s。通过SkyWalking发现是数据库连接池耗尽,根源竟是新同事写了段没关ResultSet的代码... 当时真的想砸键盘,但深吸一口气,默默加了HikariCP的泄漏检测:

spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000 # 60秒未归还连接就报警

最后几句人话

Spring Cloud不是银弹,微服务也不是。如果业务简单,单体应用+模块化可能更香。但一旦决定拆,记住:

  • 先拆业务,再拆技术
  • 监控和日志比代码更重要
  • 永远给意外留后路(补偿、降级、熔断)

现在我的微服务集群跑在K8s上,每天处理20万订单。虽然头发又少了两撮,但看到系统稳稳扛住流量高峰,凌晨三点的泡面都格外香。

对了,文中的代码我都整理到GitHub了(搜“spring-cloud-practice-old-coder”),欢迎Star——毕竟35岁老码农也要靠Star续命啊!

彩蛋:高频面试题
Q:Eureka和Zookeeper区别?
A:Eureka AP(高可用优先),ZK CP(强一致优先)。微服务要的是可用性,挂一个实例不能让整个注册中心瘫痪。

Q:Feign底层用什么?
A:默认是HttpURLConnection,但可替换为OkHttp或Apache HttpClient(记得加连接池!)

Q:配置中心怎么保证安全?
A:Nacos支持命名空间隔离 + LDAP认证,敏感配置(如DB密码)用Jasypt加密。

写完这篇,天快亮了。关电脑前看了眼招聘APP——嗯,微服务经验果然还是香饽饽。各位,保重身体,代码无Bug!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝