HystrixCacheManager 구현

Dev. 로티·2022년 3월 19일
1

Spring boot

목록 보기
12/12
post-thumbnail

Hystrix에 대해 알고 싶으신 분은 이전 포스팅을 확인해주세요.


개요

회사에서 서비스를 구현하며 조회에 대한 Redis 캐시를 적용하기 위해 CacheManager를 재정의 하였습니다.




RedisCacheManager

@Bean
@Primary
CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheManager.RedisCacheManagerBuilder redisCacheManagerBuilder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
    RedisCacheConfiguration config =
            RedisCacheConfiguration.defaultCacheConfig()
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                    .entryTtl(Duration.ofMinutes(10))
                    .computePrefixWith(...));
    return new redisCacheManagerBuilder.cacheDefaults(config).build();
}

RedisCacheManager를 적용한 후 Redis의 장애로 인해 서비스가 영향을 받을 수도 있을 것이란 생각이 들어 RedisCacheManager에 Hystrix를 적용해보겠다는 다짐을 하게되었고,

이를 구현함으로써 애플리케이션은 Redis Cache에 대해 장애 내성을 가질 수 있게 되었습니다.

오늘은 RedisCacheManager에 Hystrix를 어떻게 적용했는지에 대해 공유해보고자 합니다.




HystrixCacheManager

public class HystrixCacheManager implements CacheManager {
    private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();
    private final CacheManager cacheManger;

    public HystrixCacheManager(CacheManager cacheManger){
        this.cacheManger = cacheManger;
    }

    @Override
    public Cache getCache(String name) {
        return cacheMap.computeIfAbsent(name, key -> new HystrixCache(cacheManger.getCache(key)));
    }

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

우선 HystrixCacheManager는 Proxy 패턴을 사용하고 있는 것을 볼 수 있습니다.

기존 getCacheNames() 메소드는 별다른 처리 없이 주입받은 CacheManager의 기능을 그대로 사용하고 있지만 getCache(String name) 메소드는 기존 캐시를 HystrixCache로 감싸서 처리하고 있는 모습을 볼 수 있습니다.

이때 기존 캐시에 존재하지 않을 경우 cacheMap에 새로운 HystirxCache를 생성하여 넣어주는 것을 볼 수 있는데 동시성 처리를 위해 ConcurrentHashMap을 사용하였습니다.




HystrixCache


public class HystrixCache implements Cache {
    private final Cache cache;

    public HystrixCache(Cache cache) {
        this.cache = cache;
    }

    @Override
    public String getName() {
        return cache.getName();
    }

    @Override
    public Object getNativeCache() {
        return cache.getNativeCache();
    }

    @Override
    public ValueWrapper get(Object key) {
        return new HystrixGetCommand(cache, key).execute();
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        return cache.get(key, type);
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        return cache.get(key, valueLoader);
    }

    @Override
    public void put(Object key, Object value) {
        new HystrixPutCommand(cache, key, value).execute();
    }

    @Override
    public void evict(Object key) {
        new HystrixEvictCommand(cache, key).execute();
    }

    @Override
    public void clear() {
        cache.clear();
    }
}

위 코드 또한 Proxy 패턴을 사용한 것을 볼 수 있는데, 이 코드에서 중요하게 봐야하는 부분은 바로 get(Object key), put(Object key, Object value), evict(Object key) 메소드 3가지 입니다.

위 메소드를 보면 해당 기능을 HystrixCommand로 감싸서 처리하는 모습을 볼 수 있습니다.




HystrixGetCommand

public class HystrixGetCommand extends HystrixCommand<ValueWrapper> {
    private final Cache cache;
    private final Object key;

    protected HystrixGetCommand(Cache cache, Object key) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrix-get"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.defaultSetter()
                                .withExecutionTimeoutInMilliseconds(2000)
                                .withCircuitBreakerErrorThresholdPercentage(50)
                                .withCircuitBreakerRequestVolumeThreshold(5)));
        this.cache = cache;
        this.key = key;
    }

    @Override
    protected ValueWrapper run() throws Exception {
        return cache.get(key);
    }

    @Override
    protected ValueWrapper getFallback() {
        return null;
    }
}



HystrixPutCommand

public class HystrixPutCommand extends HystrixCommand<Object> {
    private final Cache cache;
    private final Object key;
    private final Object value;

    public HystrixPutCommand(Cache cache, Object key, Object value) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrix-put"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.defaultSetter()
                                .withExecutionTimeoutInMilliseconds(2000)
                                .withCircuitBreakerErrorThresholdPercentage(50)
                                .withCircuitBreakerRequestVolumeThreshold(5)));
        this.cache = cache;
        this.key = key;
        this.value = value;
    }

    @Override
    protected Object run() throws Exception {
        cache.put(key, value);
        return null;
    }

    @Override
    protected Object getFallback() {
        return null;
    }
}



HystrixEvictCommand

public class HystrixEvictCommand extends HystrixCommand<Object> {
    private final Cache cache;
    private final Object key;
    public HystrixEvictCommand(Cache cache, Object key) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrix-evict"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.defaultSetter()
                                .withExecutionTimeoutInMilliseconds(2000)
                                .withCircuitBreakerErrorThresholdPercentage(50)
                                .withCircuitBreakerRequestVolumeThreshold(5)));
        this.cache = cache;
        this.key = key;
    }

    @Override
    protected Object run() throws Exception {
        cache.evict(key);
        return null;
    }

    @Override
    protected Object getFallback() {
        return null;
    }
}



마지막으로 CacheManager 설정만 해주시면 HystrixCacheManager 사용 준비가 모두 완료됩니다.

CacheManager Bean 설정 코드

@Bean
@Primary
CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheManager.RedisCacheManagerBuilder redisCacheManagerBuilder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
    RedisCacheConfiguration config =
            RedisCacheConfiguration.defaultCacheConfig()
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                    .entryTtl(Duration.ofMinutes(10))
                    .computePrefixWith(...));
    return new HystrixCacheManager(redisCacheManagerBuilder.cacheDefaults(config).build());
}

0개의 댓글