-
Notifications
You must be signed in to change notification settings - Fork 12
DoubleCache V2 #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
DoubleCache V2 #25
Changes from all commits
0742f7c
8d982b1
7474c67
c4704e3
9a0793c
d61ddbd
dec9f54
ff5c348
52bfcfd
7cfd50f
919d665
2263c70
be4ce4a
35ffe0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,10 +60,20 @@ public interface ICacheAside | |
| The Add and remove methods are implemented with fire and forget, hence it does not need to be Async as this is handled by the StackExchange.Redis client. | ||
|
|
||
| DoubleCache comes with the following implementations of this interface | ||
| * LocalCache.MemCache - using System.Runtime.Memory | ||
| * Redis.RedisCache - using StackExchange.Redis client | ||
| * SubscribingCache - a decorator supporting push notifications of cache updates | ||
| * PublishingCache - a decorator publishing cache changes | ||
| * DoubleCache - a decorator wrapping a local and a remote cache | ||
| * *[obsolete]* LocalCache.MemCache - using System.Runtime.Memory, does not support null items. | ||
| * LocalCache.WrappingMemoryCache - Allows storage of null items in Memory cache.* | ||
| * SystemWebCaching.HttpCache - An in memory cache using HttpContext.Cache. | ||
| * Redis.RedisCache - using StackExchange.Redis client. | ||
| * Redis.RedisStaleCache - Use redis as a stale cache in order to mitigate cache stampede. | ||
| * SubscribingCache - a decorator supporting push notifications of cache updates. | ||
| * ExistingItemSubscribingCache - a decorator supporting push notifications, which will only update cache if the item already exists. | ||
| * PublishingCache - a decorator publishing cache changes. | ||
| * DoubleCache - a decorator wrapping a local and a remote cache. | ||
|
|
||
| \* using a custom proxy object holding the cache items. This is transparent to the client. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might as well put this directly in the description for |
||
|
|
||
| Depending on your cache need, you can combine these implementations and decorators as you need. The most complete example would be a DoubleCache which takes a local cache decorated with a SubscribingCache and a RedisCache decorated with a PublishingCache. This will result in a local cache that will be in sync with the other local caches; if a value isn't found the value will be retrieved from Redis before ultimately being resolved using the func provided to the cache. | ||
|
|
||
| ###Redis Stale Cache | ||
| Extends TTL for all objects stored in Redis. Verifies TTL when reading data from redis. If this is less than the default TTL on the redis cache, the cached item will be returned and a new fetch from the dataretriever will be extecuted on a separate thread. Storing the result in the cache. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,21 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using System.Web.Caching; | ||
|
|
||
| namespace DoubleCache.SystemWebHttpCache | ||
| namespace DoubleCache.SystemWebCaching | ||
| { | ||
| public class HttpCache : ICacheAside | ||
| { | ||
| internal class CacheItemWrapper | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have this same internal class in a few different caches. Perhaps pull it out into a separate file so you can share it between classes? |
||
| { | ||
| internal object Item { get; } | ||
|
|
||
| internal CacheItemWrapper(object item) | ||
| { | ||
| Item = item; | ||
| } | ||
| } | ||
|
|
||
| private readonly Cache _cache; | ||
| private readonly TimeSpan? _defaultTtl; | ||
|
|
||
|
|
@@ -20,12 +27,12 @@ public HttpCache(Cache cache, TimeSpan? defaultTtl = null) | |
|
|
||
| public void Add<T>(string key, T item) | ||
| { | ||
| _cache.Add(key, item, null, CalculateExpire(_defaultTtl), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); | ||
| Add(key,item,_defaultTtl); | ||
| } | ||
|
|
||
| public void Add<T>(string key, T item, TimeSpan? timeToLive) | ||
| { | ||
| _cache.Add(key, item, null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); | ||
| _cache.Add(key, new CacheItemWrapper(item), null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); | ||
| } | ||
|
|
||
| public T Get<T>(string key, Func<T> dataRetriever) where T : class | ||
|
|
@@ -35,13 +42,14 @@ public T Get<T>(string key, Func<T> dataRetriever) where T : class | |
|
|
||
| public T Get<T>(string key, Func<T> dataRetriever, TimeSpan? timeToLive) where T : class | ||
| { | ||
| var item = _cache.Get(key) as T; | ||
| if (item != null) | ||
| return item; | ||
| { | ||
| item = dataRetriever.Invoke(); | ||
| Add(key, item, timeToLive); | ||
| } | ||
| var wrapper = _cache.Get(key) as CacheItemWrapper; | ||
| if (wrapper != null) | ||
| return wrapper.Item as T; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's an inconsistency introduced here now. Before, and in The user is doing something incorrect, but do you want this to work the same as before or are you okay with it being different? I suppose in the |
||
|
|
||
| var item = dataRetriever.Invoke(); | ||
|
|
||
| Add(key, item, timeToLive); | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
|
|
@@ -52,13 +60,15 @@ public object Get(string key, Type type, Func<object> dataRetriever) | |
|
|
||
| public object Get(string key, Type type, Func<object> dataRetriever, TimeSpan? timeToLive) | ||
| { | ||
| var item = _cache.Get(key); | ||
| if (item != null && item.GetType() == type) | ||
| return item; | ||
| var wrapper = _cache.Get(key) as CacheItemWrapper; | ||
| if (wrapper != null) | ||
| return wrapper.Item; | ||
|
|
||
| var item = dataRetriever.Invoke(); | ||
|
|
||
| item = dataRetriever.Invoke(); | ||
| Add(key, item, timeToLive); | ||
| return item.GetType() == type ? item : null; | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever) where T : class | ||
|
|
@@ -68,13 +78,14 @@ public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever) where T : cl | |
|
|
||
| public async Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever, TimeSpan? timeToLive) where T : class | ||
| { | ||
| var item = _cache.Get(key) as T; | ||
| if (item != null) | ||
| return item; | ||
| { | ||
| item = await dataRetriever.Invoke(); | ||
| Add(key, item, timeToLive); | ||
| } | ||
| var wrapper = _cache.Get(key) as CacheItemWrapper; | ||
| if (wrapper != null) | ||
| return wrapper.Item as T; | ||
|
|
||
| var item = await dataRetriever.Invoke(); | ||
|
|
||
| Add(key, item, timeToLive); | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
|
|
@@ -85,13 +96,17 @@ public Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetri | |
|
|
||
| public async Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetriever, TimeSpan? timeToLive) | ||
| { | ||
| var item = _cache.Get(key); | ||
| if (item != null && item.GetType() == type) | ||
| return item; | ||
| var wrapper = _cache.Get(key) as CacheItemWrapper; | ||
| if (wrapper != null) | ||
| return wrapper.Item; | ||
|
|
||
|
|
||
| var item = await dataRetriever.Invoke(); | ||
| item = item.GetType() == type ? item : null; | ||
|
|
||
| item = await dataRetriever.Invoke(); | ||
| Add(key, item, timeToLive); | ||
| return item.GetType() == type ? item : null; | ||
|
|
||
| return item; | ||
| } | ||
|
|
||
| public void Remove(string key) | ||
|
|
@@ -107,5 +122,10 @@ private DateTime CalculateExpire(TimeSpan? ttl) | |
| ? DateTime.Now.Add(ttl.Value) | ||
| : DateTime.MaxValue; | ||
| } | ||
|
|
||
| public bool Exists(string key) | ||
| { | ||
| return _cache.Get(key) != null; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,85 +1,93 @@ | ||
| using System; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace DoubleCache | ||
| { | ||
| public class DoubleCache : ICacheAside | ||
| { | ||
| private readonly ICacheAside _localCache; | ||
| private readonly ICacheAside _remoteCache; | ||
|
|
||
| public DoubleCache(ICacheAside localCache,ICacheAside remoteCache) | ||
| { | ||
| _localCache = localCache; | ||
| _remoteCache = remoteCache; | ||
| } | ||
|
|
||
| public void Add<T>(string key, T item) | ||
| { | ||
| _localCache.Add(key, item); | ||
| _remoteCache.Add(key, item); | ||
| } | ||
|
|
||
| public void Add<T>(string key, T item, TimeSpan? timeToLive) | ||
| { | ||
| _localCache.Add(key, item, timeToLive); | ||
| _remoteCache.Add(key, item, timeToLive); | ||
| using System; | ||
| using System.Threading.Tasks; | ||
| using DoubleCache.Redis; | ||
|
|
||
| namespace DoubleCache | ||
| { | ||
| public class DoubleCache : ICacheAside | ||
| { | ||
| private readonly ICacheAside _localCache; | ||
| private readonly ICacheAside _remoteCache; | ||
| private readonly IKeyTimeToLive _remoteTimeToLive; | ||
|
|
||
| public DoubleCache(ICacheAside localCache,ICacheAside remoteCache, IKeyTimeToLive remoteTimeToLive) | ||
| { | ||
| _localCache = localCache; | ||
| _remoteCache = remoteCache; | ||
| _remoteTimeToLive = remoteTimeToLive; | ||
| } | ||
|
|
||
| public void Add<T>(string key, T item) | ||
| { | ||
| _localCache.Add(key, item); | ||
| _remoteCache.Add(key, item); | ||
| } | ||
|
|
||
| public void Add<T>(string key, T item, TimeSpan? timeToLive) | ||
| { | ||
| _localCache.Add(key, item, timeToLive); | ||
| _remoteCache.Add(key, item, timeToLive); | ||
| } | ||
|
|
||
| public T Get<T>(string key, Func<T> dataRetriever) where T : class | ||
| { | ||
| return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever)); | ||
| return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public T Get<T>(string key, Func<T> dataRetriever, TimeSpan? timeToLive) where T : class | ||
| { | ||
| return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever, timeToLive), timeToLive); | ||
| return _localCache.Get(key, () => _remoteCache.Get(key, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); | ||
|
|
||
| } | ||
|
|
||
| public object Get(string key, Type type, Func<object> dataRetriever) | ||
| { | ||
| return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever)); | ||
| return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public object Get(string key, Type type, Func<object> dataRetriever, TimeSpan? timeToLive) | ||
| { | ||
| return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever, timeToLive), timeToLive); | ||
| } | ||
|
|
||
| public Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetriever) | ||
| { | ||
| return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever)); | ||
| } | ||
|
|
||
| public Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetriever, TimeSpan? timeToLive) | ||
| { | ||
| return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), timeToLive); | ||
| } | ||
|
|
||
| public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever) where T : class | ||
| { | ||
| return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever)); | ||
| } | ||
|
|
||
| public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever, TimeSpan? timeToLive) where T : class | ||
| { | ||
| return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever),timeToLive); | ||
| return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetriever) | ||
| { | ||
| return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public Task<object> GetAsync(string key, Type type, Func<Task<object>> dataRetriever, TimeSpan? timeToLive) | ||
| { | ||
| return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public void Remove(string key) | ||
| { | ||
| _localCache.Remove(key); | ||
| _remoteCache.Remove(key); | ||
| } | ||
|
|
||
| public TimeSpan? DefaultTtl { get | ||
| { | ||
| return _localCache.DefaultTtl > _remoteCache.DefaultTtl | ||
| ? _localCache.DefaultTtl | ||
| : _remoteCache.DefaultTtl; | ||
| } } | ||
|
|
||
| public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever) where T : class | ||
| { | ||
| return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public Task<T> GetAsync<T>(string key, Func<Task<T>> dataRetriever, TimeSpan? timeToLive) where T : class | ||
| { | ||
| return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever, timeToLive), _remoteTimeToLive.KeyTimeToLive(key)); | ||
| } | ||
|
|
||
| public void Remove(string key) | ||
| { | ||
| _localCache.Remove(key); | ||
| _remoteCache.Remove(key); | ||
| } | ||
|
|
||
| public bool Exists(string key) | ||
| { | ||
| return _localCache.Exists(key) && _remoteCache.Exists(key); | ||
| } | ||
|
|
||
| public TimeSpan? DefaultTtl { get | ||
| { | ||
| return _localCache.DefaultTtl > _remoteCache.DefaultTtl | ||
| ? _localCache.DefaultTtl | ||
| : _remoteCache.DefaultTtl; | ||
| } } | ||
|
|
||
|
|
||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to say what the default implementations
DoubleCacheuses when created from the factory.