【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

AWS 系列:Dynamodb 深度体验(1)

  • 2016-01-17
  • 本文字数:4063 字

    阅读完需:约 13 分钟

编者按:本文系 InfoQ 中文站向陈天的约稿,这是 AWS 系列文章的第三篇。以后会有更多文章刊出,但并无前后依赖的关系,每篇都自成一体。读者若要跟随文章来学习 AWS,应该至少注册了一个 AWS 账号,事先阅读过当期所介绍服务的简介,并在 AWS management console 中尝试使用过该服务。否则,阅读的效果不会太好。DynamoDB 看似简单,用起来则相当麻烦。本文将探讨如何进行 DynamoDB 的数据库(表)设计,如何使用 hash key / range key 使得查询更方便快捷?如何缓冲对 DynamoDB 的突发读写,如何自动 scale up / scale down。

基本概念

DynamoDB 是 AWS 独有的完全托管的 NoSQL Database。它的思想来源于 Amazon 2007 年发表的一篇论文: Dynamo: Amazon’s Highly Available Key-value Store 。在这篇论文里,Amazon 介绍了如何使用 commodity hardware 来打造高可用、高弹性的数据存储,这篇文章影响了很多 NoSQL 数据库的设计,如 cassandra / riak,也最大程度地将 consistent hashing 这个概念从学术界引入了工业界。欲理解 DynamoDB,首先要理解 consistent hashing。consistent hashing 的原理如下图所示:

它的概念是:我有一个足够大的 keyspace(2 的 160 次方,比较一下:IPv6 是 2 的 128 次方),我们记作 X,然后将 X 放在一个环形的空间里划分成大小相等的 Y 个 partition,依次循环排列(如图),每个 partition 由一个 vnode(riak 的概念)管理,当你有 M 个 database server(node),Y 个 vnode 再平均映射到 M 个 node 上。当数据要插入时,将其主键(hash key)映射到 K 中的一个地址(addr),对应到某个 vnode,再进一步对应到某个 node,如果这个数据需要 N 个 replica,则将数据写入 addr(vnode a),addr + 1(vnode b), …​,add + N(vnode n)。在这种设置下,M 就是你的 shards,N 是 replica。以后添加新的 node 时,映射发生变化,只需要把相应的变化了的 vnode 迁移到新的 node 上即可。在这种结构下,sharding/replica 对程序员基本上是透明的。

在 DynamoDB 中,为持久化,一份数据被写入到三个 replica 中。至于需要多少个 partition(或者说 shard),则由开发者为每个 DynamoDB table 设置的读写属性来确定。对于一个有三个 partition,三个 replica 的表,数据是这样分布的:

DynamoDB 组织数据的基本单元是 table,类似于 MongoDB 的 collection,或者 RDBMS 里的 table,每个 table 里可以有任意数量的 item,类似于 MongoDB 的 document,或者 RDBMS 里的 row。而每个 item,必须有一个 hash key,用于做 consistent hashing,还可以有一个可选的 range key,用于查询。如果 range key 存在,则 hash key 可以重复,hash key + range key 唯一确定一个 item 即可;如果 range key 不存在,则 hash key 必须唯一。

我们知道,一个 hash key 只会存在于同一个 partition 上,因此,相同 hash key 不同 range key 的 item 也会在同一个 partition。如果 hash key 选择不好,容易产生过热的 partition,影响效率。

对于 hash key,查询的动作很单一,就是 Get,或者 BatchGet,在同一个 partition 内部,数据是按照 range key 进行排序的。

如果你要使用 hash key / range key 以外的字段进行查询,你必须为要查询的字段创建索引(LocalIndex,Global Secondary Index)。DynamoDB 里面的 Global Secondary Index 实质上是一张表,只不过和主表联动更新而已。

从其他数据库迁移到 DynamoDB

很多用户在第一次接触 DynamoDB 时,已经有了很多其他 DB 的工作经验,这时候,照搬已有的经验去使用 DynamoDB,会遇到很多挑战。

Race Condition

在 MySQL 或者 MongoDB 里,同一个 db client 的若干次写请求是排队的,这个特性会导致应用程序的一些 race condition 在 DB 层面被掩盖了。比较经典的问题是 Session Store。大部分的应用框架都会使用 DB 来存储 Session,然后在每个 request 到来时刷新 Session。其中,第一次访问的 request 和用户登录的 request 处理起来会特别一些,前者创建 Session,后者会在 Session 中附加用户信息,而其它的 request 对于 Session 而言,只是刷新过期时间而已。框架一般会将这些操作区分开来,比如,在 nodejs 的 express 框架中,Session Store 定义了几种基本的操作:getsettouchget 用于获取 Session,set 用于创建 / 更新(replace)Session,touch 用于刷新(update timestamp)Session。很多 DB 的实现采用偷懒的方式,不定义 touch,将刷新的行为和更新的行为等同起来,这会导致同一个用户的多个并发的 request 进入 Session 更新的 race condition,如果恰巧其中的一个 request 是处理用户登录,那么用户登录在 SessionStore 里所做的操作有可能会被其他 request 的操作覆盖,导致登录不成功:

