<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>研发效能 - Tag - SaiyuDoc</title><link>https://www.wangsaiyu.com/tags/%E7%A0%94%E5%8F%91%E6%95%88%E8%83%BD/</link><description>研发效能 - Tag - SaiyuDoc</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>panicwang@outlook.com (王赛宇)</managingEditor><webMaster>panicwang@outlook.com (王赛宇)</webMaster><lastBuildDate>Mon, 21 Sep 2026 11:09:07 +0000</lastBuildDate><atom:link href="https://www.wangsaiyu.com/tags/%E7%A0%94%E5%8F%91%E6%95%88%E8%83%BD/" rel="self" type="application/rss+xml"/><item><title>后端架构演进：服务拆分、环境选路与研发平台建设</title><link>https://www.wangsaiyu.com/posts/engineering/rd-platform-evolution/</link><pubDate>Mon, 21 Sep 2026 11:09:07 +0000</pubDate><author>王赛宇</author><guid>https://www.wangsaiyu.com/posts/engineering/rd-platform-evolution/</guid><description><![CDATA[<p>生成链路最初由两个核心服务组成：Gateway 接收请求并处理项目接口，Runtime 运行 Agent，调用模型和工具，返回执行过程与结果。</p>
<p>随着业务增加，项目管理、调度和不同客户端的接入逐步拆开。服务之间的调用、环境选择、发布与排障，也需要相应调整。本文按这些问题的出现顺序，介绍各阶段的结构和实现方法。</p>
<h2 id="1-服务拆分">1. 服务拆分</h2>
<h3 id="11-初始结构">1.1. 初始结构</h3>
<p>早期 Gateway 通过 Kitex RPC 调用 Runtime。Runtime 已经维护任务状态与事件，也采集运行、工具调用和错误指标。项目、素材、用户及其他外部依赖，围绕这两个核心服务接入。</p>
<p>当时服务地址和发布目标比较固定。个人 Gateway 对应个人 Runtime，共享开发环境使用另一组应用。CI 根据提交来源选择目标，构建镜像，再更新相应云应用。</p>
<p>服务较少时，这些对应关系容易维护。调试人员知道请求经过哪两个应用，流水线也只需要更新少量固定目标。</p>
<h3 id="12-项目与调度的拆分">1.2. 项目与调度的拆分</h3>
<p>Gateway 中的逻辑增加后，一次改动可能同时涉及客户端协议、项目状态和任务执行。我们把项目管理与调度抽出，让它们分别维护自己的状态。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/17-evolution-map.svg" title="生成链路从 Gateway 与 Runtime，演进为项目、调度和执行服务" data-thumbnail="/images/rd-platform-evolution/17-evolution-map.svg" data-sub-html="<h2>图 1：生成链路的主要服务变化</h2>">
        
    </a><figcaption class="image-caption">图 1：生成链路的主要服务变化</figcaption>
    </figure>
<p>拆分后的主要职责如下：</p>
<table>
  <thead>
      <tr>
          <th>组件</th>
          <th>负责的内容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Gateway</td>
          <td>客户端协议、请求校验和必要的业务编排。</td>
      </tr>
      <tr>
          <td>项目服务</td>
          <td>项目、版本等业务对象及其状态。</td>
      </tr>
      <tr>
          <td>Dispatcher</td>
          <td>任务提交、排队与分发，并组织项目创建和首轮执行。</td>
      </tr>
      <tr>
          <td>Runtime</td>
          <td>Agent 执行、模型与工具调用、执行状态和结果。</td>
      </tr>
  </tbody>
</table>
<p>入口校验请求后，通过 RPC 调用相应服务。Dispatcher 组织项目创建和首轮执行，需要异步处理的事件与后续工作进入队列。项目对象、执行任务和客户端连接由此具有独立的生命周期。</p>
<p>这使不同入口能够复用同一份项目状态，调度可以单独处理积压，Runtime 也可以围绕执行能力演进。相应的代价是调用次数和版本组合增加，一次请求开始需要跨服务追踪。</p>
<p>图中按职责归纳阶段，各阶段都有其他依赖，迁移期间也存在新旧实现并行的情况。</p>
<h2 id="2-环境选路">2. 环境选路</h2>
<p>这里的灰度主要指按环境标记定向验证，以及预发布环境选择。目标是让一次请求经过一组匹配的服务版本；按百分比放大正式流量属于另一种发布策略。</p>
<p>按照目标地址的确定方式，这部分可以分为四轮调整。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/18-gray-four-designs.svg" title="固定地址、命名空间、服务名前缀与请求上下文四种选路方式" data-thumbnail="/images/rd-platform-evolution/18-gray-four-designs.svg" data-sub-html="<h2>图 2：四轮环境选路方式</h2>">
        
    </a><figcaption class="image-caption">图 2：四轮环境选路方式</figcaption>
    </figure>
