本文微信公众号链接:https://mp.weixin.qq.com/s/abY24PhBgNDJgh5m9Taq4w
memberlist是go语言开发的,基于Gossip协议来传播消息,用来管理分布式集群内节点发现、 节点失效探测、节点列表的软件包。
对于Gossip协议之前写过一篇文章: Gossip协议简介---病毒感染模型的p2p算法
源码地址 https://github.com/hashicorp/memberlist
为了学习memberlist的原理设计,遵循个人从低版本代码研究的习惯。这里一提交号fe04265为分析。
再次备注:学习早期版本,只是为了学习开源代码的设计原理,底层工作原理。以及版本在进化过程中,源码的改进。
源码目录:
整体代码风格像面向对象c的风格。模块划分刚好以文件名为划分
1、broadcast.go :广播模块
2、net.go:传输与协议处理模块
3、state.go:节点状态管理模块
4、memberlist.go:主模块
github.com/hashicorp/memberlist/memberlist.go
Memberlist
在结构体Memberlist中,成员变量也是按照功能不同分隔
type Memberlist struct {
config *Config //配置
shutdown bool //本地服务关闭的标志位
leave bool //本节点退出的标志位
udpListener *net.UDPConn
tcpListener *net.TCPListener
//udp和tcp的链接管理。对应的net.go,传输与协议管理
sequenceNum uint32 // Local sequence number
//本地seq num
incarnation uint32 // Local incarnation number
//本地inc num
nodeLock sync.RWMutex
nodes []*NodeState // Known nodes
nodeMap map[string]*NodeState // Maps Addr.String() -> NodeState
//node管理以及state管理对应state.go
tickerLock sync.Mutex
tickers []*time.Ticker
stopTick chan struct{}
probeIndex int
ackLock sync.Mutex
ackHandlers map[uint32]*ackHandler
broadcastLock sync.Mutex
bcQueue broadcasts
//broadcast管理,对应broadcast.go
}
Config
在config中,前面是一些基本的配置项,注释也都有解释。
type Config struct {
Name string // Node name (FQDN)
BindAddr string // Binding address
UDPPort int // UDP port to listen on
TCPPort int // TCP port to listen on
TCPTimeout time.Duration // TCP timeout
IndirectChecks int // Number of indirect checks to use
RetransmitMult int // Retransmits = RetransmitMult * log(N+1)
SuspicionMult int // Suspicion time = SuspcicionMult * log(N+1) * Interval
PushPullInterval time.Duration // How often we do a Push/Pull update
RTT time.Duration // 99% precentile of round-trip-time
ProbeInterval time.Duration // Failure probing interval length
GossipNodes int // Number of nodes to gossip to per GossipInterval
GossipInterval time.Duration // Gossip interval for non-piggyback messages (only if GossipNodes > 0)
JoinCh chan<- *Node
LeaveCh chan<- *Node
}
在最后两行
JoinCh:这个是对外提供的一个接口,用于做新增node的时候,作为外部注册通知处理
LeaveCh:这个是对外提供的一个接口,用于做对去除一个node的时候,做为外部注册通知处理
这两个chan,在更早的版本中是在结构体memberlist中。后来移到了config中。
开始进入流程
Create
// Create will start memberlist and create a new gossip pool, but
// will not connect to an existing node. This should only be used
// for the first node in the cluster.
func Create(conf *Config) (*Memberlist, error) {
m, err := newMemberlist(conf)
//newMemberlist,中开启了tcplisten和udplisten
if err != nil {
return nil, err
}
if err := m.setAlive(); err != nil {
m.Shutdown()
return nil, err
}
m.schedule()
//schedule中开启了三个服务:probe、pushpull、gossip
return m, nil
}
这里面有两个重要步骤
1、newMemberlist
2、m.schedule
newMemberlist
// newMemberlist creates the network listeners.
// Does not schedule exeuction of background maintenence.
func newMemberlist(conf *Config) (*Memberlist, error) {
tcpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.TCPPort)
tcpLn, err := net.Listen("tcp", tcpAddr)
if err != nil {
return nil, fmt.Errorf("Failed to start TCP listener. Err: %s", err)
}
//上面是创建tcplisten
udpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.UDPPort)
udpLn, err := net.ListenPacket("udp", udpAddr)
if err != nil {
tcpLn.Close()
return nil, fmt.Errorf("Failed to start UDP listener. Err: %s", err)
}
//上面是创建udplisten
m := &Memberlist{config: conf,
udpListener: udpLn.(*net.UDPConn),
tcpListener: tcpLn.(*net.TCPListener),
nodeMap: make(map[string]*NodeState),
stopTick: make(chan struct{}, 32),
ackHandlers: make(map[uint32]*ackHandler),
}//构建Memberlist实例
go m.tcpListen() //开启tcp服务
go m.udpListen() //开启udp服务
return m, nil
}
在newMemberlist中,最主要的动作就是开启了tcp服务和udp服务
那么就看看net服务(tcp和udp)
github.com/hashicorp/memberlist/net.go
tcp
tcplisten
// tcpListen listens for and handles incoming connections
func (m *Memberlist) tcpListen() {
for {
//tcp accept
conn, err := m.tcpListener.AcceptTCP()
if err != nil {
if m.shutdown {
break
}
log.Printf("[ERR] Error accepting TCP connection: %s", err)
continue
}
//每个链接都有一个处理