diff --git a/ch6-web/ch6-12-load-balance.md b/ch6-web/ch6-12-load-balance.md index ab893d1..c1ced46 100644 --- a/ch6-web/ch6-12-load-balance.md +++ b/ch6-web/ch6-12-load-balance.md @@ -84,13 +84,19 @@ func request(params map[string]interface{}) error { 从数学上得到过证明的还是经典的 fisher-yates 算法,主要思路为每次随机挑选一个值,放在数组末尾。然后在 n-1 个元素的数组中再随机挑选一个值,放在数组末尾,以此类推。 +```go +func shuffle(indexes []int) { + lastIdx := len(indexes) - 1 + for i:=len(indexes); i>0; i-- { + idx := rand.Int(i) + indexes[lastIdx], indexes[idx] = indexes[idx], indexes[lastIdx] + } +} +``` + 在 Go 的标准库中实际上已经为我们内置了该算法: ```go -func init() { - rand.Seed(time.Now().UnixNano()) -} - func shuffle(n int) []int { b := rand.Perm(n) return b @@ -103,7 +109,11 @@ func shuffle(n int) []int { 本节中的场景是从 N 个节点中选择一个节点发送请求,初始请求结束之后,后续的请求会重新对数组洗牌,所以每两个请求之间没有什么关联关系。因此我们上面的洗牌算法,理论上不初始化随机库的种子也是不会出什么问题的。 -但在一些特殊的场景下,例如使用 zk 时,客户端初始化从多个服务节点中挑选一个节点后,是会向该节点建立长连接的。并且之后如果有请求,也都会发送到该节点去。直到该节点不可用,才会在 endpoints 列表中挑选下一个节点。在这种场景下,我们的初始连接节点选择就要求必须是“真”随机了。否则,所以客户端起动时,都会去连接同一个 zk 的实例,根本无法起到负载均衡的目的。如果在日常开发中,你的业务也是类似的场景,也务必考虑一下是否会发生类似的情况。 +但在一些特殊的场景下,例如使用 zk 时,客户端初始化从多个服务节点中挑选一个节点后,是会向该节点建立长连接的。并且之后如果有请求,也都会发送到该节点去。直到该节点不可用,才会在 endpoints 列表中挑选下一个节点。在这种场景下,我们的初始连接节点选择就要求必须是“真”随机了。否则,所以客户端起动时,都会去连接同一个 zk 的实例,根本无法起到负载均衡的目的。如果在日常开发中,你的业务也是类似的场景,也务必考虑一下是否会发生类似的情况。为 rand 库设置种子的方法: + +```go +rand.Seed(time.Now().UnixNano()) +``` 之所以会有上面这些结论,是因为某个使用较广泛的开源 zk 库的早期版本就犯了上述错误,直到 2016 年早些时候,这个问题才被修正。