以下文章来源于说给开发游戏的你,作者小说君fp

第一段职业经历里,我追求「上线经验」而不得,但是又不太在意「上线经验」。

那时我认知简单,ego也很大,认为自己做的东西通过了思想实验,只是缺了一个线上大用户量验证边界作为收尾。

第二段职业经历里,我没有「上线经验」,而身边的同学几乎个个都有。

我发现我们在认知上确实存在「gap」,在推行一些技术决策的时候也遇到了阻力。因此我在对自己工作内容的安排上希望更贴近「线上」的状态,关注「上线驱动」会给我的做事心态带来怎么样的变化。

在几年前精品化的大背景下,不得不说 ——

  • 能上线的大型项目是少数,死于一次次内部评审的项目是大多数
  • 上线后吸到大用户量的游戏项目是少数,吸不到量的游戏项目是大多数
  • 吸到大用户量的游戏项目中,成功的游戏项目更是少之又少,不那么成功的游戏项目是大多数

有幸几乎完整参与了一款虽然不那么成功,但是用户量规模还不错的项目。

去年一年,项目先后在港澳台、日本、中国大陆上线,并稳定运维一年有余。

中国大陆上线当天接近百万玩家在线,也成了我个人商业游戏开发职业生涯的高光时刻。

被业务牵引着紧赶慢赶了四年,最近终于可以稍微空下来沉淀一下几年来的一些思考。

每每有阶段性的思考成果,我还会约业内的朋友进行一些深入的交流。

每一次深入交流,也让我对「上线经验」的意义本身产生了更多新的认知。

借本文这个机会,我尝试结构化地组织一下自己的思考 ——

关于如何认知「上线经验」,以及这次上线的经历带给我的,分享给大家。

「上线经验」的意义

个人层面,「上线经验」需要能转化为知识或技巧的一部分,夯实能力;

团队层面,具备「上线经验」的个人能力补齐团队缺失的部分,进而给业务和项目带来收益。

大型项目、大用户量检验的「上线经验」,可以或多或少在以下几个方面给团队带来收益:

  • 经过大型项目验证的生产流程与技术建设能力
  • 经过大规模DAU/PCU验证过的稳定上线运维能力
  • 线上问题的判断与应急处理
  • 更完整的技术视野、上线驱动的技术规划与推进落地能力

具体到对技术细节的影响,我用两个关键词来展开描述:

  1. 「规模」
  2. 「稳定」

「规模」

大部分技术方案只有暴露在真实用户的量级与行为下,才能验证正确性;

在正确性的基础之上,进而继续基于线上表现判断投入产出比及改进的方向。

「规模」既包括直接检验系统设计正确性的并发规模,还包括间接检验流程高效运转的数据规模(例如各类跟用户数量有关的离线流程与操作)。

在一定规模之上,所有的「简单事务」都会乘以一个跟用户规模有关的大系数,变成一个「复杂问题」。

从几个简单、具体的例子来看如何在前置设计中应对「规模」问题:

单点

  • 例如游戏中常见的做顶号处理的中心管理节点,驱动逻辑的中心节点等
  • 提升默认分片的重视程度。设计阶段就要妥善处理所有跟活跃账号量级有关的数据规模如何做分片
  • 关注长异步链路中的单点卡点。系统设计之初就尽量避免或留好hook

动静分离

  • 静态文件的分发交给CDN。一个简单的热更下发,就可以在1w PCU在线时打爆LB流量上限

低级、常见的代码逻辑错误,触发概率和后果都会被规模放大

  • 在做防御式编程的时候,更多留一些余地给未来的自己遇到错误从容处理的空间,包括逻辑的临时修复与线上影响数据的修复

配置的人肉介入程度

  • 以游戏服务端通用的supervisor进程管理为例,过于简单的进程启动参数设计,如果导致不同进程的启动命令出现显著区别,上线后会带来无法承受的技术方案切换成本以及运维人力
  • 运维编排流程的设计上,从简单的服务器选择、针对各角色的复杂流程、配置检查、更新等各方各面提前考虑各类线上规模庞大后可能会出现的意想不到的问题

几乎所有技术实现上的细节决策,在上线之后,经历过「用户规模」的考验,判断合理性与不合理性,都可以沉淀为个人能力与技术判断的一部分。

