第三章:微服务架构基础

聊微服务之前,我先说说自己的经历。几年前我接手过一个单体应用,代码量大概五十万行,每次发布都像在拆炸弹。改一行代码,编译十分钟,测试两小时,上线还得挑凌晨。那时候我就想,这玩意儿必须拆。拆完之后,世界清净了。

微服务不是什么银弹,但它确实解决了很多实际问题。这一章,我把核心概念、拆分原则和通信方式掰开揉碎了讲。你想想看,这些东西搞懂了,后面做架构设计心里就有底了。

3.1 微服务的核心概念

微服务说白了,就是把一个大应用拆成多个小服务。每个服务独立部署、独立运行、独立演进。我习惯用一句话概括:一个服务只做一件事,并且把它做好。

微服务的三个核心特征:

  • 单一职责:每个服务只负责一个业务领域
  • 自治性:服务可以独立开发、测试、部署、扩容
  • 去中心化:每个服务可以选择自己的技术栈和数据存储

嗯,这里要注意。微服务不是越细越好。我在项目中遇到过有人把用户登录和用户注册拆成两个服务,这就有点过了。粒度太细,通信成本会暴涨,运维复杂度也跟着上去了。

3.2 服务拆分原则

拆分是门手艺活。我总结了几个原则,都是踩坑踩出来的。

3.2.1 按业务领域拆分

这是最常用的方式。比如电商系统,可以拆成用户服务、订单服务、商品服务、支付服务。每个服务对应一个独立的业务域。我建议你先画业务流程图,找到那些耦合度低的模块,从那里下手。

3.2.2 按变化频率拆分

有些功能经常改,有些一年都不动一次。把它们拆开,高频变更的服务可以独立发布,不影响稳定的部分。我曾经把一个报表服务拆出来,因为业务方每周都要改报表逻辑,而核心交易逻辑几乎不变。拆完之后,两边都舒服了。

3.2.3 按资源需求拆分

CPU密集型的服务和IO密集型的服务,对资源的需求完全不同。混在一起,要么浪费资源,要么性能瓶颈。比如图片处理服务需要大量CPU,而消息推送服务主要耗内存,拆开之后可以分别做弹性伸缩。

避坑指南:我曾经把一个服务拆得太碎,导致一次简单的用户查询需要调用七八个服务。响应时间从50ms飙到了500ms。后来我做了服务合并和缓存优化才救回来。记住:拆分不是目的,可维护性和性能才是。

3.3 微服务通信方式

服务拆开了,怎么通信就成了关键问题。我常用的有三种方式:REST、gRPC和消息队列。每种都有适用场景,没有的好坏。

3.3.1 REST(HTTP/JSON)

REST是最常见的通信方式。简单、直观、调试方便。我习惯用它来做对外接口,或者服务间调用频率不高的场景。

// 一个典型的REST接口示例
GET /api/v1/users/12345
Host: user-service.example.com
Accept: application/json

// 响应
{
  "userId": "12345",
  "name": "张三",
  "email": "zhangsan@example.com"
}

REST的优点是人人都懂,缺点也很明显:JSON序列化慢,协议开销大。高并发场景下,性能是个问题。

3.3.2 gRPC(HTTP/2 + Protobuf)

gRPC是我在高性能场景下的首选。它用Protobuf做序列化,数据体积小,解析速度快。而且基于HTTP/2,支持双向流和头部压缩。

// Protobuf定义
service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}

message GetUserRequest {
  string user_id = 1;
}

message User {
  string user_id = 1;
  string name = 2;
  string email = 3;
}

我在一个实时推荐系统中用过gRPC,延迟比REST降低了40%左右。但要注意,gRPC的调试不如REST方便,需要额外的工具支持。

我的建议:内部服务间调用,优先考虑gRPC。对外接口,用REST更友好。如果你需要流式传输或者实时推送,gRPC是更好的选择。

3.3.3 消息队列(异步通信)

消息队列用于解耦和削峰。服务A发消息到队列,服务B异步消费。这样即使B挂了,A也能正常工作。

// 生产者发送消息
// 使用Kafka示例
producer.send(new ProducerRecord<>("order_created", orderId, orderJson));

// 消费者处理消息
consumer.subscribe(Arrays.asList("order_created"));
while (true) {
  ConsumerRecords<String, String> records = consumer.poll(100);
  for (ConsumerRecord<String, String> record : records) {
    // 处理订单创建后的后续逻辑
    processOrder(record.value());
  }
}

消息队列的典型场景:订单创建后发短信通知、用户注册后发送欢迎邮件、日志收集等。我常用的消息队列有Kafka、RabbitMQ和RocketMQ。选型时考虑吞吐量、延迟和可靠性需求。

3.4 三种通信方式的对比

特性 REST gRPC 消息队列
通信模式 同步 同步/异步 异步
序列化 JSON/XML Protobuf 自定义
性能 中等 高 高(批量处理)
调试难度 低 中 高
适用场景 对外接口、低频调用 内部高性能调用 解耦、削峰、异步处理

3.5 微服务架构的核心逻辑图

下面这张图展示了微服务架构的核心逻辑。你可以看到服务如何拆分、如何通信、以及数据如何流转。

微服务架构核心逻辑图 API 网关 用户服务 订单服务 商品服务 支付服务 REST/gRPC REST/gRPC REST/gRPC REST/gRPC 消息队列(Kafka/RabbitMQ) 用户数据库 订单数据库 商品数据库 支付数据库 API网关 微服务 消息队列 数据库 同步通信

从图上你可以看到,所有外部请求先经过API网关,再由网关分发到各个微服务。服务之间通过REST或gRPC做同步通信,通过消息队列做异步通信。每个服务拥有独立的数据库,实现了数据层面的解耦。

我的经验:刚开始做微服务的时候,别追求一步到位。先把最核心的业务拆出来,跑通了再慢慢拆其他的。我见过太多团队一上来就拆了二十多个服务,结果运维搞不定,又合并回去了。记住:渐进式拆分,比一次性到位靠谱得多。

好了,微服务的基础就聊到这里。通信方式的选择没有标准答案,关键看你的业务场景。我个人习惯:对外用REST,对内用gRPC,异步用消息队列。你可以在实际项目中慢慢体会。

可可精品资料库