去哪儿系统高可用之法,深入分析JavaWeb技术内幕

2019-07-24 09:25栏目:科技传媒

return;

阿里巴巴在海量互联网服务以及历年双11场景的实践过程中,沉淀出了包括全链路压测、线上流量管控、故障演练等高可用核心技术,并通过开源和云上服务的形式对外输出,以帮助企业用户和开发者享受阿里巴巴的技术红利,提高开发效率,缩短业务的构建流程。

(1)需要使用自定义classloader的情况
  • <1>不在System.getProperty("java.class.path")中的类文件不可以被AppClassLoader找到(LoaderClass方法只会去classpath下加载特定类名的类),当class文件的字节码不在ClassPath就需要自定义classloader

  • <2>对加载的某些类需要作特殊处理

  • <3>定义类的实效机制,对已经修改的类重新加载,实现热部署

金沙国际官网 1

社区共建:

欢迎访问 ChaosBlade@GitHub,参与社区共建,包括但不限于:

  • 架构设计
  • 模块设计
  • 代码实现
  • Bug Fix
  • Demo样例
  • 文档、网站和翻译

本文作者:中亭

阅读原文

本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

(3)初始化class对象,执行静态初始化器并在这阶段末尾初始化静态字段为默认值

金沙国际官网 2

功能和特点

场景丰富度高

ChaosBlade 支持的混沌实验场景不仅覆盖基础资源,如 CPU 满载、磁盘 IO 高、网络延迟等,还包括运行在 JVM 上的应用实验场景,如 Dubbo 调用超时和调用异常、指定方法延迟或抛异常以及返回特定值等,同时涉及容器相关的实验,如杀容器、杀 Pod。后续会持续的增加实验场景。

使用简洁,易于理解

ChaosBlade 通过 CLI 方式执行,具有友好的命令提示功能,可以简单快速的上手使用。命令的书写遵循阿里巴巴集团内多年故障测试和演练实践抽象出的故障注入模型,层次清晰,易于阅读和理解,降低了混沌工程实施的门槛。

场景扩展方便

所有的 ChaosBlade 实验执行器同样遵循上述提到的故障注入模型,使实验场景模型统一,便于开发和维护。模型本身通俗易懂,学习成本低,可以依据模型方便快捷的扩展更多的混沌实验场景。

金沙国际官网 3

(1)ClassNotFoundException:

通常是jvm要加载一个文件的字节码到内存时,没有找到这些字节码(如forName,loadClass等方法)

金沙国际官网,以Dubbo为例说明下如何注入故障和解除故障:

ChaosBlade 是什么?

ChaosBlade 是一款遵循混沌工程实验原理,提供丰富故障场景实现,帮助分布式系统提升容错性和可恢复性的混沌工程工具,可实现底层故障的注入,特点是操作简洁、无侵入、扩展性强。

ChaosBlade 基于 Apache License v2.0 开源协议,目前有 chaosblade 和 chaosblade-exe-jvm 两个仓库。

chaosblade 包含 CLI 和使用 Golang 实现的基础资源、容器相关的混沌实验实施执行模块。chaosblade-exe-jvm 是对运行在 JVM 上的应用实施混沌实验的执行器。

ChaosBlade 社区后续还会添加 C 、Node.js 等其他语言的混沌实验执行器。

金沙国际官网 4

1.Classloader类结构分析

一、背景

高可用架构是保障服务稳定性的核心。

(3)UnsatisfiedLinkErrpr:

如native的方法找不到本机的lib

但是如何才能正确使用呢?如下图所示:

为什么要开源?

很多公司已经开始关注并探索混沌工程,渐渐成为测试系统高可用,构建对系统信息不可缺少的工具。但混沌工程领域目前还处于一个快速演进的阶段,最佳实践和工具框架没有统一标准。实施混沌工程可能会带来一些潜在的业务风险,经验和工具的缺失也将进一步阻止 DevOps 人员实施混沌工程。

混沌工程领域目前也有很多优秀的开源工具,分别覆盖某个领域,但这些工具的使用方式千差万别,其中有些工具上手难度大,学习成本高,混沌实验能力单一,使很多人对混沌工程领域望而却步。

