基于Dubbo的Hessian协议实现远程调用

Dubbo基于Hessian实现了自己Hessian协议,可以直接通过配置的Dubbo内置的其他协议,在服务消费方进行远程调用,也就是说,服务调用方需要使用Java语言来基于Dubbo调用提供方服务,限制了服务调用方。同时,使用Dubbo的Hessian协议实现提供方服务,而调用方可以使用标准的Hessian接口来调用,原生的Hessian协议已经支持多语言客户端调用,支持语言如下所示:

下面,我们的思路是,先基于Dubbo封装的Hessian协议,实现提供方服务和消费方调用服务,双方必须都使用Dubbo来开发;然后,基于Dubbo封装的Hessian协议实现提供方服务,然后服务消费方使用标准的Hessian接口来进行远程调用,分别使用Java和Python语言来实现。而且,我们实现的提供方服务通过Tomcat发布到服务注册中心。
首先,使用Java语言定义一个搜索服务的接口,代码如下所示:

package org.shirdrn.platform.dubbo.service.rpc.api;

public interface SolrSearchService {
	String search(String collection, String q, String type, int start, int rows);
}

上面接口提供了搜索远程调用功能。

基于Dubbo的Hessian协议实现提供方服务

提供方实现基于Dubbo封装的Hessian协议,实现接口SolrSearchService,实现代码如下所示:

package org.shirdrn.platform.dubbo.service.rpc.server;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
import org.shirdrn.platform.dubbo.service.rpc.utils.QueryPostClient;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SolrSearchServer implements SolrSearchService {

	private static final Log LOG = LogFactory.getLog(SolrSearchServer.class);
	private String baseUrl;
	private final QueryPostClient postClient;
	private static final Map<String, FormatHandler> handlers = new HashMap<String, FormatHandler>(0);
	static {
		handlers.put("xml", new FormatHandler() {
			public String format() {
				return "&wt=xml";
			}
		});
		handlers.put("json", new FormatHandler() {
			public String format() {
				return "&wt=json";
			}
		});
	}

	public SolrSearchServer() {
		super();
		postClient = QueryPostClient.newIndexingClient(null);
	}

	public void setBaseUrl(String baseUrl) {
		this.baseUrl = baseUrl;
	}

	public String search(String collection, String q, String type, int start, int rows) {
		StringBuffer url = new StringBuffer();
		url.append(baseUrl).append(collection).append("/select?").append(q);
		url.append("&start=").append(start).append("&rows=").append(rows);
		url.append(handlers.get(type.toLowerCase()).format());
		LOG.info("[REQ] " + url.toString());
		return postClient.request(url.toString());
	}

	interface FormatHandler {
		String format();
	}
}

因为考虑到后面要使用标准Hessian接口来调用,这里接口方法参数全部使用内置标准类型。然后,我们使用Dubbo的配置文件进行配置,文件search-provider.xml的内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	<dubbo:application name="search-provider" />
	<dubbo:registry
		address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
	<dubbo:protocol name="hessian" port="8080" server="servlet" />
	<bean id="searchService"
		class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer">
		<property name="baseUrl" value="http://nginx-lbserver/solr-cloud/" />
	</bean>
	<dubbo:service
		interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService"
		ref="searchService" path="http_dubbo/search" />

</beans>

因为使用Tomcat发布提供方服务,所以我们需要实现Spring的org.springframework.web.context.ContextLoader来初始化应用上下文(基于Spring的IoC容器来管理服务对象)。实现类SearchContextLoader代码如下所示:

package org.shirdrn.platform.dubbo.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.ContextLoader;

public class SearchContextLoader extends ContextLoader implements ServletContextListener {

	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		String config = arg0.getServletContext().getInitParameter("contextConfigLocation");
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
		context.start();
	}

}

最后,配置Web应用部署描述符文件,web.xml内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<display-name>http_dubbo</display-name>

	<listener>
		<listener-class>org.shirdrn.platform.dubbo.context.SearchContextLoader</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:search-provider.xml</param-value>
	</context-param>

	<servlet>
		<servlet-name>search</servlet-name>
		<servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>home-class</param-name>
			<param-value>org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer</param-value>
		</init-param>
		<init-param>
			<param-name>home-api</param-name>
			<param-value>org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>search</servlet-name>
		<url-pattern>/search</url-pattern>
	</servlet-mapping>

	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
</web-app>

