当前位置: 首页 > news >正文

Azure反模式——同步IO

在完成I/O时阻塞调用线程可能会降低系统性能并影响系统的可伸缩性。

问题描述
完成I/O时,同步I/O操作会阻塞调用线程。在此时间段内,调用线程会进入等待状态,且无法执行任何工作,因而浪费了系统的处理资源。
常见I/O示例:
对数据库或任何类型的持久化存储中读写数据。
向Web服务发请求。
发布消息,或者从队列中检索消息。
读写本地文件。


出现该反模式的原因通常是:
为了逻辑执行的直观性而忽视了性能。
应用程序需要等待请求返回的响应。
应用程序所使用的库只提供同步I/O的方法。
外部库在内部使用了I/O同步。一次同步的I/O调用可以阻塞整个调用链。


以下代码将某个文件上传到AzureBlob中。在以下两个位置使用了同步I/O:CreateIfNotExists方法和UploadFromStream方法。

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");


container.CreateIfNotExists();
var blockBlob = container.GetBlockBlobReference("myblob");


// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    blockBlob.UploadFromStream(fileStream);
}

下面是等待外部服务返回响应的示例。GetUserProfile方法调用返回UserProfile的远程服务。


public interface IUserProfileService
{
    UserProfile GetUserProfile();
}


public class SyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;


    public SyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }


    // This is a synchronous method that calls the synchronous GetUserProfile method.
    public UserProfile GetUserProfile()
    {
        return _userProfileService.GetUserProfile();
    }
}

可在此处找到这两个示例的完整代码。

(https://github.com/mspnp/performance-optimization/tree/master/SynchronousIO)



解决方案
将同步I/O操作替换为异步操作。这会释放(而不是阻塞)当前线程使其执行其他有意义的工作,也帮助了提高计算资源的利用率。以异步方式执行I/O特别有利于应对客户端应用程序突发大量请求的情形。


许多库提供的方法包含同步和异步版本。尽量使用异步版本。下面是将文件上传到AzureBlob存储示例的异步实现版本。

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");


await container.CreateIfNotExistsAsync();


var blockBlob = container.GetBlockBlobReference("myblob");


// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    await blockBlob.UploadFromStreamAsync(fileStream);
}

执行异步操作时,await运算符将控制权返回给调用环境。在进行异步调用时,会继续执行await语句后面的代码。
合理设计的服务也应提供异步操作。下面是返回用户配置文件的Web服务异步版本。GetUserProfileAsync方法依赖于用户配置文件服务的异步版本。

public interface IUserProfileService
{
    Task<UserProfile> GetUserProfileAsync();
}


public class AsyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;


    public AsyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }


    // This is an synchronous method that calls the Task based GetUserProfileAsync method.
    public Task<UserProfile> GetUserProfileAsync()
    {
        return _userProfileService.GetUserProfileAsync();
    }
}

对于不提供异步操作版本的库,也许可以为同步方法创建异步包装器。遵循此方法时请小心。尽管它能提高调用异步包装器的线程响应能力,但实际上会消耗更多资源。它可能会创建一个额外的线程,另外,该线程工作完成后,与其同步的工作也是一笔开销。这篇博客(http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx)讨论了一些相关的利弊:是否应为同步方法公开的异步包装器
下面是为同步方法创建的异步包装器示例。


// Asynchronous wrapper around synchronous library method
private async Task<int> LibraryIOOperationAsync()
{
    return await Task.Run(() => LibraryIOOperation());
}


现在,调用代码可以在包装器中等待完成:


// Invoke the asynchronous wrapper using a task
await LibraryIOOperationAsync();

注意事项
生存期很短且不太可能导致资源争用的I/O操作在性能上可能与同步操作相当。读取SSD驱动器中的小文件就是这样一个例子。尽管异步I/O可带来一定的好处,但是,将某个任务调度到另一个线程,并在完成该任务时再与该线程同步所产生的开销可能会使这种好处得不偿失。不过,这种情况比较少见,大多数I/O操作还是应该以异步方式执行。


提高I/O性能可能会导致系统的其他部分成为瓶颈。例如,取消阻塞线程可能导致对共享资源的并发请求数增加,从而导致资源枯竭或受限。如果出现这种问题,可能需要增加Web服务器的数量或者将数据存储分区,以减少资源争用情况的发生。


如何检测问题
对于用户而言,应用程序可能为“死机”状态,或者定时出现挂起。应用程序可能会失败并出现超时异常。这些失败还可能返回HTTP-500(内部服务器)错误。在服务器上,客户端请求可能会被阻塞直到某个线程可用为止,导致请求队列变得过长,并显示HTTP-503(服务不可用)错误。


