由于ehcache,许多并发读取+一次写入导致ObjectNotFoundException

che*_*vim 18 java concurrency hibernate ehcache

我在高流量网站上使用Hibernate 3.6.8,ehcache 2.4.5(也尝试过最新的2.8.0),jvm 1.6.0_22,有时我会体验

ObjectNotFoundException:不存在具有给定标识符的行:[com.example.Foo#123]`

Foo通过最简单的代码创建一个新的(在这种情况下为id 123):

Foo foo = new Foo();
session.save(foo);
Run Code Online (Sandbox Code Playgroud)

原因是在这个高流量网站的所有页面中,我得到Foo的是这样的:

session.createQuery("from Foo").setCacheable(true).list();
Run Code Online (Sandbox Code Playgroud)

存储Foos 的表包含1000行,实体缓存在ehcache中:

<class-cache class="com.example.Foo" usage="read-write" />
Run Code Online (Sandbox Code Playgroud)

我的Hibernate配置的其他可能相关的部分是:

<property name="connection.url">jdbc:mysql://localhost:3306/example?characterEncoding=UTF-8</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>

<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">60</property>
<property name="hibernate.c3p0.min_size">10</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.max_statements">0</property>
<property name="hibernate.c3p0.timeout">0</property>
<property name="hibernate.c3p0.acquireRetryAttempts">1</property>
<property name="hibernate.c3p0.acquireRetryDelay">1</property>

<property name="hibernate.show_sql">true</property>
<property name="hibernate.use_sql_comments">true</property>

<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.jdbc.use_scrollable_resultset">true</property>

<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</property>
<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>
<property name="hibernate.cache.use_query_cache">true</property>
Run Code Online (Sandbox Code Playgroud)

错误发生一次然后消失.我怀疑ehcache查询缓存是使用新的实体id(123)id更新的,但实体缓存尚未使用该实体的内容进行更新.我使用JMeter在本地轻松地重现了这一点.

关于如何解决这个问题的任何想法?

Foo创作时,ObjectNotFoundException抛出一次.另一方面,如果我删除了一个实例,Foo那么我经常(并且永远)得到ObjectNotFoundException每次执行.list().堆栈跟踪可以在http://pastebin.com/raw.php?i=dp3HBgDB上看到

Ang*_*ity 11

read-write策略不保证数据库和缓存之间的事务性,所以我认为这是写入发生时发生的事情:

  • 新对象Foo附加到写入的hibernate会话中.

  • 通过与写请求相关联的休眠会话将延迟加载代理插入到第二级高速缓存中.

  • 新Foo将由同一会话插入数据库中,但插入将需要一段时间来构建,刷新和提交.

  • 同时另一个请求命中代理加载所有Foos.它在缓存中找到延迟加载代理(请参阅stacktrace DefaultLoadEventListener.proxyOrLoad()),并决定加载对象(DefaultLoadEventListener.load()).

  • 这会触发load()写入线程尚未插入数据库的Foo 的Hibernate .

  • 在数据库中找不到具有该Id的Foo,因此ObjectNotFoundException抛出了a .

要确认这一点,请在IDE上放置一个异常断点,以便在抛出异常时看到该对象尚未插入到DB中.解决问题的一种方法是使用交易策略.

  • 解释似乎很可能,我只有一个异议.因为我们有一个ID,所以Foo已被插入db(并且可能已提交).但是,实体缓存尚未更新,导致其他线程执行select by id查询.查询不返回任何内容,因为db隔离级别为REPEATABLE_READ.因此,即使其他线程实际提交了插入,也不会产生任何结果. (3认同)

che*_*vim 3

list()为了缓解实体被删除然后根本不起作用的情况,我ObjectNotFoundException在更高级别上发现了这种情况,当发生这种情况时,我会这样做:

session.getSessionFactory().getCache().evictCollectionRegions();
session.getSessionFactory().getCache().evictDefaultQueryRegion();
session.getSessionFactory().getCache().evictQueryRegions();  
Run Code Online (Sandbox Code Playgroud)

清除二级缓存可以使站点重新工作。这当然并不能阻止问题的发生,但它解决了整个站点的停机问题。