SQL 不是回避 DevOps 的理由


【编者的话】DevOps 实践火遍大江南北,从一线大厂扩展推行至传统软件企业,但涉及数据库时都多少有些为难。顾介绍相关实践,以供大家借鉴。

有朋友最近告诉笔者,“我们做不了 DevOps,我们使用了关系数据库。“笔者听罢,差点从椅子上摔倒。这个观点在很多层面都是错误的。

“你不了解我的处境!”他拒绝道。“DevOps 意味着我们需要更高频率的部署我们软件的发布版本!现在我们不能控制部署,我们一年只有很少的时间可以干这个。”

笔者追问朋友当前的部署流程。

“每隔几个月我们会有新版发布”,他解释道。“将其部署到生产线需要很多工作。由于我们使用 SQL,部署过程会是这样:第一,我们踢掉所有用户,关闭应用服务。下一步,DBA 修改数据库的 schema。一旦他们的工作完成,新版软件就安装完毕,可以使用了。整个过程花费好几个小时,所以我们一般安排在周末进行……(这让人讨厌)。如果升级失败,我们不得不使用备份的磁带,将一切恢复到初始状态,然后再试一次。”

他总结道,“仅是规划一次升级部署都需要好几周的协商。我们通常都很难达成共识,这也是为什么我们需要在周末来处理升级。每隔几个月做一次都很痛苦,要面对来自方方面面的压力!我听说一些公司每天能做好几次软件发布。如果我们那样做,我们的应用系统就会因为升级而一直处于下线状态了!”

这里有很多没有说清楚的地方。让我先澄清一些误解。之后再讨论采用一些技术手段如何让部署更容易。

首先,DevOps 不是一项技术,它是一套方法论。 关于 DevOps 最精确的定义是,应用敏捷、精益的方法到从源码到部署的全过程。它是为了更快速的交付价值,或者说是用短的时间让一个需求特性从想法转变为生产系统的功能。更高频率的发布意味着减少已经就绪的特性等待上线的时间。

DevOps 不需要禁止任何特殊的数据库技术。反过来,任何技术也不是采用和不采用 DevOps 的理由,就像使用特殊的语言不是阻碍将敏捷应有到项目中的理由。SQL 是常见的理由,但也是经不起推敲的。

很理解 DevOps 在一些人头脑里是如何与少 SQL 数据库关联在一起。在2000年到2010年左右,投资和实践 DevOps 的公司大部分是大型网站,它们的共识是推行 NoSQL 数据库(键值存储)。然而两者的联系造成了因果的混淆。这些公司还向员工免费提供丰盛的午餐,但我们都同意这不是 DevOps 的前置条件。

第二,我不确定一些人是否可以做 DevOps。 你可以使用 DevOps 的技术、方法或其他。也就是说,人们已经很频繁的使用这个词,以致于我想我已经无法去有效讨论这个问题。

朋友们有一个共识。“看”,他困惑道,“这些部署是危险的。坦白讲,每次我们实施的时候都在拿公司的数据,甚至我的工作在冒险。每隔几个月做一次已经压力很大了,还需要更频繁的做吗?不!先生,那是不负责任!”

在之前的专栏中提到(小批量原则),当一件事情是高风险的时候,我们会倾向于更少的去尝试做它。但与常识相悖的是,这种做法恰恰会增加风险。下次当你做危险的事情时,你应该更多的实践,针对周围环境而累积的变化会越来越大,越容易造成因为未知的副作用导致失败。相应的,DevOps 采取的做法是,危险的事情应该更频繁的去做。高频次的做法会尽早暴露大大小小的问题,而不是每年一次的大扫除。它强迫我们将过程自动化,自动测试整个过程,让过程更流畅,这样风险会降低。它让人们更多参与到实践中。实践出精品。不再回避我们恐惧的东西,帮助我们跨越危险,克服挑战。像任何经历过术后恢复的人一样,我们不断的实践直到它不再痛苦为止。

部署有一些固定成本。原则上,你需要逐渐将这些固定成本降低。如果增加部署频率而不降低固定成本,则会伤害业务,这是不负责任的。

该文章其余部分描述了两种实践,让你即使使用 SQL 也可以快速发布。实施它们需要开发者、质控、运维走出各自的筒仓,彼此协作,这也是 DevOps 的实质。这样的结果是让业务更平滑、更少痛苦、更少压力。

技术1:自动 Schema 更新

在这个古老的方法论中,当团队中的专家(通常为 DBA)手工修改 Schema 时,任何 Schema 的变化都需要整个应用系统关闭。如果你希望实现自动化部署,你需要自动化实现 Schema 的更新。

因此,应用需要管理 Schema。每个 Schema 的版本都需要记录。应用从 Schema V1 版开始。这个值存在数据库中(大致为 1 列的表,该列字段存储为 1)。当应用启动时,它需要知道兼容 V1 的 Schema,如果它在数据库中找不到这个版本,它可以拒绝运行。

