一致性hash 算法

一、场景描述

假设有三台缓存服务器,用于缓存图片,我们为这三台缓存服务器编号为 01号、02号、03号,现在有3万张图片需要缓存,我们希望这些图片被均匀的缓存到这3台服务器上,以便它们能够分摊缓存的压力。也就是说,我们希望每台服务器能够缓存1万张左右的图片,那么我们应该怎样做呢?

常见的做法是对缓存项的键进行哈希,将hash后的结果对缓存服务器的数量进行取模操作,通过取模后的结果,决定缓存项将会缓存在哪一台服务器上

hash(图片名称)% N

存在的问题:

当服务器数量发生改变时,比如新增加一台缓存服务器,所有缓存在一定时间内是失效的,当应用无法从缓存中获取数据时,则会向后端服务器请求数据;

同理,假设突然有一台缓存服务器出现了故障,那么我们则需要将故障机器移除,那么缓存服务器数量从3台变为2台,同样会导致大量缓存在同一时间失效,造成了缓存的雪崩,后端服务器将会承受巨大的压力,整个系统很有可能被压垮。为了解决这种情况,就有了一致性哈希算法。

二、一致性hash 算法是什么?

一致性哈希算法也是使用取模的方法,但是取模算法是对服务器的数量进行取模,而理论上一致性哈希算法是对 2^32 取模,具体步骤如下:

  • 步骤一:一致性哈希算法将整个哈希值空间按照顺时针方向组织成一个虚拟的圆环,称为 Hash 环;

    将 2^32 想象成一个圆,像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个点组成的圆    
  • 步骤二:接着将各个服务器使用 Hash 函数进行哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,从而确定每台机器在哈希环上的位置

    哈希算法:hash(服务器的IP) % 2^32;计算结果一定是 0 到 2^32-1 之间的整数,那么 hash
    环上必定有一个点与这个整数对应,所以我们可以使用这个整数代表服务器,也就是服务器就可以映射到这个环上 
  • 步骤三:最后使用算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针寻找,第一台遇到的服务器就是其应该定位到的服务器

    图片的名称作为 key,所以我们使用下面算法将图片映射在哈希环上:hash(图片名称) % 2^32;
    只要从图片的位置开始,沿顺时针方向遇到的第一个服务器就是图片存放的服务器了
graph LR
A(())

三、一致性hash的优缺点

优点:

一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,只有部分缓存会失效,不至于将所有压力都在同一时间集中到后端服务器上,具有较好的容错性和可扩展性。

缺点:

一致性哈希算法在服务节点太少的情况下,容易因为节点分部不均匀而造成数据倾斜问题,也就是被缓存的对象大部分集中缓存在某一台服务器上,从而出现数据分布不均匀的情况,这种情况就称为 hash 环的倾斜。

通常优化方案:

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点,一个实际物理节点可以对应多个虚拟节点,虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大,hash环倾斜所带来的影响就越小,同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。 具体做法可以在服务器ip或主机名的后面增加编号来实现

四、python实现一致性hash

# -*- coding: utf-8 -*-
import hashlib

class ConsistHash(object):
    def __init__(self, nodes=None, n_number=12) -> None:
        """
        nodes:           所有的节点
        n_number:        一个节点对应多少个虚拟节点
        """
        self._n_number = n_number   #每一个节点对应多少个虚拟节点,这里默认是3个
        self._node_dict = dict()    #用于将虚拟节点的hash值与node的对应关系
        self._sort_list = []        #用于存放所有的虚拟节点的hash值,这里需要保持排序
        if nodes:
            for node in nodes:
                self.add_node(node)

    def add_node(self, node) ->None:
        """
        添加node,首先要根据虚拟节点的数目,创建所有的虚拟节点,并将其与对应的node对应起来
        当然还需要将虚拟节点的hash值放到排序的里面
        这里在添加了节点之后,需要保持虚拟节点hash值的顺序

        """
        for i in range(self._n_number):
            node_str = "%s#%s" % (node,i)    #虚拟节点=n#真实节点#n
            key = self._gen_key(node_str)     #计算虚拟节点的key
            self._node_dict[key] = node       #key和真实节点的对应关系
            self._sort_list.append(key)
        self._sort_list.sort()

    @staticmethod
    def _gen_key(key_str) ->str:
        """
        通过key,返回当前key的hash值,这里采用md5
        """
        return hashlib.md5(key_str.encode(encoding='UTF-8')).hexdigest()   #16进制

    def get_node(self, key_str):
        """
        返回这个字符串应该对应的node,这里先求出字符串的hash值,然后找到第一个小于等于的虚拟节点,然后返回node
        如果hash值大于所有的节点,那么用第一个虚拟节点
        """
        if self._sort_list:
            key = self._gen_key(key_str)
            for node_key in self._sort_list:
                if key <= node_key:
                    return self._node_dict[node_key]
            return self._node_dict[self._sort_list[0]]
        else:
            return None

    def remove_node(self, node):
        """
        这里一个节点的退出,需要将这个节点的所有的虚拟节点都删除
        """
        for i in range(self._n_number):
            node_str = "%s#%s" % (node, i)
            key = self._gen_key(node_str)
            del self._node_dict[key]
            self._sort_list.remove(key)

