代码改变世界

[转载]AAF灵便应用框架简介系列(8):缓存机制介绍

2009-04-02 09:39  周国选  阅读(413)  评论(0编辑  收藏  举报

好久没写文章了。标题列了很久,不动笔实在对不起大家。

今天来谈谈AAF的缓存机制。其实,关于AAF的缓存机制已经在前面几篇文章里陆陆续续地简单介绍过。今天我们就多花点时间来仔细看看。

AAF对缓存的考虑主要有三个层次:
1)普通值或普通对象的缓存
2)AgileObject的缓存
3)页面内容的缓存

在AAF中,所有缓存本质上讲都是通过ICacheService和ITypeCacheService来完成的,前者可被看作是所有缓存集的管理入口,后者则是针对一种特定信息的缓存集及其管理接口。对于普通值或普通对象,缓存的管理需要使用者自行使用ICacheService获得ITypeCacheService,并通过ITypeCacheService来管理。通过ICacheService获得ITypeCacheService,需要调用 ITypeCacheService GetTypeCacheService(object cacheOwner); 可以看到,所有的ITypreCacheService都“属于”一个cacheOwner,这个cacheOwner可以是一个类型,可以是一个对象,只要调用者认为该object能都代表这个ITypeCacheService并作为其逻辑上的拥有者而存在,同时不会和其它调用者获得的ITypeCacheService发生任何冲突即可。另外,cacheOwner会在相关Config文件中对应的/Configuration/Assemblies/Assembly/Types/Type目录下拥有一个设置节“Settings”。其具体内容可在本文后续部分看到。

*AAF不支持缓存的依赖性和缓存更新回调,因为觉得二者意义不大。

 

普通值或普通对象,主要是指一些设置项和非AgileObject对象的缓存,这种对象的缓存在AAF中是需要管理设置项或普通对象的逻辑自己或取ITypeCacheService并进行管理的。AAF中,IConfiguratiunService中就封装了这样的代码,对通过各.config配置文件加载的设置项进行透明的缓存,以便提高各设置项的查询速度。普通缓存具有如下特点:
1)可以基于时间自动更新
2)不通过Object Cache-Sync自动同步
3)通过Cache.Config配置

 


类似的,AgileObject对象的缓存主要被透明的集成在IPersister内部。外部代码并不需要关心AgileObject的缓存及更新。IPersister的实现一方面根据Persistence.config自动管理AgileObject对象的缓存,另一方面也能自动实现多个服务器并存时,各种AgileObject由更新(而不是定时)触发的缓存同步。这种机制既使得数据库服务器的负载明显下降,也使得系统Scale Out的能力明显上升。IPersister的缓存机制有如下特点:
1)可以基于时间自动更新
2)可以通过Object Cache-Sync自动同步(只有AgileObject可以同步,而不管其搜索和加载途径)
3)无论如何搜索和加载,包括基于对象树的搜索,AAF保证“实例唯一性”(后面会提到)。
4)对于未被“挂”在内存中的普通对象,可通过Persistence.Config设置其缓存池的大小。AAF会自动管理缓存池大小,对于超过缓存池大小的缓存进行清理。这里要强调一点,也适用于其它各种缓存,被清理的缓存并不会立即无法访问,只要GC还没有将其从物理上释放,IPersister和其它使用底层缓存的逻辑仍然可以利用这些缓存。
5)通过Persistence.Config配置刷新时间间隔以及是否需要缓存同步,通过Cache.Config配置是否缓存及缓存大小。

实际上AgileObject还存在其它形式的缓存:对象的根被直接或间接“钉在”AppDomain的Root对象上。比如一个商品目录,有根目录以及根目录的下属递归子目录通过IAgileRelation建立的联系组成。在初始化时,我们将根目录“钉”在某个地方。与此同时,其所有直接“关联”的对象,也间接地被“挂“在内存中。GC将不会去收割这部分对象。因此相关代码可以通过根对象以及它与下属对象的关系寻找其中的对象,也可以通过IPersister根据Id/编号加载,而且AAF保证无论通过何种方式搜索和加载,同一时间,同一类型同一编号/Id的对象一定是同一实例(实例唯一性)。我们曾经提过对象加载应该尽可能通过编号加载,通过搜索条件等固然还是可以保证“实例唯一性”,但是其速度总体来说是无法和根据Id加载相提并论的。通过Id加载对象总是首先检查缓存再决定是否加载,这保证了如果缓存存在相关对象就根本不会发生任何数据库访问行为,除非调用方要求对对象进行强制更新。通过其它方式加载对象则不会首先检查缓存,实际上也无法检查缓存,因为根本不知道其编号,而AgileObject对象的缓存是完全依赖于其类型和编号的。

