Hibernate READ_WRITE CacheConcurrencyStrategy如何工作
介紹
在我以前的文章中,我介紹了NONSTRICT_READ_WRITE二級緩存并發機制。 在本文中,我將使用READ_WRITE策略繼續本主題。
直寫式緩存
NONSTRICT_READ_WRITE是一種通讀緩存策略,可更新最終無效的緩存條目。 盡管這種策略可能很簡單,但是隨著寫入操作的增加,性能會下降。 對于需要大量寫入的應用程序,直寫式高速緩存策略是更好的選擇,因為高速緩存條目可以被日期化而不是被丟棄。
因為數據庫是記錄系統,并且數據庫操作被包裝在物理事務中,所以可以同步更新緩存(例如TRANSACTIONAL緩存并發策略的情況)或異步更新(在提交數據庫事務之后)。
READ_WRITE策略是一種異步緩存并發機制,為了防止數據完整性問題(例如,陳舊的緩存條目),它使用了提供工作單元隔離保證的鎖定機制。
插入資料
因為持久化的實體是唯一標識的(每個實體都分配給一個不同的數據庫行),所以新創建的實體會在提交數據庫事務后立即緩存:
@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 );} }對于要在插入時進行緩存的實體,它必須使用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生成器不能與事務性的后寫式第一級緩存設計配合使用,因此關聯的EntityIdentityInsertAction不會緩存新插入的條目(至少在修復HHH-7964之前)。
從理論上講,在數據庫事務提交和第二級高速緩存插入之間,一個并發事務可能會加載新創建的實體,因此觸發高速緩存插入。 雖然可能,但緩存同步滯后非常短,如果并發事務被交錯,則只會使另一個事務命中數據庫,而不是從緩存中加載實體。
更新資料
盡管插入實體是一個相當簡單的操作,但是對于更新,我們需要同步數據庫和緩存條目。 READ_WRITE并發策略采用鎖定機制來確保數據完整性:
刪除資料
從下面的序列圖中可以看出,刪除實體與更新過程類似:
- Hibernate Transaction提交過程觸發會話刷新
- EntityDeleteAction用Lock對象替換當前的緩存條目
- remove方法調用不執行任何操作,因為READ_WRITE是異步緩存并發策略
- 提交數據庫事務后 ,將調用after-transaction-completion回調
- EntityDeleteAction調用EntityRegionAccessStrategy的unlockItem方法
- ReadWriteEhcacheEntityRegionAccessStrategy用另一個超時時間增加的Lock對象替換Lock條目
刪除實體后,其關聯的二級緩存條目將被一個Lock對象代替,該對象將發出任何隨后的請求以從數據庫讀取而不是使用緩存條目。
鎖定構造
Item和Lock類都繼承自Lockable類型,并且這兩個類都有一個特定的策略,允許讀取或寫入緩存條目。
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對象不允許讀取緩存條目,因此任何后續請求都必須發送到數據庫
- 如果當前會話創建時間戳大于“鎖定超時”閾值,則允許寫入緩存條目
- 如果至少一個會話設法鎖定了該條目,則禁止進行任何寫操作
- 如果進入的實體狀態已增加其版本,或者當前的會話創建時間戳大于當前的條目解鎖時間戳,則可以使用Lock條目進行寫操作
READ_WRITE 項目對象
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; }- 僅在緩存條目創建時間之后啟動的會話中才可讀取項目
- Item條目僅在傳入實體狀態已增加其版本時才允許寫入
緩存條目并發控制
當保存和讀取底層緩存條目時,將調用這些并發控制機制。
在調用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 );} }超時
如果數據庫操作失敗,則當前高速緩存條目將保留一個Lock對象,并且無法回滾到其先前的Item狀態。 由于這個原因,鎖必須超時,以允許將緩存條目替換為實際的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屬性,否則默認超時為60秒:
final String timeout = properties.getProperty(CACHE_LOCK_TIMEOUT_PROPERTY,Integer.toString( DEFAULT_CACHE_LOCK_TIMEOUT ) );以下測試將模擬失敗的數據庫事務,因此我們可以觀察到READ_WRITE緩存如何僅在超時閾值到期后才允許寫入。 首先,我們將降低超時值,以減少緩存凍結時間:
properties.put("net.sf.ehcache.hibernate.cache_lock_timeout", String.valueOf(250));我們將使用自定義攔截器手動回滾當前正在運行的事務:
@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);}}); }運行此測試將生成以下輸出:
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- 第一個事務嘗試更新實體,因此在提交事務之前,關聯的第二級緩存條目已被鎖定。
- 第一個事務失敗,它被回滾
- 持有鎖,因此接下來的兩個連續事務將進入數據庫,而不用當前已加載的數據庫實體狀態替換Lock條目
- 在Lock超時期限到期后,第三筆交易最終可以用Item緩存條目替換Lock (保持實體分解為水合狀態 )
結論
READ_WRITE并發策略具有直寫式緩存機制的優點,但是您需要了解它的內部工作原理,才能確定它是否適合您當前的項目數據訪問要求。
對于繁重的寫爭用方案,鎖定結構將使其他并發事務進入數據庫,因此您必須確定同步高速緩存并發策略是否更適合這種情況。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/05/how-does-hibernate-read_write-cacheconcurrencystrategy-work.html
總結
以上是生活随笔為你收集整理的Hibernate READ_WRITE CacheConcurrencyStrategy如何工作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUBA平台–新的Java企业应用程序框
- 下一篇: 安卓数字密码忘了怎么解(安卓数字密码)