Skip to content

Commit

Permalink
Adding Twemproxy support #54, introducing new configuraiton options `…
Browse files Browse the repository at this point in the history
…UseTwemProxy`,. Added the option to by code, json and xml configuration variants... Added another new config option to set an explicit compatibiliy mode to a certain Redis version (to disable LUA for example). Refactored the redis configuration a little
  • Loading branch information
MichaCo committed Apr 27, 2017
1 parent 1a525b7 commit 07f1db9
Show file tree
Hide file tree
Showing 18 changed files with 390 additions and 139 deletions.
2 changes: 1 addition & 1 deletion src/CacheManager.Core/BaseCacheManager.Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ private bool UpdateInternal(
// If update fails because item doesn't exist AND the current handle is backplane source or the lowest cache handle level,
// remove the item from other handles (if exists).
// Otherwise, if we do not exit here, calling update on the next handle might succeed and would return a misleading result.
Logger.LogWarn($"Update failed on '{region}:{key}' because the region/key did not exist.");
Logger.LogInfo($"Update failed on '{region}:{key}' because the region/key did not exist.");

EvictFromOtherHandles(key, region, handleIndex);

Expand Down
91 changes: 62 additions & 29 deletions src/CacheManager.StackExchange.Redis/RedisCacheHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ public class RedisCacheHandle<TCacheValue> : BaseCacheHandle<TCacheValue>
return result";

private readonly IDictionary<ScriptType, LoadedLuaScript> _shaScripts = new Dictionary<ScriptType, LoadedLuaScript>();
private readonly IDictionary<ScriptType, LuaScript> _luaScripts = new Dictionary<ScriptType, LuaScript>();
private readonly ICacheManagerConfiguration _managerConfiguration;
private readonly RedisValueConverter _valueConverter;
private readonly RedisConnectionManager _connection;
private bool _isLuaAllowed;
private bool _canPreloadScripts = true;
private RedisConfiguration _redisConfiguration = null;

// flag if scripts are initially loaded to the server
Expand Down Expand Up @@ -105,6 +107,9 @@ public RedisCacheHandle(ICacheManagerConfiguration managerConfiguration, CacheHa
_connection = new RedisConnectionManager(_redisConfiguration, loggerFactory);
_isLuaAllowed = _connection.Features.Scripting;

// disable preloading right away if twemproxy mode, as this is not supported.
_canPreloadScripts = _redisConfiguration.TwemproxyEnabled ? false : true;

if (_redisConfiguration.KeyspaceNotificationsEnabled)
{
// notify-keyspace-events needs to be set to "Exe" at least! Otherwise we will not receive any events.
Expand Down Expand Up @@ -154,6 +159,12 @@ public override int Count
{
get
{
if (_redisConfiguration.TwemproxyEnabled)
{
Logger.LogWarn("'Count' cannot be calculated. Twemproxy mode is enabled which does not support accessing the servers collection.");
return 0;
}

var count = 0;
foreach (var server in Servers.Where(p => !p.IsSlave && p.IsConnected))
{
Expand Down Expand Up @@ -182,20 +193,11 @@ public override int Count
#pragma warning restore CS3003 // Type is not CLS-compliant

/// <summary>
/// Gets or sets a value indicating whether we can use the lua implementation instead of manual.
/// This flag will be set automatically via feature detection based on the Redis server version and should never be set manually except for testing.
/// Gets a value indicating whether we can use the lua implementation instead of manual.
/// This flag will be set automatically via feature detection based on the Redis server version
/// or via <see cref="RedisConfiguration.StrictCompatibilityModeVersion"/> if set to a version which does not support lua scripting.
/// </summary>
public bool UseLua
{
get
{
return _isLuaAllowed;
}
set
{
_isLuaAllowed = value;
}
}
public bool IsLuaAllowed => _isLuaAllowed;

/// <inheritdoc />
protected override ILogger Logger { get; }
Expand All @@ -205,15 +207,22 @@ public bool UseLua
/// </summary>
public override void Clear()
{
foreach (var server in Servers.Where(p => !p.IsSlave))
try
{
Retry(() =>
foreach (var server in Servers.Where(p => !p.IsSlave))
{
if (server.IsConnected)
Retry(() =>
{
server.FlushDatabase(_redisConfiguration.Database);
}
});
if (server.IsConnected)
{
server.FlushDatabase(_redisConfiguration.Database);
}
});
}
}
catch (NotSupportedException ex)
{
throw new NotSupportedException($"Clear is not available because '{ex.Message}'", ex);
}
}

Expand Down Expand Up @@ -944,8 +953,9 @@ private RedisResult Eval(ScriptType scriptType, RedisKey redisKey, RedisValue[]
}
}

LoadedLuaScript script;
if (!_shaScripts.TryGetValue(scriptType, out script))
LoadedLuaScript script = null;
if (!_luaScripts.TryGetValue(scriptType, out LuaScript luaScript)
|| (_canPreloadScripts && !_shaScripts.TryGetValue(scriptType, out script)))
{
Logger.LogCritical("Something is wrong with the Lua scripts. Seem to be not loaded.");
_scriptsLoaded = false;
Expand All @@ -954,7 +964,14 @@ private RedisResult Eval(ScriptType scriptType, RedisKey redisKey, RedisValue[]

try
{
return _connection.Database.ScriptEvaluate(script.Hash, new[] { redisKey }, values, flags);
if (_canPreloadScripts && script != null)
{
return _connection.Database.ScriptEvaluate(script.Hash, new[] { redisKey }, values, flags);
}
else
{
return _connection.Database.ScriptEvaluate(luaScript.ExecutableScript, new[] { redisKey }, values, flags);
}
}
catch (RedisServerException ex) when (ex.Message.StartsWith("NOSCRIPT", StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -976,15 +993,31 @@ private void LoadScripts()
var addLua = LuaScript.Prepare(_scriptAdd);
var updateLua = LuaScript.Prepare(_scriptUpdate);
var getLua = LuaScript.Prepare(_scriptGet);

foreach (var server in Servers)
_luaScripts.Clear();
_luaScripts.Add(ScriptType.Add, addLua);
_luaScripts.Add(ScriptType.Put, putLua);
_luaScripts.Add(ScriptType.Update, updateLua);
_luaScripts.Add(ScriptType.Get, getLua);

// servers feature might be disabled
if (_canPreloadScripts)
{
if (server.IsConnected)
try
{
foreach (var server in Servers)
{
if (server.IsConnected)
{
_shaScripts[ScriptType.Put] = putLua.Load(server);
_shaScripts[ScriptType.Add] = addLua.Load(server);
_shaScripts[ScriptType.Update] = updateLua.Load(server);
_shaScripts[ScriptType.Get] = getLua.Load(server);
}
}
}
catch (NotSupportedException)
{
_shaScripts[ScriptType.Put] = putLua.Load(server);
_shaScripts[ScriptType.Add] = addLua.Load(server);
_shaScripts[ScriptType.Update] = updateLua.Load(server);
_shaScripts[ScriptType.Get] = getLua.Load(server);
_canPreloadScripts = false;
}
}
}
Expand Down
91 changes: 86 additions & 5 deletions src/CacheManager.StackExchange.Redis/RedisConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using StackExchange.Redis;
using static CacheManager.Core.Utility.Guard;

namespace CacheManager.Redis
Expand All @@ -13,6 +14,9 @@ namespace CacheManager.Redis
/// </summary>
public class RedisConfiguration
{
private string _connectionString;
private ConfigurationOptions _configurationOptions;

/// <summary>
/// Initializes a new instance of the <see cref="RedisConfiguration"/> class.
/// </summary>
Expand Down Expand Up @@ -40,6 +44,10 @@ public RedisConfiguration()
/// <c>Clear</c> for example.
/// </param>
/// <param name="keyspaceNotificationsEnabled">Enables keyspace notifications to react on eviction/expiration of items.</param>
/// <param name="twemproxyEnabled">Enables Twemproxy mode.</param>
/// <param name="strictCompatibilityModeVersion">
/// Gets or sets a version number to eventually reduce the avaible features accessible by cachemanager.
/// </param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "Using it for configuration data only.")]
public RedisConfiguration(
string key,
Expand All @@ -50,7 +58,9 @@ public RedisConfiguration(
string sslHost = null,
int connectionTimeout = 5000,
bool allowAdmin = false,
bool keyspaceNotificationsEnabled = false)
bool keyspaceNotificationsEnabled = false,
bool twemproxyEnabled = false,
string strictCompatibilityModeVersion = null)
{
NotNullOrWhiteSpace(key, nameof(key));
NotNull(endpoints, nameof(endpoints));
Expand All @@ -69,6 +79,13 @@ public RedisConfiguration(
ConnectionTimeout = connectionTimeout;
AllowAdmin = allowAdmin;
KeyspaceNotificationsEnabled = keyspaceNotificationsEnabled;
TwemproxyEnabled = twemproxyEnabled;
StrictCompatibilityModeVersion = strictCompatibilityModeVersion;

_configurationOptions = CreateConfigurationOptions();

// is used later on as key in the connection lookup, and this object should be consistent no matter which ctor is used
ConnectionString = _configurationOptions.ToString();
}

/// <summary>
Expand All @@ -83,18 +100,63 @@ public RedisConfiguration(
/// </param>
/// <param name="database">The redis database to use.</param>
/// <param name="keyspaceNotificationsEnabled">Enables keyspace notifications to react on eviction/expiration of items.</param>
/// <param name="strictCompatibilityModeVersion">
/// Gets or sets a version number to eventually reduce the avaible features accessible by cachemanager.
/// </param>
public RedisConfiguration(
string key,
string connectionString,
int database,
bool keyspaceNotificationsEnabled)
int database = 0,
bool keyspaceNotificationsEnabled = false,
string strictCompatibilityModeVersion = null)
{
Key = key;
ConnectionString = connectionString;
Database = database;
KeyspaceNotificationsEnabled = keyspaceNotificationsEnabled;
StrictCompatibilityModeVersion = strictCompatibilityModeVersion;
}

private ConfigurationOptions CreateConfigurationOptions()
{
var configurationOptions = new ConfigurationOptions()
{
AllowAdmin = AllowAdmin,
ConnectTimeout = ConnectionTimeout,
Password = Password,
Ssl = IsSsl,
SslHost = SslHost,
ConnectRetry = 10,
AbortOnConnectFail = false,
Proxy = TwemproxyEnabled ? Proxy.Twemproxy : Proxy.None
};

foreach (var endpoint in Endpoints)
{
configurationOptions.EndPoints.Add(endpoint.Host, endpoint.Port);
}

return configurationOptions;
}

/// <summary>
/// Gets the <see cref="StackExchange.Redis.ConfigurationOptions"/> defined by this configuration.
/// </summary>
[CLSCompliant(false)]
public ConfigurationOptions ConfigurationOptions => _configurationOptions;

/// <summary>
/// Gets or sets a version number to eventually reduce the avaible features accessible by cachemanager.
/// E.g. set this to <c>"2.4"</c> to disable LUA support.
/// </summary>
/// <remarks>
/// This can also be used when automatic feature detection is not possible. Which is the
/// case for example if TwemProxy is used, because the servers collection. to query the features, is not available/supported.
/// CacheManager per default falls back to a version which supports LUA. If you are using a Redis server behind TwemPoxy which
/// does not allow LUA, use this property!
/// </remarks>
public string StrictCompatibilityModeVersion { get; set; }

/// <summary>
/// Gets or sets the identifier for the redis configuration.
/// <para>
Expand All @@ -105,14 +167,28 @@ public RedisConfiguration(
/// The key.
/// </value>
public string Key { get; set; }

/// <summary>
/// Gets or sets the connection string.
/// </summary>
/// <value>
/// The connection string.
/// </value>
public string ConnectionString { get; set; }
public string ConnectionString {
get => _connectionString;
set
{
_configurationOptions = ConfigurationOptions.Parse(value, true);

// read some properties back to be able to react on the settings in RedisCacheHandle
TwemproxyEnabled = _configurationOptions.Proxy == Proxy.Twemproxy;
AllowAdmin = _configurationOptions.AllowAdmin;
ConnectionTimeout = _configurationOptions.ConnectTimeout;
IsSsl = _configurationOptions.Ssl;
SslHost = _configurationOptions.SslHost;
_connectionString = _configurationOptions.ToString();
}
}

/// <summary>
/// Gets or sets the password to be used to connect to the Redis server.
Expand Down Expand Up @@ -182,6 +258,11 @@ public RedisConfiguration(
/// </para>
/// </summary>
public bool KeyspaceNotificationsEnabled { get; set; }

/// <summary>
/// Gets or sets a value to indicate if Termproxy is being used.
/// </summary>
public bool TwemproxyEnabled { get; set; }
}

/// <summary>
Expand Down
41 changes: 39 additions & 2 deletions src/CacheManager.StackExchange.Redis/RedisConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using static CacheManager.Core.Utility.Guard;

namespace CacheManager.Redis
Expand All @@ -18,6 +19,8 @@ public class RedisConfigurationBuilder
private string _password = null;
private string _sslHost = null;
private bool _enabledKeyspaceNotifications = false;
private string _useVersion;
private bool _useTwemproxy;

/// <summary>
/// Initializes a new instance of the <see cref="RedisConfigurationBuilder"/> class.
Expand All @@ -37,7 +40,18 @@ public RedisConfigurationBuilder(string configurationKey)
/// </summary>
/// <returns>The <c>RedisConfiguration</c></returns>
public RedisConfiguration Build() =>
new RedisConfiguration(_key, _endpoints, _database, _password, _isSsl, _sslHost, _connectionTimeout, _allowAdmin, _enabledKeyspaceNotifications);
new RedisConfiguration(
key: _key,
endpoints: _endpoints,
database: _database,
password: _password,
isSsl: _isSsl,
sslHost: _sslHost,
connectionTimeout: _connectionTimeout,
allowAdmin: _allowAdmin,
keyspaceNotificationsEnabled: _enabledKeyspaceNotifications,
twemproxyEnabled: _useTwemproxy,
strictCompatibilityModeVersion: _useVersion);

/// <summary>
/// Enable the flag to have CacheManager react on keyspace notifications from redis.
Expand All @@ -52,6 +66,29 @@ public RedisConfigurationBuilder EnableKeyspaceEvents()
return this;
}

/// <summary>
/// Can be used to control the available Redis features CacheManager can use. E.g. if set to <c>"2.4"</c>, this would disable all LUA support and would
/// force CacheManager to use other APIs
/// </summary>
/// <param name="version"></param>
/// <returns>The builder.</returns>
public RedisConfigurationBuilder UseCompatibilityMode(string version)
{
_useVersion = version;
return this;
}

/// <summary>
/// Enable this in case you are using Redis behind Twemproxy.
/// </summary>
/// <returns>The builder.</returns>
[CLSCompliant(false)]
public RedisConfigurationBuilder UseTwemproxy()
{
_useTwemproxy = true;
return this;
}

/// <summary>
/// If set to true, commands which might be risky are enabled, like Clear which will delete
/// all entries in the redis database.
Expand Down
Loading

0 comments on commit 07f1db9

Please sign in to comment.