欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

memcached缓存分布式部署方案

程序员文章站 2022-11-05 12:24:14
一、分布式方案介绍 比较流行的两种方案: 1.取余分布: 计算key的哈希值,与服务器数量取余,得到目标服务器。优点:实现简单,当某台服务器不可用时,故障转移方便;缺点:当增减服务器时, Key与服务器取余变动量较大,缓存重组代价极大。 代码实现可参考开源组件Memcached.ClientLibr ......

一、分布式方案介绍

比较流行的两种方案:

1.取余分布:

计算key的哈希值,与服务器数量取余,得到目标服务器。优点:实现简单,当某台服务器不可用时,故障转移方便;缺点:当增减服务器时, key与服务器取余变动量较大,缓存重组代价极大。

代码实现可参考开源组件memcached.clientlibrary下的sockiopool,源码地址:

https://sourceforge.net/p/memcacheddotnet/code/head/tree/trunk/clientlib/src/clientlib/sockiopool.cs

2.一致性哈希环分布:

其原理参考

这两位老哥写的很清楚和直白,很容易理解。

一致性哈希环分布需要物理节点和虚拟节点,且虚拟节点对应到物理节点的服务器上。

二、代码实现

由于memcached.clientlibrary的作者已出取余分布的实现,这里不再叙述,以下代码和测试均是一致性哈希分布的。

1.数据结构:

服务器列表:list<string> servers;//ip:port

服务器虚拟节点数:list<int> weights;//与servers一一对应,灵活设置每server的不同虚拟节点数

节点存储结构:sorteddictionary<long, string> buckets;

key:long类型,存储节点的hash%2^32;

value:string类型,存储节点,即ip:port;

2.代码

计算哈希值算法,参考memcached.clientlibrary下的sockiopool:

https://sourceforge.net/p/memcacheddotnet/code/head/tree/trunk/clientlib/src/clientlib/sockiopool.cs

        private int calculatehashvalue(string key)

        {

            int hv;

            switch (_hashingalgorithm)

            {

                case enumhashingalgorithm.native:

                    hv = key.gethashcode();

                    break;

 

                case enumhashingalgorithm.oldcompatiblehash:

                    hv = hashingalgorithmtool.originalhashingalgorithm(key);

                    break;

 

                case enumhashingalgorithm.newcompatiblehash:

                    hv = hashingalgorithmtool.newhashingalgorithm(key);

                    break;

 

                default:

                    // use the native hash as a default

                    hv = key.gethashcode();

                    _hashingalgorithm = enumhashingalgorithm.native;

                    break;

            }

            return hv;

        }

 

经过测试,oldcompatiblehash方式计算的哈希值比较散列。

//哈希取余值,为什么是2的32次方:ipv4的总量是2的32次方个,可以保证环上的ip不重复
long hashvalue = (long)math.pow(2, 32);
将key生成一致性哈希环中的哈希值
        private long generateconsistenthashvalue(string key)
        {
            long serverhv = calculatehashvalue(key);
            long mod = serverhv % hashvalue;
            if (mod < 0)
            {
                mod = mod + hashvalue;
            }
            return mod;
        }

将servers生成节点(物理+虚拟):

        private void generateserverstobuckets()
        {
            for (int i = 0; i < _servers.count; i++)
            {
                // 创建物理节点
                string server = _servers[i];
                long mod = generateconsistenthashvalue(server);
                buckets.add(mod, server);
                //创建虚拟节点
                list<string> virtualhostservers = generatevirtualserver(server, this.weights[i]);
                foreach (string v in virtualhostservers)
                {
                    mod = generateconsistenthashvalue(v);
                    buckets.add(mod, server);
                }
            }
        }

根据物理节点生成虚拟节点

private static list<string> generatevirtualserver(string server, int count)
        {
            if (count > 255)
            {
                throw new argumentexception("每服务器虚拟节点数不能超过254");
            }
            list<string> virtualservers = new list<string>();

            #region 1.按修改ip值+随机guid生成虚拟节点
            string[] ipaddr = server.split(':');
            string ip = ipaddr[0];
            string port = ipaddr[1];
            int header = convert.toint32(ip.split('.')[0]);
            string invariantippart = ip.substring(ip.indexof('.'));
            int succ = 0;
            for (int i = 1; i <= 255; i++)
            {
                if (i != header)
                {
                    string virtualserver = i.tostring() + invariantippart + ":" + port + i;// guid.newguid().tostring("n").toupper();
                    virtualservers.add(virtualserver);
                    succ++;
                }
                if (succ == count)
                {
                    break;
                }
            } 
            #endregion

            #region 2.物理节点自增序号||随机guid
            //for (int i = 0; i < count; i++)
            //{
            //    //virtualservers.add(server + i.tostring());
            //    virtualservers.add(server + i.tostring()+guid.newguid().tostring());
            //} 
            #endregion

            #region 32.其它生成算法
            //todo
            #endregion

            return virtualservers;
        }

 

