memcached java客户端 https://github.com/gwhalin/Memcached-Java-Client 在使用中发现对 同一JVM种对相同key的写操作时并发写的,这一点很不好,下面是对 memcached客户端优化使用,参考oscache的机制。
对MemCachedClient 二次封装目的:
优化对同一key的读写操作
对客户端的二次封装:
import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.danga.MemCached.MemCachedClient; /** * memcached客户端优化,确保对相同key的写操作并发执行时只要一次写入memcached * @author mengxiansheng * */ public class CacheTuning { // 创建全局的唯一实例 private static ConcurrentHashMap updateStates = new ConcurrentHashMap(); protected static MemCachedClient memCachedClient = null; private static transient final Log log = LogFactory .getLog(CacheTuning.class); public void setMemCachedClient(MemCachedClient mcc) { this.memCachedClient = mcc; } protected EntryUpdateState getUpdateState(String key) { EntryUpdateState updateState; // Try to find the matching state object in the updating entry map. updateState = (EntryUpdateState) updateStates.get(key); if (updateState == null) { // It's not there so add it. updateState = new EntryUpdateState(); updateStates.put(key, updateState); } else { // Otherwise indicate that we start using it to prevent its // removal // until all threads are done with it. updateState.incrementUsageCounter(); } return updateState; } /** * 释放key对EntryUpdateState实例的使用,当没有线程使用EntryUpdateState实例是,将该实例在map中删除 * * @param state * the state to release the usage of * @param key * the associated key. */ protected void releaseUpdateState(EntryUpdateState state, String key) { int usageCounter = state.decrementUsageCounter(); checkEntryStateUpdateUsage(key, state, usageCounter); } /** * Utility method to check if the specified usage count is zero, and if so * remove the corresponding EntryUpdateState from the updateStates. This is * designed to factor common code. * * Warning: This method should always be called while holding both the * updateStates field and the state parameter * * @throws Exception */ private void checkEntryStateUpdateUsage(String key, EntryUpdateState state, int usageCounter) { // Clean up the updateStates map to avoid a memory leak once no thread // is using this EntryUpdateState instance anymore. if (usageCounter == 0) { log.debug("在更新状态列表中删除:" + key + "\t"); EntryUpdateState removedState = (EntryUpdateState) updateStates .remove(key); if (state != removedState) { if (log.isErrorEnabled()) { try { throw new Exception( "memcached: internal error: removed state [" + removedState + "] from key [" + key + "] whereas we expected [" + state + "]"); } catch (Exception e) { log.error(e); } } } } } /** * 保护型构造方法,不允许实例化! * */ protected CacheTuning() { } /** * 添加一个指定的值到缓存中. * * @param key * @param value * @return */ public boolean putInCache(String key, Object value, long secords) { // set方法:在cache中存储一个指定对象 // add 和replace 方法功能差不多 // add -- 如果不存在 这个key的对象,将会存储一个对象到cache中 // replace --只有当存在指定key对象的时候 会覆盖已有对象 EntryUpdateState updateState = getUpdateState(key); try { boolean ok = memCachedClient.add(key, value, new Date( secords * 1000)); completeUpdate(key); log.debug(key + "添加成功,值:" + value.toString()); return ok; } catch (Exception e) { e.printStackTrace(); this.cancelUpdate(key); } finally { // Make sure we release the usage count for this // EntryUpdateState since we don't use it anymore. If the // current thread started the update, then the counter was // increased by one in startUpdate() log.debug(Thread.currentThread().getId() + " 唤醒:" + key); } return false; } /** * Removes the update state for the specified key and notifies any other * threads that are waiting on this object. This is called automatically by * the {@link #putInCache} method, so it is possible that no * EntryUpdateState was hold when this method is called. * * @param key * The cache key that is no longer being updated. */ protected void completeUpdate(String key) { EntryUpdateState state; state = (EntryUpdateState) updateStates.get(key); if (state != null) { synchronized (state) { int usageCounter = state.completeUpdate(); state.notify(); checkEntryStateUpdateUsage(key, state, usageCounter); } } else { // If putInCache() was called directly (i.e. not as a result of a // NeedRefreshException) then no EntryUpdateState would be found. } } public boolean replace(String key, Object value) { return memCachedClient.replace(key, value); } /** * 在缓存中删除指定的key * * @param key 指定删除的key * * @return <code>true</code>, 如果删除成功 */ public boolean delete(String key) { return memCachedClient.delete(key); } /** * 根据指定的关键字获取对象. * * @param key * @return */ public Object getFromCache(String key) throws NeedsRefreshException { EntryUpdateState updateState = null; updateState = getUpdateState(key); Object obj = memCachedClient.get(key); //log.debug("key:"+key+" 的缓存对象 is null ? "+(obj==null)); if(obj!=null){ return obj; } boolean reload = false; if (obj == null) {// 不存在缓存数据,数据已过期或者第一次访问 try { synchronized (updateState) { if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {// 第一次访问该数据或者前面线程对该数据的更新已取消 updateState.startUpdate();// 开始更新 } else if (updateState.isUpdating()) {// 该数据正在更新 // //其他线程正在更新这个缓存 do { try { updateState.wait(); } catch (InterruptedException e) { } } while (updateState.isUpdating()); if (updateState.isCancelled()) {// 更新被取消 updateState.startUpdate(); } else if (updateState.isComplete()) {// 其他线程完成了对该数据的更新 reload = true; } else { log.error("Invalid update state for cache entry " + key); } } else {// 其他线程已经对该数据更新完成,重新获取数据 reload = true; } } } catch (Exception e) { log.error(e.getMessage(), e.getCause()); } finally { // Make sure we release the usage count for this // EntryUpdateState since we don't use it anymore. If // the current thread started the update, then the // counter was // increased by one in startUpdate() releaseUpdateState(updateState, key);// 释放 对 EntryUpdateState // 的使用 // EntryUpdateState的计数器减一 } } if (reload) { obj = memCachedClient.get(key); } if (obj == null) {// 没有获取到缓存对象,则更新 throw new NeedsRefreshException("需要重新获取:" + key); } return obj; } private void cancelUpdate(String key) { // TODO Auto-generated method stub EntryUpdateState state; if (key != null) { state = (EntryUpdateState) updateStates.get(key); if (state != null) { synchronized (state) { int usageCounter = state.cancelUpdate(); state.notify(); checkEntryStateUpdateUsage(key, state, usageCounter); } } else { if (log.isErrorEnabled()) { log .error("internal error: expected to get a state from key [" + key + "]"); } } } } public boolean putInCache(String key, Object value) { // TODO Auto-generated method stub return this.putInCache(key, value, 0); } }
缓存实体状态类:
/** * 缓存更新状态。 * Holds the state of a Cache Entry that is in the process of being (re)generated. * This is not synchronized; the synchronization must be handled by the calling * classes. * * @author Author: mengxiansheng */ public class EntryUpdateState { /** * 初始化状态 */ public static final int NOT_YET_UPDATING = -1; /** * 正在处理中状态 */ public static final int UPDATE_IN_PROGRESS = 0; /** * 更新完成状态 */ public static final int UPDATE_COMPLETE = 1; /** * 取消更新状态 */ public static final int UPDATE_CANCELLED = 2; /** *初始化状态 */ int state = NOT_YET_UPDATING; /** * 线程计数器,实例可以控制这个计数器,当计数器是0的时候,可以说明缓存实例已经被释放 * 计数器受 EntryStateUpdate实例监听器的保护 * A counter of the number of threads that are coordinated through this instance. When this counter gets to zero, then the reference to this * instance may be released from the Cache instance. * This is counter is protected by the EntryStateUpdate instance monitor. */ private int nbConcurrentUses = 1; /** * 是否等待更新 * 如果是初始化状态,则等待更新 * This is the initial state when an instance this object is first created. * It indicates that a cache entry needs updating, but no thread has claimed * responsibility for updating it yet. */ public boolean isAwaitingUpdate() { return state == NOT_YET_UPDATING; } /** * 更新已经被取消 * The thread that was responsible for updating the cache entry (ie, the thread * that managed to grab the update lock) has decided to give up responsibility * for performing the update. OSCache will notify any other threads that are * waiting on the update so one of them can take over the responsibility. */ public boolean isCancelled() { return state == UPDATE_CANCELLED; } /** * 更新完成 * The update of the cache entry has been completed. */ public boolean isComplete() { return state == UPDATE_COMPLETE; } /** * 正在更新 * The cache entry is currently being generated by the thread that got hold of * the update lock. */ public boolean isUpdating() { return state == UPDATE_IN_PROGRESS; } /** * 取消更新,只能是出于增在更新状态UPDATE_IN_PROGRESS 的可以取消更新 * Updates the state to <code>UPDATE_CANCELLED</code>. This should <em>only<em> * be called by the thread that managed to get the update lock. * @return the counter value after the operation completed */ public int cancelUpdate() { // if (state != UPDATE_IN_PROGRESS) { // throw new IllegalStateException("Cannot cancel cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS"); // } state = UPDATE_CANCELLED; return decrementUsageCounter(); } /** * 完成更新 * Updates the state to <code>UPDATE_COMPLETE</code>. This should <em>only</em> * be called by the thread that managed to get the update lock. * @return the counter value after the operation completed */ public int completeUpdate() { if (state != UPDATE_IN_PROGRESS) { throw new IllegalStateException("Cannot complete cache update - current state (" + state + ") is not UPDATE_IN_PROGRESS"); } state = UPDATE_COMPLETE; return decrementUsageCounter(); } /** * 开始更新,当状态不是初始状态并且不是取消状态,才可以调用此方法,否则报异常 * Attempt to change the state to <code>UPDATE_IN_PROGRESS</code>. Calls * to this method must be synchronized on the EntryUpdateState instance. * @return the counter value after the operation completed */ public int startUpdate() { if ((state != NOT_YET_UPDATING) && (state != UPDATE_CANCELLED)) { throw new IllegalStateException("Cannot begin cache update - current state (" + state + ") is not NOT_YET_UPDATING or UPDATE_CANCELLED"); } state = UPDATE_IN_PROGRESS; return incrementUsageCounter(); } /** * 并发数增加 * Increments the usage counter by one * @return the counter value after the increment */ public synchronized int incrementUsageCounter() { nbConcurrentUses++; return nbConcurrentUses; } /** * 获取并发数 * Gets the current usage counter value * @return a positive number. */ public synchronized int getUsageCounter() { return nbConcurrentUses; } /** * 计数器递减,并发数<=0时抛出异常. * Decrements the usage counter by one. This method may only be called when the usage number is greater than zero * @return the counter value after the decrement */ public synchronized int decrementUsageCounter() { if (nbConcurrentUses <=0) { throw new IllegalStateException("Cannot decrement usage counter, it is already equals to [" + nbConcurrentUses + "]"); } nbConcurrentUses--; return nbConcurrentUses; } }
需要刷新异常:
/** * *当缓存实体过期或不存在时抛出异常 * @author mengxiansheng */ public final class NeedsRefreshException extends Exception { /** * */ private static final long serialVersionUID = 1L; /** * Current object in the cache */ private Object cacheContent = null; /** * Create a NeedsRefreshException */ public NeedsRefreshException(String message, Object cacheContent) { super(message); this.cacheContent = cacheContent; } /** * Create a NeedsRefreshException */ public NeedsRefreshException(Object cacheContent) { super(); this.cacheContent = cacheContent; } /** * Retrieve current object in the cache */ public Object getCacheContent() { return cacheContent; } }
spring 管理配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean class="ApplicationContextUtil"></bean> <!--socket连接池--> <bean id="SockIOPool" class="com.danga.MemCached.SockIOPool" factory-method="getInstance" init-method="initialize"> <!--服务器列表,可以根据需要增减--> <property name="servers"> <list> <value>127.0.0.1:11211</value> </list> </property> <!--服务器权重,根据内存大小分配--> <property name="weights"> <list> <value>2</value> </list> </property> <!--初始化,最小,最大连接数,建议开发环境使用1,1,5,线上环境使用5,5,250--> <property name="initConn" value="1"></property> <property name="minConn" value="1"></property> <property name="maxConn" value="5"></property> <!--时间--> <property name="maxIdle" value="21600000"></property> <property name="maintSleep" value="30000"></property> <property name="nagle" value="false"></property> <property name="socketTO" value="3000"></property> <property name="socketConnectTO" value="3000"></property> </bean> <bean name="MemCachedClient" class="com.danga.MemCached.MemCachedClient" depends-on="SockIOPool"> <property name="compressEnable" value="true"></property> <property name="compressThreshold" value="131072"></property> </bean> <bean name="Mem" class="CacheTuning"> <property name="memCachedClient" ref="MemCachedClient" /> </bean> </beans>
并发测试:
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class Test { //ApplicationContextUtil 类自己实现,用来加载spring配置文件, static CacheTuning mt=(CacheTuning)ApplicationContextUtil.getContext().getBean("Mem"); private static transient final Log log = LogFactory .getLog(Test.class); /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub long t1=System.currentTimeMillis(); for(int i=0;i<1000;i++){ Thread tt=new Thread(new ts().setTime(t1)); tt.start(); } } } class ts implements java.lang.Runnable{ private static transient final Log log = LogFactory .getLog(ts.class); long t1=0; public ts setTime(long t1){ this.t1=t1; return this; } public void run() { // TODO Auto-generated method stub String aa=null; long _t1=System.currentTimeMillis(); try{ Test.mt.delete("keyss"); aa=(String)Test.mt.getFromCache("keyss"); }catch(NeedsRefreshException e){ aa="kdj快来撒飞机快lsd就疯了"; Test.mt.putInCache("keyss", aa,100); log.info(Thread.currentThread().getId()+"新增:keyss"); } long t2=System.currentTimeMillis(); log.info(Thread.currentThread().getId()+"耗时:\t"+(t2-_t1)+"\t总耗时\t"+(t2-t1)+"\t"+aa); } }