添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

客户业务当前已部署在亚马逊云科技上,主要面向 2C 以及 2B 的客户,期望建立基于用户之间关系,并将构建好的人物关系用于游戏推荐,广告投递等场景。

在数据库选型之初,首选并非是 DynamoDB,因传统上 DynamoDB 被视为非关系型数据库的代表,在一个需要存储和处理关系的场景下,很容易被忽视,但在调研了不同数据库之后,首先对于可扩展性,当业务需要扩展到 TB 级以上的数据,DynamoDB 不会降低性能,DynamoDB 可以在水平扩展中实现任何规模下提供一致的性能。例如一些 DynamoDB 用户在生产环境下数据量不断扩展,超过 100TB 时,性能与建表之初依然可以保持一致,因此,DynamoDB 能满足业务方对于数据库端个位数 ms 响应的核心诉求;其次,DynamoDB 是一款完全托管的无服务器 NoSQL 数据库,无需用户处理服务器都管理、运维、升级等任务。基于以上两点诉求,客户开始尝试使用 DynamoDB 并最终应用在生产环境。

用户的业务场景主要是基于人物关系,进行相关的游戏,以及广告推荐,且需要实时在线查询。该项目的场景,DynamoDB 作为下游数据库,只需要针对上游的请求,返回至多到 1-2 级人物关系,例如人物 A-B-C 之间的关系即可。对于 DynamoDB 来说,需要将人物关系 A-B-C 拆分成 A-B 的关系以及 B-C 的关系,分别写入 DynamoDB 作为一级任务关系写入。在某些场景下,需要查询 A 的朋友的朋友有哪些,只需要查询 A 的好友,在获取 A 好友列表之后,通过 batch 查询获取 B 的好友列表返回给业务方即可。 本文重点在于使用 DynamoDB 进行一级好友关系建模,我们以 A-B 关系的建立为例,设计 DynamoDB 数据模型。B-C 关系以此类推。

ER Modeling 设计

