一波三折,APM监控系统对于OSGI架构的探索实践

栏目: 服务器 · 发布时间: 6年前

内容简介:2017年我们一直在做分布式服务跟踪系统的升级改造,为了更好的服务于广大的开发和运维人员,能够在数以千万计的微服务系统中快速的发现问题、定位问题。一年下来,接入了上千个系统,快的几分钟,慢的不到10分钟,最多就是处理一下jar包依赖冲突问题,所以能这么迅速的推广。不过前几天遇到一个客户,他们的系统采用的是OSGI的框架。OSGI这个词相信大部分人是听过没用过的。当我们的监控系统遇到了OSGI架构系统也是碰撞出了激烈的火花,足足用了两天的时间,才搞定了各种水土不服。我把期间的各种酸甜苦辣记录下来,希望为奋斗

2017年我们一直在做分布式服务跟踪系统的升级改造,为了更好的服务于广大的开发和运维人员,能够在数以千万计的微服务系统中快速的发现问题、定位问题。一年下来,接入了上千个系统,快的几分钟,慢的不到10分钟,最多就是处理一下jar包依赖冲突问题,所以能这么迅速的推广。不过前几天遇到一个客户,他们的系统采用的是OSGI的框架。OSGI这个词相信大部分人是听过没用过的。

当我们的监控系统遇到了OSGI架构系统也是碰撞出了激烈的火花,足足用了两天的时间,才搞定了各种水土不服。我把期间的各种酸甜苦辣记录下来,希望为奋斗在APM一线的同行们提供一点点帮助。

在整个过程中,我们遇到了四个技术关键点:

  • 把一个普通的jar包Bundle化。
  • OSGI的类加载机制。
  • 引入第三方非Bundle化的jar包。
  • 通过Activator在Bundle的启动和停止实现回调。

项目背景

首先我简单介绍下两个项目的背景:

监控系统:这是一个利用动态字节码技术,自动的无侵入的对目标系统实行秒级实时监控,监控内容包括但不限于容量、性能、成功率、调用链、应用拓扑等,详细内容请参考: http://os.51cto.com/art/201709/552641.htm

目标系统:分为两部分,第一部分以war包形式发布,部署到Web容器内,接收http请求;第二部分采用OSGI架构,每一个Bundle是一个业务服务(可以理解为Bundle就是微服务)。这两部分通过servlet的桥接方式连接,其中一些公用Bundle(如slf4j)会放在war包的指定目录中,每个业务Bundle不需要再单独引入。这个架构是我在解决了所有问题之后才总结出来的,在这里提前抛出是为了方便理解后边的内容。

一波三折,APM监控系统对于OSGI架构的探索实践

刚开始的时候,我们就把它当做一个普通的系统来接入,把我们的jar包放到了目标系统war包的WEB-INF/lib下,加入启动脚本,再启动系统,可以看到我们的jar打印出的启动日志,然后就没有然后了,除了那一行日志,没有任何效果。这个时候,我就意识到了OSGI系统不是这么玩的,于是开始OSGI的漫漫探索之路......

当看到OSGI的每一个Bundle包都是用单独ClassLoader负责加载时,我仿佛找到了解决方案。

我们把监控jar包打到了每一个业务的Bundle里边,然后重启,结果监控成功了,问题解决了。

从纯技术角度来看貌似是没什么问题,然而噩梦才刚刚开始。由于监控jar包打入了业务Bundle内,带来了两个比较棘手的问题:第一,技术上每个业务Bundle都需要这么去引入,加大了开发工作量,这个和我们极致的设计理念是不符的;第二,业务上每一个业务Bundle就成为了一个单独应用,客户方表示我们这个是一个应用,而不是多个应用。

所以还是要解决之前的问题,把整个目标系统所有的Bundle当做一个应用去接入监控。

我不熟悉目标系统和OSGI架构,客户开发方又不了解监控系统的原理和实现过程,经过了长时间的各种尝试和交流,仍没有丝毫进展。于是我们决定采用一个最原始,最简单,最笨,也是最有效的方法,从“hello world”开始。

当一个系统出现问题,你又不知道问题出在哪部分的时候,要么把这些“部分”一个一个去掉,直到发现没有“问题”为止;要么从“零”开始,然后一个一个的加入,直到出现“问题”为止,我们采取了后者。

首先,针对我们的监控jar包去掉基于动态字节码的自动监控,采用编程式的直接API调用,并且将API接口做了Mock,去掉了所有的第三方依赖。修改目标系统的代码,在希望被监控的方法中直接调用监控API。打包,部署,启动,被监控Bundle不能启动,报错......

Missing Constraint: Import-Package: com..................sgm...... 

经过和对方开发人员的沟通和网上查阅OSGI相关资料,了解到这是一个OSGI打包的规范问题。

之前我们的监控jar包是没有按照OSGI的规范去打包的,打的只是一个普通的jar包,所以在OSGI框架下,其他的Bundle是无法访问到我们的jar包中的类,造成启动报错的问题。这里遇到了我们第一个要解决的主要问题:

1. 普通jar包Bundle化

先看下边的两张图:

一波三折,APM监控系统对于OSGI架构的探索实践

一波三折,APM监控系统对于OSGI架构的探索实践

上边的两张图是jar包中META-INF/MANIFEST.MF文件,第一张图是普通jar包,第二图是具有OSGI规范的jar包。既然知道了,那这样的OSGI的包怎么打?我们用maven来编译,顺理成章的就找到了maven-Bundle-plugin的这个插件,使用很简单,代码如下:

<plugin> 
<groupId>org.apache.felix</groupId> 
<artifactId>maven-Bundle-plugin</artifactId> 
<version>3.3.0</version> 
<extensions>true</extensions> 
<executions> 
<execution> 
<id>Bundle-manifest</id> 
<phase>process-classes</phase> 
<goals> 
<goal>manifest</goal> 
</goals> 
</execution> 
</executions> 
<configuration> 
<instructions> 
<Import-Package>org.slf4j</Import-Package> 
<Export-Package>com.wangyin.sgm.client</Export-Package> 
<Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment> 
</instructions> 
</configuration> 
</plugin>

在这么多属性中, Import-Package和Export-Package尤其重要,一个jar包就是一个Bundle,Bundle和Bundle之间的访问全靠这两个属性来控制。

Import-Package:说明了这个Bundle jar需要调用外部其他Bundle的哪些包(package)。

Export-Package: 说明了这个Bundle jar可以提供哪些包(package)供其他Bundle去调用。

当一个Bundle启动的时候,会分为Resolved(解析),Installed(安装),Started(激活)等几个步骤,解析就是检查jar是不是正常,安装的时候会把Export-Package中的指定的包进行注册,这就知道哪些包由哪些Bundle来提供,激活的时候会检查当前这个Bundle的Import-Package依赖的包是不是都有对应的Bundle来提供,否则激活失败。

修改监控客户端,把父项目,子项目,子子项目全部按照OSGI规范打包,又是一遍部署重启,这次被监控的业务Bundle没有报错,而是监控客户端启动报错:

Missing Constraint: Import-Package: org.slf4j 

有了刚才的经验,一看就知道是没有slf4j,可是对方开发人员反馈slf4j是有的,他们自己也在用,这就奇怪了。

这时我发现还有在监控客户端里MANIFEST.MF的一句话org.slf4j;version="[1.7,2)",version这个词引起了我的注意。

原来在目标系统里的slf4j是1.5版本,而监控客户端要求1.7版本,slf4j降级再试,这次目标系统成功调用到了监控客户端的API,打印出了日志,万里长征终于迈出了第一步。但这只是一个Mock的版本,真正的监控程序还没有加入。

先小结一下:首先在OSGI的框架下,所有jar必须按照OSGI的规范去打包,否则不能使用;其次要对Import-Package和Export-Package配置好,需要依赖外部哪些包,自己又可以提供哪些包供外部使用,同时注意版本,依赖时一般都是要高于哪个版本。

接下来,就要加入真正的代码跑一跑,根据前面的经验还是要慢慢来,我们分两步加入代码,第一次先加入监控代码,待成功后加入网络传输部分的代码,后边遇到的坑让我们发现这个决定是正确的。

打包部署启动调用这个流程已经很熟练了,报错还是如期出现了,这次报错的是com.lmax的disruptor这个第三方开源jar包:

Missing Constraint: Import-Package: sun.misc 

纳尼?sun.misc没有?这不是jdk里的吗,为什么会没有呢?于是,又是一番资料查找,终于明白了其中的道理。

这里就要讲到OSGI的类加载机制了,它并不是我们常说的双亲委托机制。关于这个问题,网上已经有很多文章介绍了,但这里我还是结合这个例子简单的说一下。