三、节点分布测试

四台服务器:{ "192.168.1.100:11211", "192.168.1.101:11211", "192.168.1.102:11211", "192.168.1.103:11211" }

哈希算法不同,则节点分布规则不同

1.物理节点分布

memcached缓存分布式部署方案

2.每物理节点10虚拟节点

memcached缓存分布式部署方案

节点分布测试结果:

节点数共有(物理4+虚拟4*10):44

在第一个节点和第二个节点间:

服务器a的虚拟节点数:1    占比:10%

服务器b的虚拟节点数:1    占比:10%

服务器c的虚拟节点数:0    占比:0%

服务器d的虚拟节点数:2    占比:20%

在第二个节点和第三个节点间:

服务器a的虚拟节点数:0    占比:0%

服务器b的虚拟节点数:0    占比:0%

服务器c的虚拟节点数:2    占比:20%

服务器d的虚拟节点数:2    占比:20%

在第三个节点和第四个节点间:

服务器a的虚拟节点数:4    占比:40%

服务器b的虚拟节点数:5    占比:50%

服务器c的虚拟节点数:4    占比:40%

服务器d的虚拟节点数:4    占比:40%

在第四个节点和第一个节点间:

服务器a的虚拟节点数:5    占比:50%

服务器b的虚拟节点数:4    占比:40%

服务器c的虚拟节点数:4    占比:40%

服务器d的虚拟节点数:2    占比:20%

 3.每物理节点30虚拟节点

memcached缓存分布式部署方案

节点分布测试结果:

节点数共有(物理4+虚拟4*30):124

在第一个节点和第二个节点间:

服务器a的虚拟节点数:7    占比:23%

服务器b的虚拟节点数:7    占比:23%

服务器c的虚拟节点数:6    占比:20%

服务器d的虚拟节点数:7    占比:23%

在第二个节点和第三个节点间:

服务器a的虚拟节点数:3    占比:10%

服务器b的虚拟节点数:1    占比:3%

服务器c的虚拟节点数:4    占比:13%

服务器d的虚拟节点数:4    占比:13%

在第三个节点和第四个节点间:

服务器a的虚拟节点数:11    占比:36%

服务器b的虚拟节点数:11    占比:36%

服务器c的虚拟节点数:10    占比:33%

服务器d的虚拟节点数:10    占比:33%

在第四个节点和第一个节点间:

服务器a的虚拟节点数:9    占比:30%

服务器b的虚拟节点数:11    占比:36%

服务器c的虚拟节点数:10    占比:33%

服务器d的虚拟节点数:9    占比:30%

4. 每物理节点50虚拟节点:.

memcached缓存分布式部署方案

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*50):204

在第一个节点和第二个节点间:

服务器a的虚拟节点数:14    占比:28%

服务器b的虚拟节点数:13    占比:26%

服务器c的虚拟节点数:12    占比:24%

服务器d的虚拟节点数:13    占比:26%

在第二个节点和第三个节点间:

服务器a的虚拟节点数:4    占比:8%

服务器b的虚拟节点数:3    占比:6%

服务器c的虚拟节点数:5    占比:10%

服务器d的虚拟节点数:7    占比:14%

在第三个节点和第四个节点间:

服务器a的虚拟节点数:17    占比:34%

服务器b的虚拟节点数:18    占比:36%

服务器c的虚拟节点数:16    占比:32%

服务器d的虚拟节点数:16    占比:32%

在第四个节点和第一个节点间:

服务器a的虚拟节点数:15    占比:30%

服务器b的虚拟节点数:16    占比:32%

服务器c的虚拟节点数:17    占比:34%

服务器d的虚拟节点数:14    占比:28%

5. 每物理节点80虚拟节点

memcached缓存分布式部署方案

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*80):324

在第一个节点和第二个节点间:

服务器a的虚拟节点数:22    占比:27%

