TCP 本身并没有长短连接的区别,长短与否,完全取决于我们怎么用它。
想要简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接,我们就需要担心各种问题:比如端对端连接的维护,连接的保活。
长连接还常常被用来做数据的推送,我们大多数时候对通信的认知还是 request/response 模型,但 TCP 双工通信的性质决定了它还可以被用来做双向通信,在长连接之下,可以很方便的实现 push 模型。
(1) 长连接服务主要功能是收发数据,保持在线,使用的系统资源主要包括:CPU,内存,网卡;
(2) 当连接活跃时,大量数据接收与发送,会用到更多的CPU和网卡,大量用户在线的时候,需要维持这些连接,保持会话,需要用到大量内存;
(1) 设计单台物理连接数100W的处理能力;
(2) CPU资源充分利用,线程的分配;
(3) 内存合理分配,数据结构选择;
(4) 异步化,剥离业务逻辑和网络IO之间的相互依赖
长连接意味着连接是复用的,每次请求完连接不关闭,下次请求继续使用该连接。
如果请求是串行的,那完全没有问题。但在并发场景下,所有请求都需要使用该连接,为了保证连接的状态正确,加锁不可避免,如果连接只有一个,就意味着所有请求都需要排队等待。
因此长连接通常意味着连接池的存在:连接池中将保留一定数量的连接不关闭,有请求时从池中取出可用的连接,请求结束将连接返回池中。
连接池经常遇到的一个问题就是池大小的控制:
通常的解决办法是延迟创建和设置空闲时间,延迟创建是指连接只在请求到来时才创建,空闲时间是指连接在一定时间内未被使用则将被主动关闭。
这样日常情况下连接池控制在较小的尺度,当并发请求量较大时会为新的请求创建新的连接,这些连接在请求完毕后返还连接池,其中的大部分会在闲置一定时间后被主动关闭,这样就做到了并发性能和IO资源之间较好的平衡。
为了充分利用CPU,需要合理的进行线程规划。整个长连接服务使用事件驱动,包括:定时器事件和IO事件(listen fd,socket fd, pipe fd), 所以线程规划就是合理给这些事件分配线程。
(1) 大量连接socket需要平均分配到各个线程;
(2) 新的连接请求量比较大,listen线程压力大,需要考虑多线程处理;
主要涉及到3种fd的线程分配:
(1) 监听fd,包括监听逻辑层的连接请求和监听客户端的连接请求,并且支持启动多个监听端口,每个端口多个线程同时工作,整体会按顺序分配到线程;
(2) 连接fd,包括逻辑层的连接和客户端的连接,采用fd取余线程数,保证平均分配到所有线程;
(3) pipe fd,负责本线程管道中数据的读取,每个线程一个;
这个样是的每个线程的CPU使用率相当。
长连接的第二个问题就是连接保活的问题。虽然TCP协议并没有限制一个连接可以保持多久,理论上只要不关闭连接,连接就一直存在。
但事实上由于NAT等网络设备的存在,一个连接即使没有主动关闭,它也不会一直存活。
长连接保活由于TCP自身的断开确认机制,如果一条TCP连接中间网络断开,此时客户端和服务端物理网络断开导致客户端和服务端都没有办法通知对方连接断开,这样服务端和客户端就会存在死连接,造成假在线,占用的资源得不到有效的回收,长连接保活主要有下面两种方式:
(1) TCP keepalive,通过设置Keepalive参数,TCP协议栈会在超过一定时间没有数据交互的时候,发送Keepalive探测包,如果连接几次都没有收到回包,则断开连接,优点是TCP协议栈提供的功能,稳定占用带宽较少;
(2) 采用应用层心跳包的方式,客户端定时向服务端发送心跳包,服务端收到心跳包后立即进行回包,客户端如果没有检测到回包,则断开连接;服务端检测超时还没有收到心跳包,则断开连接,优点主要是应用层有感知,可控并且可以带些业务数据,比如时间戳;
在TCP连接建立到通信的流程中,为了防止一些恶意连接与攻击,长连接服务做了容量控制,体现在下面几个方面:
(1) 客户端建立TCP连接到TLS握手,再到发出登录请求返回登录结果,整个过程是连贯的,如果客户端停在中间的某一步骤而不往下进行,就会一直占用服务端资源,针对这种情况,服务端增加了定时控制,在TCP 连接之后30s,如果没有收到登录请求,服务端会主动断开连接;
(2) 服务统计了正在进行TLS握手,正在进行登录校验的连接数量,分别设置上限,防止同时出现大量请求的时候,对后端服务的冲击;
(3) 增加了会话通信过程中Buffer内存使用量的上限,防止对端不接收数据,导致服务端数据积压;
(4) 增加了IP统计服务,建立连接后,会将新连接的IP等信息发送到IP统计服务,对整个服务的客户端IP 情况作统计监控,增加IP黑名单功能;