<h3 id="21-固定地址映射">2.1. 固定地址映射</h3>
<p>第一轮给开发人员分配固定应用。CI 将代码部署到这些应用，请求携带环境标记，Nginx 根据标记选择对应的上游地址或端口。Gateway 再根据配置连接 Runtime。</p>
<p>这种方式要维护三组关系：人员与应用、环境标记与地址、Gateway 与 Runtime。每增加一套环境，都要同步更新这些配置。</p>
<p>这个阶段 Nginx 选择的上游中包含 NLB 地址。后续基础设施迁移改变了 NLB 与 Nginx 的连接关系，早期选路方式需要按当时的拓扑理解。</p>
<h3 id="22-命名空间与服务发现">2.2. 命名空间与服务发现</h3>
<p>第二轮通过服务名寻找目标。调用方先在当前命名空间查询同名服务，明确不存在时，再根据规则查找共享环境。</p>
<p>实例地址变化后，由服务发现和 DNS 更新，调用方继续使用稳定服务名。允许共享回退的依赖，也不必在每个个人环境中部署一份。</p>
<p>限制在于，环境身份与进程所在命名空间绑定。若一个命名空间内需要同时运行多组实例，只根据命名空间已无法区分它们。</p>
<h3 id="23-服务名前缀">2.3. 服务名前缀</h3>
<p>第三轮把环境标记加入服务名。同组个人实例使用相同前缀，调用方优先查询带前缀的目标，再按策略选择共享版本。Nginx 也可以根据环境标记推导服务名，减少逐个维护地址映射的工作。</p>
<p>CI 在部署时把环境身份写入实例配置；调度队列也逐步从命名空间维度转为环境身份维度。</p>
<p>但共享进程可以接收不同环境的请求。例如共享 Gateway 收到个人请求，如果后续服务发现只使用 Gateway 自己的启动配置，仍然会调用共享下游。因此，还需要将进程身份与本次请求选择的环境分开。</p>
<h3 id="24-请求上下文">2.4. 请求上下文</h3>
<p>第四轮把环境选择放入请求上下文。HTTP 入口识别环境，标准 RPC 客户端将其传给下游；发送消息时写入消息属性，消费时再恢复为上下文。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/19-routing-context.svg" title="环境信息在 HTTP、RPC 和消息之间传递，驱动每一跳的服务选择" data-thumbnail="/images/rd-platform-evolution/19-routing-context.svg" data-sub-html="<h2>图 3：请求环境在调用链中的传递</h2>">
        
    </a><figcaption class="image-caption">图 3：请求环境在调用链中的传递</figcaption>
    </figure>
<p>这时，共享 Gateway 可以接收个人请求，再调用对应的个人 Dispatcher 和 Runtime。是否存在个人入口，与后续任务应由哪组实例执行，可以分别决定。</p>
<p>服务发现缓存也需要包含环境维度。若缓存只按逻辑服务名保存，环境 A 查询到的地址就可能被环境 B 复用。</p>
<p>环境标记只用于选路，用户身份与操作权限仍然独立校验。共享 Gateway 的进程环境也不会因为一条个人请求而改变。</p>
<h3 id="25-回退条件">2.5. 回退条件</h3>
<p>不同依赖采用不同选路策略：</p>
<ul>
<li>优先个人：存在个人版本时使用个人版本，满足回退条件时使用共享版本；</li>
<li>必须个人：必须命中目标环境，缺失时返回错误；</li>
<li>仅共享：始终使用共享实例。</li>
</ul>
<p>关键调度和执行链要求命中同一组实例。普通依赖是否可以共享，由对应策略确定。</p>
<p>允许回退时，也要区分“未部署”和“运行出错”。明确的 DNS 不存在可以表示目标未部署；DNS 超时、连接失败或应用错误则不能直接当作未部署。否则新版本出错后转用共享版本，测试结果就无法反映新版本的实际行为。</p>
<h2 id="3-分支环境的发布与回收">3. 分支环境的发布与回收</h2>
<h3 id="31-构建范围与运行依赖">3.1. 构建范围与运行依赖</h3>
<p>严格选路之后，目标环境必须包含必要的服务。</p>
<p>例如只修改 Gateway，CI 按代码差异可能只生成 Gateway 的构建与发布任务。但新环境中的 Dispatcher 和 Runtime 尚不存在，请求就无法继续执行。</p>
<p>我们因此为关键链路增加发布组。命中相关改动时，除代码依赖决定的构建范围外，还要补齐这组服务的运行依赖。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/01-overview.svg" title="代码变更决定构建范围，发布组补齐环境所需服务" data-thumbnail="/images/rd-platform-evolution/01-overview.svg" data-sub-html="<h2>图 4：构建范围与发布顺序</h2>">
        
    </a><figcaption class="image-caption">图 4：构建范围与发布顺序</figcaption>
    </figure>
