访谈

  下载本文的PDF版本 PDF

与布鲁斯·林赛的对话

为失败而设计可能是成功的关键。

摄影:汤姆·厄普顿

如果您正在寻找数据库管理系统设计方面的专家,那么很少有人比IBM院士布鲁斯·林赛更合格。实际上,早在关系数据库管理系统 (RDBMS) 出现之前,他就已经参与了 RDBMS 的架构设计。1978 年,他刚从加州大学伯克利分校研究生院毕业,获得计算机科学博士学位,就加入了 IBM 的圣何塞研究实验室,那里的研究人员当时正在研究后来成为 IBM 的 SQL 和 DB2 数据库产品的基础。从那时起,林赛就一直在 RDBMS 的发展中发挥着指导作用。

在 20 世纪 80 年代后期,他帮助定义了 DRDA(分布式关系数据库架构)协议,后来成为 Starburst 的首席架构师,Starburst 是一种可扩展的数据库系统,最终成为 IBM 在 Unix、Windows 和 Linux 上的 DB2 的查询优化器和解释器。林赛开发了数据库扩展器的概念,该概念将多媒体数据(图像、语音和音频)视为标准关系数据库的扩展对象,并且可以使用标准 SQL(结构化查询语言)进行查询。如今,他仍然在 IBM 阿尔马登研究中心的数据管理实验室深入工作,帮助创建下一代数据库管理产品。

本月我们的采访者是 Unix “Bourne Shell” 的发明者史蒂夫·伯恩。他曾在思科系统、太阳微系统、数字设备和硅谷图形公司担任高级工程管理职位 20 年,现在是位于加利福尼亚州门洛帕克的风险投资合伙企业 El Dorado Ventures 的首席技术官。在他职业生涯的早期,他曾在贝尔实验室担任第七版 Unix 团队的成员九年。在那里,他设计了 Unix 命令语言(“Bourne Shell”),该语言用于 Unix 编程环境中的脚本编写,并且编写了 ADB 调试器工具。伯恩毕业于伦敦国王学院,获得数学学位,并在英国剑桥大学三一学院获得数学博士学位。

史蒂夫·伯恩 我们不妨从这样一个想法开始:在检测到错误之前,您无法从错误中恢复。

布鲁斯·林赛 让我们稍微思考一下错误是如何发生的——它们发生在系统的所有不同级别,从 alpha 粒子在您的内存中放电电容器,到火灾、洪水或暴动摧毁整个站点。从程序逻辑错误到磁盘返回来自错误扇区的数据,都会出错。您必须为系统所有不同级别的故障进行工程设计,从电路级别到数据库或文件系统等子系统,再到应用程序本身。

“为失败而设计”听起来像是一个糟糕的说法,但这确实是交付可靠和值得信赖的信息处理所必需的。

SB 当然,在编写代码和设计系统时,您必须具备的心态之一是:什么会崩溃?根据应用程序或软件的类型,有很多种方法可以解决这个问题。如果您正在编写 Microsoft Word 类型程序,那么您处理这个问题的方式可能与您设计心脏监护仪的方式不同。

BL 在心脏监护仪的情况下,您最好让心脏继续跳动,而在 Microsoft Word 的情况下,您可以给他们一个蓝屏,每个人都习惯了。

SB 但是在心脏监护仪的情况下,也很难询问用户是否想让心脏继续跳动,因为答案很明显,而在 Word 的情况下,在某些情况下您可以询问用户如何处理。

BL 您有时可以询问用户,尽管最好询问子系统它将如何做才能使其自身尽可能健康,或者根据其对情况的分析而放弃。

SB 您真的在考虑系统故障而不是用户错误吗?

BL 我不认为用户错误,例如不正确的输入类型等,是“失败”。这些是正常现象。我不认为编译器说你拼错了 goto 真的是一个错误。那是预料之中的。

SB 您对什么是错误和什么不是错误有什么看法?

BL 当使用的任何组件未交付预期交付的结果时,就会发生错误,无论是机器中出现奇偶校验问题的内存引用,还是没有返回结果的磁盘 I/O,还是抛出异常的子例程调用。“你让我做 X,但我没做。”

一个好的设计原则是:要么做你被告知要做的事情,要么告诉我们你没有做,以及为什么没有做,但不要做完全不同的事情。

