如何通过分布式缓存来解决可扩展性瓶颈?

日期: 2010-07-07 来源:TechTarget中国 英文

  在适应高流量用途的Web应用程序激增之后,下一波大浪潮是面向服务的体系结构(SOA)。SOA注定将成为开发具有极大可伸缩性的应用程序的标准方法,而Windows Azure这类云计算平台则代表在推动SOA实现此目标过程中的巨大飞跃。

  SOA使用户可以在Internet上将应用程序分发到多个位置、组织中的多个部门以及多个企业。此外,SOA还允许在组织内重用现有代码,更为重要的是,允许在不同业务单位之间进行协作。

  SOA应用程序通常部署在负载平衡环境的服务器场中。这是为了使应用程序能够处理其承受的负载量。因而问题变为:若要同时提高SOA应用程序的性能和可伸缩性,应考虑哪些注意事项?

  虽然SOA在设计上旨在提供可伸缩性,但是必须先解决许多问题,才能真正实现可伸缩性。其中一些问题涉及到如何编写SOA应用程序代码,但是最重要的瓶颈通常与如何存储和访问数据有关。在本文中,我将探讨这些问题并提供一些解决方案。

  确定可伸缩性瓶颈

  就应用程序体系结构而言,真正的SOA应用程序应该可轻松地进行伸缩。SOA应用程序有两种组件:服务组件和客户端应用程序。客户端应用程序可以是 Web 应用程序、另一个服务或依赖于SOA服务组件来完成其工作的任何其他应用程序。

  SOA的一个关键理念是将应用程序分为多个小块,以便这些组件可以在多台服务器上作为单独的服务运行。

  在理想情况下,这些服务应尽可能无状态。“无状态”意味着它们不会在多个调用之间保留任何数据,从而使您可以在多台计算机上运行这些服务。数据最后一次所处的位置无关紧要,因此在多个服务调用之间不会在任何特定服务器上保留数据。

  因此,SOA应用程序的体系结构在本质上是可伸缩的。它可以轻松地扩展到多台服务器上并跨数据中心。但是,与所有其他应用程序一样,SOA 应用程序也必须处理数据,这便可能是个问题。这种数据访问成为了可伸缩性瓶颈。瓶颈通常涉及到应用程序数据,这些数据存储在某个数据库(通常为关系数据库)中。如果SOA应用程序使用的是会话数据,则该数据的存储也是另一个潜在的可伸缩性瓶颈。

  一个SOA应用程序依赖于其他SOA应用程序可能是造成性能和可伸缩性低下的另一个方面。假设您的应用程序调用一个服务来执行其工作,但是该服务会调用其他服务。这些服务可能位于同一个Intranet上,也可能分布在 WAN 上的其他位置。这类数据传输可能成本很高。如果您反复进行这些调用,则无法有效地伸缩应用程序,这些调用会导致可伸缩性瓶颈,如图 1 所示。

图 1 具有潜在可伸缩性瓶颈的SOA体系结构

  图 1 具有潜在可伸缩性瓶颈的SOA体系结构

  用于提高性能的代码

  可使用一些编程技术来帮助提高 SOA 应用程序性能。

  将应用程序设计为使用“大块”Web方法调用便是一种方法。不要在SOA客户端应用程序与SOA服务层之间进行频繁调用。通常这两者之间距离很远,因为它们不在同一台计算机上运行,甚至不在同一个数据中心中运行。从客户端应用程序对服务层进行的调用越少,性能便越高。大块调用在一个调用中完成的工作要多于用于完成相同工作的多个调用。

  另一项很有用的技术是采用Microsoft .NET Framework支持的异步Web方法调用。这使SOA客户端应用程序在服务层的Web方法被调用且正在执行期间,可以继续执行其他操作。

  序列化成本是应考虑的另一个方面,因此请不要序列化任何不必要的数据。只应来回发送必需的数据,从而使您可以在要执行的序列化类型方面具有很大的选择余地。

  选择正确的通信协议

  对于在Windows Communication Foundation (WCF)中开发的SOA应用程序,SOA客户端可通过三种不同的协议与SOA服务对话。这三种协议是 HTTP、TCP和命名管道。

  如果客户端和服务都是在WCF中开发的,且在同一台计算机上运行,则命名管道可提供最佳性能。命名管道在客户端与服务器进程之间使用共享内存。

  如果SOA客户端和服务器都是在 WCF 中开发的,但是在同一个Intranet中的不同计算机上运行,则适合使用TCP。TCP比HTTP更快,但是TCP连接在多个调用之间会保持为打开状态,因此您无法自动将每个WCF调用路由到不同服务器。通过使用连接池的NetTcpBinding选项,您可以频繁地使TCP连接到期以重新启动这些连接,因此它们会路由到不同服务器,从而提供某种形式的负载平衡。

  请注意,TCP无法在WAN上可靠地工作,因为套接字连接容易频繁断开。如果SOA客户端和服务不是基于WCF,或者位于不同位置,则 HTTP 是最佳选择。虽然HTTP不如TCP快,但是可进行负载平衡,从而可提供优秀的可伸缩性。

  使用缓存提高客户端性能

  考虑周全的缓存使用可以在实际中提高SOA客户端性能。当SOA客户端对服务层进行 Web 方法调用时,您可以将结果缓存在客户端应用程序一端。因而,此SOA客户端下一次需要进行相同Web方法调用时,可改为从缓存获取该数据。

  通过在客户端缓存数据,SOA客户端应用程序可减少对服务层进行的调用数。此步骤可大大提高性能,因为不必进行代价高昂的SOA服务调用。还可减轻服务层上的整体压力,并提高可伸缩性。以下代码显示一个使用缓存的WCF客户端。

  以下代码为WCF客户端缓存

