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

时间:2014-01-23 12:44:49

标签: java hibernate concurrency 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);

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

session.createQuery("from Foo").setCacheable(true).list();

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

<class-cache class="com.example.Foo" usage="read-write" />

我的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>

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

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

Foo创建时,ObjectNotFoundException会被抛出一次。另一方面,如果我删除了Foo的实例,那么每次执行ObjectNotFoundException时我都会(并且永远)获得.list()。可以在http://pastebin.com/raw.php?i=dp3HBgDB

看到堆栈跟踪

3 个答案:

答案 0 :(得分:11)

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

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

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

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

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

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

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

要确认这一点,请在IDE上放置一个exception breakpoint,以便在抛出异常时看到该对象尚未插入到数据库中。解决问题的一种方法是使用transactional策略。

答案 1 :(得分:2)

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

session.getSessionFactory().getCache().evictCollectionRegions();
session.getSessionFactory().getCache().evictDefaultQueryRegion();
session.getSessionFactory().getCache().evictQueryRegions();  

清除二级缓存会使网站重新运行。这当然不能防止问题的发生,但它解决了整个站点的停机问题。

答案 2 :(得分:-1)

取自 Cache Configuration 的文档:

The following attributes and elements are optional.

timeToIdleSeconds:
Sets the time to idle for an element before it expires.
i.e. The maximum amount of time between accesses before an element expires
Is only used if the element is not eternal.
Optional attribute. A value of 0 means that an Element can idle for infinity.
The default value is 0.

timeToLiveSeconds:
Sets the time to live for an element before it expires.
i.e. The maximum time between creation time and when an element expires.
Is only used if the element is not eternal.
Optional attribute. A value of 0 means that and Element can live for infinity.
The default value is 0.

或者您也可以选择其他选项:

<强> Data Freshness and Expiration