背景
最近项目上需要重构一个模块,因为这个模块设计到一些全局信息的维护,多副本会有竞争问题,希望采用单副本的方式来运行。但是单副本的话又会面临高可用的问题。因此要解决的问题就是如何做主从选举来保证既是单副本工作,又可以保证高可用。
思路
本来想手撸的话可以依然用Redis的分布式锁或者Etcd的分布式锁来实现。
- 在服务启动的时候去拿锁,拿不到锁就放弃,然后过一段时间再去拿锁,直到拿到锁成为leader
- 成为leader后给这个锁加上过期时间,并且周期性去续约
但是自己手撸就需要自己写很多代码了,边界条件也比较多,比较麻烦。
刚好发现k8s的client-go中已经有实现了,直接用就好了,不需要重复造轮子,还是很爽的。正好我们的服务也是部署在k8s上的,这样就相当“云原生”了。
没错,就是这个轮子:https://pkg.go.dev/k8s.io/client-go/tools/leaderelection
代码实现
- 创建资源锁
- 开启选举循环,注册回调
- 在onStartedLeading回调中继续业务逻辑即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| lock := &resourcelock.LeaseLock{ LeaseMeta: metav1.ObjectMeta{ Name: leaseLockName, Namespace: leaseLockNamespace, }, Client: k8sclient.CoordinationV1(), LockConfig: resourcelock.ResourceLockConfig{ Identity: id, }, }
leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ Lock: lock, ReleaseOnCancel: true, LeaseDuration: 60 * time.Second, RenewDeadline: 15 * time.Second, RetryPeriod: 5 * time.Second, Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { run(ctx) }, OnStoppedLeading: func() { klog.Infof("leader lost: %s", id) os.Exit(0) }, OnNewLeader: func(identity string) { if identity == id { return } klog.Infof("new leader elected: %s", identity) }, }, }) }
|
最后需要注意服务的权限,需要创建ClusterRole、ServiceAccount和Binding来授权,否则可能会报错。
参考资料