可执行以下步骤来帮助鉴别这类问题:
监视生产系统,并确定已阻塞的工作线程是否约束了吞吐量。
如果请求由于缺少线程而阻塞,请检查应用程序,确定哪些操作在以异步方式执行I/O。
针对执行同步I/O的每个操作进行受控的负载测试,确定这些操作是否影响了系统性能。


示例
以下部分演示如何将这些步骤应用到前面所述的示例程序。


监视 Web 服务器性能
对于AzureWeb应用程序和Web角色,有必要监视IISWeb服务器的性能。尤其是要注意请求队列的长度,以判定在高峰期间请求是否处于阻塞状态,在等待可用线程。可通过启用Azure诊断来收集这类信息。有关详细信息,请参阅:
监视Azure服务中的应用(https://docs.microsoft.com/en-us/azure/app-service/web-sites-monitor)
在Azure应用程序中创建和使用性能计数器(https://docs.microsoft.com/en-us/azure/cloud-services/diagnostics-performance-counters)
检测应用程序,了解在接受请求后如何对其进行处理。跟踪某个请求的流有助于确定该请求是否在执行缓慢的调用并阻塞了当前线程。线程分析也能反映出正在阻塞的请求。


对应用程序进行负载测试
下图显示了在承受不同负载(最大负载为4000)的情况下,前面所示同步GetUserProfile方法的性能。该应用程序是在Azure云服务Web角色中运行的ASP.NET应用程序。


该同步操作硬编码为休眠2秒以模拟同步I/O操作,因此最小响应时间略微超过2秒。当负载达到大约2500个并发时,平均响应时间达到平稳,不过,每秒请求数量持续增加。注意,这些两个度量值的刻度为对数。此时间点与测试结束时的每秒请求数相差两倍。
在隔离状态下,该测试不一定能够清楚地反映是否是同步I/O造成了问题。承受更重的负载时,应用程序可能会进入一个临界点,此时,Web服务器可能不再能及时处理请求,从而导致客户端应用程序收到超时异常。IISWeb服务器会将请求排队,并将其转发到ASP.NET线程池中运行的线程。由于每个操作都同步执行I/O,因此,线程会阻塞到该操作完成为止。随着工作负荷的增大,最终会分配并阻塞线程池中的所有ASP.NET线程。此时,传入的其他任何请求必须在队列中等待有可用线程。随着队列长度不断增大,请求开始超时。


实施解决方案并验证结果
下图显示了以异步代码版本执行的负载测试结果。



吞吐量要高得多。在与上次测试相同的持续时间内,系统已成功处理将近十倍的吞吐量增长(以每秒请求数为单位)。此外,平均响应时间相对稳定,并且是上次测试的25倍左右。

相关文章:

  • irrlicht引擎源码剖析2 - IrrlichtDevice
  • 在Azure App service 中配置时区
  • Azure 最佳实践 - API 的设计与实现(2)
  • Azure 最佳实践 - CDN
  • Azure 最佳实践 - 自动伸缩
  • 《BREW进阶与精通——3G移动增值业务的运营、定制与开发》连载之6---移动增值业务概述...
  • Azure 最佳实践 - 后台作业
  • Python 使用dlib 5行代码实现人脸比对
  • 《BREW进阶与精通——3G移动增值业务的运营、定制与开发》连载之7---WAP,SMS,MMS,移动电子邮件...
  • android studio error 'unable to merge dex'
  • 在Nebula3中加载自定义模型的思路
  • asp.net core 部署在ubuntu
  • 获取当前月的第一天和最后一天...
  • tensorflowsharp异常could not load DLL 'libtensorflow'
  • 为ArcGIS Server配置反向代理
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 2017届校招提前批面试回顾
  • CAP 一致性协议及应用解析
  • Gradle 5.0 正式版发布
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java编程基础24——递归练习
  • k8s 面向应用开发者的基础命令
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • nginx 配置多 域名 + 多 https
  • React组件设计模式(一)
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 编写符合Python风格的对象
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 理解在java “”i=i++;”所发生的事情
  • 日剧·日综资源集合(建议收藏)
  • 使用docker-compose进行多节点部署
  • elasticsearch-head插件安装
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • (11)MSP430F5529 定时器B
  • (2)STM32单片机上位机
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (Forward) Music Player: From UI Proposal to Code
  • (大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量
  • (黑马C++)L06 重载与继承
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (十三)Flask之特殊装饰器详解
  • (算法二)滑动窗口
  • (一)Java算法:二分查找
  • ../depcomp: line 571: exec: g++: not found
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .Family_物联网
  • .md即markdown文件的基本常用编写语法
  • .NET 8.0 发布到 IIS
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net流程开发平台的一些难点(1)
  • .NET下的多线程编程—1-线程机制概述
  • :如何用SQL脚本保存存储过程返回的结果集
  • ??myeclipse+tomcat