SB 我猜另一个选择是,“我不打算这样做,所以找出是否有其他途径来获得您正在寻找的问题的答案,或者以您被要求的方式完成该功能。” 这是多案例情况。

BL 嗯,当然,这种情况一直出现。该功能是注册一个 UserID,当我们尝试注册时,我们发现它是重复的。在注册此 UserID 接口级别之上的下一个级别将不得不考虑该怎么做。它可以说,“对不起,我们的系统只允许一个布鲁斯,你不能在我们的系统中。请永远离开,”或者“你想改名为史蒂夫吗?”

SB 当您设计一个系统(而不是一个简单的应用程序)时,该系统非常复杂,可能非常庞大,并且涉及一些规模问题,您如何为失败而设计?

BL 当然,您采取医疗行业的立场,即在发生故障时不要造成伤害。也就是说,如果您不确定自己在做什么,请不要贸然前进。另一种说法是快速失败,我在这里说的是主要维护持久状态的系统。如果您不真正了解哪里出了问题,那么尝试前进是非常危险的。因此,快速失败的想法,并从您精心维护的冗余中拾起碎片,已经非常成功。

从我自己的数据库系统领域来看,您可以谈论数据模型的优美性及其功能和能力来完成工作,并使您能够轻松编写应用程序和存储大量数据等等;但归根结底,这些系统的成功归功于它们的可靠性。在系统维护数据、不丢失数据的可靠性足以获得用户信任之前,没有人会将关键数据放入这些系统中。

这些系统中采用的方法基本上是维护两本书的副本。一个我们称之为恢复日志,它是书的真实副本。那是按发生顺序发生的所有事情。然后是数据库的表示,该表示针对“当前”的访问和更新进行了优化。

数据库系统能够保留数据的最新提交状态,尽管处理器、存储和通信发生故障,再加上重要的事务、原子性和持久性原则,使得这些系统足够可靠,人们信任它们来保存关键信息,没有这些信息,他们会非常迷茫和困惑。SB 我们可以在这里探讨的一件事是我们在工具包中用于错误检测的技术。

BL 故障处理始终始于故障检测——最常见的是通过在系统中使用某种冗余,无论是奇偶校验、代码中的健全性检查,我们可能会花费 10% 的代码行来检查我们的状态一致性,以查看我们是否应该进入错误处理。超时不完全是冗余,但那是检测错误的另一种方式。

这里的关键点是,如果您只带一个时钟出海,您就无法判断它是否告诉您正确的时间。您需要某种方法来检查。例如,如果您从网络读取消息,您可能需要检查标头以查看它是否真的是您期望的消息——也就是说,查找某些位和某个位置,上面写着“啊哈,这看起来我应该进一步前进。”

或者,如果您从磁盘读取,您可能需要检查磁盘块上的标签,以查看它是否是您认为您要求的块。始终是状态中的某种冗余,使您能够检测到错误的发生。如果您没有考虑过故障,您为什么要将磁盘块的地址放入磁盘块中?

SB 所以,您真正想做的是建立对您对系统中正在发生的事情的信念的信心?

BL 在很大程度上,这是对的。并在您前进的过程中验证您将要操作的数据或状态是否自洽。

SB 我们在语言方面对错误检测有什么支持?

BL 事实上,在检测方面,语言支持为零。我们在语言中看到的是在发现错误后处理错误的功能。在语言中抛出异常是程序逻辑所做的。

例如,大多数脚本语言在语言和语义级别上对处理异常几乎没有支持。而且归根结底,语言中的大多数东西都是您可以自己编写代码的东西。

语言中存在一些相当危险的功能——特别是 raise error 或 throw exception 以及处理程序。这与过程调用堆栈有什么关系?我们在语言支持的错误处理的早期方法中看到的是,堆栈被剥离,但不做任何事情,直到您在堆栈中找到某个级别,该级别声明它对处理当前错误中的特定异常感兴趣。

总的来说,折叠过程或子例程激活——方法激活——而不清理可能已经造成的混乱,即该函数的部分完成状态转换,是非常危险的。例如,如果已经进行了内存分配,而您只是剥离堆栈条目,那么这些内存分配可能不会被撤消。

因此,非常重要的是,在过程激活的每个级别,都应给这些过程一个机会来整齐地收起它们的帐篷,即使它们无法处理异常。