「稳定」

大部分游戏产品的性质决定了「上线」是一个关键节点。

上线之后,先有「规模」的考验,接下来是细水长流的「稳定」考验。

产品开发阶段,做技术方案的时候,就要充分考虑将来如何更好地应对「稳定」考验。

简单展开以下几点:

技术驱动的代码质量体系保证代码库及交付版本的质量

  • 代码库质量。各维度的编码规范、开发框架的约束、静态检查、类型检查与抽象解释
  • 保证交付版本的质量。在QA的自动化测试之外,通过各种层面上的断言式测试机制保证正确性

把容灾恢复机制建设放到高优先级位置上

正确性

  • 流程正确。关注游戏内的各主流程在容灾恢复后的正确性
  • 数据正确。包括自动化的容灾测试与数据层面的diff正确性校验
  • 逻辑正确。OBT前的生产环境小规模演练,线上环境验证容灾恢复流程等

自动化

  • 零人工介入。不需要额外的人工发起流程、日志确认、状态确认等
  • 高恢复效率。通过指标关注各类数据与状态的快速恢复

保证线上无重大技术事故

  • 重大技术事故通常有出现前兆,或者由小事故扩大影响产生
  • 线上紧急问题的处理与临时修复时,保持良好心态,避免操作变形
  • 技术驱动设计各类SOP,通过预案与规范化流程把临时问题转化为标准化问题求解

一些新的思考

做了十年游戏服务端,我关注的事情经历了三个阶段的变化:

  1. 初入门路。言必称的是架构、系统设计、容灾、去单点、无状态
  2. 陷入技术瓶颈。开始关注并提升开发者体验、深入静态代码分析与动态代码生成与注入
  3. 站在交付的视角上,经历了线上运维。开始关注服务端成本、稳定可控与运维友好的服务应该是怎么样的

四年前,前leader面试的时候问了我一个问题:

「你认为游戏服务端未来应该是怎样的?」

具体的回答我已经忘了,但是内容大致逃不脱前面两个阶段的关注点。

今天,我对这个「未来」的想象图景可以描绘得更加清晰。

假如继续从事游戏服务端架构工作,我会从三个方面去描绘游戏服务端的「未来」:

  1. 极致的资源利用曲线
  2. 「SLA」的追求
  3. 「低运维」

极致的资源利用曲线

典型的CPU资源占用率曲线:

打开网易新闻 查看更多图片

可以看出,在资源的利用上非常低效:

  • 大部分时间,资源超配,利用率不到30%
  • 小部分时间,资源的吃紧程度决定了资源的配额上限

服务端技术需要面向这条资源曲线做优化,让它不那么陡峭:

大时间尺度上,削峰为主

弹性资源容量

  • 构建更灵活的扩缩容粒度。基于中低配置机器工作;合理的进程角色与各角色进程数量
  • 低成本、分钟级、无感知、无损的动态扩缩容能力。不满足这四个条件,都无法称为「生产环境」级别的扩缩容能力

充分利用多核CPU

  • 逻辑分线程、计算密集操作的异步化
  • 并行化。ECS与并行化native接口,内存紧密排布

小时间尺度上,填谷为主

充分利用主线程闲时CPU

  • 尽量基于事件,而不是基于帧去驱动逻辑
  • 游戏业务特有的自驱逻辑放在人最少的时间跑
  • 闲时主动GC

打开网易新闻 查看更多图片

理想的资源利用曲线不仅需要计算侧的削峰填谷,还需要资源侧足够灵活的容量适配。

除此之外,还有以下注意点:

建立性能基准与全链路全场景压测

基于可以量化的指标做好运行时资源管理

  • 日志管理
  • 流量管理
  • 业务指标

内存不便宜。不要再拿「内存不值钱」作为懒于优化的借口了

  • 作为参考,阿里云32C128G机型比32C64G同CPU机型贵1/3

减少每一份额外CPU使用

  • 内外网的发包量级
  • 进程级的拷贝次数

必要的native层

  • 易错易阻断易死循环的逻辑放在脚本层
  • native层具备可感知到脚本层错误、阻断、死循环的能力,具备打断能力
  • 封闭计算密集逻辑
  • 关注异步死循环、合批、分帧

警惕复用的对象

