Skip to content

Commit 9fc1884

Browse files
committed
feat: enable to use legacy FNV hashes
1 parent ae553d1 commit 9fc1884

File tree

5 files changed

+266
-19
lines changed

5 files changed

+266
-19
lines changed

src/Enyim.Caching/Configuration/MemcachedClientConfiguration.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Net;
41
using Enyim.Caching.Memcached;
5-
using Enyim.Reflection;
62
using Enyim.Caching.Memcached.Protocol.Binary;
3+
using Enyim.Caching.Memcached.Transcoders;
4+
using Enyim.Reflection;
5+
using Microsoft.Extensions.Configuration;
76
using Microsoft.Extensions.Logging;
87
using Microsoft.Extensions.Options;
9-
using Microsoft.Extensions.Configuration;
8+
using System;
9+
using System.Collections.Generic;
1010
using System.Linq;
11+
using System.Net;
1112
using System.Net.Security;
1213
using System.Net.Sockets;
13-
using Enyim.Caching.Memcached.Transcoders;
1414

1515
namespace Enyim.Caching.Configuration
1616
{
@@ -150,7 +150,14 @@ public MemcachedClientConfiguration(
150150

151151
if (NodeLocator == null)
152152
{
153-
NodeLocator = options.Servers.Count > 1 ? typeof(DefaultNodeLocator) : typeof(SingleNodeLocator);
153+
if (options.Servers.Count > 1)
154+
{
155+
NodeLocator = options.UseLegacyNodeLocator ? typeof(LegacyNodeLocator) : typeof(DefaultNodeLocator);
156+
}
157+
else
158+
{
159+
NodeLocator = typeof(SingleNodeLocator);
160+
}
154161
}
155162

156163
if (!string.IsNullOrEmpty(options.Transcoder))

src/Enyim.Caching/Configuration/MemcachedClientOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
using Microsoft.Extensions.Options;
33
using System;
44
using System.Collections.Generic;
5-
using System.Linq;
65
using System.Net.Security;
7-
using System.Threading.Tasks;
86

97
namespace Enyim.Caching.Configuration
108
{
@@ -16,6 +14,8 @@ public class MemcachedClientOptions : IOptions<MemcachedClientOptions>
1614

1715
public List<Server> Servers { get; set; } = new List<Server>();
1816

17+
public bool UseLegacyNodeLocator { get; set; }
18+
1919
public Authentication Authentication { get; set; }
2020

2121
public string KeyTransformer { get; set; }

src/Enyim.Caching/FnvHash.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Enyim
1010
/// Calculation found at http://lists.danga.com/pipermail/memcached/2007-April/003846.html, but
1111
/// it is pretty much available everywhere
1212
/// </remarks>
13-
public class FNV64 : System.Security.Cryptography.HashAlgorithm, IUIntHashAlgorithm
13+
public class FNV64 : HashAlgorithm, IUIntHashAlgorithm
1414
{
1515
protected const ulong Init = 0xcbf29ce484222325L;
1616
protected const ulong Prime = 0x100000001b3L;
@@ -20,10 +20,10 @@ public class FNV64 : System.Security.Cryptography.HashAlgorithm, IUIntHashAlgori
2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="T:FNV64"/> class.
2222
/// </summary>
23-
public FNV64()
23+
public FNV64(bool initialize)
2424
{
2525
//base.HashSize = 64;
26-
Initialize();
26+
if (initialize) Initialize();
2727
}
2828

2929
/// <summary>
@@ -76,6 +76,8 @@ uint IUIntHashAlgorithm.ComputeHash(byte[] data)
7676
/// </summary>
7777
public sealed class FNV64a : FNV64
7878
{
79+
public FNV64a(bool initialize) : base(initialize) { }
80+
7981
/// <summary>Routes data written to the object into the <see cref="T:FNV64" /> hash algorithm for computing the hash.</summary>
8082
/// <param name="array">The input data. </param>
8183
/// <param name="ibStart">The offset into the byte array from which to begin using data. </param>
@@ -108,9 +110,9 @@ public class FNV1 : HashAlgorithm, IUIntHashAlgorithm
108110
/// <summary>
109111
/// Initializes a new instance of the <see cref="T:FNV1a"/> class.
110112
/// </summary>
111-
public FNV1()
113+
public FNV1(bool initialize)
112114
{
113-
Initialize();
115+
if (initialize) Initialize();
114116
}
115117

116118
/// <summary>
@@ -163,6 +165,8 @@ uint IUIntHashAlgorithm.ComputeHash(byte[] data)
163165
/// </summary>
164166
public class FNV1a : FNV1
165167
{
168+
public FNV1a(bool initialize) : base(initialize) { }
169+
166170
/// <summary>Routes data written to the object into the <see cref="T:FNV1a" /> hash algorithm for computing the hash.</summary>
167171
/// <param name="array">The input data. </param>
168172
/// <param name="ibStart">The offset into the byte array from which to begin using data. </param>
@@ -185,6 +189,8 @@ protected override void HashCore(byte[] array, int ibStart, int cbSize)
185189
/// <remarks>Algorithm found at http://bretm.home.comcast.net/hash/6.html</remarks>
186190
public class ModifiedFNV : FNV1a
187191
{
192+
public ModifiedFNV(bool initialize) : base(initialize) { }
193+
188194
/// <summary>
189195
/// Returns the computed <see cref="T:ModifiedFNV" /> hash value after all data has been written to the object.
190196
/// </summary>

src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
3+
using System.Linq;
44
using System.Text;
55
using System.Threading;
66

@@ -42,7 +42,7 @@ private void BuildIndex(List<IMemcachedNode> nodes)
4242

4343
foreach (IMemcachedNode node in nodes)
4444
{
45-
var tmpKeys = DefaultNodeLocator.GenerateKeys(node, _serverAddressMutations);
45+
var tmpKeys = GenerateKeys(node, _serverAddressMutations);
4646

4747
for (var i = 0; i < tmpKeys.Length; i++)
4848
{
@@ -126,7 +126,7 @@ private IMemcachedNode FindNode(string key)
126126
{
127127
if (_keys.Length == 0) return null;
128128

129-
uint itemKeyHash = BitConverter.ToUInt32(new FNV1a().ComputeHash(Encoding.UTF8.GetBytes(key)), 0);
129+
uint itemKeyHash = BitConverter.ToUInt32(new FNV1a(true).ComputeHash(Encoding.UTF8.GetBytes(key)), 0);
130130
// get the index of the server assigned to this hash
131131
int foundIndex = Array.BinarySearch<uint>(_keys, itemKeyHash);
132132

@@ -168,11 +168,11 @@ private static uint[] GenerateKeys(IMemcachedNode node, int numberOfKeys)
168168
// server will be stored with keys 0x0000aabb & 0x0000ccdd
169169
// (or a bit differently based on the little/big indianness of the host)
170170
string address = node.EndPoint.ToString();
171-
var fnv = new FNV1a();
171+
var fnv = new FNV1a(true);
172172

173173
for (int i = 0; i < numberOfKeys; i++)
174174
{
175-
byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(String.Concat(i, "-", address)));
175+
byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(i, "-", address)));
176176

177177
for (int h = 0; h < PartCount; h++)
178178
{
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
7+
namespace Enyim.Caching.Memcached
8+
{
9+
/// <summary>
10+
/// This is a ketama-like consistent hashing based node locator. Used when no other <see cref="T:IMemcachedNodeLocator"/> is specified for the pool.
11+
/// </summary>
12+
public sealed class LegacyNodeLocator : IMemcachedNodeLocator, IDisposable
13+
{
14+
private readonly int _serverAddressMutations;
15+
16+
// holds all server keys for mapping an item key to the server consistently
17+
private uint[] _keys;
18+
// used to lookup a server based on its key
19+
private Dictionary<uint, IMemcachedNode> _servers;
20+
private Dictionary<IMemcachedNode, bool> _deadServers;
21+
private List<IMemcachedNode> _allServers;
22+
private ReaderWriterLockSlim _serverAccessLock;
23+
24+
public LegacyNodeLocator() : this(100)
25+
{
26+
}
27+
28+
public LegacyNodeLocator(int serverAddressMutations)
29+
{
30+
_servers = new Dictionary<uint, IMemcachedNode>(new UIntEqualityComparer());
31+
_deadServers = new Dictionary<IMemcachedNode, bool>();
32+
_allServers = new List<IMemcachedNode>();
33+
_serverAccessLock = new ReaderWriterLockSlim();
34+
_serverAddressMutations = serverAddressMutations;
35+
}
36+
37+
private void BuildIndex(List<IMemcachedNode> nodes)
38+
{
39+
var keys = new uint[nodes.Count * _serverAddressMutations];
40+
41+
int nodeIdx = 0;
42+
43+
foreach (IMemcachedNode node in nodes)
44+
{
45+
var tmpKeys = GenerateKeys(node, _serverAddressMutations);
46+
47+
for (var i = 0; i < tmpKeys.Length; i++)
48+
{
49+
_servers[tmpKeys[i]] = node;
50+
}
51+
52+
tmpKeys.CopyTo(keys, nodeIdx);
53+
nodeIdx += _serverAddressMutations;
54+
}
55+
56+
Array.Sort<uint>(keys);
57+
Interlocked.Exchange(ref _keys, keys);
58+
}
59+
60+
void IMemcachedNodeLocator.Initialize(IList<IMemcachedNode> nodes)
61+
{
62+
_serverAccessLock.EnterWriteLock();
63+
64+
try
65+
{
66+
_allServers = nodes.ToList();
67+
BuildIndex(_allServers);
68+
}
69+
finally
70+
{
71+
_serverAccessLock.ExitWriteLock();
72+
}
73+
}
74+
75+
IMemcachedNode IMemcachedNodeLocator.Locate(string key)
76+
{
77+
if (key == null) throw new ArgumentNullException("key");
78+
79+
_serverAccessLock.EnterUpgradeableReadLock();
80+
81+
try { return Locate(key); }
82+
finally { _serverAccessLock.ExitUpgradeableReadLock(); }
83+
}
84+
85+
IEnumerable<IMemcachedNode> IMemcachedNodeLocator.GetWorkingNodes()
86+
{
87+
_serverAccessLock.EnterReadLock();
88+
89+
try { return _allServers.Except(_deadServers.Keys).ToArray(); }
90+
finally { _serverAccessLock.ExitReadLock(); }
91+
}
92+
93+
private IMemcachedNode Locate(string key)
94+
{
95+
var node = FindNode(key);
96+
if (node == null || node.IsAlive)
97+
return node;
98+
99+
// move the current node to the dead list and rebuild the indexes
100+
_serverAccessLock.EnterWriteLock();
101+
102+
try
103+
{
104+
// check if it's still dead or it came back
105+
// while waiting for the write lock
106+
if (!node.IsAlive)
107+
_deadServers[node] = true;
108+
109+
BuildIndex(_allServers.Except(_deadServers.Keys).ToList());
110+
}
111+
finally
112+
{
113+
_serverAccessLock.ExitWriteLock();
114+
}
115+
116+
// try again with the dead server removed from the lists
117+
return Locate(key);
118+
}
119+
120+
/// <summary>
121+
/// locates a node by its key
122+
/// </summary>
123+
/// <param name="key"></param>
124+
/// <returns></returns>
125+
private IMemcachedNode FindNode(string key)
126+
{
127+
if (_keys.Length == 0) return null;
128+
129+
uint itemKeyHash = BitConverter.ToUInt32(new FNV1a(false).ComputeHash(Encoding.UTF8.GetBytes(key)), 0);
130+
// get the index of the server assigned to this hash
131+
int foundIndex = Array.BinarySearch<uint>(_keys, itemKeyHash);
132+
133+
// no exact match
134+
if (foundIndex < 0)
135+
{
136+
// this is the nearest server in the list
137+
foundIndex = ~foundIndex;
138+
139+
if (foundIndex == 0)
140+
{
141+
// it's smaller than everything, so use the last server (with the highest key)
142+
foundIndex = _keys.Length - 1;
143+
}
144+
else if (foundIndex >= _keys.Length)
145+
{
146+
// the key was larger than all server keys, so return the first server
147+
foundIndex = 0;
148+
}
149+
}
150+
151+
if (foundIndex < 0 || foundIndex > _keys.Length)
152+
return null;
153+
154+
return _servers[_keys[foundIndex]];
155+
}
156+
157+
private static uint[] GenerateKeys(IMemcachedNode node, int numberOfKeys)
158+
{
159+
const int KeyLength = 4;
160+
const int PartCount = 1; // (ModifiedFNV.HashSize / 8) / KeyLength; // HashSize is in bits, uint is 4 byte long
161+
162+
var k = new uint[PartCount * numberOfKeys];
163+
164+
// every server is registered numberOfKeys times
165+
// using UInt32s generated from the different parts of the hash
166+
// i.e. hash is 64 bit:
167+
// 00 00 aa bb 00 00 cc dd
168+
// server will be stored with keys 0x0000aabb & 0x0000ccdd
169+
// (or a bit differently based on the little/big indianness of the host)
170+
string address = node.EndPoint.ToString();
171+
var fnv = new FNV1a(false);
172+
173+
for (int i = 0; i < numberOfKeys; i++)
174+
{
175+
byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(address, "-", i)));
176+
177+
for (int h = 0; h < PartCount; h++)
178+
{
179+
k[i * PartCount + h] = BitConverter.ToUInt32(data, h * KeyLength);
180+
}
181+
}
182+
183+
return k;
184+
}
185+
186+
#region [ IDisposable ]
187+
188+
void IDisposable.Dispose()
189+
{
190+
using (_serverAccessLock)
191+
{
192+
_serverAccessLock.EnterWriteLock();
193+
194+
try
195+
{
196+
// kill all pending operations (with an exception)
197+
// it's not nice, but disposeing an instance while being used is bad practice
198+
_allServers = null;
199+
_servers = null;
200+
_keys = null;
201+
_deadServers = null;
202+
}
203+
finally
204+
{
205+
_serverAccessLock.ExitWriteLock();
206+
}
207+
}
208+
209+
_serverAccessLock = null;
210+
}
211+
212+
#endregion
213+
}
214+
}
215+
216+
#region [ License information ]
217+
/* ************************************************************
218+
*
219+
* Copyright (c) 2010 Attila Kisk? enyim.com
220+
*
221+
* Licensed under the Apache License, Version 2.0 (the "License");
222+
* you may not use this file except in compliance with the License.
223+
* You may obtain a copy of the License at
224+
*
225+
* http://www.apache.org/licenses/LICENSE-2.0
226+
*
227+
* Unless required by applicable law or agreed to in writing, software
228+
* distributed under the License is distributed on an "AS IS" BASIS,
229+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
230+
* See the License for the specific language governing permissions and
231+
* limitations under the License.
232+
*
233+
* ************************************************************/
234+
#endregion

0 commit comments

Comments
 (0)