Etcd 使用场景:通过分布式锁思路实现自动选主

分布式锁?选主?

分布式锁可以保证当有多台实例同时竞争一把锁时,只有一个人会成功,其他的都是失败。诸如共享资源修改、幂等、频控等场景都可以通过分布式锁来实现。

还有一种场景,也可以通过分布式锁来实现,那就是 选主,为了保证服务的可用性,我们都会以一主多从的方式去部署,特别是提供存储能力的服务。Leader服务来接收数据的写入,然后将数据同步给Follower服务。当Leader服务挂掉时,我们需要从Follower服务中重新选举一个服务来当Leader,复杂的方式是通过Raft协议去协商,简单点,可以通过分布式锁的思路来做:

  1. 所有的Follower服务去竞争同一把锁,并给这个锁设置一个过期时间
  2. 只会有一个Follower服务取到锁,这把锁的值就为它的标识,他就变成了Leader服务
  3. 其他Follower服务竞争失败后,去获取锁得到的当前的Leader服务标识,与之通信
  4. Leader服务需要在锁过期之前不断的续期,证明自己是健康的
  5. 所有Follower服务监控这把锁是否还被Leader服务持有,如果没有,就跳到了第1步

通过 Redis、Zookeeper 都可以实现,不过这次,我们使用 Etcd 来实现。

Etcd 简单介绍

Etcd:A highly-available key value store for shared configuration and service discovery。

Etcd 是一个K/V存储,和 Redis 功能类似,这是我对它的直观印象,和实现Master选举好像八竿子打不着。随着对 Etcd 了解的加深,我才开始对官网介绍那句话有了一定理解,Redis K/V 存储是用来做纯粹的缓存功能,高并发读写是核心,而 Etcd 这个基于 Raft 的分布式 K/V 存储,强一致性的 K/V 读写是核心,基本这点诞生了很多有想象力的使用场景:服务发现、分布式锁、Master 选举等等。
基于 Etcd 以下特性,我们可以实现自动选主:

  • MVCC,key存在版本属性,没被创建时版本号为0
  • CAS操作,结合MVCC,可以实现竞选逻辑,if(version == 0) set(key,value),通过原子操作,确保只有一台机器能set成功;
  • Lease租约,可以对key绑定一个租约,租约到期时没预约,这个key就会被回收;
  • Watch监听,监听key的变化事件,如果key被删除,则重新发起竞选。

准备工作

启动 Etcd

我们使用 Docker 安装,简单方便:

> docker run -d --name Etcd-server \
    --publish 2379:2379 \
    --publish 2380:2380 \
    --env ALLOW_NONE_AUTHENTICATION=yes \
    --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \
    bitnami/etcd:latest

最好是使用最新般本

Go 依赖库安装

Etcd 提供开箱即用的选主工作库,我们直接使用就行

> go get go.etcd.io/etcd/client/v3

这一步看似简单,如果放在以前,少不了一顿百度,原因是因为它依赖的 grpc 和 bbolt 库的版本不能是最新的,需要在 go.mod 中去写死版本。所幸赶上了好时代,官方终于出手整改了,现在只要一行命令行。

选主Demo

package main

import (
   "context"
   "flag"
   "fmt"
   "os"
   "os/signal"
   "time"

   clientv3 "go.etcd.io/etcd/client/v3"
   "go.etcd.io/etcd/client/v3/concurrency"
)

var (
   serverName = flag.String("name", "", "")
)