2. OSGI的类加载机制

首先每一个Bundle(jar)都会被一个单独的ClassLoader去加载,当一个Bundle的ClassLoader尝试去加载一个类的时候:

  1. 如果这个类的包名是java.*开头的,那么直接交给bootstrap ClassLoader去加载。
  2. 查看这个类是否在OSGI的配置文件中有org.osgi.framework.bootdelegation属性定义,如果定义了,交给bootstrap ClassLoader去加载这个类。
  3. 查看这个类是否在OSGI的配置文件中有org.osgi.framework.system.packages属性定义,如果定义了,交给父类加载器去加载,一般就是AppClassLoader。
  4. 查看这个类是否在本Bundle的MANIFEST.MF文件的Import-Package中定义,如果定义了,交给Export-Package这个类的Bundle去加载。
  5. 在上边条件都不满足的时候,那这个类就是自己的Bundle的内部类,由自己的ClassLoader去加载。

现在我们来看一下,下边这张图是disruptor的MANIFEST.MF文件:

一波三折,APM监控系统对于OSGI架构的探索实践

在disruptor里使用了sun.misc.Unsafe类,在启动disruptor的时候,需要加载sun.misc.Safe,那么1,2,3点都不满足,命中了第4点,就开始寻找带有Export-Package的Bundle,那肯定找不到。

遵循上边的原则,在配置文件中加入了org.osgi.framework.bootdelegation=javax.*,sun.*,同时又把disruptor中的Import-Package删掉了,问题成功解决了,又向胜利迈进了一步。

接下来,就要把网络传输这部分加入进来了,系统就可以监控了,数据需要发送出来才可以真正的使用。有了之前的经验,感觉应该问题不大,然而现实情况并不是这样……

当把这部分代码和依赖加入后,各种的Missing Constraint: Import-Package: xxx。我发现所有的依赖的第三方jar都在里边,为什么还会报错,我开始怀疑是这些第三方jar本身的问题。

打开这些jar文件的MANIFEST.MF文件查看,果然如此,我们依赖的这些jar有多一半都不是Bundle化的jar包,这里就涉及到了本文第三个要解决的核心问题。

3. OSGI如何加载第三方非Bundle化的jar包

OSGI如何加载第三方非Bundle化的jar包,有如下几种方式:

  • 通过父类加载器加载,也就是配置org.osgi.framework.system.packages。
  • 将jar转换成Bundle,然后Export-Package。
  • 把jar打包进引用方的Bundle。

第一种方式需要目标系统配置,同样不符合我们的设计理念,显然不合适,于是我们首先尝试了第二种方式,重新打包那些非Bundle的第三方jar。

在这个过程中,我们发现这绝对是个苦逼的活,需要找到源码,下载源码,修改pom,有的找源码很费劲,有的还是ant编译的,有的虽然是maven管理,但又依赖了父项目……

总之想顺利的重新打包是个很费劲的事。看来只剩下第三条路了,这又要退回到之前第一个问题,如何打一个Bundle jar。

之前我们是把maven工程的每一个子项目分别Bundle化,如果要把第三方jar打入Bundle,那就有可能一个第三方jar被多次打入不同的子项目Bundle,造成浪费。

所以决定放弃对原有项目的每个子项目单独Bundle化的方案,而是新建一个子项目,由这个子项目引入所有的其他子项目和第三方依赖jar,把他们所有打成一个大的Bundle jar。

<Import-Package>org.osgi.framework,org.slf4j</Import-Package> 
<Export-Package>com.wangyin.sgm.client</Export-Package> 
<Private-Package>com.wangyin.*,com.lmax.disruptor.*,…… 
</Private-Package>

看一下上边的配置,比之前多了一个Private-Package,就是说哪些Package是这个Bundle的内部包,也就是要打入最后Bundle jar的东西。

现在通过编程式API直接调用的方式已经可以监控到目标系统了,最后要做的就是引入运行时字节码增强技术。

还是按照常规的方式,把我们的Agent通过javaagent方式启动,有了之前的经验,我知道这个是被bootstrap ClassLoader加载的,于是就直接在org.osgi.framework.bootdelegation中加入了监控Agent的包名。

结果启动正常,但是无法自动监控,看了日志后发现是监控客户端没有启动。监控客户端的启动是通过在每个类加载的时候,尝试性的使用它的类加载去加载监控客户端的启动类。

