公司内部有一个流传很广的负载均衡算法,大概的流程如下:

数组元素对应的是 ip+port 列表,形如下面这样:

每次来一个新请求,都对 ip+port 大小,值为索引的一个数组进行一次 shuffle。然后依次取第一个元素,如果请求失败,那么请求下一台机器。

shuffle 的过程简化过是下面这样:

return slice

乍看似乎没什么问题。数组大小为 N,那么我们就进行 N 次两两交换,最后可以得到一个随机数列。当然了,如果严密一点,我们这里再加个种子:

考虑到可能的性能问题,特意把种子的初始化在初始化函数中只做一次。

实际上这个算法是错误的。验证洗牌算法最简单的方式就是看洗牌之后的数据分布:

不管运行几次,数据的分布总是会有一些规律:

对角线上的数据偏多,而且偏的不是一点半点。对角线上的数据表示 i 元素在第 i 个位置上出现的次数。这表示我们的 shuffle 算法结果具有某种倾向性。这种倾向性为:“每个位置的数字都倾向于待在原位”。

从数学上简单证明一下,因为我们已经知道了数字倾向于不移动,我们来算一下交换操作完全不涉及到 i 的概率,如果有十个数字。那么第一个数字在十次交换中不被拿来交换的概率:

这里为了计算方便,稍微做了一些简化。因为一个数可能被交换到其它位置,在之后的交换过程中又被交换回来了。

计算出来的概率表明,任意一个位置的值都有 81% 以上的概率完全不被交换。所以也就更倾向于留在原位了。

所以这样的洗牌加负载均衡会导致什么样的问题呢?

结论:负载的分布倾向于选择配置文件中的第一台机器。这也就是有一些被调用方会觉得调用方的负载分布不均匀的问题。

在和同事交流的过程中,另一个部门的一位同学说他们 shuffle 算法和我这个不一样,然后给出了他们的:

return dest