去哪儿系统高可用之法,Android性能监控实现原理

2019-12-22 00:01栏目:六合联盟宝典大全

原标题:去何方系统高可用之法:搭建故障演习平台

关系知识点:APM, java Agent, plugin, bytecode, asm, InvocationHandler, smail

小编介绍

APM : 应用程序品质处理。 二零一三年时海外的APM行当 NewRelic 和 应用软件Dynamics 已经在该领域拔得头筹,国内近些年来也冷俊不禁部分APM商家,如: 听云, OneAPM, 博睿 云智慧,Ali百川码力。 (据剖析,本国android端方案都以抄袭NewRelic公司的,由于该公司的sdk未混淆,产业界良心卡塔尔(英语:State of Qatar)

王鹏,前年投入去何方机票职业部,首要从事后端研究开发工作,方今在机票职业部肩负行程单和故障演练平台以致国有服务ES、数据同步中间件等相关的研究开发专门的学问。

能做如何: crash监察和控制,卡顿监控,内存监察和控制,扩大trace,互联网品质监察和控制,app页面自动埋点,等。

去何方网二〇〇五年确立于今,随着系统规模的日益增添,已经有不菲个使用体系,这个系统里头的耦合度和链路的复杂度不断抓牢,对于我们营造布满式高可用的种类布局具备庞大挑衅。我们需求一个平台在运营期自动注入故障,查证故障预案是还是不是起效——故障演习平台。

属性监察和控制其实就是hook 代码到花色代码中,进而完毕各个监督。常规花招都以在项目中追加代码,但什么产生非侵入式的,即二个sdk就可以。

一、背景

1. 如何hook

断面编制程序-- AOP。大家的方案是AOP的风度翩翩种,通过匡正app class字节码的款式将大家项指标class文件实行更动,进而产生放权大家的监督代码。

图片 1androidbuilder.jpg

通过查阅Adnroid编写翻译流程图,可以驾驭编写翻译器会将兼具class文件打包称dex文件,最终打包成apk。那么大家就须要在class编写翻译成dex文件的时候举办代码注入。譬如本身想计算某些方法的奉行时间,那作者只需求在各种调用了那几个艺术的代码前后都加二个时刻总括就能够了。关键点就在于编写翻译dex文件时候注入代码,那一个编写翻译进度是由dx实施,具体类和艺术为com.android.dx.command.dexer.Main#processClass。此形式的第三个参数便是class的byte数组,于是大家只须要在步入processClass方法的时候用ASM工具对class进行改建并替换掉第4个参数,最终生成的apk便是咱们改动之后的了。

类:com.android.dx.command.dexer.Main

新的难点: 要让jvm在实施processClass从前先举行我们的代码,应当要对com.android.dx.command.dexer.Main(以下简单的称呼为dexer.Main)实行纠正。怎么着本事达到那几个目标?那时候Instrumentation和VirtualMachine就上台了,仿效第四节。

那是某工作部的体系拓扑图:

2. hook 到哪里

风流浪漫期首若是网络性能监察和控制。如何能收获到网络数据经过调查斟酌开掘这几天有上面聚焦方案:

  • root手提式有线电话机,通过adb 命令进行收缴。
  • 创立vpn,将装有互连网乞求举办收缴。
  • 仿照效法听云,newrelic等付加物,针对特定库举行代理截获。

或是还会有任何的章程,须求继续调查研究。

当下我们参谋newrelic等营业所付加物,针对特定互联网央浼库开展代理的的方法实行网络数据截获。比如okhtt3, httpclient, 等互连网库。

In general, a javaagent is a JVM “plugin”, a specially crafted .jar file, that utilizes the Instrumentation API that the JVM provides.

出于我们要改正Dexer 的Main类, 而该类是在编写翻译时代由java虚构机运营的, 所以大家需求通过agent来矫正dexer Main类。

javaagent的关键功能如下:

  • 能够在加载class文件早先作拦截,对字节码做改善
  • 能够在运转期对已加载类的字节码做变通

JVMTI:JVM Tool Interface,是JVM暴表露来的有个别供客户扩展的接口集合。JVMTI是依靠事件驱动的,JVM每实行到早晚的逻辑就能够调用一些平地风波的回调接口,那一个接口能够供开辟者增添自身的逻辑。

instrument agent: javaagent效用正是它来兑现的,此外instrument agent还应该有独家名为JPLISAgent(Java Programming Language Instrumentation Services Agent卡塔尔,那个名字也完全部现了其最实质的意义:正是专程为Java语言编写的插桩服务提供支持的。

三种加载agent的艺术:

  • 在运转时加载, 运行JVM时钦点agent类。这种措施,Instrumentation的实例通过agent class的premain方法被传到。
  • 在运营时加载,JVM提供意气风发种当JVM运转实现后开启agent机制。这种状态下,Instrumention实例通过agent代码中的的agentmain传入。

参考例子instrumentation 作用介绍(javaagent卡塔尔(قطر‎

有了javaagent, 大家就足以在编写翻译app时再次改善dex 的Main类,对应改善processClass方法。

如何改正class文件? 我们必要驾驭java字节码,然后需求掌握ASM开拓。通过ASM编制程序来改良字节码,进而校订class文件。(也能够接纳javaassist来拓宽改进)

在介绍字节代码指令以前,有不可缺乏先来介绍 Java 设想机实践模型。大家驾驭,Java 代码是 在线程内部实践的。每种线程都有友好的实践栈,栈由帧组成。每一种帧表示叁个措施调用:每次调用三个措施时,会将贰个新帧压入当前线程的实施栈。当方法再次回到时,大概是常规再次来到,也许是因为特别重返,会将以此帧从施行栈中弹出,实施进度在发出调用的议程中三番五次开展(那些方 法的帧从前献身栈的顶上部分卡塔尔国。

每风流洒脱帧包含两有的:二个片段变量部分和贰个操作数栈部分。局地变量部分含有可依附索引 以自由顺序访问的变量。由名字能够看见,操作数栈部分是二个栈,在那之中含有了供字节代码指令 用作操作数的值。

字节代码指令 字节代码指令由多少个标记该指令的操作码和一定数目标参数组成:

  • 操作码是一个无符号字节值——即字节代码名
  • 参数是静态值,明确了规范的通令行为。它们紧跟在操作码之后给出.举个例子GOTO标志指令(其操作码的值为 167卡塔尔国以三个指明下一条待实行命令的暗记作为参数标志。不要 将指令参数与指令操作数相混淆:参数值是静态已知的,存款和储蓄在编写翻译后的代码中,而 操作数值来自操作数栈,唯有到运转时工夫知晓。

参考:

何足为奇指令:

  • const 将怎么着数据类型压入操作数栈。
  • push 表示将单字节或短整型的常量压入操作数栈。
  • ldc 代表将如何项目的数目从常量池中压入操作数栈。
  • load 将某项目标有些变量数据压入操作数栈顶。
  • store 将操作数栈顶的数目存入钦命的豆蔻梢头部分变量中。
  • pop 从操作数栈顶弹出多少
  • dup 复制栈顶的数据并将复制的值也压入栈顶。
  • swap 交换栈顶的数码
  • invokeVirtual 调用实例方法
  • invokeSepcial 调用超类布局方法,实例起始化,私有方法等。
  • invokeStatic 调用静态方法
  • invokeInterface 调用接口
  • getStatic
  • getField
  • putStatic
  • putField
  • New

查看demo:Java源代码

public static void print(String param) { System.out.println("hello "   param); new TestMain().sayHello();}public void sayHello() { System.out.println("hello agent");}

字节码

// access flags 0x9 public static print(Ljava/lang/String;)V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V LDC "hello " INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ALOAD 0 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V NEW com/paic/agent/test/TestMain DUP INVOKESPECIAL com/paic/agent/test/TestMain.<init> ()V INVOKEVIRTUAL com/paic/agent/test/TestMain.sayHello ()V RETURN public sayHello()V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "hello agent" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V RETURN

是因为程序深入分析、生成和更改本领的用途众多,所以大家针对广大言语实现了不菲用以解析、 生成和改变程序的工具,这个语言中就总结 Java 在内。ASM 正是为 Java 语言设计的工具之后生可畏, 用于开展运维时类生成与转移。于是,大家设计了 ASM1库,用于拍卖经过编写翻译 的 Java 类。

ASM 并非惟生机勃勃可调换和转换已编译 Java 类的工具,但它是最新、最高效的工具之后生可畏,可 从 下载。其关键优点如下:

  • 有二个总结的模块API,设计周密、使用方便。
  • 文书档案齐全,具备四个城门失火的Eclipse插件。
  • 扶助新型的 Java 版本——Java 7。
  • 小而快、特别可信赖。
  • 具备庞大的客户社区,可感觉新客商ﰁ供扶植。
  • 源许可开放,差不离允许私自使用。

图片 2ASM_transfer.png

核心类: ClassReader, ClassWriter, ClassVisitor

参考demo:

{ // print 方法的ASM代码 mv = cw.visitMethod(ACC_PUBLIC   ACC_STATIC, "print", "(Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn; mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false); mv.visitLdcInsn; mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitVarInsn; mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitTypeInsn(NEW, "com/paic/agent/test/TestMain"); mv.visitInsn; mv.visitMethodInsn(INVOKESPECIAL, "com/paic/agent/test/TestMain", "<init>", "()V", false); mv.visitMethodInsn(INVOKEVIRTUAL, "com/paic/agent/test/TestMain", "sayHello", "()V", false); mv.visitInsn; mv.visitEnd();}{ //sayHello 的ASM代码 mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("hello agent"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitInsn; mv.visitEnd();}

VirtualMachine有个loadAgent方法,它内定的agent会在main方法前运行,并调用agent的agentMain方法,agentMain的第叁个参数是Instrumentation,那样大家就可以看到给Instrumentation设置ClassFileTransformer来完成对dexer.Main的改建,同样也能够用ASM来兑现。日常的话,APM工具包罗几个部分,plugin、agent和切实的思想政治工作jar包。那一个agent正是我们说的由VirtualMachine运维的代办。而plugin要做的作业便是调用loadAgent方法。对于Android Studio来说,plugin正是二个Gradle插件。 实现gradle插件能够用intellij创建一个gradle工程并落实Plugin< Project >接口,然后把tools.jar(在jdk的lib目录下)和agent.jar参预到Libraries中。在META-INF/gradle-plugins目录下开创叁个properties文件,并在文件中参与大器晚成行内容“implementation-class=插件类的全约束名“。artifacs配置把源码和META-INF加上,但无法加tools.jar和agent.jar。(tools.jar 在 jdk中, 不过通常需求团结拷贝到工程目录中的, agent.jar开垦达成前存放plugin工程中用来获取jar包路线卡塔尔国。

agent的贯彻相对plugin则复杂超多,首先必要提供agentmain(String args, Instrumentation inst卡塔尔(英语:State of Qatar)方法,并给Instrumentation设置ClassFileTransformer,然后在transformer里改善dexer.Main。当jvm成功施行到我们设置的transformer时,就能够意识传进来的class根本就未有dexer.Main。坑爹呢那是。。。前面提到了,履行dexer.Main的是dx.bat,也正是说,它和plugin根本不在多个经过里。

dx.bat其实是由ProcessBuilder的start方法运维的,ProcessBuilder有贰个command成员,保存的是运行指标经过指导的参数,只要大家给dx.bat带上-javaagent参数就能够给dx.bat所在经过钦定大家的agent了。于是大家能够在实行start方法前,调用command方法获得command,并往个中插入-javaagent参数。参数的值是agent.jar所在的不二等秘书技,勉强接受agent.jar当中二个class类实例的getProtectionDomain(卡塔尔国.getCodeSource(卡塔尔国.getLocation.get帕特h(卡塔尔(英语:State of Qatar)得到。可是到了此地大家的顺序或许依旧不能够无误改良class。若是大家把改变类的代码单独置于八个类中,然后用ASM生成字节码调用那一个类的主意来对command参数进行更正,就能意识抛出了ClassDefNotFoundError错误。这里提到到了ClassLoader的文化。

至于ClassLoader的牵线非常多,这里不再赘言。ProcessBuilder类是由Bootstrap ClassLoader加载的,而大家自定义的类则是由AppClassLoader加载的。Bootstrap ClassLoader处于AppClassLoader的上层,大家领略,上层类加载器所加载的类是力所不及直接援用下层类加载器所加载的类的。但风流倜傥旦下层类加载器加载的类完毕或接续了上层类加载器加载的类或接口,上层类加载器加载的类获取到下层类加载的类的实例就足以将其挟持转型为父类,并调用父类的措施。那些上层类加载器加载的接口,部分APM使用InvocationHandler。还会有二个主题材料,ProcessBuilder怎么技艺取拿到InvocationHandler子类的实例呢?有多个比极美丽妙的做法,在agent运行的时候,创立InvocationHandler实例,并把它赋值给Logger的treeLock成员。treeLock是三个Object对象,并且只是用来加锁的,未有其余用处。但treeLock是叁个final成员,所以记得要修正其修饰,去掉final。Logger雷同也是由Bootstrap ClassLoader加载,那样ProcessBuilder就会因此反射的法门来拿到InvocationHandler实例了。(详见:大旨代码例子卡塔尔(英语:State of Qatar)

上层类加载器所加载的类是回天无力直接援用下层类加载器所加载的类的

层次 加载器
上层 BootStrapClassLoader ProcessBuilder
下层 AppClassLoader ProcessBuilderMethodVisitor操作的自定义类

这一句话的通晓: 大家的目标是透过ProcessBuilderMethodVisitor将我们的代码写入ProcessBuilder.class中去让BootStrapClassLoader类加载器实行加载,而这个时候, BootStrapClassLoader是力不能够支援引到大家自定义的类的,因为我们自定义的类是AppClassLoader加载的。

但生龙活虎旦下层类加载器加载的类达成或持续了上层类加载器加载的类或接口,上层类加载器加载的类获取到下层类加载的类的实例就足以将其挟持转型为父类,并调用父类的形式。

层次 加载器
上层 BootStrapClassLoader Looger
下层 AppClassLoader InvocationDispatcher

这句话的知情: 这里大家可以观望自定义类InvocationDispatcher是由AppClassLoader加载的, 大家在运行RewriterAgent(AppClassLoader加载卡塔尔(قطر‎类时,通过反射的措施将InvocationDispatcher对象放入Looger(由于引用了Looger.class,所以当时logger已经被BootStrapClassLoader加载卡塔尔国类的treelock对象中,即下层类加载器加载的类完毕了上层类加载器加载的类;当大家经过ProcessBuilderMethodVisitor类管理ProcessBuilder.class文件时,能够经过Logger提取成员变量,插入对应的调用逻辑。当运维到ProcessBuilder时,再经过这段代码动态代理的格局调用对应的业务。能够将其强逼转型为父类,并调用父类的主意 ,请参考 这里详细介绍了invokeInterface 和 invokeVirtual 的区分。

兑现上大家如今首要做那二种, 大器晚成种是代码调用替换, 另生龙活虎种是代码包裹再次回到。首假若提前写好相应法规的更换代码, 生成配置文件表, 在agent中visit每七个class代码, 遭逢对应相配调用时将张开代码替换。

ProcessBuilderMethodVisitor DexClassTransformer#createDexerMainClassAdapter InvocationDispatcher BytecodeBuilder

public BytecodeBuilder loadInvocationDispatcher() { this.adapter.visitLdcInsn(Type.getType(TransformConstant.INVOCATION_DISPATCHER_CLASS)); this.adapter.visitLdcInsn(TransformConstant.INVOCATION_DISPATCHER_FILED_NAME); this.adapter.invokeVirtual(Type.getType(Class.class), new Method("getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;")); this.adapter.dup(); this.adapter.visitInsn(Opcodes.ICONST_1); this.adapter.invokeVirtual(Type.getType(Field.class), new Method("setAccessible", "; this.adapter.visitInsn(Opcodes.ACONST_NULL); this.adapter.invokeVirtual(Type.getType(Field.class), new Method("get", "(Ljava/lang/Object;)Ljava/lang/Object;")); return this; }

解析

顺序 指令 描述
8 InvocationDispatcher object invokeVirtual 调用get方法返回具体实例对象
7 null ACONST_NULL null 入栈
6 Field object invokeVirtual 调用setAccessible,改为可访问的,目前栈中只剩一个对象
5 true ICONST_1 1 即为true,入栈
4 Field object dup 拷贝一份,目前栈中只剩两个对象
3 Field object invokeVirtual 调用getDeclaredField 获取treeLock存储的Field
2 treelock ldc treelock 入栈
1 Logger.class Type ldc Logger.class type 入栈

WrapMethodClassVisitor#MethodWrapMethodVisitor

private boolean tryReplaceCallSite(int opcode, String owner, String name, String desc, boolean itf) { Collection<ClassMethod> replacementMethods = this.context.getCallSiteReplacements(owner, name, desc); if (replacementMethods.isEmpty { return false; } ClassMethod method = new ClassMethod(owner, name, desc); Iterator<ClassMethod> it = replacementMethods.iterator(); if (it.hasNext { ClassMethod replacementMethod = it.next(); boolean isSuperCallInOverride = (opcode == Opcodes.INVOKESPECIAL) && !owner.equals(this.context.getClassName && this.name.equals && this.desc.equals; //override 方法 if (isSuperCallInOverride) { this.log.info(MessageFormat.format("[{0}] skipping call site replacement for super call in overriden method : {1}:{2}", this.context.getFriendlyClassName(), this.name, this.desc)); return false; } Method originMethod = new Method(name, desc); //处理init方法, 构造对象, 调用替换的静态方法来替换init。 if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { //调用父类构造方法 if (this.context.getSuperClassName() != null && this.context.getSuperClassName().equals { this.log.info(MessageFormat.format("[{0}] skipping call site replacement for class extending {1}", this.context.getFriendlyClassName(), this.context.getFriendlySuperClassName; return false; } this.log.info(MessageFormat.format("[{0}] tracing constructor call to {1} - {2}", this.context.getFriendlyClassName(), method.toString); //开始处理创建对象的逻辑 //保存参数到本地 int[] arguments = new int[originMethod.getArgumentTypes().length]; for (int i = arguments.length -1 ; i >= 0; i--) { arguments[i] = this.newLocal(originMethod.getArgumentTypes; this.storeLocal(arguments[i]); } //由于init 之前会有一次dup,及创建一次, dup一次, 此时如果执行了new 和 dup 操作树栈中会有两个对象。 this.visitInsn(Opcodes.POP); if (this.newInstructionFound && this.dupInstructionFound) { this.visitInsn(Opcodes.POP); } //载入参数到操作数栈 for (int arg : arguments) { this.loadLocal; } //使用要替换的方法,执行静态方法进行对象创建 super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; //如果此时才调用了dup,也需要pop, (这一部分的场景暂时还没有构造出来, 上面的逻辑为通用的) if (this.newInstructionFound && !this.dupInstructionFound) { this.visitInsn(Opcodes.POP); } } else if (opcode == Opcodes.INVOKESTATIC) { //替换静态方法 this.log.info(MessageFormat.format("[{0}] replacing call to {1} with {2}", this.context.getFriendlyClassName(), method.toString(), replacementMethod.toString; super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; } else { // 其他方法调用, 使用新方法替换旧方法的调用。 先判断创建的对象是否为null, Method newMethod = new Method(replacementMethod.getMethodName(), replacementMethod.getMethodDesc; this.log.info(MessageFormat.format("[{0}] replacing call to {1} with {2}", this.context.getFriendlyClassName(), method.toString(), replacementMethod.toString; //从操作数栈上取原始参数类型到本地变量中 int[] originArgs = new int[originMethod.getArgumentTypes().length]; for (int i = originArgs.length -1 ; i >= 0; i--) { originArgs[i] = this.newLocal(originMethod.getArgumentTypes; this.storeLocal(originArgs[i]); } //操作数栈中只剩操作对象了, 需要dup, 拷贝一份作为检查新method的第一个参数。 this.dup(); //检查操作数栈顶对象类型是否和新method的第一个参数一致。 this.instanceOf(newMethod.getArgumentTypes; Label isInstanceOfLabel = new Label(); //instanceof 结果不等于0 则跳转到 isInstanceofLabel,执行替换调用 this.visitJumpInsn(Opcodes.IFNE, isInstanceOfLabel); //否则执行原始调用 for (int arg : originArgs) { this.loadLocal; } super.visitMethodInsn(opcode, owner, name, desc, itf); Label endLabel = new Label(); //跳转到结束label this.visitJumpInsn(Opcodes.GOTO, endLabel); this.visitLabel(isInstanceOfLabel); //处理替换的逻辑 //load 参数, 第一个为 obj, 后面的为原始参数 this.checkCast(newMethod.getArgumentTypes; for (int arg: originArgs) { this.loadLocal; } super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; //结束 this.visitLabel; } this.context.markModified(); return true; } return false; }

解析 详细见tryReplaceCallSite声明就能够。

图片 3

8. 验证

将调换的apk反编写翻译,查看class 字节码。我们平日会经过JD-GUI来查看。大家来查阅一下sample生成的结果:

private void testOkhttpCall() { OkHttpClient localOkHttpClient = new OkHttpClient.Builder; Object localObject = new Request.Builder().url("https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey"); if (!(localObject instanceof Request.Builder)) { localObject = ((Request.Builder)localObject).build(); if ((localOkHttpClient instanceof OkHttpClient)) { break label75; } } label75: for (localObject = localOkHttpClient.newCalllocalObject);; localObject = OkHttp3Instrumentation.newCall((OkHttpClient)localOkHttpClient, localObject)) { localObject).enqueue(new Callback() { public void onFailure(Call paramAnonymousCall, IOException paramAnonymousIOException) { } public void onResponse(Call paramAnonymousCall, Response paramAnonymousResponse) throws IOException { } }); return; localObject = OkHttp3Instrumentation.build((Request.Builder)localObject); break; } }

上边的代码估量未有几人能够看懂, 越发for循环里面包车型大巴逻辑。其实是由于不一样的反编写翻译工具形成的剖释难点产生的,所以看起来逻辑混乱,不能切合预期。

想用查看真实的结果, 大家来看下反编写翻译后的smail。详细smail指令参谋

.method private testOkhttpCall()V .locals 6 .prologue .line 35 const-string v3, "https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey" .line 36 .local v3, "url":Ljava/lang/String; new-instance v4, Lokhttp3/OkHttpClient$Builder; invoke-direct {v4}, Lokhttp3/OkHttpClient$Builder;-><init>()V invoke-virtual {v4}, Lokhttp3/OkHttpClient$Builder;->build()Lokhttp3/OkHttpClient; move-result-object v1//new OkHttpClient.Builder; 即为okhttpclient,放到 v1 中 .line 37 .local v1, "okHttpClient":Lokhttp3/OkHttpClient; new-instance v4, Lokhttp3/Request$Builder; invoke-direct {v4}, Lokhttp3/Request$Builder;-><init>()V invoke-virtual {v4, v3}, Lokhttp3/Request$Builder;->url(Ljava/lang/String;)Lokhttp3/Request$Builder; move-result-object v4 //new Request.Builder().url执行了这一段语句,将结果放到了v4中。 instance-of v5, v4, Lokhttp3/Request$Builder; if-nez v5, :cond_0 invoke-virtual {v4}, Lokhttp3/Request$Builder;->build()Lokhttp3/Request; move-result-object v2 .line 38 .local v2, "request":Lokhttp3/Request; //判断v4中存储的是否为Request.Builder类型,如果是则跳转到cond_0, 否则执行Request.Builder.build()方法,将结果放到v2中. :goto_0 instance-of v4, v1, Lokhttp3/OkHttpClient; if-nez v4, :cond_1 invoke-virtual {v1, v2}, Lokhttp3/OkHttpClient;->newCall(Lokhttp3/Request;)Lokhttp3/Call; move-result-object v0 .line 39 .end local v1 # "okHttpClient":Lokhttp3/OkHttpClient; .local v0, "call":Lokhttp3/Call; //goto_0 标签:判断v1 中的值是否为 OKHttpclient 类型, 如果是跳转为cond_1 , 否则调用OKHttpclient.newCall, 并将结果放到v0 中。 :goto_1 new-instance v4, Lcom/paic/apm/sample/MainActivity$1; invoke-direct {v4, p0}, Lcom/paic/apm/sample/MainActivity$1;-><init>(Lcom/paic/apm/sample/MainActivity;)V invoke-interface {v0, v4}, Lokhttp3/Call;->enqueue(Lokhttp3/Callback;)V .line 51 return-void //goto_1 标签: 执行 v0.enqueue(new Callback;并return; .line 37 .end local v0 # "call":Lokhttp3/Call; .end local v2 # "request":Lokhttp3/Request; .restart local v1 # "okHttpClient":Lokhttp3/OkHttpClient; :cond_0 check-cast v4, Lokhttp3/Request$Builder; invoke-static {v4}, Lcom/paic/agent/android/instrumentation/okhttp3/OkHttp3Instrumentation;->build(Lokhttp3/Request$Builder;)Lokhttp3/Request; move-result-object v2 goto :goto_0 //cond_0:标签: 执行com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.build, 并将结果放到v2中,并goto 到 goto_0 .line 38 .restart local v2 # "request":Lokhttp3/Request; :cond_1 check-cast v1, Lokhttp3/OkHttpClient; .end local v1 # "okHttpClient":Lokhttp3/OkHttpClient; invoke-static {v1, v2}, Lcom/paic/agent/android/instrumentation/okhttp3/OkHttp3Instrumentation;->newCall(Lokhttp3/OkHttpClient;Lokhttp3/Request;)Lokhttp3/Call; move-result-object v0 goto :goto_1 //cond_1 标签: 执行com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.newCall, 并将结果放到v0中, goto 到goto_1 .end method

剖判后的伪代码

String v3 = "https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey";object v1 = new OkhttpClient.Builder;object v4 = new Reqeust.Builder;object v2 ;object v0 ;if (v4 instanceof Request.Builder) { cond_0: v2 = com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.build; } else { v2 = (Request.Builder)v4.build();}goto_0:if (v1 instanceof OkHttpClient) { cond_1: v0 = com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.newCall;} else { v0 = v1.newCall; // v0 is Call}goto_1:v4 = new Callback();v0.enqueue;return;

翻看伪代码, 符合预期结果。验证完毕。

系统之间的信赖特别复杂、调用链路很深、服务中间从未分支。在此种复杂的信赖性下,系统一发布出了几起故障:

  • 弱信任挂掉,主流程挂掉,校勘报废凭证的支付景况,下单主流程失败;
  • 主题服务调用量陡增,某服务超时引起相关联的兼具服务“雪崩”;
  • 机房网络恐怕某个机器挂掉,无法提供基本服务。

多个故障原因:

  • 系统强弱信赖混乱、弱信任无降级;
  • 系统流量猛增,系统体积不足,未有限流熔断机制;
  • 硬件财富网络现身难题影响系统运维,未有高可用的互连网布局。

数不胜数的标题,在这里种复杂的注重布局下被推广,叁个依据30个SOA服务的系统,各样服务99.99%可用。99.99%的贰拾五次方≈99.7%。0.3%意味着大器晚成亿次号令会有3,000,00次败北,换算成时间大致每月有2个钟头服务不安宁。随着服务注重数量的变多,服务不平稳的票房价值会呈指数性提升,这一个主题素材最后都会转变为故障表现出来。

二、系统高可用的方法论

怎样创设叁个高可用的系统啊?首先要剖判一下不可用的要素都有啥样:

图片 4

高可用系统独立推行

反对上的话,当图中装有的事务都做完,大家就足以感到系统是二个真正的高可用系统。但正是如此呢?

那便是说故障演习平台就热闹上场了。当上述的高可用执行都做完,利用故障练习平台做贰回真正的故障练习,在系统运转期动态地注入一些故障,进而来验证下系统是或不是据守故障预案去推行相应的降级也许熔断战术。

三、故障练习平台

故障练习平台:稽查故障预案是不是真的的起效能的阳台。

故障类型:驷比不上舌回顾运维期格外、超时等等。通过对系统有些服务动态地注入运营期格外来完毕模拟故障的指标,系统依照预案实践相应的计策验证系统是还是不是是真正的高可用。

1、故障演练平台的全部构造

故障练习平台构造重要分为四片段:

图片 5

  • 前台突显系统(WEB):呈现系统之间的拓扑关系以至各样AppCode对应的集群和办法,可以选择具体的艺术进行故障的注入和消释;
  • 透露种类(Deploy):那些系统重要用来将故障演习平台的Agent和Binder包发表到指标应用软件的机器上同有的时候间运维推行。前台体现系统会传送给发表平台要拓宽故障注入的AppCode甚至目的应用程式的IP地址,通过那四个参数发表种类能够找到呼应的机器进行Jar包的下载和运转;
  • 劳务和下令分发系统(Server):这几个系统入眼是用于命令的分发、注入故障的景观记录、故障注入和扫除操作的逻辑、权限校验以至有关的Agent的归来消息选用效果。前台页面已经接入QSSO会对当前人能够操作的IP列表做故障注入,防守危机。后端命令分发的模块会和配置在指标应用软件上的Agent实行通讯,将下令推送到Agent上实行字节码编织,Agent实行命令后回到的开始和结果通过Server和Agent的长连接传回Server端;
  • Agent和Binder程序:Agent肩负对指标APP做代办况兼做字节码加强,具体代理的措施能够因而传输的指令来决定,代理方法后对艺术做动态的字节码加强,这种字节码增强全数无侵入、实时生效、动态可插拔的表征。Binder程序主倘若由此发表系统传递过来的AppCode和运转端口(ServerPort)找到对象APP的JVM进程,之后施行动态绑定,完结运转期代码巩固的效用。

2、 Agent全体布局

一时一刻AOP的兑现成二种办法:

  • 静态编织:静态编织发生在字节码生成时依照早晚框架的规行矩步提前将AOP字节码插入到指标类和格局中;
  • 动态编织:在JVM运转期对钦定的方法成功AOP字节码加强。司空眼惯的法子大多数选取重命名原有艺术,再新建多少个同名方法做代办的行事形式来产生。

静态编织的难点是风流倜傥旦想改动字节码必得重启,那给开拓和测量试验进度招致了极大的困难。动态的法子尽管能够在运转期注入字节码落成动态增加,但不曾统生机勃勃的API相当轻便操作不当。基于此,大家利用动态编织的主意、规范的API来标准字节码的更动——Agent组件。

Agent组件:由此JDK所提供的Instrumentation-API完成了应用HotSwap本事在不重启JVM的情景下促成对自由方法的进步,无论大家是做故障演习、调用链追踪(QTrace)、流量摄像平台(Ares)以至动态扩大日志输出BTrace,都需求四个具有无侵入、实时生效、动态可插拔的字节码巩固组件。

Agent的风云模型

如图所示,事件模型首要可分为三类事件:

图片 6

BEFORE在艺术实施前事件、THROWS抛出非常事件、RETU福特ExplorerN再次回到事件。这三类事件能够在章程实施前、重返和抛出非凡那三种景况做字节码编织。

正如代码:

// BEFORE

try {

/*

* do something...

*/

foo();

// RETURN

return;

} catch (Throwable e) {

// THROWS

}

事件模型能够完结多个成效:

  • 在方法体试行从前平昔再次回到自定义结果对象,原有办法代码将不会被实践;
  • 在方法体再次回到早先再一次布局新的结果对象,以致足以变动为抛出极其;
  • 在方法体抛出万分之后再一次抛出新的卓绝,以致能够更换为常规重临。

Agent怎么着制止“类污染”

在付出Agent的时候,第一个利用是故障演习平台,那么此时实在我们并无需Agent实行的进程中有自定义结果对象的回来,所以率先个本子的Agent选择硬编码的措施举办动态织入:

图片 7

故障类加载模型

先是介绍下多少个类加载器:

  • BootstrapClassLoader教导类加载器加载的是JVM本人供给的类,那个类加载使用C 语言实现的,是虚构机自个儿的大器晚成局地;
  • ExtClassLoader它担当加载<JAVA_HOME>/lib/ext目录下照旧由系统变量-Djava.ext.dir内定位路线中的类库;
  • AppClassLoader它担当加载系统类路线java-classpath或-D java.class.path钦点路径下的类库,相当于大家平常采用的classpath路线;
  • CommonClassLoader以至上边的都是汤姆cat定义的ClassLoader。

Agent和连锁的lib会放到AppClassLoader那风流倜傥层去加载,利用Javasist做字节码的织入,所以Javasist的加载器便是AppClassLoader。

但是想改动的是汤姆cat WebClassLoader所加载的com.xxx.InvocationHandler那一个类的Invoke方法,差别的ClassLoader之间的类是无法相互拜会的,做字节码的退换并无需那个类的实例,也无需回到结果,所以能够透过Instrument API获得那几个类加载器,而且可以根据类名称获取到那一个类的字节码举办字节码调换。故障类Drill.class和变形后的com.xxx.InvocationHandler.class重新load到JVM中,实现了插桩操作。

以Dubbo为例表明下何以注入故障和肃清故障:

图片 8

Dubbo调用的流入进程

  • 服务A调用服务B在Client端的Proxy层做AOP;
  • 伊始Agent况且生成八个Drill类invoke方法,抛出多少个运维期相当;
  • 字节码变形:在代码第意气风发行早前扩大Drill.invoke(卡塔尔(英语:State of Qatar);
  • 若果想退换分外类型,退换Drill类就可以,换来Sleep 3s ClassRedifine随后会重复load到JVM达成故障类型的倒车也许杀绝。

超出的主题材料

下面的议程相同很完美的解决了难点,但是随着平台的选择专业线要对广大接口和方法同一时间开展故障演习,那么大家转移的Drill类里面就能有种种:

if method==业务线定义方法

do xxx

与此同一时间超轻易拼接出错并且难以调节和测量检验,只好把转变的类输出为文件,查看本人写的字节码编写翻译成class文件是还是不是科学,差不离太优伤了!

怎么清除?

新的布局要求缓和多个难点:

  • 类隔开的难题:不要污染原生APP;
  • 事件的得以达成是可编写翻译的;
  • 支撑回到自定义的结果。

下风度翩翩版本的Agent达成就爆发了,把持有Agent的类和兑现的功用抽象出来,放到一个自定义的AgentClassLoader里面,字节码注入到对象应用程式后方可通过反射的艺术来调用具体的事件落成。

图片 9

类加载模型

  • 在BootstrapClassLoader里面注入Drill类作为通讯类;
  • Agent会选取命令,依照事件类型对InvocationHandler做字节码变形,注入到对象应用程式;
  • 在对象APP调用的时候,调用Drill.invoke(targetJavaClass,targetJavaMethod, targetThis, args)传递过来多少个参数(目的类、方法、实例、本太子参数等);
  • Drill类通过反射的诀要调用AppClassLoader里面包车型客车切切实实事件完成,举例BEFORE事件的举办代码,来完毕注入后的逻辑实行。

Agent的总体布局

Agent的生机勃勃体化结构如图所示:

图片 10

  • 支撑区别的模块的投入,比方Mock、流量录像、故障演习等;
  • 支撑QSSO的权杖验证;
  • 支撑测量试验和虚假情形的无资金接入;
  • 支撑活动安顿无需人工参预;
  • 支持种种故障命令的昭示和施行、 超时 、格外以至数额的回来;
  • 扶助艺术等级的编写制定甚至代码试行流程的编制;
  • 支撑在从心所欲的Web容器实行Agent代理。

四、怎样使用

利用的功利是很显眼的:

  • 零开支接入,无需申请其余财富;
  • 故障注入灭亡,无需重启服务;
  • 可以提供具备集群的拓扑构造。

可是什么才具准确选取呢?如下图所示:

图片 11

使用办法

步骤一、输入AppCode;

步骤二、接纳故障方法;

步骤三、钦定机器;

步骤四、注入故障。

五、总结

故障演练平台最中央的就是Agent组件——字节码编织框架,那些框架是纯Java的基于Instrumentation-API的AOP解决方案。它能够方便研究开发职员对于字节码插桩拆桩操作,能够非常轻易的贯彻故障练习、流量录像甚至别的的使用模块。

作者:王鹏

根源:Qunar本事沙龙订阅号(ID:QunarTL)

dbaplus社会群众体育应接广大能力职员投稿,投稿邮箱:editor@dbaplus.cn归来微博,查看越来越多

主编:

版权声明:本文由六合历史开奖记录发布于六合联盟宝典大全,转载请注明出处:去哪儿系统高可用之法,Android性能监控实现原理