DynamoDB 到底有什么不一样?

在近期的工作中,使用了 DynamoDB 作为一部分服务的数据库,因此整理了一些相关的知识,并结合项目实际使用过程中遇到的一些问题与大家分享下。

简介

DynamoDB 是一款由 AWS(Amazon Web Services) 提供的 全托管 模式的 NoSQL 服务,AWS 进行运维。DynamoDB 可以存储文档或者键值对类型的数据,号称可以以非常低的延迟支持 任何级别的负载 ,同时有着较完善的权限管理系统。

为什么选择 DynamoDB:

  1. 简化运维:全托管模式,减少创建、维护数据库的开销;跨区域的分区存储,数据更安全;
  2. 自动扩容:数据量的增长对数据库性能没有太多影响
  3. 灵活的表结构:只需要定义分区键即可正常使用,随时增加减少字段
  4. 事件流:流结合 Lambda 可以非常方便的实现一些功能

基本内容

  • 表(Table)
  • 文档(Document)
  • 属性(Attribute)
  • 分区键(Partition Key/Hash Key, 记为 PK,必需,数据类型必须是基本类型)
  • 排序键(Sort Key/Range Key, 记为 SK,可选)
  • 数据类型:
    • 基本类型: number, string, binary, Boolean, and null
    • 文档: list, map
    • 集合: set

DynamoDB 的数据集合是一张表,表里的每一条数据就是一个文档,文档中可以存储基本类型的数据(BOOLEAN, NUMBER, STRING)或者一些复杂的数据(MAP, LIST, SET),可以嵌套较为深的层级。在创建一张表的时候,需要指定一个基本类型的属性值作为 PK,可以再指定一个基本类型的属性值作为 SK。PK 的作用是将数据分散到不同的分区(Partition)里,构建无序的哈希索引,SK的作用是将同一个分区中的数据按照一定的顺序排列起来,便于查找。SK 中可以使用 ==, <, >=, <=, begins with, between, contains, in 等函数来进行较为丰富的查询操作,PK 必须使用准备的值进行查询。

与 SQL 的 异同

SQL NoSQLj
Optimized for storage Optimized for compute
Normalized/relational Denormalized/Hierarchical
Ad hoc queries Instantiated view
Scale vertically Scale horizontally
Good for OLAP Built for OLTP at scale

SQL 是 为存储进行优化 的,通过存储范式化的数据,尽量减少了存储空间的使用,同时构建了非常强大的查询能力,可以通过新建不同的表对数据库容量进行扩张,适用于 OLAP 应用。

DynamoDB 是一种 NoSQL 的数据库针, 对 计算资源进行了优化 ,以存储空间换计算时间。数据表中直接存储了非范式化的数据,可以直接拿出来使用。其中数据可以进行水平扩张,建表时无需指定确定的表结构,适用于 OLTP 应用。

索引

DynamoDB 共有两种索引,一种是本地二级索引(Local Secondary Index,简称 LSI),一种是全局二级索引(Global Secondary Index,简称 GSI)

本地二级索引 (LSI)

本地二级索引和原表共用一份 Partition, 使用同一个 PK, 共用这一个 PK 的存储空间,上限为 10GB。LSI 提供了不同的筛选方式和排序方式,选用不同的属性作为 SK 可以提高查询的灵活性。

全局二级索引 (GSI)

全局二级索引几乎相当于一个全新的表,除了与原表共用同一个表名之外,GSI 使用了新的 Partition Key 和 Sort Key, 有自己独立的 Partition, 计算读写数量时也与原表相互独立。GSI 会在原表发生改变的时候,通过流(Stream)将更新同步到 GSI 中。一个表可以建立 20 个全局二级索引。推荐使用全局二级索引,避免本地二级索引引起的读写容量的竞争,同时可以重新定义 PK 和 SK,支持更多的查询模式

读一致性

DynamoDB 由于是数据库云服务出身,因此在设计中着重考虑了数据的 可用性分区容忍性。从 CAP(Consistency, Availability, Partitioning)理论的角度来讲,DynamoDB 是一个 AP 的数据库。

在 DynamoDB 中,共有两个部分会涉及到读一致性的问题:主表读和GSI读。

