分享

简单之美 | 基于Dubbo的Hessian协议实现远程调用

 埃德温会馆 2015-12-13

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

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

1package org.shirdrn.platform.dubbo.service.rpc.api;
2 
3public interface SolrSearchService {
4    String search(String collection, String q, String type, int start, int rows);
5}

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

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

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

01package org.shirdrn.platform.dubbo.service.rpc.server;
02 
03import java.io.IOException;
04import java.util.HashMap;
05import java.util.Map;
06 
07import org.apache.commons.logging.Log;
08import org.apache.commons.logging.LogFactory;
09import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
10import org.shirdrn.platform.dubbo.service.rpc.utils.QueryPostClient;
11import org.springframework.context.support.ClassPathXmlApplicationContext;
12 
13public class SolrSearchServer implements SolrSearchService {
14 
15    private static final Log LOG = LogFactory.getLog(SolrSearchServer.class);
16    private String baseUrl;
17    private final QueryPostClient postClient;
18    private static final Map<String, FormatHandler> handlers = new HashMap<String, FormatHandler>(0);
19    static {
20        handlers.put("xml", new FormatHandler() {
21            public String format() {
22                return "&wt=xml";
23            }
24        });
25        handlers.put("json", new FormatHandler() {
26            public String format() {
27                return "&wt=json";
28            }
29        });
30    }
31 
32    public SolrSearchServer() {
33        super();
34        postClient = QueryPostClient.newIndexingClient(null);
35    }
36 
37    public void setBaseUrl(String baseUrl) {
38        this.baseUrl = baseUrl;
39    }
40 
41    public String search(String collection, String q, String type, int start, int rows) {
42        StringBuffer url = new StringBuffer();
43        url.append(baseUrl).append(collection).append("/select?").append(q);
44        url.append("&start=").append(start).append("&rows=").append(rows);
45        url.append(handlers.get(type.toLowerCase()).format());
46        LOG.info("[REQ] " + url.toString());
47        return postClient.request(url.toString());
48    }
49 
50    interface FormatHandler {
51        String format();
52    }
53}

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

01<?xml version="1.0" encoding="UTF-8"?>
02 
03<beans xmlns="http://www./schema/beans"
07 
08    <dubbo:application name="search-provider" />
09    <dubbo:registry
10        address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
11    <dubbo:protocol name="hessian" port="8080" server="servlet" />
12    <bean id="searchService"
13        class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer">
14        <property name="baseUrl" value="http://nginx-lbserver/solr-cloud/" />
15    </bean>
16    <dubbo:service
17        interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService"
18        ref="searchService" path="http_dubbo/search" />
19 
20</beans>

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

01package org.shirdrn.platform.dubbo.context;
02 
03import javax.servlet.ServletContextEvent;
04import javax.servlet.ServletContextListener;
05 
06import org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer;
07import org.springframework.context.support.ClassPathXmlApplicationContext;
08import org.springframework.web.context.ContextLoader;
09 
10public class SearchContextLoader extends ContextLoader implements ServletContextListener {
11 
12    @Override
13    public void contextDestroyed(ServletContextEvent arg0) {
14        // TODO Auto-generated method stub
15 
16    }
17 
18    @Override
19    public void contextInitialized(ServletContextEvent arg0) {
20        String config = arg0.getServletContext().getInitParameter("contextConfigLocation");
21        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
22        context.start();
23    }
24 
25}

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

01<?xml version="1.0" encoding="UTF-8"?>
02<web-app id="WebApp_ID" version="2.4"
05    <display-name>http_dubbo</display-name>
06 
07    <listener>
08        <listener-class>org.shirdrn.platform.dubbo.context.SearchContextLoader</listener-class>
09    </listener>
10    <context-param>
11        <param-name>contextConfigLocation</param-name>
12        <param-value>classpath:search-provider.xml</param-value>
13    </context-param>
14 
15    <servlet>
16        <servlet-name>search</servlet-name>
17        <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
18        <init-param>
19            <param-name>home-class</param-name>
20            <param-value>org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer</param-value>
21        </init-param>
22        <init-param>
23            <param-name>home-api</param-name>
24            <param-value>org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService</param-value>
25        </init-param>
26        <load-on-startup>1</load-on-startup>
27    </servlet>
28    <servlet-mapping>
29        <servlet-name>search</servlet-name>
30        <url-pattern>/search</url-pattern>
31    </servlet-mapping>
32 
33    <welcome-file-list>
34        <welcome-file>index.html</welcome-file>
35        <welcome-file>index.htm</welcome-file>
36        <welcome-file>index.jsp</welcome-file>
37        <welcome-file>default.html</welcome-file>
38        <welcome-file>default.htm</welcome-file>
39        <welcome-file>default.jsp</welcome-file>
40    </welcome-file-list>
41</web-app>

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

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

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

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

