C21

第一个云上应用

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 第一个云上应用要交付什么最小闭环

一家零售店准备把商品订购服务放到云上。顾客在手机上选择商品,点击提交订单,系统返回订单号;店员稍后查看订单并安排发货。本课只研究其中的订单 API:接收创建订单请求,检查基本信息,把订单保存到数据库,再返回结果。支付、库存扣减、优惠券和物流先作为边界外的系统,不在本课展开。这样仍然可以看清云上应用最重要的连接关系。

在本机运行程序时,开发者常把“进程没有退出”当成成功。换到云上,顾客还要能够找到入口,请求还要经过网络规则,应用还要拿到正确配置并访问数据库。程序正常启动,却可能因为监听地址不对而无法接收请求;接口返回成功,却可能因为数据只写在临时目录里而丢失。学习第一个应用,就是学习怎样把这些条件连成可检查的链路。

云提供可以按需取得的计算、网络和存储资源,但资源就绪不等于业务就绪。平台可能已经完成实例创建,订单服务却仍在等待数据库连接;负载均衡入口可能有地址,后端却没有健康实例。我们需要分别说明平台做了什么、应用做了什么,以及顾客最终获得了什么。只有能够区分这些层次,控制台里的绿色状态才有明确含义。

本课的完成标准是:顾客通过 HTTPS 发送创建订单请求,应用在持久数据库中保存订单,返回可查询的订单号;应用重启后,订单仍能查到;遇到失败时,能用请求标识找到相关证据。这里的“完成”包括可访问、可保存、可解释 3 个方面。它不承诺所有故障都不会发生,而是要求成功和失败都具有可以验证的行为。

我们采用适合入门的结构:托管入口接收外部请求,后面运行无状态的订单应用,应用连接独立的数据库。开始可以只有 1 个应用实例,随后用 2 个实例理解流量分配和重启。托管表示平台替我们承担部分运行工作,例如维护入口服务;具体责任仍需查看服务约定。即使使用托管数据库,表结构、访问权限和备份恢复要求也需要应用团队决定。

订单 API 的入口、应用与数据边界
订单 API 的入口、应用与数据边界

2 计算、网络、存储与身份的最小集合

应用实例是正在运行的程序副本,可以来自虚拟机、容器或应用运行平台。本课不要求先掌握这些部署形式的内部差别。只需知道实例需要计算和内存,有启动命令,会监听某个端口,也可能退出或被替换。同一个程序可以启动多个实例,但这些实例不会自动共享内存,也不会自动同步各自磁盘上的文件。

部署制品是用于启动应用的确定版本,例如带版本号的程序包或容器镜像。配置则是随环境变化的内容,例如监听端口、数据库地址和日志级别。把制品与配置分开,才能让同一个版本进入测试环境和生产环境。数据库密码属于秘密,不应写进制品、代码仓库或普通日志。可以通过平台的秘密配置能力交给应用,并限定哪些身份能够读取。

入口是用户访问服务的稳定地址,通常对应域名和 HTTPS。域名负责把容易记忆的名称解析到入口,HTTPS 用于保护传输并验证服务端身份,负载均衡则将请求交给可用的后端。这些能力在一些平台中组合提供,在另一些平台中分别配置。理解职责比记住控制台按钮更有用:解析正确、证书有效、后端可达,分别需要证据。

API 是应用对外约定的请求与响应。我们的创建订单接口为 POST /orders,请求包含商品编号、数量和顾客引用,成功时返回 201 Created 与订单号;查询接口为 GET /orders/{id}。应用必须检查数量是否为正、字段是否完整,不能因为请求已经通过云入口就相信其中的数据。网络可达性和业务输入有效性是不同的边界。

持久化表示数据应在应用实例退出后继续存在。订单记录放在数据库中,实例本地目录只保存可以重新生成的临时内容。无状态应用并非完全不使用内存,而是不把恢复业务所必需的唯一数据留在某个实例中。收到请求后用内存组装响应没有问题;只在内存里保存订单,随后返回“创建成功”,就无法满足重启后的查询要求。

还要区分控制面与数据面。在控制台修改实例数量、设置访问规则、发布版本,属于管理资源的控制面操作;顾客创建订单、应用读取数据库,属于处理业务的数据面活动。能够登录控制台不意味着顾客请求一定能通过;业务正常也不意味着每次配置修改都已生效。排查时先辨认正在讨论哪类操作,可以减少跨层猜测。

