memcached单点故障与负载均衡

在上文中,主要教大家如何搭建在windows  IIS 7.5下搭建php环境,使用常见的两种memcached性能监视工具。通过自己动手实践,观察监控工具上数据,相信大家对于memcached的了解一定深入了很多。但是同样还有些疑惑。本文将用图文的方式,继续讲解memcached在集群环境下的使用技巧。

曾经看到过这样的文字(大概是翻译过来的,算是比较权威的)

memcached如何处理容错的?

不处理!:) 在memcached节点失效的情况下,集群没有必要做任何容错处理。如果发生了节点失效,应对的措施完全取决于用户。节点失效时,下面列出几种方案供您选择:

* 忽略它! 在失效节点被恢复或替换之前,还有很多其他节点可以应对节点失效带来的影响。

* 把失效的节点从节点列表中移除。做这个操作千万要小心!在默认情况下(余数式哈希算法),客户端添加或移除节点,会导致所有的缓存数据不可用!因为哈希参照的节点列表变化了,大部分key会因为哈希值的改变而被映射到(与原来)不同的节点上

* 启动热备节点,接管失效节点所占用的IP。这样可以防止哈希紊乱(hashing chaos)。

 

根据上面的说法,memcached其中一个节点失效以后,memcached本身是没有任何策略维持失效转发的,这对于大型系统是一个无法接受的事实。

 

