1.共享数据库的消亡,为什么不能使用更大的机器
虽然并不常见,但目前有数十亿条记录达到万亿字节范围的数据库并不少见。那么有什么问题呢?问题不在于特定数据库引擎的技术限制。而是组织将所有东西放入单个数据库。例如,在我工作过的一家公司,数据库中有超过30,000多张表,视图和存储过程的数量更多,这还没说触发器的数量。
没有哪个数据库工具能够处理如此数量的表。GUI工具每次连接数据库时总会导致该工具卡顿几分钟,同时它会在短时间内读取模式描述。没有人清楚数据库内部发生了什么,但是数据和围绕它的流程对组织的成功至关重要。最后的结果:要么停滞不前,要么开始将数据库分成可管理的组成部分。
那是多年前的事了,行业格局已经发生了改变。今天,当我们考虑数据时,有更多问题需要考虑,例如:
- 属于欧洲公民的个人数据,这意味着与他们相关的任何数据也必须实际存储于欧盟,并受GDPR规则的约束。
- 医疗保健信息(直接或间接),需要遵循一套全新的规则(例如,HIPAA、HITECH或ENISA规则)。
数据隐私和出处等问题更为重要,比如能够审计和分析谁访问了某个特定数据项,以及为什么它在许多领域都是一个硬性要求。组织中的所有信息都驻留在一个存储桶中的概念已不再可行。
另一个重要的巨变是常见的架构模式。我们现在不再使用单一的庞大系统来管理组织中的一切内容,而是将系统分解成更小的组件。这些组件有不同的需要和需求,按不同的时间表发布,使用不同的技术。当你想更改自己的系统时,尝试在所有这些团队之间进行协调的巨大开销是你想要在系统中进行更改的一个大障碍。跨这么多团队和组件进行协调的成本太高了。
通常的想法是使用独立应用程序数据库,而非单个共享数据库。这是一个更大的架构概念的重要组成部分。通常会在微服务和面向服务的体系结构中碰到这种情况。
2.应用程序数据库作为实现决策
从单个共享数据库迁移到一组应用程序数据库之间一个最重要的区别是没有拆分共享数据库。数据库级别的适当分离是关键。一组共享数据库也会有完全相同的协调问题,因为厨房里有太多的厨师。应用程序数据库被正确地分离,就能够为每个任务选择最佳的数据库引擎,本地化更改,减少沟通更改的开销。这种方法的缺点是在生产中要支持更多系统。
我们来更深入地讨论下共享数据库与应用程序数据库之间的区别。很容易弄错,例如图1所示:
图1:从单个共享数据库到多个(仍然共享)数据库的错误迁移路径
虽然共享数据库是你实现的,因为没有其他选择,但应用程序数据库是内部选择,除了应用程序没有人能访问。与面向对象编程的封装有相同的含义,使用私有变量隐藏状态,非常确定的是,应用程序数据库是应用程序之外任何事物都不必关心的问题。我对此深有同感。
编写代码时,直接使用其他对象的私有状态是错误的。如果违反了不变性,未来的维护和开发都会变复杂。这已经被大量事实敲定,因此大多数开发人员几乎本能地不会这么做。直接访问另一个应用程序的数据库也会发生完全相同的状况,但却非常常见。
在某些情况下,我对数据库中所有表和列的名称进行了加密,以表明你不应该查看我的数据库。应用程序数据库应该仅仅是应用程序的内部关注点。这个想法很简单。如果应用程序之外的任何实体需要一些数据,则需要向应用程序请求。他们不应该直接进入应用程序数据库获取。这是询问“你在和谁说话”与查看他们所有的交流记录及留言之间的区别。理论上,这是一个好方法,但是需要考虑到,你的应用程序不仅是系统的应用程序,还必须与生态系统的其他部分集成。问题是你如何做到这一点。
如果这里描述的系统听起来很熟悉,那是因为你可能以前听说过。它最初是DCOM/COBRA系统的一部分,后来被称为面向服务的体系架构,现在被称为微服务。
假设在我们的系统中处理发货的应用程序需要访问一些客户数据来完成其任务。如何获得这些数据?使用共享数据库时,直接查询客户表。当负责客户应用程序的团队需要添加列或重构数据时,你的系统就会遭到破坏。它们之间没有封装或分离。直接依赖另一个团队的实现细节的方式会导致破坏、停滞和不断增加的复杂性。
3.使用全局数据
或者,发货应用程序可以(通过已发布的服务接口)请求拥有客户数据的应用程序以获取所需的详细信息。这通常是通过从一个应用程序到另一个应用程序的RCP调用来完成。问题是,如此一来就在两个应用程序之间建立了牢固的联系。如果客户的应用程序因维护而停机,则运输应用程序将无法工作。再加上几十个这样的应用程序及其相互依赖关系,你就有可能陷入僵局。我们需要考虑一种更好的方法来处理这种情况。
我的建议是从另一个方向着手整个过程。发货应用程序不必查询客户应用程序的相关数据,而是进行相反的操作。作为客户应用程序服务接口的一部分,完全可以决定要向组织的其他部分公开什么样的信息。
需要注意的是,发布的数据绝对是服务契约的一部分。不提供对数据库的直接访问。应用程序应该向外界发布其数据。可以是上传到FTP站点或GraphQL端点的每日CSV文件,以选择两种截然不同的技术和语义。
我在FTP上包含了CSV,以特别表明数据共享的方式是无关紧要的。重要的是,有一种从应用程序发布数据的既定方式,因为这种架构风格的一个关键方面是不必在需要的时候查询数据。相反,我们将其摄取到自己的系统中。我想很明显,为什么发货应用程序不会打开一个FTP连接到客户的每日CSV转储文件以查找详细信息。同样的,它也不应该将查询GraphQL端点作为其常规例程的一部分。
相反,我们有一个既定的机制,通过该机制发布客户的数据(客户应用程序已向组织的其他部分公开)。这由系统中的其它应用程序摄取,当他们需要查询客户的详细信息时,可以从自己的系统中进行查询。如图2所示:
图2:客户应用程序发布数据以供运输应用程序使用
在每个应用程序中,数据可以以不同的方式存储和表示。在每种情况下,都是最适合他们的。
发布应用程序还可以以他们选择的任何方式处理数据。数据库和数据发布方式之间的服务边界允许自由修改内部细节,而无需与外部系统协调。
另一个选择是采用两阶段的流程,如图3所示。客户应用程序不必将其更新发送给发货应用程序,而是将其发送到组织数据湖。通过这种方式,每个应用程序将希望公开的数据发送到一个中心位置。其他应用程序可以将需要的数据从数据湖复制到自己的数据库中。
图3:每个应用程序发布数据到数据湖并拉取数据到各应用程序
最终结果是一个共享数据的系统,但是没有应用程序和服务之间的时间依赖关系。它还确保了不同团队和系统之间的边界。只要发布的接口保持不变,就不需要协调增加复杂性。
4.实践中的几个建议
我们深入探讨关于如何应用这种体系架构方法的一些具体建议。可以通过在服务总线上发出事件或发布每日文件来全局发布数据。可以发布特定场景的数据,例如从客户数据库到发货数据库的ETL流程。只要有适当的边界,局部的方法的改变将对整体的影响度很低。
这种操作方式只在需要引用数据或对与一致性无关的数据做出决策时有效。如果需要对数据进行更改或协调更改,则此方法不适用。一致性无关紧要的一个很好的例子就是根据客户的ID查找他们的名字,如果我们有旧名字,那不是什么大问题。很快就会自行修复,我们不会根据客户的名字来做决定。同时,我们可以在应用程序范围内完全本地运行所有的计算和任务,这是一个很大的优势。
当我们需要做出决定或修改数据时,一致性很重要。例如,在发货场景中,如果要收取超重费,需要确保客户账户中有足够的资金。在这种情况下,我们并不拥有账户中的资金,不能对自己的数据进行操作。如此一来,需要向客户发起应用程序申请,要求扣除这些资金,如果资金不足,则报告错误。注意,如果客户无法付款,更合理的结果应该是:发货操作失败。
应用程序不应该再部署到单个服务器甚至单个数据中心。如今,在边缘系统上运行应用程序(如移动应用程序或物联网设备)已经非常常见。将所有这些数据推送到自己的系统中可能会导致存储不可承受的巨量数据。数据封装和仅公开希望公开的细节这种架构风格在这个场景中发挥得非常好。
无需将所有信息复制到中心位置,而是将数据存储在边缘,并从边缘设备接收足够的数据,以便能够做出决策并操作系统的全局状态。除其他优点外,这种方法让用户可以掌控他们所有的数据,我认为这是一个主要的优点
5.写在最后
在架构中使用应用程序数据库和显式数据发布有几个原因。首先,它意味着操作是以本地资源和最少的协调运行的。反过来,意味着这些操作更快、更可靠。其次,它减少了整个系统的协调开销,这表明可以根据需要独立部署和更改每个应用程序。
最后,它意味着可以独立地为每个场景选择最佳选项。可以为每个选项选择最好的品种,而不是迎合最低公分母。例如,可以使用文档数据库来存储发货清单,而将历史数据放入数据湖中。
每个应用程序都是独立的,彼此隔离,可以为每个场景做出最佳的技术选择,而不必考虑任何全局约束。其结果是一个更易修改的系统,由更小的组件组成(因而更容易理解),并且更加敏捷。