手写RPC框架--13.优雅停机

优雅停机

  • 优雅停机
    • a.优雅停机概述
    • b.服务端实现优雅停机
    • c.客户端实现优雅停机
    • d.优雅启动

优雅停机

a.优雅停机概述

当我们快速关闭服务提供方时,注册中心感知、以及通过watcher机制通知调用方一定不能做到实时,一定会有延时,同时我们的心跳检测也会有一定的时间间隔。也就意味着当一个提供方实际上已经下线了,但是他依然在调用方的健康列表中,调用方依然认为他健康依然会给他发送消息,最后的结果就是超时等待,不断重试而已。所以如何在服务下线时快速的让调用方感知,很重要。

大概可以有以下几种解决方案:

1.通过控制台人工通知调用方,让他们手动摘除要下线的机器,这种方式很原始也很直接。但这样对于提供方上线的过程来说太繁琐了,每次上线都要通知到所有调用我接口的团队,整个过程既浪费时间又没有意义,显然不能被正常接受。

2.通过服务发现机制感知,这种方式我们探讨过,因为存在一定的时间差,所以会出现一定的问题。

3.不强依赖“服务发现”来通知调用方要下线的机器,由服务提供方自己来通知行不行。在rpc里面调用方跟服务提供方之间是长连接,我们可以在提供方应用内存里面维护一份调用方连接集合,当服务要关闭的时候,挨个去通知调用方去下线这台机器。

在这里插入图片描述

实时上第三种方式已经很好了,但是依旧会出现一些问题 ,如请求的时间点跟收到服务提供方关闭通知的时间点很接近,只比关闭通知的时间早不到1ms,如果再加上网络传输时间的话,那服务提供方收到请求的时候,它应该正在处理关闭逻辑。这就说明服务提供方关闭的时候,并没有正确处理关闭后接收到的新请求。

优雅停机方案:

因为服务提供方已经开始进入关闭流程,那么很多对象就可能已经被销毁了,关闭后再收到的请求按照正常业务请求来处理,肯定没法保证能处理的。所以我们可以在关闭的时候,设置一个请求”挡板”,挡板的作用就是告诉调用方,我已经开始进入关闭流程了,我不能再处理你这个请求了。

这种场景在生活中其实很常见,举一个例子:

银行办理业务,在交接班或者有其他要事情处理的时候,银行柜台工作人员会拿出一个纸板,放在窗口前,上面写到“该窗口已关闭”。在该窗口排队的人虽然有一万个不愿,也只能换到其它窗口办理业务,因为柜台工作人员会把当前正在办理的业务处理完后正式关闭窗口。

基于这个思路,我们可以这么处理:

1.调用方发起请求,给调用方一个特殊的响应,使用响应码标记即可,就是告诉调用方我已经收到这个请求了,但是我正在关闭,并没有处理这个请求。

2.调用方收到这个异常响应后,rpc框架把这个节点从健康列表挪出,并把请求自动重试到其他节点,因为这个请求是没有被服务提供方处理过,所以可以安全地重试至其他节点,这样就可以实现对业务无损

**问题一:**那要怎么捕获到关闭事件呢?

以通过捕获操作系统的进程信号来获取,在java 语言里面,可以使用Runtime的addShutdownHook方法,可以注册关闭的钩子。在yrpc启动的时候,我们提前注册关闭钩子,并在里面添加处理程序,负责开启关闭标识和安全关闭服务,服务在关闭的时候会通知调用方下线节点。同时需要在我们调用链里面加上挡板处理器,当新的请求来的时候,会判断关闭标识,如果正在关闭,则抛出特定异常,返回特定结果。

以下是测试用例:

public static void main(String[] args) {Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("程序正在关闭");try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("合法请求已经被处理完成");}));while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("正在处理请求");}}

在这里插入图片描述

b.服务端实现优雅停机

