UML软件工程组织

ASP.NET Web 服务、企业服务和 .NET Remoting 的性能
作者:Richard Turner

摘要:比较并对比实际的 ASP.NET Web 服务、.NET 企业服务组件和 .NET Remoting 组件之间的性能特点,并获得如何以最佳方式使用这些技术的建议。

本页内容
简介 简介
目标 目标
结果 结果
.NET Framework 2.0 性能 .NET Framework 2.0 性能
小结 小结
附录 A:合成基线 — 空方法测试 附录 A:合成基线 — 空方法测试
附录 B — 详细测试结果 附录 B — 详细测试结果
附录 C:测试应用程序注意事项 附录 C:测试应用程序注意事项
附录 D:软件和硬件安装 附录 D:软件和硬件安装

简介

虽然一些技术领域(设备、硬件控制器、人身医疗保健服务、某些金融系统)极为重视绝对性能,但是这些领域趋向于属于少数领域行列。大多数商业应用程序的首要目标是“正确性”、“交付时间”以及必要的高执行效率,仅此而已。创建能够提供绝对高性能的应用程序所花费的成本和精力是巨大的;大多数商业系统通常不需要实现高峰性能所需的时间和技巧。然而,追求绝对的高性能通常是一种过度行为,因此,保证系统整体性能良好仍然是希望获得最大投资回报的商业人士所追求的一个目标。

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

寄宿在 COM+ 内的 .NET 企业服务 (ES)

寄宿在 IIS 内的 ASP.NET Web 服务 (ASMX)

寄宿在 IIS 和自定义主机内的 .NET Remoting

System.Messaging 与 MSMQ COM API 的性能比较在 System.Messaging Performance 一文中阐述)。

目标

关于“哪一种技术是 Microsoft 最快的分布式应用程序技术”或“<请在这里插入技术名称> 使用起来太慢”的观点,业界始终存在着永无休止的辩论。本文的主要目标就是解决和阐明与 Microsoft 分布式技术的性能相关的许多问题、误解、不准确和错误的信息。

我们的目的是消除目前存在的许多误解,这些误解均与每个 Microsoft 分布式组件/服务技术的相对性能特点相关,并且提供一组清晰的说明性测试、测试结果以及一组简洁的指导,它们将帮助您根据需要选择最合适的技术。

简言之,本文的目标是:

1.

针对大多数商业应用程序研究这三种技术之间的相对性能差异

2.

纠正一些假定:关于一种技术感知到的性能损失大于另一种技术

3.

帮助并指导您决定使用每项技术的最佳地点、时间和方式。

4.

提供一个测试应用程序,以便您能够在自己的环境和计算机上运行这些测试。我们强烈建议您构建并运行这个测试环境,以研究和分析这些技术的性能特点。这样,您将能够完全理解影响分布式系统性能的多种因素。

这不是一个基点

本白皮书介绍的测试旨在提供接受测试的特定技术之间的一致比较结果。这些测试不是为度量每项技术在负载时的最大绝对性能而设计的。

测试驱动程序(客户端)是单线程的,因此序列化同步调用与调用服务的响应速度一样快。使用这种设计方式时,瓶颈通常不是服务器 CPU 的使用率。

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

对于最轻量的测试而言,使用多个客户端可能会实现更高的 (2X) 累积服务器吞吐量。多个客户端也可能改变不同技术的相对性能。

单一客户端或多个客户端是否更为实用依赖于 Web 服务的部署地点和部署方式。本文的结论不受使用单一客户端进行度量的决策影响。

测试

在以下性能测试中,我们将探究合成基线和实际情况。我们设定要检查的通用问题是:

1.

.NET Remoting 比 ASMX 更快吗?

2.

ES 比 Remoting 更慢吗?

3.

ASMX 用于实际操作是不是太慢了?

为了检查这些问题,我们进行了一些测试。主要的测试用于检查针对操作进行调用的性能,这些操作要么接受一个重大请求并完成大量工作,要么被要求完成大量工作并返回小结果集或大结果集。这些测试的目的是说明这些技术在典型商业环境中的性能,这些环境很可能就是您在构建系统时使用的。

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

