充分了解才能用,基本了解,大局实现,
充分测试,压测分析profile,
非核心业务试行,
业界选择,不是说别人牛,是因为别人拿业务来验证过
待整理
我的重构(翻译)经历
前言
- 这里的重构基本指的是:把用A语言写的服务,使用B语言重写
- 工作的大部分内容其实就是人肉代码翻译
- 当然,重构通常也伴随的架构的优化和调整
- 最后,为了保证工作的顺利进行和服务的稳定切换,也总结了一些经验
- 下面将简要讲述作者的三段重构经历,分别是将三种不同的语言重构成Java(本人公司的所在的业务的主要语言就是Java,个人没特殊语言癖好),作者其实对这三种语言都不熟悉,是怎么完成重构任务的呢?
一、PHP重构成Java(“一比一”迁移)
- 负责该业务的模块
- 启动环境太复杂,较复杂的逻辑通过在线工具运行验证
- 和熟悉该业务的PHP同事合作
- 切换服务接口使用灰度策略和配置开关
- 覆盖好测试用例
- 没本地环境,必要时在线运行验证
二、C++重构成Java(站在“巨人的肩膀”上重新出发)
- 其他公司业务调整,业务并入,需复制一套业务功能基本相同的服务
- 通过参考原业务的代码,发现其中架构存在的问题,进行优化;参考其中的实现,避免在新实现中出现考虑不周(站在“巨人的肩膀”上)
- 和第一段重构经历不同的是,原服务只是作为参考,在业务功能上一摸一样,并不需要在内部实现上保持和原来完全一致,但是新服务需要融入现有的服务基础体系
- 有意思的优化,原来因为业务逻辑的需要使用单体服务,新服务使用新的巧妙设计,是服务可以有多实例运行
- 不需要本地环境,阅读参考即可
三、Lua重构成Java(对当前“一无所知”也没关系)
- 本地把环境跑起来,通过调试验证逻辑
- 新业务需要,需要参考部分逻辑,同时进行重构,反哺原业务团队
- 需要本地环境,验证原本一无所知的逻辑
总结
- 编程语言的语言其实大同小异,花半天左右基本可以快速熟悉语法,在阅读代码的时候遇到不懂的地方,即时查阅即可;
- 对于一些复杂的逻辑,可以通过编写代码块,执行验证输出;可以在本地环境运行,也可以利用在线工具运行;
- 对于历史包袱较重的,要善于利用灰度策略;
- 阅读代码后,跟熟悉业务的同学请教确认,和同事们讨论有时也是很有必要的。
TCP连接中的各种状态
状态说明
- CLOSED:无连接是活动的或正在进行
- LISTEN:服务器在等待进入呼叫
- SYN_RECV:一个连接请求已经到达,等待确认
- SYN_SENT:应用已经开始,打开一个连接
- ESTABLISHED:正常数据传输状态
- FIN_WAIT1:应用说它已经完成
- FIN_WAIT2:另一边已同意释放
- ITMED_WAIT:等待所有分组死掉
- CLOSING:两边同时尝试关闭
- TIME_WAIT:另一边已初始化一个释放
- LAST_ACK:等待所有分组死掉
flags 标志
- S(SYN)
- F(FIN)
- P(PUSH)
- R(RST)
常用命令
查看主机上的TCP连接状态
netstat –an
netstat –an |grep 'CLOSE_WAIT'
ss -t -n|grep 5000
统计当前各种状态的连接的数量的命令
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
查看指定端口的连接
netstat -an | awk 'NR==2 || $4~/65016/'
(client)netstat -an | awk 'NR==2 || $5~/8080/'
(server)
Linux netstat命令详解
tcpdump
想知道我们可以通过哪几个网卡抓包,可以使用-D参数 :
tcpdump –D
将抓包结果存放在文件中(可以用Wireshark打开查看) :
tcpdump –w google.cap
其中http协议的数据包都给过滤出来:
tcpdump –r google.cap http
Reference
todo
- tcp 三次握手和四次断连深入分析:连接状态和socket API的关系
- close函数其实本身不会导致tcp协议栈立刻发送fin包,而只是将socket文件的引用计数减1,当socket文件的引用计数变为0的时候,操作系统会自动关闭tcp连接,此时才会发送fin包。
- 这也是多进程编程需要特别注意的一点,父进程中一定要将socket文件描述符close,否则运行一段时间后就可能会出现操作系统提示too many open files
TIME_WAIT解析
TIME_WAIT
- 通信双方建立TCP连接后,主动关闭(FIN)连接的一方就会进入TIME_WAIT状态
- 客户端主动关闭连接时,发送最后一个ack后,会进入TIME_WAIT状态,再停留2个MSL时间,进入CLOSED状态
TIME_WAIT状态存在的理由
可靠地实现TCP全双工连接的终止
- TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
- 因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持TIME_WAIT状态 。
- 对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIMEWAIT状态停留的时间为2倍的MSL。这样可尽可能让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
- 总结:尽量避免主动关闭方ack丢失导致被关闭方异常(理论上从应用层看,被关闭方需要对这种异常进行处理,因为万一主动关闭方断电了呢)
允许老的重复分节在网络中消逝
- TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,基本可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。
- IP包的最大生存时间记录在其TTL字段中,即MSL,超过将过期丢弃
MSL时间
- MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间IP数据包将在网络中消失 。MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒。
TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段 - TIME_WAIT状态维持时间是两个MSL时间长度,也就是在1-4分钟。Windows操作系统就是4分钟。
- 为什么需要2MSL?
- 第一个MSL是为了等自己发出去的最后一个ACK从网络中消失,同时让旧的数据包基本在网络中消失;
- 第二MSL是为了等在对端收到ACK之前的一刹那可能重传的FIN报文从网络中消失(主要目的)
- 问题:重传的FIN报文会重传多少次?
- 重发次数由 tcp_orphan_retries 参数控制
- 所以如果最后一个ack丢了,且对端又重试发FIN,那么还是无法避免FIN包没过期,所以2MSL只是尽可能,但这时旧的数据包基本消失了
总结
- TIME_WAIT主动关闭方的状态
- TIME_WAIT存在的原因
- 防止主动关闭方最后的ACK丢失,确保远程TCP接收到连接中断的请求
- 允许老的重复数据包在网络中过期
TIME_WAIT产生的场景
- 进入TIME_WAIT状态的一般情况下是客户端。
- 大多数服务器端一般执行被动关闭,服务器不会进入TIME_WAIT状态。
但在服务器端关闭某个服务再重新启动时,服务器是会进入TIME_WAIT状态的。
可以使用SO_REUSEADDR选项来复用端口。
举例: 1.客户端连接服务器的80服务,这时客户端会启用一个本地的端口访问服务器的80,访问完成后关闭此连接,立刻再次访问服务器的 80,这时客户端会启用另一个本地的端口,而不是刚才使用的那个本地端口。原因就是刚才的那个连接还处于TIME_WAIT状态。 2.客户端连接服务器的80服务,这时服务器关闭80端口,立即再次重启80端口的服务,这时可能不会成功启动,原因也是服务器的连 接还处于TIME_WAIT状态。
作为客户端和服务器
- 服务端提供服务时,一般监听一个端口就够了;
- 客户端则是使用一个本地的空闲端口(大于1024),与服务器的端口建立连接;
- 如果使用短连接请求,那么客户端将会产生大量TIME_WAIT状态的端口(本地最多就能承受6万个TIME_WAIT状态的连接就无端口可用了,后续的短连接就会产生address already in use : connect的异常)
- 因此,作为短连接请求的压测服务器,不能在短时间连续使用;
- 一般来说一台机器可用Local Port 3万多个,如果是短连接的话,一个连接释放后默认需要60秒回收,30000/60 =500 这是大概的理论TPS值
- 一个提供高并发的服务器,同时依赖第三方服务(间接看来服务端也作为第三方服务的客户端),怎么应对 ?
- 一般情况都是启用keepalive选项,避免短连接服务(一般依赖方也不会多达几千个,即调用的ip和端口不一样)
- 启用SO_REUSEADDR选项
- 大多数服务器端一般执行被动关闭,服务器不会进入TIME_WAIT状态
如何复用TIME_WAIT端口
- 应用层发出请求前指定,如何Java HttpClient中设置reuseaddr
1
2
3httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler());
httpClient.setReuseStrategy(new DefaultConnectionReuseStrategy());
httpClient.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()); - 调整内核参数(net.ipv4.tcp_tw_reuse)
总结避免TIME_WAIT的方法
- 使用长连接(基本大部分业务场景都可以)
- 避免主动关闭
- 关闭的时候使用RST的方式 (比如程序中设置socket的SO_LINGER选项)(应用层貌似不是很方便实现)
- TIME_WAIT状态的TCP允许重用
- 增大可用端口范围,默认是 net.ipv4.ip_local_port_range = 32768 61000 (即对同一个服务器的ip+port可创建28233个连接)(只能缓解问题,不能根本解决问题)
Reference
你记得设置TCP_NODEPLAY吗?
有接触过TCP服务器实现的同学都会知道,需要注意TCP_NODELAY参数,为什么呢?
若没有开启TCP_NODELAY,那么在发送小包的时候,可能会出现这样的现象:
通过 TCP socket 分多次发送较少的数据时,比如小于 1460 或者 100 以内,对端可能会很长时间收不到数据,导致本端应用程序认为超时报错。
Nagle算法(Nagle‘s Algorithm)
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
举个例子,一开始client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,接着,client端又调用write操作写入一个int型数据(简称B块),这个时候,A块的ACK没有返回,所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后)(ACK延迟机制的超时时间),B块才被发送。
Nagle算法的改进在于:如果发送端欲多次发送包含少量字符的数据包(一般情况下,后面统一称长度小于MSS的数据包为小包,与此相对,称长度等于MSS的数据包为大包,为了某些对比说明,还有中包,即长度比小包长,但又不足一个MSS的包),则发送端会先将第一个小包发送出去,而将后面到达的少量字符数据都缓存起来而不立即发送,直到收到接收端对前一个数据包报文段的ACK确认、或当前字符属于紧急数据,或者积攒到了一定数量的数据(比如缓存的字符数据已经达到数据包报文段的最大长度)等多种情况才将其组成一个较大的数据包发送出去。
TCP在三次握手建立连接过程中,会在SYN报文中使用MSS(Maximum Segment Size)选项功能,协商交互双方能够接收的最大段长MSS值。
ACK延迟机制(TCP Delayed Acknoledgement)
- TCP/IP中不仅仅有Nagle算法(Nagle‘s Algorithm),还有一个ACK延迟机制(TCP Delayed Ack) 。当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。
- 也就是如果一个 TCP 连接的一端启用了Nagle算法,而另一端启用了ACK延时机制,而发送的数据包又比较小,则可能会出现这样的情况:发送端在等待接收端对上一个packet的Ack才发送当前的packet,而接收端则正好延迟了此Ack的发送,那么这个正要被发送的packet就会同样被延迟。当然Delayed Ack是有个超时机制的,而默认的超时正好就是40ms。
- 现代的 TCP/IP 协议栈实现,默认几乎都启用了这两个功能,那岂不每次都会触发这个延迟问题?事实不是那样的。仅当协议的交互是发送端连续发送两个packet,然后立刻read的时候才会出现问题。
总结:问题出现的三个条件
- 发送小包(仅当协议的交互是发送端连续发送两个 packet,然后立刻 read 的 时候才会出现问题。)
- 发送方启用了Nagle算法(发送方未接收到上一个包的ack,且待发送的是小包,则会等待)
- 接收方启用了ACK延时机制 且没及时准备好数据(希望响应ack可以和响应的数据一起发送,等待本端响应数据的准备)
解决办法
- 开启TCP_NODELAY:禁用Nagle算法,禁止后当然就不会有它引起的一系列问题了。
- 优化协议:连续 write 小数据包,然后 read 其实是一个不好的网络编程模式,这样的连续 write 其实应该在应用层合并成一次 write。
扩展
另一个问题的例子(HTTP服务)
接口响应时间在client端开启keepalive后连续请求时由0ms变成40ms
因为设计的一些不足,我没能做到把 短小的 HTTP Body 连同 HTTP Headers 一起发送出去,而是分开成两次调用实 现的,之后进入 epoll_wait 等待下一个 Request 被发送过来(相当于阻塞模 型里直接 read)。正好是 write-write-read 的模式
那么 write-read-write-read 会不会出问题呢?维基百科上的解释是不会:
- “The user-level solution is to avoid write-write-read sequences on sockets. write-read-write-read is fine. write-write-write is fine. But write-write-read is a killer. So, if you can, buffer up your little writes to TCP and send them all at once. Using the standard UNIX I/O package and flushing write before each read usually works.”
- 我的理解是这样的:因为第一个 write 不会被缓冲,会立刻到达接收端,如果是 write-read-write-read 模式,此时接收端应该已经得到所有需要的数据以进行 下一步处理。接收端此时处理完后发送结果,同时也就可以把上一个packet 的 Ack 可以和数据一起发送回去,不需要 delay,从而不会导致任何问题。
Reference
有了TCP的keepalive,应用层还需要实现保活逻辑吗?
结论
- 对于实时性高的业务,基本都需要在应用层自行实现保活逻辑,应用层的心跳协议是必不可少的
TCP keepalive 的 问题
- 检测周期长,开启后默认是2h(系统内核参数 tcp_keepalive_time),这就意味着服务端可能维持着一个死连接;
- TCP keepalive 是由操作系统负责探查,即便进程死锁,或阻塞等,操作系统也会收发 TCP keepalive 消息,无法及时感知客户端已经实际已经下线;
应用层实现心跳的基本做法
- 服务端和客户端都开启tcp keepalive
- 客户端定时发心跳包到服务端
- 服务端根据自定义的规则,在一定时间内收不到心跳包的时,断开客户端的连接。
应用层实现心跳保活逻辑的好处
- 可以在发送心跳包的同时顺带业务或指令数据,这样服务端获得客户端的详细状态,同时可以更好满足业务场景
- 可以灵活控制探查客户端的时间和策略,更快下线有异常的连接,减少服务端不必要的负担
Reference
关于HTTP相关协议的一些总结
粗浅概括
- HTTP - TCP
- HTTPS - TCP + TLS
- SPDY -> TCP + TLS + 多路复用、头部压缩等特性 –> 发展成 HTTP/2
- SPDY是Speedy的音,是更快的意思
- HTTP/2 - TCP + TLS(理论上可选) + 多路复用、头部压缩等特性
- QUIC - UDP –> 发展成 HTTP/3
- HTTP/3 - UDP
其他基础
- RTT(Round-Trip Time): 往返时延。在计算机网络中它是一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。
HTTP
- 请求-响应模式(半双工)
- 安全问题
“队头堵塞” (线头阻塞)(Head-of-line blocking)(HOLB)
- HTTP 1.1 默认启用长TCP连接,但所有的请求-响应都是按序进行的(串行发送和接收)
- HTTP 1.1 的管道机制:客户端可以同时发送多个请求,但服务端也需要按请求的顺序依次给出响应的;
- 客户端在未收到之前所发出所有请求的响应之前,将会阻塞后面的请求(排队等待),这称为”队头堵塞”
管道机制(Pipelining)
- 在管道机制下,服务端如何控制按顺序返回响应的?
- HTTP是应用层协议,当然由各个应用程序按照规范自行实现了
- 比如使用nginx,或jetty等,若服务端需要支持管道机制,都要底层逻辑自行实现,避免暴露给业务层
- 那么因为要按顺序响应,那么当最前的请求的处理较慢时,同样会对服务端产生阻塞。
- Pipelining需要客户端和服务端同时支持
- 几乎所有的浏览器都是默认关闭或者不支持Pipelining的:对性能的提高有限、大文件会阻塞优先级更高的小文件等
- 只有GET和HEAD要求可以进行管线化,而POST则有所限制
HTTPS
HTTPS(HTTP over TLS/SSL),TLS/SSL(会话层)
SSL(Secure Socket Layer)是安全套接层,TLS(Transport Layer Security)是传输层安全协议,建立在SSL3.0协议规范,是 SSL3.0 的后续版本。
TLS可以用在TCP上,也可以用在无连接的UDP报文上。协议规定了身份认证、算法协商、密钥交换等的实现。
SSL是TLS的前身,现在已不再更新
jks、pfx和cer 证书文件
- jks是JAVA的keytools证书工具支持的证书私钥格式。
- pfx是微软支持的私钥格式。
- cer是证书的公钥。
权威证书颁发的公钥匙一般是预装的
当我们安装浏览器或操作系统时,将会附有一组证书颁发机构,例如DigiCert。当浏览器自带DigiCert时,这意味着浏览器具有DigiCert的公钥,网站可以向DigiCert索取证书和签名。因此,DigiCert将使用DigiCerts私钥在服务器证书上进行加密签名。当我们发起连接时,服务器将发送嵌入了其公钥的证书。由于浏览器具有DigiCert的公钥,因此可以在服务器证书上验证DigiCert的签名,同时也说明证书上写的服务器的公钥是可信的。
根据RSA的加密原理,如果用CA的公钥解密成功,说明该证书的确是用CA的私钥加密的,可以认为被验证方是可信的。
HTTP/2
- 全双工
- 二进制格式传输、多路复用、header压缩、服务端推送、优先级和依赖关系、重置、流量控制
多路复用(Multiplexing)
- 客户端发送多个请求和服务端给出多个响应的顺序不受限制, 避免”队头堵塞”
- 每个数据流都有一个唯一的编号,从而让请求和响应对应起来
- 客户端和服务器 可以发生信号取消某个数据流,并保持这个连接
- 客户端还可以提升提升某个数据流优先级
加密
- HTTP/2 沒规定一定要使用加密(例如 SSL),但目前大部分浏览器的 HTTP/2 都需要在 HTTPs上运行
- gRPC 虽然使用 HTTP/2,但默认并没有需要配置加密证书
重用连接(针对浏览器)
- 使用HTTP1.1协议,浏览器为了快速,针对同一域名设置了一定的并发数,稍微加快速度
- 使用HTTP/2,浏览器针对同一个域名的资源,只建立一个tcp连接通道
头部压缩
- 头部压缩也存在一些缺点 ,不管是Client还是Server,都要维护索引表,以确定每个索引值对应HTTP header的信息,通过占用更多内存换取数据量传输的减少(空间换时间)。
推送
- Chrome将禁用HTTP/2服务器推送(Server Push)支持
- 这功能逻辑本身就有问题,比如资源存放在单个业务服务器上,并行推送多个静态资源只会降低响应速度,性能不升反降。而对于前后端分离的业务来说,HTTP/2 本身就支持多路复用,server push 只能稍微降低浏览器解析 html 的时间,对现代浏览器来说性能提升可以忽略不计。
HTTP/3
HTTP/1.x 有连接无法复用、队头阻塞、协议开销大和安全因素等多个缺陷
HTTP/2 通过多路复用、二进制流、Header 压缩等等技术,极大地提高了性能,但是还是存在着问题的
QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议,该协议基于 UDP,又取了 TCP 中的精华,实现了即快又可靠的协议
HTTP3.0,也称作HTTP over QUIC。HTTP3.0的核心是QUIC(读音quick)协议,由Google在2015年提出的SPDY v3演化而来的新协议,传统的HTTP协议是基于传输层TCP的协议,而QUIC是基于传输层UDP上的协议,可以定义成:HTTP3.0基于UDP的安全可靠的HTTP2.0协议。
在网络条件较差的情况下,HTTP/3在增强网页浏览体验方面的效果非常好
TCP从来就不适合处理有损无线环境中的数据传输
TCP中的行头阻塞
TCP的限制
- TCP可能会间歇性地挂起数据传输
- TCP流的行头阻塞(HoL): 序列号较低的数据段丢包问题,导致阻塞
- TCP不支持流级复用
- TCP会产生冗余通信
- TCP连接握手会有冗余的消息交换序列,即使是与已知主机建立的连接也是如此。
新特性
- 选择UDP作为底层传输层协议。抛弃TCP的缺点(TCP传输确认、重传慢启动等),同时。此外QUIC是用户层协议,不需要每次协议升级时修改内核;
- 流复用和流控:解决了行头阻塞问题。
- 灵活的拥塞控制机制、更好的错误处理能力、更快的握手
- 新的HTTP头压缩机制,称为QPACK,是对HTTP/2中使用的HPACK的增强(QUIC流是不按顺序传递的,在不同的流中可能包含不同的HTTP头)
采用HTTP/3的限制
- 不仅涉及到应用层的变化,还涉及到底层传输层的变化
- UDP会话会被防火墙的默认数据包过滤策略所影响
- 中间层,如防火墙、代理、NAT设备等需要兼容
- 需迫使中间层厂商标准化
- HTTP/3在现有的UDP之上,以QUIC的形式在传输层处理,增加了HTTP/3在整个协议栈中的占用空间。这使得HTTP/3较为笨重,不适合某些IoT设备
- NGINX和Apache等主流web服务器需要支持
Q&A
HTTP 与 TCP backlog关系
- 没直接关系
- HTTP是应用层协议,TCP backlog 是应用程序在操作系统层接收tcp连接的队列数
- 比如tomcat,作为一个HTTP应用服务,TCP backlog对应其acceptCount的配置
关于 HTTP keepalive
要利用HTTP的keep-alive机制,需要服务器端和客户端同时支持
HTTP是应用层协议,具体的表现行为取决于HTTP服务器以及HTTP client的实现
- keepalive 是否开启服务端控制还是客户端控制?
- keepalive可以由双方共同控制,需要双方都开启才能生效,HTTP1.1客户端默认开启,客户端想关闭可以通过设置Connection: Close,服务端同样想关闭可以设置Connection: Close。双方哪方先收到Connection: Close 则由收到方关闭(前提是双方的实现都支持,比如telnet就不支持)
- keepalive的时间是由服务端控制还是客户端控制?
- 时间主要还是由服务端控制,时间一到由服务端主动关闭,当然客户端如果有实现设置一定时间后,由客户端主动关闭也可以。一般的HTTPclient库都有提供相应的配置,设置关闭长期不使用的连接,如connectionManager.closeIdleConnections(readTimeout * 2, TimeUnit.MILLISECONDS);
- HTTPs://my.oschina.net/greki/blog/83350
- keepalive时间一到,是由客户端主动关闭还是服务端主动关闭?
- 哪方的时间短,由哪一方来关闭,除非双方的实现有更明确的协议
- 如果客户端不是HTTPclient,使用telnet连接服务端?
- telnet客户端除了连接时进行三次握手,用来发送数据接收数据,基本无其他实现逻辑。即接收到服务器的响应之后,不会有相关HTTP协议的处理。
HTTP keepalive VS TCP keepalive
- HTTPs://zhuanlan.zhihu.com/p/385597183; HTTPs://juejin.cn/post/6992845852192702477
- HTTP 的 Keep-Alive,是由应用层(用户态) 实现的,称为 HTTP 长连接;
- TCP 的 Keepalive,是由 TCP 层(内核态) 实现的,称为 TCP 保活机制;
- HTTP协议的Keep-Alive意图在于短时间内连接复用,希望可以短时间内在同一个连接上进行多次请求/响应。
- TCP的KeepAlive机制意图在于保活、心跳,检测连接错误。当一个TCP连接两端长时间没有数据传输时(通常默认配置是2小时),发送keepalive探针,探测链接是否存活。
- tcp的keepalive是在ESTABLISH状态的时候,双方如何检测连接的可用行。而HTTP的keep-alive说的是如何避免进行重复的TCP三次握手和四次挥手的环节。
- 总之,HTTP的Keep-Alive和TCP的KeepAlive不是一回事。
Chrome中HTTP下载续传原理
HTTP连接复用时,同一个连接上的多个请求和响应如何对应上?
- “队头堵塞”(Head-of-line blocking):所有的请求-响应都是按序进行的(HTTP)
- 多路复用(Multiplexing):每个数据流都有一个唯一的编号,从而让请求和响应对应起来(HTTP/2)
可以外网使用HTTP/3,再转发到内网的HTTP服务?
- 上层nginx使用HTTP3,下层应用服务器(如spring boot jetty等)还是使用HTTP,其实理论上是可以的。nginx转发时要由接受到的udp包改成tcp发送。(内网丢包概率一般应该比外网丢包低很多),如果采用这种转发方式,这就意味着内网无法使用四层负载转发,因为底层协议不一样(udp和tcp)
- 现在主流的代理服务Nginx/Apache都没有实现QUIC,一些比较小众的代理服务如Caddy就实现了
使用HTTPS还存在中间人攻击?
结论:可以避免。只要不信任不安全的HTTPs网站,就不会被中间人攻击
中间人攻击:HTTPs://urlify.cn/zQj6f2
既然证书是公开的,如果要发起中间人攻击,我在官网上下载一份证书作为我的服务器证书,那客户端肯定会认同这个证书是合法的,如何避免这种证书冒用的情况?
其实这就是非加密对称中公私钥的用处,虽然中间人可以得到证书,但私钥是无法获取的,一份公钥是不可能推算出其对应的私钥,中间人即使拿到证书也无法伪装成合法服务端,因为无法对客户端传入的加密数据进行解密。只要客户端是我们自己的终端,我们授权的情况下,便可以组建中间人网络,而抓包工具便是作为中间人的代理。
Q: 为什么需要证书? A: 防止”中间人“攻击,同时可以为网站提供身份证明。 Q: 使用 HTTPS 会被抓包吗? A: 会被抓包,HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密。
扩展
cURL 发 HTTP/2请求
- Mac OS Curl HTTP/2 支持
brew install curl --with-ngHTTP2
/usr/local/Cellar/curl/7.50.3/bin/curl --HTTP2 -kI HTTPs://localhost:8443/user/1 HTTP/2 200 server: Jetty(9.3.10.v20160621) date: Sun, 30 Oct 2016 02:08:46 GMT content-type: application/json;charset=UTF-8 content-length: 23
HTTP/3 握手优化
- 1倍时延 = 一次单向传输时延 = 0.5 RTT
- HTTPS 的 7 次握手以及 9 倍时延
- HTTPS: 7 次握手以及 9 倍时延 (4.5 RTT); HTTP/3: 3 次握手以及 5 倍时延 (2.5 RTT)
当客户端想要通过 HTTPS 请求访问服务端时,整个过程需要经过 7 次握手并消耗 9 倍的延迟。如果客户端和服务端因为物理距离上的限制,RTT 约为 40ms 时,第一次请求需要 ~180ms;不过如果我们想要访问美国的服务器,RTT 约为 200ms 时,这时 HTTPS 请求的耗时为 ~900ms,这就是一个比较高的耗时了。我们来总结一下 HTTPS 协议需要 9 倍时延才能完成通信的原因:
TCP 协议需要通过三次握手建立 TCP 连接保证通信的可靠性(1.5-RTT);
TLS 协议会在 TCP 协议之上通过四次握手建立 TLS 连接保证通信的安全性(2-RTT);
HTTP 协议会在 TCP 和 TLS 上通过一次往返发送请求并接收响应(1-RTT);
需要注意的是,本文对往返延时的计算都基于特定的场景以及特定的协议版本,网络协议的版本在不断更新和演进,过去忽略的问题最开始都会通过补丁的方式更新,但是最后仍然会需要从底层完成重写。
HTTP/3 就是一个这样的例子,它会使用基于 UDP 的 QUIC 协议进行握手,将 TCP 和 TLS 的握手过程结合起来,把 7 次握手减少到了 3 次握手,直接建立了可靠并且安全的传输通道,将原本 ~900ms 的耗时降低至 ~500ms,
Reference
Chrome下载文件时暂停和继续是什么原理?
- 这个问题很久前就研究过了,觉得挺有意思,这里总结记录一下
- 所有的文字整理来源于: https://bbs.csdn.net/topics/392163074,感谢wjyiooo的耐心解答
Range
- Range,是在 HTTP/1.1(http://www.w3.org/Protocols/rfc2616/rfc2616.html)里新增的一个 header field,也是现在众多号称多线程下载工具(如 FlashGet、迅雷等)实现多线程下载的核心所在。
chrome 版本58
- 抓包确认,chrome点击暂停的时候会发送一系列窗口变动应答,将窗口降到5,并且不再应答ACK包。 当点击恢复的时候只是重新发送ACK给服务器,同时将窗口重新设置为256。
- 以上可以确认它的“续传”只是利用TCP滑动窗口的特性,跟断不断网没关系,也不属于真正意义的断点续传功能(一般用range头部实现)。当然如果你中断网络超过了服务器的TCP连接超时时间那么就不能续传了,而且如果关闭浏览器即使网络非常正常也不能续传(也是因为TCP连接断了)。
- 在等待足够长时间让TCP连接关掉后,chrome就可以断点续传了,原理也是头部带range。
总结
- 本地有临时下载文件
- 短时间内点继续,利用的是TCP滑动窗口的特性(与服务器未断开)
- 长时间之后点继续,再次发请求,带range头,继续下载剩余部分(与服务器断开)
扩展
使用Range进行多线程下载
- Http 请求头 Range:https://www.cnblogs.com/1995hxt/p/5692050.html
- curl -H “Range: bytes=0-1551” http://127.0.0.1:8180/bg-upper.png -v -o 0-1151.png
- curl -H “Range: bytes=1552-3103” http://127.0.0.1:8180/bg-upper.png -v -o 1552-end.png
- 合并 cat 0-1151.png 1552-end.png > filename.png
推送技术总结
概述
从客户端是手机APP的角度来理解推送(PUSH),展示的形式有两种:
- App 推送:消息内容通过手机通知栏(状态栏)展示
- 应用内消息:各种业务数据推送(通过定义模版或命令号等方式推送给APP内的业务使用)
从Web端角度看理解推送,一般只有网页内消息(跟手机APP的应用内消息是一样的类型)
APP推送:
- 在线推送(应用级方案):APP进程控制推送消息,理论上只要APP要获得“手机通知栏”的权限(一般通过在APP内维持长连接来进行推送,但前提是APP已经启动和运行,并且能常驻)
- 离线推送(系统级方案):通过手机操作系统或手机厂商提供的通道进行推送。这种推送方式可以在APP未启动的情况下,推送APP的消息。
- APP进程运行时,应该优先走在线推送,自己的推送系统更快、更有保障。
应用场景
- APP推送:电商内APP(推送促销消息)
- 应用内消息:直播类APP(推送送礼特效消息)
- APP推送和应用内消息都需要:IM类APP(推送用户聊天信息)
从推送的实现角度看,基本可以概括为两种:主动轮询(pull 拉)和长连接 (push 推)
实现方式
- 以下实现方式没有进行严格分类,从原理上看存在相互联系
HTTP轮询
- 短轮询(Polling)
- 长轮询(Long-polling)(Nacos和apollo配置中心也是用这种)
- 从TCP的角度看HTTP长轮询:HTTP开启keepalive,服务端保持连接并不需要发额外数据包,有数据时可以立刻推送,跟TCP长连推送无异。展示服务端有点费连接的相关资源,数据包是HTTP相比较大而已。
SSE (Server Sent Event 服务器发送事件)
- sse 单通道,只能服务端向客户端发消息; webscoket 是双通道
- 实现成本较低
- http 协议,服务端响应的是text/event-stream类型的数据流信息
- 场景:站内信、未读消息数、状态更新、股票行情、监控数量等场景
WebSocket
MQTT (通常结合TCP长连接一起使用)
TCP长连接(自定义消息或protobuf等格式)
系统级方案
- Android和IOS本身的消息推送(Android的C2DM和IOS的APNS,系统与服务器建立连接,APP向系统注册关注的消息,实现系统级消息推送)
- 国内Android无法访问Google服务器,所以国内的手机厂商比如小米、OPPO、华为等,都实现来各自的系统级推送。
- 避免维持长连接而导致的过多资源消耗,IM类要求即时的更应该接系统级推送
第三方推送平台
- 集成各种手机平台,各种推送类型,甚至短信等推送
- 简单来说:由专业的平台做专业的事(太麻烦了,我只是想推送了消息,帮我搞定吧。。。)