if __name__ == "__main__":
    cons = ConsistHash(["192.168.1.1","192.168.1.2","192.168.1.3","192.168.1.4"])
    print(cons.get_node("DV001"))
    print(cons.get_node("DV002"))
    print(cons.get_node("DV003"))
    print(cons.get_node("DV004"))
    print(cons.get_node("DV005"))
    print(cons.get_node("DV006"))
    print(cons.get_node("DV007"))

参考链接: https://blog.csdn.net/a745233700/article/details/120814088

机器学习-拉普拉斯平滑(Laplacian smoothing)

零概率和拉普拉斯修正

拉普拉斯平滑(Laplacian smoothing) 是为了解决零概率的问题。拉普拉斯是一个人名,他是法国数学家,并且最早提出用 加1 的方法来估计没有出现过的现象的概率。

零概率问题:在计算事件的概率时,如果某个事件在观察样本库(训练集)中没有出现过,会导致该事件的概率结果是0。这是不合理的,不能因为一个事件没有观察到,就被认为该事件一定不可能发生(即该事件的概率为0)。至于可能出现零概率问题的情况在贝叶斯定理实现是容易出现。

理论假设:假定训练样本很大时,每个分量x的计数加1造成的估计概率变化可以忽略不计,但可以方便有效的避免零概率问题。

举一个例子?来说明一下,如下图,有很多的“0”值,如何把零值影响规避掉呢?

零概率问题

previous : P(wi | wi-1) = c(wi-1, wi) / c(wi)
using smoothing: P(wi | wi-1) = ( c(wi-1, wi) + 1 ) / (c(wi-1) + V)
Then we can ensure that the p will not be zero. Now we can estimate this mothed.
We can use the Reconsitituted formula: c(wi-1, wi) = P(wi | wi-1) * c(wi-1) = ( c(wi-1, wi) + 1 ) / (c(wi-1) + V) * c(wi-1).

总结:分子加一,分母加V,V代表类别数目。
拉普拉斯修正后的数据

再进一步总结就如下:

总结拉普拉斯

场景举栗子:

假设在文本分类中,有3个类:C1、C2、C3。
在指定的训练样本中,某个词语K1,在各个类中观测计数分别为0,990,10。
则对应K1的概率为0,0.99,0.01。

显然C1类中概率为0,不符合实际。

于是对这三个量使用拉普拉斯平滑的计算方法如下:
  1/1003 = 0.001,991/1003=0.988,11/1003=0.011
  
在实际的使用中也经常使用加 λ(0≤λ≤1)来代替简单加1。如果对N个计数都加上λ,这时分母也要记得加上N*λ

股票概念-移动平均和加权算术平均

1、移动平均

移动平均值是一个最老也是最流行的技术分析工具。若依次得到一组测定值时,按顺序取一定数量的数据并算得其全部算术平均值,得到的数据就叫做移动平均值。移动平均值常常用在计算股票的移动平均线、存货成本等方面。下面将介绍如何按移动加权平均法计算存货成本。

移动平均数是指采用逐项递进的办法,将时间序列中的若干项数据进行算术平均所得到的一系列平均数。若平均的数据项数为N,就称为N期(项)移动平均。根据移动平均数来预测就是移动平均预测。

举一个例子,求一组数据的n=3移动平均值

$$ (x_1=10.17,x_2=10.18,x_3=10.19,x_4=10.21, x_5=10.31,x_6=10.41,x_7=10.51,…) $$

$$\frac {x_1 + x_2 + x_3} {3} = \frac {10.17 + 10.18 + 10.19} {3} = 10.18$$

$$\frac {x_2 + x_3 + x_4} {3} = \frac {10.18 + 10.19 + 10.21} {3} = 10.193$$

$$\frac {x_3 + x_4 + x_5} {3} = \frac {10.19 + 10.21 + 10.31} {3} = 10.24$$

$$\frac {x_4 + x_5 + x_6} {3} = \frac {10.21 + 10.31 + 10.41} {3} = 10.31$$

$$\frac {x_5 + x_6 + x_7} {3} = \frac {10.31 + 10.41 + 10.51} {3} = 10.41$$