在 SQL 传统数据库建模时,针对关系类,会通过创建 ER 图,来创建以下几个表格,其中涉及的实体包括:

  • 用户表:用户(user_id),例如上文提到的人物 A 以及 A 的属性,包括 A 的用户名、性别、地点、用户等级(是否是会员等)。
  • 关系表:Relevant(Relevantuser_id),例如代表人物 A 的人物关系,包括 Type(type_id)关系类型、create_time 关系创建的时间、update_time 关系更新时间、status 关系状态。
  • RelationType 表:用户关系类型,用户可以与另一个关系之间有不同种类之间的关系。
  • Level 表:用户等级,例如订阅用户、免费用户。
  • ER Modeling 转换成 DynamoDB 建模

    在对关系型数据库建模时,会根据 ER Modeling 的实体关系创建多个表格,但是在创建 DynamoDB 的数据库表结构时,通常针对一项业务,只创建一张表,需要将上述不同实体、属性,设计到一张表内。本次多级人物关系,使用 DynamoDB  来说,需要将人物关系 A-B-C 拆分成 A-B 的关系以及 B-C 的关系,分别写入 DynamoDB。在 DynamoDB 数据库的设计中,主要包含 Base 表(基表)与本地索引以及全局二级索引。

    DynamoDB 主键与核心属性如下

  • Partition Key:Userid 需要具有唯一性,也是 DynamoDB 中最重要的分区键,会通过分区键,将数据分散在不同的分区。
  • Sort key:Sort key 可以通过合并一些属性来提高 query 快速检索的速度,如在 relevant_id 前添加了 prefix type_id 这样代表关系类型 +user_id 来组成。例如可以通过搜索 user01 的全部好友并通过 prefix 来排序筛选出 user01 用户代表#1 好友的关系人。
  • 属性 Attributes:type_id 关系类型,例如 1 代表好友,2 代表点赞,3 代表拉黑。
  • 在设计之初需要规划好并在未来的生产环境可进一步规划,其中注意到以下几点:

  • 基表与本地索引共用同一个存储分区。因此本地索引只可以在表格创建之初创建,并且最多创建 5 个本地索引。
  • 全局二级索引可以在表格创建之后,根据需要来创建,最多可以创建 20 个全局二级索引。
  • DynamoDB 的主键有两种:a)简单主键只有分区键来作为主键;b)复合主键,由分区键+排序键构成。在较为复杂的查询情况下,使用复合主键,本文使用复合主键,这样可以通过 query 进行范围查询,一次返回某个用户的全部关系。
  • 可以在排序键中加入前缀以 # 开头,通过 ScanIndexForward 的属性将排序键通过正排或者倒排的方式进行排列,例如 PK = UserId,SK = #Metadata#UserID,这样实现 UserId 的 Metadata 保持在 Query 查询的第一条记录,也可以通过 Limit 1 的方式获取到这条记录。
  • 访问模式实现设计(Access Patten Design)

    在 DynamoDB 中,访问模式(Access Patterns)指对数据库进行读取和写入操作的方式和模式,它描述了应用程序如何访问和操作 DynamoDB 中存储的数据。访问模式决定了如何组织和设计 DynamoDB 表格以支持特定的查询和数据访问需求。根据应用程序的需求,可以选择不同的访问模式来优化数据访问和查询性能。

  • 创建关系-createRelevant
  • 过滤查找指定用户在指定区域/指定性别的好友列表-getFilteredRelevant
  • 更新关系-updateRelevent
  • 删除关系-deleteRelevant
  • 使用 Workbench for Amazon DynamoDB 进行数据建模与创建表格

    对于初次使用 DynamoDB 的用户,推荐大家使用 NoSQL Workbench 来进行数据建模,可以选择左侧直接构建新数据模型,根据现有模型设计符合应用程序数据访问模式的模型,您还可以在过程结束时导入和导出设计的数据模型。

    可以从官方下载安装: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html

    此时,可以到亚马逊云科技的控制台进行查看。以上可以通过使用 NoSQL Workbench 来快速建模,此外,NoSQL Workbench 可以帮助用户快速生成示例数据,在可视化看板中展示,并能够在接近生产环境的条件下测试应用程序访问模式,推荐大家使用。

    访问模式实现设计(Access Patten Design)

    在创建好数据库模型之后,开始考虑使用 API 的方式来修改数据以及查询数据。DynamoDB 数据库有三种主要的 API 查询方式:

  • Single item API(单个项目查询) :针对单个数据进行修改, GetItem,PutItem,UpdateItem,DeleteItem 这四种,下文创建关系就是使用单个 API 的方式。
  • Query API( 检索 API 查询): 主要是通过获取同一个分区键的方式,来返回数据,每次返回数据量最大为 1MB。
  • Scan API( 扫描 API 查询): 这种方式在 DynamoDB 的查询场景下,建议尽量避免使用,因每次查询会扫描整张表格,适用于非常小的表格,以及表格迁移的场景。
  • 1. 创建关系-createRelevant

    创建关系,是通过前文提到的单个项目查询 API 方式,通过 PutItem 的方式,插入数据。

    item = dynamodbclient.put_item(
        TableName='UserRelevantTable',
        Item = {
                "update_time": {"S": cur_time},
                "create_time": {"S": cur_time},
                "delete_status": {"BOOL": False},
                "tag":{'L':insertinfo['tag']},
                "relevantContent":{'S':insertinfo['relevantContent']},
                "relevant_gender":{'S':insertinfo['relevant_gender']},
                "type_id":{'S':insertinfo['type_id']},
                "user_id":{'S':user_id},
                "relevant_id":{'S':relevant_id}
           

    2. 查找指定用户在指定性别的关系列表

    items=dynamodbclient.query(
           TableName='UserRelevantTable',
           KeyConditionExpression="#u=:u",
           ConditionExpression:"delete_status=false" 
          FilterExpression="#relevant_gender=:relevant_gender",
           ExpressionAttributeNames={
              "#u":"user_id",
              "#relevant_gender":"relevant_gender"
           ExpressionAttributeValues={
              ":u":{"S":user01},
              ":relevant_gender":{"S":male}
           

    查询使用的是上述第二种 Query API, 并使用了三种表达式(参考下图):

  • 通过使用 KeyCondition 表达式来查询哪一条数据合集被返回(one item collection)作为返回值。下图中,黄线的部分,为表达式数值为 user01 的一条 item collection。KeyCondtion 的作用区紧在主键中。
  • 使用 Filter 表达式,通过下图,可以清晰地看到,Filter 的查询功能主要作用在属性值内,通过 Query 查询一条数据返回中,过滤出需要最终返回的数值。查询用户 user01 的所有男性用户用户。
  • ConditionExpression 作为条件筛选出 delete_status 为否的关系并未删除的数据。例代码中井号 # 为属性名字,冒号 : 后边代表属性数值。
  • 3. 更新关系-updateRelevent 

    是通过单个项目查询 API 方式,通过 UpdateItem 的方式,插入数据。基于主键更新 ReleventContent 相关内容的属性值。

    result = dynamodbclient.update_item(
           TableName='UserRelevantTable',
           Key={
                 "user_id":{"S":user_id},
                  "relevant_id":{"S":relevant_id}
           UpdateExpression="SET #relevantContent=:relevantContent,#update_time=:update_time",
           ExpressionAttributeNames={
                        "#relevantContent":'relevantContent',
                        "#update_time":'update_time',
           ExpressionAttributeValues={
                        ":relevantContent":{"S":designer},
                        ":update_time":{"S":update_time}
           

    4. 删除关系-deleteRelevant

    result = dynamodbclient.update_item(
           TableName='UserRelevantTable',
           Key={
                 "user_id":{"S":user_id},
                  "relevant_id":{"S":relevant_id}
           UpdateExpression="SET #delete_status=:delete_status,#update_time=:update_time",
                    Key={
                        "user_id":{"S":user_id},
                        "relevant_id":{"S":relevant_id}
           ExpressionAttributeNames={
                        "#delete_status":'delete_status',
                        "#update_time":'update_time',
           ExpressionAttributeValues={
                        ":delete_status":{"BOOL":True},
                        ":update_time":{"S":update_time}
           

    Lambda Python 代码:https://github.com/Summer1208/Samplecode/blob/main/DynamoDB_UserRelevant

    本文通过使用 DynamoDB 来创建人物关系,并实现毫秒级响应速度,满足客户业务需求,对响应速度有要求的客户,可以参考 Lambda github 实例代码进行快速验证。