原来Dubbo中的缓存玩的这么花

简介: 原来Dubbo中的缓存玩的这么花


前言

微服务架构已成为当今软件开发领域的主流趋势,而 Dubbo 作为一种优秀的微服务框架,其在性能优化方面有着独到的见解。然而,随着服务规模的增长,微服务架构中的性能问题也变得日益突出。就像一辆车需要油来运转一样,微服务也需要一种有效的机制来提高其性能,而 Dubbo 的缓存机制正是为此而生!

LRU缓存机制

LRU 缓存机制:

LRU(Least Recently Used)是一种常见的缓存淘汰算法,其工作原理是根据数据的访问历史来淘汰最近最少使用的数据,以保证缓存中的数据始终是最热门的数据。

工作原理和算法:

LRU 缓存机制基于数据的访问时间进行淘汰。当缓存空间满时,新加入的数据会替换掉最久未被访问的数据。LRU 缓存通常使用链表和哈希表实现。哈希表用于快速查询缓存中的数据,链表用于记录数据的访问顺序。

LRU 缓存算法的核心思想是维护一个有序的访问历史列表,当有新数据访问时,将数据移动到列表头部,当缓存空间满时,淘汰列表尾部的数据。这样可以保证频繁访问的数据总是位于列表的头部,最少访问的数据总是位于列表的尾部。

优点和局限性:
  • 优点:
  • 简单易实现:LRU 缓存算法思路清晰,实现相对简单。
  • 适用范围广:LRU 缓存适用于各种场景,特别是对于访问模式比较集中的情况。
  • 局限性:
  • 缓存污染:当数据访问模式发生变化时,LRU 缓存可能会导致缓存污染问题,即缓存中的数据可能已经不再是热门数据,但仍然被保留在缓存中。
  • 实现复杂度:LRU 缓存需要维护访问历史列表,当数据量较大时,可能会带来较高的实现复杂度和性能开销。
最佳实践和使用场景:
  • 最佳实践:
  • 合理设置缓存大小:根据系统的实际情况和性能需求,合理设置缓存的大小,避免缓存空间过大或过小。
  • 定期清理缓存:定期清理过期或者不再使用的缓存数据,以释放内存资源,避免缓存的无效占用。
  • 考虑缓存预热:在系统启动或者高峰时段之前,通过预热缓存的方式,将热门数据提前加载到缓存中,提高系统的响应速度。
  • 使用场景:
  • 高频热点数据缓存:对于访问频率较高、热点数据集中的场景,LRU 缓存可以有效地提高系统的响应速度和性能。
  • 高并发访问场景:对于需要处理大量并发请求的场景,LRU 缓存可以降低系统的负载,提高系统的并发处理能力。
  • 数据访问模式较稳定的场景:LRU 缓存适用于数据访问模式相对稳定、变化较少的场景,对于访问模式频繁变化的场景,可能需要考虑其他缓存策略。

ThreadLocal缓存机制

ThreadLocal 缓存机制:

ThreadLocal 是一种在多线程环境下使用的特殊缓存机制,它可以在每个线程中保存数据副本,保证每个线程访问的是自己的数据,从而避免了线程安全问题。

如何在线程级别缓存调用结果:

ThreadLocal 缓存机制的使用非常简单,只需要在每个线程中创建一个 ThreadLocal 对象,然后将需要缓存的数据存储在 ThreadLocal 中。这样就可以保证每个线程独立访问自己的数据副本,而不会影响其他线程。

public class MyCache {
    private static ThreadLocal<Map<String, Object>> cache = ThreadLocal.withInitial(HashMap::new);
    public static void put(String key, Object value) {
        cache.get().put(key, value);
    }
    public static Object get(String key) {
        return cache.get().get(key);
    }
}
适用场景和注意事项:
  • 适用场景:
  • 线程级别的数据共享:适用于需要在每个线程中共享数据,但又不希望数据被其他线程访问的场景。
  • 线程池环境下的数据传递:适用于线程池环境下,需要将数据从任务提交线程传递到任务执行线程的场景。
  • 注意事项:
  • 内存泄漏风险:ThreadLocal 使用静态的弱引用来保存线程本地变量,如果不及时清理 ThreadLocal,可能会导致内存泄漏问题。
  • 线程安全问题:虽然 ThreadLocal 可以保证线程之间的数据隔离,但在多线程访问同一个 ThreadLocal 变量时,仍可能存在线程安全问题,需要注意同步控制。
与其他缓存机制的对比:
  • 与LRU缓存对比:
  • LRU 缓存适用于全局的数据共享,通过淘汰最近最少使用的数据来保证缓存的命中率。
  • ThreadLocal 缓存适用于线程级别的数据共享,可以避免线程安全问题,但每个线程只能访问自己的数据副本。
  • 与JCache缓存对比:
  • JCache 是一种标准的缓存规范,提供了统一的API和功能,支持多种缓存实现。
  • ThreadLocal 缓存是一种简单的线程级别缓存,适用于特定的线程间数据共享场景,不具备跨线程的能力。

