无需麻醉即可在生产中修补Java代码

图片

在这里,我将讨论为Odnoklassniki项目开发各种服务的众多工具之一。 在公司内部,我们将其称为“热代码替换”(HCR),此工具旨在修复正在运行的生产服务中的关键且简单的错误,而不会停止它们。 这是一项非常重要的功能,因为它可以避免布局垃圾服务的新版本(校正后的版本)所带来的无聊且耗时的过程,避免每个主机的可用性出现足够长的停顿,并避免刷新缓存。

通常,它可以节省大量时间,并将从检测到错误到纠正的间隔从数小时缩短到数分钟。 通常,按照计划,代码中的小错误是固定的,例如,程序员忘记检查是否为空,并且对于某些用户,站点上的某些操作会导致错误。 即,当通过在方法内改变几行来执行校正时。 而且,由于进行了这样的微小更改,您不再需要分散同事的注意力并等待数小时的生产时间。

例如:

图片
您可以轻松地将其修复:

图片
当然,您可以进行更多更改,同时添加新的类,快速进行经理同时要求的更改,而无需等待下一次更新。 但是,如果Monsieur对变态了解很多,这已经是事实。

此外,可以将“贴片”彼此并置为无穷大。

但是此工具不是万能的,它基于Java类提供的标准功能: java.lang.instrument.Instrumentation及其方法void redefineClasses(ClassDefinition ...定义)

Instrumentation.redefineClasses用新的字节码替换以前加载的类。 您可以同时重载几个具有不同依赖关系的类。 重载不会更改现有的类实例,不会更改继承,也不会涉及实例或类字段。 您只能更改方法的主体,常量池和属性池。 您可以添加新的类或子类。 方法签名,实例字段和类字段不能更改。 如果您尝试进行不兼容的更改,则redefineClasses原则上将不起作用,并会引发错误。 必须记住,当类被重载时,代码的重载部分的执行不会被中断,下次调用同一方法时将使用新的字节码。 因此,如果您尝试修复内部循环无限长的方法的代码,则仅在此循环结束后才进行实际替换。

如果很简单:您只能在方法和要点内部更改代码。

这是一个while循环的示例,该循环在方法完成之前是固定的。

图片

主要的困难是要使一种工具能够在Odnoklassniki生态系统中工作,并且该工具适合所有已建立的工作流程。 它可以与数百个主机上的所有服务持续透明地交互,并且灵活且易于使用。 该工具应应对在生产中连续发生的数十个实验,工作和更新。

开发人员/管理员试图修复生产中的错误时,安装补丁的过程看起来如何,但是可以通过在数十台服务器上使用一些标准且可靠的过程来完成。 我们省略了查找和修复代码中错误的过程。

1.在GIT中创建了一个单独的早午餐来进行代码修复。 使用版本控制非常重要,不仅因为方便,而且对于后续可能的研究。

2. TeamCity启动补丁程序构建过程。 首先,从指定的早午餐创建项目程序集,然后将新程序集与生产中安装的程序集进行比较。 为此,我为构建工具编写了一个插件,该插件从存档中提取所有文件,比较差异并仅选择已更改或添加的文件。 在这种情况下,两个程序集中的Java编译器版本应相同,因为 编译器的另一个版本将创建不同的文件,并且几乎所有项目文件都将包含在补丁中。 仅创建一个小的归档文件非常重要,因为只有该文件才能获取必要的文件,因为 这将大大加快将补丁发布到数十台服务器的过程。 构建过程不仅适用于项目代码的修补程序,而且还可以替换项目中的修补程序库。 比较两个程序集的内容时,会在库(jar文件)中发现差异。

3.如果成功组装,则将补丁发送到特殊的存储库,并在结果窗口中发布密钥(或哈希),这是唯一标识补丁的必要条件,并且可以保证此代码可以用于生产。

图片

好吧,这又是一次-您可以无限制地打补丁,并且具有相同版本号的构建会因哈希而有所不同。

4.接下来,所有活动都将转移到配置服务,在该服务中,您可以在通常的UI中指定需要修补的服务,主机和版本的应用程序。

图片

如此丰富的参数为设置提供了必要的灵活性,这在具有许多服务器的大型动物园中非常重要。 假设在某些服务器上,应用程序的版本号是不同的,并且您根本不需要修补此代码。 或者,为了进行验证,Hot Code Rreplace首先在一个服务器或一组服务器上启动,然后分布在应用程序的所有实例中。