服务器b的虚拟节点数:23    占比:28%

服务器c的虚拟节点数:21    占比:26%

服务器d的虚拟节点数:22    占比:27%

在第二个节点和第三个节点间:

服务器a的虚拟节点数:7    占比:8%

服务器b的虚拟节点数:5    占比:6%

服务器c的虚拟节点数:9    占比:11%

服务器d的虚拟节点数:10    占比:12%

在第三个节点和第四个节点间:

服务器a的虚拟节点数:27    占比:33%

服务器b的虚拟节点数:27    占比:33%

服务器c的虚拟节点数:25    占比:31%

服务器d的虚拟节点数:24    占比:30%

在第四个节点和第一个节点间:

服务器a的虚拟节点数:24    占比:30%

服务器b的虚拟节点数:25    占比:31%

服务器c的虚拟节点数:25    占比:31%

服务器d的虚拟节点数:24    占比:30%

6. 每物理节点100虚拟节点

memcached缓存分布式部署方案

 

 

节点分布测试结果:

节点数共有(物理4+虚拟4*100):404

在第一个节点和第二个节点间:

服务器a的虚拟节点数:29    占比:29%

服务器b的虚拟节点数:30    占比:30%

服务器c的虚拟节点数:28    占比:28%

服务器d的虚拟节点数:28    占比:28%

在第二个节点和第三个节点间:

服务器a的虚拟节点数:8    占比:8%

服务器b的虚拟节点数:8    占比:8%

服务器c的虚拟节点数:11    占比:11%

服务器d的虚拟节点数:12    占比:12%

在第三个节点和第四个节点间:

服务器a的虚拟节点数:33    占比:33%

服务器b的虚拟节点数:32    占比:32%

服务器c的虚拟节点数:30    占比:30%

服务器d的虚拟节点数:30    占比:30%

在第四个节点和第一个节点间:

服务器a的虚拟节点数:30    占比:30%

服务器b的虚拟节点数:30    占比:30%

服务器c的虚拟节点数:31    占比:31%

服务器d的虚拟节点数:30    占比:30%

说明:由于统计计算时按int取值,服务器虚拟节点比率总和可能有1的误差。

总结:以上可以看出当总节点在300以上时,各物理节点之间的虚拟节点所占比率变化较小,说明分布比较均匀。

四、存取数据查找服务器

原理:根据数据的key与hashvalue取余值hv,查找buckets中key>=hv的第一个服务器,即是key的目标服务器,当返回的服务器不可用时,还可以进行故障转移

1.从节点环中查找服务器

private string findserver(string key, ref long startindex)
        {
            long mod = startindex;
            if (mod < 0)
            {
                mod = generateconsistenthashvalue(key);
            }
            foreach (keyvaluepair<long, string> kvp in buckets)
            {
                startindex = kvp.key;
                //找到第一个大于或等于key的服务器
                if (kvp.key >= mod)
                {
                    //若找到的服务器不可用,则继续查找下一服务器
                    if (_hostdead.containskey(kvp.value))
                    {
                        continue;
                    }
                    return kvp.value;
                }
            }
            //如果大于mod的服务器均不可用或没有找到,则从头开始找可用服务器
            foreach (keyvaluepair<long, string> kvp in buckets)
            {
                startindex = kvp.key;
                if (kvp.key >= mod)
                {
                    break;
                }
                if (_hostdead.containskey(kvp.value))
                {
                    continue;
                }
                return kvp.value;
            }
            //不存在可用服务器
            return string.empty;
        }

2.获取服务器及连接

 public isockio getsock(string key)
        {
            if (buckets.count == 0)
            {
                return null;
            }

            if (buckets.count == 1)
            {
                return getconnection(buckets[0]);
            }

            long startindex = -1;//开始查找位置,-1表示从hash(key)% hashvalue位置开始查找
            int tries = 0;//重试次数,不超过总服务器数
            while (tries++ <= this.servers.count)
            {
                string server = findserver(key, ref startindex);
                //找不到可用的服务器
                if (string.isnullorempty(server))
                {
                    return null;
                }
                isockio sock = getconnection(server);
                if (sock != null)
                {
                    writelog.write("key:" + key + ",server:" + server);
                    return sock;
                }
                //是否需要故障转移,若需要,则会继续查找可用的服务器
                if (!_failover)
                {
                    return null;
                }
            }
            return null;
        }