主表读过程会产生一致性问题的原因是 DynamoDB 的 Partition 是有 三个副本的(实际为可用区,详细内容可查询“AWS区域和终端节点及可用区”),默认情况下写入到两个副本中即认为写入成功,因此在高频读取时也有一定的概率读到未完成写入的数据。可以通过配置 GetItem, Scan 等的操作一致性为 强一致性来避免这个问题,但可能会带来更高的延迟(10ms级别)和吞吐量开销(强一致性为最终一致性吞吐量开销的二倍)。

GSI 读写会产生一致性的原因是 GSI 和主表实际是 两个不同的存储,写入到主表的数据会通过流同步到GSI,这个过程会存在一定的延时(10ms级别),因此在读取 GSI 中的数据时,不能设置为强一致性

表结构设计

对于关系型数据库来说,我们只需要将数据模型范式化之后,分别建表,在查询时使用 SQL 进行关联即可实现不同的查询方式,更适合于查询方式复杂多变的系统。 而 DynamoDB 是需要提前考虑好一些查询方式,才能更好的设计表结构,否则可能会在使用的过程中遇到一些不便。

DynamoDB 限于底层结构设计,进行表扫描(Scan)的操作非常耗时,对于较大的表来说基本上是 不可接受的时间消耗,因此需要根据 DynamoDB 的特点对表结构进行巧妙的设计,以实现需要的查询组合。因此,设计 DynamoDB 的第一步便是列举出可能的查询方式。当我们有了一组查询方式之后,便可以开始进行表结构的设计。

基本查询

在设计出查询方式之后,便可以优先关注使用量最多,查询方式最简单的查询需要。以下为主表中几种常见的查询方式:

  1. 使用 PK 直接查询。可以查出该分区键下的所有数据,适合于 一对多关系。例如查询一个用户下的多个订单记录,这时 PK 为用户的 ID
  2. 使用 PK + 完整 SK 进行查询。如果一个 PK 下的数据比较多,可以结合 SK 来实现更为精确的查询。例如查询一个用户下的一个特定的订单,这时 PK 为用户的 ID,SK 为订单的ID。
  3. 使用 PK + SK (==, <, >=, <=, begins with, between, contains, in) 进行查询。如果一个 PK 下数据比较多,同时每一类 SK 上有通用的前缀,可以用 begins with 来进行查询。例如查询用户的订单使用用户 ID 和 SK 前缀 order: 进行查询。

索引重载

索引重载是使用 RDS 的用户最容易感到不适应的一部分,这意味着属性名不一定是一个有实际含义的名称,其内容也可能包含多个种类。AWS 的 Principal Technologist, Rick Houlihan 曾说,一个设计良好的 DynamoDB 应该只有一张表。这显然与我们对 RDS 数据的认识完全不一样,RDS 倾向于细粒度的划分每个实体,为实体及实体之间的关系建立相关的表。如果说我们一个应用只有一张表,那么意味着用户、订单、商品、发票等数据会共用一个表定义,表的结构可能类似下面这个样子:

PK SK(GSI-PK) DATA(GSI-SK) DATA1 DATA2
用户ID profile xx xx xx
用户ID order-1 yy xx xx
用户ID order-2 zz xx xx
用户ID invoice-1 vv xx xx
商品ID detail xx xx xx

可以使用 #User#{ID} 和 #PRODUCT#{ID}这样的方式在 ID 中带上该条记录的类型,SK 也可以采用类似的设计方式,这样一方面我们的可以更好的辨识一条数据的类型,另一方面当 PK 在 GSI 中作为 SK 时,可以使用 begins with 查出同一类的数据。

因为 DynamoDB 可以自动扩容,所设计好这一个表之后,我们便可以方便的对该表创建 GSI,开启备份,使用同样的查询、数据插入的命令来完成各种各样的操作,同时因为使用同一个读写容量,所以会减少读写容量的浪费。新建不同的表来存储这些内容,其实新建的表与该表也不会太大的差异,使用同一张表反而更加方便。

在使用 PK 和 SK 进行 Query 之后,还可以使用 Filter 进行数据的筛选,这些筛选只能使用在基础数据类型上面,因此如果存储 list, map, set 时可能会简化一些操作,但在筛选时遇到一些问题。这种时候就可以利用 sk 的 begins with 查询功能,将数据拆分开来方便筛选。