关注没有与PCU成正比的指标

  • 特别是PCU下降时,没有随之下降的指标。潜在问题
  • 单位PCU峰值CPU核数,资源估算

我们经常能听到一种错误的观点 —— 服务器的资源成本远低于游戏开发中的其他成本。

这背后可能透露出三层信息:

  1. 对技术尤其是服务端技术没有足够的「敬畏」
  2. 对服务端技术建设各事项的优先级评估不准确
  3. 对稳定运营游戏的成本条目没有清晰的认知

这个说法充满了游戏开发野蛮生长时代的特色,但是在降本增效、精细化运营的大环境下已经不再适用。

让每一部分成本都有所值是每个技术团队目标的一部分。

「SLA」的追求

SLA指应用面向用户承诺的服务级别,同时也是服务端技术团队面向其他职能承诺的服务级别。

在各类云环境已经足够成熟的当下,业务依赖云环境服务商提供的SLA已经足够作为面向用户的承诺下限。但是对于技术来说,需要做的是基于云商的SLA下限,追求更高标准的SLA。

高标准的SLA达成路径有三个关键点:

  1. 配置化、灵活完善的服务治理
  2. 保证正确性、高度自动化的容灾恢复机制
  3. 完善的应用系统可观测度覆盖

依次展开一下。

1. 服务治理是服务端应用框架的基础

核心服务治理能力

服务发现

  • 基于任意第三方组件实现服务的进入感知

服务探活

  • 避免探活决策依赖单一的组件。网络原因容易导致预期外的大量服务失活
  • 一致性存储与探活逻辑分离。一致性存储依赖成熟的外部组件实现;探活借助集群内角色实现。gossip protocol是个成熟方案备选

可动态调整策略的负载均衡机制

  • 线上总会遇到各类异常,需要人工巡检识别到并介入调整

完善的降级、限流、熔断、自恢复机制

  • 粗暴的熔断容易引起雪崩,需要自恢复或手动恢复机制介入

分层服务治理

native层服务治理

  • 最小化集群角色数量。只需要保留必要的native角色,比如gate、game、消息转发与db代理
  • 同构的应用角色,并在同构的应用角色之上构建业务层的各类异构服务

业务框架层服务治理

  • 配置化定义服务。通过配置描述服务的能力、分级(可用性保证)、隔离性等
  • 默认的分片实现。全局服务总可以基于玩家id划分分片实例
  • 无状态不是容灾恢复和弹性扩缩容的唯一解

2. 容灾的正确性保证与自动化恢复

部署方案的稳定性与容灾容错

  • 多可用区部署策略
  • 依赖的外部服务做好分区部署

提前应对所有不稳定的要素

云环境基建的不稳定

VM故障。突然停机、计划重启、热迁移

  • 服务恢复机制
  • 防雪崩的设计

网络环境/ACL的不稳定。闪断、超时、重连

  • 简化连接拓扑管理

外部IO的不稳定

  • 核心服务的闪断、重连
  • 突发的高延迟或网络不通

服务自身的不稳定

  • 服务容灾
  • 数据容灾

各类突发事件

  • 完备的流程、演练、预案、SOP

保证正确性

  • 流程正确
  • 状态正确

自动化容灾恢复

  • 去中心化的服务掉线感知
  • 去单点的服务重新拉起与恢复

3. 系统观测度覆盖

  • 饱和式的监控、统计、打点、报警、日志、链路追踪机制建设
  • 基于业务侧的指标而不是机器层面的指标做决策
  • 监控、巡检、报警,针对各类服务的时延、流量/拥塞、错误/异常、负载等关键信号
  • 关注运行时各层次的inspect/debug/REPL能力
  • 全要素保活机制

「低运维」

「运维」是我经历这次「上线」后最大的认知改变。

对于稳定、长线运营的游戏服务端来说,「运维」是「持续交付」的基础。

「运维」相关框架与工作流的设计,从研发早期就需要重点关注。

  • 如果介入较晚,很有可能历经多次测试后,引擎框架陷入「改不动」的窘境,只能将就着上线;
  • 如果不经过良好设计,那会在线上运营阶段耗费大量人力来弥补基建与流程的不足。

除了线上运维工作流之外,「低运维」关注如何在研发阶段就从基建与框架上构建低成本运维的游戏服服务端。

