使用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; |
03 |
public interface SolrSearchService
{ |
05 |
String
search(String collection, String q, ResponseType type, int start, int rows); |
07 |
public enum ResponseType
{ |
基於上圖中的設計,下面我們分別詳細說明Provider和Consumer的設計及實現。
Provider所發佈的服務組件,包含了一個SolrCloud集羣,在SolrCloud集羣前端又加了一個反向代理層,使用Nginx來均衡負載。Provider的搜索服務系統,設計如下圖所示:
上圖中,實際Nginx中將請求直接轉發內部的Web Servers上,在這個過程中,使用ZooKeeper來進行協調:從多個分片(Shard)服務器上並行搜索,最後合併結果。我們看一下Nginx配置的內容片段:
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服務器,因爲SolrCloud集羣中每一個節點都可以接收搜索請求,然後由整個集羣去並行搜索。最後,我們要通過Dubbo服務框架來基於已有的系統來開發搜索服務,並通過Dubbo的註冊中心來發布服務。
首先需要實現服務接口,實現代碼如下所示:
01 |
package org.shirdrn.platform.dubbo.service.rpc.server; |
03 |
import java.io.IOException; |
04 |
import java.util.HashMap; |
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; |
13 |
public class SolrSearchServer implements SolrSearchService
{ |
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 = new HashMap<ResponseType,
FormatHandler>( 0 ); |
20 |
handlers.put(ResponseType.XML, new FormatHandler()
{ |
21 |
public String
format() { |
25 |
handlers.put(ResponseType.JSON, new FormatHandler()
{ |
26 |
public String
format() { |
32 |
public SolrSearchServer()
{ |
34 |
postClient
= QueryPostClient.newIndexingClient( null ); |
37 |
public void setBaseUrl(String
baseUrl) { |
38 |
this .baseUrl
= baseUrl; |
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()); |
51 |
interface FormatHandler
{ |
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); |
對應的Dubbo配置文件爲search-provider.xml,內容如下所示:
01 |
<? xml version = "1.0" encoding = "UTF-8" ?> |
08 |
< dubbo:application name = "search-provider" /> |
10 |
< dubbo:protocol name = "dubbo" port = "20880" /> |
11 |
< bean id = "searchService" class = "org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer" > |
14 |
< dubbo:service interface = "org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" ref = "searchService" /> |
上面,Dubbo服務註冊中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo協議。配置服務接口的時候,可以按照Spring的Bean的配置方式來配置,注入需要的內容,我們這裏指定了搜索集羣的Nginx反向代理地址http://nginx-lbserver/solr-cloud/。
這個就比較簡單了,拷貝服務接口,同時要配置一下Dubbo的配置文件,寫個簡單的客戶端調用就可以實現。客戶端實現的Java代碼如下所示:
01 |
package org.shirdrn.platform.dubbo.service.rpc.client; |
03 |
import java.util.concurrent.Callable; |
04 |
import java.util.concurrent.Future; |
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; |
12 |
import com.alibaba.dubbo.rpc.RpcContext; |
14 |
public class SearchConsumer
{ |
16 |
private final String
collection; |
17 |
private AbstractXmlApplicationContext
context; |
18 |
private SolrSearchService
searchService; |
20 |
public SearchConsumer(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 |
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); |
43 |
public String
syncCall( final String
q, final ResponseType
type, final int start, final int rows)
{ |
44 |
return search(q,
type, start, rows); |
47 |
private String
search( final String
q, final ResponseType
type, final int start, final int rows)
{ |
48 |
return searchService.search(collection,
q, type, start, rows); |
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, new Callable<AbstractXmlApplicationContext>()
{ |
56 |
public AbstractXmlApplicationContext
call() throws Exception
{ |
57 |
final AbstractXmlApplicationContext
context = new ClassPathXmlApplicationContext(config); |
62 |
String
q = "q=上海&fl=*&fq=building_type:1" ; |
65 |
ResponseType
type = ResponseType.XML; |
66 |
for ( int k
= 0 ;
k < 10 ;
k++) { |
67 |
for ( int i
= 0 ;
i < 10 ;
i++) { |
70 |
type
= ResponseType.XML; |
72 |
type
= ResponseType.JSON; |
76 |
Future<String>
future = consumer.asyncCall(q, type, start, rows); |
查詢的時候,需要提供查詢字符串,符合Solr語法,例如“q=上海&fl=*&fq=building_type:1”。配置文件,我們使用search-consumer.xml,內容如下所示:
01 |
<? xml version = "1.0" encoding = "UTF-8" ?> |
08 |
< dubbo:application name = "search-consumer" /> |
10 |
< dubbo:reference id = "searchService" interface = "org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" /> |
運行說明
首先保證服務註冊中心的ZooKeeper集羣正常運行,然後啓動SolrSearchServer,啓動的時候直接將服務註冊到ZooKeeper集羣存儲中,可以通過ZooKeeper的客戶端腳本來查看註冊的服務數據。一切正常以後,可以啓動運行客戶端SearchConsumer,調用SolrSearchServer所實現的遠程搜索服務。
參考鏈接
本文基於署名-非商業性使用-相同方式共享 4.0許可協議發佈,歡迎轉載、使用、重新發布,但務必保留文章署名時延軍(包含鏈接:http://shiyanjun.cn),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。