稀疏索引

创建 GSI 时,我们会重新选择一个基础类型的字段作为主键,这时,如果部分数据并不包含这个字段,那么这些数据就不会被添加到这个 GSI 索引中来。因此我们可以利用这个特性为不同的查询模式来创建不同 GSI,以减少 GSI 中数据的数量。

PK SK(GSI-PK) DATA(GSI-SK) DATA1 DATA2
用户ID profile xx xx xx
用户ID order-1 yy xx xx
用户ID order-2 zz xx xx
用户ID invoice-1 vv xx xx
商品ID detail xx xx

如上表中我们以 DATA2 来建立 GSI,那么最后一条和商品相关的数据便不会进入到 GSI 中。

组合键

组合键也是一个非常有用的方式,如果有一些字段必须复合起来使用,并且会作出相对复杂的筛选条件,那么我们便可以考虑采用组合键来处理。如以下示例:

Opponent Date GameId Status Host
Alice 2014-10-02 d9bl3 DONE David
Carol 2014-10-08 o2pnb IN_PROGRESS Bob
Bob 2014-09-30 72f49 PENDING Alice
Bob 2014-10-03 b932s PENDING Carol
Bob 2014-10-03 ef9ce IN_PROGRESS David

我们希望达成类似 SELECT * FROM Game WHERE Opponent = 'Bob' ORDER BY DATE DESC FILTER ON Status='PENDING' 的查询,如果 Bob 的数据有上千条,但 IN_PROGRESS 的数量只有一条,那使用 Query + Filter 的结果是需要查到这千条数据,再从中过滤出需要的数据来。但如果我们使用组合键 StatusDate = Status + Date, 如下示例:

Opponent StatusDate GameId Host
Alice DONE_2014-10-02 d9bl3 David
Carol IN_PROGRESS_2014-10-08 o2pnb Bob
Bob IN_PROGRESS_2014-09-30 72f49 Alice
Bob PENDING_2014-10-03 b932s Carol
Bob PENDING_2014-10-03 ef9ce David

那我们便可以直接查到我们需要的这两条数据来,我们也可以继续使用 Filter 再进行过滤。同时会有一些特殊的要求,在不使用组合键时,如果进行查询会是一个相当大的挑战。

分级数据

与组合键类型,分级数据意味着该字段中的数据不是一个简单的数据,也会是一个拼起来的数据,最简单的例子是地域信息。比如四川省成都市,可以存储为 #SICHUAN#CHENGDU,如果后面有多少级均可以继续接在后面。通过这种方式便把一个层级很深的数据转化成了一个简单的数据,同时使用 begins with便可以查某个区划下的所有信息。

本地开发

DynamoDB 是 AWS 的云服务,可以使用 AWS 提供的 aws-cli, 通过命令行来完成常见的表操作及数据操作。最为常用的功能是结合其它命令一起进行查询及数据分析,或者清除表中的数据。同时,如果在开发过程中需要使用 DynamoDB,依然需要付出相应的服务费用,使用 DynamoDB Local 可以减少这部分开销。

CLI 示例

aws dynamodb create-table \
    --table-name music \
    --attribute-definitions \
        AttributeName=artist,AttributeType=S \
        AttributeName=song_title,AttributeType=S \
    --key-schema \
        AttributeName=artist,KeyType=HASH \
        AttributeName=song_title,KeyType=RANGE \
--provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5

aws dynamodb describe-table --table-name music | grep TableStatus

aws dynamodb put-item \
    --table-name music  \
    --item \
        '{"artist": {"S": "No One You Know"}, "song_title": {"S": "Call Me Today"}, "album_title": {"S": "Somewhat Famous"}, "awards": {"N": "1"}}'

aws dynamodb get-item --consistent-read \
    --table-name music \
    --key '{ "artist": {"S": "Acme Band"}, "song_title": {"S": "Happy Day"}}'

aws dynamodb update-item \
    --table-name music \
    --key '{ "artist": {"S": "Acme Band"}, "song_title": {"S": "Happy Day"}}' \
    --update-expression "SET album_title = :newval" \
    --expression-attribute-values '{":newval":{"S":"Updated Album Title"}}' \
    --return-values ALL_NEW