修改core模块下的DcyRpcBootstrap启动类的start()类:

  • 优先注册一个应用程序的钩子
public void start() {// 优先注册应用程序钩子Runtime.getRuntime().addShutdownHook(new DcyRpcShutdownHook());// 略...
}

在core模块下创建shutdownHook包下

在该包下创建ShutdownHolder类:

  • 标记请求挡板
  • 请求的计数器 可以用 LongAdder 或 AtomicInteger
public class ShutdownHolder {// 标记请求挡板public static AtomicBoolean BAFFLE = new AtomicBoolean(false);// 请求的计数器public static LongAdder REQUEST_COUNTER = new LongAdder(0);
}

在该包下创建DcyRpcShutdownHook类:

  • 继承 Thread
public class DcyRpcShutdownHook extends Thread{@Overridepublic void run() {// 1.打开挡板(boolean需要线程安全)ShutdownHolder.BAFFLE.set(true);// 2.等待计数器归零(正常的请求处理结束)//  - 等待归零,继续执行 最多等十秒long start = System.currentTimeMillis();while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}if (ShutdownHolder.REQUEST_COUNTER.sum() == 0L && System.currentTimeMillis() - start > 10000) {break;}}// 3.阻塞结束后,放行。执行其他猜中,如释放资源}
}

修改core模块下的channelHandler.handler包下的MethodCallHandler类的channelRead0()方法

  • 1.先封装响应

  • 2.获得通道

  • 3.查看挡板是否打开: 如果已打开,返回一个错误的响应

  • 4.计数器加一

  • 5.限流操作

  • 6.处理限流

  • 7.写出响应

  • 8.计数器减一

@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DcyRpcRequest dcyRpcRequest) throws Exception {// 1.封装响应DcyRpcResponse dcyRpcResponse = DcyRpcResponse.builder().requestId(dcyRpcRequest.getRequestId()).compressType(dcyRpcRequest.getCompressType()).serializeType(dcyRpcRequest.getSerializeType()).build();// 2.获得通道Channel channel = channelHandlerContext.channel();// 3.查看挡板是否打开: 如果已打开,返回一个错误的响应if (ShutdownHolder.BAFFLE.get()) {dcyRpcResponse.setCode(ResponseCode.CLOSING.getCode());channel.writeAndFlush(dcyRpcResponse);return;}// 4.计数器加一ShutdownHolder.REQUEST_COUNTER.increment();// 略...// 8.计数器减一ShutdownHolder.REQUEST_COUNTER.decrement();}

c.客户端实现优雅停机

修改core模块下的channelHandler.handler包下的MySimpleChannelInboundHandler类:channelRead0 方法

@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DcyRpcResponse dcyRpcResponse) throws Exception {// 略.....else if (code == ResponseCode.CLOSING.getCode()) {completableFuture.complete(null);log.info("当前请求id【{}】,访问被拒绝,目标服务器正处于关闭状态,响应码【{}】", dcyRpcResponse.getRequestId(), code);// 修正负载均衡器// 从健康列表中移除DcyRpcBootstrap.CHANNEL_CACHE.remove(socketAddress);DcyRpcRequest dcyRpcRequest = DcyRpcBootstrap.REQUEST_THREAD_LOCAL.get();DcyRpcBootstrap.getInstance().getConfiguration().getLoadBalancer().reLoadBalance(dcyRpcRequest.getRequestPayload().getInterfaceName(), DcyRpcBootstrap.CHANNEL_CACHE.keySet().stream().toList());}
}

d.优雅启动

这就好比我们日常生活中的热车,行驶之前让发动机空跑一会,

可以让汽车的各个部件都“热”起来,减小磨损。换到应用上来看,原理也是一样的。运行了一段时间后的应用,执行速度会比刚启动的应用更快。这是因为在 Java里面,在运行过程中,JVM 虚拟机会把高频的代码编译成机器码,被加载过的类也会被缓存到 JVM 缓存中,再次使用的时候不会触发临时加载,这样就使得“热点”代码的执行不用每次都通过解释,从而提升执行速度。

