Ehcache(2.9.x) - API Developer Guide, Write-Through and Write-Behind Caches
About Write-Through and Write-Behind Caches
Write-through caching is a caching pattern where writes to the cache cause writes to an underlying resource. The cache acts as a facade to the underlying resource. With this pattern, it often makes sense to read through the cache too.
Write-behind caching uses the same client API; however, the write happens asynchronously.
While file systems or a web-service clients can underlie the facade of a write-through cache, the most common underlying resource is a database. To simplify the discussion, we will use the database as the example resource.
Potential Benefits of Write-Behind
The major benefit of write-behind is database offload. This can be achieved in a number of ways:
- Time shifting - moving writes to a specific time or time interval. For example, writes could be batched up and written overnight, or at 5 minutes past the hour, to avoid periods of peak contention.
- Rate limiting - spreading writes out to flatten peaks. Say a point-of-sale (POS) network has an end-of-day procedure where data gets written to a central server. All POS nodes in the same time zone will write all at once. A very large peak will occur. Using rate limiting, writes could be limited to 100 TPS to whittle down the queue of writes over several hours
- Conflation - consolidate writes to create fewer transactions. For example, a value in a database row is updated by 5 writes, incrementing it from 10 to 20 to 31 to 40 to 45. Using conflation, the 5 transactions are replaced by one to update the value from 10 to 45.
These benefits must be weighed against the limitations and constraints imposed.
Transaction Boundaries
If the cache participates in a JTA transaction, which means it is an XAResource, then the cache can be made consistent with the database. A write to the database, and a commit or rollback, happens with the transaction boundary. In write-behind, the write to the resource happens after the write to the cache. The transaction boundary is the write to the outstanding queue, not the write behind. In write-through mode, commit can get called and both the cache and the underlying resource can get committed at once. Because the database is being written to outside of the transaction, there is always a risk that a failure on the eventual write will occur. While this can be mitigated with retry counts and delays, compensating actions may be required.
Limitations & Constraints of Write-Behind
Transaction Boundaries
If the cache participates in a JTA transaction, which means it is an XAResource, then the cache can be made consistent with the database. A write to the database, and a commit or rollback, happens with the transaction boundary. In write-behind, the write to the resource happens after the write to the cache. The transaction boundary is the write to the outstanding queue, not the write behind. In write-through mode, commit can get called and both the cache and the underlying resource can get committed at once. Because the database is being written to outside of the transaction, there is always a risk that a failure on the eventual write will occur. While this can be mitigated with retry counts and delays, compensating actions may be required.
Time Delay
The obvious implication of asynchronous writes is that there is a delay between when the cache is updated and when the database is updated. This introduces an inconsistency between the cache and the database, where the cache holds the correct value and the database will be eventually consistent with the cache.
The data passed into the CacheWriter methods is a snapshot of the cache entry at the time of the write to operation. A read against the database will result in incorrect data being loaded.
Applications Tolerant of Inconsistency
The application must be tolerant of inconsistent data. The following examples illustrate this requirement:
- The database is logging transactions and only appends are done.
- Reading is done by a part of the application that does not write, so there is no way that data can be corrupted. The application is tolerant of delays. For example, a news application where the reader displays the articles that are written.
Note if other applications are writing to the database, then a cache can often be inconsistent with the database.
Time Synchronization Across Nodes
Ideally node times should be synchronized. The write-behind queue is generally written to the underlying resource in timestamp order, based on the timestamp of the cache operation, although there is no guaranteed ordering. The ordering will be more consistent if all nodes are using the same time. This can easily be achieved by configuring your system clock to synchronize with a time authority using Network Time Protocol.
No Ordering Guarantees
The items on the write-behind queue are generally in order, but this isn't guaranteed. In certain situations, the items can be processed out of order. Additionally, when batching is used, write and delete collections are aggregated separately and can be processed inside the CacheWriter in a different order than the order that was used by the queue. Your application must be tolerant of item reordering or you need to compensate for this in your implementation of the CacheWriter. Possible examples are:
Working with versioning in the cache elements.
You may have to explicitly version elements. Auto-versioning is off by default and is effective only for unclustered MemoryStore caches. Distributed caches or caches that use off-heap or disk stores cannot use auto-versioning. To enable auto-versioning, set the system property net.sf.ehcache.element.version.auto (it is false by default). Note that if this property is turned on for one of the ineligible cache types, auto-versioning will silently fail.
- Verifications with the underlying resource to check if the scheduled write-behind operation is still relevant.
Introductory Video
Using a Combined Read-Through and Write-Behind Cache
For applications that are not tolerant of inconsistency, the simplest solution is for the application to always read through the same cache that it writes through. Provided all database writes are through the cache, consistency is guaranteed.
The following aspects of read-through with write-behind should be considered:
Lazy Loading
The entire data set does not need to be loaded into the cache on startup. A read-through cache uses a CacheLoader that loads data into the cache on demand. In this way the cache can be populated lazily.
Caching of a Partial Dataset
If the entire dataset cannot fit in the cache, then some reads will miss the cache and fall through to the CacheLoader which will in turn hit the database. If a write has occurred but has not yet hit the database due to write-behind, then the database will be inconsistent. The simplest solution is to ensure that the entire dataset is in the cache. This then places some implications on cache configuration in the areas of expiry and eviction.
Eviction
Eviction or flushing of elements, occurs when the maximum elements for the cache have been exceeded. Be sure to size the cache appropriately to avoid eviction or flushing. See “Sizing Storage Tiers” in the Configuration Guide for Ehcache.
Expiry
Write-Behind Sample Application
A sample web application for a raffle is available, which fully demonstrates how to use write behind. You can also check out the Ehcache Raffle application, that demonstrates Cache Writers and Cache Loaders from github.com.
Configuring a Cache Writer
There are many configuration options for a cache writer. For a full list of configuration properties, see the Javadoc for the CacheWriterConfiguration class.
Below is an example of how to configure the cache writer in XML:
<cache name="writeThroughCache1" ... >
<cacheWriter writeMode="write-behind" maxWriteDelay="8" rateLimitPerSecond="5"
writeCoalescing="true" writeBatching="true" writeBatchSize="20"
retryAttempts="2" retryAttemptDelaySeconds="2">
<cacheWriterFactory class="com.company.MyCacheWriterFactory"
properties="just.some.property=test; another.property=test2"
propertySeparator=";"/>
</cacheWriter>
</cache>
Further examples:
<cache name="writeThroughCache2" ... >
<cacheWriter/>
</cache>
<cache name="writeThroughCache3" ... >
<cacheWriter writeMode="write-through" notifyListenersOnException="true"
maxWriteDelay="30" rateLimitPerSecond="10" writeCoalescing="true"
writeBatching="true" writeBatchSize="8" retryAttempts="20"
retryAttemptDelaySeconds="60"/>
</cache>
<cache name="writeThroughCache4" ... >
<cacheWriter writeMode="write-through" notifyListenersOnException="false"
maxWriteDelay="0" rateLimitPerSecond="0" writeCoalescing="false"
writeBatching="false" writeBatchSize="1" retryAttempts="0"
retryAttemptDelaySeconds="0">
<cacheWriterFactory class="net.sf.ehcache.writer.WriteThroughTestCacheWriterFactory"/>
</cacheWriter>
</cache>
<cache name="writeBehindCache5" ... >
<cacheWriter writeMode="write-behind" notifyListenersOnException="true"
maxWriteDelay="8" rateLimitPerSecond="5" writeCoalescing="true"
writeBatching="false" writeBatchSize="20"
retryAttempts="2" retryAttemptDelaySeconds="2">
<cacheWriterFactory class="net.sf.ehcache.writer.WriteThroughTestCacheWriterFactory"
properties="just.some.property=test; another.property=test2"
propertySeparator=";"/>
</cacheWriter>
</cache>
As shown below, this configuration can also be achieved through the Cache constructor in Java:
Cache cache = new Cache(
new CacheConfiguration("cacheName", 10)
.cacheWriter(new CacheWriterConfiguration()
.writeMode(CacheWriterConfiguration.WriteMode.WRITE_BEHIND)
.maxWriteDelay(8)
.rateLimitPerSecond(5)
.writeCoalescing(true)
.writeBatching(true)
.writeBatchSize(20)
.retryAttempts(2)
.retryAttemptDelaySeconds(2)
.cacheWriterFactory(new CacheWriterConfiguration.CacheWriterFactoryConfiguration()
.className("com.company.MyCacheWriterFactory")
.properties("just.some.property=test; another.property=test2")
.propertySeparator(";")
)
)
);
Instead of relying on a CacheWriterFactoryConfiguration to create a CacheWriter, it is also possible to explicitly register a CacheWriter instance from within Java code. This allows you to refer to local resources like database connections or file handles.
Cache cache = manager.getCache("cacheName");
MyCacheWriter writer = new MyCacheWriter(jdbcConnection);
cache.registerCacheWriter(writer);
CacheWriterFactory Attributes
The CacheWriterFactory supports the following attributes:
All modes
- write-mode [write-through | write-behind] - Whether to run in write-behind or write-through mode. The default is write-through.
write-through mode only
- notifyListenersOnException - Whether to notify listeners when an exception occurs on a store operation. Defaults to false. If using cache replication, set this attribute to “true” to ensure that changes to the underlying store are replicated.
write-behind mode only
writeBehindMaxQueueSize - The maximum number of elements allowed per queue, or per bucket (if the queue has multiple buckets). “0” means unbounded (default). When an attempt to add an element is made, the queue size (or bucket size) is checked, and if full then the operation is blocked until the size drops by one. Note that elements or a batch currently being processed (and coalesced elements) are not included in the size value. Programmatically, this attribute can be set with:
net.sf.ehcache.config.CacheWriterConfiguration.setWriteBehindMaxQueueSize()
writeBehindConcurrency - The number of thread-bucket pairs on the node for the given cache (default is 1). Each thread uses the settings configured for write-behind. For example, if rateLimitPerSecond is set to 100, each thread-bucket pair will perform up to 100 operations per second. In this case, settingwriteBehindConcurrency="4" means that up to 400 operations per second will occur on the node for the given cache. Programmatically, this attribute can be set with:
net.sf.ehcache.config.CacheWriterConfiguration.setWriteBehindConcurrency()
- maxWriteDelaySeconds - The maximum number of seconds to wait before writing behind. Defaults to 0. If set to a value greater than 0, it permits operations to build up in the queue to enable effective coalescing and batching optimizations.
- rateLimitPerSecond - The maximum number of store operations to allow per second.
- writeCoalescing - Whether to use write coalescing. Defaults to false. When set to true, if multiple operations on the same key are present in the write-behind queue, then only the latest write is done (the others are redundant). This can dramatically reduce load on the underlying resource.
- writeBatching - Whether to batch write operations. Defaults to false. If set to true, storeAll and deleteAll will be called rather than store and delete being called for each key. Resources such as databases can perform more efficiently if updates are batched to reduce load.
- writeBatchSize - The number of operations to include in each batch. Defaults to 1. If there are less entries in the write-behind queue than the batch size, the queue length size is used. Note that batching is split across operations. For example, if the batch size is 10 and there were 5 puts and 5 deletes, the CacheWriter is invoked. It does not wait for 10 puts or 10 deletes.
- retryAttempts - The number of times to attempt writing from the queue. Defaults to 1.
- retryAttemptDelaySeconds - The number of seconds to wait before retrying.
API
CacheLoaders are exposed for API use through the cache.getWithLoader(...) method.
CacheWriters are exposed with cache.putWithWriter(...) and cache.removeWithWriter(...) methods. The code below show the method signature for thecache.putWithWriter(...) method. For the complete API, see the Cache Javadoc.
/**
* Put an element in the cache writing through a CacheWriter. If no CacheWriter
* has been set for the cache, then this method has the same effect as
* cache.put().
*
* Resets the access statistics on the element, which would be the case if
* it has previously been gotten from a cache, and is now being put back.
*
* Also notifies the CacheEventListener, if the writer operation succeeds, that:
*
* - the element was put, but only if the Element was actually put.
* - if the element exists in the cache, that an update has occurred, even if
* the element would be expired if it was requested
*
*
* @param element An object. If Serializable it can fully participate in
* replication and the DiskStore.
* @throws IllegalStateException if the cache is not
* {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @throws IllegalArgumentException if the element is null
* @throws CacheException
*/
void putWithWriter(Element element) throws IllegalArgumentException, IllegalStateException, CacheException;
SPI
The write-through SPI is the CacheWriter interface. Implementers perform writes to the underlying resource in their implementation.
/**
* A CacheWriter is an interface used for write-through and write-behind caching
* to an underlying resource.
* <p/>
* If configured for a cache, CacheWriter's methods will be called on a cache
* operation. A cache put will cause a CacheWriter write
* and a cache remove will cause a writer delete.
* <p>
* Implementers should create an implementation which handles storing and
* deleting to an underlying resource.
* </p>
* <h4>Write-Through</h4>
* In write-through mode, the cache operation will occur and the writer
* operation will occur before CacheEventListeners are notified. If the
* write operation fails an exception will be thrown. This can result in
* a cache which is inconsistent with the underlying resource.
* To avoid this, the cache and the underlying resource should be configured
* to participate in a transaction. In the event of a failure,
* a rollback can return all components to a consistent state.
* <p/>
* <h4>Write-Behind</h4>
* In write-behind mode, writes are written to a write-behind queue. They are
* written by a separate execution thread in a configurable
* way. When used with Terracotta Server Array, the queue is highly available.
* In addition, any node in the cluster may perform the write-behind operation.
* <p/>
* <h4>Creation and Configuration</h4>
* CacheWriters can be created using the CacheWriterFactory.
* <p/>
* The manner upon which a CacheWriter is actually called is determined by the
* {@link net.sf.ehcache.config.CacheWriterConfiguration} that is set up
* for a cache using the CacheWriter.
* <p/>
* See the CacheWriter chapter in the documentation for more information on
* how to use writers.
*
* @author Greg Luck
* @author Geert Bevin
* @version $Id: $
*/
public interface CacheWriter {
/**
* Creates a clone of this writer. This method will only be called by
* ehcache before a cache is initialized.
* <p/>
* Implementations should throw CloneNotSupportedException if they do not
* support clone but that will stop them from being used with defaultCache.
*
* @return a clone
* @throws CloneNotSupportedException if the extension could not be cloned.
*/
public CacheWriter clone(Ehcache cache) throws CloneNotSupportedException;
/**
* Notifies writer to initialise themselves.
* <p/>
* This method is called during the Cache's initialise method after it has
* changed its status to alive. Cache operations are legal in this method.
*
* @throws net.sf.ehcache.CacheException
*/
void init();
/**
* Providers may be doing all sorts of exotic things and need to be able
* to clean up on dispose.
* <p/>
* Cache operations are illegal when this method is called. The cache itself
* is partly disposed when this method is called.
*/
void dispose() throws CacheException;
/**
* Write the specified value under the specified key to the underlying store.
* This method is intended to support both key/value creation and value
* update for a specific key.
*
* @param element the element to be written
*/
void write(Element element) throws CacheException;
/**
* Write the specified Elements to the underlying store. This method is
* intended to support both insert and update.
* If this operation fails (by throwing an exception) after a partial success,
* the convention is that entries which have been written successfully are
* to be removed from the specified mapEntries, indicating that the write
* operation for the entries left in the map has failed or has not been
* attempted.
*
* @param elements the Elements to be written
*/
void writeAll(Collection<Element> elements) throws CacheException;
/**
* Delete the cache entry from the store
*
* @param entry the cache entry that is used for the delete operation
*/
void delete(CacheEntry entry) throws CacheException;
/**
* Remove data and keys from the underlying store for the given collection
* of keys, if present. If this operation fails * (by throwing an exception)
* after a partial success, the convention is that keys which have been erased
* successfully are to be removed from the specified keys, indicating that the
* erase operation for the keys left in the collection has failed or has not
* been attempted.
*
* @param entries the entries that have been removed from the cache
*/
void deleteAll(Collection<CacheEntry> entries) throws CacheException;
/**
* This method will be called whenever an Element couldn't be handled by the
* writer and all of the
* {@link net.sf.ehcache.config.CacheWriterConfiguration#getRetryAttempts()
* retryAttempts} have been tried.
* <p>When batching is enabled, all of the elements in the failing batch will
* be passed to this method.
* <p>Try to not throw RuntimeExceptions from this method. Should an Exception
* occur, it will be logged, but the element will still be lost.
* @param element the Element that triggered the failure, or one of the elements
* in the batch that failed.
* @param operationType the operation we tried to execute
* @param e the RuntimeException thrown by the Writer when the last retry attempt
* was being executed
*/
void throwAway(Element element, SingleOperationType operationType,
RuntimeException e);
}
Monitoring the Size of Write-Behind Queue
Use the method net.sf.ehcache.statistics.LiveCacheStatistics#getWriterQueueLength() to get the length of the queue. This method returns the number of elements on the local queue (in all local buckets) that are waiting to be processed, or -1 if no write-behind queue exists.
Note that elements or a batch currently being processed (and coalesced elements) are not included in the returned value.
Handling Exceptions that Occur After a Writer is Called
Once all retry attempts have been executed, on exception the element (or all elements of that batch) will be passed to the net.sf.ehcache.writer.CacheWriter#throwAway() method. The user can then act one last time on the element that failed to write.
A reference to the last thrown RuntimeException, and the type of operation that failed to execute for the element, are received. Any Exception thrown from that method will simply be logged and ignored. The element will be lost forever. It is important that implementers are careful about proper Exception handling in that last method.
A handy pattern is to use an eternal cache (potentially using a writer, so it is persistent) to store failed operations and their element. Users can monitor that cache and manually intervene on those errors at a later point.
Ehcache(2.9.x) - API Developer Guide, Write-Through and Write-Behind Caches的更多相关文章
- Ehcache(2.9.x) - API Developer Guide, Key Classes and Methods
About the Key Classes Ehcache consists of a CacheManager, which manages logical data sets represente ...
- Ehcache(2.9.x) - API Developer Guide, Cache Eviction Algorithms
About Cache Eviction Algorithms A cache eviction algorithm is a way of deciding which element to evi ...
- Ehcache(2.9.x) - API Developer Guide, Basic Caching
Creating a CacheManager All usages of the Ehcache API start with the creation of a CacheManager. The ...
- Ehcache(2.9.x) - API Developer Guide, Cache Usage Patterns
There are several common access patterns when using a cache. Ehcache supports the following patterns ...
- Ehcache(2.9.x) - API Developer Guide, Searching a Cache
About Searching The Search API allows you to execute arbitrarily complex queries against caches. The ...
- Ehcache(2.9.x) - API Developer Guide, Using Explicit Locking
About Explicit Locking Ehcache contains an implementation which provides for explicit locking, using ...
- Ehcache(2.9.x) - API Developer Guide, Transaction Support
About Transaction Support Transactions are supported in versions of Ehcache 2.0 and higher. The 2.3. ...
- Ehcache(2.9.x) - API Developer Guide, Blocking and Self Populating Caches
About Blocking and Self-Populating Caches The net.sf.ehcache.constructs package contains some applie ...
- Ehcache(2.9.x) - API Developer Guide, Cache Loaders
About Cache Loaders A CacheLoader is an interface that specifies load() and loadAll() methods with a ...
随机推荐
- Spring MVC 的视图转发
Spring MVC 默认采用的是转发来定位视图,如果要使用重定向,可以如下操作 1.使用RedirectView public ModelAndView login(){ RedirectView ...
- [前端JS学习笔记]JavaScript prototype 对象
一.概念介绍 prototype 对象 : 原型对象.在JavaScript中, 每一个对象都继承了另一个对象,后者称为"原型对象". 只有 null 除外,它没有自己的原型对象. ...
- Telnet端口测试
$IP ="220.181.111.142"$Port ="801" Function Port-Test ($IP,$Port){ $Timeout = 10 ...
- VS里面如何设置环境默认的开发语言
- 从零开始学android开发-通过WebService获取今日天气情况
因为本身是在搞.NET方面的东东,现在在学习Android,所以想实现Android通过WebService接口来获取数据,网上很多例子还有有问题的.参考:Android 通过WebService进行 ...
- Codeforces Round #334 (Div. 2) A. Uncowed Forces 水题
A. Uncowed Forces Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/604/pro ...
- Android Developers:在命令行构建和运行
使用Ant构建脚本构建你的应用程序有两种方式:一种用于测试/调试你的引用程序—debug模式—另一种用于构建你最终发布的包-release模式.无论你使用哪种方式构建你的应用程序,它必须在安装在模拟器 ...
- 现在的SEO最须要会点啥
如今的SEO最须要会点啥,会飞天,会遁地,NONONO,不须要你这么流弊,咳咳,不瞎扯.在以往SEO的就是从搜索引擎中获取免费流量.是啊,曾经多好弄啊.而如今在我们不但须要流量还须要把流量进行转换,毕 ...
- Android传感器编程带实例
看了程序人生 网站的 编程高手的编程感悟 深有感触,好像也是一个android 程序员写的,推荐大家也看看.话不多说,还是言归正传吧. 一.前言 我很喜欢电脑,可是笔记本还是太大,笔记本电脑再小还是要 ...
- Java 计算两个日期相差月数
package com.myjava; import java.text.ParseException;import java.text.SimpleDateFormat;import java.ut ...