개인적으로 Microsoft Server AppFabric 은 별로 좋아하지 않고, 구현하지 않았지만, 그리 어려운 부분은 아닙니다.
이번 글은 일반적으로 가장 많이 사용하고, 속도가 높은 Memcached 를 Cache Repository 로 사용하는 경우에 대해 알아보겠습니다.
너무 유명한 제품이라 Memcached 에 대한 소개는 생략하기로 하고, Windows에서 Memcahced를 Windows Service로 사용할 수 있도록 해 주는 방법에 대해 알아봅시다.
우선 Memcached 64-bit for Windows 나 How to install Memcached on Windows machine (32-bit) 에서 다운 받아 원하는 위치에 압축을 푸시기만 하면 됩니다.
다음으로 설치 폴더에서 Command Prompt 를 열어
설치폴더/memcached.exe -d install
를 수행하시면, Windows Service로 설치 됩니다.
다음으로는 Memcached 서비스를 실행시킵니다.
설치폴더/memcached.exe -d start
이제 작업관리자에서 실제 수행 되고 있는지 확인해보시기 바랍니다.
이제 실제 Memcached 캐시 서버에 접속하여 캐시를 저장/로드를 수행할 Client를 제작해야 합니다. 이를 위해 Memcached Client Driver 인 Enym.Caching 을 사용합니다.
Enym.Caching 을 사용하여, 제작한 클래스는 다음과 같습니다.
-------------------------------------------------------------------
using System;using Enyim.Caching;using Enyim.Caching.Memcached;using NSoft.NFramework.Json; namespace NSoft.NFramework.Caching.Memcached{ /// <summary> /// Memcached 캐시 서버를 저장소로 사용하는 Cache Repository 입니다. /// 참고: https://github.com/enyim/EnyimMemcached/wiki/MemcachedClient-Usage /// </summary> public class MemcachedRepository : AbstractCacheRepository { #region << logger >> private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); private static readonly bool IsDebugEnabled = log.IsDebugEnabled; #endregion public MemcachedRepository() : base() {} public MemcachedRepository(ISerializer serializer) : base(serializer) {} private MemcachedClient _client; public MemcachedClient Client { get { return _client ?? (_client = new MemcachedClient()); } set { _client = value; } } /// <summary> /// 캐시에 저장된 항목을 반환합니다. /// </summary> /// <param name="key"></param> /// <returns></returns> public override object Get(string key) { key.ShouldNotBeWhiteSpace("key"); if(IsDebugEnabled) log.Debug("캐시에서 키[{0}]에 해당하는 값을 조회합니다.", key); var item = Client.Get(key); var isSerialized = (Serializer != null) && (item is CacheItem); if(isSerialized == false) return item; var cacheItem = (CacheItem) item; if(Serializer is IJsonSerializer) return ((IJsonSerializer) Serializer).Deserialize(cacheItem.ItemData, cacheItem.ItemType); return Serializer.Deserialize(cacheItem.ItemData); } /// <summary> /// 캐시에 항목을 저장합니다. /// </summary> /// <param name="key"></param> /// <param name="item"></param> /// <param name="validFor"></param> public override void Set(string key, object item, TimeSpan validFor = default(TimeSpan)) { key.ShouldNotBeWhiteSpace("key"); if(IsDebugEnabled) log.Debug("캐시에 값을 저장합니다. key=[{0}], item=[{1}], Expiry=[{2}]", key, item, validFor); // BUG: MemcachedClient는 유효기간을 지정하면 저장이 되지 않습니다. // if(Serializer != null) Client.Store(StoreMode.Set, key, CreateCacheItem(this, item)); else Client.Store(StoreMode.Set, key, item); } /// <summary> /// 캐시에서 항목을 제거합니다. /// </summary> /// <param name="key"></param> public override void Remove(string key) { key.ShouldNotBeWhiteSpace("key"); if(IsDebugEnabled) log.Debug("캐시에 저장된 항목을 삭제합니다... key=[{0}]", key); Client.Remove(key); } /// <summary> /// 캐시의 모든 항목을 삭제합니다. /// </summary> public override void Clear() { if(IsDebugEnabled) log.Debug("캐시에 저장된 모든 항목을 삭제합니다..."); Client.FlushAll(); } private static CacheItem CreateCacheItem(MemcachedRepository repository, object item) { return new CacheItem { ItemType = item.GetType(), ItemData = repository.Serializer.Serialize(item) }; } /// <summary> /// 캐시로 저장되는 정보 /// </summary> [Serializable] private class CacheItem { public Type ItemType { get; set; } public byte[] ItemData { get; set; } } } }-------------------------------------------------------------------
CacheItem class는 캐시에 저장된 내용이 Serializable 이 아닌 경우, 예외를 일으키기 때문에 래핑하는 역할을 수해합니다.
이제 실제로 Memcached 를 사용하는 OutputCacheProvider를 제작해봅시다.
-------------------------------------------------------------------
/// <summary>
/// ASP.NET 웹 Page의 Output Cache를 Memcached 서버에 저장해주는 OutputCacheProvider입니다./// </summary> /// <summary> /// .NET 4.0 이상에서 ASP.NET 웹 Page의 Output Cache를 Memcached 서버에 저장해주는 OutputCacheProvider입니다./// 참고:/// http://www.4guysfromrolla.com/articles/061610-1.aspx/// http://weblogs.asp.net/gunnarpeipman/archive/2009/11/19/asp-net-4-0-writing-custom-output-cache-providers.aspx/// http://weblogs.asp.net/scottgu/archive/2010/01/27/extensible-output-caching-with-asp-net-4-vs-2010-and-net-4-0-series.aspx/// </summary> /// <example> /// <code> /// <system.web> /// <compilation debug="true" targetFramework="4.0"/> /// <caching> /// <outputCache defaultProvider="MemcachedOutputCacheProvider"> /// <providers> /// <add name="MemcachedOutputCacheProvider" /// type="NSoft.NFramework.Caching.Memcached.Web.MemcachedOutputCacheProvider, NSoft.NFramework.Memcached"/> /// </providers> /// </outputCache> /// </caching> /// </system.web> /// </code> /// </example>public class MemcachedOutputCacheProvider : AbstractOutputCacheProvider{ #region << logger >> private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); private static readonly bool IsDebugEnabled = log.IsDebugEnabled; #endregion public MemcachedOutputCacheProvider() : base(() => new MemcachedRepository()) {} }-------------------------------------------------------------------
아니 뭐야? 뭐가 이리 간단해? 네… 아시다시피 Output Cache 를 Cache에 저장/로드 하는 것은 어떤 캐시 시스템을 사용해도 똑같습니다. 그래서 AbstractOutputCacheProvider 에게 ICacheRepository 를 제공하여, 확장을 손쉽게 할 수 있는 구조를 가지게 되었습니다.
그럼 핵심적인 AbstractOutputCacheProvider 소스를 보시면
-------------------------------------------------------------------
/// <summary> /// .NET 4.0 이상에서 ASP.NET Page의 OutputCache를 <see cref="CacheRepository"/>를 통해 저장/로드됩니다./// 참고:/// http://www.4guysfromrolla.com/articles/061610-1.aspx/// http://weblogs.asp.net/gunnarpeipman/archive/2009/11/19/asp-net-4-0-writing-custom-output-cache-providers.aspx/// http://weblogs.asp.net/scottgu/archive/2010/01/27/extensible-output-caching-with-asp-net-4-vs-2010-and-net-4-0-series.aspx/// </summary>public abstract class AbstractOutputCacheProvider : OutputCacheProvider, IOutputCacheProvider{ #region << logger >> private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger(); private static readonly bool IsDebugEnabled = log.IsDebugEnabled; #endregion private static readonly Func<ICacheRepository> _defaultCacheRepositoryFactory = () => { ICacheRepository repository = null; try { if(IoC.IsInitialized) repository = IoC.Resolve<ICacheRepository>(); } catch(Exception ex) { if(log.IsWarnEnabled) log.WarnException("IoC로부터 ICacheRepository를 Resolve하는데 실패했습니다.", ex); } if(repository == null) repository = new SysCacheRepository(); return repository; }; protected AbstractOutputCacheProvider() : this(_defaultCacheRepositoryFactory) {} protected AbstractOutputCacheProvider(Func<ICacheRepository> cacheRepositoryFactory) { cacheRepositoryFactory.ShouldNotBeNull("cacheRepositoryFactory"); CacheRepository = cacheRepositoryFactory(); } /// <summary> /// 실제 캐시 저장소에 데이타를 저장/조회하는 API를 제공하는 Repository입니다. /// </summary> public ICacheRepository CacheRepository { get; protected set; } /// <summary> /// 출력 캐시에서 지정된 항목에 대한 참조를 반환합니다. /// </summary> /// <returns> /// 캐시에서 지정된 항목을 식별하는 <paramref name="key"/> 값이거나 캐시에 지정된 항목이 없는 경우 null입니다. /// </returns> /// <param name="key">출력 캐시에서 캐시된 항목에 대한 고유 식별자입니다. </param> public override object Get(string key) { if(IsDebugEnabled) log.Debug("ASP.NET Page OutputCache를 로드합니다... key=[{0}]", key); return CacheRepository.Get(key); } /// <summary> /// 지정된 항목을 출력 캐시에 삽입합니다. /// </summary> /// <returns> /// 지정된 공급자에 대한 참조입니다. /// </returns> /// <param name="key"><paramref name="entry"/>에 대한 고유 식별자입니다.</param><param name="entry">출력 캐시에 추가할 내용입니다.</param> /// <param name="utcExpiry">캐시된 항목이 만료되는 날짜와 시간입니다.</param> public override object Add(string key, object entry, DateTime utcExpiry) { if(IsDebugEnabled) log.Debug("ASP.NET Page OutputCache를 캐시에 추가합니다. key=[{0}], utcExpiry=[{1}]", key, utcExpiry); CacheRepository.Set(key, entry, utcExpiry.Subtract(DateTime.UtcNow)); return entry; } /// <summary> /// 지정된 항목을 출력 캐시에 삽입하고 이미 캐시되어 있는 경우 해당 항목을 덮어씁니다. /// </summary> /// <param name="key"><paramref name="entry"/>에 대한 고유 식별자입니다.</param><param name="entry">출력 캐시에 추가할 내용입니다.</param> /// <param name="utcExpiry">캐시된 <paramref name="entry"/>가 만료되는 날짜와 시간입니다.</param> public override void Set(string key, object entry, DateTime utcExpiry) { if(IsDebugEnabled) log.Debug("ASP.NET Page OutputCache를 캐시에 설정합니다. key=[{0}], utcExpiry=[{1}]", key, utcExpiry); CacheRepository.Set(key, entry, utcExpiry.Subtract(DateTime.UtcNow)); } /// <summary> /// 출력 캐시에서 지정된 항목을 제거합니다. /// </summary> /// <param name="key">출력 캐시에서 제거할 항목에 대한 고유 식별자입니다. </param> public override void Remove(string key) { if(IsDebugEnabled) log.Debug("ASP.NET Page OutputCache를 삭제합니다. key=[{0}]", key); CacheRepository.Remove(key); } }-------------------------------------------------------------------
AbstractOutputCacheProvider 를 상속받아, SharedCacheRepository, MongoCacheRepository, RedisCacheRepository 등을 제공하게 되면 각각의 Cache Server 별로 OutputCacheProvider를 손쉽게 만들 수 있습니다. 이렇게 ICacheRepository로 나눈 것은 OutputCache 뿐 아니라 ViewState 를 저장하는 PageStatePersister, 세션 상태를 저장하는 SessionStateStoreProvider 를 손쉽게 구현할 수 있기 때문입니다.
마지막으로, OutputCacheProvider 를 적용하기 위해서는 web.config 에 다음과 같이 지정해 주시면 됩니다.
우선 Memcached 서버에 대한 설정을 지정해 줍니다.
-------------------------------------------------------------------
<configSections> <!-- Memcached --> <sectionGroup name="enyim.com"> <section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching"/> </sectionGroup> </configSections>
<!-- Memcached --> <!-- 참고: https://github.com/enyim/EnyimMemcached/wiki/MemcachedClient-Configuration --> <enyim.com> <memcached protocol="Binary"> <servers> <add address="127.0.0.1" port="11211"/> </servers> <socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:00:10" deadTimeout="00:02:00"/> </memcached> </enyim.com>-------------------------------------------------------------------
마지막으로 OutputCache 에 대한 Provider를 지정합니다.
-------------------------------------------------------------------<system.web>
<caching> <outputCache defaultProvider="MemcachedOutputCacheProvider"> <providers> <add name="MemcachedOutputCacheProvider" type="NSoft.NFramework.Caching.Memcached.Web.MemcachedOutputCacheProvider, NSoft.NFramework.Caching.Memcached"/> </providers> </outputCache> </caching>-------------------------------------------------------------------
자 좀 복잡해 보이지만, .NET 4.0 에서 제공하는 이러한 확장성을 제대로 활용하면, 좀 더 유연하고, 견고한 제품을 만들 수 있을거라 생각됩니다.
특히 CMS 등 컨텐츠의 Read 가 월등히 많은 경우에 더욱 효과가 있고, 메모리의 제한이 있다면, MongoDB 등을 활용하는 것도 좋은 방안이 될 수 있습니다.
아니면 gzip으로 압축하여 캐시에 저장하고, Browser가 gzip을 지원하는 경우 gzip 된 컨텐츠를 아무런 처리없이 그대로 응답할 수 있으므로, 서버 리소스 절약 및 성능 향상에 기여할 수 있습니다.
다음으로는 실제 ASP.NET MVC 에서 활용하는 방안에 대해 알아보겠습니다.
댓글 4개:
완전! 완전! 완전! 감사합니다. ^^
안녕하세요 전 아주 초보 입니다 하지만 조금식 배워가고 있습니다 배성혁님의 포스팅이 아주 잘 되여 있어서 보고있습니다 그러나 한가지 이 포스트에서요 [다음으로 설치 폴더에서 Command Prompt 를 열어] 이문장이 이해가 잘안갑니다 어떻게 열수 있나요 Command Prompt 설치폴더에서요 혹시 시작에서 런이나 또는 cmd를 사용해서 하라는것인가요 궁금합니다 알려주세요 ^^;;
간만에 들어옵니다^^
감사의 인사 고맙게 받겠습니다^^
"DOS Command 창을 열어" 즉 "윈도우키 + R" 을 누르시고, cmd 를 실행하시면 됩니다.
소스에는 여기에 공개되지 않은 Guard 패턴, Utility 함수등이 있을 수 있습니다. 적절히 제거하시고 실행하셔야 합니다.
교육용이 아니라 실제 사용하는 코드라 분리하지 못했네요...
인용[ 여기에 공개되지 않은 Guard 패턴, Utility 함수등이 있을 수 있습니다. 적절히 제거하시고 실행하셔야 합니다.]
위 문장에서 적절히 제거하시고 사용하라는되요 전 그렇게 하지도못하는 사람 입니다 ㅠㅠ 완전 초보라고보시면 됩니다 그래서요 뭘 제것을 해야하는지도 모릅니다 하나식 배워나가도록 하겠습니다 답글 정말 감사 합니다 ^^;;
많이 가르처주세요 ^^;;
댓글 쓰기