以下是引用片段:
using System; 
using Client.EmployeeServiceReference; 
 
using Alachisoft.NCache.Web.Caching; 
 
namespace Client { 
 class Program { 
  static string _sCacheName = ”mySOAClientCache”; 
  static Cache _sCache = NCache.InitializeCache(_sCacheName); 
 
  static void Main(string[] args) { 
   EmployeeServiceClient client = 
    new EmployeeServiceClient(“WSHttpBinding_IEmployeeService”); 
 
   string employeeId = ”1000″; 
   string key = ”Employee:EmployeeId:” + employeeId; 
       
   // first check the cache for this employee 
   Employee emp = _sCache.Get(key); 
 
   // if cache doesn’t have it then make WCF call 
   if (emp == null) { 
    emp = client.Load(“1000”); 
 
    // Now add it to the cache for next time 
    _sCache.Insert(key, emp); 
   } 
  } 
 } 
}

  在很多情况下,客户端会从服务层物理删除,并且跨WAN运行。在这种情况下,您无法了解缓存的数据是否已更新。因此,您必须确定要缓存的数据元素,这仅限于您认为至少在几分钟内到可能几小时内(具体取决于您的应用程序)不会更改的那些数据元素。随后您可以指定这些数据元素在缓存中的到期时间,以便缓存到时自动将其删除。这有助于确保缓存的数据始终保持最新且正确。

  用于实现服务可伸缩性的分布式缓存

  可在SOA服务层中通过缓存实际提高可伸缩性。尽管已提到了许多编程技术,但依靠这些技术并不总是能消除可伸缩性瓶颈,因为主要的可伸缩性瓶颈与数据存储和访问有关。服务通常位于负载平衡的服务器场中,以便服务本身可以进行良好的伸缩,但数据存储无法通过相同方式进行伸缩。数据存储因而成为SOA瓶颈。

  您可以通过向服务器场中添加更多服务器,并通过这些额外的应用程序服务器增加计算能力,从而扩展服务层。但是所有这些SOA事务仍然要处理一些数据。这些数据必须存储在某处,这种数据存储可能很容易成为瓶颈。

  可在多个级别上改进可伸缩性的这种数据存储障碍。SOA服务处理两种类型的数据。一种是会话状态数据,另一种是驻留在数据库中的应用程序数据(请参见图 2)。这两种数据都会导致可伸缩性瓶颈。

图 2 分布式缓存如何减轻对数据库的压力

  图 2 分布式缓存如何减轻对数据库的压力

  在分布式缓存中存储会话状态

  默认会话状态存储的一个局限性是不支持Web场,因为它是在WCF服务进程中进行的内存中存储。另一种好得多的选择是在WCF服务中使用ASP.NET兼容性模式和ASP.NET会话状态。这样,您便可以指定OutProc存储(包括StateServer、SqlServer或分布式缓存)作为会话状态存储。

  启用ASP.NET兼容性模式的过程包括两个步骤。首先,必须在类定义中指定ASP.NET兼容性,如下所示。随后必须在app.config文件中指定这一点。请注意,以下代码还演示了如何在同一个web.config文件中将分布式缓存指定为SessionState存储。

  以下代码为在代码中为WCF服务指定ASP.NET兼容性

以下是引用片段:
using System; 
using System.ServiceModel; 
using System.ServiceModel.Activation; 
 
namespace MyWcfServiceLibrary { 
 [ServiceContract] 
 public interface IHelloWorldService { 
  [OperationContract] 
  string HelloWorld(string greeting); 
 } 
 
 [ServiceBehavior (InstanceContextMode = 
  InstanceContextMode.PerCall)] 
 [AspNetCompatibilityRequirements (RequirementsMode = 
  AspNetCompatibilityRequirementsMode.Allowed)] 
 
 public class HelloWorldService : IHelloWorldService { 
  public string HelloWorld(string greeting) { 
   return string.Format(“HelloWorld: {0}”, greeting); 
  } 
 } 
}

  以下代码为在配置中为WCF服务指定ASP.NET兼容性

以下是引用片段:
<?xml version=”1.0″ encoding=”utf-8″?> 
<configuration> 
 <system.web> 
  <sessionState cookieless=”UseCookies” 
   mode=”Custom” 
   customProvider=”DistCacheSessionProvider” 
   timeout=”20″> 
   <providers> 
    <add name=”DistCacheSessionProvider” 
     type=”Vendor.DistCache.Web.SessionState.SessionStoreProvider”/> 
   </providers> 
  </sessionState> 
  <identity impersonate=”true”/> 
 </system.web> 
 
 <system.serviceModel> 
  <!– … –> 
  <serviceHostingEnvironment 
   aspNetCompatibilityEnabled=”true”/> 
 </system.serviceModel> 
</configuration>

  StateServer和SqlServer会话存储选项无法进行良好伸缩,而且对于StateServer,这还是单点故障。分布式缓存是好得多的选择,因为它可以很好地进行伸缩,并且可将会话复制到多台服务器以保证可靠性。

  缓存应用程序数据

  应用程序数据是在WCF服务中使用最频繁的数据,其存储和访问是主要的可伸缩性瓶颈。为了解决此可伸缩性瓶颈问题,您可以在SOA服务层实现中使用分布式缓存。分布式缓存用于仅缓存数据库中的部分数据(基于 WCF 服务在几小时内需要哪些数据)。

  此外,分布式缓存可显著提高SOA应用程序的可伸缩性,因为借助于所采用的体系结构,此缓存可以进行扩展。它使内容在多台服务器上分布,同时仍为SOA应用程序提供一个逻辑视图,以使您只将其视为一个缓存。但是缓存实际上位于多台服务器上,因此允许缓存真正地进行伸缩。如果您在服务层与数据库之间使用分布式缓存,则会显著提高服务层的性能和可伸缩性。

  要实现的基本逻辑是在连接到数据库之前,检查缓存中是否已包含所需数据。如果包含,则从缓存中取出这些数据。否则,连接到数据库以获取数据,并将其放在缓存中以供下次使用。以下代码显示了一个示例。

以下是引用片段:
using System.ServiceModel; 
using Vendor.DistCache.Web.Caching; 
 
namespace MyWcfServiceLibrary { 
 [ServiceBehavior] 
 public class EmployeeService : IEmployeeService { 
  static string _sCacheName = ”myServiceCache”; 
  static Cache _sCache = 
   DistCache.InitializeCache(_sCacheName); 
 
  public Employee Load(string employeeId) { 
   // Create a key to lookup in the cache. 
   // The key for will be like ”Employees:PK:1000″. 
   string key = ”Employee:EmployeeId:” + employeeId; 
 
   Employee employee = (Employee)_sCache[key]; 
   if (employee == null) { 
    // item not found in the cache. 
    // Therefore, load from database. 
    LoadEmployeeFromDb(employee); 
 
    // Now, add to cache for future reference. 
    _sCache.Insert(key, employee, null, 
     Cache.NoAbsoluteExpiration, 
     Cache.NoSlidingExpiration, 
     CacheItemPriority.Default); 
   } 
 
   // Return a copy of the object since 
   // ASP.NET Cache is InProc. 
   return employee; 
  } 
 } 
}

  通过缓存应用程序数据,WCF服务省去了大量成本高昂的数据库往返,改为在邻近的内存内缓存中查找频繁使用的事务性数据。

  使缓存的数据到期

  通过到期设置,您可以指定在缓存自动删除数据之前,数据应在缓存中保留多长时间。您可以指定两种类型的到期:绝对时间到期以及滑动时间或空闲时间到期。

  如果缓存中的数据也存在于数据库中,那么您知道,其他可能无法访问缓存的用户或应用程序可以在数据库中更改这些数据。发生这种情况时,缓存中的数据就会过时,这是您不希望发生的。如果您可以推断出这些数据在缓存中保留多长时间是安全的,则可以指定绝对时间到期。您可能指定诸如“从此刻起10分钟后使此项到期”或“在今天午夜使此项到期”这样的设置。到指定时间时,缓存会使此项到期:

以下是引用片段:
using Vendor.DistCache.Web.Caching; 
… 
// Add an item to ASP.NET Cache with absolute expiration 
_sCache.Insert(key, employee, null, 
 DateTime.Now.AddMinutes(2), 
 Cache.NoSlidingExpiration, 
 CacheItemPriority.Default, null);

  如果没有人会在给定时间段内使用某个项,则您也可以使用空闲时间或滑动时间到期来使该项到期。您可以指定诸如 “如果10分钟内无人读取或更新此项,则使其到期”这类的设置。如果当您的应用程序临时需要数据时,以及当应用程序使用完数据时,您希望缓存自动使数据到期,则这会十分有用。ASP.NET 兼容性模式会话状态就是空闲时间到期的好例子。

  请注意,使用绝对时间到期,可帮助您避免缓存中的数据副本比数据库中的主副本更旧或过时这种情况。另一方面,空闲时间到期用于完全不同的用途。这种到期实际上意味着,一旦应用程序不再需要数据,只需清理缓存即可。您可让缓存负责此清理,而不是让应用程序对此进行跟踪。

  管理缓存中的数据关系

  大部分数据来自关系数据库,而且即使不是来自关系数据库,数据在本质上也是相关的。例如,您尝试缓存一个客户对象和一个订单对象,这两者相关。一个客户可以具有多个订单。

  当您具有这些关系时,您需要能够在缓存中对其进行处理。这表示缓存应了解客户与订单之间的关系。如果您在缓存中更新或删除客户,则可能希望缓存自动从缓存中删除相应的订单对象。这在很多情况下有助于保持数据完整性。

  如果缓存无法跟踪这些关系,则您必须自己进行跟踪,从而使应用程序更加繁琐且复杂。如果您只需在添加数据时向缓存告知此关系,则事情便容易多了。这样缓存便了解,如果更新或删除该客户,则同时必须删除相应订单。

  ASP.NET具有一个名为CacheDependency的有用功能,使您可以跟踪不同缓存项之间的关系。一些商用缓存也具有此功能。下面的示例演示如何通过ASP.NET来跟踪缓存项之间的关系:

以下是引用片段:
using Vendor.DistCache.Web.Caching; 
… 
public void CreateKeyDependency() { 
 Cache[“key1″] = ”Value 1”; 
 
 // Make key2 dependent on key1. 
 String[] dependencyKey = new String[1]; 
 dependencyKey[0] = ”key1″; 
 CacheDependency dep1 = 
  new CacheDependency(null, dependencyKey); 
 
 _sCache.Insert(“key2″, ”Value 2”, dep2); 
}

  这是一个多层依赖关系,表示A可依赖于B,B可依赖于C。因此,如果您的应用程序更新C,则必须同时从缓存中删除A和B。

  将缓存与数据库同步

  因为数据库实际上在多个应用程序之间共享,且并非所有这些应用程序都可以访问缓存,所以便需要数据库同步。如果您的WCF服务应用程序是唯一一个更新数据库的应用程序,并且可以方便地更新缓存,则您可能无需数据库同步功能。

  但是在现实环境中,情况并不总是这样。第三方应用程序会更新数据库中的数据,这样您的缓存便会与数据库不一致。将缓存与数据库同步可确保缓存始终了解这些数据库更改,并可相应地更新自己。

  与数据库同步通常意味着使缓存中的相关缓存项失效,因此在下次应用程序需要该项时,必须从数据库中进行获取,因为缓存中已没有该项。

  ASP.NET具有一个SqlCacheDependency功能,您可通过该功能将缓存与SQL Server 2005、SQL Server 2008或Oracle 10g R2以及更高版本(基本上是支持CLR的所有数据库)同步。一些商用缓存也提供了此功能。以下代码显示了使用SQL依赖关系与关系数据库同步的示例。

以下是引用片段:
using Vendor.DistCache.Web.Caching; 
using System.Data.SqlClient; 
… 
 
public void CreateSqlDependency( 
 Customers cust, SqlConnection conn) { 
 
 // Make cust dependent on a corresponding row in the 
 // Customers table in Northwind database 
 
 string sql = ”SELECT CustomerID FROM Customers WHERE ”; 
 sql += ”CustomerID = @ID”; 
 SqlCommand cmd = new SqlCommand(sql, conn); 
 cmd.Parameters.Add(“@ID”, System.Data.SqlDbType.VarChar); 
 cmd.Parameters[“@ID”].Value = cust.CustomerID; 
 
 SqlCacheDependency dep = new SqlCacheDependency(cmd); 
 string key = ”Customers:CustomerID:” + cust.CustomerID; 
_ sCache.Insert(key, cust, dep); 
}

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