分布式服务框架可能会运行在非常恶劣的网络环境中,网络超时、闪断、对方进程僵死或者处理缓慢等情况都有可能发生。为了保证在这些极端异常场景下服务仍能够正常调用或者自动恢复,需要底层的协议栈支持HA。
5.3.1 客户端连接超时
在传统的同步阻塞编程模式下,客户端Socket发起网络连接,往往需要指定连接超时时间,这样做的目的主要有两个:
- 在同步阻塞I/O模型中,连接操作是同步阻塞的,如果不设置超时时间,客户端I/O线程可能会被长时间阻塞,这会导致系统可用I/O线程数的减少。
- 业务层需要:大多数系统都会对业务流程执行时间有限制,例如web交互类的响应时间要小于3s。客户端设置连接超时时间是为了实现业务层的超时。
对NIO的SocketChannel,在非阻塞模式下,它会直接返回连接结果,如果没有连接成功,也没有发生IO异常,则需要将SocketChannel注册到Selector上监听连接结果。所有,异步连接的超时无法在API层面直接设置,而是需要通过用户自定义定时器来主动监测。
5.3.2 客户端重连机制
客户端通过链路关闭监听器监听链路状态,如果链路中断,等待INTERVAL时间后,由客户端发起重连操作,如果重连失败,间隔周期INTERVAL后再次发起重连,知道重连成功。
为了保证服务端能够有充足的时间释放句柄资源,在首次断连时,客户端需要等待INTERVAL时间之后再次发起重连,而不是失败后立即重连。
为了保证句柄资源能够及时释放,无论什么场景下的重连失败,客户端都必须保证自身的资源被及时释放,包括但不限于SocketChannel、Socket等。
重连失败后,需要打印异常堆栈信息,方便后续的问题定位。为了防止服务端正常下线长期不再上线,客户端通常会对重连次数做限制,防止无限重连下午无谓的损耗资源。
5.3.3 客户端重复握手保护
当客户端握手成功之后,在链路处于正常状态下,不允许客户端重复握手,以防止客户端在异常状态下反复重连导致句柄资源被耗尽。
服务端接收到客户端的握手请求消息之后,首先对IP地址进行合法性检验,如果校验成功,在缓存的地址表中查看客户端是否已经登录,如果已经登录,则拒绝重复登录,返回错误码-1,同时关闭TCP链路,并在服务端的日志中打印握手失败的原因。
客户端接收到握手失败的应答消息之后,关闭客户端的TCP连接,等待INTERVAL时间之后,再次发起TCP连接,直到认证成功。
为了防止由服务端和客户端对链路状态理解不一致导致的客户端无法握手成功的问题,当服务端连续N次心跳超时之后需要主动关闭链路,清空该客户端的地址缓存信息,以保证后续该客户端可以重连成功,防止被重复登录保护机制拒绝掉。
5.3.4 消息缓存重发
无论客户端还是服务端,当发生链路中断之后,在链路恢复之前,缓存在消息队列中待发送的消息不能丢失,等链路恢复之后,重新发送这些消息,保证链路中断期间消息不丢失。
考虑到内存溢出的风险,建议消息缓存队列设置上限,当达到上限之后,应该拒绝继续想该队列添加新的消息。
5.3.5 心跳机制
在凌晨等业务低谷期时段,如果发生网络闪断、连接被Hang住等网络问题时,由于没有业务消息,应用进程很难发现。到了白天业务高峰期时,会发生大量的网络通信失败,严重的回导致一段时间进程内无法处理业务消息。为了解决这个问题,在网络空闲时采用心跳机制来检测链路的互通性,一旦发生网络故障,立即关闭链路,主动重连。
网友评论