Commit b6e4bd65 authored by Marc Gravell's avatar Marc Gravell

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget;...

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget; update tests; change namespace of keyspace isolation - that was an error
parent a541e22d
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NuGet.CommandLine" version="2.8.2" /> <package id="NuGet.CommandLine" version="2.8.3" />
<package id="Redis-64" version="2.8.12" /> <package id="Redis-64" version="2.8.17" />
</packages> </packages>
\ No newline at end of file
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7000 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7000
\ No newline at end of file
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7001 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7001
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7002 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7002
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7003 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7003
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7004 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7004
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7005 @..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7005
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6379 @..\packages\Redis-64.2.8.17\redis-cli.exe -p 6379
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6381 @..\packages\Redis-64.2.8.17\redis-cli.exe -p 6381
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6380 @..\packages\Redis-64.2.8.17\redis-cli.exe -p 6380
@start ..\packages\Redis-64.2.8.12\redis-server.exe master.conf @start ..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe slave.conf @start ..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe secure.conf @start ..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe master.conf @..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@..\packages\Redis-64.2.8.12\redis-server.exe secure.conf @..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe slave.conf @..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using BookSleeve; using BookSleeve;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
[TestFixture] [TestFixture]
...@@ -664,6 +664,22 @@ private void Incr(IDatabase database, RedisKey key, int delta, ref int total) ...@@ -664,6 +664,22 @@ private void Incr(IDatabase database, RedisKey key, int delta, ref int total)
database.StringIncrement(key, delta, CommandFlags.FireAndForget); database.StringIncrement(key, delta, CommandFlags.FireAndForget);
total += delta; total += delta;
} }
[Test]
public void WrappedDatabasePrefixIntegration()
{
using (var conn = Create())
{
var db = conn.GetDatabase().WithKeyPrefix("abc");
db.KeyDelete("count");
db.StringIncrement("count");
db.StringIncrement("count");
db.StringIncrement("count");
int count = (int)conn.GetDatabase().StringGet("abccount");
Assert.AreEqual(3, count);
}
}
} }
} }
using System; using System;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
...@@ -15,7 +16,7 @@ public sealed class BatchWrapperTests ...@@ -15,7 +16,7 @@ public sealed class BatchWrapperTests
public void Initialize() public void Initialize()
{ {
mock = new Mock<IBatch>(); mock = new Mock<IBatch>();
wrapper = new BatchWrapper(mock.Object, "prefix:"); wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Test] [Test]
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
using System.Net; using System.Net;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
...@@ -18,7 +19,7 @@ public sealed class DatabaseWrapperTests ...@@ -18,7 +19,7 @@ public sealed class DatabaseWrapperTests
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabase>(); mock = new Mock<IDatabase>();
wrapper = new DatabaseWrapper(mock.Object, "prefix:"); wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Test] [Test]
...@@ -149,7 +150,7 @@ public void HashLength() ...@@ -149,7 +150,7 @@ public void HashLength()
public void HashScan() public void HashScan()
{ {
wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Test] [Test]
...@@ -604,7 +605,7 @@ public void SetRemove_2() ...@@ -604,7 +605,7 @@ public void SetRemove_2()
public void SetScan() public void SetScan()
{ {
wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Test] [Test]
...@@ -774,7 +775,7 @@ public void SortedSetRemoveRangeByValue() ...@@ -774,7 +775,7 @@ public void SortedSetRemoveRangeByValue()
public void SortedSetScan() public void SortedSetScan()
{ {
wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority); wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority)); mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
} }
[Test] [Test]
......
...@@ -5,10 +5,6 @@ namespace StackExchange.Redis.Tests ...@@ -5,10 +5,6 @@ namespace StackExchange.Redis.Tests
[TestFixture] [TestFixture]
public class Lex : TestBase public class Lex : TestBase
{ {
protected override string GetConfiguration()
{
return "ubuntu";
}
[Test] [Test]
public void QueryRangeAndLengthByLex() public void QueryRangeAndLengthByLex()
......
...@@ -14,7 +14,7 @@ protected override string GetConfiguration() ...@@ -14,7 +14,7 @@ protected override string GetConfiguration()
return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword; return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword;
} }
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "FLUSHDB cannot be issued to a slave")] [Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")]
public void CannotFlushSlave() public void CannotFlushSlave()
{ {
ConfigurationOptions config = GetMasterSlaveConfig(); ConfigurationOptions config = GetMasterSlaveConfig();
...@@ -73,7 +73,7 @@ public void DeslaveGoesToPrimary() ...@@ -73,7 +73,7 @@ public void DeslaveGoesToPrimary()
} }
catch (RedisConnectionException ex) catch (RedisConnectionException ex)
{ {
Assert.AreEqual("No connection is available to service this operation: EXISTS", ex.Message); Assert.AreEqual("No connection is available to service this operation: EXISTS DeslaveGoesToPrimary", ex.Message);
} }
primary.MakeMaster(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates | ReplicationChangeOptions.SetTiebreaker); primary.MakeMaster(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates | ReplicationChangeOptions.SetTiebreaker);
......
...@@ -73,6 +73,8 @@ public void CheckDatabaseMethodsUseKeys(Type type) ...@@ -73,6 +73,8 @@ public void CheckDatabaseMethodsUseKeys(Type type)
{ {
case "KeyRandom": case "KeyRandom":
case "KeyRandomAsync": case "KeyRandomAsync":
case "Publish":
case "PublishAsync":
continue; // they're fine, but don't want to widen check to return type continue; // they're fine, but don't want to widen check to return type
} }
......
...@@ -33,9 +33,5 @@ public void SubscriberCount() ...@@ -33,9 +33,5 @@ public void SubscriberCount()
Assert.IsTrue(channels.Contains(channel)); Assert.IsTrue(channels.Contains(channel));
} }
} }
protected override string GetConfiguration()
{
return "ubuntu";
}
} }
} }
using System; using System;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
...@@ -15,7 +16,7 @@ public sealed class TransactionWrapperTests ...@@ -15,7 +16,7 @@ public sealed class TransactionWrapperTests
public void Initialize() public void Initialize()
{ {
mock = new Mock<ITransaction>(); mock = new Mock<ITransaction>();
wrapper = new TransactionWrapper(mock.Object, "prefix:"); wrapper = new TransactionWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Test] [Test]
......
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System; using System;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
using System.Net; using System.Net;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation; using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
...@@ -18,7 +19,7 @@ public sealed class WrapperBaseTests ...@@ -18,7 +19,7 @@ public sealed class WrapperBaseTests
public void Initialize() public void Initialize()
{ {
mock = new Mock<IDatabaseAsync>(); mock = new Mock<IDatabaseAsync>();
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, "prefix:"); wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
} }
[Test] [Test]
......
...@@ -163,7 +163,7 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult) ...@@ -163,7 +163,7 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
public override string ToString() public override string ToString()
{ {
return (hashField.IsNull ? key.ToString() : key + " > " + hashField) return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedResult ? " exists" : " does not exists"); + (expectedResult ? " exists" : " does not exists");
} }
...@@ -221,7 +221,7 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R ...@@ -221,7 +221,7 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R
public override string ToString() public override string ToString()
{ {
return (hashField.IsNull ? key.ToString() : key + " > " + hashField) return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedEqual ? " == " : " != ") + (expectedEqual ? " == " : " != ")
+ expectedValue; + expectedValue;
} }
......
using System; using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch
{ {
public BatchWrapper(IBatch inner, RedisKey prefix) public BatchWrapper(IBatch inner, byte[] prefix)
: base(inner, prefix) : base(inner, prefix)
{ {
} }
......
using System; using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
/// <summary> /// <summary>
/// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>. /// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>.
...@@ -49,7 +49,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi ...@@ -49,7 +49,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
throw new ArgumentNullException("keyPrefix"); throw new ArgumentNullException("keyPrefix");
} }
if (keyPrefix.Value.Length == 0) if (keyPrefix.IsEmpty)
{ {
return database; // fine - you can keep using the original, then return database; // fine - you can keep using the original, then
} }
...@@ -62,7 +62,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi ...@@ -62,7 +62,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
database = wrapper.Inner; database = wrapper.Inner;
} }
return new DatabaseWrapper(database, keyPrefix); return new DatabaseWrapper(database, keyPrefix.AsPrefix());
} }
} }
} }
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase
{ {
public DatabaseWrapper(IDatabase inner, RedisKey prefix) public DatabaseWrapper(IDatabase inner, byte[] prefix)
: base(inner, prefix) : base(inner, prefix)
{ {
} }
......
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction
{ {
public TransactionWrapper(ITransaction inner, RedisKey prefix) public TransactionWrapper(ITransaction inner, byte[] prefix)
: base(inner, prefix) : base(inner, prefix)
{ {
} }
......
...@@ -4,17 +4,17 @@ ...@@ -4,17 +4,17 @@
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation namespace StackExchange.Redis.KeyspaceIsolation
{ {
internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync
{ {
private readonly TInner _inner; private readonly TInner _inner;
private readonly RedisKey _prefix; private readonly byte[] _keyPrefix;
internal WrapperBase(TInner inner, RedisKey prefix) internal WrapperBase(TInner inner, byte[] keyPrefix)
{ {
_inner = inner; _inner = inner;
_prefix = prefix; _keyPrefix = keyPrefix;
} }
public ConnectionMultiplexer Multiplexer public ConnectionMultiplexer Multiplexer
...@@ -27,9 +27,9 @@ internal TInner Inner ...@@ -27,9 +27,9 @@ internal TInner Inner
get { return _inner; } get { return _inner; }
} }
internal RedisKey Prefix internal byte[] Prefix
{ {
get { return _prefix; } get { return _keyPrefix; }
} }
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
...@@ -653,7 +653,7 @@ public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None) ...@@ -653,7 +653,7 @@ public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None)
protected internal RedisKey ToInner(RedisKey outer) protected internal RedisKey ToInner(RedisKey outer)
{ {
return this.Prefix + outer; return RedisKey.WithPrefix(_keyPrefix, outer);
} }
protected RedisKey ToInnerOrDefault(RedisKey outer) protected RedisKey ToInnerOrDefault(RedisKey outer)
...@@ -713,7 +713,7 @@ protected RedisKey[] ToInner(RedisKey[] outer) ...@@ -713,7 +713,7 @@ protected RedisKey[] ToInner(RedisKey[] outer)
protected RedisValue ToInner(RedisValue outer) protected RedisValue ToInner(RedisValue outer)
{ {
return RedisKey.Concatenate(this.Prefix, outer); return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
} }
protected RedisValue SortByToInner(RedisValue outer) protected RedisValue SortByToInner(RedisValue outer)
...@@ -761,7 +761,7 @@ protected RedisValue[] SortGetToInner(RedisValue[] outer) ...@@ -761,7 +761,7 @@ protected RedisValue[] SortGetToInner(RedisValue[] outer)
protected RedisChannel ToInner(RedisChannel outer) protected RedisChannel ToInner(RedisChannel outer)
{ {
return RedisKey.Concatenate((byte[])Prefix, (byte[])outer); return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
} }
private Func<RedisKey, RedisKey> mapFunction; private Func<RedisKey, RedisKey> mapFunction;
......
...@@ -612,7 +612,7 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey ...@@ -612,7 +612,7 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey
this.Key = key; this.Key = key;
} }
public override string CommandAndKey { get { return Command + " " + Key; } } public override string CommandAndKey { get { return Command + " " + ((string)Key); } }
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{ {
return serverSelectionStrategy.HashSlot(Key); return serverSelectionStrategy.HashSlot(Key);
......
...@@ -354,7 +354,15 @@ internal void SetUnknownDatabase() ...@@ -354,7 +354,15 @@ internal void SetUnknownDatabase()
internal void Write(RedisKey key) internal void Write(RedisKey key)
{ {
WriteUnified(outStream, key.Value); var val = key.KeyValue;
if (val is string)
{
WriteUnified(outStream, key.KeyPrefix, (string)val);
}
else
{
WriteUnified(outStream, key.KeyPrefix, (byte[])val);
}
} }
internal void Write(RedisChannel channel) internal void Write(RedisChannel channel)
...@@ -494,6 +502,61 @@ internal static byte ToHexNibble(int value) ...@@ -494,6 +502,61 @@ internal static byte ToHexNibble(int value)
return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value); return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value);
} }
void WriteUnified(Stream stream, byte[] prefix, string value)
{
stream.WriteByte((byte)'$');
if (value == null)
{
WriteRaw(stream, -1); // note that not many things like this...
}
else
{
int encodedLength = Encoding.UTF8.GetByteCount(value);
if (prefix == null)
{
WriteRaw(stream, encodedLength);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
else
{
WriteRaw(stream, prefix.Length + encodedLength);
stream.Write(prefix, 0, prefix.Length);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
}
}
unsafe void WriteRaw(Stream stream, string value, int encodedLength)
{
if (encodedLength <= ScratchSize)
{
int bytes = Encoding.UTF8.GetBytes(value, 0, value.Length, outScratch, 0);
stream.Write(outScratch, 0, bytes);
}
else
{
fixed (char* c = value)
fixed (byte* b = outScratch)
{
int charsRemaining = value.Length, charOffset = 0, bytesWritten;
while (charsRemaining > Scratch_CharsPerBlock)
{
bytesWritten = outEncoder.GetBytes(c + charOffset, Scratch_CharsPerBlock, b, ScratchSize, false);
stream.Write(outScratch, 0, bytesWritten);
charOffset += Scratch_CharsPerBlock;
charsRemaining -= Scratch_CharsPerBlock;
}
bytesWritten = outEncoder.GetBytes(c + charOffset, charsRemaining, b, ScratchSize, true);
if (bytesWritten != 0) stream.Write(outScratch, 0, bytesWritten);
}
}
}
const int ScratchSize = 512;
static readonly int Scratch_CharsPerBlock = ScratchSize / Encoding.UTF8.GetMaxByteCount(1);
private readonly byte[] outScratch = new byte[ScratchSize];
private readonly Encoder outEncoder = Encoding.UTF8.GetEncoder();
static void WriteUnified(Stream stream, byte[] prefix, byte[] value) static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{ {
stream.WriteByte((byte)'$'); stream.WriteByte((byte)'$');
......
...@@ -2322,7 +2322,7 @@ public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCo ...@@ -2322,7 +2322,7 @@ public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCo
{ {
this.ttlCommand = ttlCommand; this.ttlCommand = ttlCommand;
} }
public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + Key; } } public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + (string)Key; } }
public IEnumerable<Message> GetMessages(PhysicalConnection connection) public IEnumerable<Message> GetMessages(PhysicalConnection connection)
{ {
......
...@@ -9,18 +9,36 @@ namespace StackExchange.Redis ...@@ -9,18 +9,36 @@ namespace StackExchange.Redis
public struct RedisKey : IEquatable<RedisKey> public struct RedisKey : IEquatable<RedisKey>
{ {
internal static readonly RedisKey[] EmptyArray = new RedisKey[0]; internal static readonly RedisKey[] EmptyArray = new RedisKey[0];
private readonly byte[] value; private readonly byte[] keyPrefix;
private RedisKey(byte[] value) private readonly object keyValue; // always either a string or a byte[]
internal RedisKey(byte[] keyPrefix, object keyValue)
{ {
this.value = value; this.keyPrefix = (keyPrefix != null && keyPrefix.Length == 0) ? null : keyPrefix;
this.keyValue = keyValue;
} }
internal RedisKey AsPrefix()
{
return new RedisKey((byte[])this, null);
}
internal bool IsNull internal bool IsNull
{ {
get { return value == null; } get { return keyPrefix == null && keyValue == null; }
} }
internal byte[] Value { get { return value; } } internal bool IsEmpty
{
get
{
if (keyPrefix != null) return false;
if (keyValue == null) return true;
if (keyValue is string) return ((string)keyValue).Length == 0;
return ((byte[])keyValue).Length == 0;
}
}
internal byte[] KeyPrefix { get { return keyPrefix; } }
internal object KeyValue { get { return keyValue; } }
/// <summary> /// <summary>
/// Indicate whether two keys are not equal /// Indicate whether two keys are not equal
...@@ -67,7 +85,7 @@ internal bool IsNull ...@@ -67,7 +85,7 @@ internal bool IsNull
/// </summary> /// </summary>
public static bool operator ==(RedisKey x, RedisKey y) public static bool operator ==(RedisKey x, RedisKey y)
{ {
return RedisValue.Equals(x.value, y.value); return CompositeEquals(x.keyPrefix, x.keyValue, y.keyPrefix, y.keyValue);
} }
/// <summary> /// <summary>
...@@ -75,7 +93,7 @@ internal bool IsNull ...@@ -75,7 +93,7 @@ internal bool IsNull
/// </summary> /// </summary>
public static bool operator ==(string x, RedisKey y) public static bool operator ==(string x, RedisKey y)
{ {
return RedisValue.Equals(x == null ? null : Encoding.UTF8.GetBytes(x), y.value); return CompositeEquals(null, x, y.keyPrefix, y.keyValue);
} }
/// <summary> /// <summary>
...@@ -83,7 +101,7 @@ internal bool IsNull ...@@ -83,7 +101,7 @@ internal bool IsNull
/// </summary> /// </summary>
public static bool operator ==(byte[] x, RedisKey y) public static bool operator ==(byte[] x, RedisKey y)
{ {
return RedisValue.Equals(x, y.value); return CompositeEquals(null, x, y.keyPrefix, y.keyValue);
} }
/// <summary> /// <summary>
...@@ -91,7 +109,7 @@ internal bool IsNull ...@@ -91,7 +109,7 @@ internal bool IsNull
/// </summary> /// </summary>
public static bool operator ==(RedisKey x, string y) public static bool operator ==(RedisKey x, string y)
{ {
return RedisValue.Equals(x.value, y == null ? null : Encoding.UTF8.GetBytes(y)); return CompositeEquals(x.keyPrefix, x.keyValue, null, y);
} }
/// <summary> /// <summary>
...@@ -99,7 +117,7 @@ internal bool IsNull ...@@ -99,7 +117,7 @@ internal bool IsNull
/// </summary> /// </summary>
public static bool operator ==(RedisKey x, byte[] y) public static bool operator ==(RedisKey x, byte[] y)
{ {
return RedisValue.Equals(x.value, y); return CompositeEquals(x.keyPrefix, x.keyValue, null, y);
} }
/// <summary> /// <summary>
...@@ -109,15 +127,12 @@ public override bool Equals(object obj) ...@@ -109,15 +127,12 @@ public override bool Equals(object obj)
{ {
if (obj is RedisKey) if (obj is RedisKey)
{ {
return RedisValue.Equals(this.value, ((RedisKey)obj).value); var other = (RedisKey)obj;
} return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue);
if (obj is string)
{
return RedisValue.Equals(this.value, Encoding.UTF8.GetBytes((string)obj));
} }
if (obj is byte[]) if (obj is string || obj is byte[])
{ {
return RedisValue.Equals(this.value, (byte[])obj); return CompositeEquals(this.keyPrefix, this.keyValue, null, obj);
} }
return false; return false;
} }
...@@ -127,7 +142,21 @@ public override bool Equals(object obj) ...@@ -127,7 +142,21 @@ public override bool Equals(object obj)
/// </summary> /// </summary>
public bool Equals(RedisKey other) public bool Equals(RedisKey other)
{ {
return RedisValue.Equals(this.value, other.value); return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue);
}
private static bool CompositeEquals(byte[] keyPrefix0, object keyValue0, byte[] keyPrefix1, object keyValue1)
{
if(RedisValue.Equals(keyPrefix0, keyPrefix1))
{
if (keyValue0 == keyValue1) return true; // ref equal
if (keyValue0 == null || keyValue1 == null) return false; // null vs non-null
if (keyValue0 is string && keyValue1 is string) return ((string)keyValue0) == ((string)keyValue1);
if (keyValue0 is byte[] && keyValue1 is byte[]) return RedisValue.Equals((byte[])keyValue0, (byte[])keyValue1);
}
return RedisValue.Equals(ConcatenateBytes(keyPrefix0, keyValue0, null), ConcatenateBytes(keyPrefix1, keyValue1, null));
} }
/// <summary> /// <summary>
...@@ -135,7 +164,10 @@ public bool Equals(RedisKey other) ...@@ -135,7 +164,10 @@ public bool Equals(RedisKey other)
/// </summary> /// </summary>
public override int GetHashCode() public override int GetHashCode()
{ {
return RedisValue.GetHashCode(this.value); int chk0 = (keyPrefix == null) ? 0 : RedisValue.GetHashCode(this.keyPrefix),
chk1 = keyValue is string ? keyValue.GetHashCode() : RedisValue.GetHashCode((byte[])keyValue);
return unchecked((17 * chk0) + chk1);
} }
/// <summary> /// <summary>
...@@ -148,7 +180,7 @@ public override string ToString() ...@@ -148,7 +180,7 @@ public override string ToString()
internal RedisValue AsRedisValue() internal RedisValue AsRedisValue()
{ {
return value; return (byte[])this;
} }
internal void AssertNotNull() internal void AssertNotNull()
...@@ -162,7 +194,7 @@ internal void AssertNotNull() ...@@ -162,7 +194,7 @@ internal void AssertNotNull()
public static implicit operator RedisKey(string key) public static implicit operator RedisKey(string key)
{ {
if (key == null) return default(RedisKey); if (key == null) return default(RedisKey);
return new RedisKey(Encoding.UTF8.GetBytes(key)); return new RedisKey(null, key);
} }
/// <summary> /// <summary>
/// Create a key from a Byte[] /// Create a key from a Byte[]
...@@ -170,21 +202,33 @@ internal void AssertNotNull() ...@@ -170,21 +202,33 @@ internal void AssertNotNull()
public static implicit operator RedisKey(byte[] key) public static implicit operator RedisKey(byte[] key)
{ {
if (key == null) return default(RedisKey); if (key == null) return default(RedisKey);
return new RedisKey(key); return new RedisKey(null, key);
} }
/// <summary> /// <summary>
/// Obtain the key as a Byte[] /// Obtain the key as a Byte[]
/// </summary> /// </summary>
public static implicit operator byte[](RedisKey key) public static implicit operator byte[](RedisKey key)
{ {
return key.value; return ConcatenateBytes(key.keyPrefix, key.keyValue, null);
} }
/// <summary> /// <summary>
/// Obtain the key as a String /// Obtain the key as a String
/// </summary> /// </summary>
public static implicit operator string(RedisKey key) public static implicit operator string(RedisKey key)
{ {
var arr = key.value; byte[] arr;
if(key.keyPrefix == null)
{
if (key.keyValue == null) return null;
if (key.keyValue is string) return (string)key.keyValue;
arr = (byte[])key.keyValue;
}
else
{
arr = (byte[])key;
}
if (arr == null) return null; if (arr == null) return null;
try try
{ {
...@@ -194,29 +238,61 @@ internal void AssertNotNull() ...@@ -194,29 +238,61 @@ internal void AssertNotNull()
{ {
return BitConverter.ToString(arr); return BitConverter.ToString(arr);
} }
} }
/// <summary> /// <summary>
/// Concatenate two keys /// Concatenate two keys
/// </summary> /// </summary>
[Obsolete]
public static RedisKey operator +(RedisKey x, RedisKey y) public static RedisKey operator +(RedisKey x, RedisKey y)
{ {
return Concatenate(x.value, y.value); return new RedisKey(ConcatenateBytes(x.keyPrefix, x.keyValue, y.keyPrefix), y.keyValue);
}
internal static RedisKey WithPrefix(byte[] prefix, RedisKey value)
{
if(prefix == null || prefix.Length == 0) return value;
if (value.keyPrefix == null) return new RedisKey(prefix, value.keyValue);
if (value.keyValue == null) return new RedisKey(prefix, value.keyPrefix);
// two prefixes; darn
byte[] copy = new byte[prefix.Length + value.keyPrefix.Length];
Buffer.BlockCopy(prefix, 0, copy, 0, prefix.Length);
Buffer.BlockCopy(value.keyPrefix, 0, copy, prefix.Length, value.keyPrefix.Length);
return new RedisKey(copy, value.keyValue);
} }
internal static byte[] Concatenate(byte[] x, byte[] y) internal static byte[] ConcatenateBytes(byte[] a, object b, byte[] c)
{
if ((a == null || a.Length == 0) && (c == null || c.Length == 0))
{ {
// either null? yield the other; note this includes the "both null becomes null" case if (b == null) return null;
if (x == null) return y; if (b is string) return Encoding.UTF8.GetBytes((string)b);
if (y == null) return x; return (byte[])b;
}
// either empty? yield the other int aLen = a == null ? 0 : a.Length,
if (x.Length == 0) return y; bLen = b == null ? 0 : (b is string
if (y.Length == 0) return x; ? Encoding.UTF8.GetByteCount((string)b)
: ((byte[])b).Length),
cLen = c == null ? 0 : c.Length;
byte[] result = new byte[x.Length + y.Length]; byte[] result = new byte[aLen + bLen + cLen];
Buffer.BlockCopy(x, 0, result, 0, x.Length); if (aLen != 0) Buffer.BlockCopy(a, 0, result, 0, aLen);
Buffer.BlockCopy(y, 0, result, x.Length, y.Length); if (bLen != 0)
{
if (b is string)
{
string s = (string)b;
Encoding.UTF8.GetBytes(s, 0, s.Length, result, aLen);
}
else
{
Buffer.BlockCopy((byte[])b, 0, result, aLen, bLen);
}
}
if (cLen != 0) Buffer.BlockCopy(c, 0, result, aLen + bLen, cLen);
return result; return result;
} }
} }
......
...@@ -61,15 +61,16 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer) ...@@ -61,15 +61,16 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer)
/// <summary> /// <summary>
/// Computes the hash-slot that would be used by the given key /// Computes the hash-slot that would be used by the given key
/// </summary> /// </summary>
public unsafe int HashSlot(byte[] key) public unsafe int HashSlot(RedisKey key)
{ {
//HASH_SLOT = CRC16(key) mod 16384 //HASH_SLOT = CRC16(key) mod 16384
if (key == null) return NoSlot; if (key.IsNull) return NoSlot;
unchecked unchecked
{ {
fixed (byte* ptr = key) var blob = (byte[])key;
fixed (byte* ptr = blob)
{ {
int offset = 0, count = key.Length, start, end; int offset = 0, count = blob.Length, start, end;
if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0 if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0
&& (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0 && (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0
&& --end != start) && --end != start)
...@@ -183,10 +184,9 @@ internal int CombineSlot(int oldSlot, int newSlot) ...@@ -183,10 +184,9 @@ internal int CombineSlot(int oldSlot, int newSlot)
} }
internal int CombineSlot(int oldSlot, RedisKey key) internal int CombineSlot(int oldSlot, RedisKey key)
{ {
byte[] blob = key.Value; if (oldSlot == MultipleSlots || key.IsNull) return oldSlot;
if (oldSlot == MultipleSlots || (blob = key.Value) == null) return oldSlot;
int newSlot = HashSlot(blob); int newSlot = HashSlot(key);
if (oldSlot == NoSlot) return newSlot; if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots; return oldSlot == newSlot ? oldSlot : MultipleSlots;
} }
......
...@@ -327,6 +327,16 @@ slave-priority 100 ...@@ -327,6 +327,16 @@ slave-priority 100
# Note that you must specify a directory here, not a file name. # Note that you must specify a directory here, not a file name.
# heapdir <directory path(absolute or relative)> # heapdir <directory path(absolute or relative)>
# If Redis is to be used as an in-memory-only cache without any kind of
# persistence, then the fork() mechanism used by the background AOF/RDB
# persistence is unneccessary. As an optimization, all persistence can be
# turned off in the Windows version of Redis. This will disable the creation of
# the memory mapped heap file, redirect heap allocations to the system heap
# allocator, and disable commands that would otherwise cause fork() operations:
# BGSAVE and BGREWRITEAOF. This flag may not be combined with any of the other
# flags that configure AOF and RDB operations.
# persistence-available [(yes)|no]
# Don't use more memory than the specified amount of bytes. # Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys # When the memory limit is reached Redis will try to remove keys
# accordingly to the eviction policy selected (see maxmemmory-policy). # accordingly to the eviction policy selected (see maxmemmory-policy).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment