38、使用 AWS Lambda 构建有界微服务

使用 AWS Lambda 构建有界微服务

1. 响应构建与结构体定义

当添加记录时,我们分两步构建响应。首先创建响应体,然后用额外值包装它。这是因为我们将使用 API Gateway 通过 S3 共享的外部应用程序调用 Lambda。

以下是我们需要的结构体:
- Unicorn 结构体 :包含我们要乘坐的生物信息。

#[derive(Clone, Serialize)]
#[serde(rename_all = "PascalCase")]
struct Unicorn {
    name: String,
    color: String,
    gender: String,
}

impl Unicorn {
    fn new(name: &str, color: &str, gender: &str) -> Self {
        Unicorn {
            name: name.to_owned(),
            color: color.to_owned(),
            gender: gender.to_owned(),
        }
    }
}
  • Location 结构体 :表示地图上的一个点,由应用程序的 UI 设置。
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Location {
    latitude: f64,
    longitude: f64,
}
  • Request 结构体 :包含请求体和请求上下文,用于获取 Cognito 提供的用户名。
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Request {
    body: String,
    request_context: RequestContext,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RequestContext {
    authorizer: Authorizer,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Authorizer {
    claims: HashMap<String, String>,
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct RequestBody {
    pickup_location: Location,
}
  • Response 结构体 :用于 API Gateway,包含状态码和头部信息。
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Response {
    body: String,
    status_code: u16,
    headers: HashMap<String, String>,
}

#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct ResponseBody {
    ride_id: String,
    unicorn: Unicorn,
    unicorn_name: String,
    eta: String,
    rider: String,
}
2. 功能函数
  • find_unicorn 函数 :从三个预定义的 Unicorn 值中选择一个。
fn find_unicorn(location: &Location) -> Unicorn {
    debug!("Finding unicorn for {}, {}", location.latitude, location.longitude);
    let unicorns = [
        Unicorn::new("Bucephalus", "Golden", "Male"),
        Unicorn::new("Shadowfax", "White", "Male"),
        Unicorn::new("Rocinante", "Yellow", "Female"),
    ];
    let mut rng = thread_rng();
    unicorns.iter().choose(&mut rng).cloned().unwrap()
}
  • record_ride 函数 :构建 DynamoDB 的 put 请求。
fn record_ride(
    conn: &DynamoDbClient,
    ride_id: &str,
    username: &str,
    unicorn: &Unicorn,
) -> Result<PutItemOutput, PutItemError> {
    let mut item: HashMap<String, AttributeValue> = HashMap::new();
    item.insert("RideId".into(), s_attr(ride_id));
    item.insert("User".into(), s_attr(username));
    item.insert("UnicornName".into(), s_attr(&unicorn.name));
    let timestamp = Utc::now().to_string();
    item.insert("RequestTime".into(), s_attr(&timestamp));
    item.insert("Unicorn".into(), unicorn_map(unicorn));
    let put = PutItemInput {
        table_name: "Rides".into(),
        item,
        ..Default::default()
    };
    conn.put_item(put).sync()
}
  • s_attr 函数 :将可表示为字符串引用的类型转换为 AttributeValue。
fn s_attr<T: AsRef<str>>(s: T) -> AttributeValue {
    AttributeValue {
        s: Some(s.as_ref().to_owned()),
        ..Default::default()
    }
}
  • unicorn_map 函数 :将 Unicorn 的字段转换为映射。
fn unicorn_map(unicorn: &Unicorn) -> AttributeValue {
    let mut item = HashMap::new();
    item.insert("Name".into(), s_attr(&unicorn.name));
    item.insert("Color".into(), s_attr(&unicorn.color));
    item.insert("Gender".into(), s_attr(&unicorn.gender));
    AttributeValue {
        m: Some(item),
        ..Default::default()
    }
}
3. 配置

使用 Serverless Framework 的 serverless.yml 配置文件将 Lambda 部署到 AWS。配置如下:

service: rust-sls
provider:
  name: aws
  runtime: rust
  memorySize: 128

package:
  individually: true

plugins:
  - serverless-rust
  - serverless-finch

functions:
  lambda_1:
    handler: lambda_1
    role: RustSlsLambdaRole
    events:
      - http:
          path: ride
          method: post
          cors: true
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Ref: RustSlsApiGatewayAuthorizer
  lambda_2:
    handler: lambda_2
    events:
      - http:
          path: check
          method: get
4. 资源声明

在配置文件中添加资源部分,声明必要的资源和输出变量。

resources:
  Resources:
    RustSlsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: rust-sls-aws
        WebsiteConfiguration:
          IndexDocument: index.html
    RustSlsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: "RustSlsBucket"
        PolicyDocument:
          Statement:
            -
              Effect: "Allow"
              Principal: "*"
              Action:
                - "s3:GetObject"
              Resource:
                Fn::Join:
                  - ""
                  -
                    - "arn:aws:s3:::"
                    -
                      Ref: "RustSlsBucket"
                    - "/*"
    RustSlsCognitoUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: RustSls
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email
    RustSlsCognitoUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        ClientName: RustSlsWebApp
        GenerateSecret: false
        UserPoolId:
          Ref: "RustSlsCognitoUserPool"
    RustSlsDynamoDBTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Rides
        AttributeDefinitions:
          - AttributeName: RideId
            AttributeType: S
        KeySchema:
          - AttributeName: RideId
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
    RustSlsLambdaRole:
      Type: AWS::IAM::Role
      Properties:
        RoleName: RustSlsLambda
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: DynamoDBWriteAccess
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource:
                    - 'Fn::Join':
                      - ':'
                      -
                        - 'arn:aws:logs'
                        - Ref: 'AWS::Region'
                        - Ref: 'AWS::AccountId'
                        - 'log-group:/aws/lambda/*:*:*'
                - Effect: Allow
                  Action:
                    - dynamodb:PutItem
                  Resource:
                    'Fn::GetAtt': [ RustSlsDynamoDBTable, Arn ]
    RustSlsApiGatewayAuthorizer:
      Type: AWS::ApiGateway::Authorizer
      Properties:
        Name: RustSls
        RestApiId:
          Ref: ApiGatewayRestApi
        Type: COGNITO_USER_POOLS
        ProviderARNs:
          - Fn::GetAtt: [ RustSlsCognitoUserPool, Arn ]
        IdentitySource: method.request.header.Authorization
  Outputs:
    RustSlsBucketURL:
      Description: "RustSls Bucket Website URL"
      Value:
        "Fn::GetAtt": [ RustSlsBucket, WebsiteURL ]
    RustSlsCognitoUserPoolId:
      Description: "RustSls Cognito User Pool ID"
      Value:
        Ref: "RustSlsCognitoUserPool"
    RustSlsCognitoUserPoolClientId:
      Description: "RustSls Cognito User Pool Client ID"
      Value:
        Ref: "RustSlsCognitoUserPoolClient"
    RustSlsDynamoDbARN:
      Description: "RustSls DynamoDB ARN"
      Value:
        "Fn::GetAtt": [ RustSlsDynamoDBTable, Arn ]

custom:
  client:
    bucketName: rust-sls-aws
    distributionFolder: assets
5. 部署
5.1 权限设置

要部署此应用程序,需要配置 AWS CLI 工具,并创建一个具有以下权限的用户:
- AWSLambdaFullAccess
- IAMFullAccess
- AmazonDynamoDBFullAccess
- AmazonAPIGatewayAdministrator
- AmazonCognitoPowerUser
- CloudFormationAdministrator

其中,CloudFormationAdministrator 权限需要手动创建,可通过添加以下 JSON 定义到策略中:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1449904348000",
            "Effect": "Allow",
            "Action": [
                "cloudformation:CreateStack",
                "cloudformation:CreateChangeSet",
                "cloudformation:ListStacks",
                "cloudformation:UpdateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStackEvents",
                "cloudformation:ValidateTemplate",
                "cloudformation:DescribeChangeSet",
                "cloudformation:ExecuteChangeSet"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
5.2 部署脚本

创建一个 bash 脚本,添加必要的部署函数。

extract() {
    echo "$DATA" | grep $1 | cut -d " " -f2
}

deploy() {
    echo "ASSETS DOWNLOADING"
    curl -L https://api.github.com/repos/aws-samples/aws-serverless-workshops/tarball \
        | tar xz --directory assets --wildcards "*/WebApplication/1_StaticWebHosting/website" --strip-components=4
    echo "LAMBDAS BUILDING"
    sls deploy
    echo "ASSETS UPLOADING"
    sls client deploy
    echo "CONFIGURATION UPLOADING"
    DATA=`sls info -v`
    POOL_ID=`extract PoolId`
    POOL_CLIENT_ID=`extract PoolClientId`
    REGION=`extract region`
    ENDPOINT=`extract ServiceEndpoint`
    CONFIG="
    window._config = {
        cognito: {
            userPoolId: '$POOL_ID',
            userPoolClientId: '$POOL_CLIENT_ID',
            region: '$REGION'
        },
        api: {
            invokeUrl: '$ENDPOINT'
        }
    };
    "
    echo "$CONFIG" | aws s3 cp - s3://rust-sls-aws/js/config.js
    INDEX=`extract BucketURL`
    echo "INDEX: $INDEX"
}
6. 运行与测试

如果从 GitHub 下载了项目源代码,可以使用 deploy.sh 脚本调用 deploy 函数。

./deploy.sh deploy

部署完成后,会输出应用程序的访问链接。在浏览器中打开该链接,即可看到前端 Wild Rydes 应用程序。用户点击 “GIDDY UP!” 按钮并使用 Cognito 注册账户,点击地图并设置取货点,即可看到独角兽头像移动到指定位置。

7. 更新与移除
  • 更新 :可以在部署脚本中添加 update 函数,使用 sls deploy 命令更新资源。
update() {
    sls deploy
}
  • 移除 :使用 remove 函数移除部署的资源。
remove() {
    echo "ASSETS REMOVING"
    sls client remove
    echo "LAMBDAS REMOVING"
    sls remove
    echo "ASSETS CLEANUP"
    rm -rf assets
    mkdir assets
    touch assets/.gitkeep
}

总结

通过本文,我们学习了使用 AWS Lambda 实现有界微服务的方法,包括响应构建、结构体定义、功能函数编写、配置文件设置、资源声明、部署、测试以及更新和移除操作。这种无服务器架构提供了一种直接处理传入请求的方式,并且可以将应用程序轻松移植到 Rust 语言中。

其他推荐书籍

  • 《Hands-On Concurrency with Rust》 - Brian L. Troutwine,ISBN: 9781788399975
  • 《Rust Quick Start Guide》 - Daniel Arbuckle,ISBN: 9781789616705

建议在实验结束后清理所有资源,避免产生不必要的费用。同时,欢迎在购买书籍的网站上留下评价,分享您的阅读体验。

使用 AWS Lambda 构建有界微服务

8. 部署流程总结

整个部署过程可以用以下流程图表示:

graph TD;
    A[创建具有必要权限的用户] --> B[下载前端资源];
    B --> C[构建并部署 Lambda];
    C --> D[上传资产到 S3];
    D --> E[获取配置信息并上传配置文件];
    E --> F[测试应用程序];

以下是部署步骤的详细总结表格:
| 步骤 | 操作 | 命令/说明 |
| ---- | ---- | ---- |
| 1 | 创建用户 | 创建具有 AWSLambdaFullAccess、IAMFullAccess 等权限的用户,并手动添加 CloudFormationAdministrator 权限的 JSON 定义 |
| 2 | 下载前端资源 | curl -L https://api.github.com/repos/aws-samples/aws-serverless-workshops/tarball \| tar xz --directory assets --wildcards "*/WebApplication/1_StaticWebHosting/website" --strip-components=4 |
| 3 | 构建并部署 Lambda | sls deploy |
| 4 | 上传资产到 S3 | sls client deploy |
| 5 | 获取配置信息并上传配置文件 | 使用 sls info -v 获取信息,生成配置文件并上传到 S3: echo "$CONFIG" \| aws s3 cp - s3://rust-sls-aws/js/config.js |
| 6 | 测试应用程序 | 在浏览器中打开提供的 URL,进行注册和操作测试 |

9. 测试过程分析

测试应用程序时,具体的操作步骤如下:
1. 打开部署脚本输出的链接,进入前端 Wild Rydes 应用程序页面。
2. 点击 “GIDDY UP!” 按钮,使用 Cognito 注册账户。在后台,Cognito 会处理用户注册,用户无需直接与该服务交互。
3. 注册成功后,点击地图上的某个点,然后点击 “Set Pickup” 按钮。此时,会触发我们用 Rust 编写的 Lambda 函数。
4. Lambda 函数会随机选择一只独角兽,并将相关信息存储到 DynamoDB 中。我们可以通过 AWS Console 进行验证:
- 在 User Pools 部分的 Users and groups 页面,可以看到注册的用户。
- 在 Lambda 的 Monitoring 选项卡中,打开 CloudWatch 日志,可以查看 Lambda 生成的用户名和存储的位置信息。
- 在 DynamoDB 的 Tables 页面,可以找到 Lambda 存储的记录,点击记录可以查看所有填充的字段。

10. 代码结构与功能梳理

以下是代码中各个结构体和函数的功能梳理表格:
| 结构体/函数 | 功能 |
| ---- | ---- |
| Unicorn | 表示独角兽,包含名称、颜色和性别信息,有构造函数 new |
| Location | 表示地图上的一个点,包含纬度和经度信息 |
| Request | 包含请求体和请求上下文,用于获取 Cognito 提供的用户名 |
| RequestContext | 运行时填充的映射,解析为结构体 |
| Authorizer | 包含 claims 值,用于获取 cognito:username |
| RequestBody | 包含取货点的位置信息 |
| Response | 用于 API Gateway,包含状态码和头部信息 |
| ResponseBody | 表示响应体,包含骑行 ID、独角兽信息等 |
| find_unicorn | 从三个预定义的独角兽中随机选择一个 |
| record_ride | 构建 DynamoDB 的 put 请求,将骑行信息存储到数据库中 |
| s_attr | 将可表示为字符串引用的类型转换为 AttributeValue |
| unicorn_map | 将 Unicorn 的字段转换为映射 |

11. 注意事项
  • S3 桶名称 :每个 S3 桶需要一个唯一的全局名称,在配置文件中使用的 rust-sls-aws 名称需要替换为实际可用的名称。
  • 资源清理 :实验结束后,建议使用 remove 函数清理所有部署的资源,避免 AWS 产生不必要的费用。
12. 总结与展望

通过本文的学习,我们掌握了使用 AWS Lambda 构建有界微服务的完整流程,包括从代码编写、配置设置到部署和测试的各个环节。这种无服务器架构结合 Rust 语言,为我们提供了一种高效、安全的开发方式。

在未来的开发中,我们可以进一步优化代码,提高 Lambda 函数的性能和响应速度。例如,可以对 find_unicorn 函数进行优化,根据不同的条件选择更合适的独角兽,而不是简单的随机选择。同时,还可以扩展应用程序的功能,添加更多的 API 接口和业务逻辑,以满足不同的需求。

其他推荐书籍回顾

  • 《Hands-On Concurrency with Rust》 - Brian L. Troutwine,ISBN: 9781788399975
  • 《Rust Quick Start Guide》 - Daniel Arbuckle,ISBN: 9781789616705

希望这些书籍能帮助你进一步深入学习 Rust 语言和相关的开发技术。同时,也欢迎你在购买书籍的网站上留下评价,与其他读者分享你的阅读体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值