展开看下「低」如何理解:

「低」环境认知与部署成本

通过「统一」降低心智成本

  • 统一的机器/VM规格
  • 统一的隔离环境
  • 开发、测试、预发布、小流量、生产,部署发布统一

通过「统一」减少不一致风险

  • 统一的服务运行环境与依赖
  • 统一的静态配置与注入配置

「低」运维投入

研发设计运维流程

  • 发布、外放、更新、维护的运维流程编排
  • 设计具备产品意识的流程。把其他技术当作用户

减少协作成本损耗

  • 复杂且不灵活的配置方案,会带来极高的运维风险及人力成本投
  • 尽早评估在庞大集群规模下的潜在问题与风险
  • VM粒度、组粒度、集群粒度的配置项设计,动静分离,与整体架构息息相关
  • 配置的维护与协作方式,不是简单的一句「SA来搞定就好了」,所有的配置都需要研发兜底
  • 研发需要深度介入设计,关注落地
  • 考虑几百上千组服务器,配置文件通过自动流程渲染下发,仍然会有出错的可能,需要比较深度的介入人肉校验

「低」业务感知

维护无感知,对业务的影响降到最小

  • 临时的扩缩容维护、临时停服维护、常规更新维护等

配置的重要性

  • 足够多的、覆盖全面的应用配置
  • 业务无感知的、流程化变更的动态管理。配置源变更到业务感知到的链路,保证足够短且风险可控

「技术建设总是短期内被低估成本,长期内被低估收益」。

游戏行业的「项目制」属性决定了,我们经常在做重复的「项目」层面的业务实现,而少有超越「项目」的「技术建设」。

在构建想象图景的过程中,我们看到了一些细节,比如:

  • 灵活的动态扩缩容
  • 完善的服务治理、容灾与自动化恢复
  • 低运维甚至无运维,精细发布与持续交付

等等,都指向了一个关键词 —— 「云原生」。

游戏服务端的「云原生」适配,就是一个典型的容易被低估成本,且容易被低估收益的「技术建设」。

对于游戏服务端来说,「云原生」不是一个虚无缥缈的概念,确实会带来实实在在的好处。

接下来我通过两个方面分享一下对游戏服务端「云原生」的思考:

  1. 标准化服务单元
  2. DevOps

标准化服务单元

标准化的服务单元,对游戏服务端的架构提出了两点要求:

  1. 面向「服务单元」做抽象
  2. 基于「标准化运行环境」开发、构建、部署、交付

「服务单元」,可以简单理解为将整体系统在不同层面上,划分为松耦合、可独立发布部署、可独立横向扩缩容的单元为用户提供服务。

「云原生」解决方案中,「服务单元」通常会映射为「微服务」。但是对于游戏服务端来说,微服务不是云原生的唯一解。

以前,我纠结于有状态与无状态,纠结于游戏的服务器形态上的划分。过去一段经历中,有幸参与并实践了游戏的微服务化,产生了一些新的思考:

  • 游戏的后端,不适合按无状态服务去实现,更不能粗暴地划分为有状态服务
  • 当作一个native组件集群,更适合游戏服务端的属性
  • 不需要盲目追求游戏服务端的微服务架构 —— 与通常的理解不同,其实发展到目前的游戏服务器,或多或少已经不再算是单体应用

「服务单元」更多是从交付与部署的粒度上去拆解整体服务,并不特指微服务。划分粒度决定了开发与交付的速度,以及对线上服务的影响范围。

  • 交付粒度上,需要考虑针对单个「服务单元」如何做版本控制
  • 部署粒度上,需要考虑如何渐进式发布与部署,让用户及其他系统感知最小

打开网易新闻 查看更多图片

基于同构的native应用角色层,构建异构的业务「服务单元」,在我看来,是比「微服务」更适用于游戏服务端的解。

异构的业务「服务单元」,初阶可以实现为共享主线程的逻辑实体形式,进阶可以实现为更高度抽象的actor形式。

接下来看标准化的运行环境。

需要明确的是,技术团队交付的是解决方案而不是一个个孤立的版本。

解决方案理应可以部署在任何环境,包括各类开发环境,各类私有云、公有云或混合云环境等等。

