高性能的分布式服務框架 Dubbo


我思故我在,提問啟迪思考!
1. 什么是Dubbo?
  官網:http://dubbo.io/, DUBBO是一個分布式服務框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及作為SOA服務治理的方案,它是阿里巴巴SOA服務化治理方案的核心框架,每天為2,000+個服務提供3,000,000,000+次訪問量支持,並被廣泛應用於阿里巴巴集團的各成員站點。< 目前的dubbo社區已停止維護和更新>, 它的核心功能包括:
  • #remoting:遠程通訊基礎,提供對多種NIO框架抽象封裝,包括“同步轉異步”和“請求-響應”模式的信息交換方式。
  • #cluster: 服務框架核心,提供基於接口方法的遠程過程調用,包括多協議支持,並提供軟負載均衡和容錯機制的集群支持。
  • #registry: 服務注冊中心,使服務消費方能動態的查找服務提供方,使地址透明,使服務提供方可以平滑增加或減少機器。

2. dubbo是如何使用來提供服務的?
  web應用架構的發展,從單一的ORM架構(一個應用,將所有功能部署在一起,減少部署的節點和成本)到MVC架構(拆分出互相不相干的幾個應用),再從MVC框架到RPC框架(將業務中的核心業務抽取出來,做為獨立的服務,漸漸形成服務中心),從RPC架構到SOA架構(增加一個調度中心基於訪問壓力實時管理集群容量,提高集群利用率)。
  Dubbo有自己的服務中心,寫好的服務可以注冊到服務中心,客戶端從服務中心尋找服務,然后再到相應的服務提供者機器獲取服務。通過服務中心可以實現集群、負載均衡、高可用(容錯) 等重要功能。服務中心一般使用zookeeper實現,也有redis和其他一些方式。以使用zookeeper作為服務中心為例,服務提供者啟動后會在zookeeper的/dubbo節點下創建提供的服務節點,包含服務提供者ip、port等信息。服務提供者關閉時會從zookeeper中移除對應的服務。服務使用者會從注冊中心zookeeper中尋找服務,同一個服務可能會有多個提供者,Dubbo會幫我們找到合適的服務提供者。
3. dubbo是如何做集群容錯的?

  當服務調用失敗時(比如響應超時),根據我們的業務不同,可以使用不同的策略來應對這種失敗。比如我們調用的服務是一個查詢服務,不會修改數據庫,那么可以給該服務設置容錯方式為failover <失敗重試方式>,當調用失敗時,自動切換到其他服務提供者去調用,當失敗次數超過指定重試次數,那么就拋出錯誤。如果服務是更新數據的服務,那就不能使用失敗重試的方式了, 因為這樣可能產生數據重復修改的問題,比如調用提供者A的插入用戶方法,但是該方法業務邏輯復雜,執行過程很慢,導致響應超時,那么此時如果再去調用另外一個服務提供者的插入用戶方法,將會又重復插入同一個用戶。對於這種類型的服務,可以使用容錯方式為failfast,如果第一次調用失敗,立即報錯,不需要重試。

    另外還有下面幾種容錯類型,failsafe 出現錯誤,直接忽略,不重試也不報錯; failback 失敗后不報錯,會將該失敗請求,定時重發,適合消息通知類型的服務。forking,並行調用多個服務器,只要在某一台提供者上面成功,那么方法返回,適合實時性要求較高的查詢服務,但是要犧牲性能。因為每台服務器會做同一個操作;broadcast 廣播調用所有服務提供者,逐個調用,任意一台報錯則報錯。適合與更新每台提供者上面的緩存,這種類型的服務。

  案例:我們在點我吧的APP消息推送就是采用的failback方式,點我吧的短信通知則采用的是failover方式。

