zookeeper客户端连接对象
Conn对象:负责客户端的连接,sessionTimeout 指服务端连接缓存的记录时间,在这个时间内ephemeral nodes(选举主的标志节点)和watches会被保留。若有多个ip,conn会随机选择其中一个连接,连接不上则换一个。
connect方法会返回一个接受通道,在connect中会启动一个协程连接server,若成功会将一些事件值发送给接受通道,最后为了防止阻塞其他线程,会关闭接收通道。
func Connect(servers []string, sessionTimeout time.Duration, options ...connOption) (*Conn, <-chan Event, error) {
...
go func() {
conn.loop()
conn.flushRequests(ErrClosing)
conn.invalidateWatches(ErrClosing)
close(conn.eventChan)
}()
return conn, ec, nil
}
Exists方法:检查节点是否存在
//返回的Stat为节点的状态属性
func (c *Conn) Exists(path string) (bool, *Stat, error) {
//验证路径的有效性,包括开头、字符串符合编码等
if err := validatePath(path, false); err != nil {
return false, nil, err
}
res := &existsResponse{}
//封装请求
_, err := c.request(opExists, &existsRequest{Path: path, Watch: false}, res, nil)
exists := true
//判断其中一个错误类型,是否是节点不存在,如果是节点不存在则不报错
if err == ErrNoNode {
exists = false
err = nil
}
return exists, &res.Stat, err
}
request方法向服务器请求中都会用到,比较常见,也就是常见的client/server模式
func (c *Conn) queueRequest(opcode int32, req interface{}, res interface{}, recvFunc
func(*request, *responseHeader, error)) <-chan response {
//封装rq请求
rq := &request{
xid: c.nextXid(),
opcode: opcode,
pkt: req,
recvStruct: res,
recvChan: make(chan response, 1),
recvFunc: recvFunc,
}
//将rq通过通道发送至对等到通道到来的处理请求的协程序
c.sendChan <- rq
//返回一个等到响应到来的通道,意思是处理请求协程完成后,会将这个结果发送给这个通道
return rq.recvChan
}
func (c *Conn) request(opcode int32, req interface{}, res interface{}, recvFunc func(*request, *responseHeader, error)) (int64, error) {
//request会一直阻塞等待这个通道结果,结果可能有错误,处理时需要判定
r := <-c.queueRequest(opcode, req, res, recvFunc)
return r.zxid, r.err
}
除exits方法外,还有许多类似的请求判断节点的状态的方法,例如conn.children,采用都是利用对应的通道发送请求得到子节点,服务端处理并回应请求的方式来进行。conn.Sync(parentNode)是让parentNode保持是最新的节点,同步最新的数据
另外,值得注意的是exitsW方法(带有w的方法)有一个回调函数参数,该参数会在拿到请求结果后进行调用
e
func (c *Conn) ExistsW(path string) (bool, *Stat, <-chan Event, error) {
if err := validatePath(path, false); err != nil {
return false, nil, nil, err
}
var ech <-chan Event
res := &existsResponse{}
_, err := c.request(opExists, &existsRequest{Path: path, Watch: true}, res, func(req *request, res *responseHeader, err error) {
//回调函数使用
if err == nil {
ech = c.addWatcher(path, watchTypeData)
} else if err == ErrNoNode {
ech = c.addWatcher(path, watchTypeExist)
}
})
exists := true
if err == ErrNoNode {
exists = false
err = nil
}
if err != nil {
return false, nil, nil, err
}
return exists, &res.Stat, ech, err
}
上面中出现了一个常用的函数conn.addWatcher(),conn会将wathers加入watchers集合中,Map的值是一个通道值,用来阻塞监听的结果的到来。所以若这个通道接受到值,则说明节点类型改变成功或者节点成功创建。
//接受一个节点路径和watchType(整形类型值)
func (c *Conn) addWatcher(path string, watchType watchType) <-chan Event {
//互斥锁
c.watchersLock.Lock()
defer c.watchersLock.Unlock()
ch := make(chan Event, 1)
wpt := watchPathType{path, watchType}
//watchers 数组 map[watchPathType][]chan Event
c.watchers[wpt] = append(c.watchers[wpt], ch)
return ch
}