Coherence Step by Step 第三篇 缓存(四) 缓存数据源(翻译)
本章介绍了用Coherence作为临时的system-of-record来缓存数据源。本篇包含了例子和实现的注意事项。
1 缓存数据源概述
Coherence 支持透明的读/写任何数据源的缓存,包含数据库,web服务,套装软件和文件系统;然而,数据库是最常用的用例。简要的说,数据库是用来描述任何back-end数据源。有效果的缓存必须都支持密集的只读和读写操作,并且对于读写操作,缓存和数据库必须保持完全同步。为了完成数据源的缓存,Coherence支持 Read-Through, Write-Through, Refresh-Ahead and Write-Behind 缓存。
NOTE:Read-through/write-through缓存(和变体) 是只用在Partitioned(Distributed) cache拓扑(和货站的,Near Cache),Local caches支持这功能的自己。Replicated和Optimistic 缓存不能被使用。
1.1 Pluggable Cache Store
CacheStore 是一个指定应用的适配器,用来连接一个缓存到基本数据源。CacheStore的实现通过使用一个数据访问机制来获取数据源(例如,Hibernate,Toplink Essentials, JPA, application-specific JDBC calls, another application, mainframe, another cache, 等)。CacheStore知道如何创建一个java对象来实现从数据源检索数据,映射和写一个对象到数据源,从数据源擦出一个对象。
数据源连接策略和数据源-应用-对象的映射信息是针对数据源架构,应用程序类布局,和操作环境。因此,映射信息必须由应用开发者来提供,以CacheStore实现的形式。
1.2 Read-Through Caching
当应用程序想缓存请求一条entry,例如key x,并且x不在缓存中,Coherence 会自动委托给CacheStore,请求它来从基础数据源中加载x。如果x在数据源中存在,CacheStore加载它,返回给Coherence,然后Coherence将它放在缓存中为将来使用,最后返回x给请求它的应用程序的代码。这个叫做Read-Through缓存。Refresh-Ahead Cache功能可以更加提升读取性能(通过减少感知延迟)。
1.3 Write-Through Caching
Coherence用两种不同的方式来处理更新到数据源,第一个是Write-Through。这个例子,当应用程序更新一条在缓存中的数据(是调用put(...)来改变缓存entry),直到Coherence运行了CacheStore并成功存储数据到基础数据源才算操作完成。这个完全不会提升写的性能,因为你仍然使用有延时的方式写数据源。提升写的性能建议使用Write-Behind Cache 功能。
1.4 Write-Behind Caching
在Write-Behind的场景中,修改缓存条目是在配置的延迟值后异步的写入数据源,可能是10秒,20分钟,一天,一个星期或者是更久。注意,这个只用于对缓存的插入和更新。缓存条目从数据源移除是同步的。Write-Behind缓存,Coherence维护者一个需要在数据源执行更新的write-behind数据队列。当应用程序更新缓存中的X,X被添加在write-behind队列(如果不存在;否则就替换它),然后在指定的write-behind延迟以后,COherence调用了CacheStore,用最新的X的状态来更新基础数据源,注意write-behind延迟是相对于第一个一系列的修改--换句话说,在数据源的数据从未落后于缓存超过wirte-behind延迟的这个时间。
结果是"一次读取和在配置好的时间间隔写"的场景,有四个主要的益处,对于这个架构体系的类型:
- 应用程序提升了性能,因为用户不用等待数据写入基础数据源。(数据延迟写入,并且通过其他线程)
- 应用程序经历了彻底降低数据库的负载:由于减少了大量的读和写操作,数据库的负载也是同样。和其他缓存方法一样,通过缓存,读取变少了。写,这个通常是最昂贵的操作,也减少了,因为针对同一个对象的多个变化使用write-behind是合并并且只写一次到数据源 ("write-coalescing")。此外,对多个缓存条目的写可能被合并成一个数据库事务 ("write-combining")。如果用CacheStore.storeAll()方法。
- 应用程序多少有点隔绝数据库失效的情况:Write-Behind特性能够被这样配置,失败的写入导致对象重新被请求写。如果应用程序正在使用的数据时在Coherence的缓存里,那么应用程序能够继续操作,而不用数据库是up状态的。使用Coherence Partitioned Cache,这很容易做到,只要将所有的缓存分区到所有的participating cluster 节点(开启了local-storage),允许庞大的缓存。
- 线性可扩展:要应用程序处理更多的并发用户,你只需要增加cluster的节点即可;影响数据库的负载可以用增加write-behind间隔来调节。
1.4.1 Write-Behind Requirements
要启用write-behind缓存只是一个简单的设置配置的调整,确保write-behind如期望的那样工作要根复杂。特别的,应用程序的设计必须解决几个预先设计问题。
Write-Behind缓存的最直接的含义是发生在缓存事务之外的数据库更新;就是缓存的事务通常在数据库事务开始之前完成。这意味着数据库事务永远不是失败;如果不能保存,那么必须保证可以回滚。
write-behind可能重新请求数据库更新,参照完整性约束必须允许无序的更新。概念上的,这类似于使用ISAM-style storage的数据库(基于主键的访问,保存没有更新冲突)。如果其他的应用程序共享数据库,这将引入新的挑战--没有其他方式保证write-behind事务和外部的更新不发生冲突。这意味着write-behind冲突必须通过人家的操作来启发式的处理或者手动调整来增强。
根据鹰眼,映射每个缓存条目更新到逻辑数据库的事务的比较理想的,这能保证简单的数据库事务。
因为write-behind 有效的使得缓存system-of-record(直到write-behind 队列被写进磁盘),业务规则必须允许cluster-durable(而不是disk-durable),数据存储和事务。
1.5 Refresh-Ahead Caching
在Refresh-Ahead 的场景,Coherence允许开发者配置缓存,能够在它失效之前自动和异步的从cache loader重载最近访问过的缓存条目。结果是一个平凡被访问的条目进入缓存后,应用程序没有感觉到对于一个千载的减慢cache store的读取的影响,当这个条目由于过期而重新加载时。异步的属性只会在足够接近它的国企时间时候被访问时触发--如果对象在它的过期时间之后,Coherence会从cache store执行同步的读取来刷新他的值。
refresh-ahead 时间表示的是条目的过期时间的百分比例如,假设cache中设置的条目的过期时间是60秒,refresh-ahead因素设置为0.5.如果cache对象在60秒后被访问,Coherence从cache store执行同步读取来刷新值。然后,如果对于一个条目的请求执行是超过30小于60秒,cache中现在的值会被返回,并且coherence 调度一个异步的从cache store中重新加载。
Refresh-ahead是特别有用的,如果对象被大量的用户访问。缓存中的值始终保持最新,避免了由于从cache store过度重新加载导致的延迟。
refresh-ahead 因素的值通过在coherence-cache-config.xml文件的<read-write-backing-map-scheme>元素的子元素<refresh-ahead-factor>指定。Refresh-ahead假定你也在缓存中为条目设置过期时间。
下面的例子对local cache的条目配置了一个refresh-ahead因素为0.5,过期时间是20秒,如果条目的访问在它过期时间的10秒内,那么会异步的从cache store重新加载。
<distributed-scheme> <scheme-name>categories-cache-all-scheme</scheme-name> <service-name>DistributedCache</service-name> <backing-map-scheme> <read-write-backing-map-scheme> <scheme-name>categoriesLoaderScheme</scheme-name> <internal-cache-scheme> <local-scheme> <scheme-ref>categories-eviction</scheme-ref> </local-scheme> </internal-cache-scheme> <cachestore-scheme> <class-scheme> <class-name> com.demo.cache.coherence.categories.CategoryCacheLoader </class-name> </class-scheme> </cachestore-scheme> <refresh-ahead-factor>0.5</refresh-ahead-factor> </read-write-backing-map-scheme> </backing-map-scheme> <autostart>true</autostart> </distributed-scheme> <local-scheme> <scheme-name>categories-eviction</scheme-name> <expiry-delay>20s</expiry-delay> </local-scheme>
2.选择一个缓存策略
这一节介对照和比较了几个缓存策略的益处。
2.1 Read-Through/Write-Through 对 Cache-Aside
在cluster环境中,对于cache-aside模式有两个常用的方法。一个包含检查缓存溢漏,然后查询数据库,填充缓存,然后程序继续处理。这个可能导致多次的数据库访问,如果此时有不同的应用现成执行处理。二选一,程序也可以执行双重检查锁(这个操作是对缓存条目原子的检查)。然而,结果是大量的开销花费在缓存溢漏和数据更新上(一个cluster锁,额外的读,cluster的解锁,知道10个额外的网络条数,或者6~8毫秒在通常的gigabit以太网络链接,加上额外的处理花费和一条缓存条目锁定时间的增减)。
使用inline 缓存,entry只被锁定2个网络跳数的时间(数据时被复制到为容错的备份服务器)。另外,锁被分区拥有者所维护。此外,应用程序代码完全万里cache server,意味着只是一个被控制的节点子集直接访问数据库(导致更多的可预测的加载和安全性)。此外,这将缓存客户端从数据库逻辑中分离。
2.2 Refresh-Ahead 对Read-Through
Refresh-Ahead 和read-Through相比较减少了延迟,但是只有当缓存能够准确的预测到哪个缓存想可能在将来会被用到。如果预测完全的正确,refresh-ahead提供了减少延迟和没有更多的负载。高错误率的预测,提升了吞吐率的影响(如更多的不希望的请求被发送给数据库)-可能甚至在延迟上有消极的影响,数据库开始拖累请求处理。
2.3 Write-behind 对Write-Through
如果write-behind缓存的需求能够被满足,和write-through相比,write-behind 缓存可以实现相当高的吞吐率和减少延迟。另外write-behind缓存降低了数据库的负载(更少的写),和在缓存服务器上的(减少了cache值的反序列化)。
3.创建CacheStore实现
为了插入一个CacheStore 模块,指定CacheStore实现类的名字,用distributed-scheme,backing-map-scheme,cachestore-shceme,或者read-write-backing-map-scheme,缓存配置元素。
read-write-backing-map-scheme配置了一个com.tangosol.net.cache.ReadWriteBackingMap。这个backing map由两个关键的元素组成:一个内部的映射,它正是的缓存数据,和一个CacheStore模块,它和数据库互动。
下面的例子说明了一个缓存配置,指定了CacheStore模块。<init-params>元素报了一个请求列表的参数,传递给CacheStore构造器。 {cache-name} 配置宏命令是用来传递缓存名给CacheStore的实现,允许它映射到数据库的表。完整可用的宏命令的表,请看
"Using Parameter Macros".
关于配置write-behind和refresh0ahead的更多详细信息,看read-write-backing-map-scheme,注意 write-batch-factor
, refresh-ahead-factor
, write-requeue-threshold
, and rollback-cachestore-failures
元素。
<?xml version="1.0"?> <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config" xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"> <caching-scheme-mapping> <cache-mapping> <cache-name>com.company.dto.*</cache-name> <scheme-name>distributed-rwbm</scheme-name> </cache-mapping> </caching-scheme-mapping> <caching-schemes> <distributed-scheme> <scheme-name>distributed-rwbm</scheme-name> <backing-map-scheme> <read-write-backing-map-scheme> <internal-cache-scheme> <local-scheme/> </internal-cache-scheme> <cachestore-scheme> <class-scheme> <class-name>com.company.MyCacheStore</class-name> <init-params> <init-param> <param-type>java.lang.String</param-type> <param-value>{cache-name}</param-value> </init-param> </init-params> </class-scheme> </cachestore-scheme> </read-write-backing-map-scheme> </backing-map-scheme> </distributed-scheme> </caching-schemes> </cache-config>
NOTE:
线程数量:使用CacheStore模块实质上增加了cache service 线程的消耗(即使最快的数据库选择也是比在in-memory结构中的更新要慢很多歌数量级)。因此,缓存服务的线程数量必须增加(通常是10-100的范围)。值得注意的是线程池不足的最明显的征兆就是缓存请求的延迟增加(没有响应backing database的行为)。
5.CacheStore 例子
这个章节提供了con.tangosol.net.cache.CacheStore接口的基本实现。例子中的实现使用了jdbc作为数据库的链接,没有使用批量操作。完整的实现将使用connection pool,并且,如果使用write-behind,要为bulk JDBC 插入和更新实现CacheStore.storeAll()。
package com.tangosol.examples.coherence; import com.tangosol.net.cache.CacheStore; import com.tangosol.util.Base; import java.sql.DriverManager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * An example implementation of CacheStore * interface. * * @author erm 2003.05.01 */ public class DBCacheStore extends Base implements CacheStore { // ----- constructors --------------------------------------------------- /** * Constructs DBCacheStore for a given database table. * * @param sTableName the db table name */ public DBCacheStore(String sTableName) { m_sTableName = sTableName; configureConnection(); } /** * Set up the DB connection. */ protected void configureConnection() { try { Class.forName("org.gjt.mm.mysql.Driver"); m_con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD); m_con.setAutoCommit(true); } catch (Exception e) { throw ensureRuntimeException(e, "Connection failed"); } } // ---- accessors ------------------------------------------------------- /** * Obtain the name of the table this CacheStore is persisting to. * * @return the name of the table this CacheStore is persisting to */ public String getTableName() { return m_sTableName; } /** * Obtain the connection being used to connect to the database. * * @return the connection used to connect to the database */ public Connection getConnection() { return m_con; } // ----- CacheStore Interface -------------------------------------------- /** * Return the value associated with the specified key, or null if the * key does not have an associated value in the underlying store. * * @param oKey key whose associated value is to be returned * * @return the value associated with the specified key, or * <tt>null</tt> if no value is available for that key */ public Object load(Object oKey) { Object oValue = null; Connection con = getConnection(); String sSQL = "SELECT id, value FROM " + getTableName() + " WHERE id = ?"; try { PreparedStatement stmt = con.prepareStatement(sSQL); stmt.setString(1, String.valueOf(oKey)); ResultSet rslt = stmt.executeQuery(); if (rslt.next()) { oValue = rslt.getString(2); if (rslt.next()) { throw new SQLException("Not a unique key: " + oKey); } } stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Load failed: key=" + oKey); } return oValue; } /** * Store the specified value under the specific key in the underlying * store. This method is intended to support both key/value creation * and value update for a specific key. * * @param oKey key to store the value under * @param oValue value to be stored * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void store(Object oKey, Object oValue) { Connection con = getConnection(); String sTable = getTableName(); String sSQL; // the following is very inefficient; it is recommended to use DB // specific functionality that is, REPLACE for MySQL or MERGE for Oracle if (load(oKey) != null) { // key exists - update sSQL = "UPDATE " + sTable + " SET value = ? where id = ?"; } else { // new key - insert sSQL = "INSERT INTO " + sTable + " (value, id) VALUES (?,?)"; } try { PreparedStatement stmt = con.prepareStatement(sSQL); int i = 0; stmt.setString(++i, String.valueOf(oValue)); stmt.setString(++i, String.valueOf(oKey)); stmt.executeUpdate(); stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Store failed: key=" + oKey); } } /** * Remove the specified key from the underlying store if present. * * @param oKey key whose mapping is to be removed from the map * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void erase(Object oKey) { Connection con = getConnection(); String sSQL = "DELETE FROM " + getTableName() + " WHERE id=?"; try { PreparedStatement stmt = con.prepareStatement(sSQL); stmt.setString(1, String.valueOf(oKey)); stmt.executeUpdate(); stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Erase failed: key=" + oKey); } } /** * Remove the specified keys from the underlying store if present. * * @param colKeys keys whose mappings are being removed from the cache * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void eraseAll(Collection colKeys) { throw new UnsupportedOperationException(); } /** * Return the values associated with each the specified keys in the * passed collection. If a key does not have an associated value in * the underlying store, then the return map does not have an entry * for that key. * * @param colKeys a collection of keys to load * * @return a Map of keys to associated values for the specified keys */ public Map loadAll(Collection colKeys) { throw new UnsupportedOperationException(); } /** * Store the specified values under the specified keys in the underlying * store. This method is intended to support both key/value creation * and value update for the specified keys. * * @param mapEntries a Map of any number of keys and values to store * * @throws UnsupportedOperationException if this implementation or the * underlying store is read-only */ public void storeAll(Map mapEntries) { throw new UnsupportedOperationException(); } /** * Iterate all keys in the underlying store. * * @return a read-only iterator of the keys in the underlying store */ public Iterator keys() { Connection con = getConnection(); String sSQL = "SELECT id FROM " + getTableName(); List list = new LinkedList(); try { PreparedStatement stmt = con.prepareStatement(sSQL); ResultSet rslt = stmt.executeQuery(); while (rslt.next()) { Object oKey = rslt.getString(1); list.add(oKey); } stmt.close(); } catch (SQLException e) { throw ensureRuntimeException(e, "Iterator failed"); } return list.iterator(); } // ----- data members --------------------------------------------------- /** * The connection. */ protected Connection m_con; /** * The db table name. */ protected String m_sTableName; /** * Driver class name. */ private static final String DB_DRIVER = "org.gjt.mm.mysql.Driver"; /** * Connection URL. */ private static final String DB_URL = "jdbc:mysql://localhost:3306/CacheStore"; /** * User name. */ private static final String DB_USERNAME = "root"; /** * Password. */ private static final String DB_PASSWORD = null; }
6. Controllable CacheStore 例子
这个章节要说明的是controllable cache store的实现。这个场景下,应用程序能够控制合适写更新到cache store。这个场景的最通常的用例就是在启动的时候,从data store填充数据到缓存。在启动时,没有需求将缓存中的值写入data store。任何的尝试都是自愿的浪费。
下面例子中的Main.java 文件,用两种不同的方法来和controllable cache store交互。
- 使用controllable cache启用和关闭cache store。这个通过ControllableCacheStore1类来说明。
- 使用CacheStoreAware接口来表示将对象添加进缓存而不需要存储。通过ControllableCacheStore2类来展示。
ControllableCacheStore1和ControllableCacheStore1都继承了com.tangosol.net.cache.AbstractCacheStore类。这个帮助类提供了对storeAll和eraseAll操作的没有优化的实现。
CacheStoreAware.java文件是一个借口,它能够展示一个对象被添加进缓存而不存到数据库。
下面的例子提供了Main.java接口的列表。
import com.tangosol.net.CacheFactory; import com.tangosol.net.NamedCache; import com.tangosol.net.cache.AbstractCacheStore; import com.tangosol.util.Base; import java.io.Serializable; import java.util.Date; public class Main extends Base { /** * A cache controlled CacheStore implementation */ public static class ControllableCacheStore1 extends AbstractCacheStore { public static final String CONTROL_CACHE = "cachestorecontrol"; String m_sName; public static void enable(String sName) { CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.TRUE); } public static void disable(String sName) { CacheFactory.getCache(CONTROL_CACHE).put(sName, Boolean.FALSE); } public void store(Object oKey, Object oValue) { Boolean isEnabled = (Boolean) CacheFactory.getCache(CONTROL_CACHE).get(m_sName); if (isEnabled != null && isEnabled.booleanValue()) { log("controllablecachestore1: enabled " + oKey + " = " + oValue); } else { log("controllablecachestore1: disabled " + oKey + " = " + oValue); } } public Object load(Object oKey) { log("controllablecachestore1: load:" + oKey); return new MyValue1(oKey); } public ControllableCacheStore1(String sName) { m_sName = sName; } } /** * a valued controlled CacheStore implementation that * implements the CacheStoreAware interface */ public static class ControllableCacheStore2 extends AbstractCacheStore { public void store(Object oKey, Object oValue) { boolean isEnabled = oValue instanceof CacheStoreAware ? !((CacheStoreAware) oValue).isSkipStore() : true; if (isEnabled) { log("controllablecachestore2: enabled " + oKey + " = " + oValue); } else { log("controllablecachestore2: disabled " + oKey + " = " + oValue); } } public Object load(Object oKey) { log("controllablecachestore2: load:" + oKey); return new MyValue2(oKey); } } public static class MyValue1 implements Serializable { String m_sValue; public String getValue() { return m_sValue; } public String toString() { return "MyValue1[" + getValue() + "]"; } public MyValue1(Object obj) { m_sValue = "value:" + obj; } } public static class MyValue2 extends MyValue1 implements CacheStoreAware { boolean m_isSkipStore = false; public boolean isSkipStore() { return m_isSkipStore; } public void skipStore() { m_isSkipStore = true; } public String toString() { return "MyValue2[" + getValue() + "]"; } public MyValue2(Object obj) { super(obj); } } public static void main(String[] args) { try { // example 1 NamedCache cache1 = CacheFactory.getCache("cache1"); // disable cachestore ControllableCacheStore1.disable("cache1"); for(int i = 0; i < 5; i++) { cache1.put(new Integer(i), new MyValue1(new Date())); } // enable cachestore ControllableCacheStore1.enable("cache1"); for(int i = 0; i < 5; i++) { cache1.put(new Integer(i), new MyValue1(new Date())); } // example 2 NamedCache cache2 = CacheFactory.getCache("cache2"); // add some values with cachestore disabled for(int i = 0; i < 5; i++) { MyValue2 value = new MyValue2(new Date()); value.skipStore(); cache2.put(new Integer(i), value); } // add some values with cachestore enabled for(int i = 0; i < 5; i++) { cache2.put(new Integer(i), new MyValue2(new Date())); } } catch(Throwable oops) { err(oops); } finally { CacheFactory.shutdown(); } } }
下面的例子提供了CacheStoreAware.java接口的列表
public interface CacheStoreAware { public boolean isSkipStore(); }
7.实现的注意事项
当实现CacheStore时,请始终记住以下几点。
7.1 Idempotency
所有的CacheStore操作应该被设计成idempotent(就是,重复,但是没有副作用)。如Write-through和write-behind缓存,允许Coherence提供一个低消费的,容错的部分更新,通过重新尝试更新缓存的数据库部分,当故障转移时处理。如Write-behind缓存,idemprtency 也允许Coherence 结合多个缓存更新到一个单独的CacheStore请求,不影响数据完整性。
应用程序有一个需求是write-behind缓存但是要避免write-combining(例如,审计的原因),应该创建一个版本化的缓存键(例如,通过用一个序列化id合并自然主键)。
7.2 Write-Through 限制
Coherence 不支持两段式的横穿多个CacheStore实例的CacheStore操作。换句话说,如果两个缓存条目更新,触发调用在分开的缓存服务器上的CacheStore,这个可能会导致一个数据库更新成功,另一个失败,这个也许最好使用一个应用服务器事务管理器的cache-aside结构(更新缓存的数据库,用一个两个组件的单独事务)。在许多案例中,有可能涉及数据库方案来防止逻辑提交失败(但是明显不是服务器失败)。Write-behind缓存避免了这个问题,puts不会影响数据库的行为(潜在的问题在涉及的时候已经处理了)。
7.3 缓存队列
Cache队列只在缓存的data store上操作,并不触发CacheStore来加载任何缺少的数据。因此,应用程序查询CacheStore backed caches应该确保所有需要查询的数据已经被预加载了。为了提高效率,多数的大量加载操作应该在程序启动的时候完成,通过从数据库直接流动数据集到缓存(通过使用NamedCache.putAll()毗邻加载数据块进混村。加载进程必须使用"Controllable CacheStore"模式来禁止通知更新返回给数据库。CacheStore可以使用Invocation service来控制(通过cluster发送代理来修改每个jvm中的local flag),或者通过设置Replicated Cache(不同的缓存服务)的值,在每个CacheStore方法请求中读取它(与常用数据库操作,最小化开销)。自定义的MBean 也能被使用,用Coherence的cluster JMX 设备是一个简单的任务。
7.4 Re-entrant Calls
CacheStore不能回调给托管缓存服务。这个包含了ORM解决方案,它内部的应用Coherence 缓存服务。注意的是允许调用另一个缓存服务,尽管应该小心,鼻渊深层的嵌套调用(每个称为"消费"一个缓存服务进程,能够会导致死锁,如果缓存服务的线程池耗尽)。
7.5 Cache Server Classpath
缓存条目的类必须在cache server的classpath中,cache服务器必须序列化-反序列化缓存条目来和CacheStore 模块交互。
7.6 CacheStore Collection Operation
CacheStore.storeAll 方法最有可能被使用,如果缓存缓存配置了write-behind和<write-batch-factor>。CacheLoader.loadAl方法也被Coherence使用。基于相似的理由,首次使用时候,需要开启refresh-ahead。
7.7 Connection Pools
数据库连接应该能够从container连接池中找到,或者使用本地线程 lazy-initialization模式。作为专业的缓存服务器,经常背书在没有管理容器,后者可能是最有有吸引力的选择(尽管缓存服务京城之的大小应该被约束,避免过多的同步数据库连接)。