# 使用 DynamoDB 和 Typesense 实现全文模糊搜索

本教程将展示如何将 DynamoDB 表中的数据导入 Typesense,然后使用 Typesense 进行支持容错、过滤、分面等功能的全文搜索。

总体而言,我们将设置一个 Lambda 函数,通过 DynamoDB 流 (opens new window)监听变更事件,并将数据写入 Typesense。

Typesense DynamoDB 集成图表

# 第一步:创建 Typesense 集群

Typesense Cloud (opens new window) 上注册账户,创建一个集群并获取 Endpoint URL、端口号和 API 密钥。

本教程使用 Typesense Cloud,因为我们需要一个公开的 Typesense 端点供 Lambda 函数写入数据。

你也可以选择在自己的服务器或云服务提供商上自托管 Typesense。有关自托管 Typesense 的更多详情,请参阅 Typesense 安装指南

# 第二步:创建 DynamoDB 表

创建一个 DynamoDB 表,自定义表名和分区键(推荐使用 "id")。创建表后,你需要在 AWS 控制台的概览部分启用流功能。

你也可以使用 AWS CLI 完成此操作:

aws dynamodb create-table \
    --table-name YourTableName \
    --attribute-definitions AttributeName=id,AttributeType=N \
    --key-schema AttributeName=id,KeyType=HASH  \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

# 步骤 3:创建 Lambda 执行角色

现在我们来创建一个 "Lambda 执行角色",即为您的函数授予访问所需资源的权限。

前往 AWS 控制台的 IAM 角色部分,创建一个新的 IAM 角色,并赋予以下三个主要权限:

  • AmazonDynamoDBFullAccess
  • AmazonDynamoDBFullAccesswithDataPipeline
  • AWSLambdaBasicExecutionRole

WARNING

这些 IAM 角色权限仅为本指南的示例。在生产环境部署前,请参考 IAM 文档,仅授予您的特定用例所需的最小权限。

您也可以使用 AWS CLI 完成此操作:

创建一个名为 trust-relationship.json 的文件,内容如下:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

然后执行以下命令:

aws iam create-role --role-name YourLambdaRole \
    --path "/service-role/" \
    --assume-role-policy-document file://trust-relationship.json

接下来创建 role-policy.json 文件,内容如下(替换 accountIDregion):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:region:accountID:function:typesense-indexing*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:region:accountID:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:DescribeStream",
        "dynamodb:GetRecords",
        "dynamodb:GetShardIterator",
        "dynamodb:ListStreams"
      ],
      "Resource": "arn:aws:dynamodb:region:accountID:table/typesense/stream/*"
    },
  ]
}

该策略包含三个声明,允许 TypesenseLambdaRole 执行以下操作:

  • 运行名为 typesense-indexing 的 Lambda 函数(我们将在本教程稍后创建此函数)
  • 访问 Amazon CloudWatch Logs(Lambda 函数在运行时会将诊断信息写入 CloudWatch Logs)
  • typesense 的 DynamoDB 流中读取数据

现在,我们将上述角色附加到已创建的 IAM 执行角色上:

aws iam put-role-policy --role-name YourLambdaRole \
    --policy-name TypesenseLambdaRolePolicy \
    --policy-document file://role-policy.json

# 步骤4:创建Lambda函数

前往AWS控制台的Lambda部分,使用之前创建的执行角色创建一个新的Lambda函数。详细说明请参阅AWS Lambda执行角色文档 (opens new window)

以下是DynamoDB调用我们的Lambda函数时可能传递的事件示例:

{
  "Records": [
    {
      "eventID": "2",
      "eventVersion": "1.0",
      "dynamodb": {
        "OldImage": {
          // 现有值
        },
        "SequenceNumber": "222",
        "Keys": {
          // 分区键和排序键
        },
        "SizeBytes": 59,
        "NewImage": {
          // 新值
        },
        "awsRegion": "us-east-2",
        "eventName": "MODIFY", // 可能是'INSERT'、'MODIFY'或'DELETE'
        "eventSourceARN": "<AWS-ARN>",
        "eventSource": "aws:dynamodb"
      },
    }
  ]
}

现在将以下代码添加到我们的Lambda函数中。我们使用Python作为示例,但你也可以使用Node、Ruby或AWS Lambda支持的任何语言。

def lambda_handler(event, context):
    client = typesense.Client({
        'nodes': [{
            'host': '<Endpoint URL>',
            'port': '<Port Number>',
            'protocol': 'https',
        }],
        'api_key': '<API Key>',
        'connection_timeout_seconds': 2
    })

    processed = 0
    for record in event['Records']:
        ddb_record = record['dynamodb']
        if record['eventName'] == 'REMOVE':
            res = client.collections['<collection-name>'].documents[str(ddb_record['OldImage']['id']['N'])].delete()
        else:
            document = ddb_record['NewImage'] # 在此处格式化你的文档,然后使用upsert函数建立索引
            res = client.collections['<collection-name>'].upsert(document)
            print(res)
        processed = processed + 1
        print('Successfully processed {} records'.format(processed))
    return processed

有关创建集合和文档的所有可用参数的详细信息,请参阅Typesense API文档

TIP

使用pip install <dependency-name> -t .安装所有依赖项。这将在当前目录中安装函数所需的所有依赖项,这正是Lambda所期望的。

完成后,将当前目录压缩并通过AWS控制台上传到你的Lambda函数。

你也可以使用AWS CLI完成此操作:

  • 获取你创建的执行角色的ARN:

    aws iam get-role --role-name YourLambdaRole
    

    在输出中查找ARN:

    ...
    "Arn": "arn:aws:iam::region:role/service-role/YourLambdaRole"
    ...
    
  • 现在创建Lambda函数:

    aws lambda create-function \
      --region us-east-2 \
      --function-name YourLambdaFunction \
      --zip-file fileb://YourZipFile.zip \
      --role YourRoleARN \
      --handler lambda_function.lambda_handler \
      --timeout 5 \
      --runtime python3.7
    

# 步骤5:设置触发器

现在,在AWS控制台中导航到您的DynamoDB表,访问Triggers(触发器)部分,并将现有的Lambda函数添加到该表中。

您也可以使用AWS CLI完成此操作:

  • 获取DynamoDB表的ARN
    aws dynamodb describe-table --table-name YourTableName
    
    注意流ARN:
    ...
    "LatestStreamArn": "arn:aws:dynamodb:`region`:`accountID`:table/`table-name`/stream/`timestamp`"
    ...
    
  • 现在,将此ARN添加到Lambda:
    aws lambda create-event-source-mapping \
      --region us-east-1 \
      --function-name YourLambdaFunction \
      --event-source YourStreamARN \
      --batch-size 1 \
      --starting-position TRIM_HORIZON
    

TIP

在高流量环境中处理大量变更时,我们强烈建议您将写入操作批量处理到Typesense中。 您可以使用类似Kinesis的服务暂存DynamoDB事件,然后使用import endpoint将变更批量写入Typesense。

大功告成!现在您在DynamoDB表中创建、更新或删除的任何数据都将自动索引到您的Typesense集群中。

# 参考资料