使用http作为消息投递协议的潜在问题(在实际业务中结合TCP协议的抓包分析)

本文通过实例分析了使用HTTP作为消息投递协议时,因TCP重传导致的数据重复问题。在特定网络环境下,TCP连接异常可能导致客户端重传HTTP请求,从而造成消息重复写入。通过TCP抓包分析,揭示了TCP连接建立、数据发送、确认丢失与重传的细节,以及如何避免此类问题的方法。
摘要由CSDN通过智能技术生成

背景

我们某个数据服务向位于公网的阿里云的LogHub(可以理解为一个Kafka)投递消息,投递的客户端使用的是阿里云提供的Java SDK,看其源码发现其使用http批量投递消息的方式向远端投递。

其内部逻辑大概如下

public void send(msg) {
	try {
		//正常发送
		http.send(msg);
	} catch (e) {
		//遇到异常后重发
		http.reSend(msg);
	}
}

偶然间发现投递后的数据有重复的,遂排查之,最后结合tcp协议抓包分析到了,就是因为网络带宽限制,发送的第一次请求,实际已经到达服务端,服务端已经写入Loghub服务,但是由于“** 相关ack并没有返回给客户端,所以客户端的tcp重试机制,重试的时候服务器的socket因为某种原因已经关闭,就给正阻塞在read的客户端发送了rst包,导致客户端抛异常 **”, 所以客户端认为发送失败,进而执行了重传逻辑,进行了第二次的发送,导致了消息写入MQ写了两次而出现了重复消息。

第一次请求

在这里插入图片描述

分为"5"和"6"两个ip,下面是详细的过程解释:

第一个包:5向6发起三次握手,发送一个syn

第二个包:6向5发送第二个握手包,发送一个syn+ack

第三个包:5向6发送确认包,连接由此建立

第四个包:连接建立后,开始发送post的http请求,这里的第四与第五个包实际上是一个http包来的,因为发送窗口限制,被切成了两个包分两次发送,一个包被wireshark识别成了tcp,另一个被识别成http。实际上http的header是在第四个包里的

第五个包:如上,里面是消息体

第六个包:当"ip-5"发送了第四与第五个包后,期待收到ack确认,但是"ip-6"收到了包,也发送了两个ack,但这两个ack都在网络中丢失了(待会解释为什么这么说),这里有些奇怪,不知道为什么明明没有收到第四个包的ack确认包而跳过第四个包直接重发了第五个包。这个重发包也就是第六个包,可以看到第六个包与第五个包的length一样(都是647)

第七个包:站在"ip-6"的角度想,连接建立后,我收到了两个包,一个tcp,一个http,然后我也都回了两个ack确认,但我并不知道这两个ack都在网络中丢失了,而且在我发出两个ack之后的一段时间内一直没有收到任何数据,所以我觉得这个socket开着浪费,我就关掉了这个连接(这里关掉socket的原因还有待商榷,也可能是服务端程序异常导致关闭这个socket)。但是这时候客户端又发过来一个包(也就是第六个包,而且这个时候客户端还在阻塞在read方法等待服务器回tcp包),当这个包到达操作系统内核后,内核发现该链接已经关闭了,所以内核直接向客户端发送了rst标志位的包来reset连接。reset包也就是第七个包,可以看到第七个包的ack为1066可以计算并推理得到服务端实际上是接收到了包,并返回了ack,只不过客户端没有收到这两个ack而已。

站在"ip-5"的角度想,连接建立后,我发送了两个包,没有一个收到ack,所以我迫不及待的重传了http那个包,发出去后不久,我就莫名其妙地接到了一个reset的包。

第二次请求

因为这边sdk在发送完毕后得到了rst,所以抛出Connection reset的异常,然后执行了重传逻辑,所以就有了这第二次的请求。

以下是第二次请求的情况:

在这里插入图片描述

还是根据最后一位ip分为ip5和ip6,以下是对各个包的分析

前三个包(36263,36272,36273):tcp三次握手

第四个包(36274):跟第一次请求一样,把一次http请求包切成了两个包,分两次发送

第五个包(36275):同上

第六个包(36278):这次可以看到,在ip5在发送出两个包后,ip6回复了两个ack并顺利到达了ip5端,第六个包是第一个ack包

第七个包(36279):第七个包是第二个ack包

第八个包(36284):因为一次http连接包括请求和响应,第八个包是ip6的响应包,包很小,一下就能发完

第九个包(36285):ip5在接到响应后,对ip6的ack确认

第十个包(36286):本次http连接完毕,没有数据要发送了,开始tcp四次挥手,这里由ip6首先发起发送了一个fin包

第十一个包(36287):在课本当中,tcp断开要四个包,但在实际当中,为了更快的断开tcp连接,如果当被断开方接到fin包后发现自己也没有业务数据要发送了,那么就会把课本当中的第二个ack包和第三个fin包合并到一起发送,这里就是这种情况

一下三个包,全都是我个人根据我知道的知识做出的推断,并且提供了我的依据,供参考

第十二个包(36289):我认为是ip6对于她自己发送的fin包没有接到回应,进行超时重传的包,这里的依据是从包的长度,包携带的ack number,包的seq都与她发送的第一个fin-ack包相同来判断的

第十三个包(36290):我认为是对第十二个包的确认,虽然第十三个包的从ack number上、wireshark提示的dup ack 36287#1都显示也可能第十三个包是对第十个包的确认,但我并不这么觉得。因为我是在ip5上抓的包,所以只要是ip5上发出的包我就一定能抓到,再加上按照tcp的协议来说,ip6发过一个包来,ip5肯定要回一个ack确认包。这里奇怪的是ip5只发送了一个只包含ACK的包,而没有FIN,这里可能就跟具体的操作系统的tcp实现有关吧。

第十四个包(36299):我认为是当ip6发送出第十个包后,等待超时后重传了第十二个包,发出后,ip5发送的第十一个包到达了ip6(ps:这种情况我觉得是可能会出现的,但我并不确定这种情况下,ip6的tcp层会对这个迟到的包怎么处理),然后ip6对第十一个包进行了ack确认,这个ack也就是第十四个包。依据是:单单按照第十四个包的ack number的角度来看,她确认的可能是第十三个包(因为第十三个包是一个ACK包,确认号等于本身的seq值)和第十一个包(因为第十一个包是fin-ack包,她的确认号等于本身的seq+1),但是TCP中不会对一个确认包进行确认,所以他只可能确认的是第十一个包,也就是第十一个包最后是到达了ip6的

结束。

上文中提到的一些TCP的规则,我整理成了另一篇文章,TCP协议的seq、ack的计算与实际中tcp断开的优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值