ThreadLocal 缓存机制适用于需要在线程级别共享数据的场景,可以有效避免线程安全问题,并提高系统的性能和并发能力。但在使用过程中需要注意内存泄漏和线程安全问题,以确保缓存的有效性和稳定性。

JCache缓存机制

JCache 缓存机制:

JCache 是一种标准的缓存规范,旨在提供统一的缓存API和功能,使得开发人员可以在不同的缓存实现之间进行切换和替换。JCache 的最新版本为 JSR-107,它定义了一组缓存接口和相关的规范,为缓存的实现和使用提供了标准化的方法。

JCache标准规范和实现:
  • JCache 标准规范:
  • JCache 规范定义了一组缓存相关的接口和注解,包括 Cache、CacheManager、CacheEntry、ExpiryPolicy 等。
  • 这些接口和注解提供了一套标准化的缓存操作方法,包括数据的存储、检索、过期处理等。
  • JCache 实现:
  • JCache 是一个标准规范,并不是一个具体的实现。根据 JCache 规范,各种缓存提供商(如 Ehcache、Hazelcast、Infinispan 等)都可以实现自己的 JCache 缓存产品。
  • JCache 规范的实现通常会提供一套标准的 API,以及相应的配置和管理工具,使得开发人员可以方便地在不同的缓存产品之间切换和替换。
与JSR-107的关系:

JSR-107 是 Java Community Process 中关于缓存规范的一个标准,定义了 JCache 规范的内容。JCache 是 JSR-107 的具体实现之一,它基于 JSR-107 规范提供了一套标准的缓存API和功能。因此,JCache 可以看作是 JSR-107 规范的一种实现。

在Dubbo中的应用实践和配置方法:

在 Dubbo 中,可以通过配置 JCache 缓存来提高服务的性能和稳定性。以下是在 Dubbo 中应用 JCache 缓存的实践和配置方法:

  1. 引入 JCache 实现库:
  • 首先需要引入具体的 JCache 缓存实现库,如 Ehcache、Hazelcast、Infinispan 等。可以通过 Maven 等依赖管理工具引入相应的库。
  1. 配置 JCache 缓存管理器:
  • 在 Dubbo 的配置文件中,配置 JCache 缓存管理器,指定具体的 JCache 实现和相应的配置参数。可以配置缓存的大小、过期时间、淘汰策略等。
  1. 在服务接口上添加缓存注解:
  • 在需要缓存的服务接口或方法上,添加 JCache 缓存相关的注解,如 @CacheResult、@CachePut、@CacheRemove 等。这些注解用于定义缓存的行为和策略。
  1. 启用 JCache 缓存功能:
  • 在 Dubbo 的配置文件中,启用 JCache 缓存功能,指定缓存管理器和相应的缓存配置。可以通过配置文件或者代码方式启用缓存功能。

通过以上配置和实践,可以在 Dubbo 中使用 JCache 缓存机制,提高服务的性能和稳定性,同时实现缓存的统一管理和标准化。 JCache 提供了一种灵活、标准化的缓存解决方案,适用于各种 Dubbo 项目的缓存需求。

Expiring机制

Expiring 缓存机制:

Expiring 缓存机制是一种基于时间过期的缓存策略,它允许开发人员为缓存中的数据设置生命周期,当数据的生命周期到期时,自动将数据从缓存中移除,以确保缓存中的数据是最新的。

基于时间过期的缓存策略:

Expiring 缓存策略基于时间设置缓存的生命周期,通常使用时间单位(如秒、分钟、小时)来指定数据在缓存中的存储时间。当数据存储时间超过指定的生命周期时,数据将被自动从缓存中淘汰。

如何设置缓存的生命周期:

在 Expiring 缓存策略中,开发人员可以通过以下方式来设置缓存的生命周期:

  1. 在缓存存储数据时设置过期时间:
  • 在将数据存储到缓存中时,同时指定数据的过期时间,通常以时间戳或相对时间(如5分钟后过期)的方式来设置。
  1. 在缓存配置中指定默认的过期时间:
  • 在缓存配置中,可以指定默认的过期时间,所有存储到缓存中的数据都将使用该默认过期时间。这样可以简化数据存储时的设置。
  1. 动态调整缓存的生命周期:
  • 在实际应用中,可能需要根据业务需求动态调整缓存的生命周期。开发人员可以根据业务场景和需求,灵活地调整缓存的过期时间。
处理缓存过期的方式和策略选择:

在 Expiring 缓存策略中,缓存过期时的处理方式通常有以下几种:

  1. 自动淘汰:
  • 当数据的生命周期到期时,缓存系统会自动将数据从缓存中淘汰,不再提供给客户端使用。这是最常见的缓存过期处理方式。
  1. 手动刷新:
  • 当数据的生命周期到期时,缓存系统不会立即将数据从缓存中淘汰,而是等待客户端下一次访问时重新加载数据。这种方式可以减少缓存的刷新频率,降低系统负载。
  1. 异步刷新:
  • 当数据的生命周期到期时,缓存系统会异步地从数据源重新加载数据,并更新缓存。这种方式可以减少对客户端的影响,提高系统的响应速度。
策略选择:

在选择缓存过期处理策略时,需要根据业务需求和系统性能要求进行权衡:

  • 如果数据的更新频率较低,且缓存数据的即时性要求不高,可以选择自动淘汰的方式。
  • 如果数据的更新频率较高,且需要保证缓存数据的及时更新,可以选择手动刷新或异步刷新的方式。
  • 在实际应用中,还可以根据缓存数据的重要性和访问频率,灵活选择合适的过期处理策略。

通过合理设置缓存的生命周期和选择适当的过期处理策略,可以有效提高系统的性能和稳定性,优化用户的使用体验。 Expiring 缓存机制是一种灵活、高效的缓存策略,适用于各种类型的应用场景。

缓存的配置方式

在 Dubbo 中,可以通过 @EnableDubbo 注解或者 XML 配置文件来配置缓存。以下是 Dubbo 中配置缓存的方式:

通过 @EnableDubbo 注解配置:

  1. 在 Spring Boot 项目中使用 @EnableDubbo 注解:
@SpringBootApplication
@EnableDubbo(cache = "lru")
public class DubboApplication {
    public static void main(String[] args) {
        SpringApplication.run(DubboApplication.class, args);
    }
}
  1. 通过 XML 配置文件配置缓存:
<dubbo:consumer cache="lru" />
<dubbo:provider cache="lru" />

在上述示例中,cache 属性用于指定 Dubbo 缓存的类型,可以设置为 lruthreadlocaljcacheexpiring,分别对应 LRUCache、ThreadLocalCache、JCache 和 ExpiringCache 缓存实现。

通过 Dubbo.properties 文件配置:

在 Dubbo.properties 文件中,可以使用 dubbo.cache 属性来配置缓存类型:

# Consumer 缓存类型
dubbo.consumer.cache=lru
# Provider 缓存类型
dubbo.provider.cache=lru

以上是在 Dubbo 中配置缓存的几种方式。根据项目的实际情况和需求,选择合适的配置方式来配置 Dubbo 缓存,以提高系统的性能和稳定性。

极客时间何辉老师引用

  1. 改造方案的数据是存储在JVM内存中,可能会撑爆内存
  2. 如果某些用户的权限发生变更,变更完成到使用新数据容忍时间间隔,如何完成内存数据的刷新操作?
  3. lru的底层
// 过滤器被触发调用的入口
org.apache.dubbo.cache.filter.CacheFilter#invoke
// 根据 invoker.getUrl() 获取缓存容器
org.apache.dubbo.cache.support.AbstractCacheFactory#getCache
// 若缓存容器没有的话,则会自动创建一个缓存容器
org.apache.dubbo.cache.support.lru.LruCacheFactory#createCache
// 最终创建的是一个 LruCache 对象,该对象的内部使用的 LRU2Cache 存储数据
org.apache.dubbo.cache.support.lru.LruCache#LruCache
// 存储调用结果的对象
private final Map<Object, Object> store;
public LruCache(URL url) {
    final int max = url.getParameter("cache.size", 1000);
    this.store = new LRU2Cache<>(max);
}
// LRU2Cache 的带参构造方法,在 LruCache 构造方法中,默认传入的大小是 1000
org.apache.dubbo.common.utils.LRU2Cache#LRU2Cache(int)
public LRU2Cache(int maxCapacity) {
    super(16, DEFAULT_LOAD_FACTOR, true);
    this.maxCapacity = maxCapacity;
    this.preCache = new PreCache<>(maxCapacity);
}
// 若继续放数据时,若发现现有数据个数大于 maxCapacity 最大容量的话
// 则会考虑抛弃掉最古老的一个,也就是会抛弃最早进入缓存的那个对象
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
    return size() > maxCapacity;
}
// JDK 中的 LinkedHashMap 源码在发生节点插入后
// 给了子类一个扩展删除最旧数据的机制
java.util.LinkedHashMap#afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

所以容忍时间间隔不确定,刷新的时效也是不确定的

  1. threadlocal,使用的是 ThreadLocalCacheFactory 工厂类,类名中 ThreadLocal 是本地 线程的意思,而 ThreadLocal 最终还是使用的是 JVM 内存。
    jcache,使用的是 JCacheFactory 工厂类,是提供 javax-spi 缓存实例的工厂类,既然是 一种 spi 机制,可以接入很多自制的开源框架。
    expiring,使用的是 ExpiringCacheFactory 工厂类,内部的 ExpiringCache 中还是使用 的 Map 数据结构来存储数据,仍然使用的是 JVM 内存。
  2. 实现jcache
<!--加入解决NoClassDefFoundError报错问题-->
<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>
<!--解决没有CachingProvider的实现类-->
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.18.0</version>
</dependency>
  1. 解决Default configuration hasn't been specified!
{
  "singleServerConfig": {
    "address": "redis://127.0.0.1:6379"
  }
}
相关文章
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
526 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
678 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
803 0
|
缓存 Dubbo NoSQL
dubbo的缓存实现
dubbo的缓存实现
339 0
|
存储 缓存 NoSQL
dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(六)Spring中Redis的缓存的使用
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.
1136 0
|
2月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
100 1
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
78 6
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
1月前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
238 22