阿里巴巴集团在混沌工程领域已经实践多年,将混沌实验工具 ChaosBlade 开源目的,我们希望:

  • 让更多人了解并加入到混沌工程领域;
  • 缩短构建混沌工程的路径;
  • 同时依靠社区的力量,完善更多的混沌实验场景,共同推进混沌工程领域的发展。

5.常用classLoader(书本此处其实是对tom加载servlet使用的classLoader分析)

二、系统高可用的方法论

金沙国际官网 5

(2)加载自定义路径中的class文件
  • <1>加载特定来源的某些类:重写find方法,使特定类或者特定来源的字节码 通过defineClass获得class类并返回(应该符合jvm的类加载规范,其他类仍使用父加载器加载)

  • <2>加载自顶一个是的class文件(如经过网络传来的经过加密的class文件字节码):findclass中加密后再加载

Agent的整体架构

ChaosBlade 的演进史

EOS(2012-2015):故障演练平台的早期版本,故障注入能力通过字节码增强方式实现,模拟常见的 RPC 故障,解决微服务的强弱依赖治理问题。

MonkeyKing(2016-2018):故障演练平台的升级版本,丰富了故障场景(如:资源、容器层场景),开始在生产环境进行一些规模化的演练。

AHAS(2018.9-至今):阿里云应用高可用服务,内置演练平台的全部功能,支持可编排演练、演练插件扩展等能力,并整合了架构感知和限流降级的功能。

ChaosBlade:是 MonkeyKing 平台底层故障注入的实现工具,通过对演练平台底层的故障注入能力进行抽象,定义了一套故障模型。配合用户友好的 CLI 工具进行开源,帮助云原生用户进行混沌工程测试。

金沙国际官网 6

3.如何加载class文件:

分为三个步骤 加载字节码到内存、Linking、类字节初始化赋值

作者介绍

例如,借助阿里云性能测试 PTS,高效率构建全链路压测体系,通过开源组件 Sentinel 实现限流和降级功能。这一次,经历了 6 年时间的改进和实践,累计在线上执行演练场景达数万次,我们将阿里巴巴在故障演练领域的创意和实践,浓缩成一个混沌工程工具,并将其开源,命名为 ChaosBlade。

Classloader负责将Class加载到JVM中,并且确定由那个ClassLoader来加载(父优先的等级加载机制)。还有一个任务就是将Class字节码重新解释为JVM统一要求的格式

如何构建一个高可用的系统呢?首先要分析一下不可用的因素都有哪些:

阿里妹导读:减少故障的最好方法就是让故障经常性的发生。通过不断重复失败过程,持续提升系统的容错和弹性能力。今天,阿里巴巴把六年来在故障演练领域的创意和实践汇浓缩而成的工具进行开源,它就是 “ChaosBlade”。如果你想要提升开发效率,不妨来了解一下。

(3)webAppClassLoader如:

Servlet等web应用中的类的加载(loadclass方法的规则详见P169)

各种各样的问题,在这种复杂的依赖结构下被放大,一个依赖30个SOA服务的系统,每个服务99.99%可用。99.99%的30次方≈99.7%。0.3%意味着一亿次请求会有3,000,00次失败,换算成时间大约每月有2个小时服务不稳定。随着服务依赖数量的变多,服务不稳定的概率会呈指数性提高,这些问题最后都会转化为故障表现出来。

ChaosBlade 能解决哪些问题?

衡量微服务的容错能力

通过模拟调用延迟、服务不可用、机器资源满载等,查看发生故障的节点或实例是否被自动隔离、下线,流量调度是否正确,预案是否有效,同时观察系统整体的 QPS 或 RT 是否受影响。在此基础上可以缓慢增加故障节点范围,验证上游服务限流降级、熔断等是否有效。最终故障节点增加到请求服务超时,估算系统容错红线,衡量系统容错能力。

验证容器编排配置是否合理

通过模拟杀服务 Pod、杀节点、增大 Pod 资源负载,观察系统服务可用性,验证副本配置、资源限制配置以及 Pod 下部署的容器是否合理。

测试 PaaS 层是否健壮

通过模拟上层资源负载,验证调度系统的有效性;模拟依赖的分布式存储不可用,验证系统的容错能力;模拟调度节点不可用,测试调度任务是否自动迁移到可用节点;模拟主备节点故障,测试主备切换是否正常。