在行业发展的当前阶段,基于容器和容器编排设计应用已经成为事实上的标准化解决方案。

回顾一下容器和容器编排能带给我们什么:

  • 资源抽象。基于OS、VM及各类云环境做抽象,只需要关注应用交付而不用关注基建、机器环境、网络访问规则与配置以及云环境特定的资源等
  • 集群层面,网络拓扑,内部拓扑,外网
  • 运行时隔离。提供比进程粒度更粗,比VM粒度更细的隔离抽象
  • 规定交付标准。基于容器,进行整体发布而不是只交付一个二进制版本包
  • 跨环境的发布与部署。架构适配各类云环境,可部署、可运行,并不限定为某个公司内部平台整套技术栈与基础设施

为什么需要基于容器和容器编排做构建发布?

  • 基于容器的应用发布始终是完整的,基于VM发布则会把整体流程拆分为机器初始化与版本发布。基于容器构建应用版本可以进行更敏捷的发布、部署与回退
  • 构建与发布结合在一起,全流程运维环境统一。不再需要传统意义上的打包上传阶段,内外网环境的构建发布可以做到真正统一
  • 构建可观测系统时,可以聚焦于应用层的指标,忽略机器层面的指标
  • 实现资源的隔离与最大化利用

DevOps

「DevOps」的前提之一是「CI/CD」。

CI,持续集成

  • 基于成熟的Jenkins和gitlab,大部分团队都很容易认知,也很容易实现

CD,持续交付与持续部署

  • 游戏行业大部分公司和项目仍然处于「DO分离」的研发运维合作模式,难以达到「CD」的状态

打开网易新闻 查看更多图片

如之前所说,技术交付的不仅是一个可执行文件包,而是一整套持续开发迭代、持续集成、持续交付的解决方案。

什么是「持续」?一个版本从开发完毕,涉及最少的人,以最快的速度和对外部最小的影响上线到生产环境。

接下来从游戏服务端受DevOps理念影响最大的两个方面,来看看如何在架构设计时进行DevOps适配:

  1. 部署
  2. 更新

部署

常规的部署流程包括运维资源的规划部署与筹备,具体的事务包括开服与线上扩缩容。

部署流程上,游戏行业经历了三个阶段:

DO分离

  • DevOps的反面,SA是业务研发的工具人
  • 研发发起需求,SA执行需求
  • 极高的沟通成本

DO合作

相比于DO分离,在两个切面上有所提升:

  1. 研发流程编排由研发与SA协同设计流程,研发执行流程并确认流程参数
  2. 资源筹备与部署研发深度介入并对结果负责,而不是交付一个版本

研发与运维仍然有各自的职责边界

DevOps

  • 研发自运维

我们可以从以下方面审视自己的工作流所处的阶段:

  1. 涉及内外网公共服的流程谁设计,谁发起,参数如何确认
  2. 开发环境、预发布环境与生产环境的差异度
  3. 游戏服务器「更新」都有哪些技术职能介入

这三个阶段的演化,是「版本交付」与「部署」的职能边界从清晰到模糊的过程。

设计部署与扩缩容友好的应用架构需要在以下方面做好适配:

做好进程角色规划

  • 确定的native进程角色,最小角色数量,明确的角色职责
  • 同角色各实例在静态配置层面等价

角色适配通用的服务概念

提供服务即暴露端口

  • 集群理应暴露端口的量级需要是可控的

连接发起方向需要谨慎设计

  • 角色互连接的连接方向考虑容灾场景
  • 考虑角色的相对动态性与相对静态性,偏动态的角色总是主动感知偏静态的角色并发起维护连接

部署和扩容单元最小化与标准化

  • 统一部署和扩容层面的最小单元。避免出现按集群部署,按节点扩容的设计
  • 扩缩容的最小单元,不能有独立的静态配置。否则扩缩容时的配置变更本身会带来极大风险
  • 面相生产环境设计与验证横向扩缩容。开发环境的扩缩容与生产环境的扩缩容是两个维度的问题
  • 降低业务和用户侧对扩缩容的感知

更新

游戏作为一种互联网应用,「更新」的理想形态是「渐进式」的、是「持续性」的。

但是至今,仍然有大量的新项目在计划用「stop-the-world」的机制应对每一次常规更新。技术决策的依据也令人相当哭笑不得 —— 一款几年前上线的「头部项目」就是这么做的。