但是这些“临时数据”,都在我们应用重启后就消失了。重启后的这些“红利”没有了之后,如果让我们刚启动的应用就承担像停机前一样的流量,这会使应用在启动之初就处于高负载状态,从而导致调用方过来的请求可能出现大面积超时,进而对线上业务产生损害行为。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/76909.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何把视频格式转换成mp4?支持的格式种类非常多。

如何把视频格式转换成mp4?随着计算机技术的迅猛发展,我们现在有着各种各样的视频格式可供选择,平时我们都知道的mp4、flv、mov、mkv、avi、wmv等,都是视频格式的种类。其中,MP4是一种具有极佳兼容性的视频格式&#xf…

TikTok魔法:揭秘那个“神奇”的算法

嘿,你是不是每次打开TikTok,都感觉这个应用好像了解你的内心世界一样?没错,背后有一个不为人知、神奇的算法正在起作用,让你欲罢不能。在这篇文章中,我们将揭开TikTok算法的神秘面纱,看看它是如…

车机多用户系统的适配问题

多用户问题出现背景 记录一下多用户的适配问题: 背景是system/app下面新push了两个apk,一个是我们的业务场景apk一个是虚拟车CarService服务的apk,我们的apk需要链接CarService服务通过AIDL通信。 下面这两张图是未roo的情况(当…

Python之Xlwings操作excel

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、xlwings简介二、安装与使用1.安装2.使用3.xlwings结构说明 二、xlwings对App常见的操作App基础操作工作簿的基础操作工作表的基础操作工作表其他操作 读取单元格…

MOV导出序列帧并在Unity中播放

MOV导出序列帧并在Unity中播放 前言项目将MOV变成序列帧使用TexturePacker打成一个图集将Json格式精灵表转换为tpsheet格式精灵表导入Unity并播放总结 鸣谢 前言 收集到一批还不错的MG动画,想要在Unity中当特效播放出来,那首先就得把MOV变成序列帧&…

堆排序与TopK问题

一、堆排序 堆排序(升序):堆排序的思想就是先用数组模拟建大堆,然后把根结点与最后一个结点值交换,最后一个结点的值就是最大值,然后再把前(n-1)个元素重新建大堆,然后根结点与最后一个结点值交换,就找出了…

小红书笔记爬虫

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据…

LNMP架构搭建论坛

目录 一、LNMP简介: 二、LNMP搭建: 1.前提准备: 关闭防火墙和安全机制: 2.编译安装nginx: 3.编译安装mysql: 3.1 安装依赖环境: 3.2 创建mysql运行用户: 3.3 编译安装&#xff1a…

c语言练习题52:写一个函数判断当前机器是大端还是小端

代码&#xff1a; #include<stdio.h> int check_sys() {int a 1;return *(char*)&a;//小端retrun 1 大端return 0&#xff1b; } int main() {if (check_sys() 1) {printf("小端\n");}elseprintf("大端\n"); } 这里首先取a的地址&#xff0c…

原型链(一定要搞懂啊!!!>-<)

一、概念 1、prototype 习惯称作“显示原型”&#xff0c;只有构造函数才有的属性。 2、构造函数 能用new关键字创建的对象叫做构造函数 3、__proto__ 习惯称作“隐式原型”&#xff0c;每一个实例都有的属性&#xff0c;该属性指向他构造函数的“显示原型”。Function对象…

2.14 PE结构:地址之间的转换

在可执行文件PE文件结构中&#xff0c;通常我们需要用到地址转换相关知识&#xff0c;PE文件针对地址的规范有三种&#xff0c;其中就包括了VA&#xff0c;RVA&#xff0c;FOA三种&#xff0c;这三种该地址之间的灵活转换也是非常有用的&#xff0c;本节将介绍这些地址范围如何…

Mac端交互式原型设计 Axure RP 8 for Mac汉化

Axure RP 8是一款专业的交互原型设计工具&#xff0c;它被广泛应用于用户体验设计、界面设计和产品原型制作等领域。该软件提供了丰富的功能和工具&#xff0c;使用户能够创建出具有高度交互性和可视化效果的原型。 Axure RP 8的主要特点和功能包括&#xff1a; 1. 快速原型&a…

产教融合 | 力软联合重庆科技学院开展低代码应用开发培训

近日&#xff0c;力软与重庆科技学院联合推出了为期两周的低代码应用开发培训课程&#xff0c;来自重庆科技学院相关专业的近百名师生参加了此次培训。 融合研学与实践&#xff0c;方能成为当代数字英才。本次培训全程采用线下模式&#xff0c;以“力软低代码平台”为软件开发…

光谱通用款积分球

随着惯性约束聚变&#xff08;ICF&#xff09;物理理论的不断发展以及精密物理实验要求的不断提高&#xff0c;激光驱动器的光束路数急剧增多&#xff0c;光路长度和元器件数目成倍增长。模块化是新一代激光驱动器的发展趋势。对于高功率激光多参数测量系统&#xff0c;模块化设…

《DevOps实践指南》- 读书笔记(五)

DevOps实践指南 Part 4 第二步 &#xff1a;反馈的技术实践14. 建立能发现并解决问题的遥测系统14.1 建设集中式监控架构14.2 建立生产环境的应用程序日志遥测14.3 使用遥测指导问题的解决14.4 将建立生产遥测融入日常工作14.5 建立自助访问的遥测和信息辐射器14.6 发现和填补遥…

【视觉SLAM入门】7.4.后端优化 --- 基于位姿图和基于因子图

"议论平恕&#xff0c;无所向背” 1. 位姿图1.1 具体做法1.2 小结 2. 因子图2.1 具体做法2.1.1 贝叶斯网络2.1.2 因子图2.1.3 更具体的因子图2.1.4 增量的求解方法 引入&#xff1a; 上节BA将位姿和路标都作为优化的节点&#xff0c;H矩阵也告诉我们路标远大于位姿&#…

Python绘图系统16:动态更新tkinter组件

文章目录 前情提要源代码模式输入序列源码 Python绘图系统&#xff1a; &#x1f4c8;从0开始的3D绘图系统&#x1f4c9;一套3D坐标&#xff0c;多个函数&#x1f4ca;散点图、极坐标和子图自定义控件&#xff1a;极坐标&#x1f4c9;绘图风格&#x1f4c9;风格控件图表类型和…

rsync远程同步+inotify监控

目录 一、Rsync 简介 1、rsync是什么 2、备份的方式 3、rsync同步方式 4、常用rsync命令 5、配置源的两种表达方法 二、rsync实验 1、本地复制 ​编辑​编辑 2、异地复制 2.1 rsync服务器配置 2.2 rsync客户端配置 2.2.1 普通同步 2.2.2 免密同步 2.2.3 --delet…

EmguCV-C#版本Opencv图像识别和处理

目录 0、简介 1、图像处理 &#xff08;1&#xff09;颜色处理 &#xff08;2&#xff09;图像差 &#xff08;3&#xff09;图像拼接 &#xff08;4&#xff09;直方图 &#xff08;5&#xff09;颜色空间/通道提取 2、预处理 &#xff08;1&#xff09;均衡化 &…

idea2021.1.3版本双击启动,没反应

今天打开电脑&#xff0c;点开idea&#xff0c;界面悬在这里&#xff0c;几秒然后就是没了。然后就一直打不开idea了。 然后又是卸载重装&#xff0c;又是删除缓存文件。我把电脑关于idea的文件全都删除了 。重新安装后&#xff08;首次运行倒是可以打开&#xff0c;但是关掉id…