为了自动更新 Schema,下一版本的软件发布时需要知道自己要求 V2 版的 Schema,知道 SQL 命令会将 V1版的 Schema 升级到 V2版。在启动时,它看到版本为 1,然后运行响应的 Schema 升级命名,并将存在数据库中的版本号升级为 2,之后继续运行应用。

执行这个操作的软件通常还有一个存储 SQL Schema 升级命令表。这些命令以数组存储,索引 n 表示其是支持从 Vn-1 升级到 Vn。这样,某个版本找不到也没有关系,软件能将数据库恢复到任意需要的 Schema 版本。 实际上,如果发现有没有初始化的数据库(如在测试环境中),它会循环执行若干 Schema 升级,直到获取到最新的版本。不是每个软件的发布都需要升级 Schema,因此隔离 Schema 和软件的版本号。

已经有一些开源的和商业的系统实践了这个过程。他们的一些产品比其他人更复杂,如支持多语言、多数据库、错误处理及是否支持回滚等。从一项关于“SQL 自动化更新”的研究中,你会发现更多信息。我最熟悉的是面向.NET 代码的开源项目 Mayflower 和面向 Go 的 Goose

Schema 的修改会导致数据库被锁定几分钟甚至几个小时。这会引起应用系统的超时甚至故障。现代 SQL 数据库已经减少此类问题了,需要感谢无锁 Schema 升级和在线重建索引的特性。这些特性可以在现在 SQL 数据库产品中找到,包括开源产品如 MariaDB、MySQL、PostgresSQL。查阅相关文档,以了解操作时的注意事项。

一旦你的软件使用了这些技术,采用 CI(持续集成)会变得非常容易。你的自动化测试环境可以包含测试使用旧的 Schema 的数据库并升级它,然后运行新版软件。你的 Schema 升级过程可以在发布到生产环境前,进行数百次的测试。这会为该过程带来更多的信心,减少 Schema 升级的危险,解藕 DBA 本人直接参与到升级。他们可以享受原本属于他们的周末了。

我对此技术最感兴趣的是,你的 Schema 可以按代码一起管理了。大幅减少了控制台上的手工操作,可以在开发环境、测试环境、UAT(用户验收测试)环境、生产环境中不断演练整个过程。你可以重复执行这个过程,完善它。既然它是代码,你可以应用代码管理的实践和软件工程的计算去管理它。

技术2:针对多 Schema 编码

在分布式计算环境中,如何升级数据库的 Schema 呢?

典型的网站系统前端是负载均衡服务,后端运行相同软件的多个实例或副本。每个实例都承担一部分 HTTP 负载,也访问相同的数据库实例。

如果软件和数据库 Schema 紧密耦合,而软件升级又需要数据升级 Schema 时,操作会变得难以操作。如果你先改变 Schema,应用实例会故障或者至少会因此产生混乱;你需要尽可能快的升级实例,但是其实你已经输了这场游戏,因为你已经在承受怒火。

为什么不限升级应用实例呢?!悲剧的是,如果你逐一升级应用实例,最新升级的实例会不能启动,因为它检测到的是错误的 Schema。只有 Schema 与应用系统匹配时,你才能应用拉起,而也在这个时候停机时间才结束。

最直接的解决方案是,无视现实规律,改变数据库的 Schema,并同时升级所有应用实例的版本。如果条件真的允许你这样做,一切真可以这样搞定。

遗憾的是,ACM 有套策略来应对现实法则,同样大部分企业雇主也类似。这也是为什么传统方法是关闭整个应用,升级所有东西,然后再重新上线。这种最佳实践延续直至 IEEE 的朋友核算了如何暂停的过程。

无论是违法现实规律还是计划的停机,每次停机都会引入更大的问题:你完成了许多的独立系统变更,但只有让系统重新运行后才知道它们是否都还正常。同样也不知道这些累计的改变会引发什么样的破坏。

大爆炸式升级变更是很危险的,而每次仅做一个变更,并验证这个变更,就会降低风险。如果一次执行多个变更,此时碰到问题,必须逐一运行程序以确定哪个变更引起的问题。如果每次只做一个变更,即使碰到问题,排查会很简单,也很容易回退变更。

即使是像谷歌这样的拥有极其复杂成熟的测试技术和方法,如果不能理解预发布环境和生产环境间细微的差别也可能会导致部署失败。他们采用“金丝雀”发布形式:升级其中一个实例,然后观察是否运行是否正常,如果没有问题,则继续逐渐而有序的升级其余的实例。这其实不是一项测试技术,而是应对测试不充分而采取的保障策略。需要说明的是,不是因为测试人员不优秀,而是没有人是完美的。金丝雀技术目前已经是产业界流行的最佳实践,而且已经被嵌入到 Kubernetes 系统中。(金丝雀一词来源于煤矿业中金丝雀。煤矿矿工通常会带着鸟进矿,一般会选择金丝雀,因为这种鸟对人体有害的气体非常敏感,如果在矿里有鸟死亡,则被视为危险的信号,提醒大家要撤离。)

