• 分布式系统选主场景分析及实现


    一:需要选主的场景
    1:服务有多台机器,取其中一台去执行任务。多台机器同时执行会出问题,如将数据库中状态为失败的记录取出来重新执行,如果多台机器同时执行,会导致一个失败的任务被多台机器同时执行。
    2:服务有多台机器,选其中一台作为主,主负责任务的分发,大家一起消费并处理任务。还是将数据库中状态为失败的记录取出来重新执行,由于一台机器可能处理不过来,需要多台机器协同处理。这个时候主机器负责将失败的记录从数据库中查出来,写入消息队列,其他机器一同消费队列中的任务,并处理失败的记录
     
    二:进行选主
    根据上面的选主场景,我们其实可以从多台机器中随机取一台,比raft这种选主算法简单得多。我们甚至可以在配置文件中指定一台机器,只有这台机器才执行相关功能,其他机器则不执行。如果是固定的几台机器,且一台机器也能完成我们的需求,这样搞其实也可以。如果机器不固定,而且单台处理不过来时,用配置文件的方式就不适合。
    可采用竞争选主的方式,谁先抢到谁就是主。
     
    1:方案一
    采用redis方案实现。如果指定的key不存在就将机器信息写入这个key,成功写入的那台机器就是主,设置过期时间,防止机器异常挂掉的情况,所有的机器都需要定时去抢redis锁。SETNX这个命令就满足我们的需求,写redis成功的就是主,写失败的就是从。
    优点:
    • 1:实现简单,比配置文件的方式好一点,支持机器动态
    缺点:
    • 1:需要定时去抢锁
    • 2:主可能经常变化,而且要保证主在切换的过程中业务逻辑的正确性
    • 3:有些时间片可能没有主,就是主挂掉了,而其他机器还没到抢锁的时间,这个时间片就没有主
     
    2:方案二
    采用etcd方案实现。etcd支持事务能做到不存在就写入,达到redis SETNX一样的效果,而且通过etcd的租赁机制保证在主挂掉的情况下通知所有机器,这时大家自动开始新一轮的选主,还是那句话第一个抢到的就是主。
    优点:
    • 满足我们的需求,没有设计上的缺陷
    • 只有主挂掉的情况,才会重新选主,不用担心主在切换的过程中对业务逻辑的影响
    缺点:
    • 实现起来相对复杂,那我就来试试吧 

    golang源码实现如下:

      1 package etcdDemo
      2 
      3 import (
      4     "context"
      5     "fmt"
      6     "github.com/coreos/etcd/clientv3"
      7     "github.com/google/uuid"
      8     "time"
      9 )
     10 
     11 type Callback func(isMaster bool)
     12 
     13 type SelectMaster struct {
     14     endPoints []string
     15     key       string
     16     cli       *clientv3.Client
     17     lease     *clientv3.LeaseGrantResponse
     18     chClose   chan int
     19     callback  Callback
     20     token     string
     21     isMaster  bool
     22 }
     23 
     24 func NewSelectMaster(endPoints []string, key string) (*SelectMaster, error) {
     25     sm := &SelectMaster{
     26         endPoints: endPoints,
     27         key:       key,
     28         chClose:   make(chan int, 0),
     29         token:     uuid.New().String(),
     30     }
     31 
     32     cli, err := clientv3.New(clientv3.Config{
     33         Endpoints:   endPoints,
     34         DialTimeout: 3 * time.Second,
     35     })
     36     if err != nil {
     37         return sm, err
     38     }
     39     sm.cli = cli
     40     go sm.ioLoop()
     41     return sm, nil
     42 }
     43 
     44 func (sm *SelectMaster) ioLoop() {
     45     fmt.Println("SelectMaster.ioLoop start")
     46     ticker := time.NewTicker(time.Second * 3)
     47     defer ticker.Stop()
     48     chWatch := sm.cli.Watch(context.TODO(), sm.key)
     49     for {
     50         select {
     51         case <-ticker.C:
     52             if sm.lease == nil {
     53                 leaseResp, err := sm.cli.Grant(context.Background(), 4)
     54                 if err != nil {
     55                     fmt.Println("cli.Grant error=", err.Error())
     56                 } else {
     57                     sm.lease = leaseResp
     58                 }
     59             }
     60             if sm.lease != nil {
     61                 _, err := sm.cli.KeepAliveOnce(context.Background(), sm.lease.ID)
     62                 if err != nil {
     63                     fmt.Println("cli.KeepAliveOnce error=", err.Error())
     64                     break
     65                 }
     66             }
     67         case c := <-chWatch:
     68             for _, e := range c.Events {
     69                 if e == nil || e.Kv == nil {
     70                     continue
     71                 }
     72                 token := string(e.Kv.Value)
     73                 sm.isMaster = sm.token == token
     74                 if sm.callback == nil {
     75                     fmt.Println("SelectMaster.callback is nil")
     76                 } else {
     77                     sm.callback(sm.isMaster)
     78                     fmt.Println("SelectMaster.isLoop token=", token)
     79                     if token == "" { //主挂了,开始竞选
     80                         sm.election()
     81                     }
     82                 }
     83             }
     84         case <-sm.chClose:
     85             goto stop
     86         }
     87     }
     88 stop:
     89     fmt.Println("SelectMaster.ioLoop end")
     90 }
     91 
     92 func (sm *SelectMaster) IsMaster() bool {
     93     return sm.isMaster
     94 }
     95 
     96 func (sm *SelectMaster) Close() {
     97     sm.chClose <- 1
     98 }
     99 
    100 func (sm *SelectMaster) Election(callback Callback) (bool, error) {
    101     sm.callback = callback
    102     return sm.election()
    103 }
    104 
    105 func (sm *SelectMaster) election() (bool, error) {
    106     ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
    107     defer cancel()
    108     leaseResp, err := sm.cli.Grant(ctx, 10)
    109     if err != nil {
    110         return false, err
    111     }
    112     sm.lease = leaseResp
    113     txn := clientv3.NewKV(sm.cli).Txn(context.TODO())
    114     txn.If(clientv3.Compare(clientv3.CreateRevision(sm.key), "=", 0)).
    115         Then(clientv3.OpPut(sm.key, sm.token, clientv3.WithLease(leaseResp.ID))).Else()
    116     txnResp, err := txn.Commit()
    117     if err != nil {
    118         return false, err
    119     }
    120     return txnResp.Succeeded, nil
    121 }
    122 
    123 func testSelectMaster() *SelectMaster {
    124     endPoints := []string{"172.25.20.248:2379"}
    125     sm, err := NewSelectMaster(endPoints, "/test/lock")
    126     if err != nil {
    127         fmt.Println(err.Error())
    128         return nil
    129     }
    130     callback := func(isMaster bool) {
    131         fmt.Println(sm.token, "callback=", isMaster)
    132     }
    133     isSuccess, err := sm.Election(callback)
    134     if err != nil {
    135         fmt.Println(sm.token, "Election=", err.Error())
    136     } else {
    137         fmt.Println(sm.token, "Election=", isSuccess)
    138     }
    139     return sm
    140 }
    141 
    142 func TestSelectMaster() {
    143     var master *SelectMaster
    144     for i := 0; i < 3; i++ {
    145         sm := testSelectMaster()
    146         if sm.IsMaster() {
    147             master = sm
    148         }
    149     }
    150     if master != nil {
    151         master.Close()
    152     }
    153     time.Sleep(time.Second*10)
    154 }
  • 相关阅读:
    用树莓派USB摄像头做个监控
    防火墙控制IP入站规则,控制可访问服务。
    Relational Database (Enhanced ER & Model Design)
    C 语言 #if 指令
    C 语言:#define 指令(宏定义)
    C 语言:#undef 指令
    《网络多人游戏架构与编程》之序列化、RPC、网络拓扑
    《网络多人游戏架构与编程》之游戏引擎、云托管
    《网络多人游戏架构与编程》之伯克利套接字
    《网络多人游戏架构与编程》之延迟、抖动、可靠、可扩展性
  • 原文地址:https://www.cnblogs.com/hlxs/p/13637394.html
Copyright © 2020-2023  润新知