[{"data":1,"prerenderedAt":305},["ShallowReactive",2],{"article:column:/column/cloudnative/architecture-tradeoffs":3},{"id":4,"title":5,"body":6,"description":294,"extension":295,"meta":296,"navigation":300,"path":301,"seo":302,"stem":303,"__hash__":304},"column/column/cloudnative/architecture-tradeoffs.md","微服务 网格与Serverless该在什么时候出现",{"type":7,"value":8,"toc":270},"minimark",[9,13,16,20,23,26,29,32,35,38,41,44,47,55,58,61,64,67,70,80,83,86,92,95,101,104,108,114,117,120,123,127,133,136,139,142,145,148,151,154,157,160,163,166,169,174,177,180,183,186,189,193,196,199,202,205,208,212,215,218,221,224,227,230],[10,11,12],"p",{},"前面的读书笔记服务已经可以打包、发布、观察和恢复。此时打开一张云原生生态图，仍然会看到大量没用过的项目。",[10,14,15],{},"不必急着补齐它们。系统需要增加一个组件，应该是因为某个具体问题反复出现，而且已有方法处理它的成本开始过高。下面沿着摘要任务的变化，看看几种常见架构选择究竟改变了什么。",[17,18,19],"h2",{"id":19},"先判断是否真的需要拆服务",[10,21,22],{},"最初，生成摘要只是保存笔记后的一个函数调用。后来摘要需要等待外部服务，用户每次保存笔记都要多等几秒。一个直接的改进是把摘要生成变成异步任务：保存笔记后记录待处理任务，worker稍后生成结果，界面显示“处理中”。",[10,24,25],{},"这个决定首先改变了用户可见的业务流程。保存成功不再意味着摘要已经完成，所以界面、接口和错误处理都要表达中间状态。只有把这些说清楚，队列才真正解决了等待问题。",[10,27,28],{},"是否进一步把worker拆成独立服务，则取决于运行和维护需求。摘要任务需要不同依赖、独立扩容，或由另一团队负责时，拆分可能有价值。如果只是目录里多了几个类，先保持清晰的模块边界也很好。",[10,30,31],{},"服务边界增加了部署自由，同时增加网络调用、认证、超时、版本兼容和观测成本。原来一个进程内的异常，变成了需要在两个进程之间解释的部分失败。业务分工不清楚时，拆成更多服务通常只会让原来的耦合更难看见。",[17,33,34],{"id":34},"网络失败留下的不是简单真假",[10,36,37],{},"API把摘要任务交给worker，等待响应时连接断开。API知道请求是否被worker执行了吗？",[10,39,40],{},"不知道。连接断开可能发生在worker收到请求之前，也可能发生在它已经写入结果之后。超时描述的是调用方没有及时拿到结果，不直接证明被调用方没有做事。",[10,42,43],{},"如果立即重试，可能重复生成摘要；如果永不重试，又可能丢掉任务。正确做法需要业务标识。例如以“笔记ID＋正文版本＋摘要算法版本”作为任务的幂等键，让重复处理能识别同一件工作，而不是每收到一次请求就无条件新增一条结果。",[10,45,46],{},"还要明确写入顺序。worker可以先生成结果，再在数据库中以唯一约束或事务方式提交，最后确认任务完成。若提交成功、确认消息失败，任务再次投递时应能识别已有结果。仅在内存中记一个“处理过”的集合，进程重启后就会丢失，无法承担这种保证。",[10,48,49,50,54],{},"Kubernetes Job也需要这样的设计。它负责推动任务完成，但重试和故障条件下，程序可能不止执行一次。",[51,52,53],"span",{},"1"," 调度层的成功语义，不能替代业务副作用的幂等性。",[17,56,57],{"id":57},"重试需要总预算",[10,59,60],{},"假设浏览器最多尝试三次，API调用摘要服务也最多尝试三次，摘要服务访问外部接口再最多尝试三次，这里的三次都包含首次请求。一次用户操作在故障情况下可能放大成许多次下游调用，最差组合可达到二十七次尝试。",[10,62,63],{},"每一层单独看都像在提高成功率，叠加起来却可能加重下游过载。更合理的做法是定义端到端时间预算，明确哪一层负责重试，限制次数，并使用退避与抖动分散恢复时的请求。",[10,65,66],{},"重试还需要区分错误类型。网络瞬时故障可能值得稍后再试；请求参数永远无效，重复提交通常没有帮助；下游已经明确限流，应尊重它的反馈并控制并发。",[10,68,69],{},"超时、限流、熔断和降级都是为了给失败划边界。它们并不会凭空提高下游容量。举例来说，摘要服务不可用时允许先保存笔记，是业务降级；直接返回一段编造的摘要，就改变了业务正确性，不能只看接口是否返回200。",[10,71,72],{},[73,74,76],"a",{"href":75},"/collections-assets/cloudnative/cn12-retry-amplification.png",[77,78],"img",{"alt":79,"src":75},"浏览器、API 和摘要服务三个发送方各自总共最多尝试三次，最坏组合会导致二十七次外部接口调用；应使用统一 deadline、有限并发和稳定幂等键。",[10,81,82],{},"各层独立重试会放大下游压力，需要共同时间预算和业务幂等。",[17,84,85],{"id":85},"服务网格把哪部分能力移出应用",[10,87,88,89],{},"服务数量增加后，多个语言栈可能都要处理服务间认证、流量策略和通信指标。服务网格尝试在基础设施层统一部分通信能力。Istio等实现提供流量管理、安全和可观测性相关能力；具体数据面形态与部署模式应按实现选择。",[51,90,91],{},"2",[10,93,94],{},"它的价值在于重复的网络治理可以有统一入口。代价是请求链路和控制面增加了组件，团队需要理解代理或相关数据面的配置、证书、升级、资源开销及故障。",[10,96,97,98],{},"网格也不知道“生成摘要”是否允许重复。它可以帮你配置重试，却无法仅凭HTTP状态推导业务副作用。若基础设施层和应用层同时重试，反而可能形成前面描述的放大链条。超时与重试策略必须跨层协调。",[51,99,100],{},"3",[10,102,103],{},"因此，只有两三个简单服务的团队，不必因为“微服务就应该有网格”而安装一整套系统。可以先统一客户端库与可观测性约定；当跨语言、跨团队的网络策略确实难以一致维护时，再评估网格。",[17,105,107],{"id":106},"operator把领域知识放进控制器","Operator把领域知识放进控制器",[10,109,110,111],{},"Deployment知道如何维护一组应用实例，却不了解数据库升级顺序、主从切换或恢复条件。Operator模式使用自定义资源和控制器，把某个领域的运维知识编码成持续协调逻辑。",[51,112,113],{},"4",[10,115,116],{},"例如，某个数据库Operator可以根据一个声明创建所需组件，并按它实现的流程处理特定备份或升级动作。但这些能力来自该Operator的具体实现，不能从“用了Operator”自动推出。",[10,118,119],{},"可以把它看成长期运行的软件系统，而不是安装后不需要维护的脚本。它需要权限、版本兼容、测试、告警与故障恢复方案。控制器本身出现错误时，也可能持续重复错误动作，所以权限范围和可中止性都很重要。",[10,121,122],{},"评估一个Operator，至少读清支持的数据库版本、备份恢复范围、升级与降级路径、维护状态以及失败时如何接管。对关键数据系统，能成功创建一个集群远远不够，还必须演练恢复。",[17,124,126],{"id":125},"serverless改变的是资源管理接口","Serverless改变的是资源管理接口",[10,128,129,130],{},"摘要任务也可能运行在按请求或事件触发的平台上。以Knative Serving这类平台为例，它在Kubernetes之上提供服务部署、版本和流量等抽象，并有自动扩缩相关能力。",[51,131,132],{},"5",[10,134,135],{},"使用者更少直接管理常驻实例，但服务器并没有消失。底层资源、运行时和调度仍然存在，只是更多工作由平台承担。",[10,137,138],{},"对低频、突发任务，缩到零或按需启动可能节省空闲资源。代价可能包含冷启动延迟、执行时间限制、并发控制、状态保存方式和平台耦合。实际行为取决于选定平台与配置，不能把某一产品的限制写成Serverless的统一定义。",[10,140,141],{},"若用户点击后必须立即得到摘要，而冷启动加外部调用超过可接受时延，就需要预热、最小实例数、异步交互或其他设计。若任务每小时才发生一次，长期保持大量空闲实例也许没有必要。选择来自负载形态和体验要求。",[17,143,144],{"id":144},"用同一套问题评估新组件",[10,146,147],{},"面对新技术，可以沿着五个具体问题做一次小型设计评审。",[10,149,150],{},"第一，当前瓶颈是什么，有什么证据？是发布互相阻塞、重复网络策略、资源空闲，还是根本没有明确问题？",[10,152,153],{},"第二，它通过哪条机制改善瓶颈？答案应该能画出请求或控制路径，而不只是列出“弹性、可扩展、高可用”。",[10,155,156],{},"第三，失败会发生在哪里？新增控制器不可用、证书过期、队列积压、配置冲突时，业务怎样退化？",[10,158,159],{},"第四，谁维护它？安装、升级、告警和恢复都需要明确责任人。",[10,161,162],{},"第五，退出成本是多少？能否迁回更简单的方案，数据与业务协议是否被不必要地绑进平台特有接口？",[10,164,165],{},"读书笔记服务可以从一个结构清晰的单体开始，也可以在需求成熟后拆出独立worker。只要运行边界、状态、变更和故障有清楚的设计，就已经在实践这个合集讨论的工程方法。技术数量并不能替代这些基础。",[17,167,168],{"id":168},"动手想一想",[170,171,173],"h3",{"id":172},"题目-1","题目 1",[10,175,176],{},"worker已经写入摘要，但确认任务时网络断开。队列再次投递任务，怎样避免产生重复结果？",[170,178,179],{"id":179},"提示",[10,181,182],{},"任务身份应该跨越进程重启存在，结果提交与重复检查要有可靠的一致性约束。",[170,184,185],{"id":185},"答案",[10,187,188],{},"使用稳定幂等键，例如笔记ID、正文版本和算法版本的组合，并通过数据库唯一约束或合适事务保证重复提交可识别。确认失败后的再次执行先检查相同任务结果，不能只靠内存记录。",[170,190,192],{"id":191},"题目-2","题目 2",[10,194,195],{},"服务网格配置了自动重试，应用还能不考虑幂等性吗？",[170,197,179],{"id":198},"提示-1",[10,200,201],{},"网络层知道请求是否产生了业务副作用吗？",[170,203,185],{"id":204},"答案-1",[10,206,207],{},"不能。超时可能发生在业务执行之后，网络层未必知道。应用需要设计幂等语义，并统一不同层的重试预算，防止重复副作用和请求放大。",[170,209,211],{"id":210},"题目-3","题目 3",[10,213,214],{},"一个低频任务迁到按需启动平台后成本下降，但第一次请求变慢。该如何判断是否值得？",[170,216,179],{"id":217},"提示-2",[10,219,220],{},"比较节省的空闲资源与用户等待时间，并考虑可选的异步流程或最小实例数。",[170,222,185],{"id":223},"答案-2",[10,225,226],{},"先确认冷启动和依赖初始化是否是延迟来源，再对照业务时延要求。可以选择异步交互、预热或最小实例数，也可以保留常驻服务。成本与体验的权衡应来自实际负载，不能只以平台名称决定。",[17,228,229],{"id":229},"参考资料",[231,232,233,242,249,256,263],"ol",{},[234,235,236],"li",{},[73,237,241],{"href":238,"rel":239},"https://kubernetes.io/docs/concepts/workloads/controllers/job/",[240],"nofollow","Kubernetes Jobs",[234,243,244],{},[73,245,248],{"href":246,"rel":247},"https://istio.io/latest/about/service-mesh/",[240],"The Istio service mesh",[234,250,251],{},[73,252,255],{"href":253,"rel":254},"https://istio.io/latest/docs/concepts/traffic-management/",[240],"Istio Traffic Management",[234,257,258],{},[73,259,262],{"href":260,"rel":261},"https://kubernetes.io/docs/concepts/extend-kubernetes/operator/",[240],"Kubernetes Operator pattern",[234,264,265],{},[73,266,269],{"href":267,"rel":268},"https://knative.dev/docs/serving/",[240],"Knative Serving",{"title":271,"searchDepth":272,"depth":272,"links":273},"",3,[274,276,277,278,279,280,281,282,293],{"id":19,"depth":275,"text":19},2,{"id":34,"depth":275,"text":34},{"id":57,"depth":275,"text":57},{"id":85,"depth":275,"text":85},{"id":106,"depth":275,"text":107},{"id":125,"depth":275,"text":126},{"id":144,"depth":275,"text":144},{"id":168,"depth":275,"text":168,"children":283},[284,285,286,287,288,289,290,291,292],{"id":172,"depth":272,"text":173},{"id":179,"depth":272,"text":179},{"id":185,"depth":272,"text":185},{"id":191,"depth":272,"text":192},{"id":198,"depth":272,"text":179},{"id":204,"depth":272,"text":185},{"id":210,"depth":272,"text":211},{"id":217,"depth":272,"text":179},{"id":223,"depth":272,"text":185},{"id":229,"depth":275,"text":229},"从一次摘要任务拆分出发，讨论服务边界、重试和幂等，再判断服务网格、Operator与Serverless的收益和代价。","md",{"collection":297,"order":298,"slug":299},"云原生",12,"architecture-tradeoffs",true,"/column/cloudnative/architecture-tradeoffs",{"title":5,"description":294},"column/cloudnative/architecture-tradeoffs","uJ3Or_e0k7V95gdzL5NQyMj3zU2MwltHW0ai2auP5T4",1791935829023]