1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

zk -> ZooKeeper

This commit is contained in:
Xargin 2018-12-19 11:20:58 +08:00
parent d5a3c92c9b
commit 7d2936a8fb
2 changed files with 19 additions and 19 deletions

View File

@ -226,7 +226,7 @@ setnx 很适合在高并发场景下,用来争抢一些“唯一”的资源
所以,我们需要依赖于这些请求到达 Redis 节点的顺序来做正确的抢锁操作。如果用户的网络环境比较差,那也只能自求多福了。 所以,我们需要依赖于这些请求到达 Redis 节点的顺序来做正确的抢锁操作。如果用户的网络环境比较差,那也只能自求多福了。
## 6.2.4 基于 zk ## 6.2.4 基于 ZooKeeper
```go ```go
package main package main
@ -257,7 +257,7 @@ func main() {
} }
``` ```
基于 zk 的锁与基于 Redis 的锁的不同之处在于 Lock 成功之前会一直阻塞,这与我们单机场景中的 mutex.Lock 很相似。 基于ZooKeeper的锁与基于 Redis 的锁的不同之处在于 Lock 成功之前会一直阻塞,这与我们单机场景中的 mutex.Lock 很相似。
其原理也是基于临时 sequence 节点和 watch API例如我们这里使用的是 `/lock` 节点。Lock 会在该节点下的节点列表中插入自己的值,只要节点下的子节点发生变化,就会通知所有 watch 该节点的程序。这时候程序会检查当前节点下最小的子节点的 id 是否与自己的一致。如果一致,说明加锁成功了。 其原理也是基于临时 sequence 节点和 watch API例如我们这里使用的是 `/lock` 节点。Lock 会在该节点下的节点列表中插入自己的值,只要节点下的子节点发生变化,就会通知所有 watch 该节点的程序。这时候程序会检查当前节点下最小的子节点的 id 是否与自己的一致。如果一致,说明加锁成功了。
@ -305,7 +305,7 @@ etcd 中没有像 ZooKeeper 那样的 sequence 节点。所以其锁实现和基
3. watch `/lock` 下的事件,此时陷入阻塞 3. watch `/lock` 下的事件,此时陷入阻塞
4. 当 `/lock` 路径下发生事件时,当前进程被唤醒。检查发生的事件是否是删除事件(说明锁被持有者主动 unlock),或者过期事件(说明锁过期失效)。如果是的话,那么回到 1走抢锁流程。 4. 当 `/lock` 路径下发生事件时,当前进程被唤醒。检查发生的事件是否是删除事件(说明锁被持有者主动 unlock),或者过期事件(说明锁过期失效)。如果是的话,那么回到 1走抢锁流程。
## 6.2.6 redlock ## 6.2.6 Redlock
```go ```go
package main package main
@ -365,20 +365,20 @@ func main() {
} }
``` ```
redlock 也是一种阻塞锁,单个节点操作对应的是 `set nx px` 命令,超过半数节点返回成功时,就认为加锁成功。 Redlock也是一种阻塞锁,单个节点操作对应的是 `set nx px` 命令,超过半数节点返回成功时,就认为加锁成功。
关于 redlock 的设计曾经在社区引起一场口水战,分布式专家各抒己见。不过这个不是我们要讨论的内容,相关链接在参考资料中给出。 关于Redlock设计曾经在社区引起一场口水战,分布式专家各抒己见。不过这个不是我们要讨论的内容,相关链接在参考资料中给出。
## 6.2.7 如何选择 ## 6.2.7 如何选择
业务还在单机就可以搞定的量级时,那么按照需求使用任意的单机锁方案就可以。 业务还在单机就可以搞定的量级时,那么按照需求使用任意的单机锁方案就可以。
如果发展到了分布式服务阶段,但业务规模不大,比如 qps < 1000使用哪种锁方案都差不多如果公司内已有可以使用的 ZooKeeper/etcd/Redis 集群那么就尽量在不引入新的技术栈的情况下满足业务需求 如果发展到了分布式服务阶段,但业务规模不大,比如 qps < 1000使用哪种锁方案都差不多如果公司内已有可以使用的 ZooKeeperetcd或者Redis集群那么就尽量在不引入新的技术栈的情况下满足业务需求
业务发展到一定量级的话就需要从多方面来考虑了。首先是你的锁是否在任何恶劣的条件下都不允许数据丢失如果不允许那么就不要使用Redis的setnx的简单锁。 业务发展到一定量级的话就需要从多方面来考虑了。首先是你的锁是否在任何恶劣的条件下都不允许数据丢失如果不允许那么就不要使用Redis的setnx的简单锁。
如果要使用 redlock那么要考虑你们公司 Redis 的集群方案,是否可以直接把对应的 Redis 的实例的 ip+port 暴露给开发人员。如果不可以,那也没法用。 如果要使用Redlock那么要考虑你们公司Redis的集群方案是否可以直接把对应的Redis的实例的ip+port暴露给开发人员。如果不可以,那也没法用。
对锁数据的可靠性要求极高的话,那只能使用 etcd 或者 zk 这种通过一致性协议保证数据可靠性的锁方案。但可靠的背面往往都是较低的吞吐量和较高的延迟。需要根据业务的量级对其进行压力测试,以确保分布式锁所使用的 etcd/zk 集群可以承受得住实际的业务请求压力。需要注意的是etcd 和 zk 集群是没有办法通过增加节点来提高其性能的。要对其进行横向扩展,只能增加搭建多个集群来支持更多的请求。这会进一步提高对运维和监控的要求。多个集群可能需要引入 proxy没有 proxy 那就需要业务去根据某个业务 id 来做 sharding。如果业务已经上线的情况下做扩展,还要考虑数据的动态迁移。这些都不是容易的事情。 对锁数据的可靠性要求极高的话,那只能使用etcd或者ZooKeeper这种通过一致性协议保证数据可靠性的锁方案。但可靠的背面往往都是较低的吞吐量和较高的延迟。需要根据业务的量级对其进行压力测试,以确保分布式锁所使用的 etcd/ZooKeeper 集群可以承受得住实际的业务请求压力。需要注意的是etcd 和 Zookeeper 集群是没有办法通过增加节点来提高其性能的。要对其进行横向扩展,只能增加搭建多个集群来支持更多的请求。这会进一步提高对运维和监控的要求。多个集群可能需要引入 proxy没有 proxy 那就需要业务去根据某个业务 id 来做分片。如果业务已经上线的情况下做扩展,还要考虑数据的动态迁移。这些都不是容易的事情。
在选择具体的方案时,还是需要多加思考,对风险早做预估。 在选择具体的方案时,还是需要多加思考,对风险早做预估。