aws dynamodb update-table \
    --table-name music \
    --attribute-definitions AttributeName=album_title,AttributeType=S \
    --global-secondary-index-updates \
    "[{\"Create\":{\"IndexName\": \"album_title-index\",\"KeySchema\":[{\"AttributeName\":\"album_title\",\"KeyType\":\"HASH\"}], \
    \"ProvisionedThroughput\": {\"ReadCapacityUnits\": 10, \"WriteCapacityUnits\": 5      },\"Projection\":{\"ProjectionType\":\"ALL\"}}}]"

aws dynamodb query \
    --table-name music \
    --index-name album_title-index \
    --key-condition-expression "album_title = :name" \
    --expression-attribute-values  '{":name":{"S":"Somewhat Famous"}}'

aws dynamodb query \
    --table-name music \
    --key-condition-expression "artist = :name" \
    --expression-attribute-values  '{":name":{"S":"Acme Band"}}'

aws dynamodb scan --table-name music --filter-expression 'attribute_exists(artist)'

aws dynamodb delete-table --table-name music

// 使用 jq 获取某个字段并取得不重复值
aws dynamodb scan \
    --table-name music \
    --index-name business-index \
    --projection-expression some_data \
    | jq '.Items[] | .data.S' | sort --unique

DynamoDB Local

如果在本地进行开发,可以使用 DynamoDB Local, 一种方式是通过 DynamoDBLocal 的 jar 包直接运行,另一种方式是使用 Docker 运行 DynamoDB 的镜像。命令为
docker run -p 8000:8000 -v $(pwd)/data:/data/ amazon/dynamodb-local:1.11.477 -jar DynamoDBLocal.jar -sharedDb -dbPath /data

可以使用 DynamoDB GUI 作为图形化的管理工具或者直接使用 aws-cli 加上参数 --endpoint-url http://localhost:8000 进行管理。

Stream

启用 DynamoDB Stream 之后,DynamoDB 中的数据在发生变化时,均会发送相应的事件流,默认会记录新旧数据,可以据进建立事件系统或者采取进一步的操作。比如可以使用 Lambda 接收 Stream 并将数据填充到 ElasticSearch 中便于进行复杂的查询,或者同步一些数据到其它的表中,或者进行一些聚合计算,均可以通过 DynamoDB Stream 方便的完成。

Tips

  • Truncate 操作:DynamoDB 不支持 Truncate 操作,最简单的办法是删表重建即可;如果需要删掉一部分数据,可以写脚本用 scan 查出 PK 的列表逐个进行删除;还可以设置好表的过期时间,让这批数据定期失效即可。
  • JavaScript 有两个类库,一种使用了 DynamoDB Json,其中包括了数据的类型,需要调用相关的 marshal 和 unmarshal 方法来转换成标准的 Json
  • DynamoDB 中批量操作有 25 的数据量限制,部分情况下需要手动分批分成多个请求来处理,部分类库提供了自动分批的功能
  • DynamoDB 无法写入空值、空字符串,会直接忽略相应字段
  • 非基本类型的数据可以当作 String 进行查询
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 1.GSI与LSI区别 Amazon DynamoDB 通过指定主键值来提供对表中项目的快速访问。但是,很多应用程...
    daqiaowijiu阅读 3,866评论 0 1
  • 什么是DynamoDB Amazon DynamoDB 是一种完全托管的 NoSQL 数据库服务,提供快速而可预测...
    OceanDog阅读 4,465评论 0 7
  • Chapter 7: Databases and AWS B. Amazon RDS is best suited...
    K1024阅读 644评论 0 0
  • 索引 数据库中的查询操作非常普遍,索引就是提升查找速度的一种手段 索引的类型 从数据结构角度分 1.B+索引:传统...
    一凡呀阅读 2,853评论 0 8
  • 日暮,夕阳的影子。摇晃,摇晃—— 摇晃在木讷的眼底。缄默,不语,无声吐息。 看田野温和,山林的寂静,水上的涟漪。 ...
    寒羽兮阅读 162评论 1 3