1.背景
Lina解决了目前Jar包调用下的问题(启动慢、耦合度高、依赖传递、内存消耗大等问题)
,将原来在一个虚拟机中运行的程序,分散到了多个不同的虚拟机中运行,将原来的系统改造成了一个分布式服务系统。
一期在线上有点评这一个服务使用Lina来发布服务。Lina详细说明请参照(http://wiki.koubei.com/index.php/Rd/HuoNiao/Lina)。
在使用了之后我觉得lina有如下优点:
1.使用通透的方式将rmi协议封装了,通过使用spring的factroybean客户端只需要关心服务器端所调用的接口和服务的暴露的服务名称,就能调用了服务了。
2.使用了monitor模块,使用aop方式嫁接到原有系统中,在系统运行的时候可以直观即时地查看到系统的负载状况。
另外,本文中提到的的相关代码,如果您想查看源代码的话可以发邮件来向我索取 baisui@taobao.com
2.问题
体验了这些优点之后仍然觉得有以下不足,觉得还有改善的空间。
2.1.为每个服务对象要配置一个export代理工厂
|
<!-- Class固定 -->
<bean id="leaveWordRmiServer"
class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 对应业务包中的实现Service -->
<property name="service"><ref
bean="leaveWordRmiService" /></property>
<!-- 服务名,与客户端定义的服务名一致 -->
<property name="serviceName"><value>lwt</value></property>
<!-- 业务接口 -->
<property
name="serviceInterface"><value>com.koubei.leaveword.client.rmi.ILeaveWordRmiServer</value></property>
!-- 侦听端口,固定 -->
<property name="registryPort"><value>7071</value></property>
<!-- 拦截器,固定 -->
<property name="interceptors"><list><ref
bean="monitorAdvisor"/></list></property>
</bean>
|
每一个向外暴露的服务都需要配置这样一个RmiServiceExporter的代理对象,这样IOC容器中的对象就会在RmiServiceExporter的帮助下将服务以rmi的协议公布出去,是的,这样是正确的。但是,当将来某一天由于处于性能的考虑需要将服务以其他的方式向外公开,比如,使用hession协议,或者说同一个服务需要同时以hession和rmi两种协议向外公开服务,那么,如果使用现有的方式部署的话需要怎样修改呢?很明显,原有的大量的配置都需要修改。
2.2.简化客户端调用分布式服务方式
现在在客户端调用lina分布式服务,需要在spring中配置一个服务端代理对象,下面是一个例子
|
<bean id="leavewordConf" class="com.koubei.rpc.client.conf.RmiProtocolConf">
<!-- 服务器地址 -->
<property name="server"
value="leaveword.pr.vip.hz1.koubei.com"
/>
<!-- 服务器端口 -->
<property name="port"
value="7071" />
<!-- 调用超时(单位:毫秒),可不填,默认3000
-->
<property name="invokeTimeout"
value="3000"/>
<!-- 启动时检查连接,可不填,默认false -->
<property name="lookupStubOnStartup"
value="false" />
<!-- 失败时刷新Stub,可不填,默认true -->
<property name="refreshStubOnConnectFailure"
value="true" />
<!-- 缓存Stub(性能影响大),可不填,默认true
-->
<property name="cacheStub"
value="true" />
</bean> |
客户端的配置也同样存在潜在的风险:
1.需要为每一个服务,配置相应的客户端配置,这样一来,客户端的配置量也挺大的,如果同一个服务在n个地方有调用的话就需要配置n份
2.如果某一天服务端的服务的配置信息有所改动的话,那么就要查找所有客户端的配置,将它们一一修改掉,可想而知这是一项非常繁琐而且容易出错的工作
2.3 每个服务对客户端是一个独立的服务名,这样服务端维护监控存在潜在风险。
可以将服务的通讯方式改造成另外中形式:
3.完善
3.1.问题总结:
1.要为每个服务端服务,调用端配置一个spring配置文件。这样日后维护会比较麻烦。发布流程会比较繁琐,且容易出错。
2.服务器端和客户端是单独传输没有统一的网关。
3.2.改善的思路:
1.服务器端bean的配置问题:对于解决这些问题,可以利用maven强大的插件功能,让maven来自动生成这些配置服务器端的配置文件,在发布服务器端的jar包的时候,可以自动生成服务器端bean的代理文件,并且,在打包的时候可以自动将之前生成的配置文件一同打到jar包,这个过程中用户只需要简单的敲一些maven的命令。
2.客户端xml配置问题:客户端要为每个服务调用都配置一个配置文件,是因为客户端调用的时候并不知道它能调用的服务在哪儿,在哪个服务器上,ip是多少,端口是多少,服务名是什么,所以这些都必须事先就在调用端设置好,这样客户端在启动之后才能通过客户端的配置正常加载服务器端的配置。所以,可以增加一个服务中心的服务,在服务端启动的时候,将服务所在的ip地址,服务名称,服务端口发布到服务中心的数据库中。
3.客户端所要依赖的开发包仅仅就是服务端部署包的中jar包中的那些服务接口和服务接口所依赖的pojo对象,可以开发一个maven的plugin在maven运行的package阶段为服务器端生成一个客户端依赖包。这样在客户端部署的时候只需要调用这个客户端包就行了。改造成这样的通讯方式之后客户端和服务器端的通讯就可以通过统一的gateway来通讯了,在原来的通讯方式下monitor模块是通过aop的方式嫁接到原有系统中的,现有的架构中
monitor模块可以不通过AOP方式配置,这样系统的运行效率就会提高。
3.3.客户端调用方式改善
对于客户端来说,action所依赖的service对象只需要通过ServiceLookup类的findService()方法就能找到,并不需要关心服务所在的服务器ip地址端口等信息。
以下是改造之后的客户端查找服务器端调用的时序图:
如上图,webwork查找服务端服务的主要流程如下
1.Webwork调用ActionInvocation的invoke函数,按照拦截器配置的顺序调用DistributeServiceInjectInterceptor拦截器(这个拦截器的作用是为目标action注入分布式服务的)
2.DistributeServiceInjectInterceptor利用反射的机制查找MyAction的分布式服务的注入点
|
public
class UserAction
extends ActionSupport {
public String execute() throws
Exception{
return SUCCESS;
}
@Inject
public void setUserService(UserService
userService) {
this.userService = userService;
}
}
|
在需要注入分布式服务的javabean的set方法上打上一个@Inject标识,这样DistributeServiceInjectInterceptor就会发现这个action有分布式服务的使用需求,之后它就会调用LookupService的findService方法找到需要的服务对象,并且将这个服务对象注入到action中。
3.4.LookupService
以下是和LookupService这个类相关类的类图:
当ServiceLookup初始化时会使用JGroups的JChannel对象在JGroups的group中将ServiceLookup注册为Group中的一个成员,当ServiceLookup的生命周期中会实时监听分布式服务中的事件。比如,当分布式Group中增加了一个新的服务节点,或者,某个服务节点下线了。这样的变动需要自动更新各个服务调用端的ServiceLookup对象中缓存的服务信息(服务所在ip、端口、服务名)。JGroup承担了分布式系统中消息分发的功能。
3.5.网络结构图
如上图所示,系统一共有三个部分,第一部分是服务提供服务器,第二部分保存服务元数据信息的数据库服务器,第三部分是调用服务的调用端。
以下是服务发布到服务调用端调用服务这个流程的说明:
1.分布式服务系统集群中有一台服务器有一些新的服务需要上线,首先服务器会初始化本虚拟机中的对象。
2.将自己服务器中的服务,服务所在的ip地址占用的端口发布到服务中心的数据库中。
3.在本虚拟机中初始化一个jChannel对象加入到分布式服务的JGroup中,并且向Group中广播一个更新服务员数据信息的消息。
4.各个客户端调用者收到集群中的发过来的更新消息,将本机中原有信息删除。
5.客户端调用者重新到服务中心中加载新的服务元数据信息。
3.6.创建服务对象
基于以上的考虑,我们需要对现有分包的代码做一些小小的改动。需要做以下改动适应新的分布式服务改造:
在开发过程中,需要标识要往外部发布服务的接口。例如在开发【免费网店房源关联】接口过程中需要用com.koubei.lina.ext.Service的annotation标识服务接口,如下所示:在服务接口上用@Service标记,后期初始化启动过程中就会自动将这个接口绑定到预先设置好的端口上向外发布。示例代码如下:
|
import
com.koubei.dian.pojo.DianAssociation;
import com.koubei.lina.ext.Service;
/**
* 免费网店房源关联
*/
@Service
public interface IDianAssociationService
{
boolean insertDianAssociation(
DianAssociation dianAssociation); |
通过在IDianAssociationService 服务接口上标识@Service之后,有以下作用:
1. 初始化Spring的需要
虚拟机在初始化的时候就知道这个实现这个接口的服务对象需要以rmi协议(或者其他服务)的形式向外发布服务。系统就可以自动为其生成export代理对象而不需要手动去配置了。
2. 生成客户端包
当用maven的插件生成客户端依赖包的时候,maven插件会遍历已经服务器端包中的所有资源,当发现某个接口上标识了@Service的标识之后,插件会将这个接口的class文件抽取出来,同时也会分析Service所依赖的pojo对象,如果这个pojo类资源文件也存在本服务器包中的话,那么也一并会抽取出来,将这些抽取出来的资源文件重新打包在一个新的client
jar包中。供客户端调用者调用。
3.7.客户端使用分布式服务
3.7.1.配置客户端包的依赖
在客户开发工程中需要在maven的pom.xml文件中添加服务器端客户端jar(该jar中只包含了服务service接口,和接口依赖的pojo对象)的依赖。配置如下:
|
<dependencies>
<dependency>
<groupId>com.koubei</groupId>
<artifactId>koubei-dian
</artifactId>
<version>1.4.1</version>
< classifier>client</classifier
>
</dependency>
</dependencies>
|
注意,以上的dependency节点需要额外加一个classifier属性,值必须为client,这样项目在编译的时候就会从maven库中取到服务包的client包了。
3.7.2.编写action
在Webwork中如果需要调用到分布式服务中的某个服务对象,很简单只需要像下面这样的改动:
|
public
class HomeAction extends ActionSupport {
private IFreeDianService freedianService;
@Inject
public void setFreedianService(IFreeDianService
freedianService) {
this.freedianService = freedianService;
}
。。。。。
|
3.7.3.添加拦截器配置
为了要让webwork能够识别这个@Inject标记,需要在webwork中添加一个拦截器。
|
<xwork>
<interceptors>
<interceptor name="distributeServiceInject"
class="com.koubei.lina.ext.xwork.DistributeServiceInjectInterceptor">
</interceptor>
</xwork> |
需要让distributeServiceInject这个拦截器添加到HomeAction的默认拦截器栈中,才能让distributeServiceInject在调用action的时候起作用。
3.8.Maven插件介绍
3.8.1.ServiceClassManager
com.koubei.plugin.distribute.ServiceGatewayBuild
插件的作用是在maven的compile阶段,可以在classoutput文件夹中自动生成lina service代理文件。文件内容如下:
|
<?xml version="1.0"
encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD
BEAN//EN" "/spring-beans.dtd">
<beans>
<import resource="classpath:dian.application.xml"
/>
<bean id="serviceDelegate"
class="com.koubei.lina.ext.DefaultServiceDelegate"
>
<property name="serviceCenter"
ref="serviceCenter" />
<property name="port"><value>9999</value></property>
<property name="serviceName"><value>koubei-dian</value></property>
</bean>
<!—服务中心bean-->
<bean id="serviceCenter"
class="com.koubei.lina.ext.svzcenter.ServiceCenterImpl"
>
<property name="moduleDao">
<bean class="com.koubei.lina.ext.svzcenter.dao.LookupDao"/>
</property>
<property name="serviceDao">
<bean class="com.koubei.lina.ext.svzcenter.dao.ServiceDao"/>
</property>
</bean>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service"
ref="serviceDelegate"/>
<property name="serviceName">
<value>koubei-dian_service</value>
</property>
<property name="serviceInterface">
<value>com.koubei.lina.ext.ServiceDelegate</value>
</property>
<property name="registryPort">
<value>9999</value>
</property>
</bean>
</beans>
|
以上配置文件中的端口号,和原目标配置文件需要在pom.xml的plugin的配置节点中配置:
|
<plugin>
<groupId>com.koubei</groupId>
<artifactId>distribute-build</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<springConfig>dian.application.xml</springConfig>
<port>9999</port>
</configuration>
</plugin>
|
3.8.2.PackageClientJarPlugin
将ServiceGatewayBuild插件生成的客户端依赖的class文件打包成clientjar包。
3.8.3. ServiceGatewayBuild
自动抽取服务端客户端依赖的class文件。
3.8.4. 自定义plugin与maven生命周期绑定
3.8.5. 发布服务
以口碑店分包项目(http://10.5.58.201/svn/Koubei/branches/Branch_galaxy_091014/koubei-dian)为例,在项目工程的pom.xml文件中配置自定义plugin:
|
<plugin>
<groupId>com.koubei</groupId>
<artifactId>distribute-build</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<springConfig>dian.application.xml</springConfig>
<port>9999</port>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
<execution>
<id>package</id>
<phase>package</phase>
<goals>
<goal>distill</goal>
<goal>clientjar</goal>
</goals>
</execution>
</executions>
</plugin> |
将以上提到的三个插件绑定到maven执行时的生命周期中。
以上都配置好之后就可以在koubei-dian项目下执行maven命令了:
执行
mvn package
命令就会在项目target目录下面生成两个jar包,一个koubei-dian的服务端jar包文件(koubei-dian-1.0.0.jar),另外一个是客户端依赖的jar包文件(koubei-dian-1.0.0-client.jar)。
执行
mvn deploy
就会将生成的两个jar包自动部署到maven的本地仓库中去。可以发现,如果一旦为koubei-dian项目设置好了插件的话,生成jar的工作就归结到执行mvn的命令了,多么的简单呀。这里我们不得不赞叹maven的功能是在太强大了,它已经将项目发布的生命周期的各个环节建模,并且,可以让用户自定义各个生命周期执行的任务。这样一来对于项目发布,构建带来了优美,和方便。
3.8.6. 启动服务
在命令行中执行以下命令:
java -DserviceModule=koubei-dian ^
-classpath ./target/koubei-dian-1.0.0.jar;^
D:/j2ee_solution/eclipse/workspace/Lina/dist/lina.server.jar;^
D:/j2ee_solution/eclipse/workspace/Lina/target/memcached-2.0.1.jar;^
D:/mvn_test/lina-ext/target/lina-ext-1.0-SNAPSHOT.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/log4j-1.2.12.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/commons-logging-1.0.4.jar;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/spring.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/commons-dbcp.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/commons-collections.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/commons-pool.jar;^
D:/j2ee_solution/wokspace/Release20080225/dist/koubei-core.jar;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/taobao-cache.jar;^
D:/j2ee_solution/wokspace/Release20080225/dist/koubei-util.jar;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/ibatis-2.3.0.677.jar;^
D:/j2ee_solution/wokspace/Release20080225/dist/koubei-cache.jar;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/oscache-2.3.2.jar;^
D:/j2ee_solution/eclipse/workspace/koubei-dian_bak/jars/commons-beanutils.jar;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/commons-lang.jar;^
./config;^
D:/j2ee_solution/wokspace/Release20080225/WebContent/WEB-INF/lib/mysql-connector-java-5.1.5-bin.jar
^
com.koubei.lina.ext.ModuleLauncher
执行了这个命令之后服务就启动了。
3.8.7. 服务监控页面
当服务服务器启动,向元数据服务器中注册服务信息之后,就可以对外发布服务了。这时可以打开本地web页面查看本地局域网中的服务对象来核实是否有需要的服务对象存在。比如,打开页面:http://localhost/monitor/可以看到本局域网中所有服务对象的信息:
从该监控台上可以查看到以下信息:
1.服务模块所在的服务器名称
2.服务的名称,该属性的作用是,当spring启动的时候初始化作为初始化参数用的
3.服务所在的端口
4.服务所在ip地址
服务发布时间戳,该时间戳是校验客户端代码版本与服务器端版本是否一致时用的,当服务器端的时间戳与客户端时间戳比较,相等则说明版本一致。
4. 有待完善的东西
4.1. 使用OSGI来改造现有服务构架
在3.8.6启动服务中大家可以看到启动服务的命令相当长,可以发现在服务启动的命令中,classpath中除了koubei-dian-1.0.0.jar这个jar包之外其他依赖的jar包都是系统级的jar包,如果启动多个服务的话,势必需要在虚拟机之中重复加载系统级的jar包,这样对服务器的内存是一种浪费。
另外服务所依赖的一些公共bean,例如datasource对象,对于多个服务来说是可以共用的,如果,能在服务器上只保存一份dadasource对象的话,对于服用对象,节约服务器内存,日后更新datasource配置来说都大有好处。
以上这些问题,可以使用OSGI(公用服务网关)来解决。在之后的文档中,将介绍如何使用OSGI来优化现有系统的构造。
4.2. 消息中心
在现有系统中,同步各个服务引用端对象的信息是用过JGroup组件来同步的,虽然我在本机测试环境下测试性能还可以,但是还没有经过系统级的大并发量的测试。那天听说老常同志在开发异步消息系统,据说这个消息系统会是一个高可靠的系统,我想到时候说不定能用这个异步消息系统来替换现有jgroup的状态同步问题。
4.3. 服务中心监控
现在的监控中心中只能看出有什么服务可以调用,后期改造可以在监控中心中可以增加服务负载量的监控等。
|