数据偏移、分区陷阱……咱们这样避开DynamoDB的5个坑

DynamoDB 是 Amazon 基于《 Dynamo: Amazon’s Highly Available Key-value Store 》实现的 NoSQL 数据库服务。它能够知足数据库无缝的扩展,能够保证数据的持久性以及高可用性。开发人员没必要费心关注 DynamoDB 的维护、扩展、性能等一系列问题,它由 Amazon 彻底托管,开发人员能够将更多的精力放到架构和业务层面上。数据库

本文主要介绍做者所在团队在具体业务中所遇到的挑战,基于这些挑战为什么最终选型使用 Amazon DynamoDB,在实践中遇到了哪些问题以及又是如何解决的。文中不会详细讨论 Amazon DynamoDB 的技术细节,也不会涵盖 Amazon DynamoDB 的所有特性。微信

背景与挑战

TalkingData 移动广告效果监测产品(TalkingData Ad Tracking)做为广告主与媒体之间的一个广告投放监测平台,天天须要接收大量的推广样本信息和实际效果信息,并最终将实际的效果归因到推广样本上。架构

举个例子,咱们经过手机某新闻类 APP 浏览信息,会在信息流中看到穿插的广告,广告多是文字形式、图片形式、视频形式的,而无论是哪一种形式的广告它们都是能够和用户交互的。负载均衡

若是广告推送比较精准,恰好是用户感兴趣的内容,用户可能会去点击这个广告来了解更多的信息。一旦点击了广告,监测平台会收到这一次用户触发的点击事件,咱们将这个点击事件携带的全部信息称为样本信息,其中可能包含点击的广告来源、点击广告的时间等等。一般,点击了广告后会引导用户进行相关操做,好比下载广告推荐的 APP,当用户下载并打开 APP 后,移动广告监测平台会收到这个 APP 发来的效果信息。到此为止,对于广告投放来讲就算作一次成功转化。
微信图片_20191015164915.png框架

DynamoDB实践:当数据量巨大而不可预知,如何保证高可用与实时性?函数

移动广告监测平台须要接收源源不断的样本信息和效果信息,并反复、不停的实时处理一次又一次转化。对于监测平台来说,它的责任重大,不能多记录,也不能少记录,若是转化数据记多了广告主须要多付广告费给媒体,记少了媒体会有亏损。这样就为平台带来了几大挑战:性能

  • 数据量大:有的媒体为了利益最大化可能会采起非正常手段制造假的样本,产生“虚假流量”,因此广告监测平台除了会收到真实用户样本外,也会收到大量造假的样本,影响正常的监测和归因。在最“疯狂”的时候,咱们的平台会在一天内收到 40 亿 + 的点击样本事件请求。要知道,这些点击样本事件是要保留下来做为后续效果归因用的,并且样本有效期大不相同,从最短 12 小时到最长 90 天不等。
  • 数据量不可预知:对于广告主的大推、应用商店竞价排名等一系列的推广,会致使突发大量样本数据流入。面对这些流量不可预知的状况,咱们仍要保证系统的正确、稳定、实时。
  • 实时处理:广告主依赖广告监测平台实时处理的结果来调整广告推广策略。所以广告监测平台须要支持数据实时处理,才能为广告主更快的优化推广策略提供有力支撑。与此同时,广告监测平台的处理结果也要实时回传给媒体方以及广告主。能够看到,准确性和实时性是系统必需要知足的基本条件。
  • 样本存储:咱们的业务最核心的功能就是归因,咱们要明确例如用户下载打开 APP 的这个转化效果是由哪个推广活动样本带来的——也就是上图中的第 7 步,当用户安装 APP 后,监测平台要对应找到第 1 步中样本所在推广活动,这个是一个查询匹配的过程。对于庞大的归因样本数据,有效期又各不相同,咱们应该怎样存储样本才能让系统快速归因,不影响实时结果,这也是一个很大的挑战。

最初形态

在 2017 年 6 月前咱们的业务处理服务部署在机房,使用 Redis Version 2.8 存储全部样本数据。Redis 使用多节点分区,每一个分区以主从方式部署。在最开始咱们 Redis 部署了多个节点,分红多个分区,每一个分区一主一从。刚开始这种方式尚未出现什么问题,但随着用户设置的样本有效期加长、监测样本增多,当时的节点数量逐渐已经不够支撑业务存储量级了。若是用户监测推广量一旦暴增,咱们系统存储将面临崩溃,业务也会瘫痪。因而咱们进行了第一次扩容。优化

因为以前的部署方式咱们只能将 Redis 的多个节点翻倍扩容,这一切都须要人为手动操做,而且在此期间咱们想尽各类办法来保护用户的样本数据。
微信图片_20191015165202.png
DynamoDB实践:当数据量巨大而不可预知,如何保证高可用与实时性?编码