SB 内存分配似乎是人们需要考虑的一个领域。我不知道是否有任何好的规则。您对此还有其他想法吗?

BL 嗯,当然,垃圾回收涵盖了这方面的许多罪恶。C++ 堆栈帧中对象的析构函数的自动调用也很有帮助。虽然垃圾回收会造成一些严重的性能问题,尤其是在多线程应用程序中,但它确实倾向于消除存储泄漏,而存储泄漏是最难隔离的错误之一。

SB 似乎至少一些更现代的语言解决了错误恢复问题——例如,Python 中有一些工具。在我看来,好消息是人们开始考虑这个问题并将这些东西放入语言中。我不知道他们是否做得对。

BL 人们开始考虑这个问题是好事,哪怕只是因为这种新的语言功能让他们考虑错误恢复。语言中没有任何神奇的东西可以拯救您。您仍然必须考虑您调用的东西可能无法交付的原因,以及如果它们不交付您将如何处理。SB 一旦您检测到错误,现在该怎么办?您可以报告它,但问题是您要向谁报告,以及您要报告什么?

BL 有两种检测类别。一种是我查看了自己的内部,它们看起来不太对劲,所以我说这是一个错误情况。另一种是我调用了某个其他组件,但该组件未能按要求执行。无论哪种情况,我都面临着检测到的错误。首先要做的是收起您的帐篷——也就是说,将状态放回原位,以便您管理的状态是连贯的。然后您向调用您的人报告,可能会沿途进行一些转储,或者您可以尝试备用逻辑来规避异常。

在我们的数据库项目中,通常发生的情况是,它会被向上、向上、向上地报告到链条中,直到您到达某个非常高的级别,然后该级别会说,“哦,我将此视为那些非常糟糕的情况之一。我现在要启动大规模转储。” 当您报告错误时,您应该对其进行分类。您应该给它一个名称。如果您是一个报告错误的组件,则应该有一个详尽的错误列表,您会报告这些错误。

这是当今用于异常处理的编程语言架构中的真正问题之一。每个组件都应该列出引发的异常:通常,如果我调用您,而您说您可以引发 A、B 和 C,但是您可以调用 Joe,Joe 可以引发 D、E 和 F,而您忽略了 D、E 和 F,那么我突然在我的级别面临 D、E 和 F,并且您的接口中没有任何内容表明 D、E 和 F 错误是您造成的。这在编程和语言工具中似乎很普遍。您永远不需要说这些是从对我的调用中可能逃脱的所有错误。那是因为您被允许忽略错误。我有时提倡,不,您不被允许忽略任何错误。您可以重新分类错误并将其报告回上层,但您必须将其纳入循环。

SB 系统不完全知道要报告的正确信息,但您不一定希望处于必须浏览 20 兆字节的东西才能找到某些东西的情况。

BL 就数据库系统而言,我们似乎越来越倾向于保守转储——也就是说,转储任何可能感兴趣的东西。原因是当您在现场有一个应用程序,并且当您处理诸如不容易重现的 Heisenbug 或并发错误之类的事情时,通常很难让用户在您说“嘿,我希望您再次崩溃您的系统,同时我打开我的特殊监视器以查看发生了什么”时进行特殊运行。

他们通常会说,“嗯?它现在正在运行。它已经运行了六个小时。是的,这个问题每周发生一次,我们对它发生感到非常生气。修复它,该死的。但我们不会为了您而崩溃系统。”

因此,我们正在转储越来越多的信息。现在,我们不转储数据库本身,但是如果我们发现一个可疑的页面,我们肯定会转储该页面的所有内容——几乎是按位——以进行可能的分析。如果您必须在现场维护代码,并且不允许您说“嘿,请为我再次崩溃系统”,那么转储太多可能不是一件坏事。

SB 当事情确实出错并且您决定转储您可以想到的所有内容时,问题是,“哎呀,如果知道当时在这个地方发生了什么不是很好吗?” 而不是仅仅获得堆栈转储并尝试从这些照片中找出所有内容。这几乎就像您需要电影而不是照片。

BL 嗯,当然,当您打开跟踪时,电影就来了,但话说回来,这需要特殊的能力,并且通常是在开发车间完成的。通常,如果这是一个可重现的错误,并且他们可以查看所有转储,那么开发人员首先要做的事情就是尝试它。他们将在显微镜打开系统的情况下重现它,并查看跟踪转储——这确实给您提供了电影。

