使用Dubbo進行遠程調用實現服務交互,它支持多種協議,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。因爲Dubbo將這些協議的實現進行了封裝了,不管是服務端(開發服務)仍是客戶端(調用服務),都不須要關心協議的細節,只須要在配置中指定使用的協議便可,從而保證了服務提供方與服務消費方之間的透明。
另外,若是咱們使用Dubbo的服務註冊中心組件,這樣服務提供方將服務發佈到註冊的中心,只是將服務的名稱暴露給外部,而服務消費方只須要知道註冊中心和服務提供方提供的服務名稱,就可以透明地調用服務,後面咱們會看到具體提供服務和消費服務的配置內容,使得雙方之間交互的透明化。html
示例場景前端
咱們給出一個示例的應用場景:
服務方提供一個搜索服務,對服務方來講,它基於SolrCloud構建了搜索服務,包含兩個集羣,ZooKeeper集羣和Solr集羣,而後在前端經過Nginx來進行反向代理,達到負載均衡的目的。
服務消費方就是調用服務進行查詢,給出查詢條件(知足Solr的REST-like接口)。java
應用設計nginx
基於上面的示例場景,咱們打算使用ZooKeeper集羣做爲服務註冊中心。註冊中心會暴露給服務提供方和服務消費方,因此註冊服務的時候,服務先提供方只須要提供Nginx的地址給註冊中心,可是註冊中心並不會把這個地址暴露給服務消費方,如圖所示:
咱們先定義一下,通訊雙方須要使用的接口,以下所示:git
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的設計及實現。github
Provider所發佈的服務組件,包含了一個SolrCloud集羣,在SolrCloud集羣前端又加了一個反向代理層,使用Nginx來均衡負載。Provider的搜索服務系統,設計以下圖所示:
上圖中,實際Nginx中將請求直接轉發內部的Web Servers上,在這個過程當中,使用ZooKeeper來進行協調:從多個分片(Shard)服務器上並行搜索,最後合併結果。咱們看一下Nginx配置的內容片斷:spring
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的註冊中心來發布服務。
首先須要實現服務接口,實現代碼以下所示:apache
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 = new HashMap<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,內容以下所示:json
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 |
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/。api
這個就比較簡單了,拷貝服務接口,同時要配置一下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, final int rows) { |
44 |
return search(q, type, start, rows); |
45 |
} |
46 |
|
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); |
49 |
} |
50 |
|
51 |
public static void main(String[] args) throws Exception { |
52 |
final String collection = "tinycollection" ; |
53 |
final String beanXML = "search-consumer.xml" ; |