使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由于Dubbo将这些协议的实现进行了封装了,无论是服务端(开发服务)还是客户端(调用服务),都不需要关心协议的细节,只需要在配置中指定使用的协议即可,从而保证了服务提供方与服务消费方之间的透明。 另外,如果我们使用Dubbo的服务注册中心组件,这样服务提供方将服务发布到注册的中心,只是将服务的名称暴露给外部,而服务消费方只需要知道注册中心和服务提供方提供的服务名称,就能够透明地调用服务,后面我们会看到具体提供服务和消费服务的配置内容,使得双方之间交互的透明化。
示例场景
我们给出一个示例的应用场景: 服务方提供一个搜索服务,对服务方来说,它基于SolrCloud构建了搜索服务,包含两个集群,ZooKeeper集群和Solr集群,然后在前端通过Nginx来进行反向代理,达到负载均衡的目的。 服务消费方就是调用服务进行查询,给出查询条件(满足Solr的REST-like接口)。
应用设计
基于上面的示例场景,我们打算使用ZooKeeper集群作为服务注册中心。注册中心会暴露给服务提供方和服务消费方,所以注册服务的时候,服务先提供方只需要提供Nginx的地址给注册中心,但是注册中心并不会把这个地址暴露给服务消费方,如图所示: 我们先定义一下,通信双方需要使用的接口,如下所示:
01 package org.shirdrn.platform.dubbo.service.rpc.api; 02 03 public interface SolrSearchService { 04 05 String search(String collection, String q, ResponseType type, int start, introws); 06 07 public enum ResponseType { 08 JSON, 09 XML 10 } 11 }基于上图中的设计,下面我们分别详细说明Provider和Consumer的设计及实现。
Provider服务设计Provider所发布的服务组件,包含了一个SolrCloud集群,在SolrCloud集群前端又加了一个反向代理层,使用Nginx来均衡负载。Provider的搜索服务系统,设计如下图所示: 上图中,实际Nginx中将请求直接转发内部的Web Servers上,在这个过程中,使用ZooKeeper来进行协调:从多个分片(Shard)服务器上并行搜索,最后合并结果。我们看一下Nginx配置的内容片段:
01 user nginx; 02 worker_processes 4; 03 04 error_log /var/log/nginx/error.log warn; 05 pid /var/run/nginx.pid; 06 07 08 events { 09 worker_connections 1024; 10 } 11 12 13 http { 14 include /etc/nginx/mime.types; 15 default_type application/octet-stream; 16 17 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 '$status $body_bytes_sent "$http_referer" ' 19 '"$http_user_agent" "$http_x_forwarded_for"'; 20 21 access_log /var/log/nginx/access.log main; 22 23 sendfile on; 24 #tcp_nopush on; 25 26 keepalive_timeout 65; 27 28 #gzip on; 29 30 upstream master { 31 server slave1:8888 weight=1; 32 server slave4:8888 weight=1; 33 server slave6:8888 weight=1; 34 } 35 36 server { 37 listen 80; 38 server_name master; 39 location / { 40 root /usr/share/nginx/html/solr-cloud; 41 index index.html index.htm; 42 proxy_pass http://master; 43 include /home/hadoop/servers/nginx/conf/proxy.conf; 44 } 45 } 46 }一共配置了3台Solr服务器,因为SolrCloud集群中每一个节点都可以接收搜索请求,然后由整个集群去并行搜索。最后,我们要通过Dubbo服务框架来基于已有的系统来开发搜索服务,并通过Dubbo的注册中心来发布服务。 首先需要实现服务接口,实现代码如下所示:
01 package org.shirdrn.platform.dubbo.service.rpc.server; 02 03 import java.io.IOException; 04 import java.util.HashMap; 05 import java.util.Map; 06 07 import org.apache.commons.logging.Log; 08 import org.apache.commons.logging.LogFactory; 09 import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService; 10 import org.shirdrn.platform.dubbo.service.rpc.utils.QueryPostClient; 11 import org.springframework.context.support.ClassPathXmlApplicationContext; 12 13 public 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<ResponseType, FormatHandler> handlers = newHashMap<ResponseType, FormatHandler>(0); 19 static { 20 handlers.put(ResponseType.XML, new FormatHandler() { 21 public String format() { 22 return "&wt=xml"; 23 } 24 }); 25 handlers.put(ResponseType.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, ResponseType type, 42 int start, int rows) { 43 StringBuffer url = new StringBuffer(); 44 url.append(baseUrl).append(collection).append("/select?").append(q); 45 url.append("&start=").append(start).append("&rows=").append(rows); 46 url.append(handlers.get(type).format()); 47 LOG.info("[REQ] " + url.toString()); 48 return postClient.request(url.toString()); 49 } 50 51 interface FormatHandler { 52 String format(); 53 } 54 55 public static void main(String[] args) throws IOException { 56 String config = SolrSearchServer.class.getPackage().getName().replace('.', '/') + "/search-provider.xml"; 57 ClassPathXmlApplicationContext context = newClassPathXmlApplicationContext(config); 58 context.start(); 59 System.in.read(); 60 } 61 62 }对应的Dubbo配置文件为search-provider.xml,内容如下所示:
01 <?xml version="1.0" encoding="UTF-8"?> 02 03 <beans xmlns="http://www.springframework.org/schema/beans" 04 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 05 xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd 06 http://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 07 08 <dubbo:application name="search-provider" /> 09 <dubbo:registry address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" /> 10 <dubbo:protocol name="dubbo" port="20880" /> 11 <bean id="searchService"class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer"> 12 <property name="baseUrl" value="http://nginx-lbserver/solr-cloud/" /> 13 </bean> 14 <dubbo:serviceinterface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService"ref="searchService" /> 15 16 </beans>上面,Dubbo服务注册中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo协议。配置服务接口的时候,可以按照Spring的Bean的配置方式来配置,注入需要的内容,我们这里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/。
Consumer调用服务设计这个就比较简单了,拷贝服务接口,同时要配置一下Dubbo的配置文件,写个简单的客户端调用就可以实现。客户端实现的Java代码如下所示:
01 package org.shirdrn.platform.dubbo.service.rpc.client; 02 03 import java.util.concurrent.Callable; 04 import java.util.concurrent.Future; 05 06 import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService; 07 import org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService.ResponseType; 08 import org.springframework.beans.BeansException; 09 import org.springframework.context.support.AbstractXmlApplicationContext; 10 import org.springframework.context.support.ClassPathXmlApplicationContext; 11 12 import com.alibaba.dubbo.rpc.RpcContext; 13 14 public class SearchConsumer { 15 16 private final String collection; 17 private AbstractXmlApplicationContext context; 18 private SolrSearchService searchService; 19 20 public SearchConsumer(String collection, Callable<AbstractXmlApplicationContext> call) { 21 super(); 22 this.collection = collection; 23 try { 24 context = call.call(); 25 context.start(); 26 searchService = (SolrSearchService) context.getBean("searchService"); 27 } catch (BeansException e) { 28 e.printStackTrace(); 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33 34 public Future<String> asyncCall(final String q, final ResponseType type, finalint start, final int rows) { 35 Future<String> future = RpcContext.getContext().asyncCall(newCallable<String>() { 36 public String call() throws Exception { 37 return search(q, type, start, rows); 38 } 39 }); 40 return future; 41 } 42 43 public String syncCall(final String q, final ResponseType type, final intstart, final int rows) { 44 return search(q, type, start, rows); 45 } 46 47 private String search(final String q, final ResponseType type, final intstart, final int rows) { 48 return searchService.search(collection, q, type, start, rows); 49 } 50 51 public static void main(String[] args) throws Exception { 52 final String collection = "tinycollection"; 53 final String beanXML = "search-consumer.xml"; 54 final String config = SearchConsumer.class.getPackage().getName().replace('.', '/') + "/" + beanXML; 55 SearchConsumer consumer = new SearchConsumer(collection, newCallable<AbstractXmlApplicationContext>() { 56 public AbstractXmlApplicationContext call() throws Exception { 57 final AbstractXmlApplicationContext context = newClassPathXmlApplicationContext(config); 58 return context; 59 } 60 }); 61 62 String q = "q=上海&fl=*&fq=building_type:1"; 63 int start = 0; 64 int rows = 10; 65 ResponseType type = ResponseType.XML; 66 for (int k = 0; k < 10; k++) { 67 for (int i = 0; i < 10; i++) { 68 start = 1 * 10 * i; 69 if(i % 2 == 0) { 70 type = ResponseType.XML; 71 } else { 72 type = ResponseType.JSON; 73 } 74 // String result = consumer.syncCall(q, type, start, rows); 75 // System.out.println(result); 76 Future<String> future = consumer.asyncCall(q, type, start, rows); 77 // System.out.println(future.get()); 78 } 79 } 80 } 81 }查询的时候,需要提供查询字符串,符合Solr语法,例如“q=上海&fl=*&fq=building_type:1”。配置文件,我们使用search-consumer.xml,内容如下所示:
01 <?xml version="1.0" encoding="UTF-8"?> 02 03 <beans xmlns="http://www.springframework.org/schema/beans" 04 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 05 xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd 06 http://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 07 08 <dubbo:application name="search-consumer" /> 09 <dubbo:registry address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" /> 10 <dubbo:reference id="searchService"interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" /> 11 12 </beans>运行说明
首先保证服务注册中心的ZooKeeper集群正常运行,然后启动SolrSearchServer,启动的时候直接将服务注册到ZooKeeper集群存储中,可以通过ZooKeeper的客户端脚本来查看注册的服务数据。一切正常以后,可以启动运行客户端SearchConsumer,调用SolrSearchServer所实现的远程搜索服务。
参考链接
https://github.com/alibaba/dubbo http://alibaba.github.io/dubbo-doc-static/Home-zh.htm http://alibaba.github.io/dubbo-doc-static/User+Guide-zh.htm http://alibaba.github.io/dubbo-doc-static/Developer+Guide-zh.htm http://alibaba.github.io/dubbo-doc-static/Administrator+Guide-zh.htm http://alibaba.github.io/dubbo-doc-static/FAQ-zh.htm