Dubbo实现RPC调用使用入门

使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由于Dubbo将这些协议的实现进行了封装了,无论是服务端(开发服务)还是客户端(调用服务),都不需要关心协议的细节,只需要在配置中指定使用的协议即可,从而保证了服务提供方与服务消费方之间的透明。
另外,如果我们使用Dubbo的服务注册中心组件,这样服务提供方将服务发布到注册的中心,只是将服务的名称暴露给外部,而服务消费方只需要知道注册中心和服务提供方提供的服务名称,就能够透明地调用服务,后面我们会看到具体提供服务和消费服务的配置内容,使得双方之间交互的透明化。

示例场景

我们给出一个示例的应用场景:
服务方提供一个搜索服务,对服务方来说,它基于SolrCloud构建了搜索服务,包含两个集群,ZooKeeper集群和Solr集群,然后在前端通过Nginx来进行反向代理,达到负载均衡的目的。
服务消费方就是调用服务进行查询,给出查询条件(满足Solr的REST-like接口)。

应用设计

基于上面的示例场景,我们打算使用ZooKeeper集群作为服务注册中心。注册中心会暴露给服务提供方和服务消费方,所以注册服务的时候,服务先提供方只需要提供Nginx的地址给注册中心,但是注册中心并不会把这个地址暴露给服务消费方,如图所示:
provider-registry-consumer
我们先定义一下,通信双方需要使用的接口,如下所示:

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

public interface SolrSearchService {

	String search(String collection, String q, ResponseType type, int start, int rows);
	
	public enum ResponseType {
		JSON,
		XML
	}	
}

基于上图中的设计,下面我们分别详细说明Provider和Consumer的设计及实现。

  • Provider服务设计

Provider所发布的服务组件,包含了一个SolrCloud集群,在SolrCloud集群前端又加了一个反向代理层,使用Nginx来均衡负载。Provider的搜索服务系统,设计如下图所示:
solrcloud-cluster
上图中,实际Nginx中将请求直接转发内部的Web Servers上,在这个过程中,使用ZooKeeper来进行协调:从多个分片(Shard)服务器上并行搜索,最后合并结果。我们看一下Nginx配置的内容片段:

user  nginx;
worker_processes  4;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

	upstream master {
		server slave1:8888 weight=1;
		server slave4:8888 weight=1;
		server slave6:8888 weight=1;
	}

	server {
		listen 80;
		server_name master;
		location / {
			root /usr/share/nginx/html/solr-cloud;
			index  index.html index.htm;
			proxy_pass   http://master;
			include /home/hadoop/servers/nginx/conf/proxy.conf;
		}
	}
}

一共配置了3台Solr服务器,因为SolrCloud集群中每一个节点都可以接收搜索请求,然后由整个集群去并行搜索。最后,我们要通过Dubbo服务框架来基于已有的系统来开发搜索服务,并通过Dubbo的注册中心来发布服务。
首先需要实现服务接口,实现代码如下所示:

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<ResponseType, FormatHandler> handlers = new HashMap<ResponseType, FormatHandler>(0);
	static {
		handlers.put(ResponseType.XML, new FormatHandler() {
			public String format() {
				return "&wt=xml";
			}
		});
		handlers.put(ResponseType.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, ResponseType 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).format());
		LOG.info("[REQ] " + url.toString());
		return postClient.request(url.toString());
	}
	
	interface FormatHandler {
		String format();
	}
	
	public static void main(String[] args) throws IOException {
		String config = SolrSearchServer.class.getPackage().getName().replace('.', '/') + "/search-provider.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config);
        context.start();
        System.in.read();
	}

}

对应的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="dubbo" port="20880" />
	<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" />

</beans>

上面,Dubbo服务注册中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo协议。配置服务接口的时候,可以按照Spring的Bean的配置方式来配置,注入需要的内容,我们这里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/

  • Consumer调用服务设计

这个就比较简单了,拷贝服务接口,同时要配置一下Dubbo的配置文件,写个简单的客户端调用就可以实现。客户端实现的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.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService.ResponseType;
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 ResponseType 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 ResponseType type, final int start, final int rows) {
		return search(q, type, start, rows);
	}

	private String search(final String q, final ResponseType 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;
		ResponseType type  = ResponseType.XML;
		for (int k = 0; k < 10; k++) {
			for (int i = 0; i < 10; i++) {
				start = 1 * 10 * i;
				if(i % 2 == 0) {
					type = ResponseType.XML;
				} else {
					type = ResponseType.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());
			}
		}
	}
}

查询的时候,需要提供查询字符串,符合Solr语法,例如“q=上海&fl=*&fq=building_type:1”。配置文件,我们使用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>

运行说明

