stream消息通道主要是通过长连接的方式和对端进行数据交互,peer结构体中的stream相关数据如下(忽略V2版本):
type peer struct {
writer *streamWriter //负责向Stream消息通道中写消息
msgAppReader *streamReader //负责从Stream消息通道中读消息
.......其他字段省略
}
从中我们看到stream是读写分离的,主要有streamWriter和streamReader结构体实现相关的功能。
stream实现的相关源码在 etcdserver/api/rafthttp/stream.go文件中。
一、streamWriter
streamWriter结构体如下:
type streamWriter struct {
lg *zap.Logger
localID types.ID //本端的ID
peerID types.ID //对端节点的ID
status *peerStatus
fs *stats.FollowerStats
r Raft //底层的Raft实例
mu sync.Mutex // guard field working and closer
closer io.Closer //负责关闭底层的长连接
working bool //负责标识当前的streamWriter是否可用
msgc chan raftpb.Message //Peer会将待发送的消息写入到该通道,streamWriter则从该通道中读取消息并发送出去
connc chan *outgoingConn //通过该通道获取当前streamWriter实例关联的底层网络连接, outgoingConn其实是对网络连接的一层封装,其中记录了当前连接使用的协议版本,以及用于关闭连接的Flusher和Closer等信息。
stopc chan struct{}
done chan struct{}
}
从中我们看到:
localID和peerID分别记录本端ID和对端ID
status记录当前peer的可用性状态
r 是底层的Raft实例,用于和底层的raft模块交互
closer负责关闭底层的长连接
msgc peer会将待发送到对端的消息(非快照数据)写入到该通道,streamWriter则从该通道中读取并发送到对端节点。
connc outgoingConn其实是对网络连接的一层封装,connc用于等待对端的主动连接,当对端主动和当前节点建立连接之后,本节点才会向对端发送数据。
1. streamWriter的初始化
streamWriter的初始化过程在startStreamWriter函数中
func startStreamWriter(lg *zap.Logger, local, id types.ID, status *peerStatus, fs *stats.FollowerStats, r Raft) *streamWriter {
w := &streamWriter{
lg: lg,
localID: local,
peerID: id,
status: status,
fs: fs,
r: r,
msgc: make(chan raftpb.Message, streamBufSize),
connc: make(chan *outgoingConn),
stopc: make(chan struct{}),
done: make(chan struct{}),
}
go w.run()
return w
}
该方法主要是用于初始化streamWriter中的相关字段,并开启一个协程执行run方法。
2.run方法
run方法是streamWriter的核心,源码如下:
func (cw *streamWriter) run() {
var (
msgc chan raftpb.Message //指向当前streamWriter.msgc字段
heartbeatc <-chan time.Time //定时器会定时向该通道发送信号,触发心跳消息的发送,该心跳消息与后台介绍的Raft的心跳消息有所不同,该心跳的主要目的是为了防止连接长时间不用断开的
t streamType //用来记录消息的版本信息
enc encoder //编码器,负责将消息序列化并写入连接的缓冲区
flusher http.Flusher //负责刷新底层连接,将数据真正发送出去
batched int //当前未Flush的消息个数
)
tickc := time.NewTicker(ConnReadTimeout / 3) //发送心跳的定时器
defer tickc.Stop()
unflushed := 0 //为Flush的字节数
if cw.lg != nil {
cw.lg.Info(
"started stream writer with remote peer",
zap.String("local-member-id", cw.localID.String()),
zap.String("remote-peer-id", cw.peerID.String()),
)
} else {
plog.Infof("started streaming with peer %s (writer)", cw.peerID)
}
for {
select {
case <-heartbeatc: //定时器到期,触发心跳消息
err := enc.encode(&linkHeartbeatMessage) //通过encoder将心跳编码并写入到writer
unflushed += linkHeartbeatMessage.Size() //增加未Flush出去的字节数
if err == nil { //若没有异常,则使用flusher将缓存的消息全部发送出去,并重置batched和unflushed两个统计变量
flusher.Flush()
batched = 0
sentBytes.WithLabelValues(cw.peerID.String()).Add(float64(unflushed))
unflushed = 0
continue
}
//如果有异常则设置peer的status 为 不连通状态
cw.status.deactivate(failureType{source: t.String(), action: "heartbeat"}, err.Error())
sentFailures.WithLabelValues(cw.peerID.String()).Inc()
cw.close() //若发生异常,则关闭streamWriter,会导致底层连接的关闭
if cw.lg != nil {
cw.lg.Warn(
"lost TCP streaming connection with remote peer",
zap.String("stream-writer-type", t.String()),
zap.String("local-member-id", cw.localID.String()),
zap.String("remote-peer-id", cw.peerID.String()),
)
} else {
&nbs