练习 - 将整体架构中的服务重构为微服务

已完成

现在,Fabrikam 分析了其应用程序,已准备好开始重构过程,以便将服务从其整体体系结构移动到微服务。 修改应用程序,将包处理服务移到微服务。

Visualization of the resources for the Drone Delivery application.

重构应用程序

在部署更新后的应用程序之前,先了解一下其更新方式。 整体应用提供用于处理包的服务 PackageProcessor.cs。 分析应用程序的性能后,此服务被识别为性能瓶颈。 当客户增加对无人机交付的需求时,此服务在处理无人机交付的计划和物流时会变得负载很重。 一个专门的团队会全面管理这项服务,因此将它移动到微服务有助于提高性能并提供改进的开发敏捷性。

让我们更深入地了解所做的更改。

无人机交付之前

PackageProcessor 类处理 PackageProcessor.cs 文件中包处理的核心功能。 在此示例中,它执行一些资源密集型的工作。 真实情况可能包括使用此信息计算交付时间和交付路线并更新数据源。

public class PackageProcessor : IPackageProcessor
    {
        public Task<PackageGen> CreatePackageAsync(PackageInfo packageInfo)
        {
            //Uses common data store e.g. SQL Azure tables
            Utility.DoWork(100);
            return Task.FromResult(new PackageGen { Id = packageInfo.PackageId });
        }
    }

随着对此服务的请求增加,资源利用率会增加,并且会受到分配给整体应用程序的物理资源的限制。 如果在 Azure 应用服务上部署此服务,则可以将其纵向扩展和横向扩展。理想情况下,你希望这种使用频繁的资源能够独立进行缩放以优化性能和成本。 在此方案中,我们使用 Azure Functions 来完成此操作。

无人机交付之后

在部署它之前,让我们看看 DroneDelivery-after 应用程序代码。 可以看到该 PackageProcessor 类已更改为 PackageServiceCaller 类。 它仍会实现 IPackageProcessor 接口,但会改为对微服务发出 HTTP 调用。

public class PackageServiceCaller : IPackageProcessor
    {
        private readonly HttpClient httpClient;

        public static string FunctionCode { get; set; }

        public PackageServiceCaller(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        public async Task<PackageGen> CreatePackageAsync(PackageInfo packageInfo)
        {
            var result = await httpClient.PutAsJsonAsync($"{packageInfo.PackageId}?code={FunctionCode}", packageInfo);
            result.EnsureSuccessStatusCode();

            return new PackageGen { Id = packageInfo.PackageId };
        }
    }

微服务将部署在 Azure 函数上。 可在 PackageServiceFunction.cs 中找到其代码,其中包含以下代码。

public static class PackageServiceFunction
    {
        [FunctionName("PackageServiceFunction")]
        public static Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "put", Route = "packages/{id}")] HttpRequest req,
            string id, ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            //Uses common data store e.g. SQL Azure tables
            Utility.DoWork(100);
            return Task.FromResult((IActionResult)new CreatedResult("http://example.com", null));
        }
    }

在 Azure Functions 上放置此代码后,此服务可在用户负载增加时独立进行缩放。 可以保留针对应用程序的其余部分优化的剩余应用程序代码的服务。 随着更多无人机交付请求进入系统,包服务会横向扩展。

现在,让我们重新部署应用程序。 首先,在 Azure Functions 上部署重构的服务。 然后,在应用服务上部署重构的应用程序,并将其指向函数。

部署函数应用

  1. 运行以下命令,设置指向我们服务的环境变量。

    APPSERVICENAME="$(az webapp list \
                        --resource-group "<rgn>[sandbox resource group]</rgn>" \
                        --query '[].name' \
                        --output tsv)"
    FUNCTIONAPPNAME="$(az functionapp list \
                        --resource-group "<rgn>[sandbox resource group]</rgn>" \
                        --query '[].name' \
                        --output tsv)"
    
  2. 生成并压缩该函数应用的应用程序代码。

    cd ~/mslearn-microservices-architecture/src/after
    dotnet build ./PackageService/PackageService.csproj -c Release
    cd PackageService/bin/Release/netcoreapp2.2
    zip -r PackageService.zip .
    
  3. 运行以下命令,将代码推送到函数应用。

    az functionapp deployment source config-zip \
        --resource-group "<rgn>[sandbox resource group]</rgn>" \
        --name $FUNCTIONAPPNAME \
        --src PackageService.zip
    

