服务设计原则:服务抽象、服务自治、服务无状态、服务可重用
好,咱们今天聊点实在的。服务设计原则这东西,说白了就是一套「规矩」。没有规矩,不成方圆。我在SOA项目里摸爬滚打这些年,见过太多因为设计原则没守住,最后整个系统变成一锅粥的案例。
今天重点讲四个核心原则:服务抽象、服务自治、服务无状态、服务可重用。这四个原则,你吃透了,服务设计就成功了一半。
一、服务抽象:把复杂性藏起来
什么叫服务抽象?简单说,就是「我只告诉你我能干什么,不告诉你我怎么干的」。你调用一个订单服务,传个订单ID进去,它返回订单状态。至于它背后是查了MySQL还是Redis,还是调了三个外部接口,你不需要知道。
核心要点:服务对外暴露的,仅仅是它的契约(接口),而不是实现细节。
我个人习惯,在设计服务接口时,会先问自己一个问题:「调用方需要知道这个吗?」如果答案是否定的,那就把它藏起来。
举个例子,我之前做一个支付服务。一开始,接口里把「是否走银联通道」、「是否走微信支付」都暴露出来了。结果呢?每次渠道切换,调用方都得跟着改代码。后来我重构了接口,只暴露一个 pay(amount, orderId) 方法。内部怎么路由,调用方一概不管。清爽多了。
避坑指南:我曾经犯过一个错误,把服务的数据库表结构直接暴露给了调用方。结果DBA一改表名,所有调用方全挂了。记住:接口是你的脸面,实现是你的底裤。脸面要干净,底裤别露出来。
二、服务自治:自己管好自己
服务自治,意思是每个服务应该能独立开发、独立部署、独立运维。它不依赖其他服务的运行时状态。
你想想看,如果一个服务启动前,必须先启动另外三个服务,那还叫什么「微服务」?那叫「微依赖」。
我见过一个项目,订单服务启动时要去注册中心拉配置,注册中心挂了,订单服务也起不来。这就是典型的「自治性差」。后来我们给每个服务配了本地缓存配置,注册中心挂了也不影响启动。
自治性的三个检查点:
- 数据自治:每个服务有自己的数据库,不直接访问别人的库
- 部署自治:可以独立打包、独立发布,不依赖其他服务的部署窗口
- 故障自治:下游服务挂了,上游服务要有降级方案,不能跟着一起挂
嗯,这里要注意。自治不等于「老死不相往来」。服务之间当然要通信,但通信应该是松耦合的。我个人建议,优先用异步消息,少用同步RPC。异步消息天然解耦,一个服务挂了,消息队列还能扛住。
三、服务无状态:别把数据揣在兜里
无状态,是SOA里最容易理解、也最容易犯错的原则。
什么叫有状态?就是服务实例A处理了用户的第一个请求,把用户信息存在了内存里。第二个请求来了,必须还得路由到服务实例A,因为只有A记得这个用户是谁。
这叫「粘性会话」,是分布式系统的大敌。为什么?因为一旦A挂了,用户的所有状态都丢了。而且你没法做水平扩展——新加的实例B没有用户状态,接不了请求。
重要提醒:无状态服务,不是说服务里不能有数据。而是说业务数据不能保存在服务实例的内存里。数据应该放在外部存储里,比如Redis、数据库。
我记得有一次线上事故,就是因为一个服务把用户登录态存在了本地内存里。那天我们做了个灰度发布,新版本上线后,所有登录用户都被踢下线了。为什么?因为新实例的内存里没有用户的登录态。从那以后,我们所有服务的会话数据都统一放到了Redis里。
无状态的好处很明显:
- 弹性伸缩:想加实例就加,想减就减,不用考虑状态迁移
- 故障恢复:实例挂了,新实例直接顶上,用户无感知
- 负载均衡:请求可以随机分发到任意实例,不用做一致性哈希
四、服务可重用:别重复造轮子
可重用性,是SOA的初心之一。我们搞服务化,就是为了把通用的业务能力沉淀下来,让多个系统共享。
但这里有个坑:过度追求可重用,反而会让服务变得臃肿。我见过一个「用户服务」,里面包含了用户注册、登录、权限管理、消息推送、积分查询……什么都有。结果呢?每个调用方只用了其中一两个接口,但整个服务却要维护几十个接口。
我的经验:可重用性不是「大而全」,而是「小而精」。一个服务只做一件事,但把这件事做到极致。
判断一个服务是否「可重用」,我一般看三个维度:
| 维度 | 说明 | 例子 |
|---|---|---|
| 业务通用性 | 这个功能是否多个业务场景都需要? | 短信发送、文件存储、用户认证 |
| 接口通用性 | 接口设计是否足够抽象,不绑定特定业务? | 不要叫「sendOrderSMS」,叫「sendSMS」 |
| 数据独立性 | 服务的数据模型是否独立于其他服务? | 用户服务有自己的用户表,不依赖订单表 |
举个例子。我们公司原来每个业务线都有自己的「文件上传」功能。后来我牵头做了一个统一的文件服务,只提供三个接口:upload、download、delete。所有业务线都接入这个服务。结果呢?存储成本降低了30%,运维工作量也大幅减少。
避坑指南:我曾经为了追求可重用,把一个服务的接口设计得「万能」——参数里塞了一个JSON,让调用方自己传什么字段都行。结果接口是「重用」了,但调用方传的数据格式五花八门,服务端根本没法做校验。最后不得不废弃这个接口,重新设计。记住:可重用不等于「无约束」。接口要有明确的契约,该校验的校验,该限定的限定。
总结一下
这四个原则,其实是一体的:
- 服务抽象让你藏好实现细节
- 服务自治让你独立运行
- 服务无状态让你能弹性伸缩
- 服务可重用让你避免重复劳动
你想想看,如果一个服务做到了抽象、自治、无状态、可重用,那它基本上就是一个「好服务」了。剩下的,就是具体的接口规范、协议选择、数据格式这些细节问题。那些咱们后面章节再聊。
嗯,今天就到这儿。下次咱们讲「服务接口的版本管理」,那也是个容易踩坑的地方。