etcd网络层源码分析(四)——stream通道实现

本文介绍HTTP长连接中的streamReader和streamWriter工作原理。streamReader负责接收数据,通过HTTP连接与对端建立联系并读取消息;streamWriter负责发送数据,包括心跳消息及待发送消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

stream作为网络传输的通道之一,通过建立HTTP长连接,主要传输数据量小、发送频率较高的数据,例如追加日志、心跳数据。

工作原理

结合上一节的pipeline通道实现的工作原理,我们来看下stream的工作原理:
在这里插入图片描述
我们知道stream是HTTP长连接,所以stream需要专门负责写入数据的streamWriter和专门读取数据的streamReader。

streamReader工作原理:
rafthttp.peer.startPeer()中通过初始化并调用streamReader.start()启动streamReader实例,其中还启动了一个后台goroutine来执行streamReader.run()方法。在streamReader.run()方法中,主要完成下面两件事情:

  1. 通过调用streamReader.dial()主动与对端建立连接
  2. 接收对端发送的数据,写入streamReader.recvc或streamReader.propc,这两个通道其实对应的是上层peer.recvc和peer.propc通道,最后消息在rafthttp.peer.startPeer()
    中被接收并提交到etcd-raft模块被处理。

streamWriter工作原理:
rafthttp.peer.startPeer()中通过调用startStreamWriter()初始化并启动streamWriter实例,其中还启动了一个后台goroutine来执行streamWriter.run()方法。在streamWriter.run()方法中,主要完成下面三件事情:

  1. peer章节中提到过当其他节点主动与当前节点创建连接(streamReader.dial())时,该连接实例会写入对应streamWriter.connc通道,在streamWriter.run方法中通过该通道获取该连接实例并进行绑定,之后才能开始后续的消息发送和心跳检测。
  2. 获取连接之后,定时将心跳消息写入缓冲区,通过HTTP处理器将缓冲区数据刷到对端,用于定期检测网络状况
  3. 获取连接之后,从streamWriter.msgc通道接收到要发送的消息,将消息写入缓冲区,通过HTTP处理器将缓冲区数据刷到对端。

streamWriter

结构体

type streamWriter struct {
   
	localID types.ID//本地节点ID
	peerID  types.ID//对端节点ID
	
	status *peerStatus//网络连接状态
	r      Raft //底层的Raft状态
	
	mu      sync.Mutex // guard field working and closer
	closer  io.Closer//负责关闭底层的长连接
	working bool //负责标识当前的streamWriter是否可用
	msgc  chan raftpb.Message//通过前面对Peer分析可知,Peer会将待发送的消息写入该通道,streamWriter则从该通道中读取消息并发送出去
	connc chan *outgoingConn//通过该通道获取当前streamWriter实例关联的底层网络连接,  outgoingConn其实是对网络连接的一层封装,其中记录了当前连接使用的协议版本,以及用于关闭连接的Flusher和Closer等信息。
	stopc chan struct{
   }
	done  chan struct{
   }
}

streamWriter启动

func (cw *streamWriter) run() {
   
	var (
		msgc       chan raftpb.Message //指向当前streamWriter.msgc字段
		/**
		定时器会定时向该通道发送信号,触发心跳消息的发送,该心跳消息与后面介绍的Raft的心跳消息有所不同
		该心跳消息的主要目的是为了防止连接长时间不用断开的
		**/
		heartbeatc <-chan time.Time
		t          streamType//区分不同版本的stream消息通道
		enc        encoder//编码器,负责将消息序列化并写入连接的缓冲区
		flusher    http.Flusher//负责刷新底层连接,将数据真正发送出去
		batched    int //记录当前未Flush的消息个数
	)
	tickc := time.NewTicker(ConnReadTimeout / 3)//发送心跳消息的定时器
	defer tickc.Stop()
	unflushed := 0//未Flush的字节数

	for {
   
		select {
   
		case <-heartbeatc:
			//将心跳消息编码到缓冲区
			err := enc.encode(&linkHeartbeatMessage)
			unflushed += linkHeartbeatMessage.Size()//统计未发送的字节数
			if err == nil {
   
				//若没有异常,则使用flusher将缓冲区的消息全部发送出去,并重置batched和unflushed
				flusher.Flush()
				batched = 0
				sentBytes.WithLabelValues(cw.peerID.String()).Add(float64(unflushed))
				unflushed = 0
				continue //不阻塞,直接continue
			}
			//到这里说明有错误,将网络连接状态置为不活跃
			cw.status
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值