基于OSGI的Cache组件的实现

  
基于 OSGI Cache 组件的实现
Author Wenchu.cenwc 岑文初
Date 2007-3-13
源起:
       平台新架构一个core module作为Loader Module,然后再启动系统级的Modules,最后是业务级的Modules。
       OSGI中文意思是开放服务网关接口,是规范性的接口规范定义。而Eclipse 3.0以后的架构都是通过OSGI规范来实现的,可插拔性和灵活的接口有目共睹。在Eclipse中有一个基于OSGI R4实现的Equinox项目,它在OSGI R4框架实现中十分优越。因此平台决定使用OSGI R4规范来实现平台的整体架构。而Equinox就已经为平台搭建了由Core到System到Service的装载的机制。
 
准备:
1. 因为是基于Equinox来进行开发,因此首先机器上要有Eclipse 3.2.1以上的版本,我用的是 3.2.1版本。
2. 到 http://download.eclipse.org/eclipse/equinox/ 去下载一些Equinox已经实现的bundles,对于我们开发来说会用用处,由于后面我们主要涉及到了通过web来测试,因此需要 org.eclipse.equinox.http_1.0.1.R32x_v20060717.jar这个bundle,还有就是例如需要使用DS作为Service来发布就需要下载 org.eclipse.equinox.ds_1.0.0.v20060601a.jar反正全部下载没有坏处。不过要注意一点的就是最好按照你的Eclipse的版本来下载对应的bundles,这样才不会出现版本不对导致的问题。将下载完的jar都放到%EclipseHome%/plugins下面,然后重新启动Eclipse就可以使用这些bundles了。(关于Bundles说明一下,在OSGI中,把资源,类,配置文件都放在一个文件目录下作为一个Bundle,它有它的核心描述文档和单独的Class Loader和其他Bundles交流只是通过OSGI的服务查找或者接口导出导入,所以每一个Bundle就可以看作很独立的模块对外只暴露接口,高内聚,低外耦,OO的封装在这里得到了很好的体现)。
 
设计:
       Cache模块主要定义了两个接口:Cache和CacheManager。Cache不用多说,大家常用的接口,CacheManager是管理Cache的接口,系统内部或者业务模块不能直接创建和获取Cache接口,统一通过CacheManager接口来维护系统内部的Cache,这样的好处就是整个系统所有的Cache都可以统一管理和监控,对于系统将来的性能监控以及网络管理等外围接入提供了统一的接口和植入点。
 

1 接口定义

       根据系统的需要实现了两类不同的Cache和CacheManager,一组是实现了JBossCache的接口,另外一组实现了TBStore接口。JBossCache是典型的TreeCache,这点和我们接口最初的设计很好的吻合了,每一个系统的Cache作为TreeCache的节点,CacheManager就是整个Tree,JBossCache可以配置成为分布式和本地的两类Cache,分布式Cache十分强大,支持事务的三阶段提交以及超时策略等,因此系统的普通的Cache都是通过这部分来实现和运作。另外为了融合其他系统的TBStore的内容,增加了对TBStore接口的实现。具体的结构图如下。图中还有一个外部测试Bundle,它对于系统内部的Cache和CacheManager的引用都是通过查找OSGI FrameWork的Context中的service来获取接口,它可以动态的切换和操作不同的CacheManager来获得不同类型的Cache,同时CacheManager在运行期可以动态的加载和卸载,外部引用可以立刻反应出其变化。
 
2 整体类图
开发:
需要建立一个接口Bundle,两个实现Bundle,以及一个测试Bundle。
首先建立接口Bundle:CacheBundle。
新建工程:
继续
注意要选择Target PlatForm,选择标准的osgi框架,因为以后我们最后还是要部署在通用的osgi框架下,不依赖于任何第三方的框架内容。
 
继续,由于是接口Bundle所以不需要用到Bundle的Activator来激活Bundle,所以在Plug-in Options那个第一个选项也不需要了。同时后面我们的实现Bundle里面也不需要选,因为我们将会使用OSGI的R4新的特性,直接通过DS来申明服务。
OK,finish。
然后就是要修改Bundle的最核心的总控文件,在META-INF下面的MANIFEST.MF文件了,这个文件是Bundle的描述文件,整个Bundle的资源,类,ClassPath等都是通过它来描述的,因此这个文件的正常与否直接关系到Bundle的部署是否成功以及功能是否正常。不过Eclipse替我们提供了一个很友好的图形界面来编辑这个文件,减少了我们配置出错的可能性。
双击MANIFEST.MF,默认会进入Overview标签页,这个是一个总览页面,具体还是要选择后面的不同的标签来做配置。我们一个个来介绍:
Dependencies 标签页:配置需要依赖的Imported Packages和外部的plug-ins也就是我们说的Bundles。(注意:习惯中大家如果发现工程缺少了某一个外部的jar或者接口的时候都通过选择工程的属性,然后在Libraries里面加入jar,但是这里千万千万不要这么做,问题在开发过程中看不出来,等到了部署的时候由于此时的ClassPath都通过MANIFEST.MF中的定义来寻找,因为通过工程Libraries加入的jar不会被写入到MANIFEST.MF中,因此就会出现问题,所以需要外部的包或者接口的时候,就在此页中加入,它会自动修改工程的Libraries)。
Runtime 标签页:分成左右两部分,左边是需要导出的包,一般就是我们需要暴露给外部的接口包,以供其他模块或者外部引用实现,这里面我们导出了com.osgi.demo.cache接口包。右边是Classpath,描述一下在Bundle发布的时候的Classpath,一般我们默认建立工程的时候将class的输出目录定为bin,选择把bin加入到classpath,然后自己可以建立一个lib目录在工程中,作为第三方lib的放置目录,然后就要在这边选择lib目录也作为Classpath。
Build 标签页:最上面左边右边分别是选择在运行期自动从源码目录编译到目标目录的两个列表,因此我左边选择了bin作为目标class置放的目录,右边选择src目录,保存一下。
然后为了在导出生成jar的时候会把配置文件,资源文件,第三方jar等内容一起导出,就需要在Binary Build里面把需要到处的内容打上钩。
       最后的两个标签分别是MANIFEST.MF文本编辑页面和Build文本编辑页面,当你修改完上面的内容以后可以自己手动的修改这两个文件。
       写完配置文件以后,只需要按照我们通常的开发模式在src下面开始编写java的内容了。
 
接着就是编写两个接口实现Bundle了,总体的流程和接口Bundles一致,就把不太一样的说明一下:
作为总控文件的不同主要在imported Packages中需要导入接口定义的那个包com.osgi.demo.cache,还需要依赖一个org.osgi.service.component的包,因为后面我们需要通过DS来申明和调用service,也需要通过ds的生命周期回调函数来初始化和析构。需要建立一个DS的配置文件目录以及配置文件,我这边就在工程中建立了一个OSGI-INF目录,然后再此目录中新建了文件component.xml作为配置文件,最后需要手动在MANIFEST.MF文件中增加一句 Service-Component: OSGI-INF/component.xml 来指定配置文件的路径,记得要在Binary Build中也选中这个目录,否则不会被打包到jar中,运行起来也就会出问题。具体的DS使用方法和配置方法参考一下OSGI R4的r4.cmpn.pdf。
 
       最后测试类的开发,这边是参照了OSGI实战内的范例来通过WEB方式测试具体的业务逻辑。依赖的package需要org.osgi.service.http以及org.osgi.service.component两个eclipse网站下载的构建包和com.osgi.demo.cache包,同时还需要一个Plug-ins就是org.eclipse.equionx.servlet.api。其他的配置都和上面的一样。
在来看一下测试的Servlet的代码片断:
 
作为DS来管理Bundles内部类的情况下,提供了启动和结束的两个拦截方法:activate和deactivate。在测试类启动的时候,
context.getBundleContext();
    Object[] services = context.locateServices( "CacheManager" );
    cacheMap .clear();
          
    for ( int i = 0 ;i < services. length ; i++)
    {
       Object node = services[i];
       cacheMap .put(node.getClass().getName(), node);
    }
通过 locateServices 来查找同一个接口服务所有的注册对象,然后置入 Map 来切换调用。 DS 也提供了很方便的类似于 spring 的注入式的初始化(缺点就是注入的时候每次只能够获取其中之一的接口注册对象,根据注册的先后,获取靠后的对象),参看 DS 的配置使用。
 
在doGet中代码片断如下:
if (cachetype != null )
{
        if (cachetype.equals( "tbcache" ))
        {
            cacheManager = (CacheManager) cacheMap .get( "com.osgi.demo.cache.tbstoreImpl.TBStoreCacheManager" );
        }
       
        if (cachetype.equals( "xcache" ))
        {
            cacheManager = (CacheManager) cacheMap .get( "com.osgi.demo.cache.xImpl.XCacheManager" );
        }
       
}
 