复制代码
get.set.....done // 登录
.......get.set.....done // 刷新

这个问题在 express-mysql-sessionexpress-mongo-session 里都存在,只不过被数据库的串行访问给掩盖了:

复制代码
get.set.....done
................get.set.....done

但 DynamoDB 的任何访问都是无状态的,它没有 Connection 的概念,所有的操作都是一个 HTTPS 请求。这是为何同样逻辑的代码(见:connect-dynamo,使用 DynamoDB 就会触发 race condition。这并非 DynamoDB 的毛病,只不过 DynamoDB 帮我们将这种问题显现出来而已。

数据更新

初学 DynamoDB 的用户,在使用 updateItem 时,胸毛上一定有万千个草泥马在奔腾。MySQL 的 update 语句简单直观,MongoDB 的 $set 操作稍稍有些绕,但逻辑还算比较正常,读一下手册,立刻知道该怎么写。可是 Amazon 不给 DynamoDB 好好地整 API,下面的查询代码在初次相见时,有一种如鲠在喉的感觉:

复制代码
{
TableName: 'AwesomeTable',
Key: {
id: { S: 'AwesomeID' }
},
UpdateExpression: 'SET #attrName =:attrValue',
ExpressionAttributeNames: { '#attrName': 'attr_to_be_updated' },
ExpressionAttributeValues: { ':attrValue': { S: 'value_to_be_updated'} }
}

更要命的是,API 复杂也就罢了, API 文档 还不给你好好写,初学者如无帮助,根本无法使用。

数据查询

在 DynamoDB 里,要想根据索引查询数据,必须显式指定 IndexName,这也是很多从其它 DB 迁移过来的用户感觉很绕的地方:

复制代码
{
TableName: 'AwesomeTable',
IndexName: EXPIRES_INDEX,
KeyConditionExpression: '#hash_key = :v_hkey AND #range_key < :v_rkey',
ExpressionAttributeNames: {'#hash_key': '<hash-key>', '#range_key': '<range-key>'},
ExpressionAttributeValues: {
':v_hkey': {'S': '<some-value>'},
':range_key': {'N': <some-num>.toString()}
},
ProjectionExpression: '<fields-you-want-to-projected>'
}

另外,查询的时候 hashkey 是必须的,rangekey 是可选的。DynamoDB 通过 hashkey 确定在哪个 partion 下查询,然后通过 range key 再在该 partition 下进一步筛选。由于 hashkey 的特性,你只能进行相等的判断,不能使用大于、小于、或者 begin with。更丰富的查询操作只能通过 range key 来实现。这一点也给从 MySQL / MongoDB 阵营过来的用户带来很大程度的不适,原本会写的查询,在 DynamoDB 中突然不知道怎么处理了。

如果能够掌握更新和查询的别扭语法,那么其他的数据库操作都不在话下。很多语言在 DynamoDB 直接的 SDK 上提供了一层封装,可以更优雅地用人性化的方式使用 DynamoDB,多多少少也减轻了使用上的痛苦。

处理容量和可伸缩

由于 DynamoDB 与众不同的计费模式,使其使用方式和其它 DB 也有显著不同。其它 DB,你预先估计好容量,准备好 cluster,然后为整体使用的服务器资源买单,同时需要为下一次容量扩展做准备;而 DynamoDB 你只需要设置预估的读写容量,系统会帮你自动分配资源保证这个容量。在其它 DB 里,设计容量往往是峰值容量,在低谷期这些容量被大大地浪费了;而在 DynamoDB 里,你可以动态调整容量使其和系统一起伸缩。这是个讨厌的工作,它意味着工程师再也不能写完代码往运维那里一扔,说:「兄弟,抗不扛得住,看你了」;而是在代码中考虑系统要如何运维。

DynamoDB 增删改查等操作都能够返回使用 ConsumedCapacity,这样你可以了解目前还剩多少剩余的容量,进行「动态」调整。这个调整不是即时的,有一定的时间代价,因为 DynamoDB 需要根据 consistent hashing 把 key 重新分布。因此,调整最好是指数级的,以减少调整的频次。

另外,DynamoDB 可以累积一定的读写容量来帮你应对突发状况。如果你的写容量是 100/s,系统突然瞬间涌入过多的请求,达到 200/s,只要你有累积的容量,DynamoDB 会帮你短时间撑过这个突发访问。这让你的系统在应对波动上变得简单。

这个特性可以被很好的利用。比如你的数据表要每天定期做一些数据整理活动。如果在 MySQL 里,你在凌晨 1 点自动一次性整理,但在 DynamoDB 下,这么做意味着你得规划好这次访问的容量,免得过载。简化的办法是,把清理活动均匀分布在凌晨 0~5 点的每个半小时。这样,你可以使用 1/10 的容量完成这个任务,并且可以利用累积的容量,把这一容量需求进一步降低到 1/15。

比如你要读取 1M 数据,分 10 次读,每次 100k,400s 读完,250/s,你可以用 150~200 左右的读容量,而非 2500/s,这样会大大节省费用。

本文先讲到这里,下一篇讲讲 hashKey 和 rangeKey 如何选择和定义,以便充分利用 DynamoDB 的特性。另外,会讲一些使用的场景。

参考文档

  1. Dynamo: Amazon’s Highly Available Key-value Store
  2. Amazon DynamoDB deep dive

感谢魏星对本文的策划与审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

立即免费注册 AWS 账号,获得 12 个月免费套餐:点击注册

有云计算问题?立刻联系 AWS 云计算专家:立即联系

2016-01-17 18:0330281

评论

发布
暂无评论
发现更多内容

Kotlin修炼指南(三),如何在Android-Studio下进行NDK开发

android 程序员 移动开发

lambda表达式(4)(Shawn),开发android

android 程序员 移动开发

Spring Boot+Vue实现汽车租赁系统(毕设)

偶尔善良

MySQL redis Spring Boot Vue

LayoutManager高端玩家,实现花式表格,kotlin中文

android 程序员 移动开发

LeetCode,牛客面试必刷,看了这些,flutter面试

android 程序员 移动开发

Linux学习~树莓派gpio控制,如何化身BAT面试收割机

android 程序员 移动开发

阿里大佬手写Docker学习笔记就这?也就是让我五体投地的水平罢了

Docker 编程 程序员

Kotlin-风险高、RxJava-不老,Android-原生开发现状分析

android 程序员 移动开发

Vue进阶(幺陆叁):vue项目启动后自动打开页面并设置默认浏览器

No Silver Bullet

Vue 11月日更

LayoutManager高端玩家,实现花式表格(1),安卓面试题高级

android 程序员 移动开发

Kotlin-新版来了,支持跨平台!,android视频开发面试

android 程序员 移动开发

Kotlin-风险高、RxJava-不老,Android-原生开发现状分析(1)

android 程序员 移动开发

Kotlin协程,flutterplugin打包aar

android 程序员 移动开发

金九银十,我把阿里+字节+滴滴+美团+腾讯等Java岗位面试题用12万字总结出来了

Sakura

Java 编程 程序员 架构 面试

Kotlin学习手记——构造器,【深夜思考】

android 程序员 移动开发

LC狂刷66道Dynamic-Programming算法题。跟动态规划说拜拜

android 程序员 移动开发

《黑客之道》干了一夜的kali Linux之Metasploit渗透测试框架的基本使用

学神来啦

Linux 运维 黑客 渗透 Metasploit

Kotlin协程到底是怎么切换线程的?你是否知晓?,写得太好了

android 程序员 移动开发

Kotlin的自定义View,实现带弧形的进度条,软件开发项目经理面试题

android 程序员 移动开发

LeakCanary核心源码解析,android开发从入门到精通素材

android 程序员 移动开发

Kotlin学习手记——基本类型,安卓开发kotlin推荐书籍

android 程序员 移动开发

🔥 DeepVideo 智能视频生产训练营火热报名中!

阿里云视频云

阿里云 媒体处理 智能视频 智能生产 视频云

Linux编程之权限系统与工具使用(二),一文详解

android 程序员 移动开发

手把手教你,从零开始搭建Spring Cloud Alibaba!这份笔记太牛了

Java 编程 程序员 SpringCloud

Tapdata 等40余家行业知名企业,应邀参与共建 NextArch Foundation

tapdata

数据库 数据融合

Gartner预测到2025年,将有一半的云数据中心部署具有人工智能功能的机器人

WorkPlus

FinClip通过中国信通院SDK安全专项测试

FinClip

Kotlin协程到底是怎么切换线程的?你是否知晓?(1),kotlin开源项目实战

android 程序员 移动开发

Kotlin学习手记——协程进阶,嵌入式android开发教程

android 程序员 移动开发

就这?腾讯云高工熬夜手写'Java微服务学习笔记'也就让我月薪涨3k

Java spring 程序员 面试

记一次“U盘拔出”后重要文件丢失的恢复之旅

淋雨

EasyRecovery

AWS系列:Dynamodb深度体验(1)_数据库_陈天_InfoQ精选文章