Memcached基于一 个存储键/值对的hashmap。其守护进程是用C写的,但是客户端可以用任何语言来编写(本文使用C#作为例子),并通过memcached协议与守护进程通信。可           能这些东西都太高深了,我们暂不做研究。  

 

虽然 Memcached作为一个分布式缓存数据服务,但是每个服务之间根本没有进行相互通信,这里可能与 我理解的分布式有点区别,可能是我才疏学浅,也可能是每个人思考问题的角度不同。Memcached 客户端就是通过一种分布式算法将数据保存到不同的Memcached服务器上,将数据进行缓存。

Memcached分布式环境下,每个服务器端本身没有相互相连的关系,数据分布其实是由客户端来维持的(通俗点说,是客户端按照自己的分布算法,将数据分配 给指定的服务端去存储,取值的时候,客户端再找指定的服务器拿数据。任何环境下,服务端都不可能主动去找客户端拿“东西”或者去操作客户端。B/S模式也 是的,web服务器不可能主动找浏览器拿东西,更不可能对浏览器端做任何操作)。memcached的服务端更不会这么聪明,自动去查找、匹配当前环境 中分布的其他服务器。

而且,据我所知,Memcached本身并没有为集群提供真的高可用方案,因为我个人认为,使用集群环境,通常是为了满足以下的需求:

1.压力分载 (负载均衡)    2.失效转发(故障转移)。

 

而memcached本身并不具备这两点,这对于以“分布式缓存”号称的memcached来说,是非常致命的。对于笔者来说,也是一种沉痛的打击啊(o(∩_∩)o 哈哈)。

理论上来讲,客户端连接多个memcached服务端的时候,默认的数据分布是这样的:

memcached单点故障与负载均衡_第1张图片

理论上的,%33+33%+34%=100%,看上去数据分布还还很均衡,读取的时候,分别访问从三台服务器内存,再组成完整的数据。这样的数据分发架构,倒真正做到了“负载均衡”。降低了三台服务器的内存使用率,让三台服务器同时为客户端提供服务,这难道不是完美的负载均衡吗?如果没有配置监视工具,也可以参照下面的代码:

 

[csharp]  view plain  copy
 
  1. public void testMemcachedProviders()   
  2.        {  
  3.            int runs = 100;  
  4.            int start = 200;  
  5.             
  6.            string keyBase = "testKey";  
  7.            string obj = "This is a test of an object blah blah es, serialization does not seem to slow things down so much.  The gzip compression is horrible horrible performance, so we only use it for very large objects.  I have not done any heavy benchmarking recently";  
  8.              
  9.            //Response.Write(obj);  
  10.            //循环记时往服务器缓存上插入数据  等会我们要观察一下数据都存到哪个服务器上的Memcached server上了  
  11.            long begin = DateTime.Now.Ticks;  
  12.            for (int i = start; i < start + runs; i++)  
  13.            {   
  14.              // DistCache.Add(keyBase + i, obj);  
  15.            }  
  16.            long end = DateTime.Now.Ticks;  
  17.            long time = end - begin;  
  18.   
  19.            //计算存储这些数据花了多长时间  
  20.            //Response.Write(runs + " sets: " + new TimeSpan(time).ToString() + "ms"+"<br/>");  
  21.   
  22.            //开始取数据,并记时  
  23.            begin = DateTime.Now.Ticks;  
  24.            int hits = 0;  
  25.            int misses = 0;  
  26.            for (int i = start; i < start + runs; i++)  
  27.            {  
  28.                string str = (string)DistCache.Get(keyBase + i);  
  29.                if (str != null)  
  30.                    ++hits;    //成功取到数据  
  31.                else  
  32.                    ++misses;  //丢失次数  
  33.            }  
  34.            end = DateTime.Now.Ticks;  
  35.            time = end - begin;  
  36.   
  37.            //获取这些数据花了多长时间  
  38.            Response.Write(runs + " gets: " + new TimeSpan(time).ToString() + "ms"+"<br/>");  
  39.            Response.Write("Cache hits: " + hits.ToString() + "<br/>");  
  40.            Response.Write("Cache misses: " + misses.ToString() + "<br/>");  
  41.            Response.Write("--------------------------------------------------------\r\n");  
  42.              
  43.        }  


使用上面的测试代码,可以打印输出处理时间,get/set次数。分别注释掉配置文件中指定memcached服务器配置后,再读取测试,可以清楚的看到数据分布比例。

 

我本地开启了3个memcached服务,分别指向不同端口,数据的分布比例是这样的: 37%,43%,20%。没有理论上的那么均衡。

有过分布式集群架构的朋友,肯定会想到,那万一发生了“单点故障”(就像sqlserver集群中的,单个节点上的数据库服务器宕机),那不是玩完了?

 

memcached单点故障与负载均衡_第2张图片

按照上图所示,一台服务器宕机了,就有33%的数据丢失了。那不就玩完了。如果是某银行采用这种架构,发生如此杯具,那架构师岂不是要被群众拿刀砍死。

那到底该如何解决这个问题呢?我翻阅了很多中文甚至英文的资料,好像真的没有官方或者很权威的解决方案。提供了如下两种思路。

 

解决方案1:本地备份缓存
    在本地放一份缓存,同时也在分布式Memcached上放一份缓存,如果当其中一台节点当机了,客户端程序直接读取本地的缓存,本地客户端维护一个HashMap即可,这样的方案虽然很简陋,但是可以满足一部分场景的需要,当你很急需的时候可以作为临时方案暂时替代一下。

解决方案2:采用缓存代理服务器
    采用 Magent 缓存代理,防止单点现象,缓存代理也可以做备份,通过客户端连接到缓存代理服务器,缓存代理服务器连接缓存服务器,缓存代理服务器可以连接多台Memcached机器可以将每台Memcached机器进行数据同步。这样的架构比较完善了,如果其中一台缓存代理服务器down机,系统依然可以继续工作,如果其中一台Memcached机器down掉,数据不会丢失并且可以保证数据的完整性,以上描述的系统架构如图所示:

memcached单点故障与负载均衡_第3张图片

 

在笔者的实践中,沿袭了第一种方案的思想。由于笔者项目使用的是windows的服务器,而第二种方案中的magent代理软件,好像只支持linux平台。

在客户端还是配置多台服务器,但是让其中任意的一台服务器做备份,去读取并append另外几台服务器的数据,这样依赖,该台备份服务器上就始终存储了一份完整的数据。当发生意外情况的时候,直接读取备份服务器上的数据。等服务器故障恢复后,再从客户端,将数据合理的分发出去。

在.NET平台下,就不能选用enyim.com Memcached Client或者Memcached Providers之类封装得太完善的client啦!涉及到很多基本的操作,这里推荐使用.NET memcached client library这个比较原始的类库client。我始终觉得,最原始的,往往就是最灵活的。

 

[csharp]  view plain  copy
 
  1. public void testClientLib()   
  2.        {  
  3.            string[] servers = { "127.0.0.1:11211", "127.0.0.1:11212","127.0.0.1:11213" };//多台服务器构成集群,端口号就是memcached.ini中的listener_port=11212  
  4.            string[] serversOne = { "127.0.0.1:11211" };//测试服务器列表  
  5.            //初始化池  
  6.            SockIOPool pool = SockIOPool.GetInstance();  
  7.           // pool.SetServers(servers);  
  8.            pool.SetServers(serversOne);//测试服务器  
  9.            pool.InitConnections = 3;  
  10.            pool.MinConnections = 3;  
  11.            pool.MaxConnections = 5;  
  12.            pool.SocketConnectTimeout = 1000;  
  13.            pool.SocketTimeout = 3000;  
  14.            pool.MaintenanceSleep = 30;  
  15.            pool.Failover = true;  
  16.            pool.Nagle = false;  
  17.            pool.Initialize();  
  18.            //初始化客户端  
  19.            Memcached.ClientLibrary.MemcachedClient mc = new Memcached.ClientLibrary.MemcachedClient();  
  20.            mc.EnableCompression = false;  
  21.   
  22.            string keybase = "test";  
  23.            //if (mc.Get(keybase) == null)  
  24.            //{  
  25.                //尝试添加数据  
  26.                #region 单个key的情况,value值增大,数据不会自动分布,全都集中在一台服务器上  
  27.                //List<int> list = new List<int>();  
  28.                //for (int i = 0; i < 100; i++)  
  29.                //{  
  30.                //    list.Add(i);  
  31.                //}  
  32.                //bool reslut =  mc.Add("test", list);  
  33.                //if (reslut)  
  34.                //{  
  35.                //    Response.Write("Add cache success");  
  36.                //}  
  37.                 
  38.                #endregion  
  39.                #region 多个key的情况,数据会自动均衡的分布  三台服务器 33%,33%,34%  
  40.                //for (int i = 0; i < 100; i++)  
  41.                //{  
  42.                //    bool result = mc.Add(keybase + i, i);  
  43.                //    if (!result) {  
  44.                //        Response.Write("Add cache faild");  
  45.                //    }  
  46.                //}  
  47.                #endregion  
  48.                  
  49.           // }  
  50.           // else {  
  51.                //object value = mc.Get("test");  
  52.                int count = 0;  
  53.                for (int i = 0; i < 100; i++)  
  54.                {  
  55.                    object value = mc.Get(keybase + i);  
  56.                    if(value!=null)  
  57.                    {  
  58.                        ++count;  
  59.                    }  
  60.                }  
  61.                Response.Write("服务器存储数据量:"+count);  
  62.         //   }  
  63.              
  64.            pool.Shutdown();  
  65.        }  

 

通过本地备份的方式,解决单点故障:

 

[csharp]  view plain  copy
 
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Text;  
    5. using System.Configuration;  
    6. using System.Web;  
    7. using Memcached.ClientLibrary;  
    8.   
    9.   
    10. namespace MemcachedPro  
    11. {  
    12.    public  class MemcacheProvider  
    13.     {  
    14.        MemcachedClient mainClient;  
    15.        MemcachedClient backupClient;  
    16.         /// <summary>  
    17.         /// 在构造函数中,初始化客户端(主/备)  
    18.         /// </summary>  
    19.         public MemcacheProvider()   
    20.         {  
    21.             //主服务器客户端  
    22.             mainClient = new MemcachedClient();  
    23.             mainClient.PoolName = GetMainPollName();  
    24.             mainClient.EnableCompression = false;  
    25.              
    26.             //备份服务器客户端  
    27.             backupClient = new MemcachedClient();  
    28.             backupClient.PoolName = GetBackUpPollName();  
    29.             backupClient.EnableCompression = false;  
    30.         }  
    31.       /// <summary>  
    32.       /// 初始化主服务器pool  
    33.       /// </summary>  
    34.       /// <returns></returns>  
    35.        public  string GetMainPollName()  
    36.         {  
    37.             //string[] Servers = { "127.0.0.1:11211" };//测试服务器列表  
    38.             string strServers = ConfigurationManager.AppSettings["memcacheMainServer"];  
    39.             string[] Servers = strServers.Split(';');  
    40.            //初始化池  
    41.             SockIOPool pool = SockIOPool.GetInstance("p1");  
    42.             pool.SetServers(Servers);//测试服务器  
    43.             pool.InitConnections = 3;  
    44.             pool.MinConnections = 3;  
    45.             pool.MaxConnections = 5;  
    46.             pool.SocketConnectTimeout = 1000;  
    47.             pool.SocketTimeout = 3000;  
    48.             pool.MaintenanceSleep = 30;  
    49.             pool.Failover = true;  
    50.             pool.Nagle = false;  
    51.             pool.Initialize();  
    52.             return "p1";  
    53.         }  
    54.        /// <summary>  
    55.        /// 初始化备份服务器pool  
    56.        /// </summary>  
    57.        /// <returns></returns>  
    58.         public string GetBackUpPollName()  
    59.         {  
    60.            // string[] Servers = { "127.0.0.1:11212" };//备份服务器列表  
    61.             string strServers = ConfigurationManager.AppSettings["memcacheBackupServer"];  
    62.             string[] Servers = strServers.Split(';');  
    63.             //初始化池  
    64.             SockIOPool pool = SockIOPool.GetInstance("p2");  
    65.             pool.SetServers(Servers);//测试服务器  
    66.             pool.InitConnections = 3;  
    67.             pool.MinConnections = 3;  
    68.             pool.MaxConnections = 5;  
    69.             pool.SocketConnectTimeout = 1000;  
    70.             pool.SocketTimeout = 3000;  
    71.             pool.MaintenanceSleep = 30;  
    72.             pool.Failover = true;  
    73.             pool.Nagle = false;  
    74.             pool.Initialize();  
    75.             return "p2";  
    76.         }  
    77.        /// <summary>  
    78.        /// 设置值  
    79.        /// </summary>  
    80.        /// <param name="key"></param>  
    81.        /// <param name="value"></param>  
    82.        /// <returns></returns>  
    83.         public bool SetCache(string key, object value)  
    84.         {  
    85.             bool result = false;  
    86.             try  
    87.             {  
    88.                 //设置到主服务器组  
    89.                 result = mainClient.Set(key, value);  
    90.   
    91.                 //设置备份  
    92.   
    93.                 result = backupClient.Set(key, value);  
    94.             }  
    95.             catch (Exception)  
    96.             {  
    97.                 //发送短信或者邮件提醒  
    98.                 throw;  
    99.             }  
    100.              
    101.             return result;  
    102.         }  
    103.        /// <summary>  
    104.        /// 取值  
    105.        /// </summary>  
    106.        /// <param name="key"></param>  
    107.        /// <returns></returns>  
    108.         public object GetCache(string key)  
    109.         {  
    110.             object value = null;  
    111.             //先读主服务器  
    112.             try  
    113.             {  
    114.                 value = mainClient.Get(key);  
    115.                 //如果没取到值  
    116.                 if (value == null)  
    117.                 {  
    118.                     //发送短信或者邮件提醒:可能主服务器宕机了  
    119.   
    120.                     //从备份服务器取值  
    121.                     value = backupClient.Get(key);  
    122.                     if (value == null)  
    123.                     {  
    124.                         //从备份服务器取值也失败,发送短信或者邮件提醒  
    125.                     }  
    126.                 }  
    127.             }  
    128.             catch (Exception)  
    129.             {  
    130.                 //发送短信或者邮件提醒  
    131.                 throw;  
    132.             }  
    133.               
    134.              
    135.             return value;  
    136.         }  
    137.        /// <summary>  
    138.        /// 当主服务器恢复运行后(数据已经丢失了),将备份服务器中的缓存同步到主服务器  
    139.        /// </summary>  
    140.        /// <returns></returns>  
    141.         public bool RestoreCache()   
    142.         {  
    143.             bool result = false;  
    144.               
    145.             return result;  
    146.         }  
    147.     }  
    148. }  

你可能感兴趣的:(memcached单点故障与负载均衡)