ASP.NET Web 服务、企业服务和 .NET 远程处理的性能

 

Ingo Rammer
thinktecture。

理查德·特纳
计划经理
Microsoft 分布式系统组

2005 年 8 月

总结: 比较和对比现实生活中 ASP.NET Web 服务、.NET 企业服务组件和 .NET 远程处理组件的性能特征,并获取有关如何最好地使用这些技术的建议。 ) (34 个打印页

下载关联的代码示例 ,ASMXvsEnterpriseServicesvsRemotingPerformanceTests.msi

目录

简介
目标
   这不是基准
   测试
结果
   测试 1:创建和存储订单
   测试 2:检索 Northwind 的产品数据
   测试 3:检索客户信息
.NET Framework 2.0 性能
结论
   建议
附录 A:合成基线 - 空方法测试
附录 B - 详细测试结果
   将存储订单作为对象
   存储顺序为数据集
   将产品加载为对象
   将产品加载为数据集
   将客户加载为对象
   将客户加载为数据集
   空消息,跨进程
   空消息。 跨计算机
附录 C:测试应用程序说明
   运行测试应用程序
   有趣的 Factoid - 测试应用与技术无关!
附录 D:软件和硬件设置
   应用程序环境
   基础结构环境

简介

虽然绝对性能是 (设备、硬件控制器、生命和医疗保健服务、某些金融体系) 的几个技术领域最关心的问题,但这些领域往往是少数。 大多数业务应用程序的主要目标是“正确性”、“交付时间”,并且需要尽可能快,但并不多。 提供绝对最大性能的工程应用程序的成本和工作量可能巨大:对于许多业务系统来说,峰值性能所需的大量时间和技能通常是不必要的。 但是,尽管绝对最高性能往往过于过分,但对于大多数希望最大化投资回报的企业来说,确保良好的整体系统性能仍是一个目标。

在本白皮书中,我们将提供对 .NET 中提供的三种分布式组件/服务技术中托管的实际组件/服务相对性能级别的比较分析:

  • COM+ 中托管的 .NET Enterprise Services (ES)
  • ASP.NET WEB 服务 (IIS 中托管的 ASMX)
  • IIS 和自定义主机中托管的 .NET 远程处理

(注释 System.Messaging 与 MSMQ COM API 的性能将在 System.Messaging Performance 一文) 中讨论。

目标

关于“这是最快的 Microsoft 分布式应用程序技术”或“<在此处> 插入技术名称太慢而无法使用”的说法,有一个永无止境的争论。 本文的主要目标是解决和澄清与 Microsoft 分布式技术性能相关的许多关注、误解、不准确和错误信息。

我们的目标是消除许多目前对 Microsoft 分布式组件/服务技术相对性能特征的误解,并提供一组明确的说明性测试及其结果,以及一组简洁的指导,帮助你选择最适合你需求的技术。

总之,本文的目标是:

  1. 调查大多数业务应用程序这三种技术之间的相对性能差异
  2. 更正有关一种技术对另一种技术的性能损失的感知性能损失的一些假设
  3. 帮助指导你决定在何处、何时以及如何最适当地利用每种技术
  4. 提供测试应用程序,以便可以在自己的计算机和自己的环境中运行这些测试。 强烈建议生成并运行此测试环境,并调查和分析这些技术的性能特征。 这样,便可以完全理解影响分布式系统性能的许多因素。

这不是基准

本白皮书中介绍的测试经过明确设计,目的是在受测的特定技术之间提供一致的比较结果。 这些测试不是为了测量负载下每种技术的绝对最大可能性能而设计的。

测试驱动程序 (客户端) 应用程序是单线程的,可以像调用服务响应一样快速进行串行同步调用。 在此设计中,瓶颈并不总是服务器 CPU 使用率。

多个客户端或多线程客户端可能会导致服务器每秒处理更多的调用。 对于 CPU 密集型服务器应用程序测试,多个客户端不会显著改变所测试技术的绝对或相对性能。

对于最轻的测试,使用多个客户端可以实现 (2 倍) 聚合服务器吞吐量。 多个客户端也可能在一定程度上改变各种技术的相对性能。

单个客户端还是多个客户端更现实取决于 Web 服务的部署位置和方式。 论文的结论不受与单个客户一起衡量的决定影响。

测试

在以下性能测试中,我们将探索综合基线和实际方案。 我们准备检查的常见问题包括:

  1. .NET 远程处理是否比 ASMX 快?
  2. ES 是否比远程处理慢?
  3. 对于实际方案,ASMX 是否太慢?
  • 为了检查这些声明,我们执行了许多测试。 主要测试检查对接受大型请求并执行重要工作或请求执行重要工作并返回小型或大型结果集的操作进行调用的性能。 这些测试的目的是说明在生成系统时可能体验的典型业务方案的性能。

所有这些测试都针对每个可用技术的协议运行:

  • 企业服务 (使用实时激活)
    • 无身份验证
    • 调用级身份验证和强制实施的角色访问检查
  • ASP.NET Web 服务
    • 无身份验证
    • 用户名和密码身份验证
    • 集成身份验证
  • .NET 远程处理
    • TCP/Binary,不安全
    • HTTP/Binary,不安全
    • HTTP/SOAP,不安全
    • IIS 中的 HTTP/SOAP,不安全
    • IIS 中的 HTTP/二进制文件,不安全
    • IIS 中的 HTTP/SOAP、HTTP 基本身份验证
    • IIS 中的 HTTP/二进制文件、HTTP 基本身份验证
    • IIS 中的 HTTP/SOAP,集成身份验证
    • IIS 中的 HTTP/二进制文件,集成身份验证

以下所有测试都基于重复调用服务器端方法的单个客户端应用程序。 我们计算了集合中每个测试每秒远程调用/方法调用的平均次数,并重复完整集 10 次,以获取完整的多阶段测试运行的平均值和标准偏差。

结果

测试 1:创建和存储订单

第一次测试旨在模拟每种技术在最佳情况下表现出的性能特征,即调用的操作执行大量工作。 为了执行大量工作,所调用的操作会将订单存储在数据库中,涉及将记录写入事务中的两个表。

调用方创建以下 Order 类的实例。

[Serializable]
public class Order
{
   public int OrderID;
   public String CustomerID;
   public int EmployeeID;
   public DateTime OrderDate;
   public Address ShippingAddress;
   public Address BillingAddress;
   public int ShipVia;
   public decimal Freight;
   public LineItem[] LineItems;
}

Order 类包含 一个 Address 类型的子对象和一个 20 个 LineItems 数组:

[Serializable]
public class Address
{
   public String Name;
   public String Street;
   public string City;
   public string Region;
   public string PostalCode;
   public string Country;
}

[Serializable]
public class LineItem
{
   public int ProductID;
   public double UnitPrice;
   public short Quantity;
   public double Discount;
}

此数据对象在方法调用期间从调用方传递到服务器,并通过正在测试的不同技术序列化到网络上。

服务器端实现类似于以下内容:

SqlConnection _conn;

private void InitializeComponent()
{
   // ... large parts of generated code removed 
    _conn = new SqlConnection();
   // ... large parts of generated code removed 
}

public void StoreOrder(Order o)
{
   using (_conn)
   {
      _conn.Open();
      using (SqlTransaction tx = _conn.BeginTransaction())
      {
         using (SqlCommand cmd = new SqlCommand(@"INSERT INTO Orders(EmployeeID, " + 
                      "CustomerID, OrderDate, RequiredDate, ShipVia, ShippedDate, " + 
                      "ShipName, Freight, ShipAddress, ShipCity, ShipRegion, " + 
                      "ShipCountry, ShipPostalCode) VALUES (@EmployeeID, @CustomerID, " +
                      "@OrderDate, @RequiredDate, @ShipVia, @ShippedDate, @ShipName, " +  
                      "@Freight, @ShipAddress, @ShipCity, @ShipRegion, @ShipCountry, " + 
                      "@ShipPostalCode); SELECT @@IDENTITY", _conn,tx))
         {
            cmd.Parameters.Add("@EmployeeID", o.EmployeeID);
            cmd.Parameters.Add("@CustomerID",o.CustomerID);
            cmd.Parameters.Add("@OrderDate",o.OrderDate);
            cmd.Parameters.Add("@RequiredDate",o.OrderDate.AddDays(10));
            cmd.Parameters.Add("@ShipVia",o.ShipVia);
            cmd.Parameters.Add("@ShippedDate",o.OrderDate.AddDays(5));
            cmd.Parameters.Add("@ShipName",o.ShippingAddress.Name);
            cmd.Parameters.Add("@Freight",o.Freight);
            cmd.Parameters.Add("@ShipAddress",o.ShippingAddress.Street);
            cmd.Parameters.Add("@ShipCity",o.ShippingAddress.City);
            cmd.Parameters.Add("@ShipRegion",o.ShippingAddress.Region);
            cmd.Parameters.Add("@ShipCountry",o.ShippingAddress.Country);
            cmd.Parameters.Add("@ShipPostalCode",o.ShippingAddress.PostalCode);
            
            decimal orderID = (decimal) cmd.ExecuteScalar();
            o.OrderID = (int) orderID;
         }

         using (SqlCommand cmd = new SqlCommand(@"INSERT INTO [Order Details] (OrderID, " +
                      "ProductID, UnitPrice, Quantity, Discount) VALUES (@OrderID, " + 
                      "@ProductID, @UnitPrice, @Quantity, @Discount)", _conn,tx))
         {
            cmd.Parameters.Add("@OrderID",SqlDbType.Int);
            cmd.Parameters.Add("@ProductID",SqlDbType.Int);
            cmd.Parameters.Add("@UnitPrice",SqlDbType.Money);
            cmd.Parameters.Add("@Quantity",SqlDbType.SmallInt);
            cmd.Parameters.Add("@Discount",SqlDbType.Real);

            foreach (LineItem itm in o.LineItems)
            {
               cmd.Parameters["@OrderID"].Value = o.OrderID;
               cmd.Parameters["@ProductID"].Value = itm.ProductID;
               cmd.Parameters["@UnitPrice"].Value = itm.UnitPrice;
               cmd.Parameters["@Quantity"].Value = itm.Quantity;
               cmd.Parameters["@Discount"].Value = itm.Discount;
               cmd.ExecuteNonQuery();
            }
         }
         
         tx.Commit();
      }
      _conn.Close();
   }
}

测试运行完成后,服务器端逻辑使用第二个事务删除插入的记录,无论此测试运行多少次,都提供“水平游戏环境”。

图 1. 使用实际的服务器端实现传递“订单”跨计算机

在查看图 1 中的测试结果时,请注意,ES @73 CPS 和 ASP.NET Web 服务 @63 CPS 之间只有 14% 的差异,最快协议和最慢协议之间的最大差异为 45%。 这清楚地表明,随着执行的工作量与传输数据所花费的时间成比例的增长,任何给定传输对另一个传输的好处会降低。

我们还创建了匹配 ADO.NET DataSet 测试,生成包含如图 2 中定义的数据表的类型化数据集。

图 2. 此数据集表示采购订单

此数据集与生成的 SqlDataAdapter 一起使用,包括类似于上述代码片段所示的 SqlCommand。

当订单作为 DataSet 从调用方传递到服务器并存储时,我们得到如图 3 所示的结果。

图 3. 将采购订单存储为数据集

请注意,使用 DataSet 对性能的重大影响 () 约 50%,同时协议之间的差异进一步缩小。 同样需要注意的是,传递数据对象的最慢结果在传递 ADO.NET 数据集时超过了最佳性能。

这清楚地表明,在系统层之间传递 ADO.NET 数据集会产生严重的性能损失。

测试 2:检索 Northwind 产品数据

此测试的目的是说明从不执行重大工作的服务返回相对相当大的数据量时,每种技术的性能特征。

为了对此方案进行建模,我们构建了从已知SQL Server“Northwind”示例数据库中检索并返回“product”记录列表的方法。

我们预计 (ES/COM+ 和远程处理二进制/TCP) 的二进制序列化技术将在此测试中表现最佳:我们还预计,由于传输的数据量较大,以及更复杂的序列化和反序列化机制,基于 SOAP 的技术将返回较低的性能级别。

在第一次测试中,我们定义了一个 [Serializable] 类,该类具有与 XML 序列化程序兼容的公共成员:

[Serializable]
public class Product
{
   public int ProductID;
   public string ProductName;
   public int SupplierID;
   public int CategoryID;
   public string QuantityPerUnit;
   public decimal UnitPrice;
   public short UnitsInStock;
   public short UnitsOnOrder;
   public short ReorderLevel;
   public bool Discontinued;
}

在服务器端实现中,我们打开与 SQL Server 的数据库连接,对数据库执行查询,并使用 ADO.NET SqlDataReader 填充包含此类的 77 个 Product 实例的 ArrayList。 然后,ArrayList 已返回到调用方。

结果如图 4 所示。

图 4。 以对象的形式检索 Northwind 产品目录

这些结果显示,企业服务 (DCOM) 和基于 TCP 的二进制远程处理为不安全的调用提供同等级别的性能。 结果还显示,ASP.NET Web 服务提供约 62% 的企业服务性能,而速度最慢的协议仅允许每秒 17% 的调用数,而最快的协议则只允许 17%。

在此测试以及以下所有测试中,很明显为什么通常不建议使用 .NET 远程处理 SoapFormatter 来生成 SOAP 服务:ASP.NET Web 服务堆栈的速度大约是三倍。

在此测试的第二种方法中,我们实现了一种方法,该方法在直接基于基础表布局的 ADO.NET DataSet 对象中返回请求的数据,并使用 SqlDataAdapter 填充 DataSet 的 DataTable。 表定义如图 5 所示。

图 5。 用于访问产品目录的类型化数据集

图 6 演示了结果:

图 6。 以数据集的形式检索 Northwind 产品目录

从此测试的结果中可以清楚地看出,从服务向调用方返回数据集会显著降低性能 - 对于二进制传输而言,性能会降低约 75%! 另请注意,四个最快协议的性能是完全相同的,这意味着所有四种技术都受数据集的序列化、反序列化和传输的限制。 另请注意,速度在最快 (ASMX @ 39 cps) 与最慢 (远程处理 HTTP/SOAP (IIS 之间的性能范围) 集成安全性 (25 cps) )之间仅相差 14 cps,仅下降了 30%。

这清楚地表明,虽然使用数据集在应用程序层之间传递信息很方便,但这样做会对性能产生重大影响。 稍后我们将返回此主题。

同样重要的是要指出,循环访问 ADO.NET 数据集并构造数据对象集合并序列化此集合比仅将 ADO.NET 数据集返回到调用方更快! 如果这是一个真正的系统,实现为在服务器和调用方之间传递数据集,则只需少量工作,我们几乎可以将这部分系统的性能提高两倍。

测试 3:检索客户信息

在下一个测试中,我们希望通过从“Northwind”SQL Server示例数据库检索单个客户记录,来检查从服务器到客户端传输极少量数据时每种技术的相对性能特征。

我们创建了以下 [Serializable] Customer 类,用于将检索到的数据从服务器传回客户端:

[Serializable]
public class Customer
{
   public String CustomerID;
   public String CompanyName;
   public String ContactName; 
   public String ContactTitle;
   public string Address; 
   public string City;
   public string Region;
   public string PostalCode; 
   public string Country;
   public string Phone;
   public string Fax;
}

下面演示了使用 SqlDataReader 填充 Customer 类的新实例的服务器端实现的一部分:

public Customer GetCustomer(string id)
{
   using (SqlConnection conn = new SqlConnection(...))
   {
      conn.Open();

      String sql = "SELECT CustomerID, CompanyName, ContactName, "
                   "ContactTitle, Address, City, Region, " +
                   "PostalCode, Phone, Fax, Country FROM " + 
                   "Customers WHERE (CustomerID = @CustomerID)";

      using (SqlCommand cmd = new SqlCommand(sql,conn))
      {
         cmd.Parameters.Add("@CustomerID", id);
         SqlDataReader rdr = cmd.ExecuteReader();
         if (rdr.Read())
         {
            Customer c = new Customer();
            c.CustomerID = (string) rdr[0];
            c.CompanyName = (String) rdr[1];
            c.ContactName = (String) rdr[2];
            c.ContactTitle = (String) rdr[3];
            c.Address = (String) rdr[4];
            c.City = (String) rdr[5];
            c.Region = rdr.IsDBNull(6) ? "" : rdr[6] as string;
            c.PostalCode = (String) rdr[7];
            c.Phone = (String) rdr[8];
            c.Fax = (String) rdr[9];
            c.Country = (String) rdr[10];
            return c;
         }
         else
         {
            return null;
         }
      }
   }
}

性能比较的结果如图 7 所示。

图 7。 以对象的形式检索客户数据

这些结果更突出地突出了每个基础技术之间的性能差异。 这些差异在此测试中非常明显,因为检索和返回单个记录的工作在总呼叫成本中所占的百分比要低得多,因此传输成本的作用更为显著。 因此,由于 SOAP/HTTP 的传输成本高于二进制/DCOM,因此 SOAP 传输的吞吐量级别远低于二进制机制。

接下来,我们测试了返回类型化数据集的相同操作,如图 8 所示。

图 8。 用于访问客户信息的类型化数据集

此数据集是使用如下所示的服务器端实现填充的:

SqlDataAdapter _customerAdapter;
SqlCommand _customerSelect;
SqlConnection _conn;

private void InitializeComponent()
{
   // ... large parts of generated code removed 

    _conn = new SqlConnection();
    _customerAdapter = SqlDataAdapter();
    _customerSelect = SqlCommand();

    _customerSelect.CommandText = "SELECT CustomerID, CompanyName, " +
               "ContactName, ContactTitle, Address, City, Region, " +
               "PostalCode, Phone, Fax, Country FROM Customers WHERE " + 
               "(CustomerID = @CustomerID)";

_customerSelect.Connection = _conn;

_customerSelect.Parameters.Add(new SqlParameter("@CustomerID", 
               SqlDbType.NVarChar, 5, "CustomerID"));

    _customerAdapter.SelectCommand = this.sqlSelectCommand3;

    _customerAdapter.TableMappings.AddRange(new DataTableMapping[] {
           new DataTableMapping("Table", "Customers", new DataColumnMapping[] {
                  new DataColumnMapping("CustomerID", "CustomerID"),
                  new DataColumnMapping("CompanyName", "CompanyName"),
                  new DataColumnMapping("ContactName", "ContactName"),
                  new DataColumnMapping("ContactTitle", "ContactTitle"),
                  new DataColumnMapping("Address", "Address"),
                  new DataColumnMapping("City", "City"),
                  new DataColumnMapping("Region", "Region"),
                  new DataColumnMapping("PostalCode", "PostalCode"),
                  new DataColumnMapping("Phone", "Phone"),
                  new DataColumnMapping("Fax", "Fax"),
                  new DataColumnMapping("Country", "Country")})});

   // ... large parts of generated code removed 
}


public PerfTestDataSet GetCustomerAsDataset(string id)
{
   using (_conn)
   {
      _conn.Open();
      customerAdapter.SelectCommand.Parameters["@CustomerID"].Value = id;
      
      PerfTestDataSet ds = new PerfTestDataSet();
      _customerAdapter.Fill(ds,"Customers");
      return ds;
   }
}

在测试框架中运行此代码时,我们收到了如图 9 所示的结果。

图 9. 以数据集的形式检索客户数据

与前面的测试类似,交换 ADO.NET 数据集显然会对性能产生负面影响,并且所有技术的吞吐量差异会小于返回表明 ADO.NET 数据集限制吞吐量的序列化对象时。

.NET Framework 2.0 性能

在撰写本文时,Microsoft 正在努力提供.NET Framework 2.0 (以前代号为“Whidbey”) 其中包括大量改进、增强和新功能,包括:

  • 完全支持基于 x64 和 IA64 的计算机
  • 通过 System.Transactions 支持全新的事务
  • System.Net 的许多增强功能,包括连接感知和对代理的支持
  • Windows 窗体和Web Forms的显著增强功能
  • “ClickOnce”自动应用程序部署基础结构

除了.NET Framework 2.0 中包含的新增功能和增强功能外,许多方面(包括 BCL、XML 序列化、网络吞吐量、ADO.NET、ASP.NET 等)都做了大量工作,以显著提高性能和提高内存利用率。

以下白皮书中提供了这些性能和吞吐量改进的早期示例:

  • 比较 XML 性能

    .NET Framework 2.0 Beta 2、.NET Framework 1.1 和 Sun Java 1.5 平台

  • 比较 Web 服务性能

    .NET Framework 2.0、.NET 1.1、Sun JWSDP 1.5 和 IBM WebSphere 6.0 的 WS 测试 1.1 基准测试结果

随着.NET Framework 2.0 继续向制造业) 发布 RTM (,未来几个月将发布更多的性能分析和比较白皮书。 若要随时了解此类白皮书,请访问以下联机资源:

结论

上述实际测试的结果如表 1 所示。

表 1. 结果摘要

每秒调用数
(舍入)
企业服务 ASP.NET Web 服务 .NET 远程处理 TCP 二进制文件
测试 1:存储订单
) 序列化数据
b) 数据集

73
35

63
34

72
34
测试 2:检索产品
) 序列化数据
b) 数据集

147
39

93
39

149
39
测试 3:检索客户
) 序列化数据
b) 数据集

618
90

289
83

682
91

这些结果显示了可以从实际应用程序获得的性能特征,并且 Web 服务 (通常被视为非常缓慢) 和 DCOM (这显然是此测试) 中速度最快的分布式应用程序技术之一,实际上对于实际应用程序而言,差异很小。

以下部分将更详细地讨论我们对所测试技术的观察结果,但可以在此处进行总结:

  • 虽然 ASMX 并不是我们目前交付的最快技术,但它的性能对于大多数业务方案来说已经足够了。 结合 ASMX 服务易于横向扩展并提供大量其他优势(如互操作性和未来兼容性)的事实,ASMX 应是当今构建服务的主要选择。
  • 如果绝对性能是最关心的问题,则应使用企业服务组件来构建系统性能最关键的部分。 COM+ 整体表现最佳,是分布式组件的安全、可靠、可靠的托管环境。 ES/COM+ 还将与 Indigo 服务很好地集成,将 ES 组件转换为 Indigo 服务相对简单。
  • 虽然 .NET 远程处理在通过 TCP 使用二进制序列化时性能非常好,但当远程处理组件托管在 IIS 中和/或发送和接收 SOAP 消息时,其性能会降低。 加上 .NET 远程处理组件不会与 .NET 远程处理终结点以外的任何内容进行互操作,我们敦促你尽可能在远程处理之前考虑 ASMX 或 ES。

