微服务设置中的服务间通信

Last reviewed 2023-09-20 UTC

本文档是设计、构建和部署微服务的四篇系列文章中的第三篇。本系列文章介绍微服务架构的各种元素。该系列介绍了微服务架构模式的优缺点及其应用方式。

  1. 微服务简介
  2. 将单体式应用重构为微服务
  3. 微服务设置中的服务间通信(本文档)
  4. 微服务应用中的分布式跟踪

本系列文章面向设计和实施迁移以将单体式应用重构为微服务应用的应用开发者和架构师。

本文档介绍了异步消息传递与微服务中的同步 API 之间的权衡。本文档将逐步介绍单体式应用的构建,并介绍如何在基于微服务的新设置中将原始应用中的同步请求转换为异步流。这种转换包括在服务之间实现分布式事务。

示例应用

在本文档中,您将使用名为 Online Boutique 的预建电子商务应用。应用会实现基本的电子商务流程,例如浏览商品、将商品添加到购物车和结算。该应用还根据用户选择提供建议和广告。

逻辑分离服务

在本文档中,您将付款服务与应用的其余部分隔离开来。原始 Online Boutique 应用中的所有流都是同步的。在重构的应用中,付款流程将转换为异步流程。因此,当您收到购买请求时,您无需向用户提供立即处理请求,而是向用户提供“收到请求”确认。在后台,系统会向付款服务触发异步请求来处理付款。

在将付款数据和逻辑移至新服务之前,请将付款数据和逻辑与单体式应用隔离开来。将付款数据和逻辑隔离在一起时,如果付款服务边界错误(业务逻辑或数据),您可以更轻松地在同一代码库中重构代码。

本文档中单体式应用的组件已经模块化,因此它们彼此隔离。如果您的应用具有更严格的相互依赖性,您需要隔离业务逻辑并创建单独的类和模块。您还需要将任何数据库依赖项分离到其各自的表中,并创建单独的代码库类。分离数据库依赖项时,拆分表之间可能存在外键关系。但是,在将服务与单体式应用完全分离后,这些依赖项将不再存在,并且该服务只能通过预定义的 API 或 RPC 合同进行互动。

分布式事务和部分失败

将服务与单体式应用隔离开来后,原始单体式系统中的本地事务会分布在多项服务中。在单体式实现中,结账流程遵循下图所示的顺序。

结算序列分布在多个服务中。

图 1. 单体式实现中的结账流程序列。

在图 1 中,当应用收到采购订单时,结账控制器会调用付款服务和订单服务来处理付款并分别保存订单。如果任何步骤失败,则可以回滚数据库事务。请设想一个场景:订单请求成功存储在订单表中,但付款失败。在这种情况下,系统会回滚整个事务,并从订单表中移除该条目。

将付款分离到其自己的服务中后,修改后的结算流程如下图所示:

结算流程涉及多项服务和数据库。

图 2:付款流程中的结账流程分离成自己的服务。

在图 2 中,事务现在跨多个服务及其相应的数据库,因此是一个分布式事务。收到订单请求时,结账控制器会将订单详细信息保存在本地数据库中,并调用其他服务以完成订单。这些服务(例如付款服务)可以使用自己的本地数据库来存储订单的详细信息。

在单体式应用中,数据库系统确保本地事务是原子性的。但是,默认情况下,基于微服务的每项服务都有一个单独的数据库,因此没有跨不同数据库的全局事务协调器。由于交易未进行集中协调,因此处理付款失败不会回滚在订单服务中提交的更改。因此,系统处于不一致状态。

以下模式通常用于处理分布式事务:

  • 两阶段提交协议 (2PC):作为共识协议系列的一部分,2PC 会协调分布式事务的提交并维护原子性、一致性、隔离性、持久性 (ACID) 保证。该协议分为两个阶段:准备和提交。仅当所有参与者都已经为个事务投票时,才会提交事务。如果参与者未能达成共识,则会回滚整个事务。
  • Saga:Saga 模式包括在构成分布式事务的每个微服务中运行本地事务。每个成功或失败的操作结束时都会触发事件。分布式事务所涉及的所有微服务都会订阅这些事件。如果以下微服务收到成功事件,则会执行其操作。如果失败,则上述微服务会完成补偿性操作以撤消更改。Saga 通过保证所有步骤都成功完成或补偿操作来完成所有工作,为系统提供一致的视图。