4. dubbo是如何做負載均衡的?{幾種負載均衡的策略}
   當同一個服務有多個提供者在提供服務時, 客戶端如何正確的選擇提供者實現負載均衡dubbo也給我們提供了幾種方案:random,隨機選提供者,並可以給提供者設置權重;roundrobin,輪詢選擇提供者,leastactive,最少活躍調用數,相同活躍數的隨機,活躍數指調用前后計數差。使慢的提供者收到更少請求,因為越慢的提供者的調用前后計數差會越大。consistenthash 一致性hash,相同參數的請求發到同一台機器上。
  關於一致性hash的幾點學習,我們知道普通的哈希算法在散列的時候,可能產生間隔,並不能使數據均與分布開來。一致性哈希算法正是基於哈希算法的缺點所引入的解決方案。一致性 hash算法提出了在動態變化的Cache環境中,判定哈希算法好壞的四個定義:1、平衡性(Balance):平衡性是指哈希的結果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用。很多哈希算法都能夠滿足這一條件。2、單調性(Monotonicity):單調性是指如果已經有一些內容通過哈希分派到了相應的緩沖中,又有新的緩沖加入到系統中。哈希的結果應能夠保證原有已分配的內容可以被映射到原有的或者新的緩沖中去,而不會被映射到舊的緩沖集合中的其他緩沖區。3、分散性(Spread):在分布式環境中,終端有可能看不到所有的緩沖,而是只能看到其中的一部分。當終端希望通過哈希過程將內容映射到緩沖上時,由於不同終端所見的緩沖范圍有可能不同,從而導致哈希的結果不一致,最終的結果是相同的內容被不同的終端映射到不同的緩沖區中。這種情況顯然是應該避免的,因為它導致相同內容被存儲到不同緩沖中去,降低了系統存儲的效率。分散性的定義就是上述情況發生的嚴重程度。好的哈希算法應能夠盡量避免不一致的情況發生,也就是盡量降低分散性。4、負載(Load):負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容映射到不同的緩沖區中,那么對於一個特定的緩沖區而言,也可能被不同的用戶映射為不同 的內容。與分散性一樣,這種情況也是應當避免的,因此好的哈希算法應能夠盡量降低緩沖的負荷。在分布式集群 中,對機器的添加刪除,或者機器故障后自動脫離集群這些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有機器添加或者刪除后,很多原有的數據就無法找到了,這樣嚴重的違反了單調性原則。接下來主要講解一下一致性哈希算法是如何設計的:

 

如上圖所示,是環形的hash空間,按照常用的hash算法來將對應的key哈希到一個具有2^32次方個桶的空間中,即0~(2^32)-1的數字空間中。現在我們可以將這些數字頭尾相連,想象成一個閉合的環形。我們把數據通過hash算法處理后映射到環上,現在我們將object1、object2、object3、object4四個對象通過特定的Hash函數計算出對應的key值,然后散列到Hash環上。Hash(object1) = key1;Hash(object2) = key2;Hash(object3) = key3;Hash(object4) = key4;

 
普通hash求余算法最為不妥的地方就是在有機器的添加或者刪除之后會照成大量的對象存儲位置失效,這樣就大大的不滿足單調性了。下面來分析一下一致性哈希算法是如何處理的?它通過 按順時針遷移的規則,首先保證其它對象還保持原有的存儲位置。當通過節點的添加和刪除的時候,一致性哈希算法在保持了單調性的同時,還是數據的遷移達到了最小,這樣的算法對分布式集群來說是非常合適的,避免了大量數據遷移,減小了服務器的的壓力。
5. dubbo的多協議方式?
   dubbo提供了多種協議給用戶選擇,如dubbo、hessian、rmi 。並可為每個服務指定不同的傳輸協議,粒度可以細化到方法,不同服務在性能上適用不同協議進行傳輸,比如大數據用短連接協議,小數據大並發用長連接協議。
  使用Dubbo進行遠程調用實現服務交互,它支持多種協議,如Hessian、HTTP、RMI、Memcached、Redis、Thrift等等。由於Dubbo將這些協議的實現進行了封裝了,無論是服務端(開發服務)還是客戶端(調用服務),都不需要關心協議的細節,只需要在配置中指定使用的協議即可,從而保證了服務提供方與服務消費方之間的透明。另外,如果我們使用Dubbo的服務注冊中心組件,這樣服務提供方將服務發布到注冊的中心,只是將服務的名稱暴露給外部,而服務消費方只需要知道注冊中心和服務提供方提供的服務名稱,就能夠透明地調用服務,后面我們會看到具體提供服務和消費服務的配置內容,使得雙方之間交互的透明化。
provider-registry-consumer
 
  如圖所示的應用場景,服務方提供一個搜索服務,對服務方來說,它基於SolrCloud構建了搜索服務,包含兩個集群,ZooKeeper集群和Solr集群,然后在前端通過Nginx來進行反向代理,達到負載均衡的目的。服務消費方就是調用服務進行查詢,給出查詢條件。
  基於上面的示例場景,我們打算使用ZooKeeper集群作為服務注冊中心。注冊中心會暴露給服務提供方和服務消費方,所以注冊服務的時候,服務先提供方只需要提供Nginx的地址給注冊中心,但是注冊中心並不會把這個地址暴露給服務消費方。