SB 所以真正的问题是,当您为失败设计代码时,您是否会想到您应该记录哪些内容,这些内容可以在这些情况下帮助您?

BL 当发现根本原因时,修改转储例程以转储更多信息以增强问题的发现并不罕见。SB 谈谈文件系统和注册表会很有趣,并了解您对我们从过去的日子(当我们有 fsck(文件系统检查)来修复崩溃的文件系统时)到现在的发展程度的看法。

BL 我认为文件系统已经发生了彻底的变革,它们已经从扫描或 fsck 以从紧急重启中恢复,转向数据库驱动/受启发的日志记录系统,该系统记录元数据更改,然后在后台惰性地将更改写入磁盘。

这有两个好处。一个通常是日志可以写入高性能设备,而由日志更改表示的元数据更改可以在稍后的时间惰性地写入它们在磁盘上的主位置,而不一定与用户请求内联。它还使文件系统的紧急重启速度更快,因为只需要重做或仔细检查最近的更改。

SB 让我们考虑一个系统设计用于优雅降级的示例。在用于航空公司预订的 Sabre 系统中,他们愿意接受类似千分之一的重复预订,因为事实证明,如果有人在系统中有两个预订,也没关系。最终,有人会弄清楚并采取措施。这不是一个非黑即白的系统。它是一个边缘模糊的系统,并且愿意容忍一些不一致性,因为他们有其他方法可以从不一致性中恢复。

BL 在系统的所有级别,我们都发现对降级操作的轻微尝试,从内存系统(其中坏内存芯片将简单地从内存中移除并且不再使用——希望在使用 ECC 冗余恢复其状态之后)到整个系统级的东西,其中在系统故障的情况下,您可能会在可能已经承担部分负载的备用硬件上重新启动它。因此,此时的降级在于性能。

您使一个系统过载,因为另一个系统已发生故障,并且您愿意容忍性能降低,直到第一个系统可以修复。

同样,在冗余阵列存储系统中,在重建发生故障的磁盘期间,我们发现对曾经存储在该磁盘上的数据的访问性能降低,因为该值必须从备用阵列块的值中重建。

因此,优雅降级正在系统的所有级别上出现。

必须非常小心降级,在这种降级中,系统返回的结果可能会在某种程度上受到损害。考虑航空公司控制应用程序,如果您丢失了一个预订,该人会告诉您,您稍后会弄清楚。这需要在系统设计级别进行真正的内心检查,以说“对于应用程序、对于此系统的部署,这是否可以接受?” 当然,我们在 Web 环境中看到了这一点。如果您完全忽略 Web 的百分之一的请求,您就没事了。这个人只会刷新,下次它就会工作。但是,如果您忽略百分之百的请求,即使只是几分钟,它也会登上《纽约时报》。

因此,我们看到如何响应错误的决定——是给出无答案还是甚至是部分答案——实际上取决于答案将用于什么,以及您的客户或客户对错误答案或缺乏答案的反应是什么。SB 系统调试与检测和恢复之间的关系如何?

BL 错误消息大致分为两类:一类是系统已经预料到这个问题,例如用户名重复或参数超出范围,现在正试图告诉您它观察到了什么问题。大多数系统在这方面特别糟糕。您获得的错误反馈充其量是含糊不清,最坏的情况是具有欺骗性。这源于许多问题。其中之一是,在许多软件开发环境中,都有一个完整的小型子系统来处理错误消息。要创建一条新的错误消息,您需要花费半个小时的打字时间来填写错误消息,并且您必须确保它到达国家语言翻译,并给它一个尚未分配给其他错误消息的错误代码。为了避免这种情况,有时人们会重用旧的错误消息,说“这有点像另一个错误。它已经在系统中了。我可以只使用这个。”

编程中的懒惰导致错误消息缺乏特异性。您可能会说,“你知道,我可以在大致说明发生了什么的非参数化消息和更复杂的参数化消息之间做出选择,对于后者,我必须编写 15 行代码。” 猜猜我会选择什么?

我认为,总的来说,软件开发人员——硬件人员也好不到哪里去——在正确说明错误和更准确地描述错误方面投入的资金不足。

SB 当您生成错误消息时,您必须知道它是为谁生成的,以及他们对正在发生的事情了解多少。人们通常在多大程度上与用户社区一起测试错误消息?在我看来,反馈将非常具有建设性。

