在分布式系统中,我们常常需要检测某一个节点机器的状态(是否可以正常工作)。常用的分布式部署会有两种方式:中心化和去中心化。中心化架构类似于星型架构,比如HDFS的架构,集群中会有单独一个主服务器(如HDFS中的NameNode)和多个从服务器(如HDFS中的DataNode)。一般这种架构,主服务器来管理所有从服务器的状态,所有从服务器的状态信息会在主服务器中保存,而从服务器之间一般不会知道彼此的存在。而去中心化的构架中,没有主次之分,所有的服务器都是平等的,也就是说每个服务器都知道彼此的存在,一般每台服务器上都保存所有服务器的列表,如Facebook的分布式数据库Cassandra。一般来说,不管是中心化还是去中心话,获取其他服务器的状态都是通过心跳包的形式来处理。虽然我们知道TCP是面向连接的,但是如果遇见拔网线,断电等物理层的一些特殊情况,TCP还是没有办法快速的知道异常,所以心跳包的使用可以帮我们解决这些问题。

 

传统的方法也是使用最广的方法是采用心跳包阈值的方式,我们通常为这种算法设置一个阈值Ttimeout,启用一个线程去定时查看目标服务器的最后心跳包的到达时间。如果当前时间Tnow - 上一次心跳时间Tlast >= Ttimeout的话,就断定不目标服务器不能工作。那我们来看看这种方法弊端:

1. Ttimeout的设置依赖于经验或者是一个估值。如果太小,当网络负载加大,或心跳包发送或接收方本身计算负载增大处理时间增长而引起发送或处理心跳包的时间延迟,那么偏小的阈值Ttimeout就会导致判定异常的可能性增大。而太大的阈值则会影响诊断到异常的时间。所以这个阈值很难在长期运行,变化的分布式系统中保持可靠。

2. 不适用于某些心跳包发送的方式,比如Gossip协议。Gossip并不让心跳包发送者按照固定的时间间隔向其他节点发送心跳包。

 

当然传统心跳包检测方式之所以使用广泛,还是有一定的优势:逻辑简单,容易实现,加上高负载的,高性能的大型分布式系统毕竟少数所以该方式可以快速的解决大部分分布式系统的状态管理。

 

下面介绍两种基于概率密度函数的心跳检测算法。这两种算法都是采用了在一段时间内采集到得一些心跳间隔时间的样本来进行概率计算,类似于一个队列,有最新的心跳过来的时候会进入队列作为算法样本,样本数值为当前过来的心跳于上一次心跳的时间间隔,队列会有最大长度,满了就会把对早的样本踢出样本队列,所以队列窗口保存了最近N个数据样本。通过对样本队列中的样本进行概率计算,最终得出不会再有心跳的概率。

稍后会在代码中显示。

 

基于正态分布的算法

这个算法应用于节点失效估算来源于这篇论文:The ϕ Accrual Failure Detector。论文认为,心跳时间的间隔是满足正态分布的,正态分布的累积分布函数:

正态分布公式

其中sigma,σ是标准偏差,miu,μ代表样本队列的平均值,x则代表随即变量,我们可以把x认为是需要进行概率计算的输入参数。那整个函数则表达了正态分布的几何函数图。函数图以样本数据为横坐标,纵坐标为正态分布的函数值。

而正态分布的累积分布函数为:

9[{~]@{(YQ)S2$]Z`JQ7LJH[21]

最后,论文中提出了最后的函数:

QX7ZCBHX}ZMFKG5ZA([BAVC

MSNW3OX_E~W($$EYI_V3KKP

 

这篇文章中,对该算法做了详细的介绍。

但我在用该算法测试效果的时候,感觉不是很理想,和文论效果相差很大,估计是我测试方法有问题。

 

基于指数分布的算法

在Facebook的分布式数据库Cassandra中,虽然作者参考了The ϕ Accrual Failure Detector这篇论文,但是觉得效果没有指数分布理想,也许是使用了Gossip协议的原因,所以使用了不同的概率密度函数,指数分布函数。

它使用的概率密度函数为:

WZ(EO~I$@WHU610Y~%2I6I5

累积分布函数为:

A4%9I})$07K9OZY0]EJD1MV

 

和上面的算法一样,同样采用滑动窗口采样样本数据的方式。x是随机变量,在这里代表当前时间和最后一次心跳到达时间的间隔。λ是率参数我们这里取值 1/样本平均数。

同样的,按照The ϕ Accrual Failure Detector

P_later(t) = 1 - F(t), F(t)是累计分布函数,上正态分布中使用正太累积分布函数,在这里使用的是指数分布累积函数。最终应该计算
-log10(P_later(t))的值,也就是-log10(1-(1-e^(-λx)))。通过计算也就简化成 
φ(x) = (xλ) / ln(10)。
 
最终的计算结果为节点失效的一个误判概率值。值越大,误判的几率越小。Cassandra里面设置为8。
 
使用C#实现算法如下:
 
public double GetPhi(double timeDuration, double average, double standardDeviation)
        {
            return (-1) * Math.Log10(Math.Pow(Math.E, ((-1) * (timeDuration) / average)));
        }
 

实现的滑动窗口如下:

public class ArrivalWindow
    {
        private BlockingCollection<double> _queue = new BlockingCollection<double>(1000);
        private ICumulativeDistributionAlgorithm _algorithm = new NormalDistributionAlgorithm();
        private object _locker = new object();
 
        public void Add(double timeDuration)
        {
            lock (_locker)
            {
                if (_queue.Count == _queue.BoundedCapacity)
                {
                    _queue.Take();
                }
 
                if (timeDuration <= 10000)
                    _queue.Add(timeDuration);
                else
                    Console.WriteLine(string.Format("Ignoring interval time of {0}", timeDuration));
            }
        }
        /// <summary>
        /// 获取方差
        /// </summary>
        /// <returns></returns>
        public double GetVariance()
        {
            lock (_locker)
            {
                double average = _queue.Average();
                double deviation = 0;
 
                foreach (double item in _queue)
                {
                    deviation += Math.Pow((item - average), 2);
                }
 
                return deviation / _queue.Count;
            }
        }
 
        public double GetPhi(double timeDuration)
        {
            return _algorithm.GetPhi(timeDuration, _queue.Average(), GetVariance());
        }
    }

 

在我的测试中,指数分布的方式效果还是很好的。样本数据越多效果越好。

 

完整实现代码可以在这里下载。

 

参考资料:

The ϕ Accrual Failure Detector

φ累积失败检测算法

Cassandra Paper

posted on 2013-01-15 16:09  nick hao  阅读(3133)  评论(2编辑  收藏  举报