1 public interface SolrSearchService {
2     String search(String collection, String q, ResponseType type, int start, int rows);
3     public enum ResponseType {JSON, XML}  
4 }
  其中,provider的服務設計:provider所發布的服務組件,包含了一個SolrCloud集群,在SolrCloud集群前端又加了一個反向代理層,使用Nginx來均衡負載。Provider的搜索服務系統,設計如下圖所示:
  solrcloud-cluster
 
     如圖所示,實際nginx中將請求直接轉發到內部的Web Servers上,在這個過程中,使用ZooKeeper來進行協調:從多個分片(Shard)服務器上並行搜索,最后合並結果。一共配置了3台Solr服務器,因為SolrCloud集群中每一個節點都可以接收搜索請求,然后由整個集群去並行搜索。最后,我們要通過Dubbo服務框架來基於已有的系統來開發搜索服務,並通過Dubbo的注冊中心來發布服務。首選需要實現服務接口,實現代碼如下所示:
 1 package org.shirdrn.platform.dubbo.service.rpc.server;
 2 
 3 import java.io.IOException;
 4 import java.util.HashMap;
 5 import java.util.Map;
 6 
 7 import org.apache.commons.logging.Log;
 8 import org.apache.commons.logging.LogFactory;
 9 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    public SolrSearchServer() {
32         super();
33         postClient = QueryPostClient.newIndexingClient(null);
34     }
35     public void setBaseUrl(String baseUrl) {
36         this.baseUrl = baseUrl;
37     }
38     public String search(String collection, String q, ResponseType type,
39             int start, int rows) {
40         StringBuffer url = new StringBuffer();    
       url.append(baseUrl).append(collection).append("/select?").append(q);
       url.append("&start=").append(start).append("&rows=").append(rows); 41 url.append(handlers.get(type).format()); 42 LOG.info("[REQ] " + url.toString()); 43 return postClient.request(url.toString()); 44 } 45 interface FormatHandler { 46 String format(); 47 } 48 public static void main(String[] args) throws IOException { 49 String config = SolrSearchServer.class.getPackage().getName().replace('.', '/') + "/search-provider.xml"; 50 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config); 51 context.start(); 52 System.in.read(); 53 } 54 }
對應的Dubbo配置文件為search-provider.xml,內容如下所示:
 1 <?xml version="1.0" encoding="UTF-8"?>
 2     <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 4     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
 5     <dubbo:application name="search-provider" />
 6     <dubbo:registry address="zookeeper://slave1:2188?backup=slave3:2188,slave4:2188" />
 7     <dubbo:protocol name="dubbo" port="20880" />
 8     <bean id="searchService" class="org.shirdrn.platform.dubbo.service.rpc.server.SolrSearchServer">
 9         <property name="baseUrl" value="http://nginx-lbserver/solr-cloud/" />
10     </bean>
11     <dubbo:service interface="org.shirdrn.platform.dubbo.service.rpc.api.SolrSearchService" ref="searchService" />
12 </beans>

上面,Dubbo服務注冊中心指定ZooKeeper的地址:zookeeper://slave1:2188?backup=slave3:2188,slave4:2188,使用Dubbo協議。配置服務接口的時候,可以按照Spring的Bean的配置方式來配置,注入需要的內容,我們這里指定了搜索集群的Nginx反向代理地址http://nginx-lbserver/solr-cloud/。

6. dubbo的使用特點? 
  dubbo采用全Spring配置方式,透明化接入應用,對應用沒有任何API侵入,只需用Spring加載Dubbo的配置即可,Dubbo基於Spring的Schema擴展進行加載。
7. 總結
  dubbo是高性能的分布式服務框架,它提供了服務治理的解決方案。它適用於(公司內部)跨部門跨項目遠程調用的場景,
    dubbo穩定而高效,不僅支持大規模服務集群的動態擴展,平滑升級,而且支持load balance,fail over,支持服務質量分級管理,dubbo簡化了服務配置,減少DB連接數。
  dubbo可以做什么,dubbo是基於NIO和長連接的遠程調用實現,dubbo提供的服務暴露和導入,對應用方的代碼無侵入性,dubbo提供多協議支持,如RMI、Hessian、tbr等,dubbo提供了fail over機制,當某個provider出現異常后,會自動切換provider,dubbo提供了load balance機制,采用了權重+隨機或輪詢算法,服務列表動態更新,如步驟三。dubbo解決了服務治理問題,包括provider和consumer之間的匹配問題,dubbo提供了服務的注冊和訂閱功能,dubbo的靈活的路由規則設置,且支持服務列表動態推送功能,且提供了基於web的管理功能。
/*
*   使用Dubbo服務接口
*/

// 首先,在API包中定義服務接口,同時部署於Provider端和Consumer端
public interface HelloService {
    public String sayHello();
}

// 其次,在服務端的Provider實現代碼
public class HelloServiceImpl implements HelloService {
    public String sayHello() {
        return "Welcome to dubbo!";
    }
}

// 配置:提供者暴露服務;消費者消費服務

/*provider端服務實現類*/
<bean id="helloService" class="com.alibaba.hello.impl.HelloServiceImpl" />

/*provider端暴露服務*/
<dubbo:service interface="com.alibaba.hello.HelloService" version="1.0.0"  ref="helloService"/>

/*consumer端引入服務*/
<dubbo:reference id="helloService" interface="com.alibaba.hello.HelloService" version="1.0.0" />

/*consumer端使用服務*/
<bean id="xxxAction" class="com.alibaba.xxx.XxxAction" ><property name="helloService" ref="helloService" /></bean>
我們對dubbo的性能做出測試,如下:
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM