OSGi 的核心配置、动态化及问题

一、OSGi的核心组件Bundle,与java中jar包的差别就是元数据配置:

常用的Bundle元数据定义:

a) Bundle-Activator:定义Activator的实现全限定类名称,此类必须实现BundleActivator接口,并实现start和stop方法。当Bundle被OSGi容器启动或停止时就会去调用start和stop方法。Bundle-Activator并非是必须的,只有在需要初始化或是销毁资源时才有用,并且不推荐在start方法中进行复杂的处理,以免加重OSGi容器启动的负担。在Spring DM中,ApplicationContext的创建和销毁就是在extender中通过实现BundleActivator接口实现的。

b) Bundle-Classpath:有些jar文件是某个Bundle专属的,此时就应该把这些jar包设置到Bundle的classpath中。比如JDBC驱动等Jar包。

c) Bundle-ManifestVersion:设置Bundle所遵循的OSGi规范版本,目前情况下应该将该值设置为2,表示OSGi R4版本。

d) Bundle-Name:必须的,Bundle的名称,与Bundle-SymbolicName对应,Bundle-Name类似于name,而Bundle-SymbolicName类似于id。

e) Bundle-SymbolicName:必须的,Bundle的唯一标识名,一般推荐采用类包的机制,保证其唯一性。

f) Bundle-Version:必须的,Bundle的版本号,可用于在import-package中进行过滤。

g) Export-Package:Bundle对外公开的可被其他Bundle导入的包。

h) Import-Package:Bundle需要导入的包。Bundle自身使用到的所有依赖类必须被Import进来,否则就会在OSGi容器启动时抛出NoClassDefFoundException或ClassNotFoundException异常。

i) Require-Bundle:Bundle引用到的其他Bundle,应该将该属性值设置为被引用Bundle的Bundle-SymbolicName属性。

j) Fragment-Host:指定被附属的Bundle的Bundle-SymbolicName,在前面部分已经进行了详细的讨论。

二、OSGi组件的热部署

一般Java的热部署(在不重启JVM的情况下替换class文件)只能适用于方法体的修改,如果是更大的类结构的修改则需要自定义类加载器,而OSGi号称可以实现模块的热部署,但再实际用时也有不少注意事项:

OSGi最吸引人的特性除了模块化之外,就是动态化了,在我之前写的OSGi实战以及进阶两篇Opendoc中,都有相关的示例,但不知道大家有没有注意,在两篇Opendoc中都未提及到bundle本身的更新,而基本都是以新增服务实现的bundle以及停止服务时限的bundle为例,并且相对而言是个比较简单的例子,动态化在java界更明确的词也许是hot deployment,而hot deployment的实现并不容易,同样,即使你采用OSGi,但也不代表你的应用就具备了hot deployment的能力,在hot deployment上,完美的结果就是当更新完成后,新的执行请求就在新的代码逻辑上正确的执行,就像没发生过更新这回事样,但实际要做到这样的效果,远没这么容易,即使是基于OSGi也同样如此,No magic & no silver bullet,在本篇blog中我们就来具体的看看。

OSGi以Bundle为粒度来实现动态化,也就是说,如果要更新一个类,需要做的是更新整个Bundle,虽然比直接部署一个类麻烦了点,但也还算是不错的了,更新的方法有两种,一种是直接update该bundle(在MANIFEST.MF中增加Bundle-UpdateLocation来指定Bundle更新时所使用的文件);另外一种是先uninstall旧的bundle,然后再安装并启动新的bundle,无论是哪种方法,对于OSGi的应用而言,问题就在于package的类的改变以及bundle中OSGi服务实现的改变。

在Equinox中,当update一个Bundle时,如果这个Bundle中有对外暴露的package,如果这个Bundle是singleton模式,在update后仍然保留了同样的Bundle SymbolicName的话,其实是无法update成功的,会报出一个已经有相同的Singleton的Bundle存在,因此update这种方法仅适用于没有对外暴露package的bundle,如bundle没有对外暴露的package,Equinox则可正常的完成update过程,通常来讲,不对外暴露package的bundle都是一些对外暴露OSGi服务或者使用OSGi服务的类,对于对于暴露OSGi服务的类而言,在update过程中将会把依赖了此OSGi服务的OSGi组件的实例销毁(递归),等当前bundle更新并启动完毕后,会重新实例化该OSGi组件,同时将新的服务实现对象设置进去,对于仅使用其他Bundle提供的OSGi服务的类而言则很简单了,在启动此bundle时自然会设置进来,同时也不会影响外部bundle。

从上可见,通过update方式来完成Bundle的更新受到了很大的限制,毕竟大部分时候Bundle都是singleton的,并且在更新的时候也是不会去改变其Bundle SymbolicName。

因此,在Equinox中要实现Bundle的更新,通常都使用另外一种方法,就是uninstall,然后再install并start更新后的bundle。

当uninstall时,如果此bundle有对外暴露的package,并且有使用这些package的bundle,那么Equinox会保留此Bundle的classloader,也就是说原来使用了这些package的bundle仍将使用之前bundle的类,这也是为什么一个Bundle uninstall了之后,其他Bundle仍然可使用该Bundle中export的类,要想让Bundle对外export的package的引用也失效并且切换到新的bundle中export的package,必须执行refresh动作,refresh时equinox将会找到之前uninstall没完全成功的bundle,并递归找到使用了这个bundle中package的bundle,将这些bundle的状态也置为unresolve,并解除对之前uninstall没完全成功的bundle的classloader的引用,这样被uninstall的bundle的class就能被GC卸载了,在此之后,Equinox会尝试再次去resolve之前设置为unresolve的bundle,如果resolve不了则会调用这些bundle的stop方法,卸载其对外提供的OSGi服务以及引用的OSGi服务,同时将其状态置为INSTALLED;但在uninstall时,对OSGi服务的处理方法则不太一样,此Bundle中所引用的OSGi服务会被释放,对外提供的OSGi服务也会注销,这会造成引用了这些OSGi服务的Bundle的OSGi组件(递归)的实例会被销毁。

完成了以上的动作后,可以安装新的bundle,安装新bundle时,其实就是做了些解析bundle的事情,直到start bundle时,才开始resolve过程,所谓resolve就是找到bundle对外提供的package、需要引用的package等,同时创建bundle的classloader,在这个过程,equinox也会对系统中所有unresolved的bundle进行resolve,如能够resolve则将其状态转化为resolved,最后调用BundleContext的start来完成bundle的启动,这个过程仅仅是在配置了BundleActivator的情况下才有意义,DS则完成此bundle中引用的OSGi服务或对外提供OSGi服务的组件的条件的检测,以判断这些组件是否可实例化,如有新的OSGi服务可对外提供,那么DS会检测此时其他Bundle中的OSGi组件是否需要被激活,或者是否需要调用其他Bundle中OSGi组件的set方法。

根据以上这样的描述,可以看出,在OSGi中如果要更新没有对外提供package的Bundle是比较容易的,update以及uninstallàstart都是可选的方法,而对于对外提供了package的Bundle而言,则相对复杂很多,只能选择uninstallàrefreshàstart来完成。

从两个纬度来看OSGi的动态化,对于有export-package Bundle的更新,OSGi将会重建更新的bundle以及引用了此bundle的package的ClassLoader,而对于OSGi服务组件的更新,OSGi则会重新创建引用了此OSGi服务的组件的实例,并通过unset这样的方法通知原来的组件释放对OSGi服务的引用,同时通过set方法来给新创建的实例注入更新后的OSGi服务组件实例,其实这也是hot deployment中常见的对于引用变更的处理方法。

但从上面也可以看出,OSGi并没有提供对象状态保留的处理,这也就意味着,基本上在一次更新后,此次更新的Bundle以及相关的bundle因为classloader的重建,其对象的状态数据都丢失了,不过对于更新的仅为提供或引用OSGi服务的Bundle而言,则稍微好点,毕竟只是影响到了递归的引用了OSGi服务的组件,组件由于重建实例,而导致状态数据丢失,这个倒是可以通过将服务的引用数量设置为cardinality=”0..1”或cardinality=”0..n”来解决,设置成这样的条件后,即使引用了需要更新的Bundle中提供的OSGi服务,其OSGi服务组件实例也不会被重建,这对于需要将OSGi服务引用提供给外部使用的系统而言,无疑非常有帮助。

根据以上所述,可以看到,即使是基于OSGi,要实现hot deployment还是比较麻烦的,No magic and no silver bullet,J,尤其是要注意classloader的重建以及OSGi服务组件实例的重建,否则很有可能会造成在更新后系统的异常,在基于OSGi实现hot deployment时,要合理的规划系统,常见的一些较好的实践方法有:

l  接口和实现分离

避免因为实现逻辑要更新,而造成其他引用了此Bundle export出去接口所在的package而导致classloader的重建。

l  对于需要保留状态数据的OSGi服务尽量避免引用其他bundle export-package中的类

这也是为了避免这些类所在的bundle的classloader重建,毕竟OSGi服务组件类可以通过设置cardinality来保持组件实例的不变。

l  服务组件采用cardinality=”0..1”或cardinality=”0..n”来设置对OSGi服务的引用

避免服务组件实例的重建,毕竟这是个递归过程,影响还是很大的,而且谁也不敢肯定这么多的服务组件实例的重建是不是会造成系统的异常现象。

在这种情况下,尤其要注意unset中的处理以及当没有可用服务情况下的处理,避免出现NPE。

l  尽量采用OSGi服务组件服务方式,而不是直接的类方式

由于类方式的更新成本实在是比较的高,毕竟那需要classloader的重建,但是有些类确实是没办法的,对于这些类要尽量的保证稳态。

l  严格的版本控制

毕竟接口的更新影响是很大的,因为所有实现接口的类都得改变,因此需要严格的制定版本规范,并在引用package时按照版本规范指定相应的版本范围。

http://www.blogjava.net/BlueDavy/archive/2009/04/29/268227.html

三、OSGi的问题及失败案例

淘宝HSF组件中间版本做过OSGi的尝试,最终还是因为它的使用它的代价大于好处而放弃。

在改造这个基础产品时,我看到的使用OSGi的问题是:
1. 对没有接触过OSGi的Java开发而言开发习惯绝对是巨大的挑战
通常都会使用Maven来管理Java工程,肯定很希望mvn eclipse:clean eclipse:eclipse就可以生成导进eclipse里没问题的project吧,但对于OSGi而言,如果是依赖外部的非OSGi Bundle的jar,那么则需要在META-INF/MANIFEST.MF里写明,也就是不是仅仅修改pom.xml就可以的;另外一点是OSGi对于其他bundle的jar的依赖,不是通过pom.xml去增加依赖,而是直接import package或require-bundle之类的,并且要求这个bundle是已经安装了的(可以想象,如果是业务型的应用,那得装多少bundle…),否则在eclipse之类的ide里再去import什么的时候会找不到,同时为了确保mvn clean package之类的还是能用,因此会被逼在开发的时候要同时维护pom、MANIFEST.MF。
上面的这两个问题要解决好,可以通过开发IDE插件,但这个插件是不太好做的…
而OSGi的classloader机制则会给初入门的带来很多疑惑,会觉得经常碰到各种各样的class找不到等问题。
测试也是个麻烦,因为得把所有的bundle都装进framework,否则单元测试就得全部靠mock了。
另外一点在文件的依赖上就更折腾了,OSGi只能是通过require-bundle来去获取需要依赖的文件,否则是做不了的。

2. 动态化
OSGi确实具备了很强的动态化机制,但这里的要求是必须对OSGi bundle/OSGi Declarative Services的生命周期管理机制非常清楚,否则设计出来的系统其实是完全不可动态化的,具体的细节大家可以看看我之前写的另外一篇文章

而仅仅借助OSGi的动态化机制,其实是不足以实现真正的热部署的,这里的一个原因是通常代码里是带状态信息的,或者说一些全局变量信息,而OSGi的替换其实主要是通过创建新对象实例,然后替换引用的方式来实现,这也就意味着对于有状态信息的,得自己处理好状态的保存以及还原,否则是会有问题的,我们当年为了在通信层面做到这点,折腾了一个多月还是没搞定,而且系统变得超级复杂。

另外还有个更麻烦的是,如果应用是OSGi和非OSGi混用,又要做动态化,那就得让非OSGi拿到的只是一个OSGi里对象的一个假的引用,以便随时替换,这个改造起来就更麻烦了。

当年那个基础产品改造完后,确实享受到了和应用隔离带来的好处,动态化在生产环境也玩过下,确实挺爽,但付出的代价是极大的,并且要做到真正的完全动态是不行的,也就意味着动态化这特性基本就是个玩具,因此后来在回顾这次技术决定时,我一直都承认,这是我做的一个最技术的,最失败的决定,如果是可以重来,我会选择不用OSGi,而是自己做一个简单的classloader隔离机制。

所以在那之后,当其他人问我是否可以在一些场景选择OSGi时,我给的建议都是:
如果你的场景不是对动态化(并且是无状态的那种动态化)有强烈的需求,那不要选OSGi。
仅仅是为了模块化,隔离这些,还不如自己做一个简单的实现,并且可以遵守现在的开发习惯,不要去挑战众多人的开发习惯和通用的知识体系,那对系统维护来说绝对会是一个灾难,只能说或许等到将来Java从语言级支持了可能才OK。

至于为什么众多的AS(例如Weblogic、JBoss)这些会选择基于OSGi,我觉得有一点很重要的是在没有选OSGi之前,他们对外提供的(销售的)版本是打包性质的,不能由用户来选择,这显然对销售会有限制,而如果能提供插件样的选择,则是一件好事,另外一个选择的原因很有可能是他们认为OSGi会成为语言级的标准,那意义就比较大了,不过可惜从目前来看,这估计不太可能。

至于众多人爱的eclipse,为什么选用OSGi,很大程度是看中了OSGi的动态化,以及减少了自己制定一个插件标准的折腾,eclipse这类插件的场景相对是比较好发挥动态化的(因为插件之间的交互依赖通常不多),尤其是在新装/停止一个插件时,更新会比较麻烦一些。

来源: http://hellojava.info/?p=152

四、OSGi与微服务

osgi最明显的缺陷

  • bundle尽管可以为隔离的服务建立独立生命周期管理的热部署方式,以及明确的服务导出和导入依赖能力,但是其最终基于jvm,无法对bundle对应的服务实现计算资源的隔离,一个服务的故障依然会导致整个jvm crush,这使得在一个运行时的osgi上部署模块级服务只获得了模块部署和启停隔离,服务明确依赖的好处,但是没办法实现计算节点的线性扩展,在当前分布式,微服务,网络计算的趋势下,使得osgi只适合构建单一服务节点的内部应用,但是其分离的bundle的部署负担对于微服务架构来说,有点用大炮打蚊子的臭味。

推荐的应用架构方式

  • 因此必须将基于进程间构建的分布式应用和进程内的单一应用分开来架构设计,对于进程间构建的分布式应用,采取基于soa的理念进行容器模式的服务部署模式,服务交互基于远程服务交互相关协议,采用可忍受网络失败的架构设计原则;
  • 对于进程内的应用,如果需要模块级的独立生命周期热部署和模块管理,可以考虑采用OSGI,但是,容器内基于本地进程间通信的模块交付方式不仅能提供同样的独立生命热部署和模块管理,而且具备随时脱离出去部署成单独容器级服务应用的能力,加速进程间的服务交付提供的整体管理和监视环境基础.
  • osgi还有用武只地吗?当然我前述都是以构建分布式企业和面向互联网这类应用为前提来讨论的,对于嵌入式的jvm应用,比如著名的osgi案例宝马的车载系统,osgi依然是最好的原则,不过我怀疑基于andriod系统的机制构建类似应用,osgi的采用依然值得商榷。因此,osgi确实面临鸡肋之嫌。

 分布式应用的关键技术点及解决思路汇总

  • 为什么要分布 
    为得到吞吐量和可靠性及故障隔离的架构属性,需要将传统的单一应用按照业务逻辑进行垂直拆分以实现构建工程的独立,部署的独立。
  • 分布失去了什么 
    • 进程内服务调用的便利性和可测试性
    • 代价巨大的资源分布导致的跨资源事务能力
    • 部署和运维工作量指数级增长
    • 不可靠网络的应用状态一致性
    • 及其复杂的分布式应用依赖关系
  • 分布式关键技术选择 
    • 容器级的分布式应用工程和部署管理;
    • 可视化的分布式应用及服务监视管理视图;
    • 前端和后端应用的分离;
    • 客户端路由
    • 服务注册中心
    • 分布式协调
    • 消息中件间
    • 分布式存储
    • 集成框架

      来源: http://blog.csdn.net/itd018/article/details/51035176

来自为知笔记(Wiz)

时间: 2024-10-31 10:25:26

OSGi 的核心配置、动态化及问题的相关文章

JAVAWEB开发之Hibernate详解(一)——Hibernate的框架概述、开发流程、CURD操作和核心配置与API以及Hibernate日志的使用

