微服务架构已成为当今软件开发领域的主流趋势,而 Dubbo 作为一种优秀的微服务框架,其在性能优化方面有着独到的见解。然而,随着服务规模的增长,微服务架构中的性能问题也变得日益突出。就像一辆车需要油来运转一样,微服务也需要一种有效的机制来提高其性能,而 Dubbo 的缓存机制正是为此而生!
LRU(Least Recently Used)是一种常见的缓存淘汰算法,其工作原理是根据数据的访问历史来淘汰最近最少使用的数据,以保证缓存中的数据始终是最热门的数据。
LRU 缓存机制基于数据的访问时间进行淘汰。当缓存空间满时,新加入的数据会替换掉最久未被访问的数据。LRU 缓存通常使用链表和哈希表实现。哈希表用于快速查询缓存中的数据,链表用于记录数据的访问顺序。
LRU 缓存算法的核心思想是维护一个有序的访问历史列表,当有新数据访问时,将数据移动到列表头部,当缓存空间满时,淘汰列表尾部的数据。这样可以保证频繁访问的数据总是位于列表的头部,最少访问的数据总是位于列表的尾部。
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 缓存机制适用于需要在线程级别共享数据的场景,可以有效避免线程安全问题,并提高系统的性能和并发能力。但在使用过程中需要注意内存泄漏和线程安全问题,以确保缓存的有效性和稳定性。
JCache 是一种标准的缓存规范,旨在提供统一的缓存API和功能,使得开发人员可以在不同的缓存实现之间进行切换和替换。JCache 的最新版本为 JSR-107,它定义了一组缓存接口和相关的规范,为缓存的实现和使用提供了标准化的方法。
JSR-107 是 Java Community Process 中关于缓存规范的一个标准,定义了 JCache 规范的内容。JCache 是 JSR-107 的具体实现之一,它基于 JSR-107 规范提供了一套标准的缓存API和功能。因此,JCache 可以看作是 JSR-107 规范的一种实现。
在 Dubbo 中,可以通过配置 JCache 缓存来提高服务的性能和稳定性。以下是在 Dubbo 中应用 JCache 缓存的实践和配置方法:
通过以上配置和实践,可以在 Dubbo 中使用 JCache 缓存机制,提高服务的性能和稳定性,同时实现缓存的统一管理和标准化。 JCache 提供了一种灵活、标准化的缓存解决方案,适用于各种 Dubbo 项目的缓存需求。
Expiring 缓存机制是一种基于时间过期的缓存策略,它允许开发人员为缓存中的数据设置生命周期,当数据的生命周期到期时,自动将数据从缓存中移除,以确保缓存中的数据是最新的。
Expiring 缓存策略基于时间设置缓存的生命周期,通常使用时间单位(如秒、分钟、小时)来指定数据在缓存中的存储时间。当数据存储时间超过指定的生命周期时,数据将被自动从缓存中淘汰。
在 Expiring 缓存策略中,开发人员可以通过以下方式来设置缓存的生命周期:
在 Expiring 缓存策略中,缓存过期时的处理方式通常有以下几种:
在选择缓存过期处理策略时,需要根据业务需求和系统性能要求进行权衡:
通过合理设置缓存的生命周期和选择适当的过期处理策略,可以有效提高系统的性能和稳定性,优化用户的使用体验。 Expiring 缓存机制是一种灵活、高效的缓存策略,适用于各种类型的应用场景。
在 Dubbo 中,可以通过 @EnableDubbo
注解或者 XML 配置文件来配置缓存。以下是 Dubbo 中配置缓存的方式:
@SpringBootApplication
@EnableDubbo(cache = "lru")
public class DubboApplication {
public static void main(String[] args) {
SpringApplication.run(DubboApplication.class, args);
}
}
<dubbo:consumer cache="lru" />
<dubbo:provider cache="lru" />
在上述示例中,cache
属性用于指定 Dubbo 缓存的类型,可以设置为 lru
、threadlocal
、jcache
或 expiring
,分别对应 LRUCache、ThreadLocalCache、JCache 和 ExpiringCache 缓存实现。
在 Dubbo.properties 文件中,可以使用 dubbo.cache
属性来配置缓存类型:
# Consumer 缓存类型
dubbo.consumer.cache=lru
# Provider 缓存类型
dubbo.provider.cache=lru
以上是在 Dubbo 中配置缓存的几种方式。根据项目的实际情况和需求,选择合适的配置方式来配置 Dubbo 缓存,以提高系统的性能和稳定性。
改造方案的数据是存储在JVM内存中,可能会撑爆内存
如果某些用户的权限发生变更,变更完成到使用新数据容忍时间间隔,如何完成内存数据的刷新操作?
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);
}
}
所以容忍时间间隔不确定,刷新的时效也是不确定的
threadlocal,使用的是 ThreadLocalCacheFactory 工厂类,类名中 ThreadLocal 是本地 线程的意思,而 ThreadLocal 最终还是使用的是 JVM 内存。
jcache,使用的是 JCacheFactory 工厂类,是提供 javax-spi 缓存实例的工厂类,既然是 一种 spi 机制,可以接入很多自制的开源框架。
expiring,使用的是 ExpiringCacheFactory 工厂类,内部的 ExpiringCache 中还是使用 的 Map 数据结构来存储数据,仍然使用的是 JVM 内存。
实现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>
解决Default configuration hasn't been specified!
{
"singleServerConfig": {
"address": "redis://127.0.0.1:6379"
}
}