企业服务(使用实时激活)

无身份验证

调用级别的身份验证和加强的角色访问检查

ASP.NET Web 服务

无身份验证

用户名和密码验证

集成验证

.NET Remoting

TCP/Binary,不安全

HTTP/Binary,不安全

HTTP/SOAP,不安全

IIS 中的 HTTP/SOAP,不安全

IIS 中的 HTTP/Binary,不安全

IIS 中的 HTTP/SOAP,HTTP 基本验证

IIS 中的 HTTP/Binary,HTTP 基本验证

IIS 中的 HTTP/SOAP,集成验证

IIS 中的 HTTP/Binary,集成验证

以下所有测试均基于反复调用服务器端方法的单一客户端应用程序。我们计算了集合中每个测试在每秒内的远程调用/方法调用的平均次数,整个集合重复计算了 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 测试,生成了类型化 DataSet,其中包含如图 2 定义的 DataTable。


图 2. 这个 DataSet 表示一个购买定单

这个 DataSet 与生成的 SqlDataAdapter 一起使用,SqlDataAdapter 中包含的 SqlCommand 与上述代码片段中的类似。

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


图 3. 将购买定单存储为 DataSet

需要注意的是,使用 DataSet 严重影响了性能(下降了大约 50%),但是协议之间的差异进一步缩小。另外值得注意的是,传递数据对象最慢的协议在传递 ADO.NET DataSet 时,超出了最佳性能。

这一结果清楚地表明在系统各层之间传递 ADO.NET DataSet 会带来极大的性能损失。

测试 2:检索 Northwind 产品数据

这项测试的目的是说明从一个服务返回相对可调整的数据量时每种技术的性能特点,而这个服务不需要执行大量工作。

为了模拟这个情况,我们构建了一些方法,用于从熟悉的 SQL Server“Northwind”示例数据库中检索和返回“product”记录列表。