这种部署方式随着监测量的增加以及用户设定有效期变长会愈来愈不堪重负,当出现不可预知的突发量时就会产生严重的后果。并且,手动扩容的方式容易出错,及时性低,成本也是翻倍的增加。在当时因为机器资源有限,不只 Redis 须要扩容,广告监测平台的一系列服务和集群也须要进行扩容。架构设计

化解挑战

通过讨论和评估,咱们决定将样本处理等服务迁移到云端处理,同时对存储方式从新选型为 Amazon DynamoDB,它可以知足咱们的绝大部分业务需求。通过结构调整后系统大概是下图的样子:
微信图片_20191015165206.png
DynamoDB实践:当数据量巨大而不可预知,如何保证高可用与实时性?

  • 应对数据量大且不可预知:咱们的平台须要接受推广监测链接请求,并进行持久化用于后续的数据归因处理。理论上来讲系统进来多少广告监测数据请求,DynamoDB 就能存多少数据,只须要一张表就能够存储任意数量级的数据。不用关心 DynamoDB 扩容问题,在系统运行时咱们感知不到存储正在扩容。这也是 Amazon 官方宣称的彻底托管、无缝扩展。
  • 高可用:Amazon DynamoDB 做为存储服务提供了极高的可用性,对于写入 DynamoDB 的所有数据都会存储到固态硬盘中,而且自动同步到 AWS 多个可用区,以达到数据的高可用。这些工做一样彻底由 Amazon DynamoDB 服务托管,使用者能够将精力放到业务架构及编码上。
  • 实时处理:Amazon DynamoDB 提供了极高的吞吐性能,而且支持按秒维度配置任意级别的吞吐量。对于写多读少的应用能够将每秒写入数据的数量调整成 1000 甚至更高,将每秒读取的数量下降到 10 甚至更少。吞吐量支持使用者任意设定,在设定吞吐量时除了能够随时在 Web 管理后台调整外,还能够经过 DynamoDB 提供的客户端动态调整。好比系统在运行时写入能力不足了,咱们能够选择到 Web 管理后台手动上调或在代码中经过调用客户端 API 的方式实现自动上调。使用客户端动态调整的方式会让系统具有较高的收缩能力,同时还能够保证数据的实时处理,系统数据流量变高了就动态调整上去,数据流量变低了再动态调整下来。相比手动调整来看,动态调整的方式更为灵活。基于以上几点,咱们认为 Amazon DynamoDB 能够很轻松的支撑系统的核心业务能力。对于业务侧须要作的就是,整理好业务逻辑把数据写到 DynamoDB 就能够了,剩下的就交给 DynamoDB 去作。

此外还有:

  • TTL:咱们利用了 Amazon DynamoDB 提供的 TTL 特性管理那些有生命周期的数据。TTL 是对表中要过时的数据设置特定时间戳的一种机制,一旦时间戳过时 DynamoDB 在后台会删除过时的数据,相似于 Redis 中的 TTL 概念。借助 TTL 的能力,咱们减小了不少业务上没必要要的逻辑断定,同时还下降了因存储量带来的成本。
  • 流:在咱们的业务中没有启用流来捕获表的动做,但咱们认为 DynamoDB 流是一个很是好的特性,当存储在 DynamoDB 表中的数据发生变动(新增、修改、删除)时,通知到相关的服务 / 程序。好比咱们修改了一条记录的某个字段,DynamoDB 能够捕获到这个字段的变动,并将变动先后的结果编写成一条流记录。

实践出真知

咱们在使用一些开源框架或服务时总会遇到一些“坑”,这些“坑”其实也能够理解为没有很好的理解和应对它们的一些使用规则。DynamoDB 和全部服务同样,也有着它本身的使用规则。在这里主要分享咱们在实际使用过程当中遇到的问题以及解决办法。

数据偏移

在 DynamoDB 中建立表时须要指定表的主键,这主要为了数据的惟一性、可以快速索引、增长并行度。主键有两种类型,「单独使用分区键」做为主键和「使用分区键 + 排序键」做为主键,后者能够理解为组合主键(索引),它由两个字段惟一肯定 / 检索一条数据。DynamoDB 底层根据主键的值对数据进行分区存储,这样能够负载均衡,减轻单独分区压力,同时 DynamoDB 也会对主键值尝试作“合理的”分区。

在开始咱们没有对主键值作任何处理,由于 DynamoDB 会将分区键值做为内部散列函数的输入,其输出会决定数据存储到具体的分区。但随着运行,咱们发现数据开始出现写入偏移了,并且很是严重,带来的后果就是致使 DynamoDB 表的读写性能降低,具体缘由在后面会作详细讨论。发现这类问题以后,咱们考虑了两种解决办法:

微信图片_20191015165206.png

因此咱们选择了第二种方法,调整业务代码,在写入时将主键值作哈希,查询时将主键条件作哈希进行查询。

