この記事は1であるの春、データのRedisキャッシュ使用して、それが来るとき、スプリング・データのRedisキャッシュは比較的厳しいです、補足的に、それでも上記の一部が欠けている、それが普遍的なソリューションですしかし、エンタープライズレベルのプロジェクトのために、より多くの問題を解決することが生きるために生きるために、一般的な問題があります
- キャッシュウォームアップ(プロジェクトが始まったときにキャッシュをロード)
- キャッシュの浸透(ヌル直接キャッシュを介して)
- キャッシュ雪崩(キャッシュの大多数が同時に満了します)
- キャッシュの更新(クエリデータは、古いデータの問題です)
- キャッシュのダウングレード
- ときRedisのキャッシュ、Redisのメモリ使用量の問題
この記事では、問題に対処します
強化されたバネのデータのRedisキャッシュ機能、次のような機能を強化
- 設定キャッシュのカスタム有効期限の注釈
- 、アクティブキャッシュのリフレッシュに達しているように、データ検出は、しきい値データに達しているリフレッシュするためにフェッチバッファた場合
- 検出されたデータが集合、マップ、空、空のオブジェクトを含む空のデータ領域に格納されている場合、空の文字列配列空の場合、特定の時刻を設定期限切れ
- バッチの有効期限は、値のシーケンスを使用して設定することができるKryo
- 鍵生成戦略を書き換え、MD5を使用する(目標+メソッド+のparams)
ほとんどのオンラインの記事がお互いをコピーしている参照してください、そして、彼らはすべての古いバージョン、そして時には間違っている、この記事では提供して、スプリング・データのRedis-2.0.10.RELEASE.jarのソリューションのバージョンを。本稿では、コードがテストされていますが、オンライン環境を確認していない、バグを使用するときに注意を払う必要があることもできます。
アイデアの実現
非常に簡単な修正は有効期限の設定initialCacheConfiguration
を実現することができますが、あなたはキャッシュを更新するために、以下を達成することであると述べました
- 傍受の
@Cacheable
実行の方法は、キャッシュを更新する必要がある場合の注意事項は、その後にサインアップするMethodInvoker
と同じRedisの、使用して保存キーに格納し、キー名サフィックスをステッチ - キーの有効期限時間がリフレッシュする閾値に到達する場合、現在のcacheKeyからのRedisを取るために、キャッシュを服用すると
MethodInvoker
、実行方法を - 値の最後のステップは、キャッシュに保存され、有効期限をリセットしています
入門
本明細書に記載のいくつかの方法に使用されるスプリング
// 可以从目标对象获取到真实的 class 对象,而不是代理 class 类对象
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
Object bean = applicationContext.getBean(targetClass);
// 获取到真实的对象,而不是代理对象
Object target = AopProxyUtils.getSingletonTarget(bean );
MethodInvokerは、私はRedisのためにそれをシリアライズ春パッケージ、インターセプタの方法を実行するためのツールです。
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetClass(targetClass);
methodInvoker.setTargetMethod(method.getName());
methodInvoker.setArguments(args);
SpringCacheAnnotationParser春がキャッシュ関連のノートを解決するために使用され、私は結局、それはクラスで構成された、または少し問題を解析することができ、cacheNamesを解決するために所有する必要はありません、cacheNamesを解析するために使用されます。
SpringCacheAnnotationParser annotationParser = new SpringCacheAnnotationParser();
いくつかを達成
カスタム注釈は、有効期限を設定し、しきい値を更新します
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface CacheCustom {
/**
* 缓存失效时间
* 使用 ISO-8601持续时间格式
* Examples:
* <pre>
* "PT20.345S" -- parses as "20.345 seconds"
* "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
* "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
* "P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
* "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
* "P-6H3M" -- parses as "-6 hours and +3 minutes"
* "-P6H3M" -- parses as "-6 hours and -3 minutes"
* "-P-6H+3M" -- parses as "+6 hours and -3 minutes"
* </pre>
* @return
*/
String expire() default "PT60s";
/**
* 刷新时间阀值,不配置将不会进行缓存刷新
* 对于像前端的分页条件查询,建议不配置,这将在内存生成一个执行映射,太多的话将会占用太多的内存使用空间
* 此功能适用于像字典那种需要定时刷新缓存的功能
* @return
*/
String threshold() default "";
/**
* 值的序列化方式
* @return
*/
Class<? extends RedisSerializer> valueSerializer() default KryoRedisSerializer.class;
}
Redisのにセクション、記憶アクチュエータをAOPを作成します
@Aspect
@Component
public class CacheCustomAspect {
@Autowired
private KeyGenerator keyGenerator;
@Pointcut("@annotation(com.sanri.test.testcache.configs.CacheCustom)")
public void pointCut(){}
public static final String INVOCATION_CACHE_KEY_SUFFIX = ":invocation_cache_key_suffix";
@Autowired
private RedisTemplate redisTemplate;
@Before("pointCut()")
public void registerInvoke(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object target = joinPoint.getTarget();
Object cacheKey = keyGenerator.generate(target, method, args);
String methodInvokeKey = cacheKey + INVOCATION_CACHE_KEY_SUFFIX;
if(redisTemplate.hasKey(methodInvokeKey)){
return ;
}
// 将方法执行器写入 redis ,然后需要刷新的时候从 redis 获取执行器,根据 cacheKey ,然后刷新缓存
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetClass(targetClass);
methodInvoker.setTargetMethod(method.getName());
methodInvoker.setArguments(args);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new KryoRedisSerializer());
redisTemplate.opsForValue().set(methodInvokeKey,methodInvoker);
}
}
リフレッシュ閾値未満ならば、リフレッシュするために別のスレッドを開始し、その取得キャッシュの有効期限の時点でオーバーライドされたメソッドのRedisCacheビューを取得し、ここで私は現在同期リフレッシュ午前、同時実行の問題を考慮する必要があります。
@Override
public ValueWrapper get(Object cacheKey) {
if(cacheCustomOperation == null){return super.get(cacheKey);}
Duration threshold = cacheCustomOperation.getThreshold();
if(threshold == null){
// 如果不需要刷新,直接取值
return super.get(cacheKey);
}
//判断是否需要刷新
Long expire = redisTemplate.getExpire(cacheKey);
if(expire != -2 && expire < threshold.getSeconds()){
log.info("当前剩余过期时间["+expire+"]小于刷新阀值["+threshold.getSeconds()+"],刷新缓存:"+cacheKey+",在 cacheNmae为 :"+this.getName());
synchronized (CustomRedisCache.class) {
refreshCache(cacheKey.toString(), threshold);
}
}
return super.get(cacheKey);
}
/**
* 刷新缓存
* @param cacheKey
* @param threshold
* @return
*/
private void refreshCache(String cacheKey, Duration threshold) {
String methodInvokeKey = cacheKey + CacheCustomAspect.INVOCATION_CACHE_KEY_SUFFIX;
MethodInvoker methodInvoker = (MethodInvoker) redisTemplate.opsForValue().get(methodInvokeKey);
if(methodInvoker != null){
Class<?> targetClass = methodInvoker.getTargetClass();
Object target = AopProxyUtils.getSingletonTarget(applicationContext.getBean(targetClass));
methodInvoker.setTargetObject(target);
try {
methodInvoker.prepare();
Object invoke = methodInvoker.invoke();
//然后设置进缓存和重新设置过期时间
this.put(cacheKey,invoke);
long ttl = threshold.toMillis();
redisTemplate.expire(cacheKey,ttl, TimeUnit.MILLISECONDS);
} catch (InvocationTargetException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e) {
log.error("刷新缓存失败:"+e.getMessage(),e);
}
}
}
最後に、RedisCacheManagerにその管理を手渡したカスタムRedisCacheを書き換えます
@Override
public Cache getCache(String cacheName) {
CacheCustomOperation cacheCustomOperation = cacheCustomOperationMap.get(cacheName);
RedisCacheConfiguration redisCacheConfiguration = initialCacheConfiguration.get(cacheName);
if(redisCacheConfiguration == null){redisCacheConfiguration = defaultCacheConfiguration;}
CustomRedisCache customRedisCache = new CustomRedisCache(cacheName,cacheWriter,redisCacheConfiguration, redisTemplate, applicationContext, cacheCustomOperation);
customRedisCache.setEmptyKeyExpire(this.emptyKeyExpire);
return customRedisCache;
}
説明:この記事は、gitee上の完全なコードを、コードの重要な部分を取られます
その他のヘルプ
歪みの文字列のMD5鍵生成方法の使用が保存されているかわからないので、ここでのソリューションが提供され、キーは、リフレッシュ時間は、それに対応する方法を取っている必要があります。実際に、私は、現在のメソッドのRedis実行情報ストレージに対応するインターセプタにキーを入れている、抗シーケンスは、降水情報を解決するためのクラスやメソッドを実行することができます。
リトル・プロモーション
書き込みは容易ではない、私はバグを入れて、オープンソースソフトウェアのサポートを期待して、私のガジェット、ポイントスター、フォークをgiteeを歓迎します。
エクセル共通インポートおよびエクスポートは、Excelの数式のサポート
:ブログのアドレスhttps://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poiを
ガジェットは、データベースからコードを生成、テンプレートコードを使用し、いくつかのプロジェクトは、多くの場合に使用することができます
ブログのアドレス:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/ sanri / sanri-ツール-達人