osgi最明显的缺陷
- bundle尽管可以为隔离的服务建立独立生命周期管理的热部署方式,以及明确的服务导出和导入依赖能力,但是其最终基于jvm,无法对bundle对应的服务实现计算资源的隔离,一个服务的故障依然会导致整个jvm crush,这使得在一个运行时的osgi上部署模块级服务只获得了模块部署和启停隔离,服务明确依赖的好处,但是没办法实现计算节点的线性扩展,在当前分布式,微服务,网络计算的趋势下,使得osgi只适合构建单一服务节点的内部应用,但是其分离的bundle的部署负担对于微服务架构来说,有点用大炮打蚊子的臭味。
推荐的应用架构方式
- 因此必须将基于进程间构建的分布式应用和进程内的单一应用分开来架构设计,对于进程间构建的分布式应用,采取基于soa的理念进行容器模式的服务部署模式,服务交互基于远程服务交互相关协议,采用可忍受网络失败的架构设计原则;
- 对于进程内的应用,如果需要模块级的独立生命周期热部署和模块管理,可以考虑采用OSGI,但是,容器内基于本地进程间通信的模块交付方式不仅能提供同样的独立生命热部署和模块管理,而且具备随时脱离出去部署成单独容器级服务应用的能力,加速进程间的服务交付提供的整体管理和监视环境基础.
- osgi还有用武只地吗?当然我前述都是以构建分布式企业和面向互联网这类应用为前提来讨论的,对于嵌入式的jvm应用,比如著名的osgi案例宝马的车载系统,osgi依然是最好的原则,不过我怀疑基于andriod系统的机制构建类似应用,osgi的采用依然值得商榷。因此,osgi确实面临鸡肋之嫌。
分布式应用的关键技术点及解决思路汇总
- 为什么要分布
为得到吞吐量和可靠性及故障隔离的架构属性,需要将传统的单一应用按照业务逻辑进行垂直拆分以实现构建工程的独立,部署的独立。 - 分布失去了什么
- 进程内服务调用的便利性和可测试性
- 代价巨大的资源分布导致的跨资源事务能力
- 部署和运维工作量指数级增长
- 不可靠网络的应用状态一致性
- 及其复杂的分布式应用依赖关系
- 分布式关键技术选择
- 容器级的分布式应用工程和部署管理;
- 可视化的分布式应用及服务监视管理视图;
- 前端和后端应用的分离;
- 客户端路由
- 服务注册中心
- 分布式协调
- 消息中件间
- 分布式存储
- 集成框架
备注:最近研究camel,再次提到osgi的运行模式,再次反思分布式应用架构的技术栈选择,有了前述之感,姑且作为当前的想法笔记,免得后续在选择技术方向时又犹豫不决。
1.2 为什么使用OSGi
没有什么技术是万能的,任何一门技术都有它的适用场景和最佳实践方法。OSGi不只是一门技术,更多的是一种做系统架构的工具和方法论,如果在不适用的场景中使用OSGi,或者在适用的场景中不恰当地使用OSGi,都会使整个系统产生架构级的缺陷。因此,了解什么时候该用OSGi是与学会如何使用OSGi同样重要的事情。
每个系统遇到的业务环境都是不一样的,笔者不希望以经验式的陈述去回答“什么时候该用OSGi”或 “为什么要使用OSGi”这样的问题,而试图通过几个问题的讨论和利弊权衡,让读者自己去思考为什么这些场景适用OSGi。如果读者是第一次通过本书接触OSGi,那么可能对某些讨论的内容会感到困惑,笔者建议尽可能在阅读完全书或者在准备真正在项目中使用OSGi的时候,再回过头读一遍本节的内容。
1.2.1 OSGi能让软件开发变得更容易吗
不可否认,OSGi的入门门槛在Java众多技术中算是比较高的,相对陡峭的学习曲线会为第一次使用OSGi开发系统的开发人员带来额外的复杂度。
OSGi规范由数十个子规范组成,包含了上千个不同用途的API接口。OSGi规范显得这样庞杂的主要原因是实现“模块化”本身需要解决的问题就非常多。模块化并不仅仅是把系统拆分成不同的块而已—这是JAR包就能做的事情,真正的模块化必须考虑到模块中类的导出、隐藏、依赖、版本管理、生命周期变化和模块间交互等一系列的问题。
鉴于OSGi本身就具有较高的复杂度,“引入OSGi就能让软件开发变得更容易”无论如何是说不通的,小型系统使用OSGi可能导致开发成本更高。但是这句话又不是完全错误的,随着系统不断发展,在代码量和开发人员都达到一定规模之后,OSGi带来的额外成本就不是主要的关注点了,这时候的主要矛盾是软件规模扩大与复杂度随之膨胀间的矛盾。如图1-3所示,代码量越大、涉及人员越多的系统,软件复杂度就会越高,两者成正比关系。这个观点从宏观角度看是正确的,具体到某个系统,良好的架构和设计可以有效减缓这个比率。基于OSGi架构的效率优势在这时候才能体现出来:模块化推动架构师设计出能在一定范围内自治的代码,可以使开发人员只了解当前模块的知识就能高效编码,也有利于代码出现问题时隔断连锁反应。OSGi的依赖描述和约束能力,强制开发人员必须遵循架构约束,这些让开发人员“不*”的限制,在系统规模变大后会成为开发效率的强大推动力。
图1-3 引入OSGi对软件复杂度的影响
可以用一个更具体的场景来论述上面的观点,解析OSGi架构如何在开发效率上发挥优势。有经验的架构师会有这样的感受:设计一个具有“自约束能力”的系统架构非常不容易。最常见的情况是设计人员设想得很美好,开发人员在实现时做出来的产品却不是那样。大部分软件公司是通过“开发过程”、“编码规范”、“测试驱动”,甚至“人员熟练度”来保证开发人员实现的代码符合设计人员的意图。这样即使在开发阶段做到符合设计需求,也很难保证日后维护人员能够继续贯彻原有的设计思想;随着开发的时间越来越长,系统最终实现的样子可能和原有的设计产生越来越大的偏差。在软件工程中,将这种现象称为“架构腐化”。架构的“自约束能力”就是指限定不同开发人员在实现功能的时候,实现方式都是一致的,最好只有唯一一条遵循设计意愿的路可走,别的方法无法达到目的。更通俗地说就是,尽可能使程序员不写出烂代码。
举个最浅显的例子,如果有开发人员在Web层中使用DAO直接操作数据库,或者在DAL层直接从HttpSession对象中取上下文信息,这样的代码也许能逃过测试人员的黑盒测试,但是显然是不符合软件开发基本理论的。前者可能因绕过Service层中的事务配置而出现数据安全问题;后者限制了这样的DAO就只能从Web访问,无法重用和进行单元测试。如果项目中出现这样的代码,笔者认为首要责任在架构师,因为架构师没有把各层的依赖分清,如果Web层只依赖Service层的JAR包,那么程序员就无法访问到DAO,如果DAL层没有依赖Servlet API的JAR包,那么程序员就不可能访问HttpSession对象,这就是一种架构缺乏自约束能力的表现。
大概没有哪个架构师会犯上面例子那样幼稚的错误。但是,实际情况也远比例子中的复杂,甚至有一些问题是Java语言本身的缺陷带来的,例如,依赖了一个JAR包就意味着能够访问这个JAR包中的一切类和资源,因为JAR包中的内容没有Public、Private和Protected之分,无法限制用户能访问什么、不能访问什么。更复杂的情况是在引入了同一个JAR包的不同版本时怎么办?如果依赖包需要动态变化怎么办?使用OSGi一个很重要的目的就是弥补Java中资源精细划分的缺陷,加强架构的自约束能力。
虽然OSGi起源于精小软件占多数的嵌入式领域,但是在Java SE/EE领域中,对于越庞大的系统,使用OSGi进行模块化拆分就越能发挥出优势。在商业上已经有一些使用OSGi控制软件复杂度增长、延缓架构腐化速度的成功案例,如Eclipse Marketplace,它已经拥有了上千个插件,插件的开发者来自全球各地,技术水平差异很大,插件实现的功能也各不相同,是OSGi让这些插件基本遵循了统一的架构约束,并且一般不会因为某个插件的缺陷影响整个Eclipse的质量。
1.2.2 OSGi能让系统变得更稳定吗
笔者遇到过许多由OSGi框架引发的问题,例如,最典型的ClassNotFoundException异常、类加载器死锁或者在动态环境下的OutOfMemoryError问题等,这些都是基于OSGi架构开发软件时很常见的。从这一方面看,使用OSGi确实会增加系统不稳定的风险,所以,在开发过程中团队中有一两个深入了解OSGi的成员是必要的。
不过,软件是否稳定不是只看开发阶段可能出现多少异常就能衡量的,软件的“稳定”应是多方面共同作用的结果。除了关注开发阶段是否稳定之外,还要关注是否能积累重用稳定的代码,问题出现时能否隔断连锁反应蔓延,缺陷是否容易修复等。在这些方面,OSGi就可以带来相当多的好处,例如:
- OSGi会引导程序员开发出可积累可重用的软件。我们无法要求程序刚开发出来就是完全稳定的,但可以在开发过程中尽可能重用已稳定的代码来提升程序质量。大家知道,写日志可以使用Log4j,做ORM会引入Hibernate,Java中有许多经过长期实践检验的、被证实为稳定的开源项目,这些开源项目的共同特征是都经过良好的设计,能够很方便地在其他项目中使用。相对而言,在自己开发项目时很多人没有注意到要进行可积累的设计。一种典型现象是项目中出现一些“万能的包”,通常名字会是XXXCommons.jar、XXXUtils.jar等,这些包中存放了在项目中被多次调用的代码,但是这样的包不能叫做可重用包。当这些包越来越大、类越来越多、功能越来越强时,与这个项目的耦合就越紧密,一般也就无法用在其他项目中了。在OSGi环境下,“大杂烩”形式的模块是很难生存的,如果某个模块有非常多的依赖项,那么没有人愿意为了使用其中少量功能去承担这些间接依赖的代价。因此设计者必须把模块设计得粒度合理,精心挑选对外发布的接口和引入的依赖,把每个模块视为一个商业产品来对待,这样才能积累出可重用的模块,也利于提高程序稳定性。
- 基于OSGi比较容易实现强鲁棒性的系统。普通汽车坏掉一个轮胎就会抛锚,但是飞机在飞行过程中即使坏了其中一个引擎,一般都还能保持正常飞行。对于软件系统来说,如果某一个模块出了问题,能够不波及其他功能的运作,这也是稳定性的一种体现。大多数系统都做不到在某部分出现问题时隔离缺陷带来的连锁反应。试想一下,在自己做过的项目中把Common Logging(或slf4j)的包拿掉,系统能只损失日志功能而其他部分正常运作吗?但是对于基于OSGi架构开发系统,在设计时自然会考虑到模块自治和动态化,当某部分不可用时如何处理是每时每刻都会考虑的问题,如果软件在开发阶段跟随着OSGi的设计原则来进行,自然而然会实现强鲁棒性的系统。
- 在OSGi环境下可以做到动态修复缺陷。许多系统都有停机限制,要求7×24小时运行,对于这类系统,OSGi的动态化能力在出现问题时就非常有用,可以做到不停机地增加或禁止某项功能、更新某个模块,甚至建立一个统一更新的模块仓库,让系统在不中断运行的情况下做到自动更新升级。
1.2.1节和1.2.2节提出的两个问题可以总结为OSGi是否能提升开发效率和软件质量。OSGi在这两方面的作用与软件设计得是否合理关系非常密切,这时OSGi好比一个针对“设计”这个因素的放大杠杆,配合好的设计它会更加稳定、高效,而遇到坏的设计,反而会带来更多问题。
1.2.3 OSGi能让系统运行得更快吗
系统引入OSGi的目的可能有很多种,但一般不包括解决性能问题。如果硬要说OSGi对性能有什么好处,大概就是让那些有“系统洁癖”的用户可以组装出为自己定制的系统了。例如GlassFish v3.0服务器是基于OSGi架构的,它由200多个模块构成,如果不需要EJB或JMS这类功能,就可以把对应的模块移除掉,以获得一个更精简的服务器,节省一些内存。总体上讲,OSGi框架对系统性能是有一定损耗的,我们从执行和内存两方面来讨论。
首先,OSGi是在Java虚拟机之上实现的,它没有要求虚拟机的支持,完全通过Java代码实现模块化,在执行上不可避免地会有一些损耗。例如,OSGi类加载的层次比普通Java应用要深很多,这意味着需要经过更多次的类加载委派才能找到所需的类。在两个互相依赖的模块间发生调用时,可能会由于类加载器互相锁定而产生死锁;要避免死锁的出现,有时候不得不选用有性能损失的串行化的加载策略。在服务层上,动态性(表现为服务可能随时不可用)决定了应用不能缓存服务对象,必须在每次使用前查找,这种对OSGi服务注册表的频繁访问也会带来一些开销。使用一些具体的OSGi服务,例如使用HTTP Service与直接部署在Web容器中的Servlet相比会由于请求的桥接和转发产生一些性能损耗。
其次,从内存用量来看,OSGi允许不同版本的Package同时存在,这是个优点,但是客观上会占用更多内存。例如,一个库可能需要 ASM 3.0,而同一应用程序使用的另一个库可能需要ASM 2.0,要解决这种问题,通常需要更改代码,而在OSGi中只需要付出一点Java方法区的内存即可解决。不过,如果对OSGi动态性使用不当,可能会因为不正确持有某个过期模块(被更新或卸载的模块)中一个类的实例,导致该类的类加载器无法被回收,进而导致该类加载器下所有类都无法被GC回收掉。
仅从性能角度来说,OSGi确实会让系统性能略微下降,但是这完全在可接受范围之内。使用OSGi开发时应该考虑到性能的影响,但不应当将其作为是否采用OSGi架构的主要决策依据。
1.2.4 OSGi能支撑企业级开发吗
不管关于“OSGi是否能支撑企业级开发”的讨论结果如何,一个必须正视的事实是OSGi对企业级开发的支撑能力正在迅速增强。从2007年OSGi联盟建立企业专家组以来,OSGi的发展方向已经逐渐调整到企业级应用领域。在IBM、Apache和Eclipse基金会等公司和组织推动下,企业级OSGi正在变得越来越成熟。
在企业级OSGi出现之前,企业级开发要么是走Java EE的重量级路线,要么是走SSH的轻量级路线。企业级OSGi被引入后并没有扮演一个“革命者”的角色,没有把Java EE或SSH中积累的东西推倒重来,OSGi更像是在扮演一个“组织者”的角色,把各种企业级技术变为它的模块和服务,使以前的企业级开发技术在OSGi中依然能够发挥作用。
OSGi企业级规范中定义了JDBC、JPA、JMX、JTA和JNDI等各种Java EE技术以及SCA、SDO这些非Java EE标准的企业级技术在OSGi环境中的应用方式,这些容器级的服务都可以映射为OSGi容器内部的服务来使用。并且到现在,企业级规范定义的内容已经不仅停留在规范文字中,已经有不少专注于OSGi企业级服务实现框架出现(例如Apache Aries)了。
另一方面,OSGi的Blueprint容器规范统一了Java大型程序中几乎都会用到的依赖注入(DI)方式,使基于Blueprint的OSGi模块可以在不同的DI框架中无缝迁移。这个规范得到Apache、SpringSource等组织的大力支持,目前这些组织已经发布了若干个Blueprint规范的实现容器(例如Apache Geronimo和Equinox Virgo,Virgo前身就是SpringSource捐献的Spring DM 2.0)。在最近两三年时间里,企业级OSGi成为Java社区技术发展的主要方向之一,其发展局面可以说是如火如荼。
不过,我们在使用企业级OSGi的时候也要意识到它还很年轻,其中很多先进的思想可能是遗留程序根本没有考虑过的,还有不少问题的解决都依赖于设计约束来实现。因此,如果是遗留系统的迁移,或者设计本来就做得不好,那么使用OSGi会遇到不少麻烦。以最常见的数据访问为例,如果以前遗留系统使用了ORM方式访问数据库,而迁移到OSGi时没有把实体类统一抽取到一个模块,那么ORM模块的依赖就很难配置了,这时不得不使用Equinox Buddy甚至DynamicImport-Package这类很不优雅的方式来解决。另一个问题是集群,OSGi拥有支持分布式的远程服务规范,而OSGi的动态性是针对单Java虚拟机实例而言的,因此要在集群环境下保持OSGi的动态性,就必须自己做一些工作才行