ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

单体、分布式、微服务到底啥关系?一文讲清区别与关键实践

单体、分布式、微服务到底啥关系?一文讲清区别与关键实践 聊到分布式、单体、微服务这三个词我估计每个做后端的人都被问过无数次。面试要问系统设计要聊网上技术文章天天讲但说真的我见过不少工作了三五年的开发聊起这三个词依然会串台有人觉得微服务就是分布式有人觉得分布式就是多部署几台机器还有人觉得单体项目就是“落后”的代名词。这种概念的混淆比不会写代码更可怕——因为它会直接影响你做技术选型甚至决定一个项目从起步到崩盘的全过程。这篇文章我想用最直白的方式把这三个词拆开揉碎讲清楚。我会从它们各自的本质定义聊起讲清楚它们之间到底是包含关系还是并列关系然后落到实际工作上什么时候该用单体什么时候该上微服务真上了分布式之后会遇到哪些绕不开的坑比如分布式锁、分布式事务、分布式缓存这些天天被挂在嘴边的名词到底在解决什么问题。不绕弯子尽量让刚入门的人能看懂也让有经验的同行能从中摸到一些实操判断。1. 单体、分布式、微服务到底指什么1.1 单体项目一个包打天下的时代先说单体。单体架构Monolithic Architecture指的是整个应用打包成一个进程所有功能模块写在同一个工程里共享同一个数据库部署的时候一打包、一启动整个系统就都跑起来了。打个比方它就像一家小餐馆老板、厨师、收银、洗碗全是一个人干菜谱、采购、后厨、前厅都在同一个小门面里生意再大也就是这个门面转。这种架构在项目早期阶段是非常合理的选择因为它足够简单。一个订单系统包含用户模块、商品模块、订单模块、支付模块全部放在一个 Spring Boot 工程里一个 Tomcat 启动连数据库也共享一张 schema。代码写起来不用考虑网络通信模块之间的调用就是普通的 Java 方法调用出了问题断点一打从头跟到尾排查思路极其顺畅。单体架构的优势很明显开发简单、部署简单、测试简单、运维简单。但它的问题会随着业务膨胀逐渐暴露出来。当代码量到几十万行的时候几个矛盾会同时激化一是编译打包时间越来越长改一行代码可能要等好几分钟才能看到效果二是团队协作开始互相踩脚两个人同时改同一个模块合并冲突能把人逼疯三是任何一个模块的内存泄漏、死循环、OOM都可能把整个进程拖垮导致所有业务全部不可用四是没法针对某个模块单独扩容——比如双十一订单量暴涨你只想给订单模块多开几台机器但单体架构下只能把整个应用一起复制其他模块的资源也跟着白白浪费。1.2 分布式多台机器一起干一件事分布式系统Distributed System的定义其实比大多数人想的更宽泛多个独立的计算节点通过网络相互协作共同完成一个整体任务对外表现成一个统一的系统。它并不要求每个节点跑的是不同代码也不要求节点的负载完全均衡只要是多台机器通过网络通信协作完成业务就是分布式系统。这里有个常见误区很多人觉得“微服务才是分布式”其实不对。一个单体应用部署了十个实例前面挂一个负载均衡器请求被分发到不同的机器上这已经是分布式系统了。比如一个单体商城三台服务器同时跑同一个 war 包用 Nginx 做负载均衡从运行形态上看它就是由多个节点协作构成的分布式系统。只不过它在“代码组织方式”上依然是单体。分布式系统解决的核心问题就是单个节点无法承受的压力高并发、大数据量、高可用。世界上没有一台超级计算机能无限垂直扩展但你可以在前面堆一百台普通机器让它们分工协作。每个节点只承担一部分压力出了故障还可以由其他节点顶上这就是分布式最朴素的价值。当然分布式也不是白来的它引入的新问题同样棘手后面我会专门拿一个章节讲。1.3 微服务把系统按业务能力切开微服务Microservices才是一种架构风格把一个大的应用系统按照业务能力拆分成一组小型的、独立的服务每个服务有自己的独立数据库、独立部署、独立开发服务之间通过 HTTP 或 RPC 通信。还是拿餐馆类比。单体是小餐馆一个人干所有活微服务则是把一家饭店拆成了独立的档口凉菜间只管凉菜热炒间只管热炒面点间只管点心甚至采购都独立出去了。每个档口有自己的厨房、自己的食材库存、自己的师傅团队档口之间通过“传菜口”协作。如果面点生意爆火只需要扩面点间的灶台不需要连累凉菜间也跟着扩建。代码层面一个电商系统拆成用户服务、商品服务、订单服务、库存服务每个服务都是一个独立的 Spring Boot 应用拥有自己独立的数据库实例或 schema。订单服务需要查用户信息时不能直接查用户库了必须通过用户服务提供的 HTTP 接口或 Dubbo RPC 接口去取。这种拆分带来的好处很明显独立部署、独立扩展、独立容灾团队可以各自为战技术栈也可以灵活选择。但请注意微服务架构在“运行形态”上天然就是一个分布式系统——因为它把原本进程内的调用变成了跨进程的远程调用节点之间必然通过网络协作。这一点正是后面要重点讲的关系。1.4 三个词放在一起到底怎么区分把三个词放在同一个表格里看会清晰很多维度单体架构分布式系统微服务架构关注层面代码组织与部署形态系统运行时的物理形态服务拆分与治理方式核心思路一个进程、一个库、一次部署多节点网络协作完成任务按业务边界拆小独立治理解决的问题项目早期快速迭代单机性能与可用性瓶颈团队协作与独立扩展的矛盾是否依赖多机不一定必然是多节点运行上是分布式代码上按业务拆代价后期耦合严重网络不可靠、数据一致性难分布式复杂度全盘保留还多了服务治理我个人的理解方式是单体关注的是“代码怎么组织”分布式关注的是“系统怎么跑”微服务关注的是“业务怎么拆”。它们不是一个对立面的两个选项而是两个不同维度上的概念。单体也可以分布式运行微服务一定会在分布式环境中运行而分布式系统里也可以跑着几个单体的组合。搞清楚这个关系你其实已经超过了很大一部分面试候选人了。2. 微服务与分布式它们之间到底是什么关系2.1 微服务天然就是分布式但分布式不一定是微服务这个关系一句话就能说清微服务是架构设计层面的一种选择分布式是系统运行时的一种状态。你选择了微服务就意味着系统会由多个进程组成、通过网络协作所以微服务系统天然是分布式系统但是分布式系统不一定是微服务架构因为你可以用多个单体副本加负载均衡组成分布式集群也可以把一个大任务拆成多个进程跑在同一台机器上比如消息队列集群、Hadoop 集群它们都是分布式但并不是微服务。我在工作中经常跟人举一个例子一个公司有三套电商系统第一套是单体部署了十台机器前面挂 Nginx 做负载均衡第二套是把订单、库存、用户拆成三个独立服务分别部署在不同机器上第三套是把一个数据处理任务用 Hadoop 跑在十台机器上做并行计算。这三套系统全都是分布式系统但只有第二套才是微服务架构。这个案例基本上能打通大多数人对概念的混淆。2.2 Hadoop 伪分布式是一种“假”分布式既然前面提到了 Hadoop这里顺便解释一下热词里那个“hadoop伪分布式搭建”。伪分布式Pseudo-Distributed Mode是指在一台物理机器上通过多个 Java 进程模拟 Hadoop 集群中的不同角色NameNode 进程假装自己是主节点DataNode 进程假装自己是数据节点它们之间通过本机网络回环地址通信看起来好像是一个分布式集群实际上所有进程都在同一台机器上。伪分布式的作用主要是学习和开发调试——你不需要三台服务器也能跑通 WordCount 流程能熟悉 HDFS 和 MapReduce 的工作过程。但它不是严格意义上的分布式系统因为它不具备真正的网络延迟、节点故障、数据分散等特征。很多人在简历里写“熟悉Hadoop分布式搭建”如果只是在本机跑过伪分布式面试官一问节点间如何通信、数据块怎么分布、NameNode 挂了怎么办立刻就露馅了。我建议如果真的想学分布式计算至少用三台虚拟机搭一个真集群哪怕性能弱一些也能真实体验到节点失联和副本机制的微妙之处。2.3 微服务不一定要拆得很碎说回微服务。很多团队一听到微服务就恨不得把用户注册、用户登录、用户资料、用户头像全部都拆成四个服务这种“过度拆分”其实是我见过最普遍的问题。微服务的粒度应该跟着业务边界走而不是跟着技术功能走。一个用户领域通常做成一个用户服务就够了里面可以有注册、登录、资料、头像的管理只要内部功能通过本地方法调用是合理的就不必拆开。真正的服务边界通常按照“领域驱动设计DDD”里的限界上下文来划。什么叫限界上下文简单说就是一个业务概念在不同场景里有不同含义在哪个上下文里就用哪个含义。比如“商品”在订单服务里指的是商品ID、名称、快照价格在库存服务里指的是库存量、锁定数量、仓库信息在商品服务里则包含详情、类目、属性。三个服务虽然都在说“商品”但各管各的模型通过接口协作。这种边界拆分才是微服务架构能跑得顺的根本。还有一个容易忽视的点微服务架构一定需要一个优秀的服务治理底座包括注册中心、配置中心、网关、熔断限流组件、分布式链路追踪不然服务拆了运行时的发布会变成一场灾难。这也是为什么 Spring Cloud 全家桶、阿里系的 Nacos、Sentinel、Seata 这么流行的原因——它们就是微服务落地的基础设施。3. 从单体到微服务一个真实的演进路径3.1 什么时候不该拆什么时候必须拆关于要不要上微服务我的态度非常明确没有真实痛点就不要上。判断标准根本不是“别人都在用”或“领导觉得微服务高级”而是要看系统是否出现了下面几种症状发布越来越痛苦一个功能改动要协调多个团队回归测试范围巨大版本上线总出问题。数据库耦合到无法容忍十几个业务模块共享同一个库改一张表的结构要跟十几个团队打招呼。扩容效率太低比如营销活动流量巨大只想把营销模块扩容但单体架构中只能整体复制应用资源浪费严重。团队互相阻塞产品并行迭代的时候订单团队和库存团队天天因为代码冲突和联调问题吵架。可靠性要求不同日志服务可以宕机重启但支付服务绝对不能挂单体环境下二者的故障会被绑定在一起。如果一条都没有中老老实实写单体就好。我见过太多一上来就按微服务规划的创业项目结果团队五个人光搭 Nacos、网关、Sentinel 这些基础设施就花了一个月业务功能一点没写这就是典型的用架构复杂度换业务速度。单体不是原罪它只是不适合超级复杂的场景。3.2 拆分的五个步骤每一步都不能跳如果确实决定要从单体拆到微服务不要搞“一步到位”的大爆炸式重构那样风险极高。比较稳妥的做法是绞杀者模式把新功能做成微服务让老单体逐步让路流量一点点迁移过去。我实际推行过的拆分路径大概是这样划分业务边界。把单体里的所有模块列出来按业务领域归类找出哪些功能属于同一个限界上下文。比如订单、支付结算、库存、用户、营销、商品先画出上下文映射图标出哪些服务需要依赖哪些服务。这一步不写代码纯粹是建模但决定后续所有拆分的质量。先拆无状态服务。不在数据库和事务边界上的服务比如短信通知、邮件发送、数据报表、Excel 导出这些没有强事务要求的功能可以最早独立成服务风险最低也能让团队先跑通微服务的开发、部署、联调流程。再拆有状态的核心业务。订单、库存、支付这些涉及数据库的服务难度最大。拆之前先梳理清楚订单服务要用到的表是哪几张库存服务要用到哪几张这两者之间原本的数据库外键和事务关联要先在应用层通过调用事务性补偿方案去替代。拆库这一步是整个拆分过程中最危险的一步稍不留神就会出现数据不一致。双写与迁移。数据库不能直接扔了老库新服务上线后要同时写新库和老库经过一段时间的校验确保两边数据一致后再逐步切换读流量。这个阶段通常要用消息队列做异步同步而不是同步双写避免影响核心链路性能。流量灰度切换。按接口维度用网关灰度策略把一定比例的流量切到新服务观察日志、监控、错误率确认稳定后再逐步放量到 100%。一旦发现问题立刻把流量切回老服务保证业务连续性。3.3 拆分过程中最常见的失败原因先泼一波冷水。拆分微服务失败的案例我见过的原因高度集中在这几个第一分布式事务方案根本没想清楚就动手了。原来单体里一个本地事务就能搞定的事情比如下单减库存拆开后变成了两个服务之间的事务协作。如果你没提前设计好最终一致性方案拆完第一个服务线上就会爆出一堆订单有了但库存没扣的问题。第二服务拆了数据库没拆等于拆了个寂寞。有些团队把服务边界划好了但数据库还是共用一个导致服务间为了查数据互相直连对方的表。这一直连代码上的边界瞬间被打破几天后你又回到了一锅粥的状态而且比拆分前更乱因为还多了一层远程调用开销。第三忽略了运维能力的提升。单体只需要部署一个应用微服务可能要部署几十个应用每个应用还有不同的配置、不同的扩缩容策略。如果 CI/CD 流程还停留在手工打包上传上了微服务之后光发版就能把人累死。观察一个团队是否具备上微服务的基本条件就看他们 Jenkins/GitLab CI 的自动化程度是否足够。基建不先行微服务必反噬。第四没有建立监控体系就敢上线。几十个服务之间的调用链发现一个请求变慢了你连是哪一环出的问题都不知道这种系统怎么维护至少需要接上 SkyWalking 或 Zipkin 做一些链路追踪配合 Prometheus 告警才能对分布式环境下的故障有一点掌控力。如果你的团队正好在规划和实施阶段想找一个现成的参考骨架可以看看若依微服务plus这个开源项目。它整合了 Nacos 注册配置中心、Spring Cloud Gateway 网关、Sentinel 限流、Seata 分布式事务等一整套微服务基础设施代码结构清晰适合拿来当脚手架学习。但请注意开源脚手架只是起步参考生产环境必须自己审查安全配置、连接池参数、权限模型这些细节。4. 分布式系统绕不过去的三座大山4.1 分布式锁为什么你的库存又超卖了分布式锁是网上热词中出现频率极高的一个概念几乎每个 Java 后端面试题清单里都有。它在解决什么问题呢先回到底层场景一个电商系统部署了三台实例用户同时抢购一件商品库存只剩 10 件但三台机器同时执行“查询库存 - 扣减库存 - 更新库存”这段代码。在单体单实例时代你用一个 synchronized 或者 JVM 内部的 ReentrantLock 就能保证同一时刻只有一个线程去操作库存。但现在是三台独立的机器每个进程内的锁是相互隔离的——机器 A 的锁管不了机器 B 的线程于是三个实例同时读到库存为 10同时减 1结果库存变成了 9而不是 7。这就是超卖问题。分布式锁的本质就是让多个进程之间共享一把锁把这把锁放在所有实例都能访问到的地方。常见的三种实现方式各有优劣数据库唯一约束建一张锁表锁的名称作为唯一索引谁 insert 成功谁就拿到锁释放时 delete。优点是简单缺点是性能差、依赖数据库可用性而且锁释放异常时需要额外处理。这种方式适合并发很低的场景但实际项目中用得越来越少。Redis SET NX EX使用SET key value NX EX 10命令只有在 key 不存在时才能写入成功天然支持原子操作和过期时间。性能很好应用最广。但它有个经典坑如果业务执行时间超过了过期时间锁会被自动释放导致其他线程趁机拿到锁又产生并发问题。ZooKeeper 临时顺序节点在 ZooKeeper 里创建临时顺序节点取到最小序号的客户端获得锁监听前一个节点的删除事件来排队。可靠性极高没有过期时间误删的问题但引入了额外的基础设施性能也不如 Redis适合对一致性要求极高的场景。我最常用的方案是 Redis但通常不是自己原生手写 SET NX而是用 Redisson 封装好的分布式锁理由是它内置了“看门狗”机制默认锁的生存时间为 30 秒只要业务线程还在执行看门狗就会每隔一段时间自动续期避免业务没执行完锁就过期的情况。同时它还支持可重入同一个线程可以重复获取锁而不会死锁。用 Redis 做锁有几个纯手写容易踩的坑分享给大家加锁必须用一条原子命令。很多人用两步先SETNX再EXPIRE如果第一步成功、第二步之前进程挂了锁就永远不会过期所有请求全被卡死。正确写法是SET key value NX EX 10一条命令搞定原子性。释放锁前必须校验身份。线程 A 的锁到期后线程 B 拿到锁此时线程 A 执行完调用 DELETE 把 B 的锁误删了。解决办法是 value 里存一个唯一请求 ID比如 UUID删除前先 GET 出来对比匹配才删。这一步也不是两步操作要用 Lua 脚本保证原子性。业务时间不可控时优先选择 Redisson 这类带续期能力的封装自己实现续期逻辑很容易出 bug。4.2 订单和库存的分布式事务到底怎么保持一致再看“订单与库存分布式事务一致性”这个热词背后的真实困境。单体架构下下单和扣库存是同一个数据库里的两条 SQL可以用一个本地事务包起来要么都成功要么都失败非常简单。但拆成微服务后订单服务写自己的订单库库存服务写自己的库存库此时如果订单创建成功、库存扣减失败那用户的订单就在但库存没减钱也付了货发不出来这就产生了数据不一致。解决分布式事务的思路分两个流派强一致性流派和最终一致性流派。强一致性流派里最著名的是 TCCTry-Confirm-Cancel方案和 Seata 的 AT 模式。TCC 要求在业务层面实现三个方法Try 阶段预留资源Confirm 阶段确认执行Cancel 阶段回滚释放。比如下单时Try 阶段冻结库存Confirm 阶段真正扣减Cancel 阶段解冻。这种方式一致性很高但业务代码侵入性极强三个方法都要自己写复杂度非常高。Seata 的 AT 模式则在框架层面替你做了一部分事通过解析 SQL 生成前后镜像利用 undo_log 表在分支事务回滚时自动执行反向 SQL。听起来很美好但在高并发场景下性能开销也不小而且脏写问题需要全局锁来规避适用范围有限。最终一致性流派我更推荐用于大多数业务场景尤其是电商这类可以容忍几秒内数据短暂不一致的系统。最经典的落地方案是本地消息表 消息队列。核心流程是这样的订单服务在本地事务里同时做两件事插入一条订单记录插入一条“待发送的库存扣减消息”到本地消息表。本地事务提交成功后启动一个定时任务或者通过消息中间件的生产端把这条消息发送给 RocketMQ 或 RabbitMQ。库存服务消费这条消息执行实际的库存扣减扣减成功后向消息中间件发送确认消息中间件删除这条消息。如果库存服务执行失败消息会重试。多次重试仍然失败时进入人工告警队列或发送补偿通知。这个方案的关键是消息的产生和业务数据的变化在同一个本地事务里要么都成功要么都失败这就保证了不会出现“订单建了但消息没发”的情况。消费者这边必须做好幂等处理因为消息可能被重复投递库存扣减操作执行了两次就会出现多扣。幂等通用的做法是给每条消息带一个全局唯一消息 ID消费者本地建一张消费记录表处理前先查这个 ID 是否存在存在就直接返回。还有一种更“轻”的实现思路就是用 RocketMQ 的事务消息本质上也是把本地消息表搬进了消息中间件内部但业务方少了一张表的维护成本。我是建议优先考虑方案本身而不是纠结具体的中间件你先想清楚“本地事务保证数据和消息同时成功”这个要点再选 RocketMQ 还是 RabbitMQ 都顺理成章。4.3 分布式缓存穿透、击穿、雪崩的连环坑分布式系统里缓存几乎和数据库同样重要。Redis 作为分布式缓存是标配但它带来的三个经典故障是每个经历过线上事故的人都忘不掉的缓存穿透、缓存击穿、缓存雪崩。穿透指的是查询一个根本不存在的数据永远也缓存不住。比如请求一个不存在的商品 ID缓存查不到就会穿透到数据库数据库也查不到于是接口直接返回空下一次同样的恶意请求还会再打数据库。如果是正常用户行为还好但如果是黑客用脚本批量攻击数据库可能瞬间被打爆。解决问题的方案一是缓存空值查询结果为空时也写一个短时间的缓存比如 60 秒避免频繁访问数据库二是用布隆过滤器把所有存在的商品 ID 存进过滤器里请求来了先检查过滤器不存在就直接拒绝不需要访问数据库。击穿针对的是某一个热点的 key 在缓存过期的瞬间有大量并发请求同时访问它。缓存里没有数据所有请求全部打到数据库数据库可能瞬间被压垮。解决方法是互斥锁缓存失效时只允许一个线程去数据库查数据并重建缓存其他线程等待或返回旧值核心就是我在前面讲的分布式锁。另一种更简单的办法是“逻辑过期”不给 key 设物理过期时间而是在 value 里存一个过期时间戳后台异步线程判断过期后主动刷新缓存用户请求永远能拿到旧数据不会穿透到数据库。雪崩则是大量的 key 在同一时间段集体过期导致大量请求同时落到数据库。这种情况通常是因为设置了相同的过期时间比如晚上 0 点缓存统一失效。解决的问题方法是过期时间加随机值比如base random(0, 300)秒让每个 key 的过期时间错开避免集体失效还可以采用多级缓存Redis 前面再挡一层本地缓存即使 Redis 短暂失效本地缓存依然能扛住一部分流量。缓存这三大坑我在线上是真实踩过的。有一次促销活动上线运营把所有商品的上架时间统一设置在零点导致零点一到大量过期 key 同时失效数据库连接瞬间被打满整个系统从零点开始瘫了十几分钟。后来我养成了一个习惯任何设置缓存过期时间的代码都必须加随机偏移量没有例外。4.4 分布式定时任务多实例环境下任务别重复执行最后一个高频热点是“SpringCloud 架构中分布式定时任务的解决方案”。这个问题在单体时代不存在一台机器跑一个定时任务每天凌晨三点触发结算跑完就结束。但上了微服务或分布式部署之后同一个应用可能部署了三个实例如果不做控制三个实例会在每天凌晨三点同时触发结算任务造成数据重复处理、用户收到三遍通知甚至资金重复结算的严重事故。解决思路本质上还是分布式锁。在任务执行的最开始先去 Redis 或者数据库抢一把锁抢到锁的实例才执行任务没抢到的实例直接跳过。这是最简单通用的做法适用于不带复杂调度需求的场景。如果任务是周期性的、错峰执行的、需要分布式分片处理的更专业的解法是通过 XXL-Job 这类分布式任务调度平台来做。XXL-Job 的核心模型是调度中心和执行器分离调度中心负责任务的触发策略和路由策略执行器负责具体执行业务逻辑。当任务触发的时刻到了调度中心会根据配置的路由策略比如轮询、故障转移、分片广播把任务分发给某一个执行器或所有执行器。也就是说同一时刻一个任务通常只会被一个执行器实例执行从源头上避免了重复。用 XXL-Job 做分片广播也很实用。每天凌晨要结算全国几十万个商家的账单如果只交给一个实例跑单机可能要跑几个小时。分片广播模式下调度中心通知所有执行器干活每个执行器按自己的分片序号去领取对应的商家数据十个实例同时跑结算时间直接缩短到原来的十分之一。这种场景用简单的 Redis 锁就做不到了因为分片要求所有实例同时执行业务只是各自只处理自己负责的那部分数据。当然分布式任务的设计必须时刻带着幂等意识无论调度平台还是分布式锁方案都不能百分之百保证任务不会重跑。最稳妥的做法是业务执行前查一下“今日是否已经执行过”或者执行时以业务唯一键做去重保证就算任务重复跑了对用户的影响也是零。5. 面试时怎么聊这些概念才不会露怯5.1 先看懂 CAP 定理而不是背出“三选二”分布式系统里最基础也最常被问的理论就是 CAP 定理。很多人的理解是“一致性、可用性、分区容错性三者只能选两个”这个说法其实不够准确也容易在追问中露馅。CAP 的准确表述是在一个分布式系统中当发生网络分区也就是节点之间失联时你必须在一致性和可用性之间做取舍。分区容错性P是客观存在的网络不可能永远可靠所以你默认一定会发生分区发生分区时系统要么选择一致性C拒绝无法确认数据的请求保证所有节点数据相同要么选择可用性A允许每个分区继续独立提供服务但可能出现数据不一致。举个例子你就明白了。注册中心如果选 AP比如 Eureka某个节点失联时它依然允许服务继续注册和发现哪怕数据暂时不一致也要保证服务可用选 CP比如 ZooKeeper写操作需要多数节点确认才能成功如果集群中多数节点失联它宁可拒绝服务也要避免出现数据分裂。所以不是简单地“三选二”而是“P 是前提C 和 A 二选一”。面试时把这个逻辑说清楚比干巴巴背结论要加分得多。5.2 分布式锁的灵魂三问关于分布式锁面试官最喜欢三个追问为什么需要分布式锁Redis 实现锁要注意什么Redis 和 ZooKeeper 分布式锁你怎么选回答的时候顺序不要乱。第一问结合前面的超卖场景说核心是跨进程的互斥。第二问就说 SET NX EX 的原子加锁、Lua 脚本释放锁、唯一身份标识防误删、过期时间要合理设置这些都是实践中的硬功夫。第三问话术大概是Redis 锁性能好、实现简单、能满足大多数业务场景但极端情况下存在主从切换导致锁丢失的可能ZooKeeper 锁的一致性更强但性能略低、部署成本高。如果是电商秒杀这类高并发业务通常选 Redis 加 Redisson如果是分布式协调、元数据管理这类强一致的场景选 ZooKeeper 更稳妥。5.3 幂等性为什么是所有分布式方案的基石分布式系统里几乎每个方案最后都要落到“幂等”这两个字上。幂等的意思很简单同一个操作执行一次和执行多次结果是一样的。网络是不可靠的TCP 可能超时重试消息队列可能重复投递微服务之间的调用可能被负载均衡重发如果你的接口不幂等一个订单可能会被重复创建、库存会多扣、通知会多发。实现幂等最通用的办法是“业务唯一标识 去重表”。比如支付宝回调通知你的支付结果时每笔交易都有一个 trade_no你的系统处理前先查这个 trade_no 是否已经处理过处理过就直接返回成功没处理过才执行真正的业务逻辑。本地消息表方案的消费端也必须有同一套逻辑。从某种程度上说分布式系统的所有一致性方案底层根基都是幂等设计。能把幂等讲清楚面试官基本就认可你有真实分布式系统设计经验了。6. 一点来自实战的心里话文章写到这概念、实操、方案都已经讲得差不多了。最后我想说说自己这几年摸爬滚打的真实感受。我见过太多团队在业务还没跑通的时候就高喊着“我们要微服务化”结果技术选型会开了一个星期服务拆了两三个月最后发现连最基础的用户登录都还没做好。我也见过老老实实用单体架构做到千万级用户的系统依靠数据库主从、Redis 缓存、消息队列异步化硬是撑住了大流量的冲击。架构没有高下之分关键要看匹配度。根据我的经验一个项目从单体演进到微服务最合理的触发时机是业务确实跑起来了团队确实变大了单体确实开始阻碍你迭代了而不是为了简历上写一行“熟悉微服务架构”就贸然动手。上了分布式之后你还会和各种各样的故障打交道网络抖动、时钟漂移、消息丢失、数据不一致这些复杂度不会因为你用了某个框架而消失它只是换了一种形式住在你的系统里。如果你刚接触这些概念我建议你先不要急着读各种源码分析先把单体写好把分布式锁、分布式事务、分布式缓存这几个核心问题的场景和方案理解透彻再动手拆。架构能力不是背出来的是做出来的真正的成长都在线上事故和复盘里。希望这篇文章能帮你把那层窗户纸捅破下次再有人问“分布式、单体、微服务到底是什么意思”你也能像我一样张口就讲得明明白白。
返回列表