自动扩容潜规则

在解决了数据偏移以后读 / 写性能恢复了,可是运行了一段时间以后读写性能却再次降低。查询了数据写入并不偏移,当时咱们将写入性能提高到了 6 万 +/ 秒,但没起到任何做用,实际写入速度也就在 2 万 +/ 秒。最后发现是咱们的分区数量太多了,DynamoDB 在后台自动维护的分区数量已经达到了 200+ 个,严重影响了 DynamoDB 表的读写性能。

DynamoDB 自动扩容、支持用户任意设定的吞吐量,这些都是基于它的两个自动扩容规则:单分区大小限制和读写性能限制。

单分区大小限制

DynamoDB 会自动维护数据存储分区,但每一个分区大小上限为 10GB,一旦超过该限制会致使 DynamoDB 拆分区。这也正是数据偏移带来的影响,当数据严重偏移时,DynamoDB 会默默为你的偏移分区拆分区。咱们能够根据下面的公式计算分区数量:
数据总大小 / 10GB 再向上取整 = 分区总数

好比表里数据总量为 15GB,15 / 10 = 1.5,向上取整 = 2,分区数为 2,若是数据不偏移均匀分配的话两个分区每一个存储 7.5GB 数据。

读写性能限制

DynamoDB 为何要拆分区呢?由于它要保证用户预设的读 / 写性能。怎么保证呢?依靠将每一个分区数据控制在 10G 之内。另外一个条件就是当分区不能知足预设吞吐量时,DynamoDB 也会将分区进行扩充。DynamoDB 对于每一个分区读写容量定义以下:

  • 写入容量单位:写入容量单位(WCU:write capacity units),以每条数据最大 1KB 计算,最大每秒写入 1000 条。
  • 读取容量单位:读取容量单位(RCU:read capacity units),以每条数据最大 4KB 计算,最大每秒读取 3000 条。

也就是说,一个分区的最大写入容量单位和读取容量单位是固定的,超过了分区最大容量单位就会拆分区。所以咱们能够根据下面的公式计算分区数量:

(预设读容量 /3000)+(预设写容量 /1000)再向上取整 = 分区总数

好比预设的读取容量为 500,写入容量为 5000,(500 / 3000) + (5000 / 1000) = 5.1,再向上取整 = 6,分区数为 6。

须要注意的是,对于单分区超过 10G 拆分后的新分区是共享原分区读写容量的,并非每一个表单独的读写容量。
由于预设的读写容量决定了分区数量,但因为单分区数据量达到上限而拆出两个新的分区。
因此当数据偏移严重时,读写性能会急剧降低。

冷热数据

产生上面的问题是因为咱们一开始是单表操做。这样就算数据不偏移,但随着时间推移数据量愈来愈多,天然拆出的分区也愈来愈多。

所以,咱们根据业务作了合理的拆表、设定了冷热数据表。这样作有两大好处:

  1. 提高性能:这一点根据上面的规则显而易见,热表中数据量不会持续无限增加,所以分区也稳定在必定数量级内,保证了读写性能。
  2. 下降成本:无谓的为单表增长读写性能不只效果不明显,并且费用也会急剧增高,使用成本的增长对于谁都是没法接受的。DynamoDB 存储也是须要成本的,因此能够将冷表数据存储到 S3 或其余持久化服务中,将 DynamoDB 的表删除,也是下降成本的一种方式。

表限制

表对于数据的大小以及数量并无限制,能够无限制的往一张表里写入数据。但对于 AWS 的一个帐户,每一个 DynamoDB 使用区域的限制为 256 张表。对于一个公司来讲,若是共用同一个帐号的话可能会存在建立表受限的风险。因此若是启用了冷热表策略,除了删冷表下降成本外,也是对 256 张表限制的一种解决办法。

属性名长度

上面提到了写入单位每条数据最大 1KB、读取单位每条最大 4KB 的限制。单条数据的大小除了字段值占用字节外,属性名也会占用字节,所以在保证可读性的前提下应尽可能缩减表中的属性名。

总结

DynamoDB 的使用也是存在成本的,主要体如今写入和读取的费用。咱们本身研发了一套按照实际流量实时调整读、写上限的策略。随着发展 DynamoDB 也推出了 Auto Scaling 功能,它实现了自定义策略动态调整写入与读取上限的能力,对于开发者来讲又能够省去了很多研发精力。目前咱们也有部分业务使用了 Auto Scaling 功能,但因为该功能的限制,实际使用上动态调整的实时性略显欠缺。

做者介绍:

史天舒,资深 Java 工程师,硕士毕业于北京邮电大学。任职于 TalkingData,目前从事移动广告监测产品 Ad Tracking 相关架构设计与开发。喜欢研究代码,注重系统高扩展设计,略有代码洁癖。
相关文章
相关标签/搜索