<p>构建可以并行进行；部署按依赖顺序，先 Runtime，再 Dispatcher，最后入口。发布组保证按约定准备链路，不提供跨服务事务，也不意味着数据库和全部外部资源都被复制。</p>
<p>只发布改动服务适合已有完整、兼容依赖的环境。创建新环境时，还需要检查那些没有代码变化、但执行链必需的服务。</p>
<h3 id="32-环境生命周期">3.2. 环境生命周期</h3>
<p>个人环境后来逐步改为按功能分支创建。分支打开时准备环境，更新代码时继续发布，合并、关闭或到期后回收。这样，同一个人可以同时维护多套改动，每套环境都有明确的代码来源和寿命。</p>
<p>临时环境使用内部 Service，并按策略复用共享依赖。共享入口的 NLB 绑定不能直接复制给个人实例，否则个人实例可能接收到共享流量。数据库和外部资源的隔离范围，也需要单独定义。</p>
<p>回收时还要处理资源名称复用的问题。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/10-lifecycle.svg" title="环境名称可以复用，回收操作通过创建记录和资源身份区分旧环境与新环境" data-thumbnail="/images/rd-platform-evolution/10-lifecycle.svg" data-sub-html="<h2>图 5：同名环境重建后，旧回收任务只能处理原来的资源</h2>">
        
    </a><figcaption class="image-caption">图 5：同名环境重建后，旧回收任务只能处理原来的资源</figcaption>
    </figure>
<p>同名环境关闭后重新创建，已经是另一代资源。旧回收任务如果延迟执行，不能仅根据名称删除新环境。</p>
<p>因此，回收先关闭当前代次，阻止新发布，等待正在执行的操作退出，再按保存的资源 UID 等精确身份删除。新建环境使用新的代次和资源身份，迟到的旧任务据此失效。</p>
<p>选路负责确定请求去哪一组实例，生命周期管理负责确定这组实例何时存在，两者使用同一份环境身份关联。</p>
<h2 id="4-接入层与移动端">4. 接入层与移动端</h2>
<h3 id="41-按业务拆分-gateway">4.1. 按业务拆分 Gateway</h3>
<p>项目和执行服务拆出以后，大 Gateway 中仍有不同领域的客户端接口。后续按业务逐步划分接入服务，通过 Nginx 路由让新旧入口并存。</p>
<p>外部调用方继续使用稳定入口，Nginx 按路径选择对应 Gateway。一个领域迁移完成后，再移除旧路由和旧实现。</p>
<p>迁移中的错误处理需要明确。新 Gateway 返回错误时，自动转到旧实现可能掩盖问题；写操作重试到另一上游，也需要考虑原请求是否已经执行。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/15-ingress-chain.svg" title="NLB、Nginx、鉴权服务、Gateway 和 WAP 的接入关系" data-thumbnail="/images/rd-platform-evolution/15-ingress-chain.svg" data-sub-html="<h2>图 6：业务 API 的接入结构</h2>">
        
    </a><figcaption class="image-caption">图 6：业务 API 的接入结构</figcaption>
    </figure>
