ホームページ  >  記事  >  データベース  >  Redis+Caffeine が分散 2 次キャッシュ コンポーネントを実装する方法

Redis+Caffeine が分散 2 次キャッシュ コンポーネントを実装する方法

WBOY
WBOY転載
2023-05-30 23:10:58907ブラウズ

いわゆる二次キャッシュ

キャッシュとは、読み取り速度の遅いメディアからデータを読み取り、それをディスク-->メモリなどの読み取り速度の速いメディアに置くことです。

通常、データはデータベースなどのディスクに保存されます。毎回データベースから読み込むとディスク自体のIOに読み込み速度が影響を受けるため、redisのようなメモリキャッシュが存在します。データを読み出してメモリに書き込むことができるため、データを取得する必要がある場合にメモリから直接データを返すことができるため、速度が大幅に向上します。
ただし、通常、redis はクラスターに個別にデプロイされるため、ネットワーク IO の消費が発生します。redis クラスターとの接続には接続プールなどのツールが既に存在しますが、それでもデータ送信にある程度の消費が発生します。 。したがって、カフェインなどのインプロセス キャッシュが存在します。アプリケーション内キャッシュに修飾されたデータがある場合、ネットワーク経由で Redis からデータを取得することなく直接使用でき、これにより 2 レベルのキャッシュが形成されます。アプリケーション内キャッシュは 1 次キャッシュと呼ばれ、リモート キャッシュ (redis など) は 2 次キャッシュと呼ばれます。

  • システムは CPU 使用率をキャッシュする必要がありますか: 計算して結果を取得するために大量の CPU を消費する必要がある特定のアプリケーションがある場合。

  • データベース接続プールが比較的アイドル状態である場合は、データベースの IO リソースを占有するためにキャッシュを使用しないでください。データベース接続プールがビジー状態である場合、または接続不足に関する警告が頻繁に報告される場合は、キャッシュの使用を検討してください。

分散 2 次キャッシュの利点

Redis はホット データの保存に使用され、Redis にないデータはデータベースから直接アクセスされます。
すでに Redis を持っているのに、Guava や Caffeine などのプロセス キャッシュについて知る必要があるのはなぜですか:

  • Redis が利用できない場合、現時点ではデータベースにアクセスすることしかできません。 、雪崩を簡単に引き起こす可能性がありますが、これは通常は起こりません。

  • Redis にアクセスすると、特定のネットワーク I/O、シリアル化および逆シリアル化のオーバーヘッドが発生します。パフォーマンスは非常に高いですが、結局のところ、ローカル方式ほど高速ではありません。最もホットなデータは、アクセスをさらに高速化するためにローカルに保存されます。この考え方は当社のインターネット アーキテクチャに限ったものではなく、コンピュータ システムでは L1、L2、L3 のマルチレベル キャッシュを使用して、メモリへの直接アクセスを減らし、アクセスを高速化しています。

つまり、Redis を使用するだけであれば、ほとんどのニーズを満たすことができますが、より高いパフォーマンスとより高い可用性を追求する必要がある場合は、マルチレベル キャッシュを理解する必要があります。

レベル 2 キャッシュ操作プロセスのデータ読み取りプロセスの説明

Redis+Caffeine が分散 2 次キャッシュ コンポーネントを実装する方法

Redis もローカル キャッシュも値をクエリできない場合、更新プロセスがトリガーされます。プロセス全体がロックされたキャッシュ無効化プロセスの説明

Redis+Caffeine が分散 2 次キャッシュ コンポーネントを実装する方法

Redis の更新とキャッシュ キーの削除がトリガーされます。Redis キャッシュをクリアした後

コンポーネントの使用方法?

コンポーネントは Spring Cache フレームワークに基づいて変更されています。プロジェクトで分散キャッシュを使用するには、cacheManager = "L2_CacheManager" または cacheManager = CacheRedisCaffeineAutoConfiguration を追加するだけです。分散第 2 レベル キャッシュ

