本文站在可靠连接的场景下分析,不再保证可靠性连接做过多论述。
长连接技术主要用于维持客户端和服务器之间的持续通信,减少因频繁建立和断开连接带来的开销。
Connection: keep-alive
,使得多个HTTP请求/响应可以在一个TCP连接上连续传送,而不需要每次传输都建立新连接。 不同的方式有不同的场景,例如ChatGPT
就是采用SSE
来进行消息流推送的,又比如各种游戏都是使用UDP
建立数据连接,而很多RPC
框架底层是TCP
连接,现代浏览器提供了WebSocket
支持。
Connection: keep-alive
来告知服务器希望保持连接。对于
TCP
、Socket.IO
这类直接长久占据线程资源的长连接,无疑在连接数量过多的情况下会导致连接被打满,这就是为什么很多公司不愿意在很多业务场景去使用它,比如扫码登陆和扫码支付多半采用HTTP
短轮询请求,因为长连接在业务量大的情况下,架构维护的成本是直线上升的,需要进行扩散机制、集群扩容、数据一致性机制等等。
为什么需要消息可靠?这主要取决于实际的业务场景,对于数据准确性要求不高的场景,或许并不需要在这方面多花功夫,但是如果是类似即时通讯这类场景,那一致性是非常重要的,否则上下文丢失,不能衔接语意,最主要的是,通信最重要的就是要保证100%收到消息。
IM场景下,需要确保信息准确无误地从发送者传递到接收者。
在生活中,我们经常有快递需要寄取,那么收件方怎么知道快递到了?发件方怎么知道快递已经取了? 很明显,双方都会收到通知,那么在开发中也可以这样做。
如果某个请求没有收到对方的回执或者消息,那么我们不能一直去等待,计算机是很笨的,所以我们需要实现一个机制来兜底,保证最终的请求消费(成功)。 我们可以定义,如果一个消息,在n秒内没有收到消息,我们将它默认为对方没有收到消息,我们需要对他进行重新请求,即重试。 但是如果第二次也失败了怎么办??? 这取决于业务对请求保证的容忍性,如果允许丢掉部分请求,我们可以不再重试,因为对方很可能不在线,没必要发起重试了。 但是某些请求是必须送达的,我们需要保证数据最终一致性,那么此时可以设置多次重试机制,比如最多重试n次,x秒一次。 可是我们这样想,如果它只是暂时这段时间都不在线呢?那就没法保证短时间内重试成功,此时我们可以采用叠加延时重试机制,即1s、2s、4s、8s、16s....24h,设置最大的时间值,超出后不再重试,进行进一步兜底方案触发。
对于HTTP请求,我们通常可以收集该请求的很多信息然后做哈希值运算,比如IP、参数、请求类型、请求头....等等,利用这些数据计算出该请求的唯一哈希值并缓存,然后下一次请求我们进行一次哈希值对比。 为什么需要多个参数来哈希计算呢?因为哈希计算是有小概率重复问题的,如果我们的计算因子(数据)较多更能保证计算出了的哈希值的唯一性。 如果担心请求量过大缓存哈希值而带来的内存问题,我们可以进一步使用布隆过滤器来优化,这样空间使用效率会更高。
上面说的是服务端持久化,这个一般不用多说,是必然会做的。 如果是存在大量实时数据的传输,我们去采用客户端消息持久化,这样如果断开连接后,我们不需要消耗服务度的CPU和带宽资源来推送之前的数据包,这样重连机制也更加顺畅。 可以尝试这样实现:前端每隔一段时间就将当前数据压缩携带上当前时间节点并缓存到浏览器的持久化空间中,当某一时刻出现了网络抖动,需要重新连接并拉数据,此时不需要全部拉过来,而是拉缓存时间之后的数据,这样在客户端特别多的情况下可以缓解连接握手和首次数据推送的压力,不过具体还得考虑是否存在这样的需求。
事务消息通常指那些需要在事务上下文中处理的消息,这意味着它们需要完全成功处理,或在失败时进行回滚。 例如,在电子商务平台中,用户的支付和订单更新需要在同一事务中处理,以确保数据的一致性。 事务分为强一直性与最终一直性方案。
MQ
也存在消费重试机制,这都是去保证一个操作的最终一致性,而不是去回滚操作,这样对性能优化更加友好。异步、解耦、削峰、蓄洪。
UDP
封装协议已经解决了这个问题)。 MQ
可以应对实时消息传出量突增问题,一般是在IM中使用的较多。
如果需要对长连接进行负载均衡的话,那么这将是一件非常有挑战的问题,尤其是消息的分布式同步问题。
Nginx
或HAProxy
提供灵活配置和良好的性能,对于大多数应用已经足够。很多时候,负载均衡不会只在一层,而是多层次链路负载,比如:DNS选择最优地区的服务器—>最优的服务器Nginx—>最优的服务器网关—>最优的服务端。
对于分布式状态与数据同步共享问题,可以参考我之前的一篇:浅谈分布式环境下WebSocket消息共享问题
本文探讨了在长连接环境下确保消息可靠性和处理海量消息的策略和架构,包括消息确认机制、超时和重试策略、消息持久化以及顺序控制等。
同时,讨论了在大规模实时通信中面临的挑战,以及通过消息队列、负载均衡和水平扩展等手段来优化系统的方法。
对于优化措施,需要权衡硬件与网络的实际情况,没有最优的方案,只有最适合的方案,空间换时间、资源换时间,对于需要尽可能节省服务端资源的场景,通常就是在客户端做手脚了,例如QQ与微信,都是使用了SQL Lite
来做本地缓存,这样断网重连也只需要拉某一时间之后的数据,然后进行数据合并处理,极大的减少了传输消耗,但是带来的就是客户端的存储成本,可以查看一下QQ与微信这些软件的空间占用。
如果需要保证消息的绝对可靠,那就还是需要进行大量网络数据同步,QQ与微信如果清空本地空间,就会丢失许多聊天数据,当然,并不是说腾讯服务端不存储消息,而是不愿提供这种带宽成本。
综上所属,优化点如下:
同时,均存在对立面(格式为,方法->牺牲的资源):压缩->算力,切片->传输资源,流传输->时间,重试->业务复杂性,确认机制->请求负担,幂等->算力,一致性、顺序->速度,扩容->复杂性与资源,队列->复杂性与内存,负载->链路追踪与同步复杂性。