Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Collaborator

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 DoubleCache uses when created from the factory.


\* using a custom proxy object holding the cache items. This is transparent to the client.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might as well put this directly in the description for WrappingMemoryCache. It's not helpful to have it up here.


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.

6 changes: 3 additions & 3 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj"
++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj"
let testReferences = !! "source/DoubleCacheTests/*.csproj"

let version = "1.6.1"
let version = "2.0.0-beta8"
let commitHash = Information.getCurrentSHA1(".")

let projectName = "DoubleCache"
Expand Down Expand Up @@ -67,8 +67,8 @@ Target "CreateNuget" (fun _ ->
Version = version
Publish = false
Dependencies = [
"StackExchange.Redis", "1.0.488"
"MsgPack.Cli", "0.6.5"
"StackExchange.Redis", "1.2.3"
"MsgPack.Cli", "0.8.1"
]
Files = [
(@"DoubleCache.dll", Some @"lib\net45", None)
Expand Down
80 changes: 50 additions & 30 deletions source/DoubleCache.SystemWebCaching/HttpCache.cs
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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;

Expand All @@ -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
Expand All @@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency introduced here now. Before, and in MemCache if you have a key "Users:123" that contains a User object, but you call Get<Product>("Users:123", () => ...); then it would see that as a cache miss and call your dataRetriever and overwrite the User object with the Product object. Now it will see a cache hit and return null.

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 RedisCache implementation it'll throw a SerializationException, so it's already not the same everywhere.


var item = dataRetriever.Invoke();

Add(key, item, timeToLive);

return item;
}

Expand All @@ -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
Expand All @@ -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;
}

Expand All @@ -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)
Expand All @@ -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;
}
}
}
5 changes: 3 additions & 2 deletions source/DoubleCache/CacheFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ public static ICacheAside CreatePubSubDoubleCache(IConnectionMultiplexer redisCo
{
var remoteCache = new RedisCache(redisConnection.GetDatabase(), itemSerializer, defaultTtl);
return new DoubleCache(
new SubscribingCache(new LocalCache.MemCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)),
new PublishingCache(remoteCache, new RedisPublisher(redisConnection, itemSerializer)));
new SubscribingCache(new LocalCache.WrappingMemoryCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)),
new PublishingCache(remoteCache, new RedisPublisher(redisConnection, itemSerializer)),
remoteCache);
}
}
}
138 changes: 73 additions & 65 deletions source/DoubleCache/DoubleCache.cs
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;
} }


}
}
}
}
14 changes: 8 additions & 6 deletions source/DoubleCache/DoubleCache.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="MsgPack, Version=0.6.0.0, Culture=neutral, PublicKeyToken=a2625990d5dc0167, processorArchitecture=MSIL">
<HintPath>..\packages\MsgPack.Cli.0.6.5\lib\net45\MsgPack.dll</HintPath>
<Private>True</Private>
<Reference Include="MsgPack, Version=0.8.0.0, Culture=neutral, PublicKeyToken=a2625990d5dc0167, processorArchitecture=MSIL">
<HintPath>..\packages\MsgPack.Cli.0.8.1\lib\net45\MsgPack.dll</HintPath>
</Reference>
<Reference Include="StackExchange.Redis, Version=1.0.316.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\StackExchange.Redis.1.0.488\lib\net45\StackExchange.Redis.dll</HintPath>
<Private>True</Private>
<Reference Include="StackExchange.Redis, Version=1.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\StackExchange.Redis.1.2.3\lib\net45\StackExchange.Redis.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
Expand All @@ -52,12 +51,15 @@
<Compile Include="CacheFactory.cs" />
<Compile Include="CacheUpdateNotification.cs" />
<Compile Include="DoubleCache.cs" />
<Compile Include="ExistingItemSubscribingCache.cs" />
<Compile Include="ICacheAside.cs" />
<Compile Include="ICachePublisher.cs" />
<Compile Include="ICacheSubscriber.cs" />
<Compile Include="LocalCache\MemCache.cs" />
<Compile Include="LocalCache\WrappingMemoryCache.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PublishingCache.cs" />
<Compile Include="Redis\IKeyTimeToLive.cs" />
<Compile Include="Redis\RedisCache.cs" />
<Compile Include="Redis\RedisStaleCache.cs" />
<Compile Include="Redis\RedisSubscriber.cs" />
Expand Down
Loading