编者按:一般来说,大型共享数据库的管理会遇到不少麻烦,比如维护费用、剩余垃圾等等,但做好一些事情之后,这些麻烦就会不那么让人头疼了。
以下为译文:
数据库是业务系统的基础,不仅提供数据访问,还需要在线数据库定义语言、高可用性、存档、异地磁带轮换和管理、在线备份、访问控制、监视和操作支持,以及复制到远程数据中心。
通常,数据库规模庞大,功能强大且成本昂贵。即便是集团级别的企业一般也只能负担得起一个大型数据库。此外,大型主机计算机系统及其数据库非常昂贵,常常需要专门的工程师来构建能够满足企业需求的应用程序。因此,很多公司的数据库都会面临一些相同的问题:每个应用程序都希望共享相同的数据库,且数据库内容纳了许多垃圾。“公地的悲剧”在数据库中也层出不穷。因此,在本文中,我们就来探讨一下如何在多个应用程序之间合理地共享数据库,同时确保数据库不会被文档、照片、音频文件和电影等非关系型的数据占据。
独享数据库与共享数据库
在公司招收若干数据库管理员,并建立起数据中心之后,公司的每个新应用程序都将使用这个数据库。这也无可厚非,因为一般公司都无力再负担另一个大型的主机。很快,数据管理员就需要不断设计各种的表结构,同时各种新功能和新应用程序也将源源不断地添加到这个共享的系统。随着公司的发展,这个数据库变得越来越重要,数据库管理员必须确保数据库 24 小时无间断运行。一旦数据库出现任何问题,公司的业务也会立即受到影响。公司的所有数据都集中在这个共享数据库。数据库管理员需要确保所有的数据都安全,万无一失。刚开始的时候,每个应用程序都使用各自的表,服务各自的业务。然而,随着应用程序之间的交互越来越多,程序员很快就会放弃繁琐的消息机制或 API 调用,转而直接读取其他应用程序的表。这样有什么坏处?各个应用程序和表之间的关系会越来越复杂,纠缠不清。通过一个事务更新多个应用程序的表的现象也会非常普遍。这时,数据库的整洁性也会荡然无存。
非关系型的数据应该保存在何处?
非关系型的数据,例如文档、照片、音频以及视频资料也需要妥善地保存起来。程序员常常利用 SQL 的 Blob 类型来存储大量的数据。虽然数据库是保存这类数据的一个好地方,但从数据库本身来看,这种做法弊端很多。对数据库的使用者来说,将大量的数据塞入数据省时又省心,不仅可以轻松地存储数据,而且还有备份,保证了高可用性。此外,这些数据的更新也可以通过事务保证一致性。然而,对于数据库管理员来说,这就是噩梦!随着大量数据的“入驻”,数据库会变得臃肿不堪。将文档、照片、视频这类的不可变数据存储在数据库底层昂贵的存储上,简直就是浪费。此外,从数据库中提取这类庞大的数据并不是一件易事。也许我们可以利用扫描后的纸质文档和其他介质的不可变的性质来帮助我们完成这一操作。你可以为文档分配 128 位 UUID,并将文档存储在其他位置,而数据库只需在相关的记录中保存该标识符。不过,很快你就会发现,将这些数据转移到更便宜的存储介质上时会遇到很多困难。首先,无法保证数据更新的事务性。通常,你需要通过以下方式更新这些数据:- 通过数据库的事务 1 更新关系系统,即将 UUID-X 对象插入应用程序的表中,并通过另一列管理不可变对象的状态。
- 使用 UUID-X 将不可变对象复制到新的存储中。
- 通过数据库的事务 2 更新对象的状态(表示应用程序可以使用不可变对象了)。
如果在第 1 步和第 2 步之间出现问题,那就会导致表中出现不完整的数据,应用程序虽然可以读取数据,但外部存储中却没有实际的文件。与之类似,如果第2 步和第 3 步之间出现错误,就会导,外部存储中的不可变对象就会变成永远无法访问的“垃圾”。这个问题的解决方法是,使用另一个表来记录正在进行的插入或者删除。至少,这个表的状态与应用程序表中的插入或删除操作的状态是同步的。而且,正在进行的插入或删除应该带有时间戳,这样万一出现了失败的插入,你可以通过时间戳来判断是否已经过了足够长的时间,从而决定是否应该进行清理。这样你的数据库就相对稳定了。另一个可能出现的问题是,过了几个月或几年后,存储不可变数据的 blob 即将达到容量上限。为了在多个存储中不可变数据,你不得不修改数据库中所有保存了 UUID 的表,为它们添加一列来记录存储库的 ID。结果,你会发现你不知道数据库的哪些表存储了不可变对象!因为各个应用程序在添加不可变对象的时候,并没有彼此协调。这个问题的解决方法就是,专门建立一个表来管理不可变对象,并通过一个专门的模块来封装更新操作。这样只要新的应用程序遵守这个规则,采用间接的方式来使用不可变对象即可。但是,那些依然在直接访问不可变对象的旧应用程序就只能听之任之了,因为几乎不可能把所有的直接访问都找出来并改掉。你可以通过这种间接的手段,跨数据中心复制数据,甚至可以将不可变的 Blob存储迁移到新的数据中心。
分割系统
软件工程中最大的问题之一就是解耦合。我们的系统包含成千上万的软件工程师倾注大量心血编写的代码,这些代码相互交织,又互依互存。然而,很多时候,共享宝贵的数据库会让我们的系统深陷巨大的泥潭。一旦各个应用程序之间开始互相访问表,就很难将它们隔离开来。- 禁止跨应用程序访问表。应用程序可以保留表的只读副本,并在表的“主人”更新数据时,异步更新这些副本。而其他应用程序只可访问这些副本。
通常,这些工作都需要系统架构师来推动,由他们从整体角度出发,设计各个应用程序的基础架构。很多时候,系统的整体架构设计会与实际的工作发生冲突,公司领导需要高度重视公司文化的转变与培养。在达成目标时成功给予公平合理的奖赏,并在构建新功能时优先考虑解耦合,避免解耦合成为团队前进的阻力。从某些方面来看,分割一个大型的应用程序要比将两个遗留系统以解耦合的方式合并在一起简单得多。在收购另一家公司时,他们的应用程序基于完全不同的公司文化,对最基本的概念(比如客户)都有不同的理解,如果将这些应用程序的元数据直接融合到你的数据库中,那么势必引发巨大的混乱。分割巨大的应用程序就像规划一座城市的道路一样,如何能够像古代长安一样,规划出整齐划一又四通八达的街道,这本身就是一项巨大的挑战。一不小心,城市中心就会被一座座摩天大楼割裂,道路崎岖,交通堵塞。
数据库的扩张
随着公司业务的发展,数据库必将不断增长。即使将 Blob 等大型数据存储在其他地方,实现各个应用程序之间的解耦合,相关的数据仍然在同一个数据库中。- 向上扩展:购买更多更好的硬件,扩展数据库的底层设施。
- 向外扩展:尝试向外扩展数据库,通过多台计算机组成的集群来构建更大规模的数据库。
规模扩大意味着保存的数据更多,处理的事务也更多。应用程序是在平台上开发出来的,而数据库是平台的主要组成部分。将某个大型应用程序移至新数据库的难度非常大,且风险极高。数据库中有许多微妙的方面,会对应用程序大量的代码产生影响,比如:使用非标准数据库功能。每个数据库供应商都会提供一些专属的非标准功能。在大多数情况下,代码中会存在大量使用这些非标准功能的代码,而且经过长年累月的修改和重构,这些代码已经深深扎根于代码库中,而最初的作者早已离职,根本没办法找出当初这样做的原因。
并发语义。多版本并发控制、可序列化性、可重复读取以及读取已提交数据这些并发模型之间的细微差别并不是无中生有。有关这方面的讨论层出不穷,在大型应用程序保证并发语义的一致性非常关键。
乐观并发控制与悲观并发控制。乐观并发控制假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自的数据。如果发生冲突,则事务中止。大多数时候,这种做法更有效率。在悲观并发控制中,如果一个事务执行的操作读某行数据应用了锁,那么只有当这个事务释放锁,其他事务才能够执行与该锁冲突的操作。尽管悲观并发控制的代价更高,但这种方式可以防止陷入由于冲突而反复提交失败的困境。虽然乐观和悲观之间的选择并不会影响应用程序所做更改的正确性,但数据库行为更改为超出应用程序预期的范围可能会引发很多性能问题。
数据库迁移的难度不容小觑。而将数据库从集中式系统转移到分布式系统,虽然规模的扩张毋庸置疑,但其性能肯定会受到某些影响。协调锁和并发将面临各种的性能挑战,而整个过度的过程也将充满艰险。因此,将应用程序迁移到分布式数据库上,需要耐心和洞察力。
总结
人们对于数据库往往是爱与痛并存。让每个应用程序独享数据库会导致公司内产生很多数据库,不仅会加重成本的负担,而且也会为数据库的操作和管理带来挑战。然而,共享数据库时,应用程序之间又面临解耦合与交互的问题。因此,我们需要谨慎地使用数据库:管理 Blob。创建一种形式化的机制来处理 Blob,即使目前你将这些数据保存在数据库中,以后也可以移至其他地方。
保证应用程序之间的解耦合,让每个应用程序都远离其他应用程序的表。
利用消息机制实现应用程序之间的交互。通过某种形式的异步消息将应用程序连接起来,结合应用程序的解耦合,可以降低数据库的迁移难度。
原文:https://pathelland.substack.com/p/if-all-you-have-is-a-database-everything
本文为 CSDN 翻译,转载请注明来源出处。
☞最高要价 8888元,小米 11 邀请函现身闲鱼;荣耀与微软签署全球 PC 合作协议;Xfce 4.16 发布|极客头条
☞前端诸神大战,Vue、React 依旧笑傲江湖
☞计算机巨星陨落!图灵奖得主 Edmund Clarke 因感染“新冠”逝世
☞Github 超 20000 Star,最火开源视频库 FFmpeg 这 20 年!
☞跨平台将终结
☞曾被“劝退”的 C++ 20 正式发布!
☞最令人讨厌的编程语言:C++ Java 上榜
☞Rust 2020 调查报告出炉,95%的开发者吐槽Rust难学