启动Tomcat以后,就可以将提供方服务发布到服务注册中心,这里服务注册中心我们使用的是ZooKeeper集群,可以参考上面Dubbo配置文件search-provider.xml的配置内容。

下面,我们通过两种方式来调用已经注册到服务注册中心的服务。

  • 基于Dubbo的Hessian协议远程调用

服务消费方,通过Dubbo配置文件来指定注册到注册中心的服务,配置文件search-consumer.xml的内容,如下所示:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	<dubbo:application name="search-consumer" />
	<dubbo:registry
		address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
	<dubbo:reference id="searchService"
		interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" />

</beans>

然后,使用Java实现远程调用,实现代码如下所示:

package org.shirdrn.platform.dubbo.service.rpc.client;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
import org.springframework.beans.BeansException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.alibaba.dubbo.rpc.RpcContext;

public class SearchConsumer {

	private final String collection;
	private AbstractXmlApplicationContext context;
	private SolrSearchService searchService;

	public SearchConsumer(String collection, Callable<AbstractXmlApplicationContext> call) {
		super();
		this.collection = collection;
		try {
			context = call.call();
			context.start();
			searchService = (SolrSearchService) context.getBean("searchService");
		} catch (BeansException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Future<String> asyncCall(final String q, final String type, final int start, final int rows) {
		Future<String> future = RpcContext.getContext().asyncCall(new Callable<String>() {
			public String call() throws Exception {
				return search(q, type, start, rows);
			}
		});
		return future;
	}

	public String syncCall(final String q, final String type, final int start, final int rows) {
		return search(q, type, start, rows);
	}

	private String search(final String q, final String type, final int start, final int rows) {
		return searchService.search(collection, q, type, start, rows);
	}

	public static void main(String[] args) throws Exception {
		final String collection = "tinycollection";
		final String beanXML = "search-consumer.xml";
		final String config = SearchConsumer.class.getPackage().getName().replace('.', '/') + "/" + beanXML;
		SearchConsumer consumer = new SearchConsumer(collection, new Callable<AbstractXmlApplicationContext>() {
			public AbstractXmlApplicationContext call() throws Exception {
				final AbstractXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
				return context;
			}
		});

		String q = "q=上海&fl=*&fq=building_type:1";
		int start = 0;
		int rows = 10;
		String type = "xml";
		for (int k = 0; k < 10; k++) {
			for (int i = 0; i < 10; i++) {
				start = 1 * 10 * i;
				if (i % 2 == 0) {
					type = "xml";
				} else {
					type = "json";
				}
				String result = consumer.syncCall(q, type, start, rows);
				System.out.println(result);
				// Future<String> future = consumer.asyncCall(q, type, start,
				// rows);
				// System.out.println(future.get());
			}
		}
	}

}

执行该调用实现,可以远程调用提供方发布的服务。
这种方式限制了服务调用方也必须使用Dubbo来开发调用的代码,也就是限制了编程的语言,而无论是对于内部还是外部,各个团队之间必然存在语言的多样性,如果限制了编程语言,那么开发的服务也只能在内部使用。

  • 基于标准Hessian协议接口的远程调用

下面,使用标准Hessian接口来实现远程调用,这时就不需要关心服务提供方的所使用的开发语言,因为最终是通过HTTP的方式来访问。我们需要下载Hessian对应语言的调用实现库,才能更方便地编程。

使用Java语言实现远程调用
使用Java语言实现,代码如下所示:

package org.shirdrn.rpc.hessian;

import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;

import com.caucho.hessian.client.HessianProxyFactory;

public class HessianConsumer {

	public static void main(String[] args) throws Throwable {

		String serviceUrl = "http://10.95.3.74:8080/http_dubbo/search";
		HessianProxyFactory factory = new HessianProxyFactory();

		SolrSearchService searchService = (SolrSearchService) factory.create(SolrSearchService.class, serviceUrl);

		String q = "q=上海&fl=*&fq=building_type:1";
		String collection = "tinycollection";
		int start = 0;
		int rows = 10;
		String type = "xml";
		String result = searchService.search(collection, q, type, start, rows);
		System.out.println(result);
	}
}

我们只需要知道提供服务暴露的URL和服务接口即可,这里URL为http://10.95.3.74:8080/http_dubbo/search,接口为org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService。运行上面程序,可以调用提供方发布的服务。

使用Python语言实现远程调用
使用Python客户端来进行远程调用,我们可以从https://github.com/bgilmore/mustaine下载,然后安装Hessian的代理客户端Python实现库:

git clone https://github.com/bgilmore/mustaine.git
cd mustaine
sudo python setup.py install

然后就可以使用了,使用Python进行远程调用的实现代码如下所示:

#!/usr/bin/python

# coding=utf-8
from mustaine.client import HessianProxy

serviceUrl = 'http://10.95.3.74:8080/http_dubbo/search'
q = 'q=*:*&fl=*&fq=building_type:1'
start = 0
rows = 10
resType = 'xml'
collection = 'tinycollection'

if __name__ == '__main__':
     proxy = HessianProxy(serviceUrl)
     result = proxy.search(collection, q, resType, start, rows)
     print result

运行上面程序,就可以看到远程调用的结果。

参考链接

Creative Commons License

本文基于署名-非商业性使用-相同方式共享 4.0许可协议发布,欢迎转载、使用、重新发布,但务必保留文章署名时延军(包含链接:http://shiyanjun.cn),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

评论(23): “基于Dubbo的Hessian协议实现远程调用

    • 应该是不支持的,你可以通过其他方式,发布成基于HTTP的,不过这样服务提供方无法使用很多Dubbo内置支持的特性,可以参考下https://github.com/dangdangdotcom/dubbox

  1. 尝试使用tomcat容器发布服务,可以直接使用ContextLoader来加载dubbo的配置文件
    那个,发布基于dubbo协议的服务时,建议使用tomcat容器吗,还是直接跑在jvm的main进程上比较好?

  2. 您好,请问如果发布不使用tomcat如何操作?我直接java发布在监控中心可以发现服务,hessian://192.168.1.248:20886/http_dubbo/search?anyhost=true。。。。。。timestamp=1443011342158&version=DEV,我在客户端调用, String serviceUrl = “http://192.168.1.248:20886/http_dubbo/search?version=DEV”; 按照你的例子调不通,请问该如何调用?URL应该是服务提供者还是注册中心?

    • 用Java实现调用,通过注册中心进行调用,上面例子,很明显consumer只是需要知道服务接口和配置注册中心地址就能够调用。上面的例子,使用tomcat部署,并指定context path,是为使用python实现消费者代码调用的,使用python用的是直连的方式,需要知道实际hession协议的服务端口,而不是dubbo协议端口。

  3. 写的太好了,找了好几天这方面的东西。
    我公司的提供服务端是用spring+dubbo开发的,我要在web项目上调用接口,但是又不想用服务端的那个框架,千辛万苦你帮我解决了问题。 感谢!!!

    • 如果不是用Java实现Consumer,你需要自己去管理Provider方提供的多个Hessian协议的服务地址,比如可以使用Nginx,将多个服务地址配置成backend,然后通过Nginx暴露的服务端口作为Consumer的调用地址。

      • yanjun你好,你的意思是,使用hessian, 如果不是Java实现consumer,那么我的url要配置成注册中心的地址,即通过注册中心的请求转发来找到对应的服务地址,注册中心的请求量应该就会很大吧? 如果是Java实现consumer,url就直接配置成服务的地址?

        • 你理解有误,注册中心不是用来转发请求的。如果是Java Consumer,可以直接通过注册中心自动发现Provider,如果多个Provider都注册到注册中心,那么Consumer就能够在是用某一个Provider失效时,被通知而继续是用其他可用的Provider提供的服务。

  4. 我想问一下,既然能够调用远程接口,那么需要把provider打成jar包导过来,才能使用啊!! 那有什么意义?

  5. 您的开篇第一句不太正确,“服务调用方需要使用Java语言来基于Dubbo调用提供方服务,限制了服务调用方”。

    官方文档(http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-hessian%3A%2F%2F):
    可以和原生Hessian服务互操作,即:

    提供者用Dubbo的Hessian协议暴露服务,消费者直接用标准Hessian接口调用,
    或者提供方用标准Hessian暴露服务,消费方用Dubbo的Hessian协议调用。

  6. 请问下,如果不使用tomcat而使用内置的jetty,如何用标准的hessian访问dubbo暴露的hessian接口?

    • 看这里吧:https://github.com/shirdrn/kodz-all/blob/master/dubbo/src/main/java/org/shirdrn/platform/dubbo/service/rpc/utils/QueryPostClient.java

  7. 楼主,为什么要自己实现contextloaderlistener呢?为什么不用spring自带的listener

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>