BL 通常,错误消息的文本是用系统内部深处的开发人员的角度来措辞的,并且与最终用户当时正在做的事情没有明确的关联,或者就像我说的那样,它不够具体。

现在,这是第一类错误消息——也就是说,用户的输入和错误之间存在某种相当清晰的关系。另一类错误消息是当系统意识到自己搞砸了时。在这种情况下,错误消息实际上根本不是为最终用户准备的,并且通常不会告诉最终用户任何信息。他们说,“我们无法执行该语句。对不起,走开。尝试不同的语句。”

基本上,这是系统承认存在错误,当然,在这一点上,为了维护该故障——无论是软件故障还是硬件故障——需要更多信息。将所有这些信息转储到可怜的最终用户身上是没有帮助的。

因此,大多数主要系统——硬件和软件——都维护错误日志数据集,当这些类型的错误发生时,系统最终会将大量信息转储到错误日志数据集中,说“这是情况。我发现这很糟糕,这是围绕这个东西的所有东西,这些东西可能导致它变得糟糕,这是我在变糟糕时正在做的事情。希望您能弄清楚发生了什么。”SB 什么是 Heisenbug?

BL Heisenbug 最初的定义——当它发生时我在场——是系统中显然行为不正确的错误,当您尝试查看为什么不正确时,问题就消失了。通常,当您尝试查看什么是不正确时,您会打开跟踪或添加更多参数或更改某些内容。而这种更改导致问题消失。这些问题通常由于并发执行而发生。或者它们是由于内存在特定情况下恰好被布局的方式而发生的。

因此,Heisenbug 的真正定义是当您查看时,它就消失了——为了向 [维尔纳] 海森堡博士致敬,他说,“您越仔细地观察一件事,就越不清楚地看到另一件事。”

SB 我猜当这种情况发生时,好消息是您知道自己遇到了真正的麻烦。因为它通常意味着系统的较低层——例如内存分配或类似的东西——被搞砸了?

BL 它们非常难找到,但是,当然,困难的那些才是有趣的。SB 我们如何在分布式系统中处理错误检测和恢复,分布式系统本质上往往是高度并行的,并且由大量松散耦合的部件组成?

BL 我们可以将分布式系统分为两大类。一类是其中一个系统将另一个系统用于某些功能。那是一种依赖功能。Web 服务就是这种依赖使用的示例。另一类是分布式机器正在协作处理某些单一服务。分布式文件系统就是一个例子。

第二类实际上是人们将大部分心思投入到错误恢复和可靠性中的地方,因为这种分布式系统中的想法通常是任何服务器都可以做到——您只需要找到一个已启动并正在运行并且是游戏一部分的服务器即可。但是由于分布式状态随后由这些服务器维护,因此在弄清楚谁在游戏中存在巨大的问题。这些是组共识算法。有一个重要的定理说,在正常的消息传递情况下——或在极端的消息传递情况下——不可能判断另一个系统是发生故障还是只是速度慢,还是您发生故障。也就是说,您的通信外链已发生故障。

实际上,编写和实现这些算法是可能的。周围有很好的算法。它们运行起来有点昂贵,但它们用于维护谁在线的列表,并让每个在线的人都知道还有谁在线,因为通常这些系统必须在所有在线成员之间协作以维护分布式状态。

SB 其中一个有趣的方面是在检测到某些东西所需的时间与您真正有时间在系统中恢复的时间之间的权衡。

BL 以及删除发生故障的组件意味着什么,因为存在脑裂问题,我认为您已出局,而您认为我已出局。谁负责?

SB 对,当他们争论不休时,什么也没发生。

BL 这也是可能的,尽管其中一些系统可以继续服务。分布式系统很少需要所有活动成员的参与才能执行单个操作。

还存在处理掉发生故障的成员的问题。如果我们有五个人,而我们四个人认为您死了,那么下一步就是通过再向您射三颗子弹来确保您死了。

我们特别在磁盘的共享管理领域看到了这一点,在磁盘的共享管理领域,您有两个处理器、两个系统,连接到同一组存储。问题是,如果一个系统要接管另一个系统的存储,那么第一个系统最好不要再使用该存储。我们实际上在存储子系统中发现了用于冻结参与者的架构设施——所谓的隔离设施。

