개발 이야기/SPRING

Spring - 객체캐싱 (SSM, memcached)

최서리나리 2019. 11. 14. 17:42

캐시가 도입된 서비스의 구성도

 

오늘날 빠른 속도의 웹서비스를 제공하기 위한 전략들은 여러가지가 있다.

이미지 캐싱, 객체 캐싱, CDN.. 그리고 스케일아웃 - 스케일업 까지.

 

하지만 여러가지 전략들 중에 가장 쉽고 효과가 좋은녀석은 개인적으로 캐싱이 아닐까 한다.

오늘은 웹서비스의 주요 병목구간인 (비즈니스 레이어 - RDB) 구간을 캐싱하여 성능향상을 꾀해보고자 한다.

 

구성도에도 나와있지만 (비즈니스 레이어 - RDB) 구간 사이에 캐시를 넣고

클라이언트의 요청이 발생 할 경우 캐시에서 처리가 가능한 부분은 캐시에서 객체로 리턴, 캐시에서 처리가 불가능한 (데이터가 없다거나..) 사항들은 rdb로 다시 요청하고 수신된 객체를 캐시에 저장해두는 단순한 흐름이다.

 

여러 캐시 시스템들이 있지만 (redis, memcached 그리고 스프링에서 자체적으로 지원하는 캐시들 등등...)

글에서는 memcached를 사용 할 것이다.

 

 

* 개발환경

 - maven

 - spring 5 (java config)

 

 

memcached의 설치에 대한 사항은 해당 글에서 언급하지 않는다.

아래의 글을 확인하여 각자 설치 후 진행

 

 

 

Linux : https://idchowto.com/?p=38249

 

idchowto.com - 스마일서브(Cloudv.kr)

IDC구축,운영,보안등 모든 지식을 공유합니다

idchowto.com

Windows : https://dev18.tistory.com/11

 

memcached 윈도우 설치 및 세션공유

일반적으로 php에서 세션은 파일로 많이 사용하십니다. 하지만 웹 서버가 2대 이상일경우 로그인이 풀리는 문제가 발생될 수 있습니다. A서버에서 로그인을 해서 세션을 얻었는데 B서버로 페이지 이동을 하면 바..

dev18.tistory.com

 

설치 후 memcached서버에 정상적으로 접속이 된다면 완료된것이다.

 

 

그 후 spring에서 memcached를 사용하기 위해서 아래 라이브러리들이 maven에 추가한다.

 

 - pom.xml

        <!-- memcached -->
        <dependency>
            <groupId>com.google.code.simple-spring-memcached</groupId>
            <artifactId>spring-cache</artifactId>
            <version>3.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.simple-spring-memcached</groupId>
            <artifactId>simple-spring-memcached</artifactId>
            <version>3.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.simple-spring-memcached</groupId>
            <artifactId>xmemcached-provider</artifactId>
            <version>3.6.1</version>
        </dependency>

 

 

캐시에 사용될 기본적인 설정사항들을 전역변수화 할것이다.

맴캐시드의 소켓 타임아웃, 캐시 유지시간, 캐시 이름, 서버정보를 프로퍼티에 작성한다.

 

 - *.properties

# 캐시이름
memcached.cacheName=test
# 소켓타임아웃
memcached.socketTimeOut=10000
# 캐시유지시간 (s)
memcached.expiration=300
# 서버정보
memcached.servers=localhost:11211

 

 

스프링에서 사용할 수 있도록 설정해준다.

 * 아래 주석처리 되어있는 부분의 setUseNameAsKeyPrefix와 setKeyPrefixSeparator는 키 구분자를 사용하겠다는 의미이다.

   아래와 같이 설정하게 되면, 키 구조가 다음과 같이 이루어 진다.

   (test:rdbService:getApi:23)

 * @EnableAspectJAutoProxy는 반드시 필요하다.

 

 - MemCacheConfig.java

@Configuration
@EnableCaching
@EnableAspectJAutoProxy
public class MemCacheConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {

        final String servers = GetProperties.getProperty("memcached.servers");
        final String operationTimeout = GetProperties.getProperty("memcached.socketTimeOut");

        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setConsistentHashing(true);
        cacheConfiguration.setUseBinaryProtocol(true);
        cacheConfiguration.setOperationTimeout(Integer.getInteger(operationTimeout));
        // Cache Name Key Prefix
        cacheConfiguration.setUseNameAsKeyPrefix(true);
        cacheConfiguration.setKeyPrefixSeparator(":");

        MemcacheClientFactoryImpl cacheClientFactory = new MemcacheClientFactoryImpl();

        AddressProvider addressProvider = new DefaultAddressProvider(servers);

        CacheFactory cacheFactory = new CacheFactory();
        cacheFactory.setCacheName(GetProperties.getProperty("memcached.cacheName"));
        cacheFactory.setCacheClientFactory(cacheClientFactory);
        cacheFactory.setAddressProvider(addressProvider);
        cacheFactory.setConfiguration(cacheConfiguration);

        Cache object = null;
        try {
            object = cacheFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        SSMCache ssmCache = new SSMCache(object, Integer.parseInt(GetProperties.getProperty("memcached.expiration")), true);
        ArrayList ssmCaches = new ArrayList<>();
        ssmCaches.add(0, ssmCache);
        ExtendedSSMCacheManager ssmCacheManager = new ExtendedSSMCacheManager();
        ssmCacheManager.setCaches(ssmCaches);
        return ssmCacheManager;
    }

}

 

 

그리고 설정한 MemcacheConfig를 Initializer Context에 등록한다. (xml config의 경우 initializer에 대응되는 context에 주입)

 

public class ApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(MemCacheConfig.class);
        servletContext.addListener(new ContextLoaderListener(rootContext));
        this.dispatcherServlet(servletContext);
        ...
    }

    ...

}

 

 

이렇게 하면 기본적인 사용준비는 끝난다.

실제로 사용은 아래와같이 사용하면 된다.

 

    /**
     * 메뉴 정보 GET
     * @param vo
     * @return
     */
    @UpdateMultiCache(namespace = "menuList")
    @ReadThroughMultiCache(namespace = "menuList")
    @Cacheable(value="test", key="'menuDetail'.concat(':').concat(#vo.getMenu_info_seq()).concat(':').concat(#vo.getMenu_link())")
    public Map<String, Object> getMenuDetail(MenuVO vo) {
        Map<String, Object> rtnMap = new HashMap<>();
        List<MenuVO> menuVOList = menuMapper.getMenuDetail(vo);
        
        ...
        
        return rtnMap;
    }

 

@ReadThroughMultiCache : 캐시확인 > 없을경우 함수전체내용 실행 > return되는 객체를 캐시에 저장 > return

@UpdateMultiCache : 기존 값을 제거하지 않고 덮어씌움 (성능이점)

@Cacheable : 캐시사용 (value : properties에 정의한 캐시이름 / key : 캐시이름 하단에 적재될 키 명칭)

 

 

 

자세한 명세가 필요하다면 다음 블로그를 참고하자.  https://blog.naver.com/akaroice/220298608077 

 

SSM(Simple Spring Memcached)

소개spymemcached, xmemcached 클라이언트(provider)와 annotation, Spring/AspectJ AOP 를 이용한 ...

blog.naver.com

 

 

실제 작동은 아래와같이 이루어진다.

 

 

* 맴캐시드 빈 등록

 

 

* Cacheable에 정의된 key로 적재준비

 

2019-11-14 17:58:27,319 DEBUG [org.springframework.cache.annotation.AnnotationCacheOperationSource] Adding cacheable method 'getMenuDetail' with attribute: [Builder[public java.util.Map site.menu.service.MenuService.getMenuDetail(application.site.menu.vo.MenuVO)] caches=[test] | key=''menuDetail'.concat(':').concat(#vo.getMenu_info_seq()).concat(':').concat(#vo.getMenu_link())' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false']

 

 

* 맴캐시드 서버와 통신

 

 

* 캐시 적재된 데이터가 존재하지 않을경우 (MISS) RDB조회 후 캐시적재

 

 

* 캐시 히트에 성공했을 경우

 

 

 

 

 

 

 

기타 캐시와 관련된 좋은 게시글들

 

https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#long-tail-%EB%B2%95%EC%B9%99

 

Cache에 대하여.. (Spring+EHCache) | Carrey`s 기술블로그

들어가며.. 엔터프라이즈 급 Application에서는 DBMS의 부하를 줄이고, 성능을 높이기 위해 캐시(Cache)를 사용한다. 우리 회사에서도 EHCache, Redis를 사용하지만, EHCache쪽은 공부한 적이 없어서 이번 기회에 정리하면서 공부해 보게 되었다. 대부분 docs.spring.io의 문서를 번역한 내용을 위주로 정리 하였다. Cache란? Cache의 사전적인 의미를 알아보기 위해 위키백과를 검색해 보았다. 캐시(cache, 문화어

jaehun2841.github.io

 

https://charsyam.wordpress.com/2016/07/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-%EC%99%9C-cache%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80/

 

[입 개발] 왜 Cache를 사용하는가?

가끔씩 Redis 가 뭐예요? Memcached 가 뭐예요? 또는 Cache를 왜 써요? 라는 저도 모르는 근원적인 질문을 받을 때가 있습니다. 일단 그 근원적인 질문에 답하기 위해서는 먼저 Cache 란 무엇인가로 부터 시작해야 될것 같습니다. 일단 Cache는 “많은 시간이나 연산이 필요한 일에 대한 결과를 저장해 두는 것” …

charsyam.wordpress.com