3 可迁移部署:镜像、配置与密钥注入

请求链路可以按时间展开:客户端解析域名,与入口建立 HTTPS 连接;入口选择健康实例;实例解析请求并校验字段;应用获得数据库连接,执行写入并等待提交;最后组织响应,沿原路返回。每个阶段都可能花费时间,也可能拒绝请求。把整个过程只称为“网络慢”会抹掉差异,按阶段观察才知道下一步应该查什么。

应用监听的端口与入口对外端口不必相同。例如顾客访问 HTTPS 的 443 端口,入口转发到应用的 8080 端口。应用需要监听平台能够访问的地址,通常不能只绑定本机回环地址 127.0.0.1。允许入口访问应用端口,也不等于应向整个互联网开放数据库端口。网络规则应描述谁访问谁、使用什么协议和端口,而不是简单地全部放行。

数据库连接既需要网络路径,也需要身份和权限。地址写对,但访问规则拒绝连接,请求仍会失败;网络连通,但密码错误,同样不能写订单。应用身份应只获得完成订单操作所需的权限,避免使用可以管理所有数据库的账户。入门时可以先列出应用需要的读写动作,再把这些动作映射到权限,而不是为了方便直接授予全部权限。

健康检查用于帮助入口判断是否继续向实例分配流量。存活检查关注程序是否需要重启;就绪检查关注它现在能否接收业务请求。启动过程尚未完成时,应暂时不接单。检查也需要控制成本,不能每隔几秒执行昂贵查询。若所有实例都因同一个数据库短暂故障被反复重启,重启本身可能加重连接压力,因此检查策略需要结合故障范围设计。

超时给等待设置边界。例如客户端最多等待 5 秒,入口等待后端 4 秒,应用访问数据库最多使用其中的 2 秒,应用还需留出处理和返回时间。数字仅作示例,实际应根据延迟测量确定。设置超时不会让操作自动撤销:客户端停止等待时,数据库可能已经完成提交。因此“没有收到成功响应”不能直接推导为“订单没有创建”。

部署后的验证也应覆盖整条链路。只访问健康端点,只能说明它定义的检查条件满足;创建订单并按订单号查询,才能检验基本业务路径。再次重启应用后查询同一订单,可以进一步检验持久化假设。这样的验证把抽象概念变成可重复的观察,并为后续变更提供共同的最低通过标准。

创建订单从请求到持久提交的顺序
创建订单从请求到持久提交的顺序

4 订单演示:下单到查询的链路拼装

顾客点击提交时,应用先检查请求格式和商品数量。对于缺少必要字段的请求,可以返回 400 类错误并说明可以修正的原因;对于数据库暂时不可用,应返回符合接口约定的服务错误。不要把每种失败都包装成 200 加一句“失败”,否则客户端和监控很难识别结果;也不要把数据库密码、完整连接串或内部堆栈直接返回给顾客。

创建成功的边界应放在数据库确认提交之后。应用可以先生成订单号,再执行写入,但只有生成号码并不代表订单已经保存。数据库事务提交意味着这次写入按数据库约定完成,并不代表支付、扣库存和发货也已经完成。本课的响应只表达“订单记录已创建”。用清晰状态表达后续业务进度,可以避免把多个系统的完成条件混在一起。

最容易误解的情况发生在提交之后:数据库写入成功,返回响应时连接却中断。顾客看到超时,再点提交,就可能产生重复订单。请求超时和服务端失败不是等价关系,客户端此时面对的是结果未知。简单地提示“请重试”会把网络问题转化成业务重复,尤其在促销期间,重复订单还可能影响库存与客服处理。

可以为每次购买意图附带幂等键。客户端为同一次提交生成稳定键,重试时继续使用;应用在数据库中将这个键与订单结果关联,并利用唯一约束处理并发请求。再次收到相同键和相同内容时,返回已有结果。若相同键对应不同内容,应按约定拒绝。仅在应用内存中记住键,无法覆盖实例重启或请求落到另一个实例的情况。

幂等需要规定作用范围和保留时间。例如键可限定在某个顾客的创建订单接口内,并覆盖客户端允许重试的时间范围。不能要求顾客每次重试都生成新键,那样系统无法识别同一次意图。入门实现的重点是理解稳定标识、持久关联和并发唯一性 3 个条件;具体事务写法与错误处理还要结合所用数据库验证。

