草庐IT

《RPC实战与核心原理》学习笔记Day11

技术修行者 2023-04-16 原文

13 | 优雅关闭:如何避免服务停机带来的业务损失?

我们在RPC架构下,需要考虑当服务重启时,如何做到让调用方系统不出问题。

当服务提供方要上线时,一般是通过部署系统完成实例重启,在这个过程汇总,服务提供方不会事先告诉调用方哪些实例会被重启,从而让调用方切换流量。而对调用方来说,它也无法预测服务提供方哪些实例会重启,因此负载均衡还是有可能降正在重启的实例挑选出来,这样导致请求被分发到正在重启的服务实例中,造成调用方无法拿到正确的响应结果。

在服务重启的时候,对于调用方来说,有以下2种情况:

  1. 调用方发请求前,目标服务已经下线。对于调用方来说,跟目标节点的连接会断开,这时调用可以立刻感知到,并在其健康列表中将该实例删除,这样就不会被负载均衡选中。
  2. 调用方发请求时,目标服务正在关闭,但调用方并不知道它正在关闭,而且两者之间的连接没有断开,所以这个节点还会存在健康列表里面,有可能会被负载均衡选中。

我们可以通过服务发现来实时通知服务调用方关于服务提供方是否可用吗?

不可以。这样做的话,整个过程会依赖两次RPC调用:一次是服务提供方通知注册中心下线操作,一次是注册中心通知服务调用方下线节点操作。注册中心通知服务调用方都是异步的,服务发现只保证最终一致性,并不保证实时性,所以当注册中心收到服务提供方下线的时候,并不能保证把这次要下线的节点推送给所有调用方,这样,调用方还是有可能将请求发送给错误的服务提供方节点。

如何做到优雅关闭服务?

我们可以尝试让服务提供方来通知调用方,RPC里面调用方和提供方之间是长连接,我们可以在提供方应用内存中维护一份调用方连接集合,当服务关闭时,挨个通知调用方去下线相关实例,这样整个调用链路就变短了,对于每个调用方来说只一次RPC,可以确保调用的成功率很高。

但是上述方法不能彻底解决问题,因为有时出问题请求的时间点和收到提供方关闭通知的时间点很接近,再加上网络延迟,还是有可能在服务提供方关闭服务后再接收到新的请求。

解决办法是我们在关闭的时候,在服务提供方设置一个请求“挡板”,它的作用是告诉调用方,我已经进入关闭流程,不能再处理新的请求了。

当服务提供方正在关闭,如果在之后还收到了新的业务请求,服务提供方直接返回一个特定的异常给调用方(ShutdownException),这个异常就是告诉调用方“我已经收到这个请求了,但是我正在关闭,没有处理这个请求”,然后调用方收到这个异常响应后,RPC框架就把这个节点从健康列表中挪出,并把请求自动重试到其他节点,因为这个请求没有被服务提供方处理过,所以可以安全的重试到其他节点,这样可以实现对业务无损。

我们还可以加上主动通知流程,让服务提供方给相关调用方发送关闭通知,这样既可以保证实时性,也可以避免通知失败的情况。

在Java语言中,我们可以使用Runtime.addShudownHook方法,来注册关闭的钩子,在RPC启动的时候,我们提前去注册关闭钩子,并在里面添加连个处理程序:一个复杂开启关闭标识,一个负责安全关闭服务对象,服务对象在关闭的时候会通知调用方下线节点。同时我们需要再调用链里面加上挡板处理器,当新的请求进来时,会判断关闭标识,如果正在关闭,就抛出特定异常。

对于关闭过程中还在处理的请求,我们可以根据引用计数器,等待正在处理的请求全部结束后再真正关闭服务,同时还可以设置一个超时控制,当超过指定时间,请求还没有处理完,就强制退出应用。

总结一下,关于如何优雅关闭服务,包括以下步骤:

  1. 开启关闭挡板,拒绝新的请求
  2. 利用引用计数器确保正在执行的请求处理完
  3. 设置超时时间,保证服务可以正常关闭
  4. 执行关闭时,服务提供方通知服务调用方下线相关节点

服务优雅关闭的示意图如下。

“优雅关闭”的概念除了在RPC里面,在其他很多框架中也很常见,例如Tomcat在关闭的时候,也是先从外层到里层逐层进行关闭,先保证不接收新的请求,然后再处理关闭前收到的请求。

14 | 优雅启动:如何避免流量达到没有启动完成的节点?