由于软件引起的问题通常与特定的 Schema 相关,解决方案就是松耦合。在设计阶段去解除耦合,让软件可以同时支持多个版本的 Schema,以实现独立激活或者回滚。

第一阶段是编码不再假定表中的字段。用 SQL 术语说就是,SELECT 语句需要精确指出需要的字段,而不是泛泛的使用 SELECT *。如果需要使用 SELECT *,则不要假定字段按特定的顺序排列。 LAST_NAME 也许今天是第三个字段,但明天就不一定了。

基于这个原则,从 Schema 中删除字段也会简单一些。新版部署后不使用相关字段,一切都可以正常运转。在所有实例都升级至新版后,再变更 Schema。实际上,冗余的字段可以先忽略,稍后再移除他们,甚至等到下次 Schema 变更时再进行清理工作。

添加字段也会变得简单,即在第一个实例部署前在 Schema 中添加新字段即可。如前所述,我们采用技术 1(应用管理自己的 Schema),部署一个新发布,它会修改 Schema,但不会使用这个新字段。由于合适的事务锁会控制住并行处理,第一个新升级的实例重启后会更新 Schema。如果有问题,则金丝雀会宕掉。你就可以修复软件,然后实验新的金丝雀。而回退 Schema 的变更也是一个可选的方案。

由于 Schema 和软件结构,开发者可以按需启动新字段。过去的升级需要考虑多个团队的需求后才能确定一个维护窗口,而现在流程解耦,所有相关方可以有序的工作,而不必相互锁定。

更多复杂的变更需要更多的规划。当拆分字段,移除字段,添加字段等时,收益才真正体现。

第一,软件必须按同时支持新旧 Schema 的需求进行编写,更为重要的是必须能处理变化阶段。假定你需要从存储人全名的字段迁移至拆分为几个单个字段(第一个名,中间名,最后的名,头衔等)。软件需要检查哪个字段存在,且可用。在数据库转换过程中,它必须可用正常运行,这个过程中两套字段都会存在。一旦两套字段都存在时,一个批处理任务会将全名拆解为多个部分,然后将原字段置为 NULL 值。代码需要处理这种特殊场景,即有些行已经完成转换,而有些还没有完成转换。

处理这个过程方案可称之为“五阶段在线 Schema 变更”。它有很多阶段,包括创建新字段,升级软件,迁移数据,移除旧字段。它又被成为McHenry 技术(详见《云系统管理实践》),或者叫《Expand/Contract in Release It!: Design and Deploy Production》)

在线 Schema 变更的五个阶段:
  1. 运行的代码读写旧 Schema,即从表、视图中选择需要的字段。这是初始状态。
  2. 扩展:Schema 被修改,即增加新字段,但不删除任何旧字段。没有代码需要变更。由于此时新字段没有被使用,所以如果需要回滚也不会很痛苦。
  3. 代码被修改以使用 Schema 中的新字段,并发布至生产环境。如果此时发生回滚,只需回滚至第2阶段。这个时候,执行数据转换操作。
  4. 签约:访问不再使用的旧字段的代码会被清除,且被发布至生产环境。如果此时发生回滚,只需回滚至第 3 阶段。
  5. 从 Schema 中移除旧的不再使用的字段。这个阶段如果发生罕见的回滚事件,则数据库可简单回滚至第 4 阶段。


这些技术足够处理在线分布式系统中大部分复杂的 Schema 变更。而且,每个变更都可以独立回滚。

针对特定场景阶段的数量可以裁剪。如果只是加字段,则阶段 5 可以忽略,因为没有需要移除的。四、五阶段可以合并或重叠。第 5 阶段可以合并至另一次 Schema 变更中的第 2 阶段。

基于这些技术,你可以处理最复杂的 Schema 变更而不会有停机时间。

总结

使用关系数据库并不妨碍推行 DevOps。自动化 Schema 管理加上一些开发原则就可以强化测试、缩短发布周期、降低商业风险。

自动化发布解放了我们。它将痛苦的、鸭梨山大、人工的升级过程,转换为常规事件,且没有事故。它减轻了商业风险,但更重要的是,创建了一个更可持续的工作环境。

当你可以充满自信的部署新版时,你就可以更高频率的去发布。以前需要等数周甚至数月的新特性可以更早接触用户。问题修复更快。安全漏洞也及时解决。从而使企业能为客户提供更多价值。

原文链接:SQL is No Excuse to Avoid DevOps(翻译:黄军雷

0 个评论

要回复文章请先登录注册