设计
存在两个集合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 {@Overridepublic 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秒,同时是“有可能”会冲突。
之前直接对原始表获取数据的时候,那时候的读操作跟服务注册,服务下线,服务定时检查下线的写锁就会有冲突,冲突频率要比从缓存中拿要高得多。
