在一次RPC过程中(假设一个客户端fd与一个服务端fd原先已正常建立TCP长连接),客户端向服务器发送请求时可能会遇到如下各类异常:
-
服务器限流
服务器接收请求的队列的长度超出阈值,此时服务器不能再负责处理新到的请求,否则服务器可能压力过大而崩溃。
-
服务器正在退出
服务器正在退出服务,此时服务器进程虽然存活,但不能响应客户端的请求。
-
服务器进程正常关闭fd
服务器进程正常关闭fd包括三种情况:
-
服务器进程主动调用close关闭fd;
-
服务器进程主动调用exit;
-
在shell下kill或ctrl+c杀掉服务器进程。
以上三种情况都会正常关闭服务端fd,TCP连接上会将FIN发给客户端,客户端的epoll会通知客户端的fd可读,触发的事件是EPOLLIN和EPOLLRDHUP,客户端应用层对fd执行read调用的返回值是0。
-
-
机器断电导致的服务器进程异常关闭
服务器进程所在的机器可能在如下时刻断电:
-
客户端发送数据过程中服务器机器断电
这里的客户端发送数据指的是客户端机器内核TCP/IP协议栈向服务器发送数据,客户端应用层进程只是将数据写入fd的内核inode发送缓存,操作系统内核的TCP/IP协议栈负责将inode发送缓存里的数据真正发送到网络上。如果服务器机器断电而客户端没有任何动作,则客户端不会被告知对端异常,只有当客户端的TCP/IP协议栈执行发送数据的动作时,由于滑动窗口无法滑动、无法收到已发数据的Ack等原因,协议栈会感知到对端异常,epoll会触发EPOLLERR事件通知应用层TCP连接已异常断开(此处的逻辑需要进一步查阅Linux内核协议栈代码来证实)。
-
客户端已将数据发到网络上,服务器进程接到请求数据前机器断电
客户端在超时时间内无法接收到响应,走超时处理的逻辑。
-
服务器进程处理请求过程中断电
同上,客户端会判断超时。
-
-
网络断线
断线包括网线断开、客户端与服务器间的路由器故障等,发生的场景有:
-
客户端发送数据时网络断线
与发送数据时服务器断电的逻辑一致,内核在发送数据时检测到TCP连接异常,epoll触发EPOLLERR事件通知应用层TCP连接异常(需进一步查阅Linux内核协议栈代码证实)。
-
客户端发送请求成功,请求数据在网络上传输时断线,服务器未收到请求
客户端在超时时间内无法收到响应,走超时处理的逻辑。服务器内核协议栈没有读写动作,不会得知此时TCP连接异常。
-
客户端发送请求成功,服务器处理请求过程中网络断线
客户端在超时时间内无法收到响应,走超时处理的逻辑。服务器内核协议栈发送响应数据过程中检测到TCP连接异常。
-
客户端发送请求成功,服务器处理请求后发送响应成功,响应数据在网络上传输时断线,客户端未收到响应
客户端走超时处理的逻辑。服务器内核协议栈接下来没有读写动作,不会得知此时TCP连接异常。
-
由于断线,服务器可能只接收到请求数据的一个片段,或者客户端只接收到响应数据的一个片段
按照非法数据处理。
-
综上,可以总结出RPC客户端需要面对的异常状况有:
-
服务器虽存活但不能处理请求
-
超时时间内没有接收到响应
-
服务器被正常关闭
-
因服务器机器断电或网络断线导致的TCP连接异常
下面阐述brpc是如何处理发送RPC请求过程中可能出现的上述4种异常的。
-
服务器虽存活但不能处理请求
如果服务器由于软件层面的限制不能处理请求,会发回给客户端一个响应,响应包含错误码,服务器接收队列过大的错误码为ELIMIT,服务器正在退出的错误码为ELOGOFF,服务器已停止服务的错误码为EHOSTDOWN,客户端收到这几类错误码后,如果是通过连接池与多台服务器连接,会重新将请求发往一台新的服务器,这就是一次重试。如果服务器返给客户端的带有错误码的response由于TCP连接异常等原因丢失,则只能按请求超时的逻辑处理。
重试的请求也可能遇到服务器不能处理的情况,会产生下一次重试,重试不能无限产生,必须限定一次RPC过程的最大重试次数,只有下面三个条件同时满足时,才允许进行重试:
-
未到RPC超时时间
-
有剩余的重试次数
-
服务器返回的错误码允许重试,或者TCP连接出现异常时也可以重试
-
-
超时时间内没有接收到响应
brpc规定每一次RPC都有一个超时时间,超时时间是在Channel对象中设置,通过此Channel的所有RPC的超时时间是一样的,填充在每个RPC对应的Controller对象中。在超时时间内可以进行若干次重试发送,但只要过了超时时间还没收到想要的响应,RPC就会结束,就算重试产生的响应在超时时间过后马上到来也会被忽略。
-
服务器被正常关闭
客户端对一个fd的read操作返回0后,该fd不再可用,关闭fd,客户端应将后续的请求发给连接池中的其他TCP连接。如果fd上有bthread因等待服务器的Response而被挂起,则这些bthread需要被唤醒,唤醒后去执行RPC重试的逻辑。
-
因服务器机器断电或网络断线导致的TCP连接异常
当一个TCP连接出现异常后,epoll会在对应的fd上触发EPOLLERR事件,brpc会立即将fd所属的Socket对象置为不可用,如果之前已经有bthread通过此Socket对象发送了数据、正在等待服务器Response而被挂起,则这些bthread需要被唤醒,唤醒后去执行RPC重试的逻辑。