我们认为在这项测试中,二进制序列化技术(ES/COM+ 和 Remoting Binary/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 填写一个 ArrayList,其中包含该类的 77 个 Product 实例。然后,将 ArrayList 返回给调用方。

结果如图 4 所示。


图 4. 将 Northwind 产品目录作为对象进行检索

这些结果表明企业服务(通过 DCOM)和基于 TCP 的二进制 Remoting 为不安全的调用提供同等级别的性能。同时,也表明 ASP.NET Web 服务提供的性能大约是企业服务性能的 62%,传输速度最慢的协议与最快的协议相比,每秒钟只响应后者调用次数的 17%。

在这项测试和所有后续测试中,有一点很明确,那就是为什么在生成 SOAP 服务时通常建议不要使用 .NET Remoting SoapFormatter:因为 ASP.NET Web 服务堆栈的速度比它快两倍。

在第二项测试中,我们实现了这样一个方法:它在一个 ADO.NET DataSet 对象(该对象完全基于基础表格布局)中返回请求的数据,并使用 SqlDataAdapter 来填充 DataSet 的 DataTable。表格定义如图 5 所示。


图 5. 访问产品目录的类型化 DataSet

图 6 说明结果:


图 6. 将 Northwind 产品目录作为 DataSet 进行检索

这项测试的结果清楚地表明,将 DataSet 从服务返回到调用方极大地降低了性能 — 较之于二进制传输几乎降低了 75%!还需要注意的是,四个速度最快的协议的性能完全相同,这意味着,所有这四项技术都受序列化、反序列化和 DataSet 传输的遏制。而且,最快的协议 (ASMX @ 39 cps) 和最慢的协议(Remoting HTTP/SOAP (IIS) 集成安全性 @ 25 cps)之间只有 14 cps 的性能差异 — 仅相差 30%。

这突出地说明,虽然使用 DataSet 在应用程序各层之间传递信息很方便,但是这样做会极大地影响性能。我们稍后将讨论这个问题。

需要指出的是,循环访问 ADO.NET DataSet、构造数据对象集合以及序列化该集合这一系列操作,比只将 ADO.NET DataSet 返回给调用方速度更快!如果这是一个实际系统,需要在服务器和调用方之间传递 DataSet,那么我们只需少量工作就能将系统的这部分性能提高大约四倍。

测试 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 传输显示出比二进制机制低得多的吞吐量。

下面,我们测试返回类型化 DataSet 的同一项操作,如图 8 所示。


图 8. 访问客户信息的类型化 DataSet

使用以下类似的服务器端实现来填充这个 DataSet:

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. 将客户数据作为 DataSet 进行检索

与前面的测试相似,显然,交换 ADO.NET DataSet 消极地影响了性能,而且所有技术在吞吐量方面的差异在返回序列化对象时变得更小,这表明 ADO.NET DataSet 遏制了吞吐量。

.NET Framework 2.0 性能

撰写本文时,Microsoft 正致力于发布 .NET Framework 2.0(先前代号为“Whidbey”),它包含了大量改进、增强和新功能,包括:

完全支持基于 x64 和 IA64 的机器

通过 System.Transactions 实现的全新事务支持

针对 System.Net 的许多增强,包括连接识别和支持代理

针对 Windows 窗体和 Web 窗体的重大改进

“ClickOnce”自动化应用程序部署基础结构

除了 .NET Framework 2.0 包含的新功能和增强功能之外,在显著提高性能和改进内存利用率方面也付出了大量努力,这涉及到包括 BCL、XML 序列化、网络吞吐量、ADO.NET、ASP.NET 等在内的许多领域。

有关这些性能改进和吞吐量改进的早期示例,请参见以下白皮书:

Comparing XML Performance

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

Comparing Web Service Performance

用于 .NET Framework 2.0 的 WS Test 1.1 Benchmark Results、.NET 1.1、Sun JWSDP 1.5 和 IBM WebSphere 6.0

随着 .NET Framework 2.0 继续向 RTM (release to manufacturing) 版本迈进,在未来的一段时间内,将发布更多有关性能分析和比较的白皮书。为了及时查阅这样的白皮书,请访问下列在线资源:

The .NET Framework Developer Center

Competitive Reports and Evidence: Comparing .NET to J2EE

小结

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

表 1. 结果摘要
每秒进行的调用次数(四舍五入) 企业服务 ASP.NET Web 服务 .NET Remoting TCP Binary

测试 1:存储定单
a) 序列化数据
b) DataSet

73
35

63
34

72
34

测试 2:检索产品
a) 序列化数据
b) DataSet

147
39

93
39

149
39

测试 3:检索客户
a) 序列化数据
b) DataSet

618
90

289
83

682
91

这些结果表明您期望在实际应用程序中获得的性能特点,而且,用于实际应用程序的 Web 服务(通常认为速度极慢)和 DCOM(是本测试中最快的分布式应用程序技术之一)之间的差异事实上是很小的。

有关接受测试技术的结论将在后面内容中详细讨论,但是在这里加以总结:

尽管 ASMX 不是目前发布的最快的技术,但是它的性能足以应付大多数商业环境。而且,ASMX 服务易于定制,并能提供诸如互操作和向后兼容等其他多种优点,因此在构造当今的服务时,ASMX 应该是您的首选。

如果绝对性能是最重要的因素,您应该使用企业服务组件来构建系统中以性能为重的部分。COM+ 的整体性能最佳,并且是一个用于分布式组件的安全、可靠的宿主环境。ES/COM+ 与 Indigo 服务集成得很好,使得将 ES 组件转换为 Indigo 服务相对简单。

尽管 .NET Remoting 在 TCP 上使用二进制序列化时性能良好,但是当 Remoting 组件寄宿在 IIS 中和/或用于发送和接收 SOAP 消息时,其性能将降低。而且,.NET Remoting 组件只与 .NET Remoting 端点进行交互,因此我们极力主张在任何可以使用 Remoting 的地方优先考虑 ASMX 或 ES。

从这些结果得出的一个主要结论是,如果服务器执行工作花费的时间在每个调用的整体持续时间内所占的百分比不高,那么传输时间将占用很大一部分比例的调用时间。如果您从一个“快速的”传输协议(例如 COM+ 之下的 DCOM)转换到一个“开销”更大的传输协议(例如 ASMX 之下的 SOAP/HTTP),则可能会体验到严重的性能影响。因此,这清楚地表明了我们的建议 — 在服务器端为每个方法调用执行尽可能多的工作,以及避免不必要的网络遍历。

随着调用服务执行的工作量的增加,这些技术优于其他技术的性能优势将变小。许多文章都阐述过这个结论,包括 .NET Enterprise Services Performance。

这些测试还表明,这些技术之间的性能差异比特定应用程序决策(例如,使用 DataSet 还是序列化结构)之间的差异更小。

在重点关注本文中的性能数字的同时,还要注意,在计划开发项目时,性能不是要考虑的唯一因素。诸如安全性、可扩展性、管理、开发人员工作效率、互操作和 Microsoft 分布式技术的未来发展方向等其他因素都应该予以考虑,Developing Distributed Services Today 一文为如何最好地选择利用每项技术的时间和地点及其未来发展提供了说明性指导。

建议

根据这些测试的结果以及未来的技术发展方向和最佳实践,并结合以下建议,您将能够构建不易受性能问题困扰的系统:

尽可能使用 ASMX

为了向为服务提供最广泛的使用范围,请考虑尽可能使用 ASMX 来发布服务。这将使任何通过 HTTP 与 SOAP 进行会话的系统都可以调用服务,无论该系统运行或实现哪种平台、设备或语言。您可以从上述结果中看到,ASMX 在许多情况下都很适用。即使在由单线程应用程序调用的单处理器机器上,ASMX 每秒钟也能够处理 63 个复杂操作 — 每秒钟只比同等的 ES 组件少 10 个调用!在生产环境中,您可以期望使用一台承载 ASMX 服务的机器来获得更好的性能,因此 ASMX 的性能不应该是您采用这项重要技术的障碍。

Web 服务也呈现出其他有用的特征和特性,包括(但不限于):

可扩展性 — 使用负载平衡技术很容易定制一个 Web 服务,例如 Windows 网络负载平衡,或由 Cisco、F5 等供应商提供的硬件设备。这一领域需要了解的知识还有很多。

可用性 - ASMX Web 服务可以通过结合多种技术配置成具有高度可用性的服务,例如,结合 Windows 2003 Server 中内置的负载平衡与 IIS6 基础框架的强大功能(例如,自动回收和重新启动失败的服务)。

互操作性 - Web 服务在标准化交互安全、可靠的事务型通讯协议的活动中处于核心地位,这些通讯协议能够使分散、异构的系统之间进行自由地通讯。

效率 - 对于大多数开发人员而言,通过合理使用一些属性并遵循一些建议,生成 ASMX Web 服务是一件非常容易的事。

使用 ASMX 生成 Web 服务

有很多原因可以解释为什么 ASMX 是一个用于生成和部署服务的绝佳平台(如 prescriptive guidance 中所述),并且从本文得出的结果来看,在与使用 SOAP 进行会话时,ASMX 通常要比 Remoting 优越。

还需要注意的是,虽然 .NET Remoting 能够支持 SOAP/HTTP(通过它的 SoapFormatter 和 HttpChannel),但我们还是反对使用 Remoting 公开 Web 服务。.NET Remoting SoapFormatter 使用 RPC 编码方法,它已经不是对 SOAP 进行编码的标准方法了(document-literal 是未来大多数供应商的技术和平台所使用的标准编码机制);因此,通过 Remoting 服务进行的互操作将十分有限。

根据可扩展性选择 Web 服务

