把 Elasticsearch 用作 NoSQL 数据库

更新:本文提及我们的托管式 Elasticsearch 产品时使用的是旧称 Found。请注意,Found 现在称为 Elastic Cloud。托管式 Elasticsearch 可在 Elastic Cloud 上免费试用 14 天

Elasticsearch 可以用作“NoSQL”数据库吗?NoSQL 在不同的上下文中有不同的含义,有趣的是,它实际上与 SQL 无关。我们将从一种“不确定的情况”入手,了解 Elasticsearch 的各种属性,以及它为了成为迄今为止最灵活、最具可扩展性、性能最佳的搜索和分析引擎之一而牺牲的那些属性。

究竟什么是 NoSQL 数据库?

NoSQL 数据库将 NoSQL 定义为“下一代数据库,主要涉及到以下几点特征:非关系型、分布式、开源以及可横向扩展”。换句话说,这不是非常精确的定义。

它并不是专门针对 SQL 的。例如,Hive 的查询语言显然受到了 SQL 的启发。Esper 的查询语言也是如此,它对流而不是关系进行操作。另外,您知道过去 PostgreSQL 被命名为“Postgres”,并且使用“Quel”作为查询语言吗?虽然首先是 ORDBMS,但它现在同样有许多特性使其具备不区分模式的文档存储能力

而且,与 ACID 特性也无关。Hyperdex 是 NoSQL 数据库的一个示例,旨在提供 ACID 事务。MySQL(当然是 SQL 数据库)对 ACID 的真正含义一直以来有着令人怀疑的解释。

关系?虽然大多数 NoSQL 数据库不支持与传统关系型数据库相同意义上的连接,并需要用户自行解决这一问题,但也有一些数据库能够提供支持。比如,RethinkDBHivePig 等等。Neo4j 是面向图形的数据库,当然会处理关系 — 它非常擅长遍历图形中的关系(即边)。在 Elasticsearch 中,可以使用父/子关系进行“查询时”连接,也可以使用嵌套类型进行“索引时”连接。

分布式?虽然存在一些分布式 SQL 数据库,也有一些项目旨在成为像 NoSQLite 一样的数据库,但更新一代的数据库往往采用某种分布形式。

总而言之,准确地定义 NoSQL,或者简单地说 Elasticsearch 是“文档存储”类型的 NoSQL 数据库,都没有意义。在撰写本文时,nosql-database.org 列出了其中的 20 多个属性。

在接下来的部分中,我们将了解一些重要属性,以及 Elasticsearch 如何实现或不实现这些属性。

无事务

Lucene(Elasticsearch 在它的基础上构建而成)有事务的概念。相反,Elasticsearch 没有典型意义上的事务。无法回滚已提交的文档,而且您不能提交一组文档,并对全部文档都编制索引或都不编制索引。然而,Elasticsearch 所拥有的是一个预写日志,旨在确保操作的持久性,而不必进行开销高昂的 Lucene 提交。此外,您还可以指定索引操作的一致性级别,即返回之前必须确认操作的副本数。默认为法定数量,即 \(\lfloor\frac{n}{2}\rfloor + 1\)

更改的可见性是在刷新索引时控制的,默认情况下每秒刷新一次,并且是逐个分片进行的。

乐观并发控制是通过指定已提交文档的版本来实现的。

Elasticsearch 追求的是速度。执行分布式事务是一项繁重的工作。不提供分布式事务会让很多事情变得更容易。只要我们能接受读取到的数据可能有些陈旧,而且所有人看到的是同一时间点的数据,那么 Elasticsearch 就可以利用缓存提供很多服务 — 这对于我们钟爱的极速性能来说是至关重要的。

模式灵活

Elasticsearch 不要求您预先指定模式。扔给 Elasticsearch 一个 JSON 文档,它就会进行一些有根据的猜测来推断这个文档的类型。Elasticsearch 可以很好地处理数字、布尔值和时间戳等数据。对于字符串,它会使用“标准”分析器,这通常很容易上手。

虽然 Elasticsearch 可以说是“无模式”的,从某种意义上说,您不必指定模式,但我们更愿意将其视为“模式灵活”。为了开发出色的搜索和/或分析,您确实需要对模式进行微调。Elasticsearch 有一系列功能强大的工具可以为您提供帮助,如动态模板、多字段对象等。我们在关于映射的一篇文章中对此进行了详细介绍。

关系和约束

Elasticsearch 是一种面向文档的数据库。需要对要搜索的整个对象图形编制索引,因此在对文档编制索引之前,必须对其进行反规范化处理。反规范化可提高检索性能(因为不需要查询连接),会使用更多的空间(因为必须多次存储数据),但要保持数据一致性和实时性则更加困难(因为任何数据更改都必须应用到所有实例中)。不过,对于一次写入频繁读取的工作负载,它的表现相当优异。

举例来说,假设您在数据库中存储了客户、订单和产品等数据,并且您想要通过产品名称和客户姓名来搜索订单。要解决这个问题,可以在为订单编制索引时把客户和产品的所有必要信息都加进来。这样的话,搜索起来就非常简单,但如果您想要更改某个产品的名称会出现什么情况呢?在进行了良好规范化的关系型设计中,您只需要更新相应的产品就可以搞定。这正是关系型数据库所擅长的。而在反规范化的文档数据库中,将不得不更新与该产品有关的所有订单。

换句话说,在面向文档的数据库(如 Elasticsearch)中,我们对文档进行映射和存储设计只是为了优化搜索和检索的性能。

在简介中已经提到,Elasticsearch 中可以使用父/子关系进行“查询时”连接,也可以使用嵌套类型进行“索引时”连接。我们会在以后的文章中对该主题进行深入介绍。在此我们推荐 Martijn van Groningen 的演讲“Document relations with Elasticsearch”(使用 Elasticsearch 记录关系)。

