Dubbo實現RPC調用使用入門

使用Dubbo進行遠程調用實現服務交互,它支持多種協議,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由於Dubbo將這些協議的實現進行了封裝了,無論是服務端(開發服務)還是客戶端(調用服務),都不需要關心協議的細節,只需要在配置中指定使用的協議即可,從而保證了服務提供方與服務消費方之間的透明。
另外,如果我們使用Dubbo的服務註冊中心組件,這樣服務提供方將服務發佈到註冊的中心,只是將服務的名稱暴露給外部,而服務消費方只需要知道註冊中心和服務提供方提供的服務名稱,就能夠透明地調用服務,後面我們會看到具體提供服務和消費服務的配置內容,使得雙方之間交互的透明化。

示例場景

我們給出一個示例的應用場景:
服務方提供一個搜索服務,對服務方來說,它基於SolrCloud構建了搜索服務,包含兩個集羣,ZooKeeper集羣和Solr集羣,然後在前端通過Nginx來進行反向代理,達到負載均衡的目的。
服務消費方就是調用服務進行查詢,給出查詢條件(滿足Solr的REST-like接口)。

應用設計

基於上面的示例場景,我們打算使用ZooKeeper集羣作爲服務註冊中心。註冊中心會暴露給服務提供方和服務消費方,所以註冊服務的時候,服務先提供方只需要提供Nginx的地址給註冊中心,但是註冊中心並不會把這個地址暴露給服務消費方,如圖所示:
provider-registry-consumer
我們先定義一下,通信雙方需要使用的接口,如下所示:

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, int rows);
06      
07     public enum ResponseType {
08         JSON,
09         XML
10     }  
11 }

基於上圖中的設計,下面我們分別詳細說明Provider和Consumer的設計及實現。

  • Provider服務設計

Provider所發佈的服務組件,包含了一個SolrCloud集羣,在SolrCloud集羣前端又加了一個反向代理層,使用Nginx來均衡負載。Provider的搜索服務系統,設計如下圖所示:
solrcloud-cluster
上圖中,實際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 = new ClassPathXmlApplicationContext(config);
58         context.start();
59         System.in.read();
60     }
61
62 }

對應的Dubbo配置文件爲search-provider.xml,內容如下所示:

01 <?xml version="1.0" encoding="UTF-8"?>
02
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:service interface="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, final int start,final int rows) {
35         Future<String> future = RpcContext.getContext().asyncCall(new Callable<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 int start, finalint rows) {
44         return search(q, type, start, rows);
45     }
46
47     private String search(final String q, final ResponseType type, final int start, final introws) {
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
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所實現的遠程搜索服務。

參考鏈接

Creative Commons License

本文基於署名-非商業性使用-相同方式共享 4.0許可協議發佈,歡迎轉載、使用、重新發布,但務必保留文章署名時延軍(包含鏈接:http://shiyanjun.cn),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章