0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

盘点那些强大又低调的Java缓存

jf_ro2CN3Fa 来源:勇哥java实战分享 2023-11-14 18:02 次阅读

这篇文章,笔者想聊聊那些在业务系统中较少被使用,但却活跃于中间件或者框架里,强大却又低调的缓存,笔者愿称他们为缓存世界的扫地僧。

88a477a0-82d4-11ee-939d-92fbcf53809c.png

1 HashMap/ConcurrentHashMap 配置缓存

HashMap 是一种基于哈希表的集合类,它提供了快速的插入、查找和删除操作。

HashMap 是很多程序员接触的第一种缓存 , 因为现实业务场景里,我们可能需要给缓存添加缓存统计、过期失效、淘汰策略等功能,HashMap 的功能就显得孱弱 ,所以 HashMap 在业务系统中使用得并不算多。

但 HashMap 在中间件中却是香饽饽,我们消息中间件 RocketMQ 为例。

88b1137a-82d4-11ee-939d-92fbcf53809c.jpg

上图是 RocketMQ 的集群模式 ,Broker 分为 Master 与 Slave,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个 Master。

每个 Broker 与 Name Server 集群中的所有节点建立长连接,定时每隔 30 秒注册 主题的路由信息到所有 Name Server。

消息发送者、消息消费者,在同一时间只会连接 Name Server 集群中的一台服务器,并且会每隔 30s 会定时更新 Topic 的路由信息。

我们可以理解 Name Server 集群的作用就是注册中心,注册中心会保存路由信息(主题的读写队列数、操作权限等),路由信息就是保存在 HashMap 中 。

88c56532-82d4-11ee-939d-92fbcf53809c.jpg

路由信息通过几个 HashMap 来保存,当 Broker 向 Nameserver 发送心跳包(路由信息),Nameserver 需要对 HashMap 进行数据更新,但我们都知道 HashMap 并不是线程安全的,高并发场景下,容易出现 CPU 100% 问题,所以更新 HashMap 时需要加锁,RocketMQ 使用了 JDK 的读写锁 ReentrantReadWriteLock 。

下面我们看下路由信息如何更新和读取:

1、写操作:更新路由信息,操作写锁

88d51266-82d4-11ee-939d-92fbcf53809c.jpg

2、读操作:查询主题信息,操作读锁

88e026a6-82d4-11ee-939d-92fbcf53809c.jpg

同时,我们需要注意 Name Server 维护路由信息还需要定时任务的支撑。

每个 Broker 定时每隔 30 秒注册 主题的路由信息到所有 Name Server

Name Server 定时任务每隔10 秒清除已宕机的 Broker

我们做一个小小的总结,Name Server 维护路由的模式是:HashMap + 读写锁 + 定时任务更新。

HashMap 作为存储容器

读写锁控制锁的颗粒度

定时任务定时更新缓存

写到这里,我们不禁想到 ConcurrentHashMap。

ConcurrentHashMap 可以保证线程安全,JDK1.7 之前使用分段锁机制实现,JDK1.8 则使用数组+链表+红黑树数据结构和CAS原子操作实现。

Broker 使用不同的 ConcurrentHashMap 分别用来存储消费组、消费进度、消息过滤信息等。

那么名字服务为什么不使用 ConcurrentHashMap 作为存储容器呢 ?

最核心的原因在于:路由信息由多个 HashMap 组成,通过每次写操作可能要操作多个对象 ,为了保证其一致性,所以才需要加读写锁。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

视频教程:https://doc.iocoder.cn/video/

2 LinkedHashMap 最近最少使用缓存

LinkedHashMap 是 HashMap 的子类,但是内部还有一个双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于双向链表中。

LinkedHashMap 支持两种顺序插入顺序 、 访问顺序。

插入顺序:先添加的在前面,后添加的在后面,修改操作并不影响顺序

访问顺序:问指的是 get/put 操作,对一个键执行 get/put 操作后,其对应的键值对会移动到链表末尾,所以最末尾的是最近访问的,最开始的是最久没有被访问的,这就是访问顺序。

LinkedHashMap 经典的用法是作为 LruCache (最近最少使用缓存) ,而 MyBatis 的二级缓存的淘汰机制就是使用的 LinkedHashMap 。

MyBatis 的二级缓存是使用责任链+ 装饰器的设计模式实现的。

88e74e0e-82d4-11ee-939d-92fbcf53809c.jpg

上图中,装饰器包目录下 Cache 接口有不同的实现类,比如过期淘汰、日志记录等。

88ef3088-82d4-11ee-939d-92fbcf53809c.jpg

LruCache 使用了装饰器模式 ,使用 LinkedHashMap 默认保存 1024 个缓存 key ,当 key 最久未被访问,并且 keyMap 的大小超过 1024 时 ,记录最老的 key ,当下次添加缓存对象时,删除最老的 key。

