web-dev-qa-db-ja.com

redis接続が失敗した場合に実行時にRedisキャッシングを無効にする方法

残りのAPIアプリケーションがあります。 API応答キャッシングと内部メソッドキャッシングにはredisを使用します。接続を再実行すると、APIがダウンします。 APIをダウンさせるのではなく、redis接続が失敗した場合や例外が発生した場合に、redisキャッシュをバイパスしたいと考えています。インターフェースCacheErrorHandlerがありますが、redis接続の問題ではなくredis getset操作の失敗を処理します。 Spring4.1.2を使用しています。

14
cooldude

これを少し煮詰めましょう。アプリケーションはキャッシュを使用します(Redisで実装)。 Redis接続が古くなっている/閉じられているなどの場合は、アプリケーションでキャッシュをバイパスし、(おそらく)基盤となるデータストア(RDBMSなど)に直接アクセスする必要があります。アプリケーションのサービスロジックは次のようになります...

@Service
class CustomerService ... {

    @Autowired
    private CustomerRepository customerRepo;

    protected CustomerRepository getCustomerRepo() {
        Assert.notNull(customerRepo, "The CustomerRepository was not initialized!");
        return customerRepo;
    }

    @Cacheable(value = "Customers")
    public Customer getCustomer(Long customerId) {
        return getCustomerRepo().load(customerId);
    }
    ...
}

SpringコアのCachingAbstractionでキャッシュの「ミス」を確認するために重要なのは、返される値がnullであるということだけです。そのため、Spring Caching Infrastructureは、実際のServiceメソッド(つまり、getCustomer)の呼び出しに進みます。 getCustomerRepo()。load(customerId)呼び出しが返されることに注意してください。また、Springのキャッシングインフラストラクチャが値をキャッシュしようとする場合も処理する必要があります。

シンプルに保つという精神で、AOPなしで行いますが、AOPを使用してもこれを達成できるはずです(選択)。

必要なのは、 SDR CacheManager実装 を拡張する「カスタム」RedisCacheManagerだけです。

package example;

import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
...

class MyCustomRedisCacheManager extends RedisCacheManager {

    public MyCustomerRedisCacheManager(RedisTemplate redisTemplate) {
        super(redisTemplate);
    }

    @Override
    public Cache getCache(String name) {
        return new RedisCacheWrapper(super.getCache(name));
    }


    protected static class RedisCacheWrapper implements Cache {

        private final Cache delegate;

        public RedisCacheWrapper(Cache redisCache) {
            Assert.notNull(redisCache, "'delegate' must not be null");
            this.delegate = redisCache;
        }

        @Override
        public Cache.ValueWrapper get(Object key) {
            try {
              delegate.get(key);
            }
            catch (Exception e) {
                return handleErrors(e);
            }
        }

        @Override
        public void put(Object key, Object value) {
            try {
                delegate.put(key, value);
            }
            catch (Exception e) {
                handleErrors(e);
            }
        }

        // implement clear(), evict(key), get(key, type), getName(), getNativeCache(), putIfAbsent(key, value) accordingly (delegating to the delegate).

        protected <T> T handleErrors(Exception e) throws Exception {
            if (e instanceof <some RedisConnection Exception type>) {
                // log the connection problem
                return null;
            }
            else if (<something different>) { // act appropriately }
            ...
            else {
                throw e;
            }
        }
    }
}

したがって、Redisが利用できない場合、おそらく最善の方法は、問題をログに記録し、サービスの呼び出しを実行させることです。明らかに、これはパフォーマンスを阻害しますが、少なくとも問題が存在するという認識を高めます。明らかに、これはより堅牢な通知システムに結び付けることができますが、それは可能性の大まかな例です。重要なことは、アプリケーションサービスが依存する他のサービス(Redisなど)が失敗した可能性がある間、サービスは引き続き利用可能であるということです。

この実装(以前の説明と比較して)では、基礎となる実際のRedisCache実装に委任して例外を発生させ、Redisに問題が存在することを十分に理解し、例外を適切に処理できるようにすることを選択しました。ただし、検査時に例外が接続の問題に関連していることが確実な場合は、「null」を返して、Spring Caching Infrastructureをキャッシュの「ミス」であるかのように進めることができます(つまり、Redis接続の不良==キャッシュミス、この場合)。

GemFireとPivotalの顧客の1人のために、「カスタム」CacheManager実装の同様のプロトタイプを作成したので、このようなものが問題を解決するはずです。その特定のUCでは、キャッシュの「ミス」は、アプリケーションドメインオブジェクトの「古いバージョン」によってトリガーされる必要がありました。本番環境では、Springのキャッシング抽象化を介してGemFireに接続する新しいアプリケーションクライアントと古いアプリケーションクライアントが混在していました。たとえば、アプリケーションドメインオブジェクトフィールドは、アプリの新しいバージョンで変更されます。

とにかく、これがあなたにもっと多くのアイデアを助けたり与えたりすることを願っています。

乾杯!

14
John Blum

それで、私は今日、別の質問に対処するコアSpring Framework Caching Abstractionソースを掘り下げていましたが、CacheErrorHandlerが適切に実装されている場合、問題のあるRedis接続でも目的の動作が発生する可能性があります。キャッシュ「ミス」(null値の戻りでトリガーされます)。

詳細については、 AbstractCacheInvoker ソースを参照してください。

cache.get(key)は、Redis接続の障害が原因で例外が発生するため、例外ハンドラーが呼び出されます。

catch (RuntimeException e) {
    getErrorHandler().handleCacheGetError(e, cache, key);
    return null; // If the exception is handled, return a cache miss
}

CacheErrorHandlerがキャッシュの「取得」エラーを適切に処理する場合(および例外を再スローしない場合)、キャッシュの「ミス」を示すnull値が返されます。

7
John Blum

@JohnBlumに感謝します。 Spring Bootでの私の解決策は次のとおりです。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;

import Java.util.concurrent.Callable;

class CustomRedisCacheManager extends RedisCacheManager {
    private static Logger logger = LoggerFactory.getLogger(CustomRedisCacheManager.class);

