Spring HttpInvoke的实现(远程接口调用),以及效率提升!解决Read timed out问题最近接手服务器总被人质疑效
Spring HttpInvoke的实现(远程接口调用),以及效率提升!解决Read timed out问题
最近接手服务器总被人质疑效率问题,说到底是质疑Spring HttpInvoke的效率问题。好在经过我的努力,找到了问题的根源,最终解决了这个问题。?
我也顺道整理一下Spring HttpInvoke——那曾经最为熟悉的东西。?
Spring HttpInvoke,一种较为常用的、基于Spring架构的服务器之间的远程调用实现,可以说是轻量级的RMI。?
最初,我们使用Spring HttpInvoke同步配置数据,刷新多个服务器上的缓存,当然如果用分布式缓存是不是更好
?!?
使用Spring HttpInvoke,你可以调用远程接口,进行数据交互、业务逻辑操作等等。?
废话不说了,上代码!?
用户操作接口:?
Java代码?

?

- /**?
- ?*?@author?<a?href="mailto:zlex.dongliang@gmail.com">梁栋</a>?
- ?*?@since?1.0?
- ?*/??
- public?interface?UserService?{??
- ??
- ????/**?
- ?????*?获得用户?
- ?????*??
- ?????*?@param?username?
- ?????*????????????用户名?
- ?????*?@return?
- ?????*/??
- ????User?getUser(String?username);??
- }??
用户类,注意实现
Serializable接口,这是执行远程调用传递数据对象的第一要求——数据对象必须实现
Serializable接口,因为,要执行序列化/反序列化操作!?
Java代码?

?

- /**?
- ?*?@author?<a?href="mailto:zlex.dongliang@gmail.com">梁栋</a>?
- ?*?@since?1.0?
- ?*/??
- public?class?User?implements?Serializable?{??
- ??
- ????private?static?final?long?serialVersionUID?=?5590768569302443813L;??
- ????private?String?username;??
- ????private?Date?birthday;??
- ??
- ????/**?
- ?????*?@param?username?
- ?????*?@param?birthday?
- ?????*/??
- ????public?User(String?username,?Date?birthday)?{??
- ????????this.username?=?username;??
- ????????this.birthday?=?birthday;??
- ????}??
- ???????//?省略??
- ????/*?
- ?????*?(non-Javadoc)?
- ?????*??
- ?????*?@see?java.lang.Object#toString()?
- ?????*/??
- ????@Override??
- ????public?String?toString()?{??
- ????????return?String.format("%s\t%s\t",?username,?birthday);??
- ????}??
- }??
覆盖toString()方法,输出用户信息!?
再看UserServiceImpl实现:?
Java代码?

?

- /**?
- ?*?@author?<a?href="mailto:zlex.dongliang@gmail.com">梁栋</a>?
- ?*?@since?1.0?
- ?*/??
- public?class?UserServiceImpl?implements?UserService?{??
- ????private?Logger?logger?=?Logger.getLogger(UserServiceImpl.class);??
- ??
- ????/*?
- ?????*?(non-Javadoc)?
- ?????*??
- ?????*?@see?
- ?????*?org.zlex.spring.httpinvoke.service.UserService#getUser(java.lang.String)?
- ?????*/??
- ????@Override??
- ????public?User?getUser(String?username)?{??
- ????????if?(logger.isDebugEnabled())?{??
- ????????????logger.debug("username:["?+?username?+?"]");??
- ????????}??
- ????????User?user?=?new?User(username,?new?Date());??
- ????????if?(logger.isDebugEnabled())?{??
- ????????????logger.debug("user:["?+?user?+?"]");??
- ????????}??
- ????????return?user;??
- ????}??
- ??
- }??
只把用户信息打出来即可说明调用效果!?
看applicationContext.xml?
Xml代码?

?

- <bean??
- ????id="userService"??
- class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">??
- ????<property??
- ????????name="service">??
- ????????<bean??
- class="org.zlex.spring.httpinvoke.service.impl.UserServiceImpl"?/>??
- ????</property>??
- ????<property??
- ????????name="serviceInterface"??
- ????????value="org.zlex.spring.httpinvoke.service.UserService"?/>??
- </bean>??
我们要把userService暴露出去,这样外部就可以通过http接口调用这个接口的实现了。?
说说HttpInvokerServiceExporter,这个类用来在服务器端包装需要暴露的接口。?
熟悉service,定义具体的实现类!?
Xml代码?

?

- <property??
- ????name="service">??
- ????<bean??
- lass="org.zlex.spring.httpinvoke.service.impl.UserServiceImpl"?/>??
- </property>??
熟悉serviceInterface指向需要暴露的接口,注意使用value标注接口名称!?
Xml代码?

?