首先保证服务注册中心的ZooKeeper集群正常运行,然后启动SolrSearchServer,启动的时候直接将服务注册到ZooKeeper集群存储中,可以通过ZooKeeper的客户端脚本来查看注册的服务数据。一切正常以后,可以启动运行客户端SearchConsumer,调用SolrSearchServer所实现的远程搜索服务。

参考链接

Creative Commons License

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

评论(22): “Dubbo实现RPC调用使用入门

  1.  

    hi,哥们儿,你好

    跟你咨询个事儿,我们现在用的框架跟你差不多,rpc也是用的dubbo,然后底层搜索也是用的solr,但是有一个问题一直没有解决,就是dubbo调用solr的时候时常会出现invoke time out的问题,经查验主要的时间发费在dubbo用solrj发请求到solr接受到请求这段时间,solr的QTime都很短,怀疑是solrj的httpclient的连接数问题,现在调大了,但是还是时常出现这样的问题,想问你一下,有没有遇到类似问题

     

    多谢!!

    • 你先定位一下问题到底出在哪里,我觉得不会是SOLR的问题,多数是Dubbo那哪里配置的问题吧。我直接对SolrCloud集群做过压测,没出现过超时的现象,只是Tomcat撑不住导致无法处理请求。

  2. 你好,请教个问题,dubbo2.5.3版本目前依赖的spring2.5.6.SEC03,能不能将spring升级到3.0+?

  3. 在学习dubbo的过程中有几个问题请教一下:
    1,dubbo除按生产者按照dubbo规范发布服务以外,是否还存在其他方式
    2,作为消费者,是不是其他语言开发的系统也可以使用除j2ee系统以外
    期待您的回复

  4. 有个关于dubbo的问题请假下,按照说明,HTTP协议可以提供给JS直接调用,我用HTTPClient去调用,一直没有找到正确的调用方式,不知道你有什么建议?

    • Dubbo不支持你说的那种调用方式,原因可以查看这里:https://github.com/alibaba/dubbo/issues/54
      如果你非要用REST或类似REST方式的调用,可以参考这里:https://github.com/dangdangdotcom/dubbox

      • 谢谢。已经在用当当的dubbox,只是对dubbo文档里面说的支持HTTP调用,特地去测试了下。发现确实如上所说application/x-java-serialized-object。因此想问下其他人是不是在不改用REST方式能够用HTTP直接访问。如果不行,那我还是安耽去用dubbox提供的rest方式好了。

  5. 您好,我想问下,我是用的 dubbox 配置的rest 服务,为什么 注册到zookeeper 之后,仍然是只能 通过 原有服务提供方的地址访问,不能通过服务中心调用。

  6. 我有一个疑问??你前面使用了nginx做了反向代理,这个时候已经决定走那个搜索服务器了,而你这个dubbo框架,的服务又注册到指定的服务器上面了,他是不是接受到请求之后又重新发送给对应的服务处理呢,如果是这样的那么你的nginx和zookeper是不是反而是多此一举了,我先声明一点,dubbo服务是为了解决内部系统的耦合问题的,也就说业务系统和订单系统之间存在关联的时候,就需要提供一个公用的接口,并且暴露出来,这样业务系统就能在自己系统里面调用订单系统的服务.这样才是dubbo的真正用法

    • 我也这么认为,Nginx做了负载均衡,而Dubbo也是带有负载均衡的功能,他这么做我都不明白什么意思了

    • 你的理解有问题。这里的使用场景,是把Nginx+ZK+SolrCloud作为一个独立的服务A,Nginx是为了能够把请求打到Backend的SolrCloud任意一个Shard上,因为不排除某一个Shard可能有问题,最终Nginx对外暴露了一个统一的REST接口。而Dubbo Provider只是作为A服务的一个Proxy,如果你想在Dubbo层面做LB,那也可以的。

      • 您好,我的理解这里dubbo已经没有起到负载均衡的作用,dubbo只提供了RPC的功能。Nginx才是负载均衡的作用;
        如果要使用dubbo提供负载均衡的功能,需要去掉Nginx,起三个Provider应用,每个Provider连接一个SolrCloud的子节点;
        望回复,是否理解正确;

        • 按照作者的意思 Nginx是做为REST接口层做的LB ZK 是为dubbo做的LB “最终Nginx对外暴露了一个统一的REST接口”这个是Nginx的最大用处

  7. 请问作者,图中的app1 和app2是两个不同的应用,还是一个solrcloud?

    • 你可以看成是同一个SolrCloud集群,按照业务划分,app1和app2是逻辑独立的,比如app1是有关用户基础信息的,而app2有关行为信息的。

  8. 那如果节点有挂了的zk做负载均衡可以感知到,nginx不能感知到啊,那不是zk更适合做负载么

longyuanknight进行回复 取消回复

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

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