上文没有说明(但是与这个讨论话题休戚相关)的一点是,Web 服务会话主要通过 HTTP 进行,它是一种易于达到负载平衡的无状态通讯技术。这意味着,使用 TCP 或 HTTP(例如寄宿在 IIS 内部)也可以使无状态的 Remoting 组件达到负载平衡,尽管这在要求会话状态时会变得错综复杂。

为了使 ES/COM+ 组件达到负载平衡,必须使用组件负载平衡 (CLB),这是 Application Center 2000. Alas 提供的一个功能,AppCenter 2000 将在 2006 年 7 月至 2011 年之间提供这项扩展支持。因此,为了避免在 2011 年之前重新构建系统以使之不再依赖于 CLB,强烈建议您在新的开发项目中回避这项技术,并尽快迁移至其他策略。一个好的移植策略是使当前的 COM+ 组件面向 ASMX Web 服务,由 ASMX Web 服务将调用直接传递到 COM+ 组件,并使您的 Web 服务达到负载平衡,如上文所述。

仅在服务内部使用企业服务

.NET 企业服务是 .NET Framework 的一层,它提供对多种 COM+ 服务的访问和支持。如果您需要使用多种 COM+ 服务中的任意一种,例如,跨越几个对象/服务的分布式事务、细粒度的安全性、JITA 和对象池,那么您应该选择企业服务。企业服务和 COM+ 是经过严格测试、高度优化的技术,能够提供非常快速、低延迟时间的进程间和计算机间的调用。

然而,我们强烈建议您不要广泛公开您的企业服务组件,并且仅在服务边界内使用这项技术。如果您希望提供对服务的访问,则我们极力主张通过使用 ASMX 的 Web 服务公开您的服务功能。如果您有确定的 ES/COM+ 代码基,则我们建议尽可能使您的 COM+ 服务面向 ASMX Web 服务。

考虑在您构建的架构之上使用一个宿主架构

正如在前面看到的,ASP.NET 发布了功能强大的 Web 服务,这些服务交互性强,并且得益于 IIS(特别是 Windows 2003 Server 中的 IIS6)提供的强大宿主功能。

COM+ 在 Microsoft 平台上提供最快的组件性能,它还可以为企业服务和 COM+ 组件提供经过试验的、可信、安全而可靠的宿主环境。它还提供分布式事务支持、安全措施(身份验证和加密)、可靠性、可用性和管理服务,是用于在服务内承载复杂的、基于组件的系统的明智选择。

尽管 .NET Remoting 在 TCP 上使用二进制序列化也能够提供良好的性能,但是它不提供任何宿主环境、安全性、可靠性、可用性或“范围外”的管理功能。为了承载使用 TCP 的 Remoting 组件,必须编写自己的(安全的、可扩展的、可靠的)宿主应用程序,这可不是微不足道的任务。如果希望在 HTTP 上使用 Remoting,可以选择使用 IIS 作为宿主,但是您会发现,在 IIS 中承载 Remoting 组件并使用 SOAP/HTTP 会话将极大地影响 Remoting 的性能 — ASMX 通常性能更好!

因此,我们极力主张尽可能使用寄宿在 IIS 内的 ASMX 或寄宿在 COM+ 内的企业服务组件来发布服务(而不是使用 Remoting)。

避免在服务之间传递 DataSet

在我们的测试中,选择服务与调用方交换数据的方法(以 DataSet 或序列化结构)带来的性能影响比选择任何一种特定的分布式系统技术带来的影响大得多。

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

ADO.NET DataSet 提供许多优秀方法,用于检索、操作、排序和塑造数据,将数据存储在本机以便脱机使用,以及将更改同步到中央数据库。如果您的应用程序需要这些功能,那么,这当然是个正确的选择。在 .NET Framework 1.x 中,DataSet 值通常序列化为 XML 格式(即使您声明想要序列化为二进制格式,或者将它们与 ES 或 Remoting 一起使用),并且还包含描述数据的 XSD。这当然会影响性能。

此外,请记住 DataSet 是一项特定于 .NET 的技术,它将极大地限制您的互操作性,因为其他平台不一定能够序列化和解析包含序列化 DataSet 的数据。

通过交换序列化数据结构,您能够获得重大的性能改进。正如上述测试表明的那样,内置的运行时格式化和 XML 序列化框架使之变得很容易。

使用经过验证的连接共享

所有使用 Internet Information Server 作为宿主并与集成安全性相结合的测试已经配置为通过选项 useAuthenticatedConnectionSharinguseUnsafeAuthenticatedConnectionSharing 运行,这两个选项可用于 ASP.NET 客户端和 .NET Remoting。这项配置允许客户端和服务器重用一个现有的 NTLM 验证连接。

当我们设置这个选项时,您能够看到如图 10 所示的性能结果,我们采用的测试最初是在图 1(将购买定单存储为对象)中使用的。请注意,仅当使用 Internet Information Server (IIS) 做为服务器并在虚拟目录的配置中要求“Windows In Security”时,这项设置才起作用。

MSDN Magazine 2004 年 9 月号的专栏 Web Q&A: Caching Transforms, Connection Sharing, and More 讨论了这些折衷选择。


图 10. UnsafeAuthenticatedConnectionSharing 的影响

您可以在 ASP.NET Web 服务内部结合使用Windows 集成安全性 和该项设置:

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

按以下方式与 .NET Remoting 结合使用:

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 Remoting 网络协议的性能次之。ASP.NET Web 服务稍逊一筹,但是仍然要比寄宿在 IIS 内的 .NET Remoting 快。

第二步,我们在计算机之间运行了这项测试。我们通过设置生成的 ASP.NET 的“Url”属性来将其重定向,为 COM+/企业服务组件导出应用程序代理,并针对这项测试的 .NET Remoting 部分使用 Activator.GetObject()。得出的结果与之前的测试结果如出一辙,只不过由于涉及到网络遍历,因此每秒钟的调用次数有些偏低:


图 12. 在计算机之间调用无参数的空方法(合成基线)

请注意,计算机之间的网络遍历从本质上抑制了最快的传输协议(ES 和 Remoting Binary/TCP),但是对较慢的传输协议几乎没有影响。这清楚地表明,与主要为网络通讯服务的技术(例如,IIS 和 ASMX)相比,COM+ 的进程间通讯机制特别高效。

需要记住的一点是,正如前面提到的那样,这项测试是虚构的,仅用于说明传输协议的功能。

附录 B — 详细测试结果

将定单存储为对象
测试 平均调用次数/秒 标准偏差

企业服务

73

0.39

企业服务(验证)

73

0.34

Remoting TCP/Binary

72

2.71

Remoting HTTP/Binary

68

0.86

Remoting HTTP/Binary (IIS)

66

0.31

ASP.NET Web 服务

63

2.71

Remoting HTTP/Binary (IIS) 密码

63

0.39

Remoting TCP/SOAP

56

1.97

Remoting HTTP/SOAP

53

0.57

Remoting HTTP/Binary (IIS) 集成

51

0.28

Remoting HTTP/SOAP (IIS)

50

0.16

ASP.NET Web 服务 — 集成

50

0.30

Remoting HTTP/SOAP (IIS) 密码

49

0.29

Remoting HTTP/SOAP (IIS) 集成

40

0.84

将定单存储为 DataSet
测试 平均调用次数/秒 标准偏差

企业服务

35

0.54

企业服务(验证)

35

0.51

ASP.NET Web 服务

34

0.43

Remoting TCP/Binary

34

0.85

Remoting HTTP/Binary

32

0.77

Remoting HTTP/Binary (IIS)

32

1.10

Remoting TCP/SOAP

32

0.77

Remoting HTTP/Binary (IIS) 密码

31

1.47

Remoting HTTP/SOAP

30

0.68

Remoting HTTP/SOAP (IIS)

30

0.48

Remoting HTTP/SOAP (IIS) 密码

30

0.46

ASP.NET Web 服务 — 集成

29

0.37

Remoting HTTP/Binary (IIS) 集成

28

0.37

Remoting HTTP/SOAP (IIS) 集成

26

0.31

将产品作为对象加载
测试 平均调用次数/秒 标准偏差

Remoting TCP/Binary

149

3.05

企业服务

147

2.29

企业服务(验证)

146

2.49

Remoting HTTP/Binary

118

2.13

Remoting HTTP/Binary (IIS)

114

0.63

Remoting HTTP/Binary (IIS) 密码

106

1.19

ASP.NET Web 服务

93

1.04

Remoting HTTP/Binary (IIS) 集成

76

0.81

ASP.NET Web 服务 — 集成

67

0.35

Remoting TCP/SOAP

33

0.34

Remoting HTTP/SOAP

30

0.32

Remoting HTTP/SOAP (IIS)

30

0.25

Remoting HTTP/SOAP (IIS) 密码

29

0.16

Remoting HTTP/SOAP (IIS) 集成

26

0.14

将产品作为 DataSet 加载
测试 平均调用次数/秒 标准偏差

ASP.NET Web 服务

39

0.12

企业服务

39

0.26

企业服务(验证)

39

0.21

Remoting TCP/Binary

39

1.16

Remoting HTTP/Binary (IIS)

36

0.24

Remoting HTTP/Binary

35

1.10

Remoting HTTP/Binary (IIS) 密码

35

0.17

ASP.NET Web 服务 — 集成

33

0.09

Remoting HTTP/Binary (IIS) 集成

31

0.21

Remoting TCP/SOAP

30

1.27

Remoting HTTP/SOAP (IIS)

29

0.12

Remoting HTTP/SOAP

28

1.07

Remoting HTTP/SOAP (IIS) 密码

28

0.06

Remoting HTTP/SOAP (IIS) 集成

0.08

[Text]

将客户作为对象加载
测试 平均调用次数/秒 标准偏差

Remoting TCP/Binary

682

12.32

企业服务

618

13.78

企业服务(验证)

616

7.76

Remoting HTTP/Binary

406

7.84

Remoting TCP/SOAP

359

11.62

Remoting HTTP/Binary (IIS)

324

4.26

ASP.NET Web 服务

289

2.68

Remoting HTTP/SOAP

267

6.18

Remoting HTTP/Binary (IIS) 密码

261

2.39

Remoting HTTP/SOAP (IIS)

214

2.84

Remoting HTTP/SOAP (IIS) 密码

186

0.81

Remoting HTTP/Binary (IIS) 集成

134

0.95

ASP.NET Web 服务 — 集成

130

0.67

Remoting HTTP/SOAP (IIS) 集成

112

0.55

将客户作为 DataSet 加载
测试 平均调用次数/秒 标准偏差

Remoting TCP/Binary

91

2.69

企业服务

90

0.30

企业服务(验证)

90

0.26

ASP.NET Web 服务

83

0.24

Remoting HTTP/Binary

80

0.67

Remoting TCP/SOAP

78

1.04

Remoting HTTP/Binary (IIS)

78

0.22

Remoting HTTP/Binary (IIS) 密码

75

0.26

Remoting HTTP/SOAP

71

0.92

Remoting HTTP/SOAP (IIS)

67

0.34

Remoting HTTP/SOAP (IIS) 密码

64

0.20

ASP.NET Web 服务 — 集成

62

0.14

Remoting HTTP/Binary (IIS) 集成

59

0.15

Remoting HTTP/SOAP (IIS) 集成

52

0.13

空消息,进程间
测试 平均调用次数/秒 标准偏差

企业服务

14687

52.66

企业服务(验证)

12293

31.71

Remoting TCP/Binary

3538

38.87

Remoting HTTP/Binary

1069

8.05

Remoting TCP/SOAP

1075

11.87

Remoting HTTP/SOAP

612

7.88

Remoting HTTP/Binary (IIS)

589

1.83