- <property??
- ????name="serviceInterface"??
- ????value="org.zlex.spring.httpinvoke.service.UserService"?/>??
最后再看servlet.xml配置?
Xml代码?

?

- <bean?class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">??
- ????<property??
- ????????name="urlMap">??
- ????????<map>??
- ????????????<entry??
- ????????????????key="*"??
- ????????????????value-ref="userService"?/>??
- ????????</map>??
- ????</property>??
- </bean>??
直接将请求指向刚才配置的userService?
现在我们之间访问一下http://localhost:8080/spring/service/?

这就说明,服务器端配置已经成功了!如果在日志中频繁得到这种异常,那很可能服务器被恶意访问了!?
再看客户端实现:?
Java代码?

?

- /**?
- ?*?@author?<a?href="mailto:zlex.dongliang@gmail.com">梁栋</a>?
- ?*?@since?1.0?
- ?*/??
- public?class?UserServiceTest?{??
- ????private?Logger?logger?=?Logger.getLogger(UserServiceTest.class);??
- ????private?ApplicationContext?context;??
- ??
- ????private?UserService?userService;??
- ??
- ????@Before??
- ????public?void?initialize()?{??
- ????????context?=?new?ClassPathXmlApplicationContext("applicationContext.xml");??
- ????????userService?=?(UserService)?context.getBean("userService");??
- ????}??
- ??
- ????@Test??
- ????public?void?getUser()?{??
- ????????User?user?=?userService.getUser("zlex");??
- ????????if?(logger.isDebugEnabled())?{??
- ????????????logger.debug("user["?+?user?+?"]");??
- ????????}??
- ????}??
- }??
我们做了什么?Nothing!就跟调用一般Spring容器中的实现一样!?
再看applicationContext.xml:?
Xml代码?

?

- <bean??
- ????id="userService"??
- class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">??
- ????<property??
- ????????name="serviceUrl"??
- ????????value="http://localhost:8080/spring/service"?/>??
- ????<property??
- ????????name="serviceInterface"??
- ????????value="org.zlex.spring.httpinvoke.service.UserService"?/>??
- </bean>??
这里我们可以通过Spring容器调用userService,而实际上,他是一个
HttpInvokerProxyFactoryBean,在这个配置里,定义了访问地址serviceUrl,和访问接口serviceInterface。?
执行测试!?

如果我们这样写,其实默认调用了
SimpleHttpInvokerRequestExecutor做实现,这个实现恐怕只能作为演示来用!?
这也是效率问题所在!!!?
为提高效率,应该通过Commons-HttpClient!?
我们需要做什么?导入这个jar,改改xml就行!?
Xml代码?

?

- <bean??
- ????id="userService"??
- ????class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">??
- ????<property??
- ????????name="serviceUrl"??
- ????????value="http://localhost:8080/spring/service"?/>??
- ????<property??
- ????????name="serviceInterface"??
- ????????value="org.zlex.spring.httpinvoke.service.UserService"?/>??
- ????<property??
- ????????name="httpInvokerRequestExecutor">??
- ????????<ref??
- ????????????bean="httpInvokerRequestExecutor"?/>??
- ????</property>??
- </bean>??
- <bean??
- ????id="httpInvokerRequestExecutor"?class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">??
- ????<property??
- ????????name="httpClient">??
- ????????<bean??
- ????????????class="org.apache.commons.httpclient.HttpClient">??
- ????????????<property??
- ????????????????name="connectionTimeout"??
- ????????????????value="2000"?/>??
- ????????????<property??
- ????????????????name="timeout"??
- ????????????????value="5000"?/>??
- ????????</bean>??
- ????</property>??
- </bean>??
通过HttpClient,我们可以配置超时时间timeout和连接超时connectionTimeout两个属性,这样,服务器执行操作时,如果超时就可以强行释放连接,这样可怜的tomcat不会因为HttpInvoke连接不释放而被累死!

?
?
执行操作!?

这时,转为org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor实现了!?
不过同事认为,这个效率还是不够高!!!?
再改,改什么?还是xml!?
Xml代码?

?