我们建议使用 Saga 来处理长期事务。在基于微服务的应用中,您期望服务间调用以及与第三方系统的通信。因此,最好针对最终一致性进行设计:重试可恢复的错误,并公开最终修改不可恢复错误的补偿事件。

您可以通过多种方式实现 Saga,例如,您可以使用 Apache AirflowApache CamelConductor 等任务和工作流引擎。您还可以使用基于 Kafka、RabbitMQ 或 ActiveMQ 的系统编写您自己的事件处理程序。

Online Boutique 应用使用结账服务来编排付款、送货和电子邮件通知服务。结算服务还会处理业务和订单工作流程。除了构建自己的工作流引擎之外,您还可以使用第三方组件(例如 Zeebe)。Zeebe 提供了一个基于界面的建模者。我们建议您根据应用的要求仔细评估微服务编排器的选择。此选择是运行和扩缩微服务的关键部分。

重构应用

为了在重构应用中启用分布式事务,结账服务会处理付款、送货和电子邮件服务之间的通信。一般的业务流程模型和标记 (BPMN) 工作流采用以下流程:

重构应用中的事务遵循 BPMN 工作流。

图 3.可帮助确保典型微服务中的分布式事务的订单流程。

上图显示了以下工作流:

  • 前端服务收到订单请求,然后执行以下操作:
    • 将订单商品发送到购物车服务。然后,购物车服务会保存订单详细信息 (Redis)。
    • 重定向至结算页面。结算服务从购物车服务中提取订单,将订单状态设置为 Pending,并要求客户付款。
    • 确认用户已付款。确认后,结算服务会告知电子邮件服务生成确认电子邮件并将其发送给客户。
  • 付款服务随后会处理请求。
    • 如果付款请求成功,付款服务会将订单状态更新为 Complete
    • 如果付款请求失败,则付款服务会启动补偿性交易。
      • 付款请求已取消。
      • 结账服务会将订单状态更改为 Failed
    • 如果付款服务不可用,请求会在 N 秒后超时,并且结算服务会启动补偿性交易。
    • 结账服务会将订单状态更改为 Failed

目标

  • 在 Google Kubernetes Engine (GKE) 上部署大型 Online Boutique 应用。
  • 验证单体式结账流程。
  • 部署重构单体式应用的微服务版本
  • 验证新的结账流程是否正常工作。
  • 如果发生故障,请验证分布式事务和补偿操作是否生效。

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本文档后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

须知事项

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  2. 确保您的 Google Cloud 项目已启用结算功能

  3. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  4. 启用适用于 Compute Engine、Google Kubernetes Engine、Cloud SQL、Artifact Analysis 和 Container Registry 的 API:

     gcloud services enable \
         compute.googleapis.com \
         sql-component.googleapis.com \
         servicenetworking.googleapis.com\
         container.googleapis.com \
         containeranalysis.googleapis.com \
         containerregistry.googleapis.com \
         sqladmin.googleapis.com
    
  5. 导出以下环境变量:

    export PROJECT=$(gcloud config get-value project)
    export CLUSTER=$PROJECT-gke
    export REGION="us-central1"
    

部署电子商务单体式应用

在本部分中,您将在 GKE 集群中部署单体式 Online Boutique 应用。该应用使用 Cloud SQL 作为其关系型数据库。下图演示了单体式应用架构:

应用采用单体式架构。

图 4.客户端连接到 GKE 集群中的应用,应用连接到 Cloud SQL 数据库。

