大家想象这样一个场景:
“公司决定,下个项目全面拥抱微服务!”你们老板在会上宣布,眼神里透着那种刚听完某云厂商销售忽悠的光芒。
你身边的同事老王默默打开手机,开始刷招聘网站。你则陷入沉思:上个月刚把单体应用跑顺,这个月就要拆成几百个服务?这跟你刚收拾整齐的出租屋,突然被砸成毛坯房有什么区别?
当晚路过小区门口的夜市,看着一排排卖烤串、卖奶茶、卖臭豆腐的摊位,你忽然灵光一闪——这不就是微服务吗?每个摊位各卖各的,互不干扰,组合起来就是一条完整的美食街。顾客想吃啥去对应的摊,摊主想涨价就涨,想休息就歇,只要别影响整条街的生意就行。
第二天你信心满满地跟老板说:“微服务没问题,咱们就像开美食街一样搞!”老板一脸懵:“美食街?”对,就是让每个服务像摊位一样独立经营,但又要统一管理,别让烤串的臭豆腐味飘到隔壁奶茶摊。
今天咱们就用“美食街”的视角,把微服务设计里那些坑一个个填平。保证你看完后,不仅会搭微服务,还能去夜市摆个摊。
一、摊位划分:别让烤鱿鱼的跑去卖奶茶
微服务拆分的核心问题:一个系统该切成几块?切大了像单体,切小了像碎片。
我见过一个狠人,把用户管理拆成“注册服务”、“登录服务”、“修改密码服务”、“找回密码服务”,结果用户注册一次要调4个接口,响应时间从 200ms 飙到 2 秒。老板问为什么这么慢,他说:“因为要开四个会啊!”
这就好比开美食街,本来一个烤鱿鱼摊就能搞定鱿鱼的所有做法,你非要拆成“鱿鱼宰杀服务”、“鱿鱼穿串服务”、“鱿鱼烤制服务”、“酱料涂抹服务”。顾客点一份鱿鱼,得排四次队,拿四个号,最后鱿鱼凉了还没吃上。这美食街不倒闭才怪。
正确的拆分姿势是什么?按“品类”分,别按“工序”分。
什么叫“品类”?就是顾客眼里的一个完整商品。在美食街,烤鱿鱼是一个品类,奶茶是一个品类,臭豆腐是另一个品类。每个品类从原料到成品都由一个摊位负责。对应到系统里,就是按业务域划分:订单是一个业务域,商品是一个业务域,支付是一个业务域。订单服务负责订单的创建、修改、查询、取消,别把订单里的商品信息扔给商品服务,也别把支付状态甩给支付服务。
还有个铁律:如果两个功能总是一起变,千万别拆。比如商品的价格和库存,每次搞活动都得一起调。你非要拆成两个服务,那改价格时还得同步发版库存,万一哪个步骤慢了,就会出现“显示有货,下单后却说没货”的惨案。顾客骂街,你背锅。
拆完的标准是:一个摊位着火,不影响其他摊位营业。比如支付服务挂了,顾客还能浏览商品、加购物车,只是暂时不能付款,等支付恢复再付。这才叫拆出了“容灾能力”。
二、摊位沟通:别让顾客当传话筒
摊位分好了,问题来了:顾客点一份套餐,需要烤鱿鱼摊和奶茶摊配合,这两个摊怎么沟通?总不能顾客拿着鱿鱼去奶茶摊说“帮我带句话”吧?
微服务间的通信,核心就两件事:怎么找到对方摊位,怎么说清楚要什么。
先说“怎么找到对方”。美食街刚开张时,摊位位置可能经常变(今天烤鱿鱼在A区,明天移到B区)。如果顾客死记地址,肯定找不到。所以美食街门口要有块“摊位指引牌”,实时更新每个摊位的当前位置。想找烤鱿鱼,看一眼指引牌就知道去哪。
微服务里这个指引牌叫“服务注册中心”,比如 Nacos、Eureka。每个服务启动时,跑去注册中心喊一声:“我是订单服务,IP 192.168.1.100,端口8080!”注册中心记下来。当订单服务要调支付服务时,就去问注册中心:“支付服务在哪?”注册中心返回一堆 IP 列表,订单服务挑一个调用。这样支付服务换机器、扩缩容,订单服务完全不用改代码。
再说“怎么说清楚事儿”,也就是用什么方式和数据格式沟通。
(1)如果只是偶尔点个单,用 HTTP+JSON 就行。HTTP 像喊话,谁都能懂;JSON 像写纸条,内容一目了然。比如订单服务发个 POST 请求给支付服务,body 里是 {"orderId":123, "amount":99.9} ,支付服务返回 {"code":200, "msg":"success"} 。简单粗暴,出问题也好查。
(2)如果两个摊位高频配合,比如烤鱿鱼摊和酱料摊每秒要传递几十次信息,那就得用 RPC,比如 Dubbo 或 gRPC。RPC 像心有灵犀,效率高,但需要提前约定好接口和数据结构,就像两个摊主提前商量好暗号。
还有个坑:一定要有“菜单”。比如订单服务调支付服务,得约定好传什么字段、返回什么字段。最好写成文档或用 Swagger 自动生成。别搞成“你比划我猜”——订单服务传个 orderId=123 ,支付服务回“你给的是啥?我要的是支付单号!”然后两边扯皮半小时,发现是字段名没对齐。这种坑,早定规矩早省心。
三、数据一致:别让“鱿鱼”和“奶茶”对不上
微服务里最刺激的,就是数据一致性。顾客下单买一份套餐:订单服务要记下订单,库存服务要扣掉鱿鱼和奶茶的库存。要是订单记下了,库存没扣成,就会超卖;要是库存扣了,订单没记成,就会少卖。这两种情况,老板都得找你算账。
很多人一听“分布式系统”里的事务,就头大,什么 TCC、SAGA、本地消息表,像听天书。其实核心逻辑很简单:能不用就不用,非用不可选最简单的。
先说“能不用”的情况。怎么做到?把“两个摊位的操作”变成“一个摊位的操作”。比如下单流程改一下:订单服务先创建“待支付”状态的订单,然后调库存服务扣库存,库存扣成功后再把订单状态改成“已锁定”。
如果库存扣失败,订单就改成“创建失败”。这样每个服务只改自己的数据库,就算中间出问题(比如扣完库存后订单没更新),也能用定时任务扫出来:哪些订单“待支付”超过30分钟没锁定,自动取消,同时把库存加回去。
这种方式叫“最终一致性”,虽然不是实时同步,但90%的业务场景都能用。就像在夜市点餐,服务员说“稍等,鱿鱼正在烤”,你等几分钟才拿到——这就是最终一致性。用户能接受,系统也简单。
那什么时候必须用强一致的事务?比如转账:A 账户扣钱,B 账户加钱,必须同时成功或失败。这时候可以试试“本地消息表”这种简单方案。逻辑是:A 服务扣钱时,同时往自己数据库写一条“转账消息”(状态“待发送”);然后用定时任务把消息发给 B 服务;B 服务收到后加钱,返回成功;A 服务把消息状态改成“已完成”。如果 B 服务没收到,A 服务会反复重试,直到成功或人工介入。虽然有点绕,但比 TCC 简单多了。
记住:数据一致性不是追求“绝对完美”,而是在可靠性和复杂度之间找平衡。别为了那1%的极端情况,把系统搞得像“满汉全席”——自己累死,别人也看不懂。
四、容错限流:别让一个摊子挤爆整条街
微服务最大的风险是“雪崩”。支付服务挂了,订单服务一直调它超时,线程被占满,最后订单服务也挂了;然后商品服务因为调订单服务超时,也挂了——就像美食街一个摊子着火,大家不救火反而围观,结果火烧连营,整条街都烧了。
所以微服务必须要有“安全绳”:容错和限流。核心就一句话:别让一个摊子累死,也别让一条街挤爆。
先说容错,也就是“某个摊子暂时关门,怎么让其他摊子正常营业”。三个简单技巧:
(1)超时重试:调别人家的服务一定要设超时时间,别死等。比如订单服务调支付服务,设3秒超时——3秒没响应就认为失败,赶紧释放线程。同时可以加重试,但只重试“幂等”的操作(重复调用结果一样)。比如“查支付状态”可以重试,“扣库存”绝对不能重试,否则扣两次。
(2)熔断:就像美食街的保安,看到某个摊子排队太长、老出问题,就暂时拦着顾客不让去。如果调用某个服务失败率超过阈值(比如50%),就暂时“断开”调用,直接返回失败,让那个摊子自己缓一缓。过一会儿,再试探放几个顾客进去,没问题了再恢复。Resilience4j、Sentinel 都能轻松实现。
(3)降级:实在不行就放弃一些不重要的功能,保住核心。比如美食街大促销,支付服务压力大,可以把“查看历史订单”这个功能降级——用户暂时看不到历史订单,但能正常下单支付。降级的关键是提前分清“核心”和“非核心”,别到时候手忙脚乱。
Java 代码示例(用 Resilience4j 实现熔断):
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class PaymentService {
private final RestTemplate restTemplate;
public PaymentService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
public String callPayment() {
return restTemplate.getForObject("http://payment-service/pay", String.class);
}
public String fallback(Throwable t) {
return "支付服务暂时不可用,请稍后再试";
}
}
配置好熔断阈值(比如失败率50%,5秒后尝试恢复),就搞定了。
再看限流,也就是“控制进街的人数,别让整条街被挤爆”。比如秒杀活动,系统只能扛1万次/秒,结果来了10万次,不限流直接崩。限流就像美食街门口放个闸机,每秒只放1000人进去,多了就排队或提示“人太多,请稍后再来”。实现可用令牌桶、漏桶算法,Guava 的 RateLimiter 或 Sentinel 都有现成工具。
五、监控排查:别当“睁眼瞎”
服务多了,问题也多了:用户说下单后没收到餐,你根本不知道是订单服务漏了,还是支付服务卡了,还是网络抽了。这时候要是没监控,就像在黑夜里找摊子——摊子在哪不知道,自己还被绊倒。
微服务必须要有“监控”和“追踪”。至少要盯住三样:
(1)摊位状态:每个服务活着吗?CPU、内存、磁盘用多少?用 Prometheus 收集数据,Grafana 画图,一眼就能看到“支付服务 CPU 快满了”、“订单服务内存泄漏了”。
(2)摊位生意:每个接口的调用量、成功率、响应时间。用 SkyWalking 或 Pinpoint,能看到“订单创建接口今天调了10万次,成功率99.8%,平均响应 200ms”。如果成功率突然掉到90%,响应时间涨到1秒,赶紧查。
(3)美食街业绩:核心业务指标正常吗?比如“今天下单量1万,支付转化率80%”,如果突然降到100单,或者转化率跌到10%,肯定出事了。
排查问题靠“分布式链路追踪”。比如一个用户下单请求,会经过“网关→商品→订单→支付”,链路追踪能把整个路径记录下来,包括每个服务的调用时间和结果。用户说超时了,你通过请求 ID 一查,发现“支付服务调银行接口卡了5秒”,问题瞬间定位。现在 Spring Cloud Sleuth + Zipkin 就能搞定,配置一下就行。
示例:用 Spring Cloud Sleuth 在日志里加入 Trace ID,然后在 Zipkin 里查看链路。
spring:
zipkin:
base-url: http://zipkin-server:9411
sleuth:
sampler:
probability: 1.0 # 100%采样
然后在代码里正常调用,Zipkin 就会自动收集链路信息。排查问题时,搜一下 Trace ID,所有调用一目了然。
六、总结性的聊两句
聊了这么多,你发现没?微服务其实没那么玄乎,核心逻辑就那么几条:
- 拆分按品类,别按工序;
- 通信找指引牌,再定规矩;
- 数据一致,能最终就别强求实时;
- 容错限流,提前系好安全绳;
- 监控追踪,别当睁眼瞎。
微服务就像开美食街:每个摊位独立经营,但要有统一管理、统一招牌、统一应急预案。你不用一开始就开成米其林三星,也不用追求摊位数最多,能满足食客需求,好维护、好扩展,就是好美食街。
最后给个小建议:如果你的项目还小,团队就三五人,别急着上微服务,先搞个“大排档”(单体应用)试试水。等生意火爆了,摊位多了,再慢慢改造成美食街。技术是为业务服务的,别为了“赶时髦”硬上——不然最后累的是自己,坑的是项目。
下次老板再拍桌子说“上微服务”,你可以淡定地回一句:“行啊,咱先聊聊怎么规划美食街,别搞成菜市场。”然后把这篇文章甩给他,保证他看完后,再也不敢随便拆家了。
如果你想更系统地研习架构设计与分布式技术,不妨多和圈子里的同行聊聊,看看一线项目是怎么在可靠性和复杂度之间取舍的。毕竟,光有美食街的理想还不够,还得懂每条街该用什么厨具、怎么走线、怎么安排消防——这些,就是后端的硬功夫了。