<p>图从业务 API 的 NLB 展开，各层分别负责：</p>
<ul>
<li><code>NLB</code>：将连接交给入口实例；</li>
<li><code>Nginx</code>：HTTP 路由、协议升级，以及通过子请求衔接鉴权；</li>
<li><code>Gateway</code>：对应业务的客户端协议与必要编排；</li>
<li><code>WAP</code>：面向移动端的创建、会话和流式交互协议；</li>
<li><code>内部服务</code>：通过 RPC 提供项目、版本、调度和执行能力。</li>
</ul>
<p>域名前的代理与 TLS 终止配置，按具体入口确定，不在图中统一假设。</p>
<p>迁移到 Kubernetes 时，我们保留外部入口地址与监听，将后端接到由集群控制器维护的 Pod 集合。原托管平台管理的服务器组，需要重新确认管理归属，不能假设新控制器会直接接管。</p>
<h3 id="42-httpwebsocket-与-rpc-流">4.2. HTTP、WebSocket 与 RPC 流</h3>
<p>移动端需要更集中、稳定的创建和对话协议，因此增加了与普通 Gateway 并列的 WAP。它复用已有的项目、版本、文件、调度与执行能力。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/16-client-rpc-lifecycle.svg" title="HTTP 创建项目，WebSocket 连接交互，WAP 桥接内部 RPC 流" data-thumbnail="/images/rd-platform-evolution/16-client-rpc-lifecycle.svg" data-sub-html="<h2>图 7：客户端连接与执行任务的生命周期</h2>">
        
    </a><figcaption class="image-caption">图 7：客户端连接与执行任务的生命周期</figcaption>
    </figure>
<p>一次创建请求通过 HTTP 返回项目对象。客户端随后通过 WebSocket 连接项目，WAP 校验访问权限，再桥接内部双向 RPC 流，将客户端命令和执行事件进行转换。</p>
<p>关闭页面结束的是观察连接。若用户要求停止生成，需要发送指向当前轮次的终止命令；已经存在的项目和任务不会因为连接关闭而自动删除。</p>
<p>按业务拆分 Gateway 和增加移动端入口，解决的是两种接口变化，但业务状态仍由原服务维护，避免不同入口各存一份项目数据。</p>
<h2 id="5-指标采集与查询">5. 指标采集与查询</h2>
<h3 id="51-统一采集方式">5.1. 统一采集方式</h3>
<p>Runtime 和 Gateway 原来已有各自的指标。服务增加后，主要问题是同一个概念可能使用不同的标签、分母和聚合方式，面板之间难以比较。</p>
<p>公共组件逐步接管重复的采集逻辑：Kitex 中间件记录 RPC 调用量、错误、耗时和并发；Hertz 入口统一采集 HTTP 指标；队列与业务代码补充任务相关指标。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/20-metrics-collection.svg" title="公共组件采集指标，经 PushGateway、Prometheus 和 Grafana 查询展示" data-thumbnail="/images/rd-platform-evolution/20-metrics-collection.svg" data-sub-html="<h2>图 8：指标的采集、存储与查询</h2>">
        
    </a><figcaption class="image-caption">图 8：指标的采集、存储与查询</figcaption>
    </figure>
<p>服务定期将指标发送到托管 Prometheus 的 PushGateway，再由 Prometheus 保存和查询，Grafana 展示并计算规则。</p>
<p>服务清单声明能力与部署目标，对应的监控模板生成面板和规则。平台根据实际目标确定查询范围。服务代码仍需产生正确样本，声明某项能力本身不会生成数据；采集未接入时，应当反映为缺少数据。</p>
<h3 id="52-成功与失败的统计范围">5.2. 成功与失败的统计范围</h3>
<p>一次请求可能在入口成功接收后继续排队，也可能在多次工具调用后失败。因此，不同层的成功与失败要分别统计。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/04-outcomes.svg" title="入口接收、RPC 调用、队列等待和任务结束分别记录状态" data-thumbnail="/images/rd-platform-evolution/04-outcomes.svg" data-sub-html="<h2>图 9：不同层的统计对象</h2>">
        
    </a><figcaption class="image-caption">图 9：不同层的统计对象</figcaption>
    </figure>
<p>HTTP 成功表示入口接收或处理了这次请求，RPC 成功表示该次调用完成。最终任务失败率则按任务的终态统计，一次任务中的多次重试放在调用指标中。</p>
<p>用户断开页面、主动取消任务和执行器异常，也要保留不同结束原因。否则一次用户请求中的三次调用失败，会被误解成三个用户任务失败；入口持续返回 200，又可能掩盖后续任务异常。</p>
<h3 id="53-依赖资源指标">5.3. 依赖资源指标</h3>
<p>数据库、Redis 等依赖同时需要观察应用调用和资源运行情况。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/05-infrastructure.svg" title="标准客户端采集调用指标，云监控采集资源指标，再按实例和时间关联" data-thumbnail="/images/rd-platform-evolution/05-infrastructure.svg" data-sub-html="<h2>图 10：应用调用与云资源指标</h2>">
        
    </a><figcaption class="image-caption">图 10：应用调用与云资源指标</figcaption>
    </figure>
