查看原文
其他

注册中心 Eureka 源码解析 —— StringCache

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

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

技术文章第一时间送达!

源码精品专栏

 



  • 1. 概述

  • 2. StringCache

  • 3. 使用场景

  • 666. 彩蛋


1. 概述

本文主要分享 Eureka 自己实现的 StringCache

先一起来看下美团点评技术团队对 String#intern(...) 的分享:

FROM 《深入解析String#intern》「 引言 」 
在 JAVA 语言中有8中基本类型和一种比较特殊的类型 String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个 JAVA 系统级别提供的缓存。 
8 种基本类型的常量池都是系统协调的,String 类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的 String 对象会直接存储在常量池中

  • 如果不是用双引号声明的 String 对象,可以使用String提供的 intern 方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

  • 字符串常量池能带来速度更快,更节省内存的好处

  • 非双引号声明的 String 对象,需要使用 String#intern() 方法,将字符串存储到字符串常量池。

看起来一切都非常非常非常美好,那为什么 Eureka 自己实现了 StringCache ?

继续参见美团点评技术团队对 String#intern(...) 的分享:

FROM 《深入解析String#intern》「 native 代码 」 
JAVA 使用 JNI 调用 c++ 实现的 StringTable 的 intern 方法, StringTable的 intern 方法跟 Java 中的 HashMap 的实现是差不多的, 只是不能自动扩容。默认大小是1009

要注意的是,String 的 String Pool 是一个固定大小的 Hashtable,默认值大小长度是 1009,如果放进 String Pool 的 String 非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

在 JDK6 中 StringTable 是固定的,就是 1009 的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:

  • -XX:StringTableSize=99991

  • JDK 自带的 String Pool 固定大小( 即使可配 ),不支持自动扩容,大量使用 String#intern(…) 后,会导致性能大幅度下降。

  • Eureka 的应用实例( InstanceInfo ) 的 appNameappGroupNamevipAddresssecureVipAddressmetadata 和应用( Application )的 name 等属性需要使用到 String Pool ,为了在大量的网络通信序列化反序列的过程中,速度更快,更节省内容。

另外,FastJSON 在 1.124 版本之前也使用 String#intern(...) 方法,优化 JSON Key 的速度和空间,但是在大量动态 JSON Key 的场景下,反而会导致性能下降。所以 FastJSON 1.124 修复了该问题。参见如下:

FROM 《深入解析String#intern》「 fastjson 不当使用 」 


  • But ,FastJSON 1.124 版本之前恰好适合 Eureka ,因为 appNameappGroupName 相对不那么动态。考虑到可能还是有大量的字符串存在,因而实现自定义的 StringCache 类,以解决 StringPool 的 HashTable 不支持动态扩容的情况。

OK,下面我们来看看 Eureka 是如何实现自定义的 StringCache 类。

推荐 Spring Cloud 书籍

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

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

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

  • 两书齐买,京东包邮。

推荐 Spring Cloud 视频

  • Java 微服务实践 - Spring Boot

  • Java 微服务实践 - Spring Cloud

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

2. StringCache

com.netflix.discovery.util.StringCache ,字符串缓存。代码如下:

  1public class StringCache {
  2
  3:     public static final int LENGTH_LIMIT = 38;
  4
  5:     private static final StringCache INSTANCE = new StringCache();
  6
  7:     private final ReadWriteLock lock = new ReentrantReadWriteLock();
  8:     private final Map<String, WeakReference<String>> cache = new WeakHashMap<String, WeakReference<String>>();
  9:     private final int lengthLimit;
 10
 11:     public StringCache() {
 12:         this(LENGTH_LIMIT);
 13:     }
 14
 15:     public StringCache(int lengthLimit) {
 16:         this.lengthLimit = lengthLimit;
 17:     }
 18
 19:     public String cachedValueOf(final String str) {
 20:         if (str != null && (lengthLimit < 0 || str.length() <= lengthLimit)) {
 21:             // Return value from cache if available
 22:             try {
 23:                 lock.readLock().lock();
 24:                 WeakReference<String> ref = cache.get(str);
 25:                 if (ref != null) {
 26:                     return ref.get();
 27:                 }
 28:             } finally {
 29:                 lock.readLock().unlock();
 30:             }
 31
 32:             // Update cache with new content
 33:             try {
 34:                 lock.writeLock().lock();
 35:                 WeakReference<String> ref = cache.get(str);
 36:                 if (ref != null) {
 37:                     return ref.get();
 38:                 }
 39:                 cache.put(str, new WeakReference<>(str));
 40:             } finally {
 41:                 lock.writeLock().unlock();
 42:             }
 43:             return str;
 44:         }
 45:         return str;
 46:     }
 47
 48:     public int size() {
 49:         try {
 50:             lock.readLock().lock();
 51:             return cache.size();
 52:         } finally {
 53:             lock.readLock().unlock();
 54:         }
 55:     }
 56
 57:     public static String intern(String original) {
 58:         return INSTANCE.cachedValueOf(original);
 59:     }
 60
 61: }
  • INSTANCE 属性,字符串缓存单例

  • lock 属性,读写锁,保证读写互斥。

  • cache 属性,缓存哈希表。

    • 使用 WeakHashMap,当 StringCache 被回收时,其对应的值一起被移除。

    • 《WeakHashMap和HashMap的区别》

    • 《Java 集合系列13之 WeakHashMap详细介绍(源码解析)和使用示例》

  • lengthLimit 属性,缓存字符串最大长度。默认值:38 。

  • #cachedValueOf(…) 方法,获得字符串缓存。若缓存不存在,则进行缓存。和 String#intern() 的逻辑相同,区别在于 cache 支持自动扩容。

    • 第 22 至 30 行 :读锁,读取缓存。

    • 第 32 至 42 行 :缓存不存在,写锁,写入缓存。

  • #size() 方法,缓存大小。

  • #intern() 静态方法,使用 INSTANCE 获取缓存字符串。

3. 使用场景

在 InstanceInfo 下的使用,点击 链接 查看。

在 Application 下的使用,点击 链接 查看。

666. 彩蛋

又 Get 新姿势了,好开森。

胖友,分享个朋友圈,可好?!




如果你对 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 篇++


源码不易↓↓↓↓

点赞支持老艿艿↓↓

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

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