if ( cacheManager == null ){
        output.println( "No usable cacheManager service" );
        output.close();
        return ;
    }
if (operater.equals( "create" ))
{
    cacheManager .create(cacheName);
    output.println( "create cache success" );
}
                 
if (operater.equals( "put" ))
{
    Cache cache= cacheManager .get(cacheName);
                    
    if (cache != null )
    {
       cache.put(key, value);
       output.println( "put cache success" );
    }
    else
       output.println( "cache is null" );
                    
}
……
 
    至此,开发部分基本结束,已经可以在 eclipse 里面调试和运行了。调试和运行的话,首先,
选择 Equinox OSGI Framework 这种类型,然后 new 一个。在选择 plug-ins 中自己的几个 Bundle 以及系统的,这边自己的几个不说了,说一下系统的几个: ds,http,servlet,services 。系统自己默认会选中一个 osgi 的最基类 bundle
OK, 你可以自己设置断点调试运行了。
 
部署:
       当在Eclipse里面测试完成以后,其实工作才作了70%,剩下的30%将会更加困难,因为你将脱离调试环境,进入命令行环境来测试和验证各个Bundle是否正常。
       部署前第一步就是需要导出各个Bundle为jar。
       选中某个需要导出的工程,选择Deployable plug-ins and framents
Next以后就只要选择自己需要导出的位置,不过注意的是,导出以后会自动增加一层plug-ins目录,这个主要是在配置启动脚本的时候需要注意的。
现在来构建一个发布总目录,建立一个文件夹,将eclipse的plugins里面的org.eclipse.osgi_3.2.1.R32x_v20060919.jar(版本不同后面的数字不同)拷贝到目录中,并重新更名为equinox.jar。
然后建立一个configuration目录新建一个config.ini作为配置文件,范例如下:
#Configuration File
#Mon Jun 12 21:04:30 CST 2006
osgi.noShutdown=true
osgi.bundles=reference/:file/:bundles/org.eclipse.osgi.services_3.1.100.v20060511.jar@start,/
reference/:file/:bundles/org.eclipse.equinox.ds_1.0.0.v20060601a.jar@start,/
reference/:file/:bundles/org.eclipse.equinox.http_1.0.0.v20060601a.jar@start,/
reference/:file/:bundles/org.eclipse.equinox.servlet.api_1.0.0.v20060601.jar@start,/
reference/:file/:bundles/plugins/CacheManagerBundle_1.0.0.jar@start,/
reference/:file/:bundles/plugins/TBStoreCacheBundle_1.0.0.jar@start,/
reference/:file/:bundles/plugins/XCacheBundle_1.0.0.jar@start,/
reference/:file/:bundles/plugins/CacheTestBundle_1.0.0.jar@start
osgi.bundles.defaultStartLevel=4
 
新建一个bundles用来放bundles,系统级的现在一共用了4个(http,servlet,ds,services),再将刚才导出的plug-ins目录拷贝到bundles下面(其实后面就在导出的时候指定导出位置为bundles就可以了)。
建立一个批处理运行文件run.bat.,内容如下(不多解释,就是普通的jar运行):
java -Dorg.osgi.service.http.port=8080 -jar equinox.jar -console
 
OK,可以Run了。
 
测试
       功能性测试在Eclipse开发中已经基本走过了,剩下的就是为了验证一下bundle的动态载入和卸载。
       默认的配置文件中,程序起来以后每个bundle都回变成active。
       命令行输入:ss可以查看bundle的状态。Bundles可以更清楚地看到每一个bundles的状态以及注册的service和引用的service。
       这里首先输入ss,可以看到屏幕输出:
 
Framework is launched.
 
id      State       Bundle
0       ACTIVE      system.bundle_3.2.0.v20060510
1       ACTIVE      org.eclipse.osgi.services_3.1.100.v20060511
2       ACTIVE      org.eclipse.equinox.ds_1.0.0.v20060601a
3       ACTIVE      org.eclipse.equinox.http_1.0.0.v20060601a
4       ACTIVE      org.eclipse.equinox.servlet.api_1.0.0.v20060601
5       ACTIVE      CacheManagerBundle_1.0.0
7       ACTIVE      XCacheBundle_1.0.0
8       ACTIVE      TBStoreCacheBundle_1.0.0
9       ACTIVE      CacheTestBundle_1.0.0
 
