内容简介:Cloudant 多租户服务最佳实践
IBM 于 2014 年 2 月收购了 Cloudant,自那以后,事实证明它是一个很有用的基于文档的存储系统。Cloudant 提供了一个直观的仪表板,其中包含许多管理大数据的必要 工具 和各种各样的客户端库,使得在自己的应用程序中利用 Cloudant 变得很容易。但是,与任何优秀的工具一样,您需要遵守一些最佳实践和避免一些陷阱。
本文将介绍一些关于文档/数据库的组织、文档的查询和删除,以及性能优化的最佳实践。我们分享了在多租户云服务中使用 Cloudant 存储配置数据的过程中学到的经验教训。我们不会自诩是 Cloudant 专家,不过我们使用 Cloudant 已有一年多的时间。我们通过反复试验学到了很多经验,而且因为我们无法在现实中找到太多使用 Cloudant 构建多租户企业系统的真实案例,所以我们认为与其他架构师和开发人员分享我们的经验可能很有用。
我们的经历
我们使用 Cloudant 为约 1,000 个租户存储了配置数据。其中大部分租户只依靠少量的配置数据(每个租户使用约 200 个文档),而另一些租户则依靠大量配置数据(每个租户使用约 50-250 万个文档)。我们通过提供一个在 Node.js 中实现的 REST API,封装了存储在 Cloudant 中的数据。最终用户无法直接访问 Cloudant。该 REST API 作为一个可扩展的微服务集合而托管在 Bluemix 上。
“ 本教程将介绍一些关于文档/数据库的组织、文档的查询和删除以及性能优化的 Cloudant 最佳实践。 ”
我们学到的经验教训和如何学习本文
有许多优秀的文章都介绍了 CouchDB 和 Cloudant 的基础知识。本文的目的不是介绍这些基础知识,而是提供一些关于在多租户环境中使用 Cloudant 的指南。如果您不熟悉这些基础知识,请查阅本教程末尾的 “相关主题” 部分,从那里获得一些有帮助的文章的链接。我们推荐在继续学习之前阅读这些文章。
了解基础知识后,我们仍然不很确定如何搭建数据或设计搜索索引。下面,我们将通过一组简单 排序 的最佳实践,分享我们学到的经验教训。如果您对后面的章节更感兴趣,可以跳过一些章节。我们希望我们学到的一些经验教训对您的下一个项目有所帮助!
组织文档
一个不错的想法是将文档组织为大量小数据库,而不是少量大数据库。在我们的项目中,我们最初为每个租户创建了一个 Cloudant 数据库。所有与该租户相关的文档都存储在该租户独有的数据库中。随着租户数据库大小的增加,我们遇到了一些索引性能问题。后来,我们重组了文档,为每个租户、每种文档类型都创建了一个数据库。最终,我们获得了更好的性能。(我们将在下面的 “” 部分讨论我们学到的经验教训。)
使用各种文档类型时,应该将每种文档类型分离到一个单独的数据库中。许多视图和索引仅适用于特定类型的文档。使用包含类似文档的较小的数据库可让索引变得更小,更小的索引减少了 Cloudant 集群在重建索引时执行的总工作量。例如,一个计算 “商品” 均价的视图可能不会检查 “地址” 文档,所以一个不错的想法是不仅为商品创建一个数据库,还为地址也创建一个数据库。
删除文档
删除 Cloudant 中的文档时,Cloudant 不会从存储中实际删除整个文档。相反,它会留下一个包含文档基本信息的 墓碑 。墓碑包含字段 _deleted
(布尔标志)、 _id
和 _rev
。尝试采用避免删除大量文档的模式,因为只要数据库仍然存在,墓碑就会继续占用存储空间。如果必须频繁地删除大量文档,还应该定期删除包含这些墓碑的数据库。
例如,考虑以下情况:一个在 Cloudant 中存储大量仅需要存在一个月的数据的应用程序。如果在不再需要文档时删除它们,墓碑数量的激增可能很容易超过活动文档的数量。相反,您应为每个月的数据创建一个新数据库,在数据过期时删除整个数据库。
查询文档
Cloudant 提供了多个查询和检索文档的选项。可能您已预料到,每个选项都有其优缺点,因此,特定用例的最佳选项取决于您的使用场景。在本节中,我们将讨论查询选项和它们分别适合哪些场景。
主索引(按文档 ID)搜索
主索引 是从数据库检索数据的最快方式。每个 Cloudant 数据库都带有主索引,这意味着您无需编写任何代码即可使用它。主索引通常称为 _all_docs
,对于数据库中的每个文档,它都会返回一个 ID、一个键和一个值。ID 和键是相同的(Cloudant 使用文档 ID 作为索引键),而值是文档的 _rev
。 _all_docs
还会报告文档总数和任何用于查询索引的偏移量。如果您知道想要查询的文档的 ID,使用该 ID 来检索文档是更高效的。
优点:
- 最快的查询选项
- 没有额外的存储开销 — 自动使用键建立索引
- 可在一个请求中一次性查询任意多个文档
缺点:
- 最不灵活的查询选项 — 只能按 ID 进行查询
次索引(MapReduce 视图)
次索引 是一种处理数据库中的文档内容的机制。次索引可以选择性地过滤文档,加速内容搜索,并在将结果返回到客户端之前预先处理它们。为视图建立索引会产生相关的性能成本,我们将在 “” 部分对此进行更详细的讨论。
优点:
- 可在一个请求中检索任意多个文档的结果
- 可以减少服务器上的查询结果,以提高性能和减少通过 HTTP 传输的数据
缺点:
- 自定义 JavaScript reduce 函数并不总是能够得到很好的扩展,所以不要尝试使用自定义函数;尽可能使用内置的 reduce 函数来实现更高的性能。
- 存储开销 — 会为数据库中的每个文档存储 map 函数发出的键。
Cloudant 搜索(按文档中的特定字段进行搜索)
搜索索引 (在设计文档中定义)允许使用 Lucene 查询解析器语法来查询数据库。搜索索引由一个 index 函数定义,类似于 MapReduce 视图中的 map 函数。index 函数将决定为哪些数据建立索引并将它们存储在该索引中。
优点:
- 比 MapReduce 视图更灵活,可以单独搜索任何索引字段,或与其他索引字段一起搜索,所以您不需要进行太多的未雨绸缪,以让搜索索引灵活地满足未来搜索需求。
缺点:
- 一次只能检索最多 200 个文档—当搜索得到超过 200 个结果时,必须使用书签来在后续 HTTP 查询中检索下一页结果。
- 存储开销—为数据库中的每个文档建立索引字段。
下图直观地描绘了 Cloudant 中的不同查询选项之间的关系。主索引提供了最佳的性能和最低的灵活性,而 Cloudant 搜索以一定的性能为代价提供了最高的灵活性。
Cloudant 查询选项
这个简短视频 概述了主索引、次索引、Cloudant 搜索和其他搜索选项。
优化查询性能
您应该只依靠内置的 reduce 函数。我们发现,当文档很多时,自定义 JavaScript reduce 函数无法高效执行。如果需要使用一个操作查询 200 多个文档,可使用次索引来代替 Cloudant 搜索。在能够容忍稍微过期的数据时,应该尽可能地对查询使用 stale
选项。如果要对一个视图的大量结果进行翻页,可使用 startkey
/ startkey_docid
选项代替 skip
选项,如 CouchDb wiki 中所述:
skip 选项仅应用于较小的值,在跳过大量文档时,这种方法的效率很低(它扫描来自 startkey 的索引,然后跳过 N 个元素,但仍需要读取所有索引值才能实现此目的)。要高效地分页,需要使用 startkey 和 limit。如果期望让多个文档发出相同的键,那么除了 startkey 之外,还需要使用 startkey_docid 来正确地分页。原因在于仅使用 startkey 无法唯一标识某一行。
优化索引性能
创建或更新设计文档时,Cloudant 会为该文档中的每个视图都填充了一个索引。这些索引很重要,因为它们有助于让查询运行得更快。但是,理解索引过程的工作原理也很重要,因为对一个包含大量文档或复杂视图的数据库建立索引可能导致意外的副作用。
例如,索引是一种锁定操作,所以在为数据库建立索引期间不能使用该数据库(除非使用 “” 部分讨论的 stale
选项)。此外,在大量建立索引期间,整个 Cloudant 集群可能变得无响应或不稳定,所以您需要密切关注索引代码的性能。
当 Cloudant 为某个数据库的特定视图建立索引时,它会对数据库中的每个文档运行 map 函数,并存储该 map 函数发出的键来供未来引用。包含许多文档的数据库需要花很长时间才能建立索引,因为必须为数据库中的每个文档运行 map 函数,无论为每个文档发出的键有多少。包含非常复杂的 map 函数的视图也需要很长时间才能建立索引,因为必须对每个文档反复执行这种复杂的逻辑。
索引进程在后台运行,而且您为了创建或更新设计文档而提交的 HTTP 命令将在索引完成之前返回。可以通过 Cloudant UI 或监视 API 来监视索引进度,但无法 100% 准确地估算一个特定索引任务将花多长时间完成。
根据我们的经验,以下实践可减少为数据库建立索引的性能成本。
- 最大限度地减少每个数据库中的视图/索引数量。
- 严格地为每个设计文档定义视图或索引。此实践使您能够更新特定的视图/索引,无需为了所有未更改的视图/索引而重新建立数据库中的所有文档的索引。
- 案例 1 —1 个包含 50 个视图的设计文档。如果需要更新一个视图,您需要更新 Cloudant 中的整个设计文档。此更新会导致 Cloudant 为了所有 50 个视图重新建立数据库中的所有文档的索引。
- 案例 2 — 50 个设计文档,每个文档 1 个视图。如果需要更新一个视图,只需更新 Cloudant 中的一个设计文档。此更新会导致 Cloudant 为更改的视图重新建立索引,但无需为所有未更改的视图重新建立索引。
- 视图和索引应该应用于相应数据库中的所有(或大部分)文档。如果视图/索引未应用于数据库中的所有文档,您应泛化该视图/索引,以便将它应用于数据库中的所有文档,或者将不相关的文档转移到单独的数据库中。此实践通过仅在合适的文档上运行索引来减少了索引实践(根据数据库搭建方式,该数据库可能是一个小得多的文档集合)。
- 对于包含许多文档的数据库,应该一次更新一个设计文档。换句话说,如果您有 100 个租户,每个租户有 250 万个文档, 不要 同时在每个数据库中创建或更新同一个设计文档。相反,应该创建或更新一个数据库的设计文档,监视 Cloudant 集群,等待索引完成,然后再对下一个数据库重复此过程。此过程不会因为一次处理大量重复索引任务而让集群不堪重负,从而使集群保持积极响应状态。
- 绝不更新设计文档。可以创建一个新设计文档,在以后删除旧文档。许多生产系统必须不宕机地运行,而且这些系统通常会在旧代码保持运行的同时升级新代码。这种实践使旧代码处理旧视图,同时新代码在处理新视图。
- 最好为同一个文档发出许多不同的键,以便为它定义多个视图/索引。例如,如果您有一个包含许多 “Person” 文档的数据库,不要定义一个视图来发出表示名字的键(例如 “findPeopleByFirstName”),定义另一个视图来发出表示姓氏的键(“findPeopleByLastName”)。可以定义一个视图来为您打算查询的每个属性发出键(“findPeople”)。此实践减少了索引时间,因为必须在每次更新文档时运行的视图/索引数量减少了,但查询时间可能增加了,因为您要查询的键更多。
优化客户端性能
针对 Cloudant 的所有请求必须通过 HTTP 进行传输,每个 HTTP 请求都具有性能成本。Cloudant 支持各种各样封装 HTTP 细节的客户端库,而且允许调用抽象操作,比如运行视图或创建文档。对于大部分客户,在处理少量请求时,底层 HTTP 请求的性能成本几乎察觉不到。但是,如果有大量与多租户应用程序有关联的请求,投入到 HTTP 请求中的时间量可能会削弱应用程序的性能。
- 调优 HTTP 客户端,以便高效处理大量请求。具体的设置取决于您选择的客户端技术。我们使用了 “nodejs-cloudant” 库,发现使用 HTTP 代理设置
[KeepAlive = true]
重用现有 HTTP 连接可显著提升性能。其他语言(比如 Java)中的 HTTP 客户端实现将此概念称为 HTTP 连接池。 - 批量更新文档,而不一次创建或更新一个文档。请记住,JSON 文档的每个创建、更新和删除操作也是一个 HTTP 请求。如果您的应用程序每隔几天才创建或更新一个文档,那么 HTTP 请求的成本非常低,而且需要的优化很少。但是,如果您的应用程序每秒创建或更新数百个文档,那么对每个文档使用一个单独的 HTTP 请求远远没有为所有文档批量使用一个 HTTP 请求的效率高。Cloudant 批量操作可在一个 HTTP 请求中为任意多个文档处理创建、更新和删除操作,所以您可以通过为所有 3 个操作重用相同的代码来简化应用程序逻辑。单独借助批量操作支持来实现应用程序代码是一个不错的想法,因为您很容易实现单文档函数来作为批量操作请求的特例。聚合各个文档的更新请求也是一个不错的想法。例如,如果每秒有 100 个应用程序用户发出请求来更新 100 个不同文档,那么最好将一个 HTTP 请求发送给 Cloudant,而不是发送 100 个 HTTP 请求。
- 使用
limit
选项限制查询可返回的记录数。太大的结果可能导致客户端内存不足问题。
结束语
在本教程中,我们讨论了改进 Cloudant 在处理多租户服务方面的可靠性和可伸缩性的最佳实践。这包括组织文档和数据库,查询和删除文档,以及性能考虑因素。组织文档和数据库是实现可伸缩性的关键因素,而优化性能是维护可靠性的最重要因素之一。我们还提供了提高客户端性能和减少服务负载的最佳实践。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Cloudant 多租户服务最佳实践
- [译] Kubernetes 多租户集群的实践
- K8s 实践 | 如何解决多租户集群的安全隔离问题?
- OrangeAdmin 橙单中台化低代码生成器发布 v1.4 版本,支持多租户及租户运营管理功能
- 多租户已死!云架构上位
- 一文读懂HBase多租户
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。