gossip协议的原理和实战应用

先理解一下gossip协议:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。每个节点可能知道所有其他节点,也可能仅知道几个邻居节点,只要这些节可以通过网络连通,最终他们的状态都是一致的,当然这也是疫情传播的特点。
简单的描述下这个协议,首先要传播谣言就要有种子节点。种子节点每秒都会随机向其他节点发送自己所拥有的节点列表,以及需要传播的消息。任何新加入的节点,就在这种传播方式下很快地被全网所知道。这个协议的神奇就在于它从设计开始就没想到信息一定要传递给所有的节点,但是随着时间的增长,在最终的某一时刻,全网会得到相同的信息。当然这个时刻可能仅仅存在于理论,永远不可达。

这个算法已经有大神实现了,参见memberlist
我这里先通过一个demo演示一下,怎么使用

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "net/http"
    "os"
    "strings"
    "sync"

    "github.com/hashicorp/memberlist"
    "github.com/pborman/uuid"
)

var (
    mtx        sync.RWMutex
    members    = flag.String("members", "", "comma seperated list of members")
    port       = flag.Int("port", 4001, "http port")
    items      = map[string]string{}
    broadcasts *memberlist.TransmitLimitedQueue
)

type broadcast struct {
    msg    []byte
    notify chan<- struct{}
}

type delegate struct{}

type update struct {
    Action string // add, del
    Data   map[string]string
}

func init() {
    flag.Parse()
}

func (b *broadcast) Invalidates(other memberlist.Broadcast) bool {
    return false
}

func (b *broadcast) Message() []byte {
    return b.msg
}

func (b *broadcast) Finished() {
    if b.notify != nil {
        close(b.notify)
    }
}

func (d *delegate) NodeMeta(limit int) []byte {
    return []byte{}
}

func (d *delegate) NotifyMsg(b []byte) {
    if len(b) == 0 {
        return
    }

    switch b[0] {
    case 'd': // data
        var updates []*update
        if err := json.Unmarshal(b[1:], &updates); err != nil {
            return
        }
        mtx.Lock()
        for _, u := range updates {
            for k, v := range u.Data {
                switch u.Action {
                case "add":
                    items[k] = v
                case "del":
                    delete(items, k)
                }
            }
        }
        mtx.Unlock()
    }
}

func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte {
    return broadcasts.GetBroadcasts(overhead, limit)
}

func (d *delegate) LocalState(join bool) []byte {
    mtx.RLock()
    m := items
    mtx.RUnlock()
    b, _ := json.Marshal(m)
    return b
}

func (d *delegate) MergeRemoteState(buf []byte, join bool) {
    if len(buf) == 0 {
        return
    }
    if !join {
        return
    }
    var m map[string]string
    if err := json.Unmarshal(buf, &m); err != nil {
        return
    }
    mtx.Lock()
    for k, v := range m {
        items[k] = v
    }
    mtx.Unlock()
}

func addHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    key := r.Form.Get("key")
    val := r.Form.Get("val")
    mtx.Lock()
    items[key] = val
    mtx.Unlock()

    b, err := json.Marshal([]*update{
        &update{
            Action: "add",
            Data: map[string]string{
                key: val,
            },
        },
    })

    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    //广播数据
    broadcasts.QueueBroadcast(&broadcast{
        msg:    append([]byte("d"), b...),
        notify: nil,
    })
}

func delHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    key := r.Form.Get("key")
    mtx.Lock()
    delete(items, key)
    mtx.Unlock()

    b, err := json.Marshal([]*update{
        &update{
            Action: "del",
            Data: map[string]string{
                key: "",
            },
        },
    })

    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    broadcasts.QueueBroadcast(&broadcast{
        msg:    append([]byte("d"), b...),
        notify: nil,
    })
}

func getHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    key := r.Form.Get("key")
    mtx.RLock()
    val := items[key]
    mtx.RUnlock()
    w.Write([]byte(val))
}

func start() error {
    hostname, _ := os.Hostname()
    c := memberlist.DefaultLocalConfig()
    c.Delegate = &delegate{}
    c.BindPort = 0
    c.Name = hostname + "-" + uuid.NewUUID().String()
    //创建gossip网络
    m, err := memberlist.Create(c)
    if err != nil {
        return err
    }
    //第一个节点没有member,但从第二个开始就有member了
    if len(*members) > 0 {
        parts := strings.Split(*members, ",")
        _, err := m.Join(parts)
        if err != nil {
            return err
        }
    }
    broadcasts = &memberlist.TransmitLimitedQueue{
        NumNodes: func() int {
            return m.NumMembers()
        },
        RetransmitMult: 3,
    }
    node := m.LocalNode()
    fmt.Printf("Local member %s:%d\n", node.Addr, node.Port)
    return nil
}

func main() {
    if err := start(); err != nil {
        fmt.Println(err)
    }

    http.HandleFunc("/add", addHandler)
    http.HandleFunc("/del", delHandler)
    http.HandleFunc("/get", getHandler)
    fmt.Printf("Listening on :%d\n", *port)
    if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil); err != nil {
        fmt.Println(err)
    }
}

这里通过一个简单的http服务查询和插入数据,找两台机器,第一台执行

memberlist

会生成gossip监听的服务ip和端口
使用上面的ip和端口在第二台执行

memberlist --members=xxx.xxx.xxx.xxx:xxxx 

那么一个gossip的网络就搭建完成了。

# add
curl "http://localhost:4001/add?key=foo&val=bar"

# get
curl "http://另一台机器:4001/get?key=foo"

# delete
curl "http://localhost:4001/del?key=foo"
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柳清风09

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

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

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

打赏作者

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

抵扣说明:

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

余额充值