如需部署该应用,请完成以下步骤:

  1. 克隆 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
    
  2. 替换 Terraform 变量清单文件中的 PROJECT_ID 占位符:

    cd monolith-to-microservices-example/setup && \
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
    
  3. 运行 Terraform 脚本以完成基础架构设置并部署基础架构。如需详细了解 Terraform,请参阅 Terraform on Google Cloud 使用入门

    terraform init && terraform apply -auto-approve
    

    Terraform 脚本会创建以下内容:

    • 名为 PROJECT_ID-vpc 的 VPC 网络
    • 名为 PROJECT_ID-gke 的 GKE 集群
    • 名为 PROJECT_ID-mysql 的 Cloud SQL 实例
      • 应用使用的名为 ecommerce 的数据库
      • 用户 root,密码设为 password

    您可以修改 Terraform 脚本以自动生成密码。此设置使用简化示例,不应在生产环境中使用。

    基础架构预配最多可能需要 10 分钟。脚本成功完成后,输出如下所示:

    ...
    
    Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    kubernetes_cluster_name = PROJECT_ID-gke
    sql_database_name = PROJECT_ID-mysql
    vpc_name = PROJECT_ID-vpc
    
  4. 连接到集群并创建名为 monolith 的命名空间。您可在 GKE 集群自己的命名空间中部署应用:

    gcloud container clusters get-credentials $CLUSTER \
       --region $REGION \
       --project $PROJECT && \
       kubectl create ns monolith
    
  5. 在 GKE 上运行的应用使用 Kubernetes Secret 访问 Cloud SQL 数据库。创建一个使用数据库用户凭据的 Secret:

    kubectl create secret generic dbsecret \
      --from-literal=username=root \
      --from-literal=password=password -n monolith
    
  6. 构建单体式应用映像,并将其上传到 Container Registry:

    cd ~/monolith
    gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
    
  7. deploy.yaml 文件中的引用更新为新创建的 Docker 映像:

    cd ~/monolith
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
    
  8. 替换部署清单文件中的占位符,然后部署应用:

    cd .. && \
    DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}')
    
    sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml
    kubectl apply -f monolith/deploy.yaml
    
  9. 检查部署的状态:

    kubectl rollout status deployment/ecomm -n monolith
    

    输出如下所示。

    Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available...
    deployment "ecomm" successfully rolled out
    
  10. 获取已部署应用的 IP 地址:

    kubectl get svc ecomm -n monolith \
            -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
    

    等待负载均衡器 IP 地址发布。如需退出命令,请按 Ctrl+C。请记下负载均衡器 IP 地址,然后通过网址 http://IP_ADDRESS 访问应用。负载均衡器可能需要一段时间才能恢复正常并开始传输流量。

验证单体式结账流程

在本部分中,您将创建一个测试订单来验证结账流程。

  1. 转到您在上一部分记下的网址 http://IP_ADDRESS
  2. 在显示的应用首页上,选择任何产品,然后点击添加到购物车
  3. 如需创建测试购买,请点击下单
  4. 结算成功后,系统会显示订单确认窗口并显示订单确认 ID。
  5. 如需查看订单详情,请连接到数据库:

    gcloud sql connect $PROJECT-mysql --user=root
    

    您还可以使用任何其他受支持的方法来连接到数据库。出现提示时,请输入 password 作为密码。

  6. 如需查看已保存的订单详细信息,请运行以下命令:

    select cart_id from ecommerce.cart;
    
  7. 输出如下所示:

    +--------------------------------------+
    | cart_id                              |
    +--------------------------------------+
    | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 |
    +--------------------------------------+
    

部署基于微服务的电子商务应用

在本部分中,您将部署重构的应用。本文档仅重点介绍分离前端和付款服务。本系列的下一个文档(即微服务应用中的分布式跟踪)介绍了其他服务(例如推荐和广告服务),您可以将其与单体式应用。结算服务处理前端和付款服务之间的分布式事务,并将其部署为 GKE 集群中的 Kubernetes 服务,如下图所示:

前端和付款服务与单体式应用分离。

图 5. 结算服务编排购物车、付款和电子邮件服务之间的交易。

部署微服务

