源地址哈希算法—不丟失策略的負載均衡方法,可用於AB實驗分流


一.分流和負載均衡區別

結論:AB實驗分流不能丟失策略,分流比要完美趨近權重。

負載均衡只要將流量根據權重打到不同的機器即可,所有機器的業務邏輯都是一樣的。

AB實驗分流則需要:

  1. 將同一個請求多次訪問時打到同一個策略,保證請求不丟失策略。比如用戶訪問一個頁面時不能交替出現不同的展示;
  2. 負載均衡,保證流量比完美趨近於權重。

綜上AB實驗需要保證不丟失策略流量分配無限接近與權重比。

二.常用負載均衡優缺點

結論:源信息hash算法是唯一不丟失策略的算法,但是負載均衡和源數據信息和哈希算法有很大關系。 因為分流策略是工程方引入的jar包,平台也需要提供調試功能,因此也不能使用統一的緩存管理。

常用負載均衡算法有輪詢法、隨機法、源地址哈希、一致性哈希和最少活躍調用五種思路。其中 dubbo實現除了源地址哈希的其他四種。其詳細分析見另一片博文,此處重點講源地址哈希算法:

  1. 輪詢法:最趨於平衡的負載均衡算法,但是修改指針時需要加鎖,性能較低;
  2. 隨機發:請求數量越高平衡性越趨近於輪詢,一般使用隨機數或者System.identityHashCode(obj)
  3. 源地址哈希:唯一不丟失策略的算法,但是負載均衡和源數據信息和哈希算法有很大關系,AB實驗分流一般采用此方法;
  4. 一致性哈希算法:todo
  5. 最少獲取調用:todo

三.幾種分流策略的標准差比較

使用輪詢和隨機路由做基准,然后對比uuid簡單hash、考慮高位的hash、md5算法以及md5后hash,經測md5后hash效果最好

37617條數據,分成100個桶,幾種方法標准差(程序輸出)如下:

期望值:expect:376.17
輪詢:0.375632799419859	base
隨機路由:17.838192172975376	randomRoutain

//如下可知md5然后哈希的效果最好
uuid簡單哈希:19.11337489822245	simpleHash
考慮高位的uuid哈希: 19.753002303447445	bitHash from HashMap:
md5值轉數字取模:76.02381929369243	md5Hash:
md5值求哈希: 18.963678440640148	md5ThenHash

/**
 * @Description 標准差衡量數據總體波動范圍(方差描述離散程度)
 * fixme:一千萬次調用,一百個桶,每個桶期望值十萬;
 * @Date 2018/9/8 下午4:48
 * -
 * @Author dugenkui
 **/

public class SourceHashBalanceTest {
    /**
     * 獲取uuid數據集合
     */
    static List<String> uuidList = new ArrayList<>();
    static {
        try {
            File file = new File("/Users/moriushitorasakigake/Desktop/uuid1.txt");
            BufferedReader fileReader = new BufferedReader(new FileReader(file));
            String tmp;
            while ((tmp = fileReader.readLine()) != null) {
                uuidList.add(tmp);
            }
        } catch (Exception e) {
            System.out.println("請在指定目錄添加樣本數據");
        }
    }

    /**
     * 實驗次數,分桶和每個桶期望值
     */
    static final long EXP_COUNT = uuidList.size();
    static final long MODEL = 100;
    static final double AVG_COUNT = (double)EXP_COUNT / MODEL;


    //基准測試的標准差:證明樣本數據每個值完全不同而且變化時標准差為0.0
    static double baseTest() {
        Map<Integer, Integer> pointCount = new HashMap();
        for (int i = 0; i < EXP_COUNT; i++) {
            int pos = i % 100;
            pointCount.compute(pos, (k, v) -> v == null ? 1 : ++v);
        }

        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }


    /**
     * 隨機方法的誤差:理論上請求量越大誤差越小
     */
    static double randomRoutain() {
        Random rand = new Random(System.currentTimeMillis());
        Map<Integer, Integer> pointCount = new HashMap();
        for (int i = 0; i < EXP_COUNT; i++) {
            int hash = Math.abs(rand.nextInt() % 100);
            pointCount.compute(hash, (k, v) -> v == null ? 1 : ++v);
        }
        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }

    /**
     * 字符串簡單哈希求值
     *
     * @return
     */
    static double simpleHash() {
        Map<Integer, Integer> pointCount = new HashMap();
        for (String uuid : uuidList) {
            int hash = Math.abs(uuid.hashCode() % 100);
            pointCount.compute(hash, (k, v) -> v == null ? 1 : ++v);
        }
        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }

    /**
     * 字符串注意高位影響的哈希(參考HashMap)
     *
     * @return
     */
    static double bitHash() {
        Map<Integer, Integer> pointCount = new HashMap();
        for (String uuid : uuidList) {
            int hash = Math.abs((uuid.hashCode() ^ (uuid.hashCode() >>> 16)) % 100);
            pointCount.compute(hash, (k, v) -> v == null ? 1 : ++v);
        }
        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }


    /**
     * 字符串求MD5值取模
     *
     * @return
     */
    static double md5Hash() {
        Map<Integer, Integer> pointCount = new HashMap();
        for (String uuid : uuidList) {
            int md5Num=Integer.parseInt(DigestUtils.md5DigestAsHex(uuid.getBytes()).substring(30),16);
            int hash = Math.abs(md5Num % 100);
            pointCount.compute(hash, (k, v) -> v == null ? 1 : ++v);
        }
        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }

    /**
     * 字符串求MD5值后在hash
     *
     * @return
     */
    static double md5ThenHash() {
        Map<Integer, Integer> pointCount = new HashMap();
        for (String uuid : uuidList) {
            int hash = Math.abs(DigestUtils.md5DigestAsHex(uuid.getBytes()).hashCode() % 100);
            pointCount.compute(hash, (k, v) -> v == null ? 1 : ++v);
        }
        //標准差計算公式
        double tmpPowSum = pointCount.values().stream().collect(Collectors.summingDouble(x -> Math.pow((x - AVG_COUNT), 2)));
        return Math.sqrt(tmpPowSum / MODEL);
    }

    public static void main(String[] args) {
        System.out.println("expect:"+AVG_COUNT);
        System.out.println(baseTest() + "\tbase");
        System.out.println(randomRoutain() + "\trandomRoutain");
        System.out.println(simpleHash() + "\tsimpleHash");
        System.out.println(bitHash() + "\tbitHash from HashMap:");
        System.out.println(md5Hash() + "\tmd5Hash:");
        System.out.println(md5ThenHash() + "\tmd5ThenHash");

    }
}

四.常用負載均衡算法的實現

見另一篇文章,主要討論 dubbo中實現的集中負載均衡算法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM