设计
存在两个集合ReadOnlyMap、ReadWriteMap。
读请求,全部是从ReadOnlyMap里面,如果里面没有,就从ReadWriteMap中找。如果ReadWriteMap也没有就去服务注册表中拿。
ReadOnlyMap,大量的线程并发的读服务注册表的数据,在这里是不需要加锁的,大家直接读,不需要加读锁,就没有大量的读锁和写锁的冲突了。
只有在ReadOnlyMap里面没数据的时候,此时会加一个synchronized锁,只有一个线程可以去找ReadWriteMap,去找服务注册表的数据,此时会加服务注册表的读锁,但是找到了数据,填充完了ReadWriteMap两级缓存之后,就会释放synchronized锁,其他线程就可以直接读缓存里的数据了。
将服务注册表的读锁,降低到了,很少的频率,某个线程发现缓存里没数据,要直接读取原服务注册表的数据的时候,才会加读锁。
如果更新数据之后,就会过期掉ReadWriteMap的一些数据,清空缓存。
实现
public class ServiceRegistryCache {
private static final ServiceRegistryCache instance = new ServiceRegistryCache();
/**
* 缓存数据同步间隔
*/
private static final Long CACHE_MAP_SYNC_INTERVAL = 30 * 1000L;
/**
* 缓存操作类型key
*/
public static class CacheKey {
/**
* 全量注册表的key
*/
public static final String FULL_SERVICE_REGISTRY = "full_service_registry";
/**
* 增量注册表的key
*/
public static final String DELTA_SERVICE_REGISTRY = "DELTA_service_registry";
}
/**
* 实际注册表
*/
private ServiceRegistry registry = ServiceRegistry.getInstance();
/**
* 只读缓存
*/
private Map<String, Object> readOnlyMap = new HashMap<String, Object>();
/**
* 读写缓存
*/
private Map<String, Object> readWriteMap = new HashMap<String, Object>();
/**
* 缓存表同步后台线程
*/
private CacheMapSyncDaemon cacheMapSyncDaemon;
/**
* 内部锁
*/
private Object key = new Object();
/**
* 读写锁
*/
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
/**
* 构造函数
*/
private ServiceRegistryCache() {
//启动缓存数据的后台线程
this.cacheMapSyncDaemon = new CacheMapSyncDaemon();
this.cacheMapSyncDaemon.setDaemon(true);
this.cacheMapSyncDaemon.start();
}
/**
* 根据缓存key获取数据
* @param cacheKey
* @return
*/
public Object get(String cacheKey) {
try {
readLock.lock();
//这里获取数据的时候就不加锁,也就不存在和写锁互斥然后等待了。
Object cacheValue = readOnlyMap.get(cacheKey);
if (cacheValue == null) {
synchronized (key) {
if ((cacheValue = readOnlyMap.get(cacheKey)) == null) {
cacheValue = readWriteMap.get(cacheKey);
if (cacheValue == null) {
cacheValue = getCacheValue(cacheKey);
readWriteMap.put(cacheKey,cacheValue);
readOnlyMap.put(cacheKey,cacheValue);
}
readOnlyMap.put(cacheKey,cacheValue);
}
}
}
return cacheValue;
} finally {
readLock.unlock();
}
}
/**
*获取实际的缓存数据(就是从原注册表中拿)
* @param cacheKey
* @return
*/
public Object getCacheValue(String cacheKey) {
try {
registry.readLock();
if (CacheKey.FULL_SERVICE_REGISTRY.equals(cacheKey)) {
return new Applications(registry.getRegistry());
} else if (CacheKey.DELTA_SERVICE_REGISTRY.equals(cacheKey)) {
return registry.getDeltaRegistry();
}
} finally {
registry.readUnlock();
}
return null;
}
/**
* 过期掉对应缓存
*/
public void invalidate() {
synchronized (readOnlyMap) {
readWriteMap.remove(CacheKey.FULL_SERVICE_REGISTRY);
readWriteMap.remove(CacheKey.DELTA_SERVICE_REGISTRY);
}
}
/**
* 返回单例
* @return
*/
public static ServiceRegistryCache getInstance() {
return instance;
}
/**
* cachemap同步后台线程
*/
class CacheMapSyncDaemon extends Thread {
@Override
public void run() {
while (true) {
try {
synchronized (key) {
if (readWriteMap.get(CacheKey.FULL_SERVICE_REGISTRY) == null) {
try {
writeLock.lock();
readOnlyMap.put(CacheKey.FULL_SERVICE_REGISTRY,null);
} finally {
writeLock.unlock();
}
}
if (readWriteMap.get(CacheKey.DELTA_SERVICE_REGISTRY) == null) {
try {
writeLock.lock();
readOnlyMap.put(CacheKey.DELTA_SERVICE_REGISTRY,null);
} finally {
writeLock.unlock();
}
}
}
Thread.sleep(CACHE_MAP_SYNC_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
分析一下具体优化了什么?
获取缓存数据,Object cacheValue = readOnlyMap.get(cacheKey);这里就加了个读锁锁的,别看那有个readLock这个是保证在清理缓存的时候读到的数据是准确的。更多时间是没有影响,同时定时清理缓存是每30秒来加写锁,争用的频率很低了。可以说是80%以上读注册表数据的时候都是没有冲突的,因此就很快。
当系统启动的时候,这块会从原始注册表中拉取数据放到缓存表中,这里用synchronized串行化了一下保证并发安全。之后大量读请求都是读的readOnlyMap这里就一个读锁影响是非常小的,最多就是和同步缓存表数据的后台线程那里冲突一下,而且是每隔30秒,同时是“有可能”会冲突。
之前直接对原始表获取数据的时候,那时候的读操作跟服务注册,服务下线,服务定时检查下线的写锁就会有冲突,冲突频率要比从缓存中拿要高得多。