验证监控告警的时效性

通过对系统注入故障,验证监控指标是否准确,监控维度是否完善,告警阈值是否合理,告警是否快速,告警接收人是否正确,通知渠道是否可用等,提升监控告警的准确和时效性。

定位与解决问题的应急能力

通过故障突袭,随机对系统注入故障,考察相关人员对问题的应急能力,以及问题上报、处理流程是否合理,达到以战养战,锻炼人定位与解决问题的能力。

(2)NoClassDefFoundError:

通常是使用new关键字,属性引用了某个类,继承了某个类或接口,但JVM加载这些类时发现这些类不存在的异常

类加载模型

近期规划

功能迭代:

  • 增强 JVM 演练场景,支持更多的 Java 主流框架,如 Redis,GRPC
  • 增强 Kubernetes 演练场景
  • 增加对 C 、Node.js 等应用的支持

7.实现类的热部署:

  • (1)同一个classLoader的两个实例加载同一个类,JVM也会识别为两个

  • (2)不能重复加载同一个类(全名相同,并使用同一个类加载器),会报错

  • (3)不应该动态加载类,因为对象呗引用后,对象的属性结构被修改会引发问题

注意:使用不同classLoader加载的同一个类文件得到的类,JVM将当作是两个不同类,使用单例模式,强制类型转换时都可能因为这个原因出问题。

金沙国际官网 7

(2)StandardClassLoader:

加载tomcat容器的classLoader,另外webAppClassLoader在loadclass时,发现类不在JVM的classPath下,在PackageTriggers(是一个字符串数组,包含一组不能使用webAppClassLoader加载的类的包名字符串)下的话,将由该加载器加载(注意:StandardClassLoader并没有覆盖loadclass方法,所以其加载的类和AppClassLoader加载没什么分别,并且使用getClassLoader返回的也是AppClassLoader)(另外,如果web应用直接放在tomcat的webapp目录下该应用就会通过StandardClassLoader加载,估计是因为webapp目录在PackageTriggers中?)

故障演练平台架构主要分为四部分:

2.ClassLoader的等级加载机制

故障类型:主要包括运行期异常、超时等等。通过对系统某些服务动态地注入运行期异常来达到模拟故障的目的,系统按照预案执行相应的策略验证系统是否是真正的高可用。

原书链接

以上内容只是个人笔记纪录,更多完整内容请购买作者原书籍查看。《深入分析JavaWeb技术内幕》

但是想改变的是Tomcat WebClassLoader所加载的com.xxx.InvocationHandler这个类的Invoke方法,不同的ClassLoader之间的类是不能相互访问的,做字节码的变换并不需要这个类的实例,也不需要返回结果,所以可以通过Instrument API拿到这个类加载器,并且可以根据类名称获取到这个类的字节码进行字节码变换。故障类Drill.class和变形后的com.xxx.InvocationHandler.class重新load到JVM中,完成了插桩操作。

(2)Jvm加载class文件到内存有两种方式,隐式加载和显示加载,通常这两种方式是混合使用的
  • <1>隐式加载:是通过JVM来自动加载需要的类到内存的方式,当某个类被使用时,JVM发现该类不在内存中,那么它就会自动加载该类到内存

  • <2>显示加载:通过调用this.getClasss.getClassLoader.loadClass(),Class.forName,自己实现的ClassLoader的findClass方法

  • 服务A调用服务B在Client端的Proxy层做AOP;
  • 启动Agent并且生成一个Drill类invoke方法,抛出一个运行期异常;
  • 字节码变形:在代码第一行之前增加Drill.invoke();
  • 如果想变换异常类型,改变Drill类即可,换成Sleep 3s ClassRedifine之后会重新load到JVM完成故障类型的转化或者清除。
(2)Linking:验证与解析,包含3步:
  • <1>字节码验证

  • <2>类准备:准备代表每个类中定义的字段、方法和实现接口所需的数据结构

  • <3>解析:这个阶段类装入器转入类所应用的其他类

* do something...

4.常见加载类错误分析

理论上来说,当图中所有的事情都做完,我们就可以认为系统是一个真正的高可用系统。但真是这样吗?