如果可以加载上,那么就启动成功了,因为启动程序是放在了启动类的static块中,且启动类是一个单例模式,记录着被监控应用的信息。

从这个启动日志来分析,被监控系统有200多个Bundle,每个Bundle启动都去加载监控客户端,然后我们希望监控客户端被它自己的类加载器去加载。这里就是本文第四个核心问题。

4. 如何在Bundle启动的时候去做一些初始化操作

在OSGI的规范里提供了一个叫BundleActivator的接口,里边有start和stop两个方法,顾名思义,在Bundle启动和停止的时候会回调这两个方法,这就好办了,我们可以在start方法中实现启动监控客户端的代码。

<instructions> 
<Bundle-Activator>com.wangyin.sgm.client.Activator</Bundle-Activator> 
<Import-Package>org.osgi.framework,org.slf4j</Import-Package> 
<Export-Package>com.wangyin.sgm.client</Export-Package> 
<Private-Package>com.wangyin.*,com.lmax.disruptor.*,org.apache.flume.* 
        ,org.apache.avro.*,com.thoughtworks.*, 
        ,org.apache.commons.compress.*,org.apache.commons.lang.* 
        ,org.codehaus.jackson.*,org.jboss.netty.*,org.apache.velocity.* 
        ,org.xerial.snappy,org.tukaani.xz.* 
</Private-Package> 
<Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment> 
</instructions>

上边的Bundle-Activator这个标签,指定这个Bundle的Activator是哪个类。

至此所有的问题都得到了解决,这次OSGI应用接入APM监控足足花掉了两天的时间。

由于负责监控系统的人员并没有使用过OSGI,被监控目标系统的开发人员也不知道监控的原理是什么,一开始我们都以两个产品整体去接入,做了许多的无用功,耽误了很多时间。

从这个案例可以看出,当你所做的东西需要应用到一个你不熟悉的技术领域的时候,又不可能有足够的时间去学习这个领域的知识,有一个最好的办法就是改造你所做的系统,从零开始,逐渐加码,去适应那个不熟悉的领域。

千万不要想着能整体一下解决所有问题,因为可能要解决所有问题有100个技术点需要去修改,这100个问题同时暴露,你只有把它们同时都修改了,才能看到你的成果,这是根本不可能的事情。

而这100个问题一个个暴露出来,把一个不可能完成的大任务拆分成若干个可完成的小任务,修改一个,看到一步成功的效果,问题就得到了解决。

作者简介:

一波三折,APM监控系统对于OSGI架构的探索实践

张晨, 资深研发工程师,目前任职京东金融,曾任职于搜狐等互联网公司,擅长 Java 底层技术的研发及疑难问题的定位。从2015年开始从事智能运维监控平台的研发与实践,参与并主导了APM等产品的研发与应用,经历了多次618和双11的千万级TPS的运维保障,支撑了京东金融的大量业务应用。

一波三折,APM监控系统对于OSGI架构的探索实践

沈建林,曾在多家知名第三方支付公司任职系统架构师,致力于基础中间件与支付核心平台的研发,主导过 RPC 服务框架、数据库分库分表、统一日志平台,分布式服务跟踪、流程编排等一系列中间件的设计与研发,参与过多家支付公司支付核心系统的建设。现任京东金融集团资深架构师,负责基础开发部基础中间件的设计和研发工作。擅长基础中间件设计与开发,关注大型分布式系统、JVM 原理及调优、服务治理与监控等领域。

【责任编辑:wangxueyan TEL:(010)68476606】


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法分析(Java版)(英文原版)

数据结构与算法分析(Java版)(英文原版)

(美)Clifford A.Shaffer / 电子工业出版社 / 2002-5 / 39.00元

《数据结构与算法分析(C++版)(第2版)》采用程序员最爱用的面向对象C++语言来描述数据结构和算法,并把数据结构原理和算法分析技术有机地结合在一起,系统介绍了各种类型的数据结构和排序、检索的各种方法。作者非常注意对每一种数据结构的不同存储方法及有关算法进行分析比较。书中还引入了一些比较高级的数据结构与先进的算法分析技术,并介绍了可计算性理论的一般知识。本版的重要改进在于引入了参数化的模板,从而提......一起来看看 《数据结构与算法分析(Java版)(英文原版)》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码