    public CustomRedisCacheManager(RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    public Cache getCache(String name) {
        return new RedisCacheWrapper(super.getCache(name));
    }


    protected static class RedisCacheWrapper implements Cache {

        private final Cache delegate;

        public RedisCacheWrapper(Cache redisCache) {
            Assert.notNull(redisCache, "delegate cache must not be null");
            this.delegate = redisCache;
        }

        @Override
        public String getName() {
            try {
                return delegate.getName();
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public Object getNativeCache() {
            try {
                return delegate.getNativeCache();
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public Cache.ValueWrapper get(Object key) {
            try {
                return delegate.get(key);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public <T> T get(Object o, Class<T> aClass) {
            try {
                return delegate.get(o, aClass);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public <T> T get(Object o, Callable<T> callable) {
            try {
                return delegate.get(o, callable);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public void put(Object key, Object value) {
            try {
                delegate.put(key, value);
            } catch (Exception e) {
                handleException(e);
            }
        }

        @Override
        public ValueWrapper putIfAbsent(Object o, Object o1) {
            try {
                return delegate.putIfAbsent(o, o1);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public void evict(Object o) {
            try {
                delegate.evict(o);
            } catch (Exception e) {
                handleException(e);
            }
        }

        @Override
        public void clear() {
            try {
                delegate.clear();
            } catch (Exception e) {
                handleException(e);
            }
        }

        private <T> T handleException(Exception e) {
            logger.error("handleException", e);
            return null;
        }
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        CustomRedisCacheManager redisCacheManager = new CustomRedisCacheManager(redisTemplate);
        redisCacheManager.setUsePrefix(true);
        return redisCacheManager;
    }
}
4
hxy

実際、私の応答は@Vivek Aditya氏に向けられています-私は同じ問題に直面しました:新しいspring-data-redis apiであり、RedisTemplateごとにRedisCacheManagerを構築していません。 @John Blumの提案に基づく唯一のオプションは、アスペクトを使用することでした。そして、以下は私のコードです。

@Aspect
@Component
public class FailoverRedisCacheAspect {

    private static class FailoverRedisCache extends RedisCache {

        protected FailoverRedisCache(RedisCache redisCache) {
            super(redisCache.getName(), redisCache.getNativeCache(), redisCache.getCacheConfiguration());
        }

        @Override
        public <T> T get(Object key, Callable<T> valueLoader) {
            try {
                return super.get(key, valueLoader);
            } catch (RuntimeException ex) {
                return valueFromLoader(key, valueLoader);
            }
        }

        private <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
            try {
                return valueLoader.call();
            } catch (Exception e) {
                throw new ValueRetrievalException(key, valueLoader, e);
            }
        }
    }

    @Around("execution(* org.springframework.cache.support.AbstractCacheManager.getCache (..))")
    public Cache beforeSampleCreation(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            Cache cache = (Cache) proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            if (cache instanceof RedisCache) {
                return new FailoverRedisCache((RedisCache) cache);
            } else {
                return cache;
            }
        } catch (Throwable ex) {
            return null;
        }
    }
}

すべての合理的なシナリオで正常に機能します。

  • アプリはredisダウンで正常に起動します
  • アプリは(まだ)(突然の)redisの停止中に動作します
  • redisが再び機能し始めると、アプリはそれを認識します

編集:コードはpocに似ています-「get」専用であり、キャッシュがヒットするたびにFailoverRedisCacheを再インスタンス化するのは好きではありません-マップがあるはずです。

3
zorro

私も同じ問題を抱えていましたが、残念ながら、上記の解決策はどれも私にはうまくいきません。問題を確認したところ、Redisへの接続がない場合、実行されたコマンドがタイムアウトにならないことがわかりました。それで私は解決策のためにレタスライブラリを研究し始めます。接続がないときにコマンドを拒否することで問題を解決します。

@Bean
public LettuceConnectionFactory lettuceConnectionFactory()
{
    final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).build();
    ClientOptions clientOptions = ClientOptions.builder()
            .socketOptions(socketOptions)
            .autoReconnect(true)
            .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
            .build();

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            .commandTimeout(Duration.ofSeconds(10))
            .clientOptions(clientOptions).build();

    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(this.Host, this.port);
    return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);

}
0
khesam109

すべてのコアSpring Frameworkキャッシュ抽象化アノテーション (例:@Cacheable)と JSR-107 JCacheアノテーションコアSFによってサポートされています 基盤となる CacheManager の内部に委任し、Redisの場合は RedisCacheManager です。

ここに似たSpring XML構成メタデータでRedisCacheManagerを構成します

1つのアプローチは、 RedisConnectionRedisTemplate から間接的に)を使用して各(Redis)の接続の状態を確認する(Redis)CacheManagerのAOPプロキシを作成することです。 )CacheManger操作。

標準のキャッシュ操作で接続が失敗したか閉じられた場合、(Redis)CacheManagerは RedisCache for getCache(String name) のインスタンスを返す可能性があります。これは常にnullを返します。 (エントリのキャッシュミスを示します)、したがって、基になるデータストアに渡されます。

私はRedis(またはSDR)のすべての専門家ではないので、これを処理するためのより良い方法があるかもしれませんが、これは機能し、おそらくあなた自身のいくつかのアイデアを与えるはずです。

乾杯。

0
John Blum