View File

@ -71,7 +71,7 @@ func request(params map[string]interface{}) error {
真的没有问题么?实际上还是有问题的。这段简短的程序里有两个隐藏的隐患: 真的没有问题么?实际上还是有问题的。这段简短的程序里有两个隐藏的隐患:
1. 没有随机种子。在没有随机种子的情况下rand.Intn 返回的伪随机数序列是固定的。 1. 没有随机种子。在没有随机种子的情况下rand.Intn()返回的伪随机数序列是固定的。
2. 洗牌不均匀,会导致整个数组第一个节点有大概率被选中,并且多个节点的负载分布不均衡。 2. 洗牌不均匀,会导致整个数组第一个节点有大概率被选中,并且多个节点的负载分布不均衡。
@ -104,17 +104,17 @@ func shuffle(n int) []int {
在当前的场景下,我们只要用 rand.Perm 就可以得到我们想要的索引数组了。 在当前的场景下,我们只要用 rand.Perm 就可以得到我们想要的索引数组了。
## 6.5.3 zk 集群的随机节点挑选问题 ## 6.5.3 ZooKeeper 集群的随机节点挑选问题
本节中的场景是从 N 个节点中选择一个节点发送请求,初始请求结束之后,后续的请求会重新对数组洗牌,所以每两个请求之间没有什么关联关系。因此我们上面的洗牌算法,理论上不初始化随机库的种子也是不会出什么问题的。 本节中的场景是从 N 个节点中选择一个节点发送请求,初始请求结束之后,后续的请求会重新对数组洗牌,所以每两个请求之间没有什么关联关系。因此我们上面的洗牌算法,理论上不初始化随机库的种子也是不会出什么问题的。
但在一些特殊的场景下,例如使用 zk 时,客户端初始化从多个服务节点中挑选一个节点后,是会向该节点建立长连接的。并且之后如果有请求,也都会发送到该节点去。直到该节点不可用,才会在 endpoints 列表中挑选下一个节点。在这种场景下,我们的初始连接节点选择就要求必须是“真”随机了。否则,所有客户端起动时,都会去连接同一个 zk 的实例,根本无法起到负载均衡的目的。如果在日常开发中,你的业务也是类似的场景,也务必考虑一下是否会发生类似的情况。为 rand 库设置种子的方法: 但在一些特殊的场景下,例如使用ZooKeeper时,客户端初始化从多个服务节点中挑选一个节点后,是会向该节点建立长连接的。并且之后如果有请求,也都会发送到该节点去。直到该节点不可用,才会在 endpoints 列表中挑选下一个节点。在这种场景下,我们的初始连接节点选择就要求必须是“真”随机了。否则,所有客户端起动时,都会去连接同一个ZooKeeper的实例根本无法起到负载均衡的目的。如果在日常开发中你的业务也是类似的场景也务必考虑一下是否会发生类似的情况。为rand库设置种子的方法:
```go ```go
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
``` ```
之所以会有上面这些结论,是因为某个使用较广泛的开源 zk 库的早期版本就犯了上述错误,直到 2016 年早些时候,这个问题才被修正。 之所以会有上面这些结论,是因为某个使用较广泛的开源ZooKeeper库的早期版本就犯了上述错误,直到 2016 年早些时候,这个问题才被修正。
## 6.5.4 负载均衡算法效果验证 ## 6.5.4 负载均衡算法效果验证