//这个方法会使用分布式二级缓存来提供查询
@Cacheable(cacheNames = CacheNames.CACHE_12HOUR, cacheManager = "L2_CacheManager")
public Config getAllValidateConfig() { 
}

分散キャッシュと分散 2 次キャッシュ コンポーネントの両方を使用したい場合は、@Primary CacheManager Bean を Spring に注入する必要があります

@Primary
@Bean("deaultCacheManager")
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    // 设置缓存的默认过期时间,也是使用Duration设置
    config = config.entryTtl(Duration.ofMinutes(2)).disableCachingNullValues();

    // 设置一个初始化的缓存空间set集合
    Set<String> cacheNames =  new HashSet<>();
    cacheNames.add(CacheNames.CACHE_15MINS);
    cacheNames.add(CacheNames.CACHE_30MINS);

    // 对每个缓存空间应用不同的配置
    Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
    configMap.put(CacheNames.CACHE_15MINS, config.entryTtl(Duration.ofMinutes(15)));
    configMap.put(CacheNames.CACHE_30MINS, config.entryTtl(Duration.ofMinutes(30)));
  
    // 使用自定义的缓存配置初始化一个cacheManager
    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
        .initialCacheNames(cacheNames)  // 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
        .withInitialCacheConfigurations(configMap)
        .build();
    return cacheManager;
}

その後:

//这个方法会使用分布式二级缓存
@Cacheable(cacheNames = CacheNames.CACHE_12HOUR, cacheManager = "L2_CacheManager")
public Config getAllValidateConfig() {
}

//这个方法会使用分布式缓存
@Cacheable(cacheNames = CacheNames.CACHE_12HOUR)
public Config getAllValidateConfig2() {
}

Core 実装method

実際の核心は、org.springframework.cache.CacheManager インターフェースを実装し、org.springframework.cache.support.AbstractValueAdaptingCache を継承して、Spring キャッシュ フレームワークでのキャッシュの読み取りと書き込みを実装することです。

RedisCaffeineCacheManager は CacheManager インターフェイスを実装します

RedisCaffeineCacheManager.class は主にキャッシュ インスタンスを管理し、さまざまな CacheName に基づいて対応するキャッシュ管理 Bean を生成し、それらをマップに配置します。

package com.axin.idea.rediscaffeinecachestarter.support;

