查看原文
其他

注册中心 Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁

老艿艿 芋道源码 2019-05-13

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 



  • 1. 概述

  • 2. 读写锁

  • 666. 彩蛋


1. 概述

本文主要分享 Eureka 注册中心的那把读写锁,让我瘙痒难耐,却不得其解。在某次意外的抠脚的一刻( 笔者不抽烟,如果抽烟的话,此处应该就不是抠脚了 ),突然顿悟,爽,这好比… 比喻有点猥琐,笔者就省略 100 字。

不瞎比比,上代码:

public abstract class AbstractInstanceRegistry implements InstanceRegistry {

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock read = readWriteLock.readLock();
    private final Lock write = readWriteLock.writeLock();

    // ... 省略其他代码

}

推荐 Spring Cloud 书籍

  • 请支持正版。下载盗版,等于主动编写低级 BUG 。

  • 程序猿DD —— 《Spring Cloud微服务实战》

  • 周立 —— 《Spring Cloud与Docker微服务架构实战》

  • 两书齐买,京东包邮。

推荐 Spring Cloud 视频

  • Java 微服务实践 - Spring Boot

  • Java 微服务实践 - Spring Cloud

  • Java 微服务实践 - Spring Boot / Spring Cloud

2. 读写锁

我们把设计到读写锁的方法整理如下:

方法读锁写锁不使用
#register(...)

#cancel(...)

#evict(...)

#renew(...)

#statusUpdate(...)

#deleteStatusOverride(...)

#getApplicationDeltasFromMultipleRegions(...)

#getApplicationsFromMultipleRegions(...)

是否看到这读写感到几丝诡异的味道?OK,我们把问题梳理如下:

  • A. 为什么 #register(…) / #cancel(…) / #evict(…) / #statusUpdate(…) / #deleteStatusOverride(…) 等写操作使用读锁

  • B. 为什么 #renew(…) 写操作不使用

  • C. 为什么 #getApplicationDeltasFromMultipleRegions(…) 读操作使用写锁

  • D. 为什么 getApplicationsFromMultipleRegions(…) 读操作不使用


先解释 A + C :

我们来回想下,在 Eureka 应用集合一致性哈希码的公式:appsHashCode = ${status}_${count}_ 。( 不了解的同学可以加载下 《Eureka 源码解析 —— 应用实例注册发现(七)之增量获取》「 2. 应用集合一致性哈希码 」 )

应用实例的数量和状态都会影响哈希码的计算结果。也就是说,上述前六个( 包括不使用锁的 #renew(...) 方法 )方法的调用都会影响哈希码。

我们把目光移向唯一使用写锁的 #getApplicationDeltasFromMultipleRegions(...) 方法,该方法执行过程中,需要保证 recentlyChangedQueue 和 registry 共享变量的应用实例的状态一致,不然返回的增量应用实例集合的状态是不准确的。此时能够达到该效果,必须让 #getApplicationDeltasFromMultipleRegions(...) 和前六个方法互斥。方案如下:

  • a. 全部 synchronized

  • b. #getApplicationDeltasFromMultipleRegions(…) 使用读锁,前六个方法使用写锁

  • c. #getApplicationDeltasFromMultipleRegions(…) 使用写锁,前六个方法使用读锁

Eureka 选择了方案c,原因如下:

  • a. 性能太差

  • b. 前六个方法使用写锁,势必冲突太大,虽然读肯定比写多。

  • c. #getApplicationDeltasFromMultipleRegions(…) 使用写锁,配合 ResponseCache ,即减少了写锁使用的频率,每次缓存过期才使用,又避免了前六个方法因为方案b中的写锁导致互斥。( 不了解 ResponseCache 的同学可以加载下 《Eureka 源码解析 —— 应用实例注册发现(六)之全量获取》「 3.2 响应缓存 ResponseCache 」 )


再解释 D

#getApplicationsFromMultipleRegions(...) 方法的逻辑,只依赖 registry 共享变量,不存在应用实例的状态一致的困扰,所以不使用锁。


最后解释 B

#renew(...) 方法的逻辑,虽然会影响应用实例的状态,但是是极小概率,考虑到它调用的比较频繁,比起因为锁给这个方法带来的性能降低,不如返回的结果暂时不够准确。( 想了解极小概率发生原因的同学可以加载 《Eureka 源码解析 —— 应用实例注册发现(八)之覆盖状态》「 4.3 续租场景 」 )


TODO [0029] 读写锁

笔者路上突然又想了问题,可能不是上述原因,可能和 ResponseCache 有关系,参见 #invalidateCache(...) 方法的每次调用。也就是说,这个读写锁是针对 ResponseCache 的读写锁。

666. 彩蛋

开森 !

本来以为需要跟 Eureka 官方提交 issue 提问,并且做好了获得不到答案的准备,结果无意中的抠脚( 请允许我热爱抠脚给我带来的灵感 )解答了自己的疑惑。

岁月是把纠结而又萌萌哒的锁,你不知道你的困扰,哪天不经意的被打开。

愿大喜大悲,不枉仅知的这一生。




如果你对 Dubbo 感兴趣,欢迎加入我的知识星球一起交流。


知识星球



目前在知识星球(https://t.zsxq.com/2VbiaEu)更新了如下 Dubbo 源码解析如下:

01. 调试环境搭建
02. 项目结构一览
03. 配置 Configuration
04. 核心流程一览

05. 拓展机制 SPI

06. 线程池

07. 服务暴露 Export

08. 服务引用 Refer

09. 注册中心 Registry

10. 动态编译 Compile

11. 动态代理 Proxy

12. 服务调用 Invoke

13. 调用特性 

14. 过滤器 Filter

15. NIO 服务器

16. P2P 服务器

17. HTTP 服务器

18. 序列化 Serialization

19. 集群容错 Cluster

20. 优雅停机

21. 日志适配

22. 状态检查

23. 监控中心 Monitor

24. 管理中心 Admin

25. 运维命令 QOS

26. 链路追踪 Tracing

...
一共 60 篇++

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存