部署更新后的无人机交付应用程序

我们的服务现已在 Azure Functions 上运行,我们需要将无人机应用程序指向该函数应用。

  1. 首先需要获取函数应用的访问代码,以便可从应用程序中成功调用该应用。 运行以下命令检索此代码。 随后将显示函数应用名称和代码,供后续步骤使用。

    RESOURCEGROUPID=$(az group show \
                        --resource-group "<rgn>[sandbox resource group]</rgn>" \
                        --query id \
                        --output tsv)
    FUNCTIONCODE=$(az rest \
                        --method post \
                        --query default \
                        --output tsv \
                        --uri "https://management.azure.com$RESOURCEGROUPID/providers/Microsoft.Web/sites/$FUNCTIONAPPNAME/functions/PackageServiceFunction/listKeys?api-version=2018-02-01")
    echo "FunctionName - $FUNCTIONAPPNAME"
    echo "FunctionCode - $FUNCTIONCODE"
    
  2. 在 Azure Cloud Shell 中运行以下命令,在代码编辑器中打开 appsettings.json。

    cd ~/mslearn-microservices-architecture/src/after
    code ./DroneDelivery-after/appsettings.json
    
  3. 在代码编辑器中,替换值 PackageServiceUriPackageServiceFunctionCode。 在 PackageServiceUri 中,将 <FunctionName> 替换为函数应用的名称。

    PackageServiceFunctionCode 中,将 <FunctionCode> 替换为检索到的函数代码。 appsettings.json 文件的外观类似于以下示例

    {
        "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
        },
        "AllowedHosts": "*",
        "PackageServiceUri": "https://packageservicefunction-abc.azurewebsites.net/api/packages/",
        "PackageServiceFunctionCode": "SvrbiyhjXJUdTPXrkcUtY6bQaUf7OXQjWvnM0Gq63hFUhbH2vn6qYA=="
    }
    
  4. Ctrl+S 保存文件,然后按 Ctrl+Q 关闭代码编辑器。

  5. 运行以下命令,以将更新后的应用程序部署到应用服务。

    zip -r DroneDelivery-after.zip . -x \*/obj/\* \*/bin/\*
    az webapp deploy \
        --resource-group "<rgn>[sandbox resource group]</rgn>" \
        --name $APPSERVICENAME \
        --src-path DroneDelivery-after.zip
    
  6. 重新部署网站后,刷新页面。 它现在应会更新。

    Screenshot of the redeployed Drone Delivery website.

测试新体系结构的性能

现在,受资源约束的服务已经移动到在 Azure Functions 上运行的微服务,接下来我们看看此更改对应用程序性能的影响。

  1. 在网站主页上,选择“发送请求”。 此操作将来自整体应用的请求提交到在 Azure 函数上运行的微服务。

  2. 第一次尝试可能会对整体应用程序产生类似的结果。 刷新页面,并在系统提示时重新提交请求。 多次执行此步骤,应看到“1 秒内已发送出 100 条消息”。

    Screenshot of performance of the Drone Delivery site after moving to a microservices architecture.

尽管函数应用已启动,但初始尝试速度较慢。 应用启动并运行后,响应时间优于在整体体系结构中运行此代码的情况。

现在,此部分体系结构几乎可无限横向扩展,同时还能提供相同的性能。 通过将此应用程序代码移到微服务,我们将性能提高了 5 到 10 倍。 由于 Fabrikam 具有专门开发此服务的团队,他们还可以在此微服务上进行循环访问,并实现增加灵活性和功能发布的优势。