| 使用Dubbo進(jìn)行遠(yuǎn)程調(diào)用實現(xiàn)服務(wù)交互,它支持多種協(xié)議,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由于Dubbo將這些協(xié)議的實現(xiàn)進(jìn)行了封裝了,無論是服務(wù)端(開發(fā)服務(wù))還是客戶端(調(diào)用服務(wù)),都不需要關(guān)心協(xié)議的細(xì)節(jié),只需要在配置中指定使用的協(xié)議即可,從而保證了服務(wù)提供方與服務(wù)消費方之間的透明。另外,如果我們使用Dubbo的服務(wù)注冊中心組件,這樣服務(wù)提供方將服務(wù)發(fā)布到注冊的中心,只是將服務(wù)的名稱暴露給外部,而服務(wù)消費方只需要知道注冊中心和服務(wù)提供方提供的服務(wù)名稱,就能夠透明地調(diào)用服務(wù),后面我們會看到具體提供服務(wù)和消費服務(wù)的配置內(nèi)容,使得雙方之間交互的透明化。
 示例場景 我們給出一個示例的應(yīng)用場景:服務(wù)方提供一個搜索服務(wù),對服務(wù)方來說,它基于SolrCloud構(gòu)建了搜索服務(wù),包含兩個集群,ZooKeeper集群和Solr集群,然后在前端通過Nginx來進(jìn)行反向代理,達(dá)到負(fù)載均衡的目的。
 服務(wù)消費方就是調(diào)用服務(wù)進(jìn)行查詢,給出查詢條件(滿足Solr的REST-like接口)。
 應(yīng)用設(shè)計 基于上面的示例場景,我們打算使用ZooKeeper集群作為服務(wù)注冊中心。注冊中心會暴露給服務(wù)提供方和服務(wù)消費方,所以注冊服務(wù)的時候,服務(wù)先提供方只需要提供Nginx的地址給注冊中心,但是注冊中心并不會把這個地址暴露給服務(wù)消費方,如圖所示:
  我們先定義一下,通信雙方需要使用的接口,如下所示:
 | 01 | packageorg.shirdrn.platform.dubbo.service.rpc.api; | 
