一致性HASH, 作用在于当有服务器实效的情况下,保持CACHE的一致性.避免引起大量读CACHE失效.
假设服务部分的架构如下: appServer(10台)--- servicesServer(3台)---Database(2台 master,slave)
servicesServer有相关用户数据的CACHE, 通过id模servicesServerNum映射到相应的servicesServer上。 假设 有用户 1 到 3 访问某个页面,即用户(u1,u2,u3)访问 appServer. appServer通过接口调用访问servicesServer,用户0的CACHE在servicesServer0上,以此类推.
可是天有不测风云, servicesServer0因为磁盘问题挂掉了,现在会出现什么情况呢?
由于我们通过 id模servicesServerNum映射得到相应的servicesServer,但是现在只有2台机器了, 于是 :
before servicesServer0 down: (0--s0, 1--s1, 2--s2)
u1 1mod3--> s1
u2 2mod3--> s2
u3 3mod3--> s0
after servicesServer0 down(0--s1, 1--s2)
u1 1mod2--> s2 change
u2 2mod2--> s1 change
u3 3mod2--> s2 change
在这种情况下,原来的CACHE全部失效, 所有的读请求全部瞬间压倒Database上面。
一致性CACHE就是为了解决这个问题提出来的。它不是直接的取模映射,而是将一个区间影射到特定服务器上,这样即使机器减少或者增加,大部分的CACHE仍然能够有效,避免了瞬间的冲击。 为了更好的平衡CACHE的分布,还引入了虚拟节点的概念,一个实际的节点,可以化生为多个不同的虚拟节点,这样能够让区间映射更加平衡。 下面是我测试的代码 :
class Program
{
BNode[] _BNodes;
int _ArrayLength;
static void Main(string[] args)
{
Program ptest = new Program();
ptest.InitArray(5, 4);
ptest.ShowNodes(5,4);
ptest.SortArray();
int restring = ptest.BserachServer(3);
restring = ptest.BserachServer(152);
restring = ptest.BserachServer(155);
restring = ptest.BserachServer(0);
}
void InitArray(int RealNodeNum, int VirtualCopyNum)
{
int totalNum = RealNodeNum * VirtualCopyNum ;
_BNodes = new BNode[totalNum];
_ArrayLength = totalNum;
for (int i = 0; i < RealNodeNum; i++)
{
for(int j = 0; j < VirtualCopyNum ; j++)
{
BNode tempNode = new BNode();
tempNode.IPAddr = "172.16.73." + (i+1);
tempNode.hashcode = ( "V" + j + tempNode.IPAddr ).GetHashCode();
//Console.Out.WriteLine(tempNode.hashcode);
tempNode.flag = 1;
//Console.Out.WriteLine(tempNode.hashcode);
_BNodes[i] = tempNode;
}
}
}
void ShowNodes(int RealNodeNum, int VirtualCopyNum)
{
int totalNum = RealNodeNum * VirtualCopyNum;
for (int i = 0; i < totalNum; i++)
{
Console.Out.Write(_BNodes[i].hashcode + " ");
Console.Out.Write(_BNodes[i].flag + " ");
Console.Out.WriteLine(_BNodes[i].IPAddr);
}
}
bool DelOneNode(int machineID, int VirtualCopyNum)
{
int i = machineID;
for (int j = 0; j < VirtualCopyNum; j++)
{
string IPAddr = "172.16.73." + (i + 1);
int hashcode = ("V" + j + IPAddr).GetHashCode();
BNode delNode = BserachNode(hashcode);
if (delNode != null)
{
delNode.flag = 0;
}
}
return true;
}
int FindNode(int objectHashCode)
{
int pos = BserachServer(objectHashCode);
int tempos = pos;
int cycleFlag =0;
while ( !( tempos == pos && cycleFlag == 1) )
{
if (_BNodes[pos].flag > 0)
{
return pos;
}
else
{
if (pos == 0)
{
pos = _ArrayLength-1;
cycleFlag = 1;
}
pos = pos - 1;
}
}
return 0;
}
void SortArray()
{
ShowNodes(5, 4);
IComparer myComparer = new myCompareClass();
Array.Sort(_BNodes, myComparer);
ShowNodes(5, 4);
}
int BserachServer(int cmphashcode)
{
if (_BNodes[_ArrayLength - 1].hashcode <= cmphashcode || _BNodes[0].hashcode > cmphashcode)
{
return _ArrayLength - 1;
}
{
int lowPos = 0;
int highPos = _ArrayLength;
int middlePos = (lowPos + highPos) / 2;
while (true)
{
middlePos = (lowPos + highPos) / 2;
if (middlePos < lowPos )
{
return middlePos;
}
if (lowPos > highPos)
{
return middlePos;
}
if (cmphashcode < _BNodes[middlePos].hashcode)
{
highPos = middlePos - 1;
}
else if (cmphashcode > _BNodes[middlePos].hashcode)
{
lowPos = middlePos + 1;
}
else
{
return middlePos;
}
}
}
}
BNode BserachNode(int cmphashcode)
{
int lowPos = 0;
int highPos = _ArrayLength;
int middlePos = (lowPos + highPos) / 2;
while (middlePos < lowPos || lowPos > highPos)
{
middlePos = (lowPos + highPos) / 2;
if (cmphashcode < _BNodes[middlePos].hashcode)
{
highPos = middlePos - 1;
}
else if (cmphashcode > _BNodes[middlePos].hashcode)
{
lowPos = middlePos + 1;
}
else
{
return _BNodes[middlePos];
}
}
return null;
}
internal class BNode
{
public int hashcode;
public string IPAddr;
public int flag;
}
internal class myCompareClass : IComparer
{
int IComparer.Compare(object x, object y)
{
if (((BNode)x).hashcode == ((BNode)y).hashcode)
{
return 0;
}
else if (((BNode)x).hashcode > ((BNode)y).hashcode)
{
return 1;
}
else
return -1;
}
}
}