Spring - 객체캐싱 (SSM, memcached)

Spring - 객체캐싱 (SSM, memcached)

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

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

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

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

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

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

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

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

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

* 개발환경

- maven

- spring 5 (java config)

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

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

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

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

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

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

- pom.xml

com.google.code.simple-spring-memcached spring-cache 3.6.1 com.google.code.simple-spring-memcached simple-spring-memcached 3.6.1 com.google.code.simple-spring-memcached xmemcached-provider 3.6.1

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

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

- *.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 getMenuDetail(MenuVO vo) { Map rtnMap = new HashMap<>(); List menuVOList = menuMapper.getMenuDetail(vo); ... return rtnMap; }

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

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

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

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

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

* 맴캐시드 빈 등록

* 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

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/

from http://seorinari.tistory.com/12 by ccl(A) rewrite - 2020-03-06 09:54:51

댓글

이 블로그의 인기 게시물

데이터 바인딩 추상화 - propertyEditor

[sts] spring boot groovy 적용 실행 하기

2020 LCK 롤챔스 Spring 경기 재개 및 일정