`
lokki
  • 浏览: 58948 次
  • 来自: ...
社区版块
存档分类
最新评论

RPC框架中request submit后,request received和timeout、长连接LifeCycle

    博客分类:
  • java
阅读更多

我写代码还算比较细致的,经常用肉眼和模拟细节的运行情况,对于不通的问题也严于律己,找到解决方案,或 say no

进程里,只要存在这种跨线程的两步模式“发送 --> 接收到”,就可能会出现接收不到,而发送方也不能感知的情况,特别是通过网络进行的RPC框架。
RPC肯定是跨了线程的,在“应用 至 rpc框架”这个集成点,如果要实现可靠的发送方感知,那得实现应用层的“三路握手”,即必须要有“request received”的反馈包。而即使实现了这种反馈包,在同一进程的3线程协同上也得处理很复杂,3线程是:request线程、正常response线程、“request received”反馈包线程。

同时,即使有“request received”反馈包,在业务处理上都可能出现处理超时的情况。

情景:“request submitted”后的下一行代码,submit方,他关注的是“我下一步要怎么做,才能让我的工作/下一步是正确的”,由于处理超时的情况,是不可能避免的,所以为了能让submit方做好他的工作,异步(跨线程)时,一定要能够返回“超时”信息。而对于submit方细分“网络超时”和“业务处理超时”,如果提供了“网络超时”给submit方,肯定是错的,画蛇添足的。因为你无法保证“只有”你脑里想像到的“网络超时”“业务处理超时”两种状况。

对于应用sumbit后的这行代码,RPC框架方能确保的是,有业务response,无业务response。没,就返回(抛)TimeoutException。

所以,跨了线程,做“request received”反馈包是多余并大大增加了复杂性的事情。

从“RPC 至 submit应用方,返回结果”这个方向上,要么是业务respone,要么是timeout。

那在应用sumbit后的这行代码,RPC框架要能让应用方处理,就得提供TimeoutException类,让应用方catch。如果是显式RPC request,那TimeoutException可为CheckException。如果是隐式,TimeoutException是绝对要为RuntimeException的。

因为对于一行调用,如果声明上没这个CheckException,而应用方又想写下catch这异常的代码,是编译通不过的。

所以必须是RuntimeException时,用文档交代清楚即可。

对于CheckException和RuntimeException,少出现的东西,应该用RuntimeException异常。并且使用方随时可以在任何地方都能写下catch RuntimeException的代码,能预先写好,也很好,不能预先考虑到,但发现情况时再加上,也能的。关于RuntimeException的说明,从“是编译通不过的”起,以上已基本将观点/原因介绍完毕了,不再写新blog了。“编译通不过”,java通过这个透露了他的一些思想/思维。

很多人比较纠结,“不想用RuntimeException”,觉得这是对使用方“交代得不够清楚”。不过,代码是机器执行,不是人执行,不是“你”钻进CPU里执行,你担保不了。不要纠结于"我显式抛了RuntimeException,那别人不知道,没catch,那线程终结了,怎么办",你无意的写错代码的NPE,这个也是你造成的错误,也是无法预先告知的。能做到“让别人知道”,就很不错了。

你能"担保对"的是,“我提供了RuntimeException型的TimeoutException,你考虑是否要catch并处理”。能担保的是“没抛TimeoutException时,那业务肯定被执行了”,做这个担保时,不说这句话也没关系:“如果request所在的进程/线程如果没有被kill”,这句话说多说少改变不了事实。

如果你根本没能力担保,但又拍下胸膛,这就不对了。

我做过一个RPC,开始时没把TimeoutException这个类暴露出来,这是我的错,想清晰后待再搞新的RPC,就不会再错了。

长连接RPC的stopping lifecycle:
引起以上思考,主要是进程stop时,长连结RPC服务端要怎么细致细节处理。
正确的原则还是:先关入口,然后等待内部已经在运行中的,处理完。

正确的处理/顺序总结如下
(1)先将listen端口 停掉/unbind。

(2)shutdown boss/worker线程池,这样就不会再处理新业务了,如果有网络包(完整接收完了的)进来而没有线程处理,会有(3)中“本连接停止服务包”迅速反馈了回去。
而IO boss/worker的线程是可以为daemon,但业务处理的线程是不能为daemon的。
这里只是shutdown线程池而已,不是在这步close socket。

(3)给各个长连接发“本连接停止服务包”,让客户端给此connection打上个标记,并从connection list holder中释放(去掉)此connection instance,这样client clustering 路由选址时就不再选中此连接。

这条获得的思考结果是“本连接停止服务”或者“服务器端连接池已满”这种技术消息,应该是RPC Framework的client处接收到并再路由选址,而不是立刻就抛给“request submitted”应用方。以前的RPC由于没有这个消息,所以有点不好。

(4)unregister ResourceLocator上的“服务活着记录”。
ResourceLocator,很多同义词的,比如一些人叫name service、WS上叫register和discovery、zookeeper、address service、tracker等等。

如果有(1)(2)(3)做好的前提下,这里和ResourceLocator的处理超时,问题已经不大了。这个也可以从(1)起,就并行进行。

(5)服务器端等待各个进行中的业务线程处理完后(判断ThreadPoolExecutor是否终止完毕),close 各长连接。这一步是否超时,已不重要,你也没办法。
但无论怎样,另一个地方:client和server端对于socket的异常是要处理精细并且不出现交叉错误的。“交叉错误”的意思时,有时候为了做好这个要求,结果这个要求是做好了,但产生了bug,让另一个去承受。

selector wakeup和close socket的细节,看着办。

(6)在客户端的易用性可靠性措施方面,很多RPC是当发现一个服务IP不可用时,是重新选址几次(比如3次)试图建立长连接,如果还是没办法把request送出去,就抛“找不到服务地址”这个异常给应用方,让应用方自己处理。这样和应用方交互的“找不到服务地址”这个消息,也是明确的:你的请求还没被业务处理。

“找不到服务地址”这个异常也肯定是RuntimeException,并且要暴露class给应用方catch。

从以上总结下来,长连接里的包只需要:心跳包、“本连接停止服务包”和正常response包即可。

在request submited交互处,RPC framework要抛给应用方的消息是:“TimeoutException”和“找不到服务地址”。

只限于RPC framework的server端和client端的技术交互消息是:心跳包、“本连接停止服务包”。
心跳包可以从client处来发,接收端完全不需要处理,而是让操作系统早点发现此socket有问题,发出socket异常消息给RPC Framework。心跳包的作用是让client发出的消息及早发现网络错误,重新选址,迅速点把这个request消费出去,不堵在client端。其实从server端发出心跳包没有很大作用,server端一发出正常repsone,就发现网络错误了,socket异常处理,会处理好的。

这里仅说正常停止的处理过程,对于异常格式的处理,这里说的少,得以后再说。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics