免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

从缓存到微服务:架构演进的两级跳与实战指南

从缓存到微服务:架构演进的两级跳与实战指南 1. 为什么缓存到微服务这条路线几乎是所有系统的必经之路之前有朋友问我说他在一家传统公司做后端开发系统跑了好几年单体架构几百万行代码最近领导让他牵头做架构升级问我是该先上缓存还是直接拆微服务。这个问题其实问得非常好因为很多人一谈架构演进就直奔微服务把缓存当成一个边缘话题但实际上从实际项目的发展轨迹来看缓存往往是架构演进的第一个转折点而微服务则是第二个、也是代价更高的转折点。我习惯把这条路线称作架构演进的两级跳。第一级跳是从单机应用走向单机缓存第二级跳是从膨胀的单体应用走向微服务。每一级跳都有它明确的触发条件、技术选型和成本代价跳早了浪费资源跳晚了系统撑不住。先说一个核心认知架构演进从来不是技术越新越好的问题而是当前业务痛点到底用什么手段解决最划算的问题。缓存解决的是性能痛点微服务解决的是协作和扩展性痛点。这两个痛点通常按时间顺序出现——先有性能问题然后才有规模问题。所以缓存到微服务这条演进路径本质上是业务规模从小到大、团队从少到多的自然映射。这篇文章我会结合自己实际经历过的几个项目把这条演进路线上每一个关键节点的判断依据、踩坑经验和落地细节拆开讲一遍。如果你正在做技术规划或者准备系统架构师考试这条从缓存到微服务的演进主线也是高频考点理清楚逻辑链条比背概念有用得多。2. 先从缓存聊起它是成本最低、见效最快的架构升级手段2.1 什么情况下你才真正需要引入缓存很多人在项目一开始就引入Redis美其名曰架构前瞻性在我看来这是典型的过度设计。判断该不该上缓存先看你的数据访问特征。我判断是否需要缓存通常看三个指标读多写少二八法则依然成立如果一个系统80%以上的请求是读只有不到20%是写那缓存就有发挥空间。数据变更频率低比如商品信息、配置信息、用户资料这类数据几分钟甚至几小时才变一次非常适合缓存。热点集中比如电商的爆款商品、社交平台的头部内容大部分流量都打在少部分数据上。这三个条件满足得越多缓存带来的收益就越大引入缓存的理由就越充分。反过来如果系统写多读少或者数据实时性要求极高比如股票行情、实时库存那缓存不仅帮不上忙还会成为一致性隐患的来源。我在早期一个电商项目中接手过一个案例当时的做法是在数据库上面直接套了一层Redis缓存所有商品详情。但问题是商品价格和库存也一起缓存了导致用户下单时看到的价格和支付时实际扣除的价格不一致引发了大量客诉。后来我们把价格和库存拆出来走实时查询只缓存商品描述、图片信息这类静态内容问题才得到缓解。2.2 缓存选型和基础架构怎么设计确定引入缓存后下一步是选型。当前主流选择基本就是Redis但即便是Redis也分单机、主从、哨兵、Cluster几种部署形态每一步跨越都对应着不同的数据规模和可用性要求。我的建议是分阶段走阶段部署形态适用场景容量规模初期单机Redis数据量小允许短暂不可用10GB以内增长期主从哨兵读多写少需要自动故障转移50GB以内规模化Redis Cluster数据量过百GB需要横向扩展百GB以上单机Redis的性能其实非常惊人10万级的QPS都扛得住所以很多团队在很长一段时间内根本不需要考虑Cluster。我见过不少项目在单机阶段就把缓存用得很好省下了大量运维成本。架构上还需要注意一个问题缓存应该放在业务层还是数据访问层。我自己更推荐把缓存逻辑下沉到数据访问层DAO层或者Repository层理由很简单——上层业务不需要关心数据到底是来自缓存还是数据库这样缓存策略变更时业务代码不用改。等到后期拆微服务时数据访问层的缓存逻辑还可以作为独立的公共组件被多个服务复用。2.3 缓存带来的三个副作用穿透、击穿、雪崩缓存不是上了就完事它会给系统引入三类经典问题这三个问题也是系统架构师考试的高频考点我结合实战来讲。缓存穿透是访问一个一定不存在的数据比如用户查询一个不存在的商品ID缓存里没有数据库里也没有于是每次请求都打到数据库上缓存形同虚设。防御手段有两个一是把空结果也缓存起来设置较短的过期时间比如30秒二是用布隆过滤器在缓存前面拦一道不存在的数据直接返回。我常用的是缓存空值方案因为它实现简单几乎所有缓存框架都有现成支持。缓存击穿是热点Key在过期瞬间被大量请求同时打到数据库。这个问题的核心在于单点过期。解决办法是互斥锁重建缓存或者热点数据不设置过期时间、改为后台异步更新。我在项目中更倾向于后者因为互斥锁引入了线程等待对高延迟敏感的业务不友好。缓存雪崩是大量Key在同一时间集中过期或者Redis实例整体宕机导致流量瞬间压到数据库上。防范手段包括过期时间加随机偏移、多级缓存本地缓存分布式缓存、Redis主从架构做高可用。这三类问题有一个共同的排查思路先看缓存层的命中率命中率健康再看数据库的QPS是否有异常波动。我在线上定位问题时习惯把缓存命中率和数据库慢查询日志放在同一张监控大盘上一旦出现异常两个指标互相印证很容易定位到是穿透、击穿还是雪崩。3. 缓存也顶不住的时候就轮到拆分出场了3.1 单体应用膨胀后的三个典型信号缓存解决了性能问题但业务继续增长你会发现缓存并不是万能的。我在一个交易系统项目里经历过这个过程——缓存命中率明明很高数据库负载也算正常但系统还是出了问题发布越来越难、协作越来越乱、故障影响范围越来越大。这三个信号才是推动架构走向微服务的真正动力。信号一发布困难。代码从几千行涨到几十万行编译一次要几分钟测试回归用例从一百个涨到上千个每次发布都像赌博。这背后的问题根源不在于代码量本身而在于变更影响范围不可控——一个小模块的修改可能导致整个系统都要跟着回归验证。信号二协作混乱。当十几个开发同时在一个代码仓库里提交时冲突变得频繁代码评审流于形式不同模块的开发者互相踩对方的改动。这时候单体应用已经不只是技术问题而是组织沟通成本问题。信号三故障爆炸半径。单体应用意味着任何一个模块的Bug都会拖垮整个系统。比如一个日志模块的内存泄漏会让购物车、订单、支付全部瘫痪。在单体架构下你无法对不同的业务模块提供差异化可用性保障。3.2 拆分的本质从按层切到按业务切很多团队拆微服务时习惯按技术层切——把控制层拆成一个服务、业务层拆成一个服务、数据层拆成一个服务。这是最常见的错误。正确的切法一定是按业务能力切也就是按领域的边界来切。以电商为例应该拆成商品服务、库存服务、订单服务、支付服务、用户服务每个服务包含自己完整的控制层、业务层和数据访问层。这样每个服务才能独立开发、独立部署、独立扩展。我在实际项目中总结过一套服务划分的检查清单分享出来供参考这个服务是否对应一个明确的业务能力如果说不清楚它到底负责什么边界就不清晰。这个服务的数据是否独立如果两个服务共享同一个数据库表说明它们没拆干净。这个服务能否独立发布和回滚只要需要和其他服务一起发布才能上线它就不算真正的服务。3.3 拆分过程中最容易踩的坑数据怎么拆微服务拆分里最难的不是代码拆分而是数据拆分。代码可以很快搬完但数据库里的表是共享的拆不好就会出现跨服务Join、分布式事务爆炸等问题。我倾向于分三步走第一步先做逻辑隔离。共享数据库中的表按业务域打标记明确每张表的归属但物理上还在同一个库。这个阶段服务调用还是可以直接访问表先让业务跑通。第二步做物理隔离。把归属清晰的表迁到独立的数据库实例中改数据访问方式——不再直接连库而是通过归属服务的API获取数据。第三步处理跨域数据需求。这时会出现经典的我需要别人的数据问题。可选方案有三个调用API实时获取、数据冗余一份到自己的库、通过消息订阅方式同步数据。这三个方案的选取我通常遵循这么一条原则对实时性要求高的走API对一致性要求高的走数据冗余配合最终一致对数据量大的走消息同步。没有万能的方案只有当时场景下性价比最高的方案。4. 微服务不只是拆配套基础设施才是大头4.1 服务发现、配置中心和网关拆成微服务后第一个要解决的就是服务间怎么找到彼此。以前单体架构是方法调用现在变成网络调用必须有服务注册与发现机制。主流方案是Nacos或者Consul服务启动时注册自己的IP和端口消费方通过服务名找实例列表再配合负载均衡策略选一个来调用。这里要提醒一个细节服务发现一定要配合健康检查不是注册了就完事。如果服务实例假死——进程还在但已经处理不了请求——注册中心还把它视为健康节点流量打过去就是一堆超时。所以我建议健康检查不要只做TCP端口探测要做一个简单的HTTP健康接口里面顺带检查一下这个服务依赖的关键资源是否可用。配置中心也是拆微服务后必须上的基础设施。我把配置分成两类一类是启动配置比如数据库连接串另一类是运行时可动态调整的开关配置比如某个功能是否放量。这两类配置都应该放到配置中心管理避免每个服务一套本地配置导致修改成本高。网关则是所有外部请求的统一入口。它的职责不只是路由转发还包括鉴权、限流、灰度发布等横切逻辑。这块有个经验要分享网关层的逻辑越薄越好把网关当成门卫而不是中转站具体的业务逻辑一定要下沉到后面的微服务中。否则网关会从一开始的轻量入口慢慢膨胀成一个新的胖单体重蹈之前的覆辙。4.2 分布式事务能不用就不用拆微服务后原本在一个本地事务里的操作被拆到多个服务里分布式事务就成了绕不开的话题。我的个人原则是能不用分布式事务就不用最好从业务设计上避免跨服务事务。怎么避免常见手段包括调整服务边界把强一致的数据归属到同一个服务内。用本地消息表配合消息队列实现最终一致性。业务上允许先干一步、后续补偿比如下单先锁库存、支付成功再减库存。如果真的必须用分布式事务我推荐TCC模式而不是2PC。原因是2PC的协调者单点和资源锁时间太长在高并发下会拖垮整个系统TCC把业务操作拆成Try、Confirm、Cancel三个阶段每个阶段由业务方自行实现虽然写代码量更大但可用性明显更好。4.3 可观测性线上出问题时你要能快速回答三个问题微服务架构下一个请求会穿过多达五六个服务出问题时排查链路比单体时代复杂很多倍。所以可观测性不是加分项而是必备项。我要求团队每个服务必须接入三样东西链路追踪、日志聚合、指标监控。链路追踪用SkyWalking或者Micrometer Tracing解决这个请求经过了哪些服务、每一跳耗时多少的问题。日志聚合用ELK或Loki解决异常栈在哪个服务、哪个节点的问题。指标监控用PrometheusGrafana解决哪个服务QPS飙高、CPU飚满的问题。这套体系搭起来后排查问题的时间能从小时级降到分钟级。具体排查时我的习惯是先看指标大盘找到异常服务再通过链路追踪拉出慢调用链最后定位到具体日志看异常栈。三步走下来绝大多数问题都能快速定位。5. 从缓存到微服务的演进复盘架构师的决策思维5.1 演进路线图与阶段检查点把前面讲的全部串起来一条完整的演进路线是这样的单体阶段应用和数据库一体先解决业务跑通的问题。引入缓存读写压力出现后用缓存扛住读流量。缓存加固处理穿透、击穿、雪崩配合多级缓存提升可用性。应用拆分业务复杂性让单体难以维护时按业务能力拆分为多个微服务。基础设施补齐服务发现、配置中心、网关、事务方案、可观测性逐步到位。数据治理数据库逐步拆库通过API、冗余、消息满足跨域数据需求。每个阶段不是凭空想出来的而是有明确的触发信号。我把它整理成一个自查表阶段触发信号核心动作主要风险单体业务未验证快速迭代技术债积累缓存数据库压力大、读多写少引入Redis一致性问题微服务发布困难、协作混乱按业务拆分分布式复杂性治理链路长、排障难可观测性建设监控盲区做架构决策时我最看重的是当前系统最痛的三个问题是什么。排前三的问题解决了架构演进就是成功的。如果排名前三的问题是代码难读和测试难写那该做的是重构和补测试而不是急着拆微服务。5.2 演进节奏怎么把控不要搞一步到位的推倒重来我见过不少团队在架构升级上犯同一个错误想一步到位推倒重来。结果是旧系统还在跑业务新系统迟迟上不了线两边都要维护团队精力被耗光。推荐的节奏是绞杀者模式——这个名字很形象就是用新的架构逐步绞杀掉旧的架构。具体做法是在单体应用旁边新建一个微服务应用把新需求、新功能直接写在微服务里老功能继续留在单体中。然后按模块逐步把老的业务往微服务迁移每迁移完成一个模块就在网关层把对应的流量切到新服务上。这个模式的好处是每一步都可回退、可验证风险被控制在很小的范围内。我在实践中总结的经验是一次只迁移一个完整业务闭环不要同时拆多个模块。比如先把用户登录完整迁走验证没问题后再迁商品查询切忌同时迁移七八个模块出问题都不知道该回滚谁。另外拆分工作一定要有明确的时间盒。每个模块的迁移时间建议控制在一到两周内超过三周说明这个模块的边界没摸清停下来重新梳理而不是硬着头皮继续。5.3 架构师的自我修养技术判断力比技术本身重要回看整个演进过程缓存和微服务的技术实现其实都不算难难的是在正确的时机做正确的决策。所谓架构师的成长本质上是判断力的提升——不是会使用Redis、会用Spring Cloud就算架构师而是能在引入缓存和优化SQL之间做出正确的选择能在拆分微服务和维持单体之间做出正确的权衡。我把这个判断过程拆成三步每次都问自己当前业务的核心矛盾是什么是性能、是稳定性还是团队协作效率解决方案的代价有多大引入缓存要处理一致性问题拆微服务要面对分布式复杂度每一项都有隐性代价。这个方案能维持多久技术的适用范围到底有多宽会不会过两年又顶不住。这套思考框架比单纯背知识点有用得多。如果你想走架构师这条路我建议你从手头项目里挑一个小模块尝试梳理它的演进过程看看能不能用上面这张表格去套。做多了你对架构演进的理解会从听过很多术语变成真的知道什么时候该用什么。最后分享一个我在实际项目中反复验证过的体会架构没有标准答案每个方案都是权衡的结果。缓存和微服务都只是手段业务的稳定、团队的效率、系统的可维护性才是真正要守护的目标。技术是服务于这些目标的工具而不是反过来让团队为了先进技术而疲于奔命。想清楚这一点很多架构决策做起来就不会再纠结了。
返回列表