import com.axin.idea.rediscaffeinecachestarter.CacheRedisCaffeineProperties;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RedisCaffeineCacheManager implements CacheManager {

    private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCacheManager.class);

    private static ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();

    private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;

    private RedisTemplate<Object, Object> stringKeyRedisTemplate;

    private boolean dynamic = true;

    private Set<String> cacheNames;
    {
        cacheNames = new HashSet<>();
        cacheNames.add(CacheNames.CACHE_15MINS);
        cacheNames.add(CacheNames.CACHE_30MINS);
        cacheNames.add(CacheNames.CACHE_60MINS);
        cacheNames.add(CacheNames.CACHE_180MINS);
        cacheNames.add(CacheNames.CACHE_12HOUR);
    }
    public RedisCaffeineCacheManager(CacheRedisCaffeineProperties cacheRedisCaffeineProperties,
                                     RedisTemplate<Object, Object> stringKeyRedisTemplate) {
        super();
        this.cacheRedisCaffeineProperties = cacheRedisCaffeineProperties;
        this.stringKeyRedisTemplate = stringKeyRedisTemplate;
        this.dynamic = cacheRedisCaffeineProperties.isDynamic();
    }

    //——————————————————————— 进行缓存工具 ——————————————————————
    /**
    * 清除所有进程缓存
    */
    public void clearAllCache() {
        stringKeyRedisTemplate.convertAndSend(cacheRedisCaffeineProperties.getRedis().getTopic(), new CacheMessage(null, null));
    }

    /**
    * 返回所有进程缓存(二级缓存)的统计信息
    * result:{"缓存名称":统计信息}
    * @return
    */
    public static Map<String, CacheStats> getCacheStats() {
        if (CollectionUtils.isEmpty(cacheMap)) {
            return null;
        }

        Map<String, CacheStats> result = new LinkedHashMap<>();
        for (Cache cache : cacheMap.values()) {
            RedisCaffeineCache caffeineCache = (RedisCaffeineCache) cache;
            result.put(caffeineCache.getName(), caffeineCache.getCaffeineCache().stats());
        }
        return result;
    }

    //—————————————————————————— core —————————————————————————
    @Override
    public Cache getCache(String name) {
        Cache cache = cacheMap.get(name);
        if(cache != null) {
            return cache;
        }
        if(!dynamic && !cacheNames.contains(name)) {
            return null;
        }

        cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(name), cacheRedisCaffeineProperties);
        Cache oldCache = cacheMap.putIfAbsent(name, cache);
        logger.debug("create cache instance, the cache name is : {}", name);
        return oldCache == null ? cache : oldCache;
    }

    @Override
    public Collection<String> getCacheNames() {
        return this.cacheNames;
    }

    public void clearLocal(String cacheName, Object key) {
        //cacheName为null 清除所有进程缓存
        if (cacheName == null) {
            log.info("清除所有本地缓存");
            cacheMap = new ConcurrentHashMap<>();
            return;
        }

        Cache cache = cacheMap.get(cacheName);
        if(cache == null) {
            return;
        }

        RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
        redisCaffeineCache.clearLocal(key);
    }

    /**
    * 实例化本地一级缓存
    * @param name
    * @return
    */
    private com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache(String name) {
        Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
        CacheRedisCaffeineProperties.CacheDefault cacheConfig;
        switch (name) {
            case CacheNames.CACHE_15MINS:
                cacheConfig = cacheRedisCaffeineProperties.getCache15m();
                break;
            case CacheNames.CACHE_30MINS:
                cacheConfig = cacheRedisCaffeineProperties.getCache30m();
                break;
            case CacheNames.CACHE_60MINS:
                cacheConfig = cacheRedisCaffeineProperties.getCache60m();
                break;
            case CacheNames.CACHE_180MINS:
                cacheConfig = cacheRedisCaffeineProperties.getCache180m();
                break;
            case CacheNames.CACHE_12HOUR:
                cacheConfig = cacheRedisCaffeineProperties.getCache12h();
                break;
            default:
                cacheConfig = cacheRedisCaffeineProperties.getCacheDefault();
        }
        long expireAfterAccess = cacheConfig.getExpireAfterAccess();
        long expireAfterWrite = cacheConfig.getExpireAfterWrite();
        int initialCapacity = cacheConfig.getInitialCapacity();
        long maximumSize = cacheConfig.getMaximumSize();
        long refreshAfterWrite = cacheConfig.getRefreshAfterWrite();

        log.debug("本地缓存初始化:");
        if (expireAfterAccess > 0) {
            log.debug("设置本地缓存访问后过期时间,{}秒", expireAfterAccess);
            cacheBuilder.expireAfterAccess(expireAfterAccess, TimeUnit.SECONDS);
        }
        if (expireAfterWrite > 0) {
            log.debug("设置本地缓存写入后过期时间,{}秒", expireAfterWrite);
            cacheBuilder.expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS);
        }
        if (initialCapacity > 0) {
            log.debug("设置缓存初始化大小{}", initialCapacity);
            cacheBuilder.initialCapacity(initialCapacity);
        }
        if (maximumSize > 0) {
            log.debug("设置本地缓存最大值{}", maximumSize);
            cacheBuilder.maximumSize(maximumSize);
        }
        if (refreshAfterWrite > 0) {
            cacheBuilder.refreshAfterWrite(refreshAfterWrite, TimeUnit.SECONDS);
        }
        cacheBuilder.recordStats();
        return cacheBuilder.build();
    }
}

RedisCaffeineCache は AbstractValueAdaptingCache を継承します

中心となるのは get メソッドと put メソッドです。

rree

以上がRedis+Caffeine が分散 2 次キャッシュ コンポーネントを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。