使用 LinkedHashMap 重点需要做到使用访问顺序模式和重写 removeEldestEntry 方法。因为 LinkedHashMap 并不是线程安全的,Mybatis 二级缓存责任链中 SynchronizedCache 对象可以实现线程安全的对缓存读写。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/yudao-cloud

视频教程:https://doc.iocoder.cn/video/

3 TreeMap 排序对象缓存

TreeMap 是一种基于红黑树的有序 Map,它可以按照键的顺序进行遍历。

TreeMap 有两种应用场景让笔者印象极为深刻 ,他们分别是一致性哈希算法和 RocketMQ 消费快照 。

下面重点介绍 TreeMap 在一致性哈希算法中的应用。

一致性哈希(Consistent Hashing)算法被广泛应用于缓存系统、分布式数据库、负载均衡器等分布式系统中,以实现高性能和高可用性。它解决了传统哈希算法在动态环境下扩展性和负载均衡性能的问题。

一致性哈希的主要优点是在节点增减时,只有少量的数据需要重新映射,因为只有那些直接或间接与新增或删除节点相邻的数据项需要迁移。这大大减少了系统的迁移开销和影响,使得系统更具扩展性和可伸缩性。

TreeMap 在一致性哈希中可以用作节点/虚拟节点的存储结构,用来维护节点在哈希环上的位置和键的有序性。

1、我们定义一个 TreeMap 存储节点/虚拟节点 。

890959fe-82d4-11ee-939d-92fbcf53809c.png

2、初始化节点

构造函数包含三个部分:物理节点集合、每个物理节点对应的虚拟节点个数、哈希函数 。

89146614-82d4-11ee-939d-92fbcf53809c.png

我们重点看下添加节点逻辑:

891dd2e4-82d4-11ee-939d-92fbcf53809c.png

3、按照 key 查询节点

添加完节点之后,节点分布类似下图:

8935c548-82d4-11ee-939d-92fbcf53809c.jpg

893fe244-82d4-11ee-939d-92fbcf53809c.png

当需要定位某个 key 属于哪个节点时,先通过哈希函数计算 key 的哈希值,并在环上顺时针方向找到第一个大于等于该哈希值的节点位置。该节点即为数据的归属节点 。

我们添加一个新的节点 node5 , 从下图中,我们可以看到,影响的范围(深黄色)并不大 ,这也就是一致性哈希算法的优势。

8956c536-82d4-11ee-939d-92fbcf53809c.jpg

4 ByteBuffer 网络编程缓冲池

ByteBuffer 是字节缓冲区,主要用于用户读取和缓存字节数据,多用于网络编程、文件 IO 处理等。

笔者第一次接触 ByteBuffer 是在分库分表中间件 Cobar 中 。在网络编程里,经常需要分配内存,在高并发场景下,性能压力比较大。

Cobar 抽象了一个 NIOProcessor 类用来处理网络请求,每个处理器初始化的时候都会创建一个缓冲池 BufferPool 。BufferPool 用于池化 ByteBuffer ,这和我们平常使用的数据库连接池的思路是一致的。

8964ec9c-82d4-11ee-939d-92fbcf53809c.png

下图展示了缓冲池 BufferPool 的源码:

896fb83e-82d4-11ee-939d-92fbcf53809c.png

缓冲池 BufferPool 的核心功能是分配缓存和回收缓存 ,通过将缓存池化,可以大大提升系统的性能。

如今 ,Netty 内置了更为强大的内存池化工具 ByteBuf ,我们会在后面的文章里详聊。

5 写到最后

这篇文章,笔者总结了四种强大且低调的缓存。

1、HashMap/ConcurrentHashMap 经常用于配置缓存,对于 HashMap 来讲,HashMap + 读写锁 + 定时任务更新是常用的模式。而 ConcurrentHashMap 广泛存在于各种中间件,线程安全且灵活易用。

2、LinkedHashMap 经常被用于创建最近最少使用缓存 LruCache 。推荐学习 Mybatis 二级缓存的设计,它使用责任链+ 装饰器的设计模式,内置 LruCache 的实现就是使用 LinkedHashMap 。

3、TreeMap 是一种基于红黑树的有序 Map 。TreeMap 在一致性哈希中可以用作节点/虚拟节点的存储结构,用来维护节点在哈希环上的位置和键的有序性。

4、ByteBuffer 是字节缓冲区,主要用于用户读取和缓存字节数据,多用于网络编程、文件 IO 处理等。分库分表中间件 Cobar 在网络请求处理中,创建了缓冲池 BufferPool 用于池化 ByteBuffer ,从而大大提升系统的性能。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2904

    浏览量

    102996
  • 缓存
    +关注

    关注

    1

    文章

    220

    浏览量

    26444
  • 程序员
    +关注

    关注

    4

    文章

    931

    浏览量

    29572