如果使用注入的方式,那么在CacheTestBundle里面能够获取到的CacheManager接口只能是TBStoreCacheManager的实现,这边我们使用了不是注入方式,是在Test启动的时候,载入全部的可用接口注册服务,以此没有此问题。
当使用stop 8以后,发现TBStoreCacheBundle当前的对象被释放,但是如果去调用CacheTest的TBStoreManager的实现,依然会new一个新的对象来运行,也就是说就算TBStoreCacheBundle被Stop,处于非Activie的时候CacheTest还是能够继续使用的。
此时我尝试了修改了TBStoreManager中的逻辑,加了一句打印语句,然后重新打包,这时再把TBStoreCacheBundle uninstall了,然后再install此Bundle并且start,对于CacheTest一点没有什么影响,接着当我stop了CacheTest以后,然后再start,此时可以发现,新的bundle实现那句输出启效了。
所以由此可见,如果我们要在运行期替换一些业务实现十分容易,对于业务模块基本是透明的,而且不需要重起服务器,这种无间断的运行期替换对于网站来说十分具有吸引力。来看看应用场景。
例如:
场景一:A业务模块使用了系统所提供的K接口,K接口此时有多个实现暂时叫做K1Bundle,K2Bundle,K3Bundle。此时如果A是通过DS的注入来获取接口,那么如果按照K1Bundle,K2Bundle,K3Bundle,A的顺序载入,那么A应该默认的获取到了K3Bundle作为业务逻辑处理,如果此时stop K3Bundle,再重新启动A模块,那么A将会使用K2Bundle,不知不觉中就切换了实现,真正的面向接口对外开放。
       场景二:A业务模块使用了系统所提供的K接口,K接口此时由K1Bundle实现,那么载入以后正常运行,当K1Bundle修改以后,重新打包,并且重新装载并启动,然后再重起A模块,那么K1Bundle的新逻辑将会启用。
 
Tips
1. 接口定义包名千万注意不要和实现包名同名,例如接口包为com.osgi.demo.cache,开始的时候我把XBundleCache中的实现类直接放入到了com.osgi.demo.cache下,结果由于这个Bundle有引用接口包,那么两个包冲突了, 结果半天没有出来正常的运行结果。因此多加一层com.osgi.demo.cache.xImpl或者干脆就改名。
2. DS的载入顺序很重要。如果要调用某个ds,那么这个ds要优先于调用这load,同时记得系统的services,ds要优先load。
3. DS本来有自己的生命周期回调函数,定义为 protected void activate(ComponentContext context),但是我自己新增以后怎么跟踪都没有进入回调函数,找了半天才发现,原来用了eclipse的add import,误把 com.sun.jndi.toolkit.ctx.ComponentContext导入了,由于在plug中没有add in org.osgi.service.component这个包,因此就算写了函数申明还是没有起效,命苦。
4. 如果用外部启动bundles的话,常常提示什么已经初始化过了的错误,那么去configuation的目录中删除没用的那些零时目录就可以了。
5. 千万千万记得这一点,我们习惯当需要引用外部的jar的时候通过project的属性也里面增加project的lib,得却立刻红色的错误消失了,但是带来的苦果就是当你发布jar的时候由于没有把核心配置文件中的classpath修改好,导致就算你把那些jar打入你的bundle里面还是会出现找不到类。
6. 通过DS来装载最大的缺点就是如果一个component转载失败(可能由于某个类没有找到),但是它还是提示你整个bundle是active的,直到运行期的时候发现拿到了一个错误的service,报了一堆错误,或者本来想通过注入来植入一个service的,结果因为这个service初始化失败,毫无声息的继续正常使用,直到用到了这个service来执行流程。因此需要学会使用命令行的bundles查看各个bundle的加载状态以及bundles的Service的载入状态。同时要去分析在configuration目录下面的日志文件。
7. 要学会解开jar来看里面的东西,学会分析里面的各个配置文件。因为初学的时候对于eclipse的打包方式不是很清楚,因此很多问题其实是打包出错,例如缺少lib,配置等等,因此需要通过解压工具解开jar看看里面的配置情况以及目录结构情况是否符合你的核心配置文件的描述。
 
 
本文作者 岑文初 是阿里巴巴阿里软件公司平台高级开发工程师。目前从事阿里软件平台架构开发工作。可以通过 wenchu.cenwc@alibaba-inc.com 和我联系。
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值