<p>客户端插件或 Hook 记录调用方、耗时与错误；云监控提供实例 CPU、内存、连接数和 I/O。先对齐资源身份与时间窗口，才能判断延迟来自调用方、连接池还是资源本身。</p>
<p>查询目标也需要准确。预发布和正式环境可能使用同一份生产配置，单一环境标签未必足以区分，要继续约束实际部署实例。共享 Gateway 接收个人请求时，进程环境与请求环境不同，也不能把全部基础指标都改写为请求环境。</p>
<h3 id="54-告警处理">5.4. 告警处理</h3>
<p>Grafana 计算告警规则，平台维护事件状态、分级和通知节奏，既有消息服务负责发送。</p>
<p>调用错误、队列积压、最终任务失败和资源饱和分别设置条件。多个指标可能来自同一故障，通知时需要结合事件状态组织，而不是每层都无差别发送。</p>
<p>平台在这里主要维护服务、部署目标、实例与告警对象之间的关系，指标存储与查询仍然由 Prometheus 和 Grafana 完成。</p>
<h2 id="6-日志采集与查询">6. 日志采集与查询</h2>
<h3 id="61-两条采集通道">6.1. 两条采集通道</h3>
<p>跨服务排查需要将日志汇聚到一起，并保留请求、任务与运行实例的关联。业务服务和 Nginx 使用不同的采集通道。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/21-log-collection.svg" title="Go 服务通过公共日志库上报，Nginx 通过文件采集进程上报 SLS" data-thumbnail="/images/rd-platform-evolution/21-log-collection.svg" data-sub-html="<h2>图 11：业务日志与入口日志的采集过程</h2>">
        
    </a><figcaption class="image-caption">图 11：业务日志与入口日志的采集过程</figcaption>
    </figure>
<p>业务代码通过公共日志接口写入，公共库补充服务、运行环境和调用位置，保留上下文中的请求与任务字段，再由 SLS 后端异步批量发送。标准输出仍可保留，用于进程启动和现场排查。</p>
<p>Nginx 不直接调用 Go 日志库。它先写结构化访问日志和错误日志，由随入口镜像运行的采集进程读取文件，解析字段、脱敏并批量写入 SLS。</p>
<p>入口日志中的请求编号、上游、状态和耗时，说明请求经过了哪一跳；业务日志中的请求、任务和执行编号，用于继续查找后续处理。消息队列 SDK 等依赖库的日志，也需要接到公共通道，才能在同一时间窗口内排查。</p>
<h3 id="62-查询范围">6.2. 查询范围</h3>
<p>日志集中后，需要进一步限定查哪一组实例。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/06-logs.svg" title="发布目标推导服务与实例范围，结合固定时间窗口查询日志" data-thumbnail="/images/rd-platform-evolution/06-logs.svg" data-sub-html="<h2>图 12：由发布目标确定日志查询范围</h2>">
        
    </a><figcaption class="image-caption">图 12：由发布目标确定日志查询范围</figcaption>
    </figure>
<p>预发布与正式环境共用生产配置时，只筛选 <code>env=prod</code> 会混入两组实例。平台根据目标声明确定完整实例范围，加入固定时间窗口，再查询并核对返回记录的归属。</p>
<p>翻页也使用同一窗口，避免第一页和第二页覆盖不同时间范围。日志字段还需要正确建立索引，否则记录已经写入，查询仍然可能没有结果。</p>
<p>一条日志从写入到被查出，会经过应用输出、采集与发送、SLS 接收、字段索引和查询条件。排查时逐段确认；发布验证则使用同一个请求编号，检查入口和业务日志能否被找到。</p>
<p>异步发送还存在进程退出、发送失败和缓冲积压等情况，所以任务最终结果仍以业务持久记录为准。日志集中也不自动等同于完整的分布式调用追踪。</p>
<h2 id="7-发布制品与执行记录">7. 发布制品与执行记录</h2>
<h3 id="71-从固定目标到发布操作">7.1. 从固定目标到发布操作</h3>
<p>早期 CI 直接维护提交来源与应用目标的对应关系，完成构建、推镜像和更新应用。</p>
<p>服务拆分后，构建范围要展开共享代码影响，发布范围还要补齐运行依赖。页面、CLI 和自动化也开始发起发布，若各自拼接集群、应用和环境配置，就容易出现不同入口指向不同目标的情况。</p>
<p>后来，发布目标与执行过程收进研发平台。CI 提交可核验的构建结果，平台登记制品、解析目标、保存发布操作，再由持久 Worker 执行。各入口使用同一个操作编号查询进度。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/22-release-bindings.svg" title="构建结果、发布目标、操作记录与运行实例之间的关联" data-thumbnail="/images/rd-platform-evolution/22-release-bindings.svg" data-sub-html="<h2>图 13：发布所需的记录与关联，字段为逻辑示意</h2>">
        
    </a><figcaption class="image-caption">图 13：发布所需的记录与关联，字段为逻辑示意</figcaption>
    </figure>