5.通过配置更改,选定的服务会收到有关需要安装修补程序,其版本和验证哈希的信息。 想法是所有服务都接收命令“安装补丁”,然后独立运行。 他们独立比较自己的版本,只有版本匹配并且补丁的哈希值缺失或不同时,他们才从存储库中独立下载补丁程序集。 下载过程本身是通过HTTP进行的,您可以快速更改存储库地址,下载尝试次数和重试之间的等待时间。

6.每个应用程序在本地检查程序集的哈希并解压缩。 同时,在Instrumentation.getAllLoadedClasses()返回的文件中检查每个文件是否存在于数组中,所有新的类和文件都写入一个新的类-临时类路径,并通过Instrumentation.appendToSystemClassLoaderSearch()添加该类路径,并将现有类读入内存并通过redefineClasses方法。

7.整个过程:有关需要修补应用程序,下载,验证,解压缩和应用程序的信号的到达详细记录在应用程序的常规日志中或应用程序自身的日志中,以便您可以快速而无需不必要的手势来监视该过程。

8.成功应用补丁后,该过程结束,方法是通过添加包含补丁哈希的特殊组成的行,将应用程序版本更改为补丁版本。 如果某些主机的版本未更改为预期版本,则转到该主机的“热代码替换”日志,然后查看发生了什么。 如果这些是通信问题,则可以安全地重复执行patch命令,然后所需的主机将重试。

哪些可能的问题可能会阻止应用程序打补丁? 其中有很多,其中最后一个是Instrumentation类的功能。 到目前为止,JVM一直刷新不满足redefineClasses严格条件的弯曲代码,而不会对应用程序造成任何后果。 当应用redefineClasses方法时,JVM会完全停止应用程序,但是此过程耗时一秒钟。 因为这一点都不可怕。

最危险的时刻是将修补程序交付到服务器,这是由其他重新培训决定的。 但是,如果重新扫描没有帮助,则可以重复执行该命令以调用补丁程序,并且每个主机都将尝试重复该过程,但仅在必要时安装补丁程序,即 以前尚未安装该修补程序,或者哈希键已更改。

另一个潜在的问题是,该修复程序修复了一个错误并添加了一个新错误。 为了最大程度地降低这种风险,我们首先将补丁程序上载到有限数量的服务器上,查看日志,图表并监视结果。 只有这样,我们才能对其他主机进行更正。

重新启动应用程序或服务器怎么办? 这已经嵌入到所有同学应用程序的逻辑中:HCR模块是所有应用程序中第一个应用程序。 并且如果在初始化期间注意到有关需要修补应用程序的信息,则将首先应用修补程序。

现在介绍一下热代码替换的内容。

  1. 我们的JavaAgent。 JavaAgent( 如果有人忘记了) ,这是一个单独的特殊格式的* .jar归档文件,当应用程序使用附加参数启动时,JVM会拾取该文件。例如:-javaagent:/path/to/lib/my-agent.jar由于Javaagent-的附加功能并可以使用代码替换魔术。 在代理中,java.lang.instrument.Instrumentation类可用。 但是,我没有用额外的代码阻塞他(代理),因为 代理程序更新是一项艰巨的任务,只需将Instrumentation类的实例移至Utility类的static字段即可。 因此,所有操作都可以从应用程序中的任何位置启动。
  2. 配置服务-负责我们任何应用程序的配置,因此首先在每个应用程序中初始化。 此处隐藏了“热代码替换”的主要功能。 在应用程序启动时或更改特定应用程序的HCR配置时,将检查版本兼容性并执行所有上述操作。
  3. TeamCity和构建脚本-方便地创建“补丁”,并仅在其中保存修改或添加的类和资源。

我们使用此工具有什么优势? 首先是纠正产品中严重错误的速度。 从日志中,我看到同事逐渐开始越来越频繁地使用HCR,而不是等待发布。 接下来是应用程序的速度。 无需停止应用程序,JVM只需冻结一秒钟,所有对象都将保留在原处并继续工作。

我们的开发人员可以自由,愉快地进行恢复,并且可以在生产中立即独立地直接纠正错误,而无需考虑服务器数量和负载。

Source: https://habr.com/ru/post/zh-CN429040/


All Articles