4、微服务架构设计:微服务拆分原则、服务边界划分、服务间通信(RPC/消息队列)、API网关设计、服务注册与发现
好,咱们进入第四章。微服务架构设计。
说实话,这一章是CRM系统架构里最考验功力的部分。很多团队不是倒在业务逻辑上,而是倒在服务拆分的「度」上。拆得太细,运维成本爆炸;拆得太粗,又享受不到微服务的红利。
我个人习惯,在动手拆分前,先想清楚三个问题:为什么拆?拆成什么样?拆完后怎么连? 咱们一个一个来聊。
4.1 微服务拆分原则:别为了拆而拆
我见过最典型的反面教材,是把一个用户表拆成「用户基本信息服务」「用户扩展信息服务」「用户权限服务」三个独立部署单元。结果每次查询用户详情,要调三次接口,延迟直接翻倍。
所以,拆分的第一原则是:高内聚、低耦合。说白了,就是把经常一起变化、一起访问的数据,放在同一个服务里。
核心原则清单:
- 业务能力原则:按业务领域拆分。比如CRM里,客户管理、销售管理、订单管理、营销活动,天然就是不同的业务能力。
- 闭包原则:一个服务的变更,尽量不要影响其他服务。我曾经把一个「客户评分计算」逻辑放在订单服务里,结果每次调整评分规则,订单服务都要重新发布,风险极高。
- 数据主权原则:每个服务拥有自己的数据库。别搞共享数据库,那是伪微服务。
- 团队规模原则:一个服务由一个小团队(2-5人)独立维护。如果服务太大,团队沟通成本会急剧上升。
我的经验:刚开始拆分时,宁粗勿细。先拆成5-8个核心服务,运行稳定后再考虑进一步拆分。我见过一个项目,一开始就拆了30多个服务,结果半年后回退成单体——运维搞不定。
4.2 服务边界划分:用DDD来画圈
服务边界怎么划?我强烈推荐用领域驱动设计(DDD)里的「限界上下文」来指导。
你想想看,CRM系统里,「客户」这个概念,在销售眼里是「商机来源」,在客服眼里是「服务对象」,在财务眼里是「账期主体」。它们虽然都叫客户,但关心的属性、行为完全不同。
所以,边界划分的核心是:一个限界上下文,就是一个微服务。
举个例子,CRM里典型的服务边界:
| 服务名称 | 核心职责 | 数据实体 |
|---|---|---|
| 客户服务 | 客户基本信息管理、客户分层、客户标签 | Customer, CustomerTag, CustomerGroup |
| 销售服务 | 商机管理、销售漏斗、报价单 | Opportunity, Lead, Quote |
| 订单服务 | 订单创建、订单履约、订单状态机 | Order, OrderItem, OrderStatus |
| 营销服务 | 活动配置、优惠券发放、触达记录 | Campaign, Coupon, TouchRecord |
| 用户服务 | 员工账号、角色权限、组织架构 | User, Role, Department |
注意:我曾经犯过一个错误,把「客户服务」和「销售服务」放在同一个数据库里,因为觉得「客户和商机关系太紧密了」。结果后来销售团队要频繁改商机字段,每次都要和客户团队协调数据库变更,痛苦不堪。记住:服务边界 = 数据库边界。
4.3 服务间通信:RPC vs 消息队列
服务拆开了,怎么通信?这是个大问题。我一般分两种情况处理:
4.3.1 同步通信:用RPC
适合需要实时返回结果的场景。比如「查询客户详情时,同时展示该客户的最新订单」。这种场景,用RPC最直接。
我个人习惯用gRPC,原因很简单:
- 基于HTTP/2,性能比REST高一个量级
- 强类型接口定义,生成客户端代码,减少沟通成本
- 支持流式传输,适合大数据量场景
// 示例:客户服务调用订单服务的gRPC接口
service OrderService {
rpc GetLatestOrder (GetLatestOrderRequest) returns (OrderResponse);
}
message GetLatestOrderRequest {
string customer_id = 1;
}
message OrderResponse {
string order_id = 1;
double total_amount = 2;
string status = 3;
}
避坑指南:我曾经在RPC调用链上超过3层时,遇到过一次雪崩。A调B,B调C,C挂了,B的线程池被打满,A也跟着挂了。所以,同步调用链不要超过2层。超过2层,考虑用异步。
4.3.2 异步通信:用消息队列
适合「最终一致性」的场景。比如「订单创建后,需要通知营销服务发放积分、通知仓储服务准备发货」。这些操作不需要立即完成,但必须保证最终都执行。
我常用的消息队列是RabbitMQ和Kafka:
- RabbitMQ:适合业务消息,路由灵活,支持死信队列。我一般用它处理「订单状态变更通知」这类业务事件。
- Kafka:适合高吞吐、日志类数据。比如「用户行为埋点」「操作审计日志」,一天几千万条,Kafka扛得住。
// 示例:订单服务发送消息到RabbitMQ
// 订单创建后,发布事件
OrderCreatedEvent event = new OrderCreatedEvent();
event.setOrderId("ORD20241001");
event.setCustomerId("CUST001");
event.setTotalAmount(2999.00);
rabbitTemplate.convertAndSend("order.exchange", "order.created", event);
// 营销服务和仓储服务各自监听这个队列,做自己的事情
我的选择标准:
- 需要实时响应 → RPC
- 不需要实时响应,但必须可靠 → 消息队列
- 需要广播给多个服务 → 消息队列
- 需要削峰填谷 → 消息队列
4.4 API网关设计:统一入口,隔离复杂性
服务拆开后,客户端(比如前端、移动端)不可能直接调用几十个微服务。这时候就需要API网关。
API网关的核心职责,我总结为四个字:入口、隔离。
- 统一入口:所有外部请求都走网关,客户端只需要知道网关地址。
- 路由转发:根据请求路径,转发到对应的微服务。比如
/api/customer/**转到客户服务。 - 认证鉴权:在网关层统一做Token校验,避免每个服务都写一遍认证逻辑。
- 限流熔断:防止某个服务被突发流量打垮。我习惯用Sentinel做限流,配置简单,效果明显。
我常用的网关是Spring Cloud Gateway。它基于WebFlux,性能比Zuul好很多。
// 示例:Spring Cloud Gateway 路由配置
spring:
cloud:
gateway:
routes:
- id: customer-service
uri: lb://customer-service
predicates:
- Path=/api/customer/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
注意:不要把业务逻辑写在网关里。我见过有人把「客户积分计算」写在网关的Filter里,理由是「这样响应快」。结果每次改积分规则,网关都要重启,影响所有服务的访问。网关只做通用能力,业务逻辑下沉到微服务里。
4.5 服务注册与发现:动态感知,弹性伸缩
微服务实例是动态的。今天部署了3个客户服务实例,明天可能扩到5个,后天可能某个实例挂了。服务之间怎么知道彼此在哪里?
这就需要一个「通讯录」——服务注册中心。
我常用的注册中心是Nacos。原因很简单:
- 支持CP和AP模式切换,灵活
- 自带配置中心,省得再搭一套
- 控制台界面清晰,运维友好
工作流程是这样的:
- 服务启动时,向Nacos注册自己的IP和端口
- 服务每隔一段时间(比如30秒)发送心跳,告诉Nacos「我还活着」
- 服务调用方(比如API网关)从Nacos拉取服务列表,缓存到本地
- 如果某个实例挂了,Nacos会把它从列表里剔除,调用方下次拉取时就不会再调它
// 示例:Spring Boot 集成 Nacos 注册
spring:
application:
name: customer-service
cloud:
nacos:
discovery:
server-addr: 192.168.1.100:8848
namespace: crm-prod
group: CRM_GROUP
我的经验:注册中心一定要做高可用部署。我见过一个事故,Nacos单节点挂了,所有服务都找不到彼此,整个CRM系统瘫痪了半小时。后来我们部署了3节点Nacos集群,配合VIP,再没出过类似问题。
嗯,到这里,微服务架构设计的核心内容就讲完了。总结一下:
- 拆分原则:高内聚低耦合,按业务能力拆
- 边界划分:用DDD限界上下文画圈
- 通信方式:实时用RPC,异步用消息队列
- API网关:统一入口,做认证、限流、路由
- 注册发现:用Nacos做动态通讯录
下一章,咱们聊聊数据架构设计。CRM系统里数据量最大的就是客户行为数据和订单数据,怎么存、怎么查、怎么保证一致性,都是硬骨头。