对于被挂在内存中的对象,因为外部逻辑可能根本不会通过IPersister对其进行加载,而直接通过对象树进行搜索。因此,这种对象的更新一般应该使用Object Cache-Sync技术。这一技术是开发透明而可配置的(通过SyncProxy.Config)。

 


AAF的另一个缓存机制植根于页面内容的缓存。一方面,在Asp.Net1.1中,不存在页面局部缓存的概念,页面要不整体缓存要不不缓存;另一方面在Asp.Net的页面生成机制中,DataSet成为主要的数据对象来源,而其性能及速度使得我们倾向于不采用DataSet。考虑到这些因素,我们提供了自己页面内容缓存技术。该技术有这样几个特点:
1)内部使用IDataReader进行数据读取,其分配以及相关连接的分配及释放被完全封装,以便降低内存泄漏的可能性。
2)通过Callback,允许外部介入IDataReader相关查询语句以及界面的生成过程。
3)通过AAF的IParaService,允许把界面的组成信息(主要是一些HTML片段)存放在数据库中,并且在使用时,全部通过缓存(而不是数据库)进行访问。这样既解决了不使用DataSet的页面组装问题,也避免了把所有页面组装逻辑写入CodeBehind代码导致的维护难题。在这种方式下,简单的界面维护可以直接在后台进行,不需要重新编译任何源代码,甚至不需要登录Web服务器。
4)实现了透明的缓存机制。页面内容的缓存时间可由后台通过IParaService进行设置,其具体缓存则由IDataReaderVisualizer内在管理。
5)不通过Object Cache-Sync自动同步
6)通过Cache.Config配置

下面我们看看典型的Cache.Config文件、Persistence.Config文件和SyncProxy.Config文件:
首先看一下Cache.Config文件:
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <Assemblies>
    <Assembly Name="Aaf.Core.Imp">
      <Types>
        <Type Name="Aaf.Core.Imp.ConfigurationService">
          <Settings>
            <Setting Name="UseCache">True</Setting>
            <Setting Name="CacheSize">500</Setting>
            <Setting Name="AddWhenGet">true</Setting>
            <Setting Name="OsRefreshInterval" />
          </Settings>
        </Type>
      </Types>
    </Assembly>
    <Assembly Name="AafTest">
      <Types>
        <Type Name="AafTest.AtOrder">
          <Settings>
            <Setting Name="CacheSize">1</Setting>
            <Setting Name="UseCache">true</Setting>
            <Setting Name="OsRefreshInterval" />
            <Setting Name="UseWR" />
          </Settings>
        </Type>
      </Types>
    </Assembly>
    <Assembly Name="Aaf.Para.Imp">
      <Types>
        <Type Name="Aaf.Para.Imp.ParaRoot">
          <Settings>
            <Setting Name="UseCache" />
            <Setting Name="UseWR" />
          </Settings>
        </Type>
        <Type Name="Aaf.Para.Imp.ParaEntryObj">
          <Settings>
            <Setting Name="UseCache" />
            <Setting Name="UseWR" />
          </Settings>
        </Type>
      </Types>
    </Assembly>
    <Assembly Name="Aaf.UiHelper.Imp">
      <Types>
        <Type Name="Aaf.UiHelper.Imp.DataReaderVisualizer">
          <Settings>
            <Setting Name="CacheSize">500</Setting>
            <Setting Name="UseCache">True</Setting>
            <Setting Name="UseWR" />
          </Settings>
        </Type>
      </Types>
    </Assembly>
  </Assemblies>
</Configuration>
其中UseCache决定了是否使用缓存,CacheSize决定了对应缓存池的大小,OsRefreshInterval决定了普通值或普通对象缓存的以秒为单位的刷新间隔(默认为不刷新)。