增加实例主要提升应用层处理能力,也可以减少单个实例退出的影响。但数据库连接数、写入能力和入口限制不会随之自动增长。假设每个实例最多使用 10 个数据库连接,2 个实例的配置上限就是 20 个;扩到 8 个后变为 80 个。连接上限不是实际持续占用量,却能揭示扩容可能把压力转移到哪里。扩容前应检查下游余量。

5 动手:一键创建与健康检查

动手前先写下 5 项信息:应用版本、启动命令、监听端口、数据库连接配置名称、健康端点。下面假设程序读取 PORT,健康端点为 /health/ready,测试入口为 https://orders.example.test。这是说明用途的地址,需要换成实际测试域名。先在隔离的测试环境练习,测试数据与正式订单分开,避免排查时误删真实记录。

在控制台里创建运行服务时,把制品版本填到部署配置,把端口设置为程序实际监听的值,把秘密配置关联到应用身份。数据库应位于应用可以访问的网络范围,并通过规则只允许必要来源连接。检查入口的目标端口是否与应用端口一致。若平台自动处理其中某些步骤,也应知道它替你建立了哪条关系,以便以后定位失败。

应用启动后,先检查启动日志是否包含版本、监听端口和就绪状态转换。日志可以写“数据库配置已加载”,不能输出密码来证明读取成功。实例状态显示运行中,只说明运行平台看到了进程或容器;还需要确认就绪检查通过。启动失败时先读取具体错误,反复点击重新部署通常不会修正缺失配置或错误启动命令。

下面使用 curl 查看响应状态与响应头。测试创建请求包含幂等键,请求体仅用于说明接口约定,不代表完整零售订单模型。

curl -i https://orders.example.test/health/ready

curl -i -X POST https://orders.example.test/orders \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: demo-order-001' \
  -d '{"customerRef":"demo-user","sku":"SKU-101","quantity":1}'

curl -i https://orders.example.test/orders/ORDER_ID

把 ORDER_ID 换成创建响应里的订单号,再执行查询。记录状态码、订单号和请求标识,确认返回的商品与数量正确。随后用相同幂等键和相同内容再次提交,应按接口约定返回同一个订单结果;响应状态未必与首次相同,需要事先定义。再换新键提交,应产生新的购买意图,而不是被错误地合并。

最后通过平台提供的重启或替换实例功能触发应用重新启动,等待恢复就绪,再查询先前订单。订单仍在,说明这次实验中的数据没有依赖被替换实例的本地状态,但不能据此证明备份可恢复或所有故障都能承受。将实验结论限制在实际验证范围内,是写出可靠上线记录的基础。

6 观测:请求追踪、错误率、冷启动

观测首先要回答顾客是否能完成创建订单。请求量告诉我们流量规模,错误率告诉我们失败比例,延迟告诉我们等待时间。延迟应同时关注中位数和较高分位,例如 p95 表示约 95% 的请求耗时不超过该值。平均值可能掩盖少量很慢的请求;请求量很低时,分位值也容易波动,需要结合样本量解释。

应用日志适合记录单次请求的上下文。建议包含时间与时区、请求标识、接口路径模板、状态码、耗时、实例标识和应用版本。创建完成后可记录订单号,便于关联业务记录。路径最好写成 /orders/{id} 这样的模板用于统计,避免每个订单号形成独立类别。请求标识应在入口与应用间按约定传递,并在不可信输入处限制长度和字符。

不要为了方便把完整请求体、认证令牌或顾客联系方式全部写进日志。排查创建失败通常只需要商品引用、错误类别、请求标识和必要的脱敏信息。详细异常保留在受控的内部日志中,对外提供稳定错误码和可供客服定位的标识。日志权限和保留时间也属于设计内容,否则日志会成为难以管理的数据副本。

指标用于看总体变化,日志用于追查具体事件;链路追踪则把入口、应用和数据库调用的耗时关联起来。初学阶段即使尚未部署完整追踪系统,也可以记录数据库操作耗时,并让入口日志与应用日志共享请求标识。没有同一请求的关联依据,仅凭时间接近就判断它们属于同一次调用,证据仍然偏弱。

资源信号帮助解释业务变化:CPU 利用率、内存、实例重启次数、就绪实例数、数据库连接使用量和查询延迟都值得观察。CPU 很低并不能证明服务健康,应用可能正在等待数据库;CPU 很高也不能单独证明需要扩容,可能是错误循环或异常请求。先确认顾客受到的影响,再用资源信号缩小原因范围。