- <bean??
- ????????id="httpInvokerRequestExecutor"??
- ????????class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">??
- ????????<property??
- ????????????name="httpClient">??
- ????????????<bean??
- ????????????????class="org.apache.commons.httpclient.HttpClient">??
- ????????????????<property??
- ????????????????????name="connectionTimeout"??
- ????????????????????value="2000"?/>??
- ????????????????<property??
- ????????????????????name="timeout"??
- ????????????????????value="5000"?/>??
- ????????????????<property??
- ????????????????????name="httpConnectionManager">??
- ????????????????????<ref??
- ????????????????????????bean="multiThreadedHttpConnectionManager"?/>??
- ????????????????</property>??
- ????????????</bean>??
- ????????</property>??
- ????</bean>??
- ????<bean??
- ????????id="multiThreadedHttpConnectionManager"??
- ????????class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager">??
- ????????<property??
- ????????????name="params">??
- ????????????<bean??
- ????????????????class="org.apache.commons.httpclient.params.HttpConnectionManagerParams">??
- ????????????????<property??
- ????????????????????name="maxTotalConnections"??
- ????????????????????value="600"?/>??
- ????????????????<property??
- ????????????????????name="defaultMaxConnectionsPerHost"??
- ????????????????????value="512"?/>??
- ????????????</bean>??
- ????????</property>??
- ????</bean>??
改用
MultiThreadedHttpConnectionManager,多线程!!!?
测试就不说了,实践证明:?
默认实现,服务器平均10s左右才能响应一个请求。?
多线程实现,服务器平均20ms左右响应一个请求。?
这简直不是一个数量级!!!?
注意:以上是用HttpClient的3.1版本以下的Jar包,3.1以上已不支持如下配置,相应的方法已经废弃!?
Xml代码?

?

- <property????
- ????name="connectionTimeout"????
- ????value="2000"?/>????
- <property????
- ????name="timeout"????
- ????value="5000"?/>???
如果仔细看看文档,?
引用HttpClient that uses a default MultiThreadedHttpConnectionManager.
commons 系列的实现怎么会不考虑多线程呢?人家默认实现就是多线程的!同时多虑了!?
当然,同时还补充了一句,需要控制连接数!?
好了 附上 新的HttpClient 优化 配置: (httpclient 3.1jar包以上版本)
Xml代码?
- <!--?调用?远程?openAPI?-->??
- ????<bean?id="userService"??
- ????????class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">??
- ????????<property?name="serviceUrl"??
- ????????????value="http://localhost:8080/spring/service"?/>??
- ????????<property?name="serviceInterface"?value="org.zlex.spring.httpinvoke.service.UserService"?/>??
- ????????<!--?into?project?引用?配置方案?-->??
- ????????<property?name="httpInvokerRequestExecutor"?ref="httpInvokerRequestExecutor"?/>??
- ????</bean>??
- ?<!--?httpinvoker?optimization?让?httpinvoker转用?HttpClient通信,不使用默认的?SimpleHttpInvokerRequestExecutor?-->??
- ????<bean?id="httpInvokerRequestExecutor"?class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor">????
- ????????<property?name="httpClient"?ref="httpclient"?/>????
- ????</bean>??
- ??????
- ????<!--??HttpClient?启用Apache?HttpClient?通信?-->??
- ????<bean?id="httpclient"?class="org.apache.commons.httpclient.HttpClient">????
- ????????<constructor-arg>????
- ????????????<ref?bean="connectionManager"/>????
- ????????</constructor-arg>?????
- ????</bean>??
- ??????
- ????<!--?http管理参数配置?-->??
- ????<bean?id="connectionManager"?class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager">????
- ????????<property?name="params"?ref="connectionManagerParams"/>????
- ????</bean>???
- ??????
- ????<!--?httpclient线程池?-->????
- ????<bean?id="connectionManagerParams"?class="org.apache.commons.httpclient.params.HttpConnectionManagerParams">??
- ????????<!--?设置?连接超时时间(毫秒),默认为0不使用?-->????
- ????????<property?name="connectionTimeout"?value="5000"/>???
- ????????<!--?设置?读取数据超时(毫秒),默认为0不使用?-->???
- ????????<property?name="soTimeout"?value="10000"/>????
- ????????<!--?设置默认的连接数量最大允许对一个给定的主机配置?-->??
- ????????<property?name="maxTotalConnections"?value="30"/>??
- ????????<!--?允许的最大连接数?-->????
- ????????<property?name="defaultMaxConnectionsPerHost"?value="20"/>????
- ????</bean>? ?
?
里面的connectionManagerParams配置 根据你的 环境 改变它吧...变成最优方案^_^...
另外,用spring 默认的?SimpleHttpInvokerRequestExecutor 时, 有时会产生
?java.net.SocketTimeoutException: Read timed out
?Could not access HTTP invoker remote service at [http://localhost:8080/spring/service]; nested exception is java.net.SocketTimeoutException: Read timed out?
就是没有设置 超时 或 连接溢出 超时的没释放. 所以除了演示外其他项目都要改成 上面 新的配置,?让?httpinvoker转用 apache?HttpClient通信,不使用默认的?SimpleHttpInvokerRequestExecutor。虽然底层一样,但处理上不一样。
OK了 完结!!! (有时间 再发表SimpleHttpInvokerRequestExecutor的源码)...
1 楼 yuhui0531 2012-04-19 没源码没几把