NHibernate没有二级缓存存问题

缓存管理面临的主要问题

缓存作為一个数据中心具备添加、更新、删除数据的操作,因此跟数据库类似会存在事务性、并发情况下数据一致性等问题需要解决

使用缓存比较典型的方式如下面代码:


上 面的示例代码,是在一个事务性环境中使用缓存存在更新操作(非只读缓存),如果这是一个共享缓存这样的使用方式存在很多问题,比如说: 如果事务中的其他处理导致异常数据库中对entity1的更新可以被回滚掉,但是cache中的entity1已经被更新了如果不处理这样的情况后续 从cache中读出的entity1就是一个不正确的数据

所以,要正确的使用缓存必须有一个完善的方案,充分考虑事务、并发等状況确保数据的正确性、一致性


相对于session来说,一级缓存是私有缓存没有二级缓存存是共享缓存

session加载实体的搜索顺序为: 1. 从一级缓存中查找;2. 从没有二级缓存存中查找;3. 从数据库查找

一级缓存在事务之间担当了一个隔离区域的作用,事务内对实体对象的所有新增、修改、删除在事务提交之前对其他session是不可见的,事务提交成功之后批量的将这些更新应用到没有二级缓存存中

这样的2级缓存机制能够在很大程度上確保数据的正确性(比如前面示例代码中事务失败的情况下就不会将数据更新到没有二级缓存存中,防止了没有二级缓存存出现错误的数據)以及防止ReadUncommited等其他一些事务一致性问题

待 新增、更新、删除的实体,使用3个列表缓存起来事务提交的时候将他们应用到数据库和没有②级缓存存中(Flush调用或者因为查询等导致的 NHibernate自动执行的Flush操作也会将他们应用到数据库,但不会应用到没有二级缓存存中没有二级缓存存只茬事务提交成功之后才更新)

没有二级缓存存因为是共享缓存,存在并发更新冲突但又必须保证没有二级缓存存数据的正确性,因此处理機制就复杂得多下面是详细的没有二级缓存存处理机制

没有二级缓存存的主要结构主要接口:


ICache: 统一的缓存存取访问接口


以memcached为例,实体缓存時的状态转换如上图

1. CacheEntry表示一个需要存储到缓存中或者从缓存中返回的对象

    CacheEntry中包含拆解后的实体属性值(DisassembledStateobject[]类型,数组中是每个属性的值)、实體的版本(乐观 锁时使用)、类型名称采用这样的处理方式,我们定义的domain对象就不需要实现Serializable接口也可以被序列化存储到缓存 中

存放的是owner(即當前被缓存的实体对象)的id值,组装过程中根据这个id值去取相关的对象设置到这个属性上(可能从一级缓存、没有二级缓存存或者数据 库加載,依赖于具体的设置和运行时的状态)

2. CacheItem用于解决并发更新没有二级缓存存时的数据一致性问题(不考虑这个问题的话直接将CacheEntry存到缓存中就鈳以了),主要是对soft lock机制的处理后面详细介绍

Insert, AfterInsert: 新增实体时的方法,实体新增到数据库之后会执行Insert方法事务提交后会执行AfterInsert方法。这些方法Φ如何处理没有二级缓存存由具体的缓存策略确定

Update, AfterUpdate: 更新实体时的方法,实体修改update到数据库之后会执行Update方法事务提交后会执行AfterUpdate方法。这些方法中如何处理没有二级缓存存由具体的缓存策略确定

Lock, Release: 这2个方法分别对缓存项进行加锁、解锁。语义上事务中开始更新实体时对缓存项执行Lock方法,事务提交后对缓存项执行Release方法在这些方法中如何处理没有二级缓存存由具体的缓存策略确定

ReadOnly策略 运用场景为,数据不会被更新NHibernate不更新没有二级缓存存的数据。采用只读策略的实体不能执行update操作否则会抛出异常,可以执行新增、删除操作只读策略只在實体从数据库加载后写到缓存中

UnstrictReadWrite策略 运用场景为,数据会被更新但频率不高,并发存储情况很少

采用该策略的实体新增时不会操作没囿二级缓存存;更新时只是简单的将没有二级缓存存的数据删除掉(Update, AfterUpdate方法中都会删除没有二级缓存存数据),这样期间或者后续的请求将从数據库加载数据并重新缓存

因为更新过程没有对缓存数据使用lock读取时也不会进行版本检查,因此并发存取时无法保证数据的一致性下面昰一个这样的示例场景:


1, 2: 请求1在事务中执行更新,NH更新数据库并从没有二级缓存存删除该数据

3: 某些操作(例如ISession.Evict)导致请求1的一级缓存中该数據失效

4, 5: 请求2从数据库加载该数据并放入没有二级缓存存。因为请求2在另外的事务上下文中因此加载的数据不包含请求1的更新

6: 请求1需要偅新加载该数据,因为一级缓存中没有因此从没有二级缓存存读取,结果读到的将是一份错误的数据

soft lock的原理比较简单假如事务中需要哽新key为839的数据,首先创建一个soft lock对象用839这个key存到cache中(如果cache中原来已经用839的key缓存了这个数据,也直接用soft lock覆盖他)然后更新数据库,完成事务的其他处理事务提交之后将id为839的实体对象再重新存入cache中。事务期间其他所有从没有二级缓存存读取


1: 更新操作前先锁定没有二级缓存存的数據
2,3: 从没有二级缓存存取数据如果返回的是null或者CacheItem,则新建一个CacheLock并存入没有二级缓存存;如果返回的是一个CacheLock则表明有另外的事务已经锁定該值,将并发锁定计数器增1并更新回没有二级缓存存中
    如果锁已经过期或者返回的CacheLock已经不是加锁时返回的那个(锁过期后又被其他线程重噺加锁了),则新建一个CacheLock设为 unlock状态放回没有二级缓存存,结束整个更新处理
    如果不是上面这些情况则说明期间没有并发更新,将新的实體状态更新到没有二级缓存存(锁自然被解除掉了)

一 旦发生并发更新并发的最后一个事务提交之后,NHibernate也不会将实体重新存入没有二级缓存存此时在没有二级缓存存中存储的是一个unlock状态的 CacheLock对象,在这个CacheLock过期以后实体才可能被重新缓存到没有二级缓存存中。采用这样的处理方式是因为并发事务发生 时,NHibernate不知道数据库中哪一个事务先执行、哪一个后执行为了确保ReadWrite策略的语义,强制这段时间内没有二级缓存存失 效

ReadWriteCache的Get方法除了在没有二级缓存存的数据被锁定时将返回null之外,还会将缓存项的时间戳与请求线程的事务时间进行比较也可能返回null,使得请求转向数据库查询由数据库保证事务隔离级别


而put方法还会比较实体的版本(使用乐观锁的情况)

看源代码时,Timestamper类是一个时间戳与计數器结合的产物在时间上精确到毫秒,每毫秒内采用1-4096的一个计数器增量分配。NHibernate.Caches.MemCache将ReadWriteCache的没有二级缓存存锁超时时间设置为0xea60000换算过来就是1汾钟


我正在使用HashtableCacheProvider作为我的Web应用程序的NHibernate沒有二级缓存存提供程序问题是,即使我重新启动IISNHibernate似乎也会保留缓存的数据。在这种情况下我认为缓存的数据会消失。有人可以解釋这个东西是如何工作的以及如何在使用HashtableCacheProvider时清除没有二级缓存存?谢谢!

我要回帖

更多关于 没有二级缓存 的文章

 

随机推荐