在本部分中,您将使用之前预配的基础架构在其自身的命名空间 microservice 中部署微服务:

  1. 确保您满足以下要求:

    • Google Cloud 项目
    • 包含 gcloudgitkubectl 的 Shell 环境
  2. 在 Cloud Shell 中,克隆微服务代码库:

    git clone https://github.com/GoogleCloudPlatform/microservices-demo
    cd microservices-demo/
    
  3. 设置 Google Cloud 项目和区域,并确保已启用 GKE API:

    export PROJECT_ID=PROJECT_ID
    export REGION=us-central1
    gcloud services enable container.googleapis.com \
    --project=${PROJECT_ID}
    

    替换为您的 Google Cloud 项目 ID。

  4. 创建 GKE 集群并获取其凭据:

    gcloud container clusters create-auto online-boutique \
        --project=${PROJECT_ID} --region=${REGION}
    

    创建集群可能需要几分钟的时间。

  5. 将微服务部署到集群:

    kubectl apply -f ./release/kubernetes-manifests.yaml
    
  6. 等待 Pod 准备就绪:

    kubectl get pods
    

    几分钟后,您将看到 Pod 处于 Running 状态。

  7. 使用前端的外部 IP 地址在浏览器中访问该前端:

    kubectl get service frontend-external | awk '{print $4}'
    

    在网络浏览器中访问 http://EXTERNAL_IP 以访问 Online Boutique 实例。

验证新的结账流程

  1. 如需验证结账流程,请按照前面的验证单体式结账流程部分所述,选择一件商品并下单。
  2. 完成订单结算后,确认窗口不会显示确认 ID。相反,确认窗口会引导您查看电子邮件,获取确认详细信息。
  3. 如需验证订单已收到、付款服务已处理付款以及订单详细信息已更新,请运行以下命令:

    kubectl logs -f deploy/checkoutservice --tail=100
    

    输出如下所示:

    [...]
    {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"}
    {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"}
    {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
    

    如需退出日志,请按 Ctrl+C

  4. 验证付款是否成功:

    kubectl logs -f deploy/paymentservice -n --tail=100
    

    输出如下所示:

    [...]
    {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454     Amount: CAD119.30128260"}
    {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
    

    如需退出日志,请按 Ctrl+C

  5. 验证订单确认电子邮件是否已发送:

    kubectl logs -f deploy/emailservice -n --tail=100
    

    输出如下所示:

    [...]
    {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
    

    每个微服务的日志消息都表示跨结账、付款和电子邮件服务的分布式事务已成功完成。

验证分布式事务中的薪酬操作

本节模拟客户下单并且付款服务关闭的情况。

  1. 如需模拟服务不可用,请删除付款部署和服务:

    kubectl delete deploy paymentservice && \
    kubectl delete svc paymentservice
    
  2. 再次访问应用并完成结算流程。在此示例中,如果付款服务没有响应,则请求会超时并触发补偿操作。

  3. 在界面前端,点击下单按钮。输出类似以下内容:

    HTTP Status: 500 Internal Server Error
    rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host"
    failed to complete the order
    main.(*frontendServer).placeOrderHandler
        /src/handlers.go:360
    
  4. 查看前端服务日志:

    kubectl logs -f deploy/frontend --tail=100
    

    输出类似以下内容:

    [...]
    {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
    
  5. 查看结账服务日志:

    kubectl logs -f deploy/frontend --tail=100
    

    输出类似以下内容:

    [...]
    {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"}
    {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
    

    请注意,以后不会调用电子邮件服务来发送通知。没有 payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca) 等事务日志

  6. 查看电子邮件服务日志:

    kubectl logs -f deploy/emailservice --tail=100
    

    请注意,系统不会为电子邮件服务中的失败事务创建日志条目。

作为编排者,如果服务调用失败,结算服务将返回错误状态并退出结账流程。

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

如果您计划完成本系列文章下一个文档(微服务应用中的分布式跟踪)中的步骤,则可以重复使用项目和资源,而无需将其删除。

删除项目

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除资源

如果您希望保留本文档中使用的 Google Cloud 项目,请删除各个资源:

  1. 在 Cloud Shell 中,运行以下命令:

    cd setup && terraform destroy -auto-approve
    
  2. 如需使用 Google Cloud CLI 删除微服务集群,请运行以下命令:

    gcloud container clusters delete online-boutique \
        --location $REGION
    

后续步骤