对游戏服务端「更新」的思考,在我看来可以简单分为四个阶段:

1. 对「更新」的认知局限在「stop-the-world」,并对新的更新形式产生「畏惧」。

打开网易新闻 查看更多图片

2. 理解、认知并以实践新技术的心态落地「不停服更新」,最简单的形式是蓝绿部署

3. 通过实际运维经验,反思「不停服更新」的成本与业务需求的适配度

4. 从云原生开发部署一体化的视角去重新设计并实践「持续更新」

在网络游戏单机化的趋势下,之前我认为适配「持续更新」的业务需求是减少了。但是经过思考之后,我认为反而增加了。

「持续更新」带来的基准提升是让玩家感知不到游戏的维护与更新。

从业务视角来说,单机化网络游戏一个典型的更新策略是大版本更新前会招募测试玩家。并且:

  • 测试服与正式服同时开放,多版本同时运营
  • 测试玩家在测试服与正式服的数据互通

「持续更新」可以应对这两点业务需求带来的挑战。

简单聊聊落地路径。

所有的维护与发布策略,技术层面都可以简化为:

  • 同一服务同时部署了新旧两个版本
  • 在一段时间窗口内,新旧两个版本的服务同时面向玩家
  • 生产环境流量逐步从全量旧版本过渡到部分小流量新版本,最终过渡到全量新版本

要做到适配云原生的「持续更新」,需要从架构设计之初就深度定制:

确定的、与业务无关的集群角色划分

  • 前文提到的服务治理能力

各角色本身「必须」具备横向扩容能力

  • 同一服务新旧版本同时部署发布的必备机制

外置与集群内置的流量管理机制

  • 生产环境流量在新旧服务间的切换

不同版本间实现互相兼容

  • 包括协议层面的兼容与数据层面的兼容
  • 通过一定的策略标记流量只要经过了新版本就无法会退

业务框架与上层系统的适配

  • 无法处理的系统在「持续更新」期间关闭即可

「上线经验」对于个人来说,有递进的四层意义:

  1. 履历,成为简历中可以写出来的一段项目经历
  2. 路径,增加了可以依赖的依据与技术储备,在做方案的时候可以有更多的技术自信
  3. 思考,获得从研发到上线到稳定运维多视角深度思考沉淀的契机
  4. 认知,可以梳理并完善各类上线前技术决策的预估收益与上线后实际收益的偏差模型,可以基于这个模型继续做新的技术探索

没「上线经验」的时候,我喜欢在技术方案review的时候说,不要过于依赖上线经验;

有「上线经验」了之后,我发现我确实没办法做到默认信任没有经过线上验证的技术方案。

这不是屁股决定脑袋,是认知发生了变化。

不过,这也不是绝对。

技术方案的选择具备时空属性 —— 在特定时间特定场合下,技术方案的决策是有意义、可参考的;过了时效,不具备可参考价值。

在做全新的技术方案时,「上线经验」不是权威,不应该成为技术判断与决策的绊脚石,一切以事实为依据。

我们经常听到的一个说法 ——

「某个方案上线验证了那就一定要这么做」

更合适的说法是:某个方案是这么做的且上线验证了,把其中有意义的点和没有意义的点通过线上验证的结果拆出来,并且提出更合理的需要改进的点,综合起来,才是「上线经验」的意义。

「上线经验」对于行业来说,同样意义重大。

对工业界来说,将「上线经验」与背后的知识快速铺开,是整个行业进入工业化的意义。

以好莱坞为例,早期一部大型工业级影视作品的诞生,中间各环节各流程的关键角色在行业内开枝散叶,快速生产出类似体量的一部又一部更优秀的影视作品,这是行业维度看工业化的意义所在。

具体到项目与业务来说,以有限、确定的成本,减少试错,把资源投在需要试错与探索的方向上,无疑会增加项目的成功率。

敬畏「上线经验」。

祝大家的项目都可以顺利上线。

祝大家都能从上线中收获新的思考,提升认知。

经历一次又一次的项目开发与「上线」历练,不论是做业务系统,还是基建或框架,还是架构设计,都要沉淀属于自己的「设计语言」与「技术审美」。

共勉!