func main() {
   flag.Parse()

   // Etcd 服务器地址
   endpoints := []string{"127.0.0.1:2379"}
   clientConfig := clientv3.Config{
      Endpoints:   endpoints,
      DialTimeout: 2 * time.Second,
   }
   cli, err := clientv3.New(clientConfig)
   if err != nil {
      panic(err)
   }

   s1, err := concurrency.NewSession(cli)
   if err != nil {
      panic(err)
   }
   fmt.Println("session lessId is ", s1.Lease())

   e1 := concurrency.NewElection(s1, "my-election")
   go func() {
      // 参与选举,如果选举成功,会定时续期
      if err := e1.Campaign(context.Background(), *serverName); err != nil {
         fmt.Println(err)
      }
   }()

   masterName := ""
   go func() {
      ctx, cancel := context.WithCancel(context.TODO())
      defer cancel()
      timer := time.NewTicker(time.Second)
      for range timer.C {
         timer.Reset(time.Second)
         select {
         case resp :=  0 {
               // 查看当前谁是 master
               masterName = string(resp.Kvs[0].Value)
               fmt.Println("get master with:", masterName)
            }
         }
      }
   }()

   go func() {
      timer := time.NewTicker(5 * time.Second)
      for range timer.C {
         // 判断自己是 master 还是 slave
         if masterName == *serverName {
            fmt.Println("oh, i'm master")
         } else {
            fmt.Println("slave!")
         }
      }
   }()

   c := make(chan os.Signal, 1)
   // 接收 Ctrl C 中断
   signal.Notify(c, os.Interrupt, os.Kill)

   s :=

我们在两个终端分别运行下面两个命令,模拟两个服务去竞争:

> go run main.go -name A
session lessId is  7587863771971134868
get master with: A
get master with: A
get master with: A
get master with: A
oh, i'm master
> go run main.go -name B
session lessId is  7587863771971134876
get master with: A
get master with: A
get master with: A
get master with: A
slave!

当我们使用 Ctrl C 中断,此时 B 就成为了 master

> go run main.go -name A
session lessId is  7587863771971134868
get master with: A
get master with: A
get master with: A
get master with: A
oh, i'm master
^CGot signal: interrupt
> go run main.go -name B
session lessId is  7587863771971134876
get master with: A
get master with: A
get master with: A
get master with: A
slave!

get master with: B
get master with: B
get master with: B
get master with: B
oh, i'm master

原理

当我们启动 A 和 B 两个服务时,他们后会在公共前缀 “my-election/” 下创建自己的 key,这个 key 的构成为 “my-election/” + 十六进制(LessId)。这个LessId 是在服务启动时,从 Etcd 服务端取到的客户端唯一标识。比如上面程序运行的两个服务创建的 key 分别是:

  • A 服务创建的 key 是 “my-election/694d81e5fc652594″,值是 “A”
  • B 服务创建的 key 是 “my-election/694d81e5fc65259c”,值是 “B”

因为是通过事务的方式去创建 key,可以保证如果这个 key 已经创建了,不去创建了。并且这个 key 是有过期时间,两个服务 A 和 B 会启动一个协程定期去刷新过期时间,通过这个方式证明自己的健康的。

现在两个服务都创建了 key, 那么那个才是 master 呢?我们选取最早创建的那个 key 的拥有者作为 master。
Etcd 服务的查询接口支持根据前缀查询和按照创建时间排序,所以我们可以轻松的拿到第一个创建成功的 key,这个 key 对应的值就是 master 了,也就是 A 服务。

当现在 master 服务挂掉了,因为它的 key 没有在过期之前续期,就会被删除的,此时当初第二个创建的 key 就会变成第一个,那个 master 就变成了 B 服务。

Etcd 使用场景:通过分布式锁思路实现自动选主

我们是通过 e1.Campaign(context.Background(), *serverName)行代码是参加去参加选举的,里面有一个细节:如果竞争失败,这个函数会阻塞,直到它选举成功或者服务中断。也就是说:如果 B 服务创建的 key
不是最早的一个,那它会一直等待,直到服务 A 的 key 被删除后,函数才会有返回。

Original: https://www.cnblogs.com/Zioyi/p/16464248.html
Author: Zioyi
Title: Etcd 使用场景:通过分布式锁思路实现自动选主

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575844/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 建造者设计模式

    可以看到这个学生类的属性是非常多的,所以构造方法不是一般的长,如果我们现在直接通过new的方式去创建: 可以看到,我们光是填参数就麻烦,我们还得一个一个对应着去填,一不小心可能就把…

    数据库 2023年6月14日
    084
  • 编程相关书单

    软工概论软件工程软件工程:实践者的研究方法 职业素养软技能码农翻身技术之瞳程序员修炼之道程序员的职业素养程序员的自我修养 程序设计计算机程序的构造和解释 (SICP) UML软件建…

    数据库 2023年6月11日
    090
  • windows bat

    windows bat windows bat netsh2-ipv4 新建文件夹 Windows 10 右下角时间显示时分秒 windows route 检测到以管理员权限运行 …

    数据库 2023年6月9日
    0108
  • 16 两个对象值相同(x.equals(y) == true),但却可以有不同的hashCode,这句话对不对?

    不对,如果两个对象x和y满足x.equals(y) == true,那么他们的哈希码应该相同。 根据hashCode方法协定:在每个重写了equals方法的类中,必须重写hashC…

    数据库 2023年6月6日
    085
  • typora最后的免费版本

    需要的阿里云盘自取 「typora最后的免费版本」https://www.aliyundrive.com/s/p3Ci2gbgwYh点击链接保存,或者复制本段内容,打开「阿里云盘」…

    数据库 2023年6月6日
    094
  • 从SQL Server到MySQL,携程核心系统无感迁移实战

    前言 携程酒店订单系统的存储设计从1999年收录第一单以来,已经完成了从单一SQLServer数据库到多IDC容灾、完成分库分表等多个阶段,在见证了大量业务奇迹的同时,也开始逐渐暴…

    数据库 2023年5月24日
    076
  • 慢查询SQL排查

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/c56bd0c5.html 你好,我是测试蔡坨坨。 在往期文章中,我们聊过数据库基础知识,可参考「数据库…

    数据库 2023年5月24日
    095
  • Excel文件校验

    工作中,经常存在excel文件的导入导出的相关工作,因此正确的文件格式校验成为必须。不合适的文件校验方式会导致非法文件跳过校验,从而产生不必要的麻烦。比如,通过文件后缀名的方式进行…

    数据库 2023年6月14日
    0148
  • Linux–>定时任务调度

    指定系统在某个时间执行特点的命令或程序。 任务调度分类: crontab 选项 常用选项 选项 说明 -e 编辑crontab定时任务 -l 查询crontab任务 -r 删除当前…

    数据库 2023年6月14日
    074
  • HMX-Server C++ 分步式服务器大版本更新了(有源码)

    原文地址:http://www.cnblogs.com/hellohuang/p/6294763.htmlHMX-ServerHMX-Server分步式服务器框架,主要分为网关、登…

    数据库 2023年6月14日
    0113
  • Linux–>磁盘分区,挂载

    对于IDE硬盘,驱动器标识符为 “hdx~”,其中”hd”表明分区所在设备类型,这里是指IDE硬盘 “x”为…

    数据库 2023年6月14日
    0102
  • 8、IDEA提交代码出现: Fetch failed fatal: Could not read from remote repository

    转载自 第一步、确认Git公钥/密钥是否生成: 1、 首先查看本地是否生成git密钥,一般在C盘home目录下:【C:你自己的home目录.ssh】 第二步:添加Git密钥: 右键…

    数据库 2023年6月6日
    0102
  • 尚硅谷Git教程

    尚硅谷Git教程BV1vy4y1s7k6 免费 开源 分布式版本控制工具 / 集中式版本控制工具 本地库、暂存区域、工作流分支 弹幕:应该是git比SVN多了个本地仓库。SVN中央…

    数据库 2023年6月11日
    099
  • Redis和Mysql保持数据一致性

    1、简述 在高并发的场景下,大量的请求直接访问Mysql很容易造成性能问题。所以,我们都会用Redis来做数据的缓存,削减对数据库的请求。但是,Mysql和Redis是两种不同的数…

    数据库 2023年6月16日
    096
  • RadonDB MySQL on K8s 2.1.2 发布!

    RadonDB MySQL on Kubernetes 于 2 月 17 日发布了新版本 2.1.2 。该版本在节点的重建、增删等方面进行了全面升级。致谢: 首先感谢 @andyl…

    数据库 2023年5月24日
    085
  • Linux–>进程管理

    基本介绍 在Linux中, 每个执行程序都称为一个进程。每一个进程都会分配一个ID号(pid,进程号) 每个进程都可能以俩种方式存在的。分别是 前台与 后台,所谓前台进程就是用户目…

    数据库 2023年6月14日
    0113
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球