从这些结果中得出的一个关键观察是,如果服务器上执行的工时量在每次调用的总持续时间中所占的百分比不是很大百分比,则传输时间将占呼叫时间的很大一部分。 如果从 COM+) 下的“快速”传输 ((例如 DCOM)迁移到“成本更高的”传输 (例如 ASMX) 下的 SOAP/HTTP,则可能会遇到严重的性能影响。 因此,我们明确说明了如何在服务器上针对每个方法调用执行尽可能多的工作,并避免进行不必要的网络遍历。

这些技术比其他技术的性能优势随着所调用服务正在执行的工作量的增加而降低。 许多文章(包括 .NET 企业服务性能)中都介绍了这一观点。

这些测试还清楚地表明,这些技术之间的性能差异小于特定于应用程序的决策(例如数据集的使用或序列化结构)之间的差异。

虽然本文中我们非常关注性能数据,但性能并不是在规划开发项目时应考虑的唯一因素。 安全性、可伸缩性、管理、开发人员工作效率、互操作性和 Microsoft 分布式技术的未来方向等因素都会发挥作用。 《今日开发分布式服务 》提供了有关如何最好地选择何时何地利用每种技术以及未来情况的规范性指导。

建议

根据这些测试的结果,并根据我们未来的技术方向和最佳做法,你将能够通过遵循以下建议来构建不太容易出现性能问题的系统:

尽可能使用 ASMX

为了为服务提供最广泛的范围,请考虑尽可能使用 ASMX 发布服务。 这样,任何可以通过 HTTP 与 SOAP 通信的系统都可以调用服务,而不管该系统在哪种平台、设备或语言上运行或实现。 从上述结果中可以看出,ASMX 在许多方案中的表现非常出色。 即使在由单线程应用程序调用的单处理器计算机上,ASMX 也能够每秒处理大约 63 次繁重操作,每秒仅比等效 ES 组件少 10 次调用! 在生产环境中,托管 ASMX 服务的单个盒子的性能要高得多,因此 ASMX 性能不应阻止你采用这一重要技术。

Web 服务表现出其他有用的特征和功能,包括 (但不限于) :

  • 可伸缩性 - 使用负载均衡技术(例如 Windows 网络负载均衡)或来自 Cisco 和 F5 等供应商的硬件设备横向扩展 Web 服务非常简单。 有关此主题的详细信息,请关注。
  • 可用性 - 可以将 ASMX Web 服务配置为高度可用,结合使用负载均衡等技术,以及内置于 Windows 2003 Server (的 IIS6 基础结构的强大功能,例如自动回收和重启故障服务) 。
  • 互操作性 - Web 服务是标准化可互操作的安全、可靠、事务处理通信协议的核心,使不同的异类系统能够自由通信。
  • 生产力 - 对于大多数开发人员来说,构建 ASMX Web 服务非常简单,只需明智地使用一些属性并遵循一些建议即可。

将 ASMX 用于 Web 服务

ASMX 是一个用于生成和部署服务的绝佳平台的原因有很多,如 规范性指南中所述,从本文的结果可以看出,ASMX 在谈到 SOAP 时通常优于远程处理。

同样需要注意的是,虽然 .NET 远程处理能够通过 SoapFormatter 和 HttpChannel) 支持 SOAP/HTTP (,但我们建议不要使用远程处理来公开 Web 服务。 .NET 远程处理 SoapFormatter 使用 RPC 编码,它不再是 SOAP (document-literal 编码的标准方式,是大多数供应商技术和平台未来使用的标准编码机制) :因此,与远程处理服务的互操作性将非常有限。

选择 Web 服务实现可伸缩性

上面没有说明但与本讨论高度相关的一件事是,Web 服务主要通过 HTTP(一种易于负载均衡的无状态通信技术)进行通信。 这意味着也可以使用 TCP 或 HTTP ((即在 IIS) 中托管)对无状态远程处理组件进行负载均衡,但如果需要会话状态,这可能会变得复杂和有问题。

若要对 ES/COM+ 组件进行负载均衡,必须使用组件负载均衡 (CLB) (Application Center 2000 的一项功能)。 唉,AppCenter 2000 于 2006 年 7 月进入扩展支持,支持将于 2011 年结束。 因此,为了避免在 2011 年之前重新构建系统以不再依赖 CLB,强烈建议避免使用这种技术进行新开发,并尽快迁移到其他策略。 一个好的迁移策略是使用 ASMX Web 服务在当前 COM+ 组件前面,这只需将调用直接传递到 COM+ 组件,并如上所述对 Web 服务进行负载均衡。

仅在服务内使用企业服务

.NET 企业服务是.NET Framework的一个层,提供对 COM+ 提供的丰富服务的访问和支持。 如果需要任何丰富的 COM+ 服务,例如跨多个对象/服务的分布式事务、精细的安全性、JITA 和对象池,则应选择“企业服务”。 企业服务和 COM+ 是经过充分测试、高度优化的技术,可提供极其快速、低延迟的跨进程和跨计算机调用

但是,我们强烈建议不要广泛公开企业服务组件,并且仅在服务边界内使用此技术。 如果你希望提供对服务的访问权限,我们敦促你使用 ASMX 通过 Web 服务公开服务的功能。 如果你有一个已建立的 ES/COM+ 代码库,建议尽可能使用 ASMX Web 服务在 COM+ 服务前面。

考虑托管基础结构,胜过构建自己的基础结构

如上所示,ASP.NET 提供了高性能的 Web 服务,这些服务具有高度互操作性,还受益于 IIS 提供的出色托管设施,尤其是 Windows 2003 服务器中的 IIS6。

COM+ 在 Microsoft 平台上提供最快的组件性能,并为企业服务和 COM+ 组件提供经过尝试、受信任、安全、安全可靠的托管环境。 它还提供分布式事务支持、安全性 (身份验证和加密) 、可靠性、可用性、管理服务,是托管服务中基于组件的复杂系统的绝佳选择。

虽然通过 TCP 使用二进制序列化的 .NET 远程处理也提供了出色的性能,但它不提供任何托管环境、安全性、可靠性、可用性或管理功能。“开箱即用”。 若要托管使用 TCP 的远程处理组件,必须编写自己的 (安全、可缩放、可靠的) 托管应用程序,这不是一项简单任务。 如果要通过 HTTP 使用远程处理,可以选择将 IIS 用作主机,但正如你所看到的,在 IIS 中托管远程处理组件并且说话 SOAP/HTTP 会对远程处理性能产生很大影响,通常 ASMX 的性能会更好!

因此,我们敦促你考虑在 COM+ 内 IIS 或企业服务组件中托管的 ASMX 中提供服务,而不是尽可能使用远程处理。

避免在服务之间传递数据集

在我们的测试中,选择服务和调用方交换数据的方式(作为数据集或序列化结构)比选择任何特定的分布式系统技术对性能的影响要大得多。

从性能角度来看,我们强烈建议你将 DataSet 用作数据传输机制的使用限制为应用程序真正需要数据集的部分,并尽可能选择 [Serializable] 结构。

ADO.NET 数据集提供了一种很好的方法来检索、操作、排序和调整数据,将其存储在本地以供脱机使用,并将更改同步回中央数据库。 如果这是应用程序的要求,那么当然,这是一个有效的选择。 在 .NET Framework 1.x 中,DataSet 值始终序列化为 XML (,即使你声明希望它们序列化为二进制文件或将其与 ES 或远程处理) 一起使用,并且它们还包含描述数据的 XSD。 当然,这会影响性能。

更重要的是,请记住数据集是 。特定于 NET 的技术 和 将显著限制互操作性,因为其他平台不一定能够序列化和分析包含序列化数据集的数据。

通过交换序列化的数据结构,可以获得相当大的性能改进。 内置的运行时格式设置和 XML 序列化框架使它变得简单,如上面的测试所示。

使用经过身份验证的连接共享

将所有将 Internet 信息服务器用作主机以及集成安全性的测试都配置为使用可用于 ASP.NET 客户端和 .NET 远程处理的选项 useAuthenticatedConnectionSharinguseUnsafeAuthenticatedConnectionSharing 来运行。 此设置允许客户端和服务器重复使用现有的 NTLM 身份验证连接。

在图 1 中最初提供的测试期间设置此选项时,可以看到性能结果, (将采购订单存储为对象) 。 请注意,仅当你将 Internet Information Server (IIS) 用作服务器,并且你在虚拟目录的配置中强制要求“Windows In Security”时,此设置才有效。

MSDN 杂志 2004 年 9 月刊的 Web Q&A:缓存转换、连接共享等专栏讨论了这些权衡。

图 10. UnsafeAuthenticatedConnectionSharing 的影响

可以从 Web 服务 ASP.NET 将 Windows 集成安全性 与此设置结合使用,如下所示:

MyWebService svc = new MyWebService();
svc.Credentials = System.Net.CredentialCache.DefaultCredentials;
svc.UnsafeAuthenticatedConnectionSharing = true;

使用 .NET 远程处理时,如下所示:

IMyRemote rem = (IMyRemote) Activator.GetObject(typeof(IMyRemote), "HTTP://...");
IDictionary snkprops = ChannelServices.GetChannelSinkProperties(rem);
snkprops["unsafeauthenticatedconnectionsharing"] = true;

附录 A:合成基线 - 空方法测试

此测试“基线”要检查的基础技术之间的性能差异。 这些测试对不接受任何参数、不执行任何工作且不返回任何结果的操作发出调用。 服务器端实现简单如下:

public void TransferEmpty()
{
   // do nothing
}

注意 请务必记住,这些综合基线数字的呈现纯粹是为了说明所检查的每一项技术的通信基础结构的相对性能水平,并不反映实际系统中预期的性能特征。

图 11. 调用没有参数的方法跨进程 (综合基线)

图 11 中的测试结果与常见假设非常匹配:ES 依赖于 COM+ 和 DCOM,以及高性能 LPC (轻型过程调用) 内置通信基础结构,因此提供了迄今为止的最佳性能。 .NET 远程处理线路协议排在第二位。 ASP.NET Web 服务排在第三位,但仍比 IIS 中托管的 .NET 远程处理组件快。

第二步是跨计算机运行此测试。 我们通过设置其“Url”属性重定向生成的 ASP.NET,导出了 COM+/企业服务组件的应用程序代理,并将 Activator.GetObject () 用于此测试的 .NET 远程处理部分。 结果与前面的测试非常一致,但由于涉及网络遍历,每秒调用数略少:

图 12. 跨计算机调用没有参数的空方法 (合成基线)

请注意,跨计算机网络遍历基本上限制了 (ES 和远程处理二进制/TCP) 的最快传输,而几乎不会影响速度较慢的传输。 这清楚地说明了将 COM+ 的跨进程通信机制与主要为网络流量 ((即 IIS & ASMX) )而构建的技术相比是多么有效。

请务必记住,如上所述,此测试非常人工,仅说明传输的功能。

附录 B - 详细测试结果

将订单存储为对象

测试 平均调用数/秒 标准偏差
企业服务 73 0.39
企业服务 (身份验证) 73 0.34
远程处理 TCP/二进制 72 2.71
远程处理 HTTP/二进制 68 0.86
远程处理 HTTP/二进制 (IIS) 66 0.31
ASP.NET Web 服务 63 2.71
远程处理 HTTP/二进制 (IIS) 密码 63 0.39
远程处理 TCP/SOAP 56 1.97
远程处理 HTTP/SOAP 53 0.57
远程处理 HTTP/二进制 (IIS) 集成 51 0.28
远程处理 HTTP/SOAP (IIS) 50 0.16
ASP.NET Web 服务 - 集成 50 0.30
远程处理 HTTP/SOAP (IIS) 密码 49 0.29
远程处理 HTTP/SOAP (IIS) 集成 40 0.84

将订单存储为数据集

测试 平均调用数/秒 标准偏差
企业服务 35 0.54
企业服务 (身份验证) 35 0.51
ASP.NET Web 服务 34 0.43
远程处理 TCP/二进制 34 0.85
远程处理 HTTP/二进制 32 0.77
远程处理 HTTP/二进制 (IIS) 32 1.10
远程处理 TCP/SOAP 32 0.77
远程处理 HTTP/二进制 (IIS) 密码 31 1.47
远程处理 HTTP/SOAP 30 0.68
远程处理 HTTP/SOAP (IIS) 30 0.48
远程处理 HTTP/SOAP (IIS) 密码 30 0.46
ASP.NET Web 服务 - 集成 29 0.37
远程处理 HTTP/二进制 (IIS) 集成 28 0.37
远程处理 HTTP/SOAP (IIS) 集成 26 0.31

将产品作为对象加载

测试 平均调用数/秒 标准偏差
远程处理 TCP/二进制 149 3.05
企业服务 147 2.29
企业服务 (身份验证) 146 2.49
远程处理 HTTP/二进制 118 2.13
远程处理 HTTP/二进制 (IIS) 114 0.63
远程处理 HTTP/二进制 (IIS) 密码 106 1.19
ASP.NET Web 服务 93 1.04
远程处理 HTTP/二进制 (IIS) 集成 76 0.81
ASP.NET Web 服务 - 集成 67 0.35
远程处理 TCP/SOAP 33 0.34
远程处理 HTTP/SOAP 30 0.32
远程处理 HTTP/SOAP (IIS) 30 0.25
远程处理 HTTP/SOAP (IIS) 密码 29 0.16
远程处理 HTTP/SOAP (IIS) 集成 26 0.14

将产品作为数据集加载

测试 平均调用数/秒 标准偏差
ASP.NET Web 服务 39 0.12
企业服务 39 0.26
企业服务 (身份验证) 39 0.21
远程处理 TCP/二进制 39 1.16
远程处理 HTTP/二进制 (IIS) 36 0.24
远程处理 HTTP/二进制 35 1.10
远程处理 HTTP/二进制 (IIS) 密码 35 0.17
ASP.NET Web 服务 - 集成 33 0.09
远程处理 HTTP/二进制 (IIS) 集成 31 0.21
远程处理 TCP/SOAP 30 1.27
远程处理 HTTP/SOAP (IIS) 29 0.12
远程处理 HTTP/SOAP 28 1.07
远程处理 HTTP/SOAP (IIS) 密码 28 0.06
远程处理 HTTP/SOAP (IIS) 集成 25 0.08

将客户加载为对象

测试 平均调用数/秒 标准偏差
远程处理 TCP/二进制 682 12.32
企业服务 618 13.78
企业服务 (身份验证) 616 7.76
远程处理 HTTP/二进制 406 7.84
远程处理 TCP/SOAP 359 11.62
远程处理 HTTP/二进制 (IIS) 324 4.26
ASP.NET Web 服务 289 2.68
远程处理 HTTP/SOAP 267 6.18
远程处理 HTTP/二进制 (IIS) 密码 261 2.39
远程处理 HTTP/SOAP (IIS) 214 2.84
远程处理 HTTP/SOAP (IIS) 密码 186 0.81
远程处理 HTTP/二进制 (IIS) 集成 134 0.95
ASP.NET Web 服务 - 集成 130 0.67
远程处理 HTTP/SOAP (IIS) 集成 112 0.55

将客户加载为数据集

测试 平均调用数/秒 标准偏差
远程处理 TCP/二进制 91 2.69
企业服务 90 0.30
企业服务 (身份验证) 90 0.26
ASP.NET Web 服务 83 0.24
远程处理 HTTP/二进制 80 0.67
远程处理 TCP/SOAP 78 1.04
远程处理 HTTP/二进制 (IIS) 78 0.22
远程处理 HTTP/二进制 (IIS) 密码 75 0.26
远程处理 HTTP/SOAP 71 0.92
远程处理 HTTP/SOAP (IIS) 67 0.34
远程处理 HTTP/SOAP (IIS) 密码 64 0.20
ASP.NET Web 服务 - 集成 62 0.14
远程处理 HTTP/二进制 (IIS) 集成 59 0.15
远程处理 HTTP/SOAP (IIS) 集成 52 0.13

空消息,跨进程

测试 平均调用数/秒 标准偏差
企业服务 14687 52.66
企业服务 (身份验证) 12293 31.71
远程处理 TCP/二进制 3538 38.87
远程处理 HTTP/二进制 1069 8.05
远程处理 TCP/SOAP 1075 11.87
远程处理 HTTP/SOAP 612 7.88
远程处理 HTTP/二进制 (IIS) 589 1.83
ASP.NET Web 服务 517 1.44
远程处理 HTTP/二进制 (IIS) 集成 451 1.19
远程处理 HTTP/二进制 (IIS) 密码 421 0.90
远程处理 HTTP/SOAP (IIS) 406 1.76
ASP.NET Web 服务 - 集成 403 0.76
远程处理 HTTP/SOAP (IIS) 集成 336 0.57
远程处理 HTTP/SOAP (IIS) 密码 321 0.37

空消息。 跨计算机

测试 平均调用数/秒 标准偏差
企业服务 (身份验证) 3068 25.35
企业服务 3048 38.24
远程处理 TCP/二进制 2609 77.28
远程处理 TCP/SOAP 760 31.65
远程处理 HTTP/二进制 704 8.00
远程处理 HTTP/二进制 (IIS) 479 2.60
远程处理 HTTP/SOAP 413 10.63
ASP.NET Web 服务 399 2.00
远程处理 HTTP/二进制 (IIS) 密码 351 1.98
远程处理 HTTP/SOAP (IIS) 309 1.38
远程处理 HTTP/SOAP (IIS) 密码 252 1.25
远程处理 HTTP/二进制 (IIS) 集成 156 0.52
ASP.NET Web 服务 - 集成 150 0.57
远程处理 HTTP/SOAP (IIS) 集成 133 0.28

附录 C:测试应用程序说明

运行测试应用程序

可以在本文开头指定的位置下载测试应用程序。 若要在自己的硬件上执行这些测试,至少需要两台计算机:一台客户端和一台服务器。 此外,还需要有权访问包含 Northwind 示例数据库的SQL Server安装。 在包含源代码的 ZIP 存档中的文件README.TXT中找到有关如何部署、配置和运行此测试套件的分步指南。

有趣的 Factoid – 测试应用与技术无关!

你将在测试应用程序中看到,可以创建与传输无关的应用程序。 在示例应用程序中,你将找到一个方法 Test.CreateProxy () ,该方法创建对远程服务的引用,并返回一个客户端包装器, (Web 服务代理或用于 ES 和 .NET 远程处理) 实现特定接口的相应 TransparentProxy。 调用此方法时,可以指定应使用哪个传输协议、哪些安全设置、格式化程序等。 其余应用程序不关心基础协议。 这样就可以创建与协议无关的应用程序,从而根据应用程序环境选择合适的协议。

附录 D:软件和硬件设置

已对三台计算机执行以下测试:客户端、应用程序服务器和数据库服务器。 所有测试都已使用足够数量的可用物理内存执行,以防止不必要的分页。

应用程序环境

  • 客户端:使用 C# 编写的单线程控制台应用程序,在发布模式下使用 Visual Studio.NET 2003 构建。
  • ASP.NET Windows Server 2003 上的 IIS 6.0 中运行的 Web 服务
  • 不同配置的企业服务组件 (库激活和服务器激活,) Windows Server 2003 上的 COM+ 1.5 中运行没有身份验证和调用级身份验证
  • 作为 C# 控制台应用程序的独立 .NET 远程处理服务器,以 C# 编写,在发布模式下使用 Visual Studio .NET 2003 构建

基础结构环境

Client:

  • CPU:2.4 GHz Intel P4
  • 内存:768 MB
  • O/S:Windows Server 2003 Standard Edition (截至 2004 年 6 月的所有修补程序)
  • .NET Framework 1.1 SP1

应用程序服务器:

  • CPU:2.8 GHz Intel P4
  • 内存:1024 MB
  • O/S:Windows Server 2003 Standard Edition
  • .NET Framework 1.1 SP1

数据库服务器:

  • CPU:2.8 GHz Intel P4 Prescott,带 HT,800 MHz FSB
  • 内存:1024 MB
  • O/S:Windows Server 2003 Standard Edition
  • 数据库:使用 SP3A) SQL Server 2000 Enterprise Edition (
  • .NET Framework 1.1 SP1

网络:

  • 100 Mbps,交换以太网