从业务信号关联入口、应用与数据库证据
从业务信号关联入口、应用与数据库证据

告警应指向需要行动的现象。例如持续出现创建订单失败,且请求量达到有意义的样本规模,可以触发排查;单次测试产生的预期 400 不应与服务不可用混为一谈。为告警写清观察窗口、阈值、负责人和首个检查入口。阈值先依据测试与业务要求设定,上线后再根据正常波动调整,不必在首次部署时追求复杂规则。

7 常见坑:把演示当生产、密钥进镜像、无租户隔离

第 1 个坑是把能访问首页当成订单功能可用。首页可能由入口直接提供,也可能根本不访问数据库。检查应包含真实但可识别的测试订单,并确认它能被查询。健康检查与业务验证各自有用途:前者需要轻量、频繁,后者用于验证关键路径,不能因为其中某项通过就省略另一个层面的检查。

第 2 个坑是为了排障开放所有网络来源,或把管理员密码交给应用。这会扩大影响范围,也让真正缺失的规则被掩盖。更好的做法是逐段确认入口到应用、应用到数据库的连通性,并只补上缺失的必要路径。连通测试成功后还要检查身份权限,因为“能够建立连接”和“允许写入订单”并非同一结论。

第 3 个坑是把订单、上传文件和会话都留在实例本地,然后直接增加副本。顾客的下一次请求可能落到其他实例,本来偶尔出现的问题就会变成随机失败。先识别哪些状态必须跨请求保存,再选择持久存储。第一个订单 API 可以通过独立数据库解决核心问题,不必为了显得完整而立刻加入多种缓存、队列和复杂编排。

第 4 个坑是无限重试与无限等待。依赖已经拥堵时,立即反复重试会制造更多请求。可以先设置明确超时,仅对适合重试的错误进行有限次数的重试,并在重试间增加等待;创建订单还必须配合幂等设计。输入错误不会因为重试而变正确,权限错误也不应靠高频重试掩盖。重试策略必须依据失败类别制定。

第 5 个坑是没有记录版本就直接发布,出错后也不知道恢复哪个制品。发布前保留版本与配置变更记录,发布后观察关键接口指标,并准备恢复上一可用版本。恢复应用版本不一定能够撤销数据库结构变化,所以首次练习宜选择向后兼容的小变更。备份也需要恢复验证,控制台出现“备份成功”并不等于团队已掌握恢复步骤。

8 案例:首周上线后的权限与成本失控

一次演练中,团队为了接待活动流量,把应用从 2 个实例扩到 6 个。发布后不久,创建订单的 p95 延迟从 300 毫秒升到 4 秒,部分请求返回服务错误。入口仍然能接收请求,应用 CPU 也没有明显升高。若只看实例数量与 CPU,可能得出“再加实例”的结论;但等待资源的程序并不一定忙于计算。

团队查看应用日志,发现失败请求多数停留在获取数据库连接阶段。数据库连接使用量接近当前可用上限,而每个实例的连接池都配置得较大。实例增加后,总连接需求上升;同时新实例启动建立连接,又与已有业务争抢资源。这里的关键不是“云扩容没有作用”,而是扩容改变了应用对共享数据库的需求。

处置时,团队先限制继续扩容,并把连接池配置调整到数据库可承受的总预算内,保留管理与其他服务需要的余量。随后观察获取连接耗时、创建订单错误率和数据库负载是否回落。如果减小连接池后排队仍然过长,还需研究慢查询、事务时间和数据库容量。配置调整应分阶段进行,避免同时更改太多变量而失去判断依据。

另一次演练刻意在订单提交后中断返回连接。客户端得到超时,数据库里却已存在订单。使用新幂等键重试会创建另一条记录,使用原键重试则得到原订单。这个实验说明,连接错误只描述通信结果;确认业务结果还需要查询持久记录。客服处理也应围绕购买意图和订单号,避免要求顾客反复盲点提交。

恢复后还需要确认是否存在重复订单、是否有长期等待的请求,以及告警是否及时发现问题。服务错误率恢复只是技术现象,业务记录可能仍需核对。事故记录应分别写明顾客影响、触发变化、技术原因和后续改进,并对尚未证明的解释保留不确定性。这样后来的同学可以复用排查方法,而不是只记住某个配置数字。

9 排查案例:首发证据链

