千家信息网

NHibernate缓存管理机制怎么理解

发表于:2024-11-23 作者:千家信息网编辑
千家信息网最后更新 2024年11月23日,这篇文章主要介绍"NHibernate缓存管理机制怎么理解",在日常操作中,相信很多人在NHibernate缓存管理机制怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家
千家信息网最后更新 2024年11月23日NHibernate缓存管理机制怎么理解

这篇文章主要介绍"NHibernate缓存管理机制怎么理解",在日常操作中,相信很多人在NHibernate缓存管理机制怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"NHibernate缓存管理机制怎么理解"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

缓存管理面临的主要问题

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

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

Database db = new Database();  Transaction tx = db.BeginTransaction();  try {      //从缓存读取      MyEntity1 entity1 = cache.Get("pk of entity1");       //缓存中没有时从数据库读取      if (entity1 == null) entity1 = db.Get("pk of entity1");            //对entity1进行处理       updated = db.Update(entity1); //entity1的更新保存到数据库中      if (updated) cache.Put(entity1); //数据库更新成功,则更新缓存       //事务中的其他处理       tx.Commit();  }  catch {      tx.Rollback();      throw;  }

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

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

NHibernate 2个级别的缓存机制

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

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

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

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

内部实现上,对一级缓存的管理很简单,所有已加载的实体(以及已经创建proxy但未加载的实体等)都被缓存在持久化上下文(NHibernate.Engine.StatefulPersistenceContext)中

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

NH1.2中这3个列表维护在SessionImpl中,NH2.0以后添加的新功能特性以及代码本身的重构动作相当多,这3个列表维护在NHibernate.Engine.ActionQueue中

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

二级缓存的主要结构

主要接口:

接口职责:

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

ICacheProvider: 工厂类、初始化类,用于创建ICache对象,启动时对cache server或组件进行初始化,退出时对cache server或组件进行必要的退出处理等

处理过程:

1. 配置文件中指定ICacheProvider的实现类

2. SessionFactory启动时创建ICacheProvider对象,执行ICacheProvider.Start()方法,并为每一个cache region创建一个ICache对象

3. 整个运行过程中,NHibernate可以使用SessionFactory创建的ICache完成缓存的存取操作

4. SessionFactory关闭时调用ICacheProvider.Stop()方法

实体状态的转换:

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

  • NHibernate2.1新特性之Tuplizers

  • 浅析NHibernate一对一映射的延迟加载

  • LINQ to SQL与NHibernate横向对比

  • 讲解Nhibernate与代码生成

  • 讲解NHibernate Session

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

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

对于primitive type的实体属性,拆解和组装过程没有特殊的处理;对于composite component、one-to-one、one-to-many的collection等实体属性,分解之后在DisassembledState中存放的是owner(即当前被缓存的实体对象)的id值,组装过程中根据这个id值去取相关的对象设置到这个属性上(可能从一级缓存、二级缓存,或者数据库加载,依赖于具体的设置和运行时的状态)

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

3. 将CacheItem转换成DictionaryEntry的处理,是由NHibernate.Caches.Memcache进行的,完全是一个多余的处理

NHibernate使用规则 [完整的类名#id值] 生成cache key,NHibernate.Caches.Memcache会在NHibernate生成的key前面再添加上 [region名称@](如果类的hbm文件中没有设置region名称,默认region为完整的类名,这样完整类名会在cache key中出现2次)

memcached的key最长只能是250个字符,NHibernate.Caches.Memcache在cache key超过250字符时,取key的hash值作为新的memcached key值,因为这样会存在hash冲突,所以NHibernate.Caches.Memcache构造一个DictionaryEntry对象(原 key值的MD5作为DictionaryEntry的key值,被缓存的对象作为value),将 DictionaryEntry存到memcached中。从缓存get对象时,NHibernate.Caches.Memcache对返回的 DictionaryEntry的key值再做一次比较,排除掉hash冲突的情况

这样的方式使用memcached,效率上太浪费了。一不留神,完整的类名就会在缓存数据中出现4次!

基于NHibernate的机制和memcached的特点,可以考虑使用cache region来区分不同的memcached集群,比如说用A、B 2台服务器作为只读缓存,region取名为readonly_region;C、D、E 3台服务器作为读写缓存,region取名为readwrite_region

4. 从DictionaryEntry到Memcached Server这段处理由Memcached.ClientLibrary完成,关于Memcached.ClientLibrary的分析,参考memcached client - memcacheddotnet (Memcached.ClientLibrary)

解决并发更新冲突

NHibernate定义了3中缓存策略: 只读策略(useage="read-only")、非严格的读写策略(useage="nonstrict-read-write")和读写策略(useage="read-write")

处理并发更新的结构

ICacheConcurrencyStrategy聚合了一个ICache对象,NHibernate操作缓存时不是直接使用ICache对象,而是通过ICacheConcurrencyStrategy 完成,这样确保系统对二级缓存的操作,都是在特定的缓存策略下进行的

ICacheConcurrencyStrategy和ICache接口的语义有差别,ICache纯粹是缓存的操作接口,而ICacheConcurrencyStrategy则与实体的状态变化相关

ICacheConcurrencyStrategy的语义

Evict: 让缓存项失效

Get, Put, Remove, Clear: 与ICache的相关方法相同,纯粹的缓存读取、存储等操作

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

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

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

在前面实体状态转换的图中,CacheEntry到CacheItem的转换由ICacheConcurrencyStrategy接口完成,CacheItem只被ICacheConcurrencyStrategy使用,NHibernate内部其他需要与缓存交互的地方均使用 CacheEntry和ICacheConcurrencyStrategy接口

ReadOnly策略

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

UnstrictReadWrite策略

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

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

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

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

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

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

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

ReadWrite策略

运用场景为,数据可能经常并发更新,NHibernate确保ReadCommitted的事务隔离级别,如果数据库的隔离级别为RepeatableRead,该策略也能基本保证二级缓存满足RepeatableRead的隔离级别

NHibernate通过使用版本、timestamp检查、soft lock等机制实现这一目标

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

ReadWriteCache.ILockable为soft lock接口,CacheItem和CacheLock两个类实现了这个接口

更新数据时的处理步骤

1: 更新操作前先锁定二级缓存的数据

2,3: 从二级缓存取数据,如果返回的是null或者CacheItem,则新建一个CacheLock并存入二级缓存;如果返回的是一个CacheLock,则表明有另外的事务已经锁定该值,将并发锁定计数器增1并更新回二级缓存中

4: 返回lock对象给EntityAction

5, 6, 7: 更新数据库,完成事务的其他处理,提交事务。ReadWriteCache的Update不做任何处理

8: 事务提交后执行ReadWriteCache的AfterUpdate方法

先从二级缓存读取CacheLock对象,如果返回null说明锁已经过期(事务时间太长造成)

如果锁已经过期,或者返回的CacheLock已经不是加锁时返回的那个(锁过期后又被其他线程重新加锁了),则新建一个CacheLock,设为 unlock状态放回二级缓存,结束整个更新处理

如果CacheLock为并发锁状态,则将CacheLock并发锁计数器减一,更新回二级缓存,结束整个更新处理

如果不是上面这些情况,则说明期间没有并发更新,将新的实体状态更新到二级缓存(锁自然被解除掉了)

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

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

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

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

到此,关于"NHibernate缓存管理机制怎么理解"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

缓存 数据 更新 事务 实体 数据库 处理 方法 对象 策略 机制 接口 状态 情况 时间 问题 管理 一致 一致性 代码 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 数据库 表头 英文catle 两个数据库表格数据比对 魔兽怀旧那个服务器火 银行微服务架构数据库 渝北区咨询软件开发服务常见问题 软件开发增票项目名称 电脑上出现键入网络安全钥匙 太空浮岛网络技术有限公司 msql数据库频繁读写优化 ipad管理服务器 2021网络安全美亚柏科 杭州全景软件开发公司 数据库安全性的含义和授权机制 南京中兴软件开发大概月薪多少 阿里云服务器连接两个网站 网络安全设备设计方案概述 贵阳金蝶erp软件开发 网络技术服务优点 如何用vb程序读取数据库 安徽学生网络技术开发计划表 服务器性能指标报告 网络安全难在哪里 互联网科技树总结 东莞易维网络技术有限公司 银川软件开发技术项目实训中心 学校软件开发培训机构 管理代理服务器设置 班会课青少年网络安全 数据库基础及应用教学ppt 太钢有没有软件开发公司
0