移动平均预测法与算术平均预测法都是以算术平均数作为预测的依据,但二者又有明显区别。算术平均预测法是对时间序列的全部观察数据求一个平均值,该平均值只能反映现象在观察期内的平均水平,不能反映出趋势的变化。而移动平均预测法是按一定的平均项数滑动着对时间序列求一系列平均值(也叫平滑值),这些平均值不仅能消除或减弱时间序列中的不规则变动,而且能揭示现象的变化趋势,所以移动平均预测法在市场预测中有着广泛的应用。
根据时间序列的特征不同,移动平均预测有的只需要作一次移动平均,有的则需要计算二次移动平均。

一次移动平均

一次移动平均预测就是只需要对时间序列进行一次移动平均,直接用第t期的移动平均数Mt 作为第t期的预测值 yt+1 .

移动平均值,既可以是简单移动平均,也可以是加权移动平均。
如果认为所平均的各项数据重要性相同,就采用简单算术平均法计算移动平均值作为预测值。其计算公式为:

$$ \hat {y}_{t+1} = M_t $$

$$\Longrightarrow {{y_t + y_{t-1} + … + y_{t-N+1}}\over{N}} = \sum_{i=0}^{N-1}y_{t-1}$$

其中yt , yt-1,… 分别代表t,t-1,…期的观察值;N为平均项数。

t+ 1 为了突出近期数据对预测值的影响,可采用加权算术平均法计算移动平均值来预测。权数按“近大远小”的原则确定,具体地说,就是离预测期较近的数据给以较大的权数,离预测期较远的数据给以较小的权数。加权移动平均预测第期预测值的计算公式为

$$ \hat {y}_{t+1} = M_t $$

$$\Longrightarrow {{y_tw_t + y_{t-1}w_{t-1} + … + y_{t-N+1}w_{t-N+1}}\over{w_t + w_{t-1} + … + w_{t-N+1}}} $$

式中,N为移动平均的项数;wt为观察值yt的权数,且满足由近到远权数逐渐递减的原则,即有 wt > wt-1 > … > wt-N+1,为了简便,由近到远各期观察值的权数常常取自然数N,N-1,N-2,…2,1。

采用一次移动平均预测法,需注意以下几点:
(1)平均的项数 越大,移动平均的平滑修匀作用越强。所以如果时间序列中不规则变动的影响大,要想得到稳健的预测值,就要将 取大一些;反之,若不规则变动的影响较小,要想使预测值对现象的变化作出较快的跟踪反应,就要将 取小一些。
(2)当序列包含周期性变动时,移动平均的项数k应与周期长度一致。这样才能在消除不规则变动的同时,也消除周期性波动,使移动平均值序列只反映长期趋势。因此,季度数据通常采用四项移动平均,月度数据通常采用十二期移动平均。
(3)一次移动平均预测只具有推测未来一期趋势值的预测功能,而且只适用于呈水平趋势的时间序列。如果现象的发展变化具有明显的上升(或下降)趋势,就不能直接采用一次移动平均值作为预测值,否则预测结果就会产生偏低(或偏高)的滞后偏差,即预测值的变化要滞后于实际趋势值的变化。移动平均的项数 越大,这种滞后偏差的绝对值就越大。对具有上升(或下降)趋势的时间序列进行移动平均预测,必须要考虑滞后偏差,最常用的方法是下面介绍的二次移动平均预测。

3、二次移动平均法

二次移动平均预测是指先对时间序列进行N项移动平均,平均的结果称为一次移动平均值Mt(1) ,再对一次移动平均值序列Mt(1)进行N项移动平均,平均的结果称为二次移动平均值,记为Mt(2);然后根据两次移动平均值建立预测模型进行预测。

两次移动平均值一般都采用简单算术平均法来计算。其计算公式为

$$M_{t}^{(1)} = {{y_t + y_{t-1} + … + y_{t-N+1}}\over{N}}$$

$$M_{t}^{(2)} = {{M_{t}^{(1)} + M_{t-1}^{(1)} + … + M_{t-N+1}^{(1)}}\over{N}}$$

如果现象的变化呈线性趋势,则利用两次移动平均值可建立如下的线性预测模型:

$$a_{t} + b_{t}K = \hat y_{t+T} $$

式中,t是预测的时间起点;K是时间t距离预测期的期数(即第t+K期为预测期);at ,bt是预测模型中第期的参数估计值。其计算公式为

二次移动平均公式推导

4、加权算术平均

加权算法平均

用加权移动平均法求预测值,对近期的趋势反映较敏感,但如果一组数据有明显的季节性影响时,用加权移动平均法所得到的预测值可能会出现偏差。因此,有明显的季节性变化因素存在时,最好不要加权。