01<?xml version="1.0" encoding="UTF-8"?>
02 
03<beans xmlns="http://www./schema/beans"
07 
08    <dubbo:application name="search-consumer" />
09    <dubbo:registry
10        address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
11    <dubbo:reference id="searchService"
12        interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" />
13 
14</beans>

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

01package org.shirdrn.platform.dubbo.service.rpc.client;
02 
03import java.util.concurrent.Callable;
04import java.util.concurrent.Future;
05 
06import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
07import org.springframework.beans.BeansException;
08import org.springframework.context.support.AbstractXmlApplicationContext;
09import org.springframework.context.support.ClassPathXmlApplicationContext;
10 
11import com.alibaba.dubbo.rpc.RpcContext;
12 
13public class SearchConsumer {
14 
15    private final String collection;
16    private AbstractXmlApplicationContext context;
17    private SolrSearchService searchService;
18 
19    public SearchConsumer(String collection, Callable<AbstractXmlApplicationContext> call) {
20        super();
21        this.collection = collection;
22        try {
23            context = call.call();
24            context.start();
25            searchService = (SolrSearchService) context.getBean("searchService");
26        } catch (BeansException e) {
27            e.printStackTrace();
28        } catch (Exception e) {
29            e.printStackTrace();
30        }
31    }
32 
33    public Future<String> asyncCall(final String q, final String type, final int start, final int rows) {
34        Future<String> future = RpcContext.getContext().asyncCall(new Callable<String>() {
35            public String call() throws Exception {
36                return search(q, type, start, rows);
37            }
38        });
39        return future;
40    }
41 
42    public String syncCall(final String q, final String type, final int start, final int rows) {
43        return search(q, type, start, rows);
44    }
45 
46    private String search(final String q, final String type, final int start, final int rows) {
47        return searchService.search(collection, q, type, start, rows);
48    }
49 
50    public static void main(String[] args) throws Exception {
51        final String collection = "tinycollection";
52        final String beanXML = "search-consumer.xml";
53        final String config = SearchConsumer.class.getPackage().getName().replace('.', '/') + "/" + beanXML;
54        SearchConsumer consumer = new SearchConsumer(collection, new Callable<AbstractXmlApplicationContext>() {
55            public AbstractXmlApplicationContext call() throws Exception {
56                final AbstractXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
57                return context;
58            }
59        });
60 
61        String q = "q=上海&fl=*&fq=building_type:1";
62        int start = 0;
63        int rows = 10;
64        String type = "xml";
65        for (int k = 0; k < 10; k++) {
66            for (int i = 0; i < 10; i++) {
67                start = 1 * 10 * i;
68                if (i % 2 == 0) {
69                    type = "xml";
70                } else {
71                    type = "json";
72                }
73                String result = consumer.syncCall(q, type, start, rows);
74                System.out.println(result);
75                // Future<String> future = consumer.asyncCall(q, type, start,
76                // rows);
77                // System.out.println(future.get());
78            }
79        }
80    }
81 
82}

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

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

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

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

01package org.shirdrn.rpc.hessian;
02 
03import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService;
04 
05import com.caucho.hessian.client.HessianProxyFactory;
06 
07public class HessianConsumer {
08 
09    public static void main(String[] args) throws Throwable {
10 
11        String serviceUrl = "http://10.95.3.74:8080/http_dubbo/search";
12        HessianProxyFactory factory = new HessianProxyFactory();
13 
14        SolrSearchService searchService = (SolrSearchService) factory.create(SolrSearchService.class, serviceUrl);
15 
16        String q = "q=上海&fl=*&fq=building_type:1";
17        String collection = "tinycollection";
18        int start = 0;
19        int rows = 10;
20        String type = "xml";
21        String result = searchService.search(collection, q, type, start, rows);
22        System.out.println(result);
23    }
24}

我们只需要知道提供服务暴露的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实现库:

2cd mustaine
3sudo python setup.py install

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

01#!/usr/bin/python
02 
03# coding=utf-8
04from mustaine.client import HessianProxy
05 
07q = 'q=*:*&fl=*&fq=building_type:1'
08start = 0
09rows = 10
10resType = 'xml'
11collection = 'tinycollection'
12 
13if __name__ == '__main__':
14     proxy = HessianProxy(serviceUrl)
15     result = proxy.search(collection, q, resType, start, rows)
16     print result

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

参考链接

Creative Commons License

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多