Hibernate框架概述 什么是Hibernate? 框架:软件的半成品,完成部分代码的功能. Hibernate:Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思想来操作数据库.Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序中使用,也可以在Servlet/JSP的web应用程序中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成

第二十天 TCP 及socket通信原理、http协议及web服务、httpd核心配置详解

一.TCP及socket通信原理详解 二.http协议及web服务原理(一) 三.http协议及web服务原理(二) 四.httpd核心配置详解 1.tcp.udp是一种传输协议,实现进程地址标记,套接字是一个虚拟设备,用来表明主机上的某个进程      众所周知:0-1023:管理员才有权限使用,永久地分配给某应用使用(由IANA分配)      注册端口:1024-41951:只有一部分被注册,分配原则上非特别严格.      动态端口或私有端口:41952-65535:由内核分配临时端口,

006-spring cloud gateway-GatewayAutoConfiguration核心配置-GatewayProperties初始化加载、Route初始化加载

一.GatewayProperties 1.1.在GatewayAutoConfiguration中加载 在Spring-Cloud-Gateway初始化时,同时GatewayAutoConfiguration核心配置类会被初始化加载如下 : NettyConfiguration 底层通信netty配置 GlobalFilter (AdaptCachedBodyGlobalFilter,RouteToRequestUrlFilter,ForwardRoutingFilter,ForwardPat

JavaWeb_(SSH)struts.xml核心配置、动态方法调用、结果集的处理

前导博文 JavaWeb_(SSH)使用Struts框架实现用户的登陆 传送门 JavaWeb_(SSH)Struts创建Action的三种方式 传送门 核心配置 动态方法调用 结果集处理 一.核心配置 struts.xml <!-- name:配置包名 namespace:给action的访问路径定义一个命名空间 --> <package name="MyPackage" namespace="/user" extends="strut

Mybatis学习——Mybatis核心配置

MyBatis的核心配置 在使用MyBatis框架时,设计两个核心的d对象:SqlSessionFactory和SqlSession. SqlsessionFactory SqlSessionFactory是单个数据库映射关系经过编译后的内存镜像,其主要作用用来创建SqlSession对象,SqlSessionFactory实例对象是可以通过SqlSessionFactoryBulider对象来构建,而SqlSessionFactoryBulider对象可以通过XML文件或者Configurat

Nginx 核心配置详解

目录 Nginx 核心配置详解 Nginx 四层访问控制: Nginx账户认证功能: 自定义错误页面: 自定义访问日志: 检测文件是否存在: 长连接配置: 作为下载服务器配置: 作为上传服务器: 其他配置: Nginx 核心配置详解 Nginx 四层访问控制: 准备两个客户端,做访问测试使用. centos7 IP:192.168.39.7 centos6 IP:192.168.39.6 [[email protected] images1]#vim /apps/nginx/conf/conf.

ASP.NET Core搭建多层网站架构【5.1-WebCore网站核心配置】

2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[5.1-WebCore网站核心配置] 统一封装网站核心配置,注册跨域策略,实例化雪花算法,后期可扩展添加多语言支持 文章目录 此分支项目代码 本章节介绍了统一封装网站核心配置,注册跨域策略,实例化雪花算法,后期可扩展添加多语言支持 添加网站配置及跨域配置 在MS.WebApi应用程序appsettings.json中添加以下节点: "SiteSe

一、VIP课程:互联网工程专题 03-Maven基本概念与核心配置

概要: maven 基本概念 maven 核心配置 一.maven  安装与核心概念 概要: maven 安装 maven 编译(compile) 执行测试用例(test) maven 打包 maven  依懒管理 1.安装 官网下载 Maven (https://maven.apache.org/) 解压指定目录 配置环境变量 检查安装是否成功 (mvn -version) 2.maven 编译 maven 编译过程演示 l  创建maven项目. l  创建src 文件 l  编写 pom

osgi + camel + karaf配置日志输出

上篇博文中编了felix的一个简单example,然后演示了example的启动,Activator启动之后进行了控制台输出,但整个example feature的启动过程中那些bundle进行了install过程并没有演示,接下来将演示这一过程. 配置日志 首先对karaf的输出进行日志输出,先前在parent module中pom.xml中配置插件指定配置文件位置,如下所示: <plugin> <groupId>org.apache.maven.plugins</grou