<p>发布记录需要保存以下关系：</p>
<table>
  <thead>
      <tr>
          <th>关系</th>
          <th>需要保存的内容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>代码与制品</td>
          <td>提交、真实构建任务、镜像 digest 和构建来源。</td>
      </tr>
      <tr>
          <td>制品与目标</td>
          <td>逻辑服务、目标环境、部署对象和配置身份。</td>
      </tr>
      <tr>
          <td>目标与流量</td>
          <td>Service 选择器与实例标签；入口还涉及 NLB 监听及后端集合。</td>
      </tr>
      <tr>
          <td>实例与观测</td>
          <td>实际实例范围、日志字段、指标模板和查询条件。</td>
      </tr>
      <tr>
          <td>操作与结果</td>
          <td>发布进度、实际目标和制品，以及对应的功能验证结果。</td>
      </tr>
  </tbody>
</table>
<p>镜像 digest 为构建产物提供确定身份。对于声明了预发布的服务，晋级正式环境时需要核对同一制品和兼容配置；重新构建后得到的新镜像，需要重新确认。没有预发布声明的服务，使用自身声明的发布流程。</p>
<p>配置也要能够识别。有的配置随镜像封装，有的由外部提供，操作记录需要说明本次使用哪份配置。</p>
<p>实例启动以后，还要确认 Service 选择器是否命中它，NLB 后端是否由正确控制器管理，个人实例是否误带共享入口绑定。否则进程正常运行，也可能接不到目标请求。</p>
<h3 id="72-超时与恢复">7.2. 超时与恢复</h3>
<p>发布请求超时，并不能确定部署动作失败。调用方可能只丢失了响应，Worker 仍然在更新目标。</p>
<figure><a class="lightgallery" href="/images/rd-platform-evolution/09-unknown-result.svg" title="发布响应丢失后，使用原操作编号查询并继续跟踪" data-thumbnail="/images/rd-platform-evolution/09-unknown-result.svg" data-sub-html="<h2>图 14：发布操作的查询与恢复</h2>">
        
    </a><figcaption class="image-caption">图 14：发布操作的查询与恢复</figcaption>
    </figure>
<p>调用方先查询原操作，核对目标、制品和当前进度，再决定是否需要后续处理。若直接新建发布，两次操作可能同时修改同一个目标。</p>
<p>提交入口还需要保留原操作的查询凭证。例如在提交前生成请求编号，平台接受后保存它与操作编号的关联。首个响应丢失时，调用方也能据此找回同一次发布。</p>
<p>平台内部还需要互斥、租约和执行代次。Worker 接管时更新执行身份，迟到的旧结果不能覆盖新状态；外部部署动作则需要继续核对实际目标，不能仅靠数据库状态判断已经停止。</p>
<p>研发平台由此承担持久执行、恢复与排障的责任。它不在用户业务请求的转发路径上，但新的发布依赖它正常工作。</p>
<h2 id="8-整条链路的验证">8. 整条链路的验证</h2>
<p>上面的机制最终需要在同一个任务上验证。以一套分支环境为例，验证过程可以分为：</p>
<ol>
<li>确认分支环境及必要依赖已经准备，目标版本与制品一致；</li>
<li>发起生成请求，检查入口识别的环境与实际后端；</li>
<li>沿 RPC 和消息检查 Dispatcher、Runtime 是否保持同一环境选择；</li>
<li>检查任务结束原因、产物与用户得到的版本；</li>
<li>用同一请求或任务编号查到入口和业务日志；</li>
<li>确认监控查询的是本次部署的实例，并验证目标功能。</li>
</ol>
<p>流水线完成、Pod Ready、HTTP 成功、任务结束分别验证其中的一部分。保存它们对应的对象和结果，才能说明这次变更实际运行到了哪一步。</p>]]></description></item></channel></rss>