扫地僧,是金庸武侠小说《天龙八部》中的人物。

他的来历没有太多描绘,担任清扫藏经阁,奥秘而且武功莫测高深,并具有大智慧,有极高技艺却深藏不露,隐匿在少林寺默默无闻。

这篇文章,笔者想聊聊缓存,只不过并不是大家耳熟能详的 Redis ,而是那些隐藏在中间件或者结构中强大却又隐秘的缓存,笔者愿称他们为缓存世界的扫地僧

缓存世界里,那些强大又隐秘的扫地僧

1 HashMap/ConcurrentHashMap 配置缓存

HashMap 是一种根据哈希表的调集类,它供给了快速的刺进、查找和删去操作。能够将键值对作为缓存项的存储方式,将键作为缓存项的仅有标识符,值作为缓存项的内容。

HashMap 是许多程序员接触的第一种缓存 , 由于实际事务场景里,咱们或许需求给缓存增加缓存统计过期失效筛选战略等功用,HashMap 的功用就显得孱弱 ,所以 HashMap 在事务体系中运用得并不算多。

HashMap 在中间件中却是香饽饽,咱们音讯中间件 RocketMQ 为例。

缓存世界里,那些强大又隐秘的扫地僧

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

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

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

咱们能够了解 Name Server 集群的效果便是注册中心,注册中心会保存路由信息(主题的读写队列数、操作权限等),路由信息便是保存在 HashMap 中 。

缓存世界里,那些强大又隐秘的扫地僧

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

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

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

缓存世界里,那些强大又隐秘的扫地僧

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

缓存世界里,那些强大又隐秘的扫地僧

同时,咱们需求注意 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 组成,经过每次写操作或许要操作多个目标 ,为了保证其一致性,所以才需求加读写锁。

2 LinkedHashMap 最近最少运用缓存

LinkedHashMap 是 HashMap 的子类,可是内部还有一个双向链表保护键值对的次序,每个键值对既坐落哈希表中,也坐落双向链表中。

LinkedHashMap 支撑两种次序刺进次序 、 拜访次序

  • 刺进次序:先增加的在前面,后增加的在后面,修正操作并不影响次序
  • 拜访次序:问指的是 get/put 操作,对一个键执行 get/put 操作后,其对应的键值对会移动到链表结尾,所以最结尾的是最近拜访的,最开端的是最久没有被拜访的,这便是拜访次序。

LinkedHashMap 经典的用法是作为 LruCache (最近最少运用缓存) ,而 MyBatis 的二级缓存的筛选机制便是运用的 LinkedHashMap 。

MyBatis 的二级缓存是运用职责链+ 装饰器形式来完成的,尽管 Mybatis 的二级缓存功用在出产环境并不引荐运用,但它的规划形式仍是值得学习。

缓存世界里,那些强大又隐秘的扫地僧

上图中,装饰器包目录下 Cache 接口有不同的完成类,比方过期筛选日志记载等。

缓存世界里,那些强大又隐秘的扫地僧

LruCache 同样运用了装饰器形式 ,运用 LinkedHashMap 默认保存 1024 个缓存 key ,当 key 最久未被拜访,并且 keyMap 的大小超过 1024 时 ,记载最老的 key ,当下次增加缓存目标时,删去最老的 key。

运用 LinkedHashMap 重点需求做到运用拜访次序形式重写 removeEldestEntry 办法。 由于 LinkedHashMap 并不是线程安全的,Mybatis 二级缓存职责链中 SynchronizedCache 目标能够完成线程安全的对缓存读写。

3 TreeMap 排序目标缓存

TreeMap 是一种根据红黑树的有序 Map,它能够按照键的次序进行遍历。

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

一致性哈希的首要优点是在节点增减时,只要少数的数据需求从头映射,由于只要那些直接或直接与新增或删去节点相邻的数据项需求搬迁。这大大减少了体系的搬迁开销和影响,使得体系更具扩展性和可伸缩性。

TreeMap 在一致性哈希中能够用作节点/虚拟节点的存储结构,用来保护节点在哈希环上的方位和键的有序性。

1、咱们界说一个 TreeMap 存储节点/虚拟节点 。

缓存世界里,那些强大又隐秘的扫地僧

2、初始化节点

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

缓存世界里,那些强大又隐秘的扫地僧

咱们重点看下增加节点逻辑:

缓存世界里,那些强大又隐秘的扫地僧

3、按照 key 查询节点

增加完节点之后,节点分布相似下图:

缓存世界里,那些强大又隐秘的扫地僧

缓存世界里,那些强大又隐秘的扫地僧

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

咱们增加一个新的节点 node5 , 从下图中,咱们能够看到,影响的规模(深黄色)并不大 ,这也便是一致性哈希算法的优势。

缓存世界里,那些强大又隐秘的扫地僧

4 ByteBuffer 网络编程缓冲池

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

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

Cobar 抽象了一个 NIOProcessor 类用来处理网络请求,每个处理器初始化的时候都会创立一个缓冲池 BufferPool 。咱们平常运用的数据库连接池便是一个十分典型的池化的事例。

缓存世界里,那些强大又隐秘的扫地僧

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

缓存世界里,那些强大又隐秘的扫地僧

缓冲池 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 ,从而大大提升体系的功能。


假如我的文章对你有所协助,还请帮忙点赞、在看、转发一下,你的支撑会激励我输出更高质量的文章,十分感谢!