Hibernate READ_WRITE CacheConcurrencyStrategy如何工作
介紹
在我以前的文章中,我介紹了NONSTRICT_READ_WRITE二級緩存并發(fā)機(jī)制。 在本文中,我將使用READ_WRITE策略繼續(xù)本主題。
直寫式緩存
NONSTRICT_READ_WRITE是一種通讀緩存策略,可更新最終無效的緩存條目。 盡管這種策略可能很簡單,但是隨著寫入操作的增加,性能會下降。 對于需要大量寫入的應(yīng)用程序,直寫式高速緩存策略是更好的選擇,因?yàn)楦咚倬彺鏃l目可以被日期化而不是被丟棄。
因?yàn)閿?shù)據(jù)庫是記錄系統(tǒng),并且數(shù)據(jù)庫操作被包裝在物理事務(wù)中,所以可以同步更新緩存(例如TRANSACTIONAL緩存并發(fā)策略的情況)或異步更新(在提交數(shù)據(jù)庫事務(wù)之后)。
READ_WRITE策略是一種異步緩存并發(fā)機(jī)制,為了防止數(shù)據(jù)完整性問題(例如,陳舊的緩存條目),它使用了提供工作單元隔離保證的鎖定機(jī)制。
插入資料
因?yàn)槌志没膶?shí)體是唯一標(biāo)識的(每個實(shí)體都分配給一個不同的數(shù)據(jù)庫行),所以新創(chuàng)建的實(shí)體會在提交數(shù)據(jù)庫事務(wù)后立即緩存:
@Override public boolean afterInsert(Object key, Object value, Object version) throws CacheException {region().writeLock( key );try {final Lockable item = (Lockable) region().get( key );if ( item == null ) {region().put( key, new Item( value, version, region().nextTimestamp() ) );return true;}else {return false;}}finally {region().writeUnlock( key );} }對于要在插入時進(jìn)行緩存的實(shí)體,它必須使用SEQUENCE生成器 ,該緩存由EntityInsertAction填充:
@Override public void doAfterTransactionCompletion(boolean success, SessionImplementor session) throws HibernateException {final EntityPersister persister = getPersister();if ( success && isCachePutEnabled( persister, getSession() ) ) {final CacheKey ck = getSession().generateCacheKey( getId(), persister.getIdentifierType(), persister.getRootEntityName() );final boolean put = cacheAfterInsert( persister, ck );}}postCommitInsert( success ); }IDENTITY生成器不能與事務(wù)性的后寫式第一級緩存設(shè)計(jì)配合使用,因此關(guān)聯(lián)的EntityIdentityInsertAction不會緩存新插入的條目(至少在修復(fù)HHH-7964之前)。
從理論上講,在數(shù)據(jù)庫事務(wù)提交和第二級高速緩存插入之間,一個并發(fā)事務(wù)可能會加載新創(chuàng)建的實(shí)體,因此觸發(fā)高速緩存插入。 雖然可能,但緩存同步滯后非常短,如果并發(fā)事務(wù)被交錯,則只會使另一個事務(wù)命中數(shù)據(jù)庫,而不是從緩存中加載實(shí)體。
更新資料
盡管插入實(shí)體是一個相當(dāng)簡單的操作,但是對于更新,我們需要同步數(shù)據(jù)庫和緩存條目。 READ_WRITE并發(fā)策略采用鎖定機(jī)制來確保數(shù)據(jù)完整性:
刪除資料
從下面的序列圖中可以看出,刪除實(shí)體與更新過程類似:
- Hibernate Transaction提交過程觸發(fā)會話刷新
- EntityDeleteAction用Lock對象替換當(dāng)前的緩存條目
- remove方法調(diào)用不執(zhí)行任何操作,因?yàn)镽EAD_WRITE是異步緩存并發(fā)策略
- 提交數(shù)據(jù)庫事務(wù)后 ,將調(diào)用after-transaction-completion回調(diào)
- EntityDeleteAction調(diào)用EntityRegionAccessStrategy的unlockItem方法
- ReadWriteEhcacheEntityRegionAccessStrategy用另一個超時時間增加的Lock對象替換Lock條目
刪除實(shí)體后,其關(guān)聯(lián)的二級緩存條目將被一個Lock對象代替,該對象將發(fā)出任何隨后的請求以從數(shù)據(jù)庫讀取而不是使用緩存條目。
鎖定構(gòu)造
Item和Lock類都繼承自Lockable類型,并且這兩個類都有一個特定的策略,允許讀取或?qū)懭刖彺鏃l目。
READ_WRITE 鎖定對象
Lock類定義以下方法:
@Override public boolean isReadable(long txTimestamp) {return false; }@Override public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {if ( txTimestamp > timeout ) {// if timedout then allow writereturn true;}if ( multiplicity > 0 ) {// if still locked then disallow writereturn false;}return version == null? txTimestamp > unlockTimestamp: versionComparator.compare( version, newVersion ) < 0; }- Lock對象不允許讀取緩存條目,因此任何后續(xù)請求都必須發(fā)送到數(shù)據(jù)庫
- 如果當(dāng)前會話創(chuàng)建時間戳大于“鎖定超時”閾值,則允許寫入緩存條目
- 如果至少一個會話設(shè)法鎖定了該條目,則禁止進(jìn)行任何寫操作
- 如果進(jìn)入的實(shí)體狀態(tài)已增加其版本,或者當(dāng)前的會話創(chuàng)建時間戳大于當(dāng)前的條目解鎖時間戳,則可以使用Lock條目進(jìn)行寫操作
READ_WRITE 項(xiàng)目對象
Item類定義以下讀取/寫入訪問策略:
@Override public boolean isReadable(long txTimestamp) {return txTimestamp > timestamp; }@Override public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {return version != null && versionComparator.compare( version, newVersion ) < 0; }- 僅在緩存條目創(chuàng)建時間之后啟動的會話中才可讀取項(xiàng)目
- Item條目僅在傳入實(shí)體狀態(tài)已增加其版本時才允許寫入
緩存條目并發(fā)控制
當(dāng)保存和讀取底層緩存條目時,將調(diào)用這些并發(fā)控制機(jī)制。
在調(diào)用ReadWriteEhcacheEntityRegionAccessStrategy get方法時讀取緩存條目:
public final Object get(Object key, long txTimestamp) throws CacheException {readLockIfNeeded( key );try {final Lockable item = (Lockable) region().get( key );final boolean readable = item != null && item.isReadable( txTimestamp );if ( readable ) {return item.getValue();}else {return null;}}finally {readUnlockIfNeeded( key );} }緩存條目由ReadWriteEhcacheEntityRegionAccessStrategy putFromLoad方法編寫:
public final boolean putFromLoad(Object key,Object value,long txTimestamp,Object version,boolean minimalPutOverride)throws CacheException {region().writeLock( key );try {final Lockable item = (Lockable) region().get( key );final boolean writeable = item == null || item.isWriteable( txTimestamp, version, versionComparator );if ( writeable ) {region().put( key, new Item( value, version, region().nextTimestamp() ) );return true;}else {return false;}}finally {region().writeUnlock( key );} }超時
如果數(shù)據(jù)庫操作失敗,則當(dāng)前高速緩存條目將保留一個Lock對象,并且無法回滾到其先前的Item狀態(tài)。 由于這個原因,鎖必須超時,以允許將緩存條目替換為實(shí)際的Item對象。 EhcacheDataRegion定義以下超時屬性:
private static final String CACHE_LOCK_TIMEOUT_PROPERTY = "net.sf.ehcache.hibernate.cache_lock_timeout"; private static final int DEFAULT_CACHE_LOCK_TIMEOUT = 60000;除非我們重寫net.sf.ehcache.hibernate.cache_lock_timeout屬性,否則默認(rèn)超時為60秒:
final String timeout = properties.getProperty(CACHE_LOCK_TIMEOUT_PROPERTY,Integer.toString( DEFAULT_CACHE_LOCK_TIMEOUT ) );以下測試將模擬失敗的數(shù)據(jù)庫事務(wù),因此我們可以觀察到READ_WRITE緩存如何僅在超時閾值到期后才允許寫入。 首先,我們將降低超時值,以減少緩存凍結(jié)時間:
properties.put("net.sf.ehcache.hibernate.cache_lock_timeout", String.valueOf(250));我們將使用自定義攔截器手動回滾當(dāng)前正在運(yùn)行的事務(wù):
@Override protected Interceptor interceptor() {return new EmptyInterceptor() {@Overridepublic void beforeTransactionCompletion(Transaction tx) {if(applyInterceptor.get()) {tx.rollback();}}}; }以下例程將測試鎖定超時行為:
try {doInTransaction(session -> {Repository repository = (Repository)session.get(Repository.class, 1L);repository.setName("High-Performance Hibernate");applyInterceptor.set(true);}); } catch (Exception e) {LOGGER.info("Expected", e); } applyInterceptor.set(false);AtomicReference<Object> previousCacheEntryReference =new AtomicReference<>(); AtomicBoolean cacheEntryChanged = new AtomicBoolean();while (!cacheEntryChanged.get()) {doInTransaction(session -> {boolean entryChange;session.get(Repository.class, 1L);try {Object previousCacheEntry = previousCacheEntryReference.get();Object cacheEntry = getCacheEntry(Repository.class, 1L);entryChange = previousCacheEntry != null &&previousCacheEntry != cacheEntry;previousCacheEntryReference.set(cacheEntry);LOGGER.info("Cache entry {}", ToStringBuilder.reflectionToString(cacheEntry));if(!entryChange) {sleep(100);} else {cacheEntryChanged.set(true);}} catch (IllegalAccessException e) {LOGGER.error("Error accessing Cache", e);}}); }運(yùn)行此測試將生成以下輸出:
selectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_ fromrepository readwritec0_ wherereadwritec0_.id=1updaterepository setname='High-Performance Hibernate',version=1 whereid=1 and version=0JdbcTransaction - rolled JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_ fromrepository readwritec0_ wherereadwritec0_.id = 1Cache entry net.sf.ehcache.Element@3f9a0805[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=Lock Source-UUID:ac775350-3930-4042-84b8-362b64c47e4b Lock-ID:0,version=1,hitCount=3,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280657865,cacheDefaultLifespan=true,id=0 ] Wait 100 ms! JdbcTransaction - committed JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_ fromrepository readwritec0_ wherereadwritec0_.id = 1Cache entry net.sf.ehcache.Element@3f9a0805[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=Lock Source-UUID:ac775350-3930-4042-84b8-362b64c47e4b Lock-ID:0,version=1,hitCount=3,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280657865,cacheDefaultLifespan=true,id=0 ] Wait 100 ms! JdbcTransaction - committed JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_ fromrepository readwritec0_ wherereadwritec0_.id = 1 Cache entry net.sf.ehcache.Element@305f031[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Item@592e843a,version=1,hitCount=1,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280658322,cacheDefaultLifespan=true,id=0 ] JdbcTransaction - committed JDBC Connection- 第一個事務(wù)嘗試更新實(shí)體,因此在提交事務(wù)之前,關(guān)聯(lián)的第二級緩存條目已被鎖定。
- 第一個事務(wù)失敗,它被回滾
- 持有鎖,因此接下來的兩個連續(xù)事務(wù)將進(jìn)入數(shù)據(jù)庫,而不用當(dāng)前已加載的數(shù)據(jù)庫實(shí)體狀態(tài)替換Lock條目
- 在Lock超時期限到期后,第三筆交易最終可以用Item緩存條目替換Lock (保持實(shí)體分解為水合狀態(tài) )
結(jié)論
READ_WRITE并發(fā)策略具有直寫式緩存機(jī)制的優(yōu)點(diǎn),但是您需要了解它的內(nèi)部工作原理,才能確定它是否適合您當(dāng)前的項(xiàng)目數(shù)據(jù)訪問要求。
對于繁重的寫爭用方案,鎖定結(jié)構(gòu)將使其他并發(fā)事務(wù)進(jìn)入數(shù)據(jù)庫,因此您必須確定同步高速緩存并發(fā)策略是否更適合這種情況。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/05/how-does-hibernate-read_write-cacheconcurrencystrategy-work.html
總結(jié)
以上是生活随笔為你收集整理的Hibernate READ_WRITE CacheConcurrencyStrategy如何工作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUBA平台–新的Java企业应用程序框
- 下一篇: 用随机数发生器射击自己的脚