原文标题:盘点那些强大又低调的 Java 缓存

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    被“玩坏”的奇葩LED灯盘点

    今天,为大家来盘点一下那些奇葩的LED灯。
    发表于 02-25 14:28 1613次阅读

    2012年6月全球收购浪潮盘点:那些即将消失的企业

    2012年6月全球收购浪潮盘点:那些即将消失的企业。科技发展过程中,企业总会遇到收购与被收购,尤其是全球性知名企业。随着谷歌125亿美元收购摩托罗拉移动,收购狂潮不断继续着。
    发表于 06-25 09:48 2843次阅读

    基于Java的分布式缓存优化在网络管理系统中的应用

    基于Java的分布式缓存优化在网络管理系统中的应用讨论建立在JMX管理框架上的网络性能管理系统的优化方案,利用JMX体系结构的可扩展特性,在系统的服务器端嵌入了分布式缓存系统对服务器端的缓存
    发表于 09-19 09:20

    ASP缓存技术

    缓存中时,再次查询时耗费的时间主要是在显示数据的时间上了。也就是说,我们不应该把经常需要改变的数据放到服务端的缓存中,我们应该把改变少,但是需要经常访问的数据放到缓存中。现在我们先
    发表于 11-21 10:53

    Java中的输入输出流盘点

    Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的。
    发表于 07-11 07:56

    Java程序员笔记之mybatis结合redis实战二级缓存

    Java程序员笔记——mybatis结合redis实战二级缓存
    发表于 06-10 09:15

    一款基于Java实现的小巧而强大的关系型数据库

    H2 是一款基于 Java 实现的小巧而强大的关系型数据库,支持嵌入式、客户端/服务器以及混合部署模式。H2 数据库适合嵌入小型应用程序、元数据管理、快速应用开发和测试、内存数据库。
    发表于 10-27 06:12

    Java 使用Redis缓存工具的详细解说

    本文是关于Java 使用Redis缓存工具的详细解说。详细步骤请看下文
    的头像 发表于 02-09 14:10 7637次阅读
    <b class='flag-5'>Java</b> 使用Redis<b class='flag-5'>缓存</b>工具的详细解说

    盘点嵌入式的那些常见GUI:emWin、TouchGFX、MiniGUI、Qt等

    盘点嵌入式那些常见的GUI:emWin、TouchGFX、MiniGUI、Qt等
    的头像 发表于 02-05 12:38 9127次阅读

    Redis集群缓存方案,缓存常见问题盘点

    如今,缓存系统的应用非常广泛,能够用来提高并发数、数据吞吐量,提高快速响应能力。那么当数据量达到一定程度,单机环境可能就显得有些力不从心了,就需要一个分布式缓存系统。
    发表于 12-16 10:48 2150次阅读
    Redis集群<b class='flag-5'>缓存</b>方案,<b class='flag-5'>缓存</b>常见问题<b class='flag-5'>盘点</b>

    关于内存缓存那些

    而包含和不包含的区别在这里就会有所体现。如果是包含策略,那么新数据直接覆盖旧数据即可,旧数据等于直接作废,除非这个数据最近在CPU中被改写过,需要返回到内存中进行保存,那么才需要将该缓存行刷回内存(那么如何确定该缓存行是否被改写过呢?可以用一个名为dirty的标志位注明)
    的头像 发表于 08-03 16:59 2541次阅读
    关于内存<b class='flag-5'>缓存</b>的<b class='flag-5'>那些</b>事

    介绍下cpu缓存一致性(MESI协议)

    之前介绍了java并发包的cas原理和java内存模型,这篇我们介绍下cpu缓存一致性原理,可以帮助我们更好的理解cas的底层原理。
    的头像 发表于 06-09 16:01 2954次阅读
    介绍下cpu<b class='flag-5'>缓存</b>一致性(MESI协议)

    Ehcache!这才是Java本地缓存之王!

    Java而言,其常用的缓存解决方案有很多,例如数据库缓存框架EhCache,分布式缓存Memcached等,这些缓存方案实际上都是为了提升
    的头像 发表于 07-29 11:21 1218次阅读
    Ehcache!这才是<b class='flag-5'>Java</b>本地<b class='flag-5'>缓存</b>之王!

    基于java开发的缓存框架jetcache简介

    在实际应用中,并不是单一的使用本地缓存或者redis,更多是组合使用来满足不同的业务场景,于是如何优雅的组合本地缓存和远程缓存就成了我们要研究的问题,而这一点,阿里开源的jetcache组件帮我们实现了
    的头像 发表于 09-07 10:36 592次阅读
    基于<b class='flag-5'>java</b>开发的<b class='flag-5'>缓存</b>框架jetcache简介

    CPU缓存那些事儿

    CPU Cache 在读取内存数据时,每次不会只读一个字或一个字节,而是一块块地读取,这每一小块数据也叫CPU 缓存行(CPU Cache Line)。这也是对局部性原理的运用,当一个指令或数据
    的头像 发表于 09-10 10:57 388次阅读
    CPU<b class='flag-5'>缓存</b><b class='flag-5'>那些</b>事儿