一.分流和負載均衡區別
結論:AB實驗分流不能丟失策略,分流比要完美趨近權重。
負載均衡只要將流量根據權重打到不同的機器即可,所有機器的業務邏輯都是一樣的。
AB實驗分流則需要:
- 將同一個請求多次訪問時打到同一個策略,保證請求不丟失策略。比如用戶訪問一個頁面時不能交替出現不同的展示;
- 同負載均衡,保證流量比完美趨近於權重。
綜上AB實驗需要保證不丟失策略流量分配無限接近與權重比。
二.常用負載均衡優缺點
結論:源信息hash算法是唯一不丟失策略的算法,但是負載均衡和源數據信息和哈希算法有很大關系。 因為分流策略是工程方引入的jar包,平台也需要提供調試功能,因此也不能使用統一的緩存管理。
常用負載均衡算法有輪詢法、隨機法、源地址哈希、一致性哈希和最少活躍調用五種思路。其中 dubbo實現除了源地址哈希的其他四種。其詳細分析見另一片博文,此處重點講源地址哈希算法:
- 輪詢法:最趨於平衡的負載均衡算法,但是修改指針時需要加鎖,性能較低;
- 隨機發:請求數量越高平衡性越趨近於輪詢,一般使用隨機數或者
System.identityHashCode(obj)
; - 源地址哈希:唯一不丟失策略的算法,但是負載均衡和源數據信息和哈希算法有很大關系,AB實驗分流一般采用此方法;
- 一致性哈希算法:todo
- 最少獲取調用: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中實現的集中負載均衡算法。