设计

存在两个集合ReadOnlyMap、ReadWriteMap。
读请求,全部是从ReadOnlyMap里面,如果里面没有,就从ReadWriteMap中找。如果ReadWriteMap也没有就去服务注册表中拿。
ReadOnlyMap,大量的线程并发的读服务注册表的数据,在这里是不需要加锁的,大家直接读,不需要加读锁,就没有大量的读锁和写锁的冲突了。
只有在ReadOnlyMap里面没数据的时候,此时会加一个synchronized锁,只有一个线程可以去找ReadWriteMap,去找服务注册表的数据,此时会加服务注册表的读锁,但是找到了数据,填充完了ReadWriteMap两级缓存之后,就会释放synchronized锁,其他线程就可以直接读缓存里的数据了。
将服务注册表的读锁,降低到了,很少的频率,某个线程发现缓存里没数据,要直接读取原服务注册表的数据的时候,才会加读锁。
如果更新数据之后,就会过期掉ReadWriteMap的一些数据,清空缓存。

实现

  1. public class ServiceRegistryCache {
  2. private static final ServiceRegistryCache instance = new ServiceRegistryCache();
  3. /**
  4. * 缓存数据同步间隔
  5. */
  6. private static final Long CACHE_MAP_SYNC_INTERVAL = 30 * 1000L;
  7. /**
  8. * 缓存操作类型key
  9. */
  10. public static class CacheKey {
  11. /**
  12. * 全量注册表的key
  13. */
  14. public static final String FULL_SERVICE_REGISTRY = "full_service_registry";
  15. /**
  16. * 增量注册表的key
  17. */
  18. public static final String DELTA_SERVICE_REGISTRY = "DELTA_service_registry";
  19. }
  20. /**
  21. * 实际注册表
  22. */
  23. private ServiceRegistry registry = ServiceRegistry.getInstance();
  24. /**
  25. * 只读缓存
  26. */
  27. private Map<String, Object> readOnlyMap = new HashMap<String, Object>();
  28. /**
  29. * 读写缓存
  30. */
  31. private Map<String, Object> readWriteMap = new HashMap<String, Object>();
  32. /**
  33. * 缓存表同步后台线程
  34. */
  35. private CacheMapSyncDaemon cacheMapSyncDaemon;
  36. /**
  37. * 内部锁
  38. */
  39. private Object key = new Object();
  40. /**
  41. * 读写锁
  42. */
  43. private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  44. private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
  45. private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
  46. /**
  47. * 构造函数
  48. */
  49. private ServiceRegistryCache() {
  50. //启动缓存数据的后台线程
  51. this.cacheMapSyncDaemon = new CacheMapSyncDaemon();
  52. this.cacheMapSyncDaemon.setDaemon(true);
  53. this.cacheMapSyncDaemon.start();
  54. }
  55. /**
  56. * 根据缓存key获取数据
  57. * @param cacheKey
  58. * @return
  59. */
  60. public Object get(String cacheKey) {
  61. try {
  62. readLock.lock();
  63. //这里获取数据的时候就不加锁,也就不存在和写锁互斥然后等待了。
  64. Object cacheValue = readOnlyMap.get(cacheKey);
  65. if (cacheValue == null) {
  66. synchronized (key) {
  67. if ((cacheValue = readOnlyMap.get(cacheKey)) == null) {
  68. cacheValue = readWriteMap.get(cacheKey);
  69. if (cacheValue == null) {
  70. cacheValue = getCacheValue(cacheKey);
  71. readWriteMap.put(cacheKey,cacheValue);
  72. readOnlyMap.put(cacheKey,cacheValue);
  73. }
  74. readOnlyMap.put(cacheKey,cacheValue);
  75. }
  76. }
  77. }
  78. return cacheValue;
  79. } finally {
  80. readLock.unlock();
  81. }
  82. }
  83. /**
  84. *获取实际的缓存数据(就是从原注册表中拿)
  85. * @param cacheKey
  86. * @return
  87. */
  88. public Object getCacheValue(String cacheKey) {
  89. try {
  90. registry.readLock();
  91. if (CacheKey.FULL_SERVICE_REGISTRY.equals(cacheKey)) {
  92. return new Applications(registry.getRegistry());
  93. } else if (CacheKey.DELTA_SERVICE_REGISTRY.equals(cacheKey)) {
  94. return registry.getDeltaRegistry();
  95. }
  96. } finally {
  97. registry.readUnlock();
  98. }
  99. return null;
  100. }
  101. /**
  102. * 过期掉对应缓存
  103. */
  104. public void invalidate() {
  105. synchronized (readOnlyMap) {
  106. readWriteMap.remove(CacheKey.FULL_SERVICE_REGISTRY);
  107. readWriteMap.remove(CacheKey.DELTA_SERVICE_REGISTRY);
  108. }
  109. }
  110. /**
  111. * 返回单例
  112. * @return
  113. */
  114. public static ServiceRegistryCache getInstance() {
  115. return instance;
  116. }
  117. /**
  118. * cachemap同步后台线程
  119. */
  120. class CacheMapSyncDaemon extends Thread {
  121. @Override
  122. public void run() {
  123. while (true) {
  124. try {
  125. synchronized (key) {
  126. if (readWriteMap.get(CacheKey.FULL_SERVICE_REGISTRY) == null) {
  127. try {
  128. writeLock.lock();
  129. readOnlyMap.put(CacheKey.FULL_SERVICE_REGISTRY,null);
  130. } finally {
  131. writeLock.unlock();
  132. }
  133. }
  134. if (readWriteMap.get(CacheKey.DELTA_SERVICE_REGISTRY) == null) {
  135. try {
  136. writeLock.lock();
  137. readOnlyMap.put(CacheKey.DELTA_SERVICE_REGISTRY,null);
  138. } finally {
  139. writeLock.unlock();
  140. }
  141. }
  142. }
  143. Thread.sleep(CACHE_MAP_SYNC_INTERVAL);
  144. } catch (InterruptedException e) {
  145. e.printStackTrace();
  146. }
  147. }
  148. }
  149. }
  150. }

分析一下具体优化了什么?
获取缓存数据,Object cacheValue = readOnlyMap.get(cacheKey);这里就加了个读锁锁的,别看那有个readLock这个是保证在清理缓存的时候读到的数据是准确的。更多时间是没有影响,同时定时清理缓存是每30秒来加写锁,争用的频率很低了。可以说是80%以上读注册表数据的时候都是没有冲突的,因此就很快。

image.png
当系统启动的时候,这块会从原始注册表中拉取数据放到缓存表中,这里用synchronized串行化了一下保证并发安全。之后大量读请求都是读的readOnlyMap这里就一个读锁影响是非常小的,最多就是和同步缓存表数据的后台线程那里冲突一下,而且是每隔30秒,同时是“有可能”会冲突。
之前直接对原始表获取数据的时候,那时候的读操作跟服务注册,服务下线,服务定时检查下线的写锁就会有冲突,冲突频率要比从缓存中拿要高得多。
image.png