对于每一个关注于用户体验的 web 与移动应用程序来说,诸如开源的 Redis 与 Memcached 等基于内存的 NoSQL 存储系统正在成为事实上的标准。但是,近几年间,大型企业对于这些数据库的使用仍发展缓慢,其原因主要归结于性能、可伸缩性及可用性等方面的挑战。
幸运的是,现代编程语言(Ruby,Node.js 和 Python 等)以及开发平台(Rails,Sinatra 和 Django 等)已经直接创建好了一系列的工具和类库,它们能够充分利用基于内存的数据存储系统(Redis 体现得尤为明显)的高性能以及各种操作命令类型,实现了一系列常见的用例。
这些开源软件项目的用例包括了任务管理、论坛、实时分析、twitter clone,地理位置搜索及缓存高级应用。
不过,对于每一个应用程序来说,数据库的可用性、可伸缩性以及性能对于整个应用的成败都有着莫大的影响。
本文概括介绍了为了将你的内存 NoSQL 数据库为企业应用做好准备所需的各种知识,以及在云端管理这些数据库时,如何克服七项最大挑战的提示与建议。
1. 可用性
无论你进行何种操作,你的数据集对应用程序来说应该是始终可用的。这对内存数据库来说尤其重要,因为如果没有应用正确的策略,当以下状况发生时,你就会丢失你的数据集的部分或全部内容:
- 节点失败(这点在云端尤其常见)。
- 进程重启(你或许时常需要进行重启)。
- 系统扩展(希望你会需要到它)。
对于状况 1 与状况 2 的情形(本文稍后会讨论状况 3 的内容),你必须应用两种主要的机制:
- 分发(Replication):你至少必须确保在另一个云端实例中托管一份数据集的拷贝,而如果你要确保在整个数据中心故障发生时(2012 年间,Amazon Web Services 就至少发生了四次这样的情况)仍能够保护你的数据,你最好在一个不同的数据中心保存一份拷贝。遗憾的是做到这一点并不容易。以下这种场景就是在分发时所面临的一项挑战:
- 当你的应用程序中直接写入磁盘的操作大量增长时,你将会发现你的应用服务器的写入速度要高于分发的速度,尤其是你的主结点和分发结点出现网络拥堵时,这种问题会更加严重。一旦这种问题开始产生,如果你的数据集非常大的话,那么你的分发结点有很大的可能性完全停止同步操作。
- 自动故障转移(Auto failover):为什么需要自动故障转移?这是因为你的内存数据库通常每秒处理的请求数量达到其它数据库的 100 倍之多,因此每一秒的停机都意味着你的应用堆积了更多的处理延迟,因此导致了糟糕的用户体验。在实现你自己的自动故障转移机制时,请按照以下列出的建议来做:
- 请确保你的主结点一旦故障时,分发结点能够立刻进行故障转移,这一点应该基于一个健壮的看门狗机制,它持续地监控你的各个结点,并在发生故障时自动转移至某个情况最良好的结点上。
- 这一过程应该尽量对你的应用程序保持透明,理想的情况下应该不需要任何配置的变更。最高级的解决方案是修改 DNS 中数据存储节点的 IP 地址,这可以确保你的恢复过程只需几秒种就可以完成。
- 你的自动故障转移应该基于请求集(Quorum),并且实现完全一致性或者最终一致性。关于这一点的更多信息请见下文。
2. 网络分裂期间与之后的一致性
网络分裂(network splits)在云端频繁发生,它或许是世界上任何一个分布式数据库系统中最复杂的部分。一旦发生分裂,你的应用程序也许只能见到你的全部内存 NoSQL 结点中的一部分,并且你的任意一个内存 NoSQL 结点也只能见到其它内存 NoSQL 结点的一部分。
为什么说这是一个很大的问题呢?如果你的数据库隐含着某些方面的设计缺陷的话,那么当网络分裂发生时,你或许你发现你的应用程序将数据写入了错误的结点。这就意味着,一旦分裂状况恢复时,在此阶段你的应用程序所发出的数据写入请求都将消失。这对于内存 NoSQL 数据库来说是个极大的问题,因为它每秒钟所产生的“写”操作远远大于其它任何 NoSQL 数据库系统。
那么如果你的内存 NoSQL 数据库设计正确呢?很不幸,你将不得不在两种非常糟糕的替代方案(实际上是一种……)中进行选择,如下所示:
- 如果你的内存 NoSQL 数据库是完全一致的,你需要了解的是,在某些情况下它将不允许你写入任何数据,直到网络分裂恢复为止。
- 如果你的内存 NoSQL 数据库是最终一致的,你的应用程序大概在发送读请求时会使用一个请求集向量,它或者返回一个值(基于请求集),或者被阻塞(等待请求集)。
请注意:由于目前市面上还没有任何一种最终一致的内存 NoSQL 数据库存在,实际上你只能选择第 1 个选项。
3. 数据持久化
即使你的内存 NoSQL 解决方案允许多种分发方案,你仍应该考虑数据持久化与备份问题,出于以下几个原因:
- 或许你不愿意为内存分发投入更多的资源,但仍然希望在结点故障发生时能够确保能够在某处保留你的数据集,并能够通过它进行故障恢复(即使恢复速度缓慢)。
- 假设你希望能够从某种故障情况(例如节点故障、多节点故障、数据中心故障等等)进行恢复,并且可以在某个安全所在保留着你的数据集的备份,即使它并不包含你最新的某些变更记录。
- 还有其它诸多原因促使你使用数据持久化,例如将你在生产环境中的数据导入到预发布环境中,以满足调试的需要。
希望我已经使你了解到数据持久化是必要的,在多数云端环境中,你应该为你的云端实例附加一个存储设备(例如 AWS 的 EBS 和 Azure 的 Cloud Drive 等等),如果你依然使用本地磁盘保存数据,那么下次节点故障时数据就会丢失。
一旦你启用了数据持久化机制之后,你的头号挑战就是如何在实时地将数据写入你的持久化存储介质时,保持你的内存 NoSQL 数据库依然高速运作。
4. 稳定的性能
诸如 Redis 与 Memcached 等内存 NoSQL 数据库在设计时的指标是:每秒能够处理超过 10 万次请求,并保证延迟小于毫秒级。但如果你不按照以下方法去做的话,你在云端环境中的速度是达不到这些值的:
- 确保为你的解决方案选择性能最强大的云端实例(例如 AWS 的 m2.2*large/m2.4*large 实例或者 Azure 的 A6/A7 实例),并且将它们保留为专用的环境。作为替代,你也可以实现某种机制,只要该机制能够阻止不同的云端帐号间发生相互影响。这种机制应该基于某种标准对你的数据集性能进行实时监控,并且覆盖每个命令。该机制还需要同时应用一系列其它机制,举例来说,当它发现延迟已超过某个阀值时,能够自动将数据集迁移至某个其它节点。
- 为了避免存储介质的 I/O 瓶颈,请确保为你的解决方案选择一个强大的持久化存储设备,最好是配置了 RAID。随后要确保你的解决方案在请求数量突然爆发时不会阻塞你的应用程序。举例来说,在开源的 Redis 中,你可以配置 slave 节点,让它将数据写入到某个持久化存储设备中,而让主服务器处理你的应用程序的请求,以避免峰值时的请求超时。
- 对于云提供商所建议的存储 I/O 优化手段,例如 AWS 的 PIOPS 进行全面测试。大多数情况下,这些方案在随机访问(读 / 写)时具有良好的表现,但在顺序写操作的场景下,例如那些内存 NoSQL 数据库系统中所使用的方式,这种方案比起标准的存储配置并没有带来额外的优势。
- 如果你的内存数据库像 Redis 一样,是基于某种单线程架构的,请确保不要在同一个单线程进程中运行多个数据库。这种配置会潜在性地产生某种阻塞式的场景,即某个数据库会阻塞另外的数据库执行命令。
5. 网速
多数云端实例都配置了一块独立的 1G 网卡。在内存 NoSQL 数据库的情况下,这 1G 需要处理以下内容:
- 应用程序请求
- 集群内部通信
- 分发
- 存储介质访问
这 1G 流量会很容易成为各种操作的瓶颈,以下是解决该问题的一些建议:
- 使用 10G 流量的云端实例(但请做好准备,它们可是相当昂贵的)。
- 选择能够在某些特殊配置的情况下(例如在 VPC 中)提供多块 1G 网卡的云服务,例如 AWS。
- 建立一种能够有效地在多个内存 NoSQL 节点之间分配资源的解决方案,将网络阻塞降至最低。
6. 可伸缩性
对于简单的键 / 值缓存解决方案来说(例如 Memcached 或者 Redis 的简单应用),一般而言都不会把扩展当作一个大问题,因为在多数情况下,只需将一台服务器加入服务器列表(或从列表中移除),并修改哈希方法即可。不过,富有经验的用户会意识到,扩展仍然可能成为一项令人头疼的任务。以下是处理此问题的一些建议:
- 使用一致性哈希算法。使用像 modulo 这样的简单哈希算法会导致在扩展时丢失全部的 key。另一方面,多数使用者并未察觉到,即使使用一致性哈希算法,在扩展时仍然会丢失部分数据。举例来说,在横向扩展时你就会丢失 1/N 的 key,其中 N 代表扩展后的节点数目。因此如果 N 的数目较小的话,这一过程仍旧是令人头痛的(例如你使用一致性哈希算法对一个包含 2 个节点的集群进行横向扩展,那么将意味着扩展后整个数据集的 1/3 将会丢失)。
- 创建一项机制,在扩展发生时对你所有的内存 NoSQL 客户端进行同步,以避免在扩展过程中不同的应用服务器对不同的节点进行写操作。
在处理复杂的命令,例如 Redis 的 UNION 或者 INTERSECT 时,扩展会成为一个真正的难题。这些命令的作用相当于 SQL 语句中的 JOIN 命令,在对一个多分片(multi-shard)的架构进行操作时,必然需要加入一定量的延迟以及复杂性。如果在应用程序级别进行分片则能够部分解决此问题,因为它允许你在分片级别运行一些复杂的命令。但这意味着,你的应用程序设计会与内存 NoSQL 节点的配置紧密相关,使整个设计变得非常复杂。比方说,支持分片的应用程序必需了解每个键存储在哪一个节点上。并且像重新分片等扩展事件将导致大量的代码变更,并消耗运维部门的大量精力。
作为替代方案,有些用户声称新一代的超高性能 RAM,例如 AWS 的 High Memory Cluster Eight Extra Large 244GB 内存( cr1.8*large )能够通过纵向扩展实例的方式解决多数复杂数据类型的扩展问题。但现实稍有不同,因为像 Redis 这样的内存 NoSQL 数据库,当它的数据集大小达到了 25GB 到 30GB 的规模后,会有许多其它操作上的难题出现,它们会使你执行纵向扩展的计划受阻。这些难题与本文之前描述的诸多挑战密切相关,例如分发、存储介质 I/O、单核的单线程架构,网络开销等等。
7. 运维团队的巨大开销
处理内存 NoSQL 数据库的各种运维操作会消耗巨大的精力。它需要你对这些技术的所有细节都有深入的了解,以保证在各种紧要关头作出正确的决定。它同时要求你紧跟这些系统的最新变化与发展趋势,因为技术的变化是非常频繁的(或许太频繁了些)。
结论
正如我以上所阐述的一样,为了充分利用 Redis 和 Memcached 这些开源技术的各种优点,充分了解它们的各种问题也是非常关键的。对企业级 IT 团队来说,了解如何在企业级环境下以最佳的方式克服这些挑战,以最大程度发挥内存 NoSQL 数据库的作用,这一点是尤其重要的。我对开源项目并没有偏见,但我依然建议寻求一些能够克服可伸缩性及高可用性的种种限制,同时保证在功能与性能方面不会作出任何妥协的商业解决方案。因为执行内存 NoSQL 数据库运维操作需要尖端的领域专家,而这种专家是数量很少的。
当前市面上已经有一些基于 Redis 和 Memcached 的内存 NoSQL 即服务(NoSQL-as-a-service)解决方案存在了。我建议你对这些服务以及自己动手打造解决方案的方式做一个全面的对比,然后再决定对你的应用程序来说,怎样克服这些在云端管理内存 NoSQL 时所面临的挑战才是最佳的方式。对你心仪的解决方案最好能建立一些基于真实项目的经验,这也是为什么很多服务商会提供一个免费试用阶段的原因之一。
关于作者
Yiftach Schoolman是 Garantia Data 的联合创始人之一,并担任 CTO,他是一位经验丰富的技术专家,在多个领域担任过软件开发与产品设计的领导者,包括了应用程序加速、云计算、软件即服务(Saas)、Broadband Networks 与 Metro Networks。Yiftach 也是 Crescendo Networks 公司(后被 F5 – NASDAQ 代码 FFIV 收购)的创始人、主席以及 CTO,Native Networks 公司(后被 Alcatel – NASDAQ 代码 ALU 收购)的 VP 软件工程师,并且是 ECI 电信宽带业务部门的创始团队成员之一,担任 VP 软件工程师。Yiftach 拥有数学及计算机科学专业的学士学位,并且修完了 Tel-Aviv 大学的计算机科学专业的硕士学位。
查看英文原文: How to Make Your In-memory NoSQL Datastores Enterprise-Ready
评论