| 03 | publicinterfaceSolrSearchService { | 
| 05 |     String search(String collection, String q, ResponseType type, intstart, introws); | 
| 07 |     publicenumResponseType { | 
基于上圖中的設(shè)計,下面我們分別詳細(xì)說明Provider和Consumer的設(shè)計及實現(xiàn)。 Provider所發(fā)布的服務(wù)組件,包含了一個SolrCloud集群,在SolrCloud集群前端又加了一個反向代理層,使用Nginx來均衡負(fù)載。Provider的搜索服務(wù)系統(tǒng),設(shè)計如下圖所示:
  上圖中,實際Nginx中將請求直接轉(zhuǎn)發(fā)內(nèi)部的Web Servers上,在這個過程中,使用ZooKeeper來進(jìn)行協(xié)調(diào):從多個分片(Shard)服務(wù)器上并行搜索,最后合并結(jié)果。我們看一下Nginx配置的內(nèi)容片段:
 | 04 | error_log  /var/log/nginx/error.log warn; | 
| 05 | pid        /var/run/nginx.pid; | 
| 09 |     worker_connections  1024; | 
| 14 |     include       /etc/nginx/mime.types; | 
| 15 |     default_type  application/octet-stream; | 
| 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"'; | 
| 21 |     access_log  /var/log/nginx/access.log  main; | 
| 31 |         server slave1:8888 weight=1; | 
| 32 |         server slave4:8888 weight=1; | 
| 33 |         server slave6:8888 weight=1; | 
| 40 |             root /usr/share/nginx/html/solr-cloud; | 
| 41 |             index  index.html index.htm; | 
| 43 |             include /home/hadoop/servers/nginx/conf/proxy.conf; | 
一共配置了3臺Solr服務(wù)器,因為SolrCloud集群中每一個節(jié)點都可以接收搜索請求,然后由整個集群去并行搜索。最后,我們要通過Dubbo服務(wù)框架來基于已有的系統(tǒng)來開發(fā)搜索服務(wù),并通過Dubbo的注冊中心來發(fā)布服務(wù)。首先需要實現(xiàn)服務(wù)接口,實現(xiàn)代碼如下所示:
 | 01 | packageorg.shirdrn.platform.dubbo.service.rpc.server; | 
| 03 | importjava.io.IOException; | 
| 04 | importjava.util.HashMap; | 
| 07 | importorg.apache.commons.logging.Log; | 
| 08 | importorg.apache.commons.logging.LogFactory; | 
| 09 | importorg.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService; | 
| 10 | importorg.shirdrn.platform.dubbo.service.rpc.utils.QueryPostClient; | 
| 11 | importorg.springframework.context.support.ClassPathXmlApplicationContext; | 
| 13 | publicclassSolrSearchServer implementsSolrSearchService { | 
| 15 |     privatestaticfinalLog LOG = LogFactory.getLog(SolrSearchServer.class); | 
| 16 |     privateString baseUrl; | 
| 17 |     privatefinalQueryPostClient postClient; | 
| 18 |     privatestaticfinalMap<ResponseType, FormatHandler> handlers = newHashMap<ResponseType, FormatHandler>(0); | 
| 20 |         handlers.put(ResponseType.XML, newFormatHandler() { | 
| 21 |             publicString format() { | 
| 25 |         handlers.put(ResponseType.JSON, newFormatHandler() { | 
| 26 |             publicString format() { | 
| 32 |     publicSolrSearchServer() { | 
| 34 |         postClient = QueryPostClient.newIndexingClient(null); | 
| 37 |     publicvoidsetBaseUrl(String baseUrl) { | 
| 38 |         this.baseUrl = baseUrl; | 
| 41 |     publicString search(String collection, String q, ResponseType type, | 
| 42 |             intstart, introws) { | 
| 43 |         StringBuffer url = newStringBuffer(); | 
| 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 |         returnpostClient.request(url.toString()); | 
| 51 |     interfaceFormatHandler { | 
| 55 |     publicstaticvoidmain(String[] args) throwsIOException { | 
| 56 |         String config = SolrSearchServer.class.getPackage().getName().replace('.', '/') + "/search-provider.xml"; | 
| 57 |         ClassPathXmlApplicationContext context = newClassPathXmlApplicationContext(config); | 
對應(yīng)的Dubbo配置文件為search-provider.xml,內(nèi)容如下所示: | 01 | <?xmlversion="1.0"encoding="UTF-8"?> | 
| 08 |     <dubbo:applicationname="search-provider"/> | 
| 10 |     <dubbo:protocolname="dubbo"port="20880"/> | 
| 11 |     <beanid="searchService"class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer"> | 
| 14 |     <dubbo:serviceinterface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService"ref="searchService"/> | 
上面,Dubbo服務(wù)注冊中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo協(xié)議。配置服務(wù)接口的時候,可以按照Spring的Bean的配置方式來配置,注入需要的內(nèi)容,我們這里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/。 Consumer調(diào)用服務(wù)設(shè)計
 這個就比較簡單了,拷貝服務(wù)接口,同時要配置一下Dubbo的配置文件,寫個簡單的客戶端調(diào)用就可以實現(xiàn)??蛻舳藢崿F(xiàn)的Java代碼如下所示: | 01 | packageorg.shirdrn.platform.dubbo.service.rpc.client; | 
| 03 | importjava.util.concurrent.Callable; | 
| 04 | importjava.util.concurrent.Future; | 
| 06 | importorg.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService; | 
| 07 | importorg.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService.ResponseType; | 
| 08 | importorg.springframework.beans.BeansException; | 
| 09 | importorg.springframework.context.support.AbstractXmlApplicationContext; | 
| 10 | importorg.springframework.context.support.ClassPathXmlApplicationContext; | 
| 12 | importcom.alibaba.dubbo.rpc.RpcContext; | 
| 14 | publicclassSearchConsumer { | 
| 16 |     privatefinalString collection; | 
| 17 |     privateAbstractXmlApplicationContext context; | 
| 18 |     privateSolrSearchService searchService; | 
| 20 |     publicSearchConsumer(String collection, Callable<AbstractXmlApplicationContext> call) { | 
| 22 |         this.collection = collection; | 
| 24 |             context = call.call(); | 
| 26 |             searchService = (SolrSearchService) context.getBean("searchService"); | 
| 27 |         } catch(BeansException e) { | 
| 29 |         } catch(Exception e) { | 
| 34 |     publicFuture<String> asyncCall(finalString q, finalResponseType type, finalintstart, finalintrows) { | 
| 35 |         Future<String> future = RpcContext.getContext().asyncCall(newCallable<String>() { | 
| 36 |             publicString call() throwsException { | 
| 37 |                 returnsearch(q, type, start, rows); | 
| 43 |     publicString syncCall(finalString q, finalResponseType type, finalintstart, finalintrows) { | 
| 44 |         returnsearch(q, type, start, rows); | 
| 47 |     privateString search(finalString q, finalResponseType type, finalintstart, finalintrows) { | 
| 48 |         returnsearchService.search(collection, q, type, start, rows); | 
| 51 |     publicstaticvoidmain(String[] args) throwsException { | 
| 52 |         finalString collection = "tinycollection"; | 
| 53 |         finalString beanXML = "search-consumer.xml"; | 
| 54 |         finalString config = SearchConsumer.class.getPackage().getName().replace('.', '/') + "/"+ beanXML; | 
| 55 |         SearchConsumer consumer = newSearchConsumer(collection, newCallable<AbstractXmlApplicationContext>() { | 
| 56 |             publicAbstractXmlApplicationContext call() throwsException { | 
| 57 |                 finalAbstractXmlApplicationContext context = newClassPathXmlApplicationContext(config); | 
| 62 |         String q = "q=上海&fl=*&fq=building_type:1"; | 
| 65 |         ResponseType type  = ResponseType.XML; | 
| 66 |         for(intk = 0; k < 10; k++) { | 
| 67 |             for(inti = 0; i < 10; i++) { | 
| 70 |                     type = ResponseType.XML; | 
| 72 |                     type = ResponseType.JSON; | 
| 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()); | 
查詢的時候,需要提供查詢字符串,符合Solr語法,例如“q=上海&fl=*&fq=building_type:1”。配置文件,我們使用search-consumer.xml,內(nèi)容如下所示: | 01 | <?xmlversion="1.0"encoding="UTF-8"?> | 
| 08 |     <dubbo:applicationname="search-consumer"/> | 
| 10 |     <dubbo:referenceid="searchService"interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService"/> | 
運行說明 首先保證服務(wù)注冊中心的ZooKeeper集群正常運行,然后啟動SolrSearchServer,啟動的時候直接將服務(wù)注冊到ZooKeeper集群存儲中,可以通過ZooKeeper的客戶端腳本來查看注冊的服務(wù)數(shù)據(jù)。一切正常以后,可以啟動運行客戶端SearchConsumer,調(diào)用SolrSearchServer所實現(xiàn)的遠(yuǎn)程搜索服務(wù)。 參考鏈接 |