为什么Java程序运行一段时间会执行速度会变快?

这是因为在Java里面,在运行过程中,JVM虚拟机会把高频的代码编译成机器码,被加载过的类也会被缓存到JVM缓存中,再次使用的时候就不会触发临时加载,这样就使得
“热点”代码的执行不用每次都通过解释,从而提升执行速度。

什么是启动预热?

启动预热就是让刚启动的服务提供方应用不承担全部的流量,而是让它被调用的次数随着时间的移动慢慢增加,最终让流量缓和地增加到跟已经运行一段时间后的水平一样。

服务调用方应用通过服务发现能够取得服务提供方的IP地址,然后每次发送请求前,都需要通过负载均衡算法从连接池中选择一个可用连接,我们可以让负载均衡在选择连接的时候,区分一下是不是刚启动的应用,如果是刚启动的应用,我们可以调低它的权重值,这样它被选中的概率会很低,随着时间推移,我们逐渐增大它的权重值,从而实现一个动态增加流量的效果。

我们如何获取服务提供方应用的启动时间?有两种方法:

  1. 服务提供方在启动的时候,把自己启动的时间告诉注册中心。
  2. 注册中心收到的服务提供方请求注册的时间。

启动越热更多是从调用方的角度出发,去解决服务提供方应用冷启动的问题,让调用方的请求量通过一个时间窗口过渡,慢慢达到一个正常的水平,从而实现平滑上线。

从服务提供方的角度来说,有什么优化方案吗?服务提供方可以使用延迟暴露的方法来优化热启动过程。

问题:服务提供方应用在没有完成启动的时候,调用方的请求就过来了,而调用方请求过来的原因,在于服务提供方应用启动过程中把解析到的RPC服务注册到了注册中心,这就导致了后续加载没有完成的情况下,服务提供方地址就被服务调用方感知到了。

解决办法:我们在应用启动加载、解析Bean的时候,如果遇到了RPC服务的Bean,只先把这个Bean注册到Spring-BeanFactory里面,而不把这个Bean对应的接口注册到注册中心,只有等应用启动完成后,才被接口注册到注册中心用于服务发现,从而实现让服务调用方延迟获取到服务提供方地址。

我们还可以利用服务启动完成到注册到注册中心的那段时间,预留一个Hook,让用户可以扩展Hook逻辑,在Hook里面模拟业务调用逻辑,从而使得JVM指令能够预热起来,同时还可以在Hook中预先加载一些资源,只有等所有缓存和资源都加载完成后,才把接口注册到注册中心,这样也就完成了热启动整个流程。

如果我们有大批量的服务都需要重启,如何避免同时重启造成请求被分发到新启动的应用实例而造成超时错误?

我们可以采取一些措施:

  1. 分时分批启动,就像灰度发布一样。
  2. 根据重启比例来设置重启服务的权重。
  3. 在请求低峰重启应用。
  4. 在重启过程中,如有必要,对服务进行限流处理。

有关《RPC实战与核心原理》学习笔记Day11的更多相关文章

  1. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  6. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  7. ruby - ri 有空文件 – Ubuntu 11.10, Ruby 1.9 - 2

    我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da

  8. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  9. ruby-on-rails - rails : Find tasks that were created on a certain day? - 2

    我有一个任务列表(名称、starts_at),我试图在每日View中显示它们(就像iCal)。deftodays_tasks(day)Task.find(:all,:conditions=>["starts_atbetween?and?",day.beginning,day.ending]end我不知道如何将Time.now(例如“2009-04-1210:00:00”)动态转换为一天的开始(和结束),以便进行比较。 最佳答案 deftodays_tasks(now=Time.now)Task.find(:all,:conditio

  10. ruby - rails 3.2.2(或 3.2.1)+ Postgresql 9.1.3 + Ubuntu 11.10 连接错误 - 2

    我正在使用PostgreSQL9.1.3(x86_64-pc-linux-gnu上的PostgreSQL9.1.3,由gcc-4.6.real(Ubuntu/Linaro4.6.1-9ubuntu3)4.6.1,64位编译)和在ubuntu11.10上运行3.2.2或3.2.1。现在,我可以使用以下命令连接PostgreSQLsupostgres输入密码我可以看到postgres=#我将以下详细信息放在我的config/database.yml中并执行“railsdb”,它工作正常。开发:adapter:postgresqlencoding:utf8reconnect:falsedat

随机推荐