zoukankan      html  css  js  c++  java
  • XPO:Session管理与缓存机制篇

    缓存的意义已经无需多言了。这里整理了一篇DevExpress的关于XPO的Session管理和缓存的文章:Session Management and Caching

     

    About Sessions

    从6.1版开始,XPO新增了一个单独的Data Layer层,居于Session和IData Store之间,它的作用是接管之前版本中由Session管理的所有持久类的元数据。这样创建Session的资源开销被大大的减少,常常需要频繁创建新的Session实例的项目成为可能。

    The Session as an Identity Map

    Session包含了一个object  cache,用以实现Identity Map。Identity Map在Martin Fowler的 "Patterns of Enterprise Application Architecture" 一书中有很好的解释(Summary)。

    简单说来,它的任务是确保当相同的对象分别位于两个独立的查询的结果集里时,返回的应该是完全相同的同一个对象--不是任何的副本。即如下代码是应该可以通过的。

    Code
    Person person1 = session.FindObject<Person>(new BinaryOperator("Name""Billy Bott"));
    Person person2 
    = session.FindObject<Person>(new BinaryOperator("Name""Billy Bott"));
    Assert.AreSame(person1, person2); 

    注意:Session的object cache不是一个可选设置,不能被关闭。

    Object cache的内部原理非常简单,每次有至数据库的查询时,XPO会检查相关对象的版本(使用Optimistic Locking字段)。若数据库中有更新版本的记录,则相关对象的数据会被更新至缓存并返回。否则即直接返回缓存中的数据而跳过重新创建相关对象等操作。

    How current is the object cache?

    “如何确保缓存中的数据足够新?”问题很简单,但是“足够新”在不同的项目中的含义是不同的。XPO提供如下的办法来处理这个问题:

    • 创建新的Session来创建新的object cache。Session的生命周期越短,则其中的object cache中的内容也越新。并且这是通常推荐使用的唯一不会破坏缓存一致性(object cache consistency)的方法。
    • 使用Optimistic Locking。XPO会自动处理。如下述代码所演示的:
    Code
    // Create a test object
    using (Session session = new Session( )) {
      
    new Person(session, "Billy Bott").Save( );
    }

    Session session1 
    = new Session( );
    Session session2 
    = new Session( );

    // Create a collection in session1 and check the content
    XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
    Assert.AreEqual(
    1, peopleSession1.Count);
    Assert.AreEqual(
    "Billy Bott", peopleSession1[0].Name);

    // Fetch the test object in session2 and make a change
    Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name""Billy Bott"));
    billySession2.Name 
    = "Billy's new name";
    billySession2.Save( );

    // Create a new collection in session1 - it will have the change from the other session
    XPCollection<Person> newPeopleSession1 = new XPCollection<Person>(session1);
    Assert.AreEqual(
    1, newPeopleSession1.Count);
    Assert.AreEqual(
    "Billy's new name", newPeopleSession1[0].Name);

    // This last test shows the workings of the Identity Map - the object in the "old"
    // collection has the same change, because it's actually the same object.
    Assert.AreEqual("Billy's new name", peopleSession1[0].Name);
    Assert.AreSame(peopleSession1[
    0], newPeopleSession1[0]);
    •  还有一些其他的方法可供我们显式的从数据库里reload内容(或者至少看起来是这样--原文所述)。 Session.Reload(object)用以reload一个单一对象;XPBaseObject.Reload()通过调用Session的方法来reload它自己(等效于前一个方法?);该方法的另一个重载Session.Reload(object, forceAggregatesReload)在reload自己的同时可额外的reload所有相关联的属性;最后XPBaseCollection.Reload可reload一个Collection中的所有对象。
    • 为避免破坏缓存一致性, Session.DropCache()方法可以用来一次性的抛弃掉所有object cache里的内容。注意这样将会使得所有之前通过该cache加载的对象都失效,所以在再次访问之前必须reload所有这些对象。这一般在多Session场合里只是一个备选方案,例如打算重用一个Session实例时。下述代码演示Collection Reloading:
    Code
    // Create a test object
    using (Session session = new Session( )) {
      
    new Person(session, "Billy Bott").Save( );
    }

    Session session1 
    = new Session( );
    Session session2 
    = new Session( );

    // Create a collection in session1 and check the content
    XPCollection<Person> peopleSession1 = new XPCollection<Person>(session1);
    Assert.AreEqual(
    1, peopleSession1.Count);
    Assert.AreEqual(
    "Billy Bott", peopleSession1[0].Name);

    // Fetch the test object in session2 and make a change
    Person billySession2 = session2.FindObject<Person>(new BinaryOperator("Name""Billy Bott"));
    billySession2.Name 
    = "Billy's new name";
    billySession2.Save( );

    // The old session1 collection still has the old "version" of the object
    Assert.AreEqual("Billy Bott", peopleSession1[0].Name);

    // Now reload the session1 collection and check - the change will be there
    peopleSession1.Reload();
    Assert.AreEqual(
    "Billy's new name", peopleSession1[0].Name);

    当有选择性的reload数据时,需要留意的是它们的行为可能并不一定像我们所想的那样。实际上Session.Reload(...)重载是唯一在任何情况下都立刻从数据库中刷新所有对象的方法。而当我们reload一个Collection时,它只是在内部被标记为"not loaded"状态,而当程序下一次访问该Collection时,它才会如上述机制那样通过Optimistic Locking去走一次“常规的”数据加载流程。

    Object Cache Consistency

     缓存一致性。这个词在文章中已经出现多次,DX的原文对它做了较大篇幅的解释。简单说来,对于被缓存起来了的一批对象,他们个体的“新旧程度”不一定是最最重要的,很多时候,数据集来自于同一个时间片段,互相之间可以构成一个符合逻辑的意义的整体更为重要。

    所以,作为一个object cache的实例,例如这里的Session实例,应该只包含一组逻辑上可以确保一致的数据。较好的实现方法则是“大方的”使用Session,每一个实例针对“一部分”的数据。(使用同一个Session做不同的工作可能会导致其object cache中的数据在逻辑上不一致,当选择性的reload数据时情况可能会更糟。)实际上这样也在一定程度上契合了上文中提到的“Session的生命周期越短,则其中的object cache中的内容也越新。并且这是通常推荐使用的唯一不会破坏缓存一致性(object cache consistency)的方法。 ”。

     

    Variations

     上文提到的缓存一致性和关系型数据里的隔离级别比较像,不同的是一个持久化对象的生命周期很可能要比一个事务要长久得多。XPO提供了一个参数用以改变当发现对象内容变动以后XPO的行为模式:

    The flag is accessible as XpoDefault.OptimisticLockingReadBehavior (for the default value) and as Session.OptimisticLockingReadBehavior (for the session instance specific value) and it can have the following values:

    Value Description
    Ignore Changed objects are never reloaded. Best object cache consistency.
    ReloadObjects Changed objects are automatically reloaded.
    ThrowException When changed objects are encountered, a LockingException is thrown.
    Mixed Outside of transactions, the behavior is ReloadObjects, inside transactions it's Ignore.

    The default value for this flag is currently "Mixed".

    Data Layer Caching

     除了上述Session里的object cache,XPO还提供了在数据级别缓存的功能。它将在数据库端执行的查询和相应的结果集缓存起来。该功能通过DataCacheRoot和DataCacheNode两个类实现。这两个类至少需要各一个,而当有特定需要时一个DataCacheRoot挂多个DataCacheNode也可以。数据实际上是缓存在DataCacheNode里的,DataCacheRoot负责全局的管理。

    当一个已经执行过的查询被重复执行时,结果集将从缓存里直接返回而不再去数据库跑一遍。数据的新鲜程度可由MaxCacheLatency属性指定,它指定了缓存过期的最大TimeSpan。如果有多个DataCacheNode连接到一个DataCacheRoot,该DataCacheRoot亦负责更新所有包含了缓存数据的表。每次当DataCacheNode和DataCacheRoot通信时,DataCacheRoot都将更新信息推送给DataCacheNode。

    下述代码演示了一个由1个DataCacheRoot和2个DataCacheNode组成的缓存结构。

    Code
    // Create the data store and a DataCacheRoot
    InMemoryDataStore dataStore = 
      
    new InMemoryDataStore(new DataSet( ), AutoCreateOption.SchemaOnly);
    DataCacheRoot cacheRoot 
    = new DataCacheRoot(dataStore);

    // Create two DataCacheNodes for the same DataCacheRoot
    DataCacheNode cacheNode1 = new DataCacheNode(cacheRoot);
    DataCacheNode cacheNode2 
    = new DataCacheNode(cacheRoot);

    // Create two data layers and two sessions
    SimpleDataLayer dataLayer1 = new SimpleDataLayer(cacheNode1);
    SimpleDataLayer dataLayer2 
    = new SimpleDataLayer(cacheNode2);
    Session session1 
    = new Session(dataLayer1);
    Session session2 
    = new Session(dataLayer2);

    // Create an object in session1
    Person billySession1 = new Person(session1, "Billy Bott");
    billySession1.Save( );

    // Load and check that object in session2
    XPCollection<Person> session2Collection = new XPCollection<Person>(session2);
    Assert.AreEqual(
    "Billy Bott", session2Collection[0].Name);

    // Make a change to that object in session1
    billySession1.Name = "Billy's new name";
    billySession1.Save( );

    // Reload the session2 collection. The DataCacheNode returns the result 
    // directly, so the change is not recognized. For the fun of it, do this 
    // several times. This whole block doesn't query the database at all.
    for (int i = 0; i < 5; i++) {
      session2Collection.Reload( );
      Assert.AreEqual(
    "Billy Bott", session2Collection[0].Name);
    }

    // Do something (anything) in session2. This makes the cacheNode2 
    // contact the cacheRoot and information about the updated data in 
    // the Person table is passed on to cacheNode2.
    new DerivedPerson(session2).Save( );

    // Reload the session2 collection again, and now the change is recognized.
    // In this case, the query goes through to the database.
    session2Collection.Reload( );
    Assert.AreEqual(
    "Billy's new name", session2Collection[0].Name);

    在很多实际用途里,一般一个DataCacheNode就够了。代码可简化如下:

    Code
    XpoDefault.DataLayer = new SimpleDataLayer(new DataCacheNode(
      
    new DataCacheRoot(XpoDefault.GetConnectionProvider(
      MSSqlConnectionProvider.GetConnectionString(
    "server""database"),
      AutoCreateOption.DatabaseAndSchema))));

    Optimistic Locking

    Optimistic Locking也在上文中被提及多次。XPObject和XPCustomObject会自动加上这个INT型字段,它的主要功能是避免多个用户同时修改同一条记录。当需要在数据库中更新一条记录时,XPO首先检查这个字段,如果这条记录已经被更新了,则抛出一个错误。若都正常则XPO更新该记录,并且也步增一次Optimistic Locking字段。

    可以通过把Session.LockingOption的值由默认的LockingOption.OptimisticLocking改为LockingOption.None来禁用Optimistic Locking。但这样不光会失去上述变更检测的功能,还会失去大部分的选择性reload功能。只剩XPBaseObject.Reload()Session.DropCache()方法还能工作。

    Common Usage Scenarios

    ASP.NET Applications

    这部分内容实际上在之前的文章“在ASP.NET项目中使用XPO的最佳准则”已经有详解,这里就不赘述了。

  • 相关阅读:
    动态规划之 0-1背包问题及改进
    动态规划之矩阵连乘
    python3 入门 (四) 类与继承
    动态规划之最长公共子序列(LCS)
    java并发包分析之———AQS框架
    java并发包分析之———BlockingQueue
    java并发包分析之———Deque和LinkedBlockingDeque
    java并发包分析之———concurrentHashMap
    java并发包分析之———ConcurrentSkipListMap
    java并发包分析之———Atomic类型
  • 原文地址:https://www.cnblogs.com/Elvin/p/1665565.html
Copyright © 2011-2022 走看看