因此,如果我认为您死了,并且我想接管您对存储的使用和责任,那么我要做的第一件事就是告诉存储,“不要再注意他了。我现在负责。” 如果您继续使用存储,而我愉快地继续前进并认为我是负责存储的人,那么可能会发生可怕的事情。

分布式系统中的协作有两个方面。一是弄清楚谁在玩游戏,二是,如果现在认为有人不玩游戏了,请务必以某种方式确保他们不玩游戏。 SB 使错误检测和恢复变得困难的一件事是多线程应用程序。我们应该 просто 不编写多线程系统吗?

BL 我认为我们需要编写多线程系统,但这很困难。我过去常对人们说,“如果您五年没有编写多处理代码,那么您就没有资格编写多处理代码。” 然后我意识到,如果我保持这种态度,就不会有人取代我。因此,我一直在努力帮助人们了解如何编写多处理代码。有很多技术,但基本上同步关键部分和大量检查是必要的。多处理代码和使用共享内存数据结构的优势非常引人注目,以至于我们必须这样做。

SB 我猜检测和恢复方面的最新技术在过去 20 年中没有太大进展。

BL 我们正在稍微改进的一件事是我们所谓的错误范围。它是否只是搞砸了这个功能?您调用了这个函数,但它没有执行。它说,“我失败了,返回。” 此时,范围是该函数。当然,如果您返回,如果您向代码抛出异常,那么范围现在变得更大了。您必须确定此错误的范围是否只是此线程。如果错误发生在任何共享数据操作之外,您可以说,“也许如果我们只是杀死了这个线程,其他线程可以继续运行,也许我们甚至可以重新启动这个线程,下次它会更快乐。”

SB 这在代码中是如何表示的?

BL 通常是通过线程管理中的机制,您最终必须为任何多线程应用程序编写这些机制。线程可能会死亡,然后线程管理器会说,“哦,那是如果线程死亡就想要重新启动的线程类型之一。”

例如,在数据库系统中,您可能会收到一条语句,系统可能会崩溃并说,“哦,我无法弄清楚这里的语法。” 那是用户错误,我们很乐意告诉用户,但是执行该操作的线程很乐意接受另一条语句,甚至再次接受同一条语句并尝试处理它。

在另一个极端情况下,日志磁盘发生故障,我们无法写入日志记录。 在这种情况下,你最好停止整个系统。 你必须能够放弃并重启,并在备用硬件上提供服务。 我们必须处理站点灾难,对吧? 这是真实存在的。 你必须从应用程序和子系统设计的最初就考虑到这一点。

喜欢它,讨厌它? 告诉我们

[email protected] 或 www.acmqueue.com/forums

© 2004 1542-7730/04/1100 $5.00

acmqueue

最初发表于 Queue vol. 2, no. 8
数字图书馆 中评论这篇文章





更多相关文章

Qian Li, Peter Kraft - 事务和无服务器天生一对
数据库支持的应用程序是无服务器计算领域令人兴奋的新前沿。 通过紧密集成应用程序执行和数据管理,事务性无服务器平台实现了许多在现有无服务器平台或基于服务器的部署中不可能实现的新功能。


Pat Helland - 换汤不换药的身份
新兴的系统和协议既收紧又放宽了我们对身份的概念,这很好! 它们使事情更容易完成。 REST、IoT、大数据和机器学习都围绕着有意保持灵活甚至有时含糊不清的身份概念。 身份概念是我们分布式系统基本机制的基础,包括互换性、幂等性和不变性。


Raymond Blum, Betsy Beyer - 实现数字永恒
今天的信息时代正在为世界依赖的数据创造新的用途和新的管理方式。 世界正在从熟悉的物理人工制品转向更接近信息本质的新表示方式。 我们需要流程来确保知识的完整性和可访问性,以保证历史将被了解和真实地记录。


Graham Cormode - 数据速写
你是否曾感到被源源不断的信息淹没? 似乎大量的新电子邮件和短信需要持续关注,还有电话要接听,文章要阅读,以及敲门声要回应。 将这些信息拼凑在一起以跟踪重要内容可能是一个真正的挑战。 为了应对这一挑战,流数据处理模型越来越受欢迎。 其目的不再是捕获、存储和索引每一分钟的事件,而是快速处理每个观察结果,以便创建当前状态的摘要。





© 保留所有权利。

© . All rights reserved.