假设测试人员报告“10:05 提交订单失败”。先把描述变成可定位的事件:记录日期、时区、接口、客户端观察到的状态、请求标识和影响范围。不要只留下“页面转圈”这样的现象,也不要立刻判断数据库宕机。可以先确认是否仅某个顾客失败、是否所有创建请求受影响,以及查询订单是否正常,这些差异会改变排查方向。

沿请求方向检查。若入口没有对应请求,查看客户端地址、域名解析和连接过程;若入口记录了请求但没有可用后端,查看就绪状态与最近部署;若应用已接收请求,继续检查校验、连接获取、数据库执行与提交阶段。某层没有日志也可能源于采集缺失,所以“没有找到”应写成观察结果,不能直接当作请求从未到达的证明。

本例找到以下证据:入口在 10:05:12 收到请求 req-7a,转发到实例 app-3;应用同标识日志显示已通过参数校验,随后等待连接 2 秒并超时;数据库指标显示当时连接使用量接近上限;部署记录显示 10:03 实例数增加。证据共同支持“连接容量不足导致该请求失败”,但还不能说明所有数据库错误都由同一原因造成。

接下来验证假设:调整总连接预算后,保持相近测试负载,观察连接等待和错误率是否下降;必要时比较未受影响接口的表现。变更前后要记录时间、配置值和流量条件。只有“修改以后好了”仍然较弱,因为流量也可能恰好下降。可比较的负载与相关指标,让处置效果更有说服力。

按请求经过的边界逐段建立排查证据
按请求经过的边界逐段建立排查证据

排查记录可以采用固定句式:“在某时间窗口,观察到某范围的请求出现某现象;请求标识把入口与应用记录关联;某指标支持某假设;实施某项变更后,在某条件下结果如何。”同时写下未验证的部分,例如尚未测试更高峰值、尚未演练数据库整体不可用。证据链的价值在于让其他人能检查推理,并明确下一次实验该补哪块空白。

放进方案里

为零售订单 API 写方案时,先限定目标:支持创建和查询订单,成功响应表示订单记录已持久提交,支付与发货另行设计。写清预计请求量、可接受延迟、失败时的用户提示和测试环境范围。暂时没有可靠流量预测时,标明假设并安排压测验证,不把猜测写成系统已经具备的能力。

结构部分画出 HTTPS 入口、应用实例和数据库,标注访问方向、端口与责任人。说明应用使用哪个制品版本,配置和秘密如何提供,实例替换后哪些数据必须保留。明确数据库仅允许必要来源访问,应用身份仅获得订单读写权限。实例数与连接池应放在同一张容量说明中,给数据库保留必要余量。

接口部分规定创建成功、输入错误与依赖故障的响应语义,定义幂等键的作用范围、重复请求行为和保留时间。将“超时后结果未知”写入客户端处理约定,让客户端使用原键重试或查询结果。不要将数据库提交等同于支付完成,也不要用增加应用副本替代订单唯一性约束。

运维部分列出版本、请求标识、状态码、耗时和订单号等观测字段,规定脱敏、日志访问与保留要求。验收包含创建后查询、重复提交、应用重启后查询,以及依赖故障下的超时表现。准备上一可用版本的恢复步骤,并明确数据库变更的兼容要求。最后记录压测结果、尚未覆盖的故障和下一次验证日期,使方案能够随着实际证据修订。

上线清单再补 3 条可验收条目,全部绑定「第一个云上应用」本身:第一,发布产物与配置分离——镜像摘要、环境变量清单、密钥引用方式写进变更单,回滚时能在 10 分钟内切回上一制品并保留同一数据库;第二,数据面验收——用同一幂等键连续提交 2 次创建,只产生 1 条订单,查询接口在应用重启后仍能读到该记录;第三,依赖故障演练——人为拉长数据库响应或断开安全组,客户端在约定超时内收到明确失败或「结果未知」提示,并走查询路径确认,而不是把超时当成成功。把这 3 条的负责人、证据截图位置和下一次复验日期写进方案附件,避免把「能部署」误当成「能交付」。

10 小结:走向主流云对比(C22)

第一个云上应用的核心,是把可运行的程序变成可访问、可保存、可解释的服务。上线前可以逐项检查:

自测时,尝试解释 3 个现象:健康端点成功但创建订单失败;客户端超时但数据库已有订单;扩容后 CPU 很低而延迟升高。能分别指出需要补充的证据,比立即给出某个配置答案更接近可靠的工程判断。

下一课转向 C22 主流云对比:把本章验收过的结论带进后续主题,写成可检查的方案约束。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务