1. 项目概述:为什么网络是Java面试的“必答题”?
干了这么多年Java开发,也面过不少人,我发现一个挺有意思的现象:很多候选人能把Spring全家桶、JVM调优讲得头头是道,但一碰到网络相关的问题,比如“TCP三次握手为什么是三次而不是两次”、“浏览器输入URL后发生了什么”,回答就开始变得含糊不清,甚至漏洞百出。这其实挺可惜的,因为网络知识恰恰是区分一个程序员是“会用框架”还是“理解系统”的关键分水岭。尤其是在微服务、云原生架构大行其道的今天,服务间的每一次调用本质上都是一次网络通信,理解底层网络原理,对于排查线上高并发下的超时、连接池耗尽、性能瓶颈等问题,有着不可替代的价值。
这份“Java面试核心知识点整理2——网络”,就是针对这个痛点来的。它不是一本包罗万象的网络教科书,而是从一线Java后端开发的视角出发,筛选出那些在面试中最高频出现、在实际工作中最常“踩坑”的网络核心概念和协议。我们的目标很明确:帮你建立起一个清晰、实用的网络知识图谱,让你不仅能从容应对面试官的“灵魂拷问”,更能将这些知识内化为解决实际工程问题的能力。无论你是正在准备跳槽的资深工程师,还是希望夯实基础的初级开发者,这份整理都将是你技术栈中一块坚实的拼图。
2. 网络协议栈核心:从OSI七层到TCP/IP四层
谈到网络,避不开协议栈模型。教科书上常讲OSI七层模型,它理论完备,层次清晰,是理解网络通信的绝佳框架。但在实际的互联网和软件开发中,我们打交道更多的是它的实践版本——TCP/IP四层模型。理解两者的对应关系和侧重点,是构建网络知识体系的第一步。
2.1 OSI七层模型:理想的理论框架
OSI模型像一个精密的蓝图,定义了网络通信所需的所有功能,并将它们分层隔离。从上到下依次是:
- 应用层:为应用程序提供网络服务接口,比如HTTP、FTP、SMTP协议。你写的Java程序,通过HttpClient发送请求,就是在这一层工作。
- 表示层:负责数据格式转换、加密解密。比如将Java对象序列化为JSON或Protocol Buffers格式,或者进行SSL/TLS加密。
- 会话层:建立、管理和终止会话。在HTTP/1.1中,一个TCP连接上可以发送多个请求(Pipeline),这与会话管理有些关联,但在现代协议中,这一层功能常被融入应用层。
- 传输层:提供端到端的可靠或不可靠数据传输。核心协议就是TCP和UDP。这是Java程序员需要深入理解的一层,因为
Socket编程、连接池、端口等概念都在这层。 - 网络层:负责将数据包从源主机路由到目标主机,核心协议是IP。我们常说的IP地址、路由器就在这一层。
- 数据链路层:负责在相邻节点(如同一局域网内的两台机器)间可靠地传输数据帧,比如以太网协议、MAC地址。
- 物理层:定义物理设备标准,如网线、光纤、光猫,负责比特流的传输。
注意:OSI模型是一个参考模型,实际中很少有协议严格遵循全部七层。它的价值在于为我们分析和设计网络系统提供了一个通用的思维框架。
2.2 TCP/IP四层模型:互联网的实践标准
TCP/IP模型更贴近互联网的实际运作,它被广泛实现和使用。它常被表述为四层:
- 应用层:对应OSI的应用层、表示层和会话层。HTTP、HTTPS、DNS、WebSocket等协议都在这里。
- 传输层:与OSI传输层对应,核心是TCP和UDP。
- 网络层:与OSI网络层对应,核心是IP协议(包括IPv4和IPv6),以及ICMP(ping命令用的)、ARP等辅助协议。
- 网络接口层:对应OSI的数据链路层和物理层,负责处理与物理网络的接口。
为什么Java开发者要关注这个分层?因为你的代码和问题定位,就分布在这些不同的层次上。当你用HttpURLConnection发起一个HTTPS请求时:
- 你在应用层使用了HTTP/HTTPS协议。
HttpURLConnection底层会通过传输层的TCP协议建立一个到服务器的可靠连接。- TCP的数据段会被封装上网络层的IP头,形成IP数据包,通过路由寻址。
- 最终,数据包被交给网络接口层,转换成电信号或光信号发送出去。
当出现“连接超时”时,你需要判断是应用层服务器未响应(HTTP 504),还是传输层TCP连接建立失败,或者是网络层路由不可达。分层思想帮你快速缩小排查范围。
3. 传输层双雄:TCP与UDP的深度解析
传输层是网络编程的基石,TCP和UDP则是这块基石上最重要的两种材料。选择哪一个,直接决定了你应用程序的通信特性。
3.1 TCP:可靠的、面向连接的“快递服务”
你可以把TCP想象成一家提供“保价、签收、物流跟踪”的快递公司。它的核心目标是可靠传输。为了实现这个目标,TCP机制复杂但精妙:
三次握手建立连接这是面试超高频考点。为什么是三次,不是两次或四次?
- 客户端发送SYN:客户端发送一个SYN(同步)包(seq=x)到服务器,进入
SYN_SENT状态。意思是:“你好,我想和你建立连接,我的初始序列号是x。” - 服务器回复SYN-ACK:服务器收到SYN,如果同意连接,则回复一个SYN-ACK包(ack=x+1, seq=y)。这个包有两层含义:ACK是对客户端SYN的确认(“你的x我收到了”);SYN是服务器发起的同步(“我也想和你连,我的初始序列号是y”)。服务器进入
SYN_RCVD状态。 - 客户端发送ACK:客户端收到SYN-ACK后,再发送一个ACK包(ack=y+1)给服务器。意思是:“你的y我也收到了,连接建立完成。” 服务器收到后,双方进入
ESTABLISHED状态,连接建立。
为什么非要三次?主要是为了防止已失效的连接请求报文突然又传到了服务器,导致服务器错误打开连接。假设只有两次握手:客户端发送一个SYN请求,但这个请求在网络中滞留了(失效了)。客户端超时未收到回复,于是重发一个SYN并成功建立连接。结束后,那个滞留的SYN终于到达服务器,服务器以为是新的请求,直接回复SYN-ACK并打开连接,但客户端此时已不关心这个连接,导致服务器资源被白白占用。三次握手的情况下,服务器需要收到客户端的最终ACK才确认连接,而客户端对于那个失效的请求不会发出ACK,因此服务器在发出SYN-ACK后,如果收不到ACK,会超时关闭这个半连接,避免了资源浪费。
四次挥手断开连接断开连接需要四次,因为TCP连接是全双工的,每一方向都需要单独关闭。
- 主动方发送FIN:主动关闭方(比如客户端)发送FIN包,进入
FIN_WAIT_1状态,表示“我这边没有数据要发了”。 - 被动方回复ACK:被动方收到FIN,回复一个ACK,进入
CLOSE_WAIT状态。此时,从主动方到被动方的通道关闭,但被动方可能还有数据要发送。 - 被动方发送FIN:被动方数据发送完毕后,发送自己的FIN包,进入
LAST_ACK状态。 - 主动方回复ACK:主动方收到FIN,回复ACK,进入
TIME_WAIT状态。等待2MSL(Maximum Segment Lifetime,报文最大生存时间,通常为2分钟)后,连接彻底关闭。
TIME_WAIT状态为什么需要等待2MSL?这是另一个高频问题。主要有两个原因:
- 可靠地终止连接:确保主动方最后的ACK能到达被动方。如果这个ACK丢失,被动方会重发FIN,主动方在2MSL时间内还能收到并重发ACK。
- 让旧连接的报文在网络中消逝:防止之前连接的延迟报文段被误认为是新连接的报文。2MSL时间足以让这个方向上的报文最多存活MSL后被丢弃,反方向上的报文也最多存活MSL后被丢弃。
流量控制与滑动窗口TCP使用滑动窗口机制进行流量控制,防止发送方发送过快导致接收方缓冲区溢出。接收方在ACK中会通告自己的“接收窗口大小”,发送方发送的数据量不能超过这个窗口。在Java的NIO编程中,SocketChannel的读写缓冲区就与此密切相关。当网络延迟高或接收方处理慢时,窗口会变小,甚至为零(Zero Window),发送方会暂停发送。
拥塞控制这是TCP最复杂的部分之一,目的是避免网络过载。它通过“慢启动”、“拥塞避免”、“快速重传”、“快速恢复”等算法动态调整发送速率。核心是维护一个“拥塞窗口”。在微服务调用中,如果网络出现波动,TCP的拥塞控制机制会自动调整,这有时会导致调用延迟的突然增加,了解其原理有助于区分是应用问题还是网络问题。
3.2 UDP:不可靠的、无连接的“广播通知”
UDP则像寄明信片:写上地址内容就寄出,不保证对方一定能收到,也不保证按顺序到达。它简单、高效。
- 无连接:无需握手,直接发送。
- 不可靠:不保证交付、不保证顺序、不进行流量和拥塞控制。
- 头部开销小:仅8字节,而TCP头部至少20字节。
UDP的用武之地不要因为“不可靠”而小看UDP。在对实时性要求极高、可容忍少量丢包的场景下,UDP是首选:
- 音视频直播/通话:如WebRTC,几帧画面的丢失用户不易察觉,但TCP的重传机制会导致卡顿。
- DNS查询:请求响应模式简单快速,一次失败可以立即重试。
- 物联网传感器数据:海量设备上报数据,丢几个读数可能不影响整体分析,但需要低功耗和低延迟。
- 广播/多播:如DHCP、某些服务发现协议。
实操心得:TCP vs UDP的选择在项目中如何选?记住一个原则:默认用TCP,除非你有充分理由用UDP。TCP的可靠性为你省去了无数重传、乱序处理的麻烦。只有当你的应用场景符合以下所有条件时,才考虑UDP:1) 实时性要求高于可靠性;2) 应用层自己能处理丢包和乱序(比如通过时间戳、序号);3) 通信是单向或简单的请求-响应模式。例如,自己实现一个简单的服务健康检查心跳包,用UDP就比TCP更轻量。
4. 应用层协议:HTTP/HTTPS与WebSocket
应用层协议是Java开发者最直接打交道的部分,尤其是HTTP(S)。
4.1 HTTP/1.1, HTTP/2 与 HTTP/3 的演进
HTTP/1.1:目前仍广泛使用的版本。它引入了持久连接(默认Connection: keep-alive),允许在一个TCP连接上发送多个请求-响应,减少了握手开销。但它的问题是“队头阻塞”:同一个连接上的请求必须串行处理,如果前一个请求处理慢,会阻塞后面的请求。虽然浏览器可以通过开启多个TCP连接(通常6-8个)来并行下载资源,但这增加了服务器负担和握手开销。
HTTP/2:主要解决HTTP/1.1的性能问题。核心特性:
- 二进制分帧:将报文分解为二进制帧,突破纯文本解析的限制,更高效。
- 多路复用:在一个TCP连接上,可以同时交错发送多个请求和响应的帧,彻底解决了队头阻塞。
- 头部压缩:使用HPACK算法压缩头部,减少冗余。
- 服务器推送:服务器可以主动向客户端推送资源。
HTTP/3:革命性的变化是将底层传输协议从TCP换成了基于UDP的QUIC协议。QUIC在用户空间实现,集成了TLS 1.3,将握手过程从2-3个RTT减少到0-1个RTT,连接建立更快。更重要的是,它解决了传输层队头阻塞:由于QUIC基于UDP,每个流(Stream)独立处理,一个流的丢包不会影响其他流,而TCP下一个包的丢失会阻塞该连接上所有后续数据。
对Java后端的影响Spring Boot 2.x+ 内嵌的Tomcat/Jetty/Undertow都支持HTTP/2。要启用它,通常需要配置SSL证书,因为浏览器对HTTP/2的明文连接支持有限。对于内部微服务调用,如果双方都是Java服务且版本较新,可以考虑启用HTTP/2以获得更好的连接利用率和性能。而HTTP/3的支持还在逐步完善中,需要额外的库(如quiche)和更现代的JDK版本。
4.2 HTTPS:在HTTP之上构筑安全通道
HTTPS = HTTP + SSL/TLS。TLS是SSL的后续版本,现在主要用TLS。它的核心目标是机密性、完整性和身份认证。
- 机密性:通过对称加密算法(如AES)加密传输数据,密钥通过非对称加密(如RSA、ECDHE)在握手阶段安全协商。
- 完整性:通过消息认证码(MAC)防止数据在传输中被篡改。
- 身份认证:通过数字证书验证服务器(有时也包括客户端)的身份,防止中间人攻击。
TLS握手简化过程:
- 客户端发送“Client Hello”,包含支持的TLS版本、加密套件、随机数等。
- 服务器回复“Server Hello”,选择加密套件,发送自己的随机数和数字证书。
- 客户端验证证书(是否可信、是否过期、域名是否匹配等)。验证通过后,用证书中的公钥加密一个“预主密钥”发给服务器。
- 服务器用私钥解密得到预主密钥。至此,双方都有了客户端随机数、服务器随机数和预主密钥,可以独立计算出相同的会话密钥(对称加密密钥)。
- 后续通信使用会话密钥加密。
Java中的HTTPS实践在Java中,使用HttpsURLConnection或Apache HttpClient、OkHttp等库可以方便地发起HTTPS请求。关键点在于证书管理:
- 服务器证书:如果你的服务需要对外提供HTTPS,需要向CA(证书颁发机构)申请证书,或者在测试环境使用自签名证书。Spring Boot中可以在
application.properties中配置server.ssl.*相关属性。 - 客户端信任库:当Java客户端(如一个微服务)调用一个HTTPS服务端时,客户端需要信任服务端的证书。如果证书是公开CA签发的,JVM默认的信任库(
cacerts)通常已经包含。如果是自签名证书,你需要将服务端的证书导入到客户端的信任库,或者配置HTTP客户端跳过证书验证(仅限测试环境)。
# 示例:将自签名证书导入Java信任库 keytool -import -alias myserver -file server.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit重要警告:在生产环境中,绝对不要使用
TrustAllStrategy或禁用主机名验证等方法来绕过证书检查,这会完全破坏HTTPS的安全性,使系统暴露在中间人攻击之下。
4.3 WebSocket:双向实时通信的利器
HTTP是无状态的请求-响应协议,服务器不能主动推送消息给客户端。WebSocket协议在HTTP握手基础上建立了一个全双工的持久连接,实现了服务器和客户端的双向实时通信。
握手过程:客户端发起一个特殊的HTTP请求,头部包含Upgrade: websocket和Sec-WebSocket-Key。服务器返回101 Switching Protocols响应,并计算Sec-WebSocket-Accept进行校验。成功后,连接协议就从HTTP升级为WebSocket。
Java中的实现在Java后端,你可以使用:
- 原生API:JSR 356定义的
javax.websocketAPI。 - Spring框架:
@ServerEndpoint注解或更强大的Spring WebSocket模块,它集成了STOMP消息协议,非常适合与消息中间件(如RabbitMQ)和前端框架(如Stomp.js)配合,构建复杂的实时通知、聊天应用。 - Netty:如果需要极高的性能和自定义协议,Netty提供了强大的WebSocket编解码器支持。
应用场景:实时聊天、协同编辑、股票行情推送、在线游戏、监控仪表盘数据实时更新等。在选择WebSocket前,也可以考虑SSE(Server-Sent Events,服务器推送事件),它基于HTTP,只能服务器向客户端单向推送,但实现更简单。
5. IO模型与Java网络编程演进
理解了协议,我们还要知道Java程序是如何使用这些协议进行通信的。这涉及到IO模型,它的演进直接关系到服务端的并发处理能力。
5.1 阻塞IO(BIO):最直观的模型
Java最早的java.net.Socket和ServerSocket就是阻塞IO的典型。当你在一个线程中调用Socket.read()方法时,这个线程会一直阻塞,直到有数据可读。对于服务端,通常采用“一个连接一个线程”的模式。
// 经典的BIO服务器伪代码 ServerSocket serverSocket = new ServerSocket(8080); while (true) { Socket clientSocket = serverSocket.accept(); // 阻塞等待连接 new Thread(() -> { // 处理客户端请求 InputStream in = clientSocket.getInputStream(); // read()会阻塞 // ... }).start(); }缺点:线程是宝贵的系统资源。当连接数成千上万时,创建大量线程会导致巨大的内存消耗和线程上下文切换开销,系统性能急剧下降。这种模型只适用于连接数较少且固定的场景。
5.2 非阻塞IO(NIO)与多路复用器
Java NIO(New IO)在JDK 1.4引入,核心是通道(Channel)、缓冲区(Buffer)和选择器(Selector)。
- Channel:类似于流,但可以异步读写。
ServerSocketChannel用于监听连接,SocketChannel用于数据传输。 - Buffer:一个数据容器,所有读写都通过Buffer进行。
- Selector:一个多路复用器。一个线程可以管理多个Channel,通过Selector轮询哪些Channel已经就绪(可读、可写、有连接接入),然后进行相应的IO操作。
// NIO服务器模式伪代码 Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); // 设置为非阻塞 serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册连接事件 while (true) { selector.select(); // 阻塞,直到有事件发生 Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectedKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); if (key.isAcceptable()) { // 处理新连接 SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读事件 SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); // ... 处理数据 } iter.remove(); } }优势:用一个或少量线程即可处理大量连接,极大地提升了系统的可伸缩性。Netty、Tomcat NIO Connector等高性能网络框架都基于此模型。
难点:编程模型复杂,需要自己处理拆包粘包、缓冲区管理、事件分发等,对开发者要求高。
5.3 异步IO(AIO):理想化的未来
JDK 1.7引入了AIO(Asynchronous IO),提供了真正的异步操作。你发起一个IO请求(如read)后立即返回,操作系统完成IO操作后会回调你指定的处理器。
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(); server.bind(new InetSocketAddress(8080)); server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(AsynchronousSocketChannel client, Void attachment) { // 连接建立成功后的回调 ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { // 数据读取完成后的回调 } @Override public void failed(Throwable exc, ByteBuffer buffer) { // 读取失败 } }); } @Override public void failed(Throwable exc, Void attachment) { // 连接接受失败 } });现状:尽管AIO模型更先进,但它在Linux上的实现底层仍使用了epoll,并未真正利用Linux的AIO系统调用,性能优势不明显,且API相对复杂。因此,在业界,基于NIO的Netty框架成为了事实上的标准,而不是JDK原生AIO。
5.4 Netty:高性能网络应用的基石
Netty是一个基于NIO的客户端-服务器框架,它极大地简化了TCP/UDP套接字服务器和网络应用的开发。它不仅仅是封装了NIO的API,更重要的是提供了一套优雅的、事件驱动的编程模型。
核心组件:
- EventLoopGroup:事件循环组,包含多个EventLoop。每个EventLoop像一个工人,不断处理分配给它的Channel上的IO事件。通常,服务端会有两个Group:
bossGroup负责接受连接,workerGroup负责处理IO。 - Channel:网络连接的抽象,提供了绑定、连接、读写等操作。
- ChannelPipeline和ChannelHandler:这是Netty的精髓。Pipeline可以看作是一个处理流水线,数据(ByteBuf)像流水一样经过一个个Handler。Handler分为入站(处理读)和出站(处理写),你可以自由添加编解码器、业务逻辑处理器等。
- ByteBuf:Netty自己实现的字节缓冲区,相比JDK的ByteBuffer,它提供了更丰富的API(如读写索引分离、池化、复合缓冲区),性能更高。
为什么选择Netty?
- 高性能:精心设计的线程模型、零拷贝、内存池等技术,使其吞吐量和延迟表现极佳。
- 易用性:虽然底层复杂,但上层API封装良好,处理拆包粘包(通过
LengthFieldBasedFrameDecoder等)、心跳、重连等常见功能都有现成的Handler。 - 健壮性:经历了大规模互联网应用(如Dubbo、RocketMQ、Elasticsearch的传输层)的验证,功能稳定。
- 社区活跃:文档丰富,遇到问题容易找到解决方案。
实操心得:Netty中的拆包粘包这是网络编程的经典问题。TCP是流式协议,没有消息边界。发送方连续发送的多个数据包,在接收方缓冲区可能被粘成一个包;一个大数据包也可能被拆成多个接收。Netty提供了多种解码器来解决:
- 固定长度解码器
FixedLengthFrameDecoder:每个消息长度固定。 - 行分隔解码器
LineBasedFrameDecoder:以换行符\n或\r\n为分隔。 - 分隔符解码器
DelimiterBasedFrameDecoder:自定义分隔符。 - 长度字段解码器
LengthFieldBasedFrameDecoder:最常用、最灵活。在消息头中定义一个字段来表示消息体的长度。这是自定义二进制协议的首选。
6. 网络排查与性能优化实战
懂原理是为了解决问题。当线上出现网络相关问题时,如何快速定位和解决?
6.1 常用网络诊断工具链
- ping & traceroute:检查网络连通性和路由路径。
ping基于ICMP协议,测试主机是否可达及延迟。traceroute(Windows是tracert)可以显示数据包到达目标主机经过的每一跳路由。 - telnet & nc:测试TCP端口连通性。
telnet host port或nc -zv host port。这是判断防火墙规则或服务是否监听的最快方法。 - netstat & ss:查看网络连接、路由表、接口统计等信息。
netstat -tunlp查看所有TCP/UDP监听端口和对应进程。ss命令是netstat的现代替代,速度更快。 - lsof:列出进程打开的文件,包括网络连接。
lsof -i:8080查看谁在占用8080端口。 - tcpdump & Wireshark:网络抓包分析的黄金组合。
tcpdump是命令行工具,可以在服务器上抓取原始数据包。Wireshark是图形化工具,提供强大的协议分析和过滤功能。当你需要深入分析HTTP请求内容、TLS握手细节、TCP重传等问题时,它们必不可少。# 抓取指定网卡、主机和端口的数据包,并写入文件 tcpdump -i eth0 host 192.168.1.100 and port 8080 -w capture.pcap - curl & postman:HTTP客户端工具,用于手动测试API接口。
curl -v可以显示详细的请求和响应头,对于调试HTTPS、认证等问题很有帮助。
6.2 Java应用层网络问题排查
连接超时:
java.net.ConnectException: Connection timed out- 可能原因:目标服务器防火墙阻止、服务未启动、网络路由问题。
- 排查:先用
telnet测试端口通不通。检查服务器防火墙(如iptables)、安全组规则。检查服务进程是否存活。
读取超时:
java.net.SocketTimeoutException: Read timed out- 可能原因:服务器处理时间过长,超过了客户端设置的
readTimeout。 - 排查:检查服务器端业务逻辑是否有慢查询、死锁或Full GC。适当增加客户端超时时间(但需评估业务影响),并优化服务器性能。
- 可能原因:服务器处理时间过长,超过了客户端设置的
连接被拒绝:
java.net.ConnectException: Connection refused- 可能原因:目标端口没有进程在监听。
- 排查:确认服务是否已正确启动并在指定端口监听(
netstat -tunlp | grep port)。
Too many open files:系统打开文件数(包括Socket连接)达到上限。
- 可能原因:连接未正确关闭,导致文件描述符泄漏。
- 排查:使用
lsof -p <pid>查看进程打开的文件。检查代码中是否在所有分支都正确关闭了Socket、InputStream/OutputStream。建议使用try-with-resources语句。调整系统级和进程级的文件描述符限制(ulimit -n)。
ESTABLISHED连接数过高:
netstat看到大量ESTABLISHED状态的连接。- 可能原因:连接池配置过大,或连接未复用(如HTTP/1.1未启用keep-alive)。
- 排查:检查HTTP客户端(如OkHttp、Apache HttpClient)的连接池配置。确保使用了连接池并设置了合理的最大连接数和存活时间。
6.3 高性能网络编程优化要点
连接池化:对于频繁的短连接(如数据库、HTTP调用),创建和销毁TCP连接的成本很高。务必使用连接池。配置时需关注:最大连接数、最小空闲连接、连接最大存活时间、空闲连接回收策略。设置不当会导致连接泄漏或池子失效。
合理设置超时时间:这是线上稳定的生命线。至少设置三种超时:
- 连接超时:建立TCP连接的最长等待时间。
- 读取超时:从连接建立成功到收到响应数据的最大间隔。
- 写入超时:发送请求数据的最大等待时间(并非所有客户端都支持)。 超时时间应根据依赖服务的SLA(服务等级协议)来设定,并留有冗余。避免设置成0(无限等待)。
背压与限流:服务调用方要有熔断、降级和限流机制(如使用Resilience4j、Sentinel)。当被调用方网络延迟增高或失败时,调用方应快速失败,避免线程池被拖垮,导致级联故障。
序列化优化:网络传输的数据大小直接影响延迟和带宽。JSON可读性好但体积大,Protocol Buffers、Thrift、Avro等二进制序列化协议体积小、性能高,适合内部微服务通信。选择合适的序列化工具。
内核参数调优:对于高并发服务器,可能需要调整Linux内核参数,例如:
net.core.somaxconn:TCP连接等待队列的最大长度,高并发下需要调大。net.ipv4.tcp_tw_reuse&net.ipv4.tcp_tw_recycle:关于TIME_WAIT状态连接的复用,需谨慎设置,在某些NAT环境下可能有问题,新版内核已废弃tcp_tw_recycle。net.ipv4.tcp_fin_timeout:减少FIN_WAIT_2状态的等待时间。 这些调优需要结合压测进行,不能盲目修改。
网络知识浩如烟海,但作为Java开发者,抓住协议核心、理解编程模型、掌握排查工具,就能解决工作中绝大多数问题。面试官问网络,归根结底是想考察你对系统通信底层的理解深度和解决实际问题的能力。把每一次线上网络问题的排查都当作一次学习机会,你的知识图谱自然会越来越牢固。最后,记住一个朴素的道理:网络永远是不可靠的,你的代码必须对此有充分的容错设计。