[size=medium]
我写代码还算比较细致的,经常用肉眼和模拟细节的运行情况,对于不通的问题也严于律己,找到解决方案,或 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。
所以,跨了线程,[color=red]做“request received”反馈包是多余并大大增加了复杂性的事情。[/color]
从“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,这个也是你造成的错误,也是无法预先告知的。能做到“让别人知道”,就很不错了。
你能"担保对"的是,[color=red]“我提供了RuntimeException型的TimeoutException,你考虑是否要catch并处理”[/color]。能担保的是[color=red]“没抛TimeoutException时,那业务肯定被执行了”[/color],做这个担保时,不说这句话也没关系:“如果request所在的进程/线程如果没有被kill”,这句话说多说少改变不了事实。
如果你根本没能力担保,但又拍下胸膛,这就不对了。
我做过一个RPC,开始时没把TimeoutException这个类暴露出来,这是我的错,想清晰后待再搞新的RPC,就不会再错了。
长连接RPC的stopping lifecycle:
引起以上思考,主要是进程stop时,长连结RPC服务端要怎么细致细节处理。
正确的原则还是:[color=red]先关入口,然后等待内部已经在运行中的,处理完。[/color]
[color=blue]正确的处理/顺序总结如下[/color]:
(1)先将listen端口 停掉/unbind。
(2)shutdown boss/worker线程池,这样就不会再处理新业务了,如果有网络包(完整接收完了的)进来而没有线程处理,会有(3)中“本连接停止服务包”迅速反馈了回去。
而IO boss/worker的线程是可以为daemon,但业务处理的线程是不能为daemon的。
这里只是shutdown线程池而已,不是在这步close socket。
(3)[color=red]给各个长连接发“本连接停止服务包”[/color],让客户端给此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送出去,就抛[color=red]“找不到服务地址”[/color]这个异常给应用方,让应用方自己处理。这样和应用方交互的“找不到服务地址”这个消息,也是明确的:你的请求还没被业务处理。
“找不到服务地址”这个异常也肯定是RuntimeException,并且要暴露class给应用方catch。
从以上总结下来,长连接里的包只需要:心跳包、“本连接停止服务包”和正常response包即可。
在request submited交互处,RPC framework要抛给应用方的消息是:“TimeoutException”和“找不到服务地址”。
只限于RPC framework的server端和client端的技术交互消息是:心跳包、“本连接停止服务包”。
[color=red]心跳包可以从client处来发,接收端完全不需要处理,而是让操作系统早点发现此socket有问题,发出socket异常消息给RPC Framework[/color]。心跳包的作用是让client发出的消息及早发现网络错误,重新选址,迅速点把这个request消费出去,[color=red]不堵在client端[/color]。其实从server端发出心跳包没有很大作用,server端一发出正常repsone,就发现网络错误了,socket异常处理,会处理好的。
这里仅说正常停止的处理过程,对于异常格式的处理,这里说的少,得以后再说。
[/size]
我写代码还算比较细致的,经常用肉眼和模拟细节的运行情况,对于不通的问题也严于律己,找到解决方案,或 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。
所以,跨了线程,[color=red]做“request received”反馈包是多余并大大增加了复杂性的事情。[/color]
从“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,这个也是你造成的错误,也是无法预先告知的。能做到“让别人知道”,就很不错了。
你能"担保对"的是,[color=red]“我提供了RuntimeException型的TimeoutException,你考虑是否要catch并处理”[/color]。能担保的是[color=red]“没抛TimeoutException时,那业务肯定被执行了”[/color],做这个担保时,不说这句话也没关系:“如果request所在的进程/线程如果没有被kill”,这句话说多说少改变不了事实。
如果你根本没能力担保,但又拍下胸膛,这就不对了。
我做过一个RPC,开始时没把TimeoutException这个类暴露出来,这是我的错,想清晰后待再搞新的RPC,就不会再错了。
长连接RPC的stopping lifecycle:
引起以上思考,主要是进程stop时,长连结RPC服务端要怎么细致细节处理。
正确的原则还是:[color=red]先关入口,然后等待内部已经在运行中的,处理完。[/color]
[color=blue]正确的处理/顺序总结如下[/color]:
(1)先将listen端口 停掉/unbind。
(2)shutdown boss/worker线程池,这样就不会再处理新业务了,如果有网络包(完整接收完了的)进来而没有线程处理,会有(3)中“本连接停止服务包”迅速反馈了回去。
而IO boss/worker的线程是可以为daemon,但业务处理的线程是不能为daemon的。
这里只是shutdown线程池而已,不是在这步close socket。
(3)[color=red]给各个长连接发“本连接停止服务包”[/color],让客户端给此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送出去,就抛[color=red]“找不到服务地址”[/color]这个异常给应用方,让应用方自己处理。这样和应用方交互的“找不到服务地址”这个消息,也是明确的:你的请求还没被业务处理。
“找不到服务地址”这个异常也肯定是RuntimeException,并且要暴露class给应用方catch。
从以上总结下来,长连接里的包只需要:心跳包、“本连接停止服务包”和正常response包即可。
在request submited交互处,RPC framework要抛给应用方的消息是:“TimeoutException”和“找不到服务地址”。
只限于RPC framework的server端和client端的技术交互消息是:心跳包、“本连接停止服务包”。
[color=red]心跳包可以从client处来发,接收端完全不需要处理,而是让操作系统早点发现此socket有问题,发出socket异常消息给RPC Framework[/color]。心跳包的作用是让client发出的消息及早发现网络错误,重新选址,迅速点把这个request消费出去,[color=red]不堵在client端[/color]。其实从server端发出心跳包没有很大作用,server端一发出正常repsone,就发现网络错误了,socket异常处理,会处理好的。
这里仅说正常停止的处理过程,对于异常格式的处理,这里说的少,得以后再说。
[/size]