(1)主要由四个方法,分别是defineClass,findClass,loadClass,resolveClass
  • <1>defineClass(byte[] , int ,int) 将byte字节流解析为JVM能够识别的Class对象(直接调用这个方法生成的Class对象还没有resolve,这个resolve将会在这个对象真正实例化时resolve)

  • <2>findClass,通过类名去加载对应的Class对象。当我们实现自定义的classLoader通常是重写这个方法,根据传入的类名找到对应字节码的文件,并通过调用defineClass解析出Class独享

  • <3>loadClass运行时可以通过调用此方法加载一个类(由于类是动态加载进jvm,用多少加载多少的?)

  • <4>resolveClass手动调用这个使得被加到JVM的类被链接(解析resolve这个类?)

1、故障演练平台的整体架构

(2)实现自定义ClassLoader一般会继承URLClassLoader类,因为这个类实现了大部分方法。
  • 静态编织:静态编织发生在字节码生成时根据一定框架的规则提前将AOP字节码插入到目标类和方法中;
  • 动态编织:在JVM运行期对指定的方法完成AOP字节码增强。常见的方法大多数采用重命名原有方法,再新建一个同名方法做代理的工作模式来完成。
(3)上级委托机制:当一个加载器加载类字时,先委托其父加载器加载,若加载成功则反馈给该加载器,若父加载器不能加载,则由该加载器加载

事件模型可以完成三个功能:

6.自定义的classloader

步骤二、选择故障方法;

(1)JVM平台提供三层的ClassLoader,这三层ClassLoader可以分为两类,分别是服务JVM自身的,和服务广大普通类的。分别是:
  • <1>BootstrapClassLoader:主要加载JVM自身工作所需要的类,该ClassLoader没有父类加载器和子类加载器

  • <2>ExtClassLoader:这个类加载器同样是JVM自身的一部分,但是不是由JVM实现,主要用于加载System.getProperty(“java.ext.dirs”)目录地下的类,如本机的值“D:javajdk7jrelibext;C:WindowsSunJavalibext”

  • <3>AppClassLoader:加载System.getProperty("java.class.path")(注意了在ide中运行程序时,该值通常是该项目的classes文件夹)中的类。所有的自定义类加载器不管直接实现ClassLoader,是继承自URLClassLoader或其子类,其父加载器(注意:父加载器与父类的分别)都是AppClassLoader,因为不管调用哪个父类的构造器,最终都将调用getSystemClassLoader作为父加载器,而该方法返回的正是AppClassLoader。(当应用程序中没有其他自定义的classLoader,那么除了System.getProperty(“java.ext.dirs”)目录中的类,其他类都由AppClassLoader加载)

BEFORE在方法执行前事件、THROWS抛出异常事件、RETURN返回事件。这三类事件可以在方法执行前、返回和抛出异常这三种情况做字节码编织。

(1)AppClassLoader:

加载jvm的classpath中的类和tomcat的核心类

在开发Agent的时候,第一个应用是故障演练平台,那么这个时候其实我们并不需要Agent执行的过程中有自定义结果对象的返回,所以第一个版本的Agent采用硬编码的方式进行动态织入:

(1)加载字节码到内存:(这一步通常通过findclass()方法实现)

以URLClassLoader为例:该类的构造函数返现必须制定一个URL数据才能创建该对象,该类中包含一个URLClassPath对象,URLClassPath会判断传过来的URL是文件还是Jar包,创建相应的FileLoader或者JarLoader或者默认加载器,当jvm调用findclass时,这些加载器将class文件的字节码加载到内存中

下一版本的Agent实现就产生了,把所有Agent的类和实现的功能抽象出来,放到一个自定义的AgentClassLoader里面,字节码注入到目标APP后可以通过反射的方式来调用具体的事件实现。

Dubbo调用的注入过程

Agent的整体架构如图所示:

if method==业务线定义方法

系统之间的依赖非常复杂、调用链路很深、服务之间没有分层。在这种复杂的依赖下,系统发生了几起故障:

金沙国际官网 8

版权声明:本文由金沙国际登录网址发布于科技传媒,转载请注明出处:去哪儿系统高可用之法,深入分析JavaWeb技术内幕