【golang】基于redis实现集群中的master主实例选举

在集群中,有些业务逻辑只需要1个实例去执行,例如定时通知、任务调度器等。本文通过redis实现了在集群中选举一个master实例。

package redisutil

import (
	"context"
	"log"
	"time"
)

const expiration = 60 * time.Second

var globalInstanceID = strutil.RandomStr("master-", 16)

// AcquireAsMaster 选举master。如果当前节点选中为master,则向后执行;如果没选中,则阻塞等待下一次选举
func AcquireAsMaster(ctx context.Context, masterKey string, redisEval RedisEval) (instanceID string, release func(), err error) {
	instanceID = globalInstanceID
	for wait := 5; ; wait = (wait << 1) % 75 { // 5, 10, 20, 40, 5, 10, ...
		select {
		case <-ctx.Done():
			return instanceID, func() {}, ctx.Err()
		default:
		}

		beElected, err := redisEval(`if redis.call("SET", KEYS[1], ARGV[1], "EX", tonumber(ARGV[2]), "NX") then return 1 else return 0 end`,
			[]string{masterKey}, instanceID, int(expiration.Seconds()))
		if be, ok := beElected.(int64); err != nil || !ok || be == 0 {
			time.Sleep(time.Duration(wait) * time.Second) // 选举出错了 or 选举落选,指数退避重新选举
		}
		if beElected.(int64) == 1 {
			// 启动看门狗无限续期,防止master身份丢失
			go func() {
				ticker := time.NewTicker(expiration / 3)
				defer ticker.Stop()
				for {
					select {
					case <-ctx.Done():
						return
					case <-ticker.C:
						success, er := redisEval(`
						if redis.call("GET", KEYS[1]) == ARGV[1] then
							return redis.call("EXPIRE", KEYS[1], ARGV[2])
						end
						return 0`, []string{masterKey}, instanceID, int(expiration.Seconds()))
						if er != nil || success == 0 {
							return
						}
					}
				}
			}()
			// 返回释放函数
			releaseFunc := func() {
				released, e := redisEval(`
					if redis.call("GET", KEYS[1]) == ARGV[1] then
						return redis.call("DEL", KEYS[1])
					end
					return 0`, []string{masterKey}, instanceID)
				if e != nil {
					log.Printf("failed to release as master: %+v\n", e)
				} else if released == 0 {
					log.Println("failed to release as master: not master")
				}
			}
			return instanceID, releaseFunc, nil
		}
	}
}

使用示例:

masterKey := fmt.Sprintf("%s:this-is-the-master-instance", ps.config.Topic)
instanceID, release, err := redisutil.AcquireAsMaster(ctx, masterKey, ps.redisEval)
defer release()
if err != nil {
	log.Printf("acquire master lock error. masterKey=%s err=%+v", masterKey, err)
	return
}
log.Printf("acquire master lock success. masterKey=%s instanceID=%s", masterKey, instanceID)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值