再看看Persistence.Config文件:
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <Assemblies>
    <Assembly Name="Aaf.Core.Imp">
      <Types>
        <Type Name="Aaf.Core.Imp.ConfigurationService">
          <Settings>
            <Setting Name="AddWhenGet">true</Setting>
          </Settings>
        </Type>
      </Types>
    </Assembly>
    <Assembly Name="Aaf.Persistence.Imp">
      <Types>
        <Type Name="Aaf.Persistence.Imp.StorageContextMappingService">
          <Settings>
            <Setting Name="IsDirty">False</Setting>
            <Setting Name="NeedExecuteScript">False</Setting>
            <Setting Name="DefaultStorageContext" />
          </Settings>
        </Type>
        <Type Name="Aaf.Persistence.Imp.Persister">
          <Settings>
            <Setting Name="DealPkgSync" />
            <Setting Name="UseStmtCache" />
            <Setting Name="AoRefreshInterval" />
            <Setting Name="LogNotifications" />
          </Settings>
        </Type>
        <Type Name="Aaf.Persistence.Imp.ModificationRecord">
          <Settings>
            <Setting Name="TableName" />
            <Setting Name="StorageContext" />
            <Setting Name="NeedExecuteScript" />
            <Setting Name="StorageAliases" />
          </Settings>
          <Members>
            <Member Name="OccurDate">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="ModificationType">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="ObjectTypeFullName">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="OperatorId">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="IsSuspentChange">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="ObjectId">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="Remark">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="OldValue">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="NewValue">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
            <Member Name="IsDeletedChange">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
          </Members>
        </Type>
      </Types>
      <Settings>
        <Setting Name="StorageContext" />
      </Settings>
    </Assembly>
  ......
    <Assembly Name="Aaf.Para.Imp">
      <Types>
        <Type Name="Aaf.Para.Imp.ParaRoot">
          <Settings>
            <Setting Name="TableName" />
            <Setting Name="StorageContext" />
            <Setting Name="StorageAliases" />
            <Setting Name="NeedExecuteScript" />
            <Setting Name="AoRefreshInterval" />
          </Settings>
        </Type>
        <Type Name="Aaf.Para.Imp.ParaEntryObj">
          <Settings>
            <Setting Name="TableName" />
            <Setting Name="StorageContext" />
            <Setting Name="StorageAliases" />
            <Setting Name="NeedExecuteScript" />
            <Setting Name="AoRefreshInterval" />
          </Settings>
          <Members>
            <Member Name="Xml">
              <Settings>
                <Setting Name="FieldName" />
              </Settings>
            </Member>
          </Members>
        </Type>
      </Types>
      <Settings>
        <Setting Name="StorageContext" />
      </Settings>
    </Assembly>
  </Assemblies>
</Configuration>
其中AoRefreshInterval决定了AgileObject对象缓存的以秒为单位的刷新间隔(默认为不刷新)。AgileObject对象的是否需要缓存以及缓存大小也是通过Cache.Config来配置的(UseCache决定了是否使用缓存,CacheSize决定了对应缓存池的大小)。

下面是一个SyncProxy.Config文件:
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <Assemblies>
    <Assembly Name="Aaf.Core.Imp">
      <Types>
        <Type Name="Aaf.Core.Imp.ConfigurationService">
          <Settings>
            <Setting Name="AddWhenGet">true</Setting>
          </Settings>
        </Type>
      </Types>
    </Assembly>
    <Assembly Name="Aaf.SyncProxy.Imp">
      <Types>
        <Type Name="Aaf.SyncProxy.Imp.SyncServiceProxy">
          <Settings>
            <Setting Name="NotifierDelay" />
            <Setting Name="ListenSelfNotifications">true</Setting>
            <Setting Name="StrictRestart" />
          </Settings>
          <Members>
            <Member Name="FocusTypes">
              <FocusType>*</FocusType>
            </Member>
            <Member Name="FocusContextNames">
              <FocusContextName>*</FocusContextName>
            </Member>
          </Members>
        </Type>
      </Types>
    </Assembly>
  </Assemblies>
</Configuration>

我们可以通过多个<FocusType>[NameSpace.TypeName, AssemblyName]</FocusType>标记设定需要同步的对象类型。上例中该标记只有一个标记:<FocusType>*</FocusType>,这意味着对所有对象进行同步。
我们也可以通过多个<FocusContextName>[ContextName]</FocusContextName>标记设定需要同步的存储上下文。上例中,只有一个标记<FocusContextName>*</FocusContextName>,这意味着对所有存储上下文进行同步。