ASP.NET Web 服务

517

1.44

Remoting HTTP/Binary (IIS) 集成

451

1.19

Remoting HTTP/Binary (IIS) 密码

421

0.90

Remoting HTTP/SOAP (IIS)

406

1.76

ASP.NET Web 服务 — 集成

403

0.76

Remoting HTTP/SOAP (IIS) 集成

336

0.57

Remoting HTTP/SOAP (IIS) 密码

321

0.37

空消息,计算机之间
测试 平均调用次数/秒 标准偏差

企业服务(验证)

3068

25.35

企业服务

3048

38.24

Remoting TCP/Binary

2609

77.28

Remoting TCP/SOAP

760

31.65

Remoting HTTP/Binary

704

8.00

Remoting HTTP/Binary (IIS)

479

2.60

Remoting HTTP/SOAP

413

10.63

ASP.NET Web 服务

399

2.00

Remoting HTTP/Binary (IIS) 密码

351

1.98

Remoting HTTP/SOAP (IIS)

309

1.38

Remoting HTTP/SOAP (IIS) 密码

252

1.25

Remoting HTTP/Binary (IIS) 集成

156

0.52

ASP.NET Web 服务 — 集成

150

0.57

Remoting HTTP/SOAP (IIS) 集成

133

0.28

附录 C:测试应用程序注意事项

运行测试应用程序

您可以在本文开始指定的位置下载测试应用程序。为了在您自己的硬件上执行这些测试,至少需要两台计算机:一个客户端和一个服务器。此外,您需要有安装 SQL Server 的权限,其中包含 Northwind 示例数据库。您可以在 README.TXT 文件中找到部署、配置和运行该测试套件的分步指导,该文件位于含有源代码的 ZIP 归档中。

有趣的仿真陈述 — 测试应用程序是技术无关的!

在测试应用程序中,您会发现可以创建与传输协议无关的应用程序。在示例应用程序中,您可以看到一个 Test.CreateProxy() 方法,它创建一个对远程服务的引用,并返回一个实现特定接口的客户端包装(一个 Web 服务代理或用于 ES 和 .NET Remoting 的相应 TransparentProxy)。在调用这个方法时,您能够指定使用哪个传输协议、安全设置以及格式化程序等等。应用程序的其他部分不关心底层协议。这允许您创建一个与协议无关的应用程序,以便根据应用程序环境来选择合适的协议。

附录 D:软件和硬件安装

下面的测试是在三台计算机上执行的:一个客户端、一个应用程序服务器和一个数据库服务器。所有测试在执行时都分配了足够的可用物理内存,以防止发生不必要的分页。

应用程序环境

客户端:用 C# 编写的单线程控制台程序,通过发布模式的 Visual Studio.NET 2003 生成

在 Windows Server 2003 的 IIS 6.0 中运行的 ASP.NET Web 服务

具有不同配置的企业服务组件(库激活和服务器激活,前者没有验证,后者没有调用级别的验证),在 Windows Server 2003 的 COM+ 1.5 中运行

用 C# 编写的独立的 .NET Remoting 服务器,它作为一个 C# 控制台应用程序,在发布模式下的 Visual Studio .NET 2003 生成

基础结构环境

客户端:

CPU:2.4 GHz Intel P4

内存:768 MB

操作系统:Windows Server 2003 Standard Edition(具有 2004 年 6 月发布的全部修补程序)

.NET Framework 1.1 SP1

应用程序服务器:

CPU:2.8 GHz Intel P4

内存:1024 MB

操作系统:Windows Server 2003 Standard Edition

.NET Framework 1.1 SP1

数据库服务器:

CPU:2.8 GHz Intel P4 Prescott、w/ HT、800 MHz FSB

内存:1024 MB

操作系统:Windows Server 2003 Standard Edition

数据库:SQL Server 2000 Enterprise Edition(具有 SP3A)

.NET Framework 1.1 SP1

网络:

100 Mbps,交换以太网

 

版权所有:UML软件工程组织