大多数关系型数据库还允许您通过指定约束来定义哪些数据需要保持一致性,哪些不需要保持一致性。比如,参照完整性和唯一性都是强制性的。您可以要求帐户变动的金额必须是正数,等等。面向文档的数据库往往不会这样做,Elasticsearch 也不例外。

稳健性

数据库应该稳定可靠,尤其是当它是您的权威记录系统时。理想情况下,耗费资源的查询应当可以取消,并且您肯定不希望数据库停止工作,除非您命令它停下来。

遗憾的是,Elasticsearch(及其组件)目前并不能很好地处理 OutOfMemory 错误。我们在生产环境中的 Elasticsearch - OutOfMemory 导致的崩溃一文中更深入地介绍了这一点。因此,为 Elasticsearch 配置足够多的内存就显得非常重要,而且要在生产集群上谨慎运行那些无法预知将会耗费多少内存的查询。

不过,随着 Elasticsearch 越来越成熟,这个问题很可能会得到改善,但重要的是要记住,Elasticsearch 的目标是提升速度,所以它假设内存是充足的。

分布式

另请参阅:生产环境中的 Elasticsearch - 网络

在创建 Elasticsearch 之前, Shay Banon 一直在开发 Compass。由于意识到很难将其转变成一个分布式的搜索引擎,他就从头开始创建了 Elasticsearch1。Elasticsearch 采用分布式且易于扩展的设计,可以在商用硬件上处理海量数据。

作为一个分布式的系统,Elasticsearch 的上手和使用都非常简单,但是分布式系统本身是很复杂的。关于这一点,我们在生产环境中的 Elasticsearch - 网络一文中有更多的介绍,所以下面只是一个简短的摘要。

分布式系统天生的特性,意味着很多事情都可能会出错。因此,不同的数据库系统侧重于不同的优势:有些力争强大的安全保证,另外一些则致力于始终可用,即使某些时候(甚至多数时候)出现错误也在所不惜。而且,正如 Kyle Kingsbury 在他关于 perils of network partitions(网络分区的危险)的优秀系列文章中所指出的那样,在出现问题时,实际上数据库系统并没有像它们所宣称的那样来处理问题。简言之,他发现,虽然分布式数据库在阳光明媚的日子里运行良好,但绝大部分数据库在遭受到大量可能的问题时所做的努力,都会以失败而告终

就一致性、可用性和分区容错性而言,Elasticsearch 是一个 CP 系统,对于“一致性”的定义相当不靠谱。如果您有一个只读工作负载,Elasticsearch 允许您通过不严格的“最小主节点”要求(即不需要法定数量)来实现 AP 行为。然而,通常情况下,您需要集群中的大多数节点是可用的。写入一个配置错误(无法保证大多数节点可用)的集群,即“脑裂”集群,可能会导致无法恢复的数据丢失。这绝不只特定于 Elasticsearch。

就扩展而言,一个索引可以被划分为一个或多个分片。这是在创建索引时指定的,不能更改。因此,随着预期的增长,应将一个索引合理地进行分片。随着越来越多的节点被添加到 Elasticsearch 集群中,它在重新分配和移动分片方面表现良好。因此,Elasticsearch 很容易横向扩展。

安全性

另请参阅:生产环境中的 Elasticsearch - 安全性

Elasticsearch 没有任何身份验证或授权特性。您可以这样认为:任何人只要能连接到您的 Elasticsearch 集群,就拥有了“超级用户”权限,尤其是在启用了 Elasticsearch 强大的脚本功能的情况下。

总结

如果本文描述的这些限制都不能阻止您,您当然可以将 Elasticsearch 用作主存储。使用 Logstash 就是一个很好的例子。Logstash 是一个非常棒的工具,用来管理日志并把它们导入到 Elasticsearch 中,也许还可以将日志归档到其他地方以备不时之需。日志都是一次写入,多次读取的。不需要更新,不需要事务支持,也没有完整性约束等。

那么像 Postgres 这样带有全文搜索 ACID 事务的系统呢?(其他示例包括 MySQL、MongoDB、Riak 等的全文检索能力。) 虽然您可以用 Postgres 来实现基本的搜索,但在性能和功能上与 Elasticsearch 都有很大的差距。正如我们在事务部分所提到的,Elasticsearch 会进行大量缓存,所以可能会“欺骗”我们,而且不关心多版本之间的并发控制和其他复杂的事情。搜索也远不止在一段文本中找到一个关键字那么简单:它涉及到运用领域特定的知识来实现良好的相关性模型,由此给出整个结果空间的一个全貌,并且能做拼写检查和自动补全之类的工作。而且,所有这些操作都要保证速度。

Elasticsearch 通常被用作其他数据库的补充。这类数据库系统更注重约束、正确性和稳健性,并且随时可进行事务更新,拥有主记录 – 这些记录随后会被异步推送到 Elasticsearch。(也可能是拉取,前提是您使用了 Elasticsearch 的某一种“river”。) 保持数据同步是我们打算将来撰文深入探讨的话题。在 Found 中,我们通常使用 PostgreSQL 和 ZooKeeper 作为真实数据的来源,我们将这些数据输入给 Elasticsearch 来提供出色的搜索体验。

就像所有其他事情一样,没有包治百病的灵丹妙药,没有一个数据库可以做到所有的事情。也许这种情况会永远持续下去,所以要清楚您的数据库的优点和弱点!

参考资料

Shay Banon:The future of compass & elasticSearch(Compass 和 Elasticsearch 的未来)– https://thedudeabides.com/articles/the_future_of_compass


  1. Shay Banon,The future of compass & elasticSearch(Compass 和 Elasticsearch 的未来)– https://thedudeabides.com/articles/the_future_of_compass