分布式系統(distributed system)是建立在網絡之上的軟件系統。正是因為軟件的特性,所以分布式系統具有高度的內聚性和透明性。因此,網絡和分布式系統之間的區別更多的在於高層軟件(特別是操作系統),而不是硬件。
分布式消息隊列(MQ)
為什么使用 MQ?
異步處理 - 相比於傳統的串行、並行方式,提高了系統吞吐量。
應用解耦 - 系統間通過消息通信,不用關心其他系統的處理。
流量削鋒 - 可以通過消息隊列長度控制請求量;可以緩解短時間內的高並發請求。
日志處理 - 解決大量日志傳輸。
消息通訊 - 消息隊列一般都內置了高效的通信機制,因此也可以用在純的消息通訊。比如實現點對點消息隊列,或者聊天室等。
如何保證 MQ 的高可用?
將所有 Broker 和待分配的 Partition 排序
將第 i 個 Partition 分配到第(i mod n)個 Broker 上
將第 i 個 Partition 的第 j 個 Replica 分配到第((i + j) mode n)個 Broker 上
MQ 有哪些常見問題?如何解決這些問題?
MQ 的常見問題有:
消息的順序問題
消息的重復問題
消息的順序問題
消息有序指的是可以按照消息的發送順序來消費。
假如生產者產生了 2 條消息:M1、M2,假定 M1 發送到 S1,M2 發送到 S2,如果要保證 M1 先於 M2 被消費,怎么做?
解決方案:
(1)保證生產者 - MQServer - 消費者是一對一對一的關系
缺陷:
並行度就會成為消息系統的瓶頸(吞吐量不夠)
更多的異常處理,比如:只要消費端出現問題,就會導致整個處理流程阻塞,我們不得不花費更多的精力來解決阻塞的問題。 (2)通過合理的設計或者將問題分解來規避。
不關注亂序的應用實際大量存在
隊列無序並不意味着消息無序 所以從業務層面來保證消息的順序而不僅僅是依賴於消息系統,是一種更合理的方式。
消息的重復問題
造成消息重復的根本原因是:網絡不可達。
所以解決這個問題的辦法就是繞過這個問題。那么問題就變成了:如果消費端收到兩條一樣的消息,應該怎樣處理?
消費端處理消息的業務邏輯保持冪等性。只要保持冪等性,不管來多少條重復消息,最后處理的結果都一樣。保證每條消息都有唯一編號且保證消息處理成功與去重表的日志同時出現。利用一張日志表來記錄已經處理成功的消息的 ID,如果新到的消息 ID 已經在日志表中,那么就不再處理這條消息。
Kafka, ActiveMQ, RabbitMQ, RocketMQ 各有什么優缺點?
分布式服務(RPC)
Dubbo 的實現過程?
節點角色:
調用關系:
務容器負責啟動,加載,運行服務提供者
服務提供者在啟動時,向注冊中心注冊自己提供的服務。
服務消費者在啟動時,向注冊中心訂閱自己所需的服務。
注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基於長連接推送變更數據給消費者。
服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一台提供者進行調用,如果調用失敗,再選另一台調用。
服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鍾發送一次統計數據到監控中心。
Dubbo 負載均衡策略有哪些?
Random
隨機,按權重設置隨機概率。
在一個截面上碰撞的概率高,但調用量越大分布越均勻,而且按概率使用權重后也比較均勻,有利於動態調整提供者權重。
RoundRobin
輪循,按公約后的權重設置輪循比率。
存在慢的提供者累積請求的問題,比如:第二台機器很慢,但沒掛,當請求調到第二台時就卡在那,久而久之,所有請求都卡在調到第二台上。
LeastActive
最少活躍調用數,相同活躍數的隨機,活躍數指調用前后計數差。
使慢的提供者收到更少請求,因為越慢的提供者的調用前后計數差會越大。
ConsistentHash
一致性 Hash,相同參數的請求總是發到同一提供者。
當某一台提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
缺省只對第一個參數 Hash,如果要修改,請配置 <dubbo:parameter key="hash.arguments" value="0,1" />
缺省用 160 份虛擬節點,如果要修改,請配置 <dubbo:parameter key="hash.nodes" value="320" />
Dubbo 集群容錯策略 ?
Failover- 失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。可通過 retries="2" 來設置重試次數(不含第一次)。
Failfast- 快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
Failsafe- 失敗安全,出現異常時,直接忽略。通常用於寫入審計日志等操作。
Failback- 失敗自動恢復,后台記錄失敗請求,定時重發。通常用於消息通知操作。
Forking- 並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。
Broadcast- 播調用所有提供者,逐個調用,任意一台報錯則報錯。通常用於通知所有提供者更新緩存或日志等本地資源信息。
動態代理策略?
Dubbo 作為 RPC 框架,首先要完成的就是跨系統,跨網絡的服務調用。消費方與提供方遵循統一的接口定義,消費方調用接口時,Dubbo 將其轉換成統一格式的數據結構,通過網絡傳輸,提供方根據規則找到接口實現,通過反射完成調用。也就是說,消費方獲取的是對遠程服務的一個代理(Proxy),而提供方因為要支持不同的接口實現,需要一個包裝層(Wrapper)。調用的過程大概是這樣:
消費方的 Proxy 和提供方的 Wrapper 得以讓 Dubbo 構建出復雜、統一的體系。而這種動態代理與包裝也是通過基於 SPI 的插件方式實現的,它的接口就是ProxyFactory。
ProxyFactory 有兩種實現方式,一種是基於 JDK 的代理實現,一種是基於 javassist 的實現。ProxyFactory 接口上定義了@SPI("javassist"),默認為 javassist 的實現。
Dubbo 支持哪些序列化協議?Hessian?Hessian 的數據結構?
dubbo 序列化,阿里尚不成熟的 java 序列化實現。
hessian2 序列化:hessian 是一種跨語言的高效二進制的序列化方式,但這里實際不是原生的 hessian2 序列化,而是阿里修改過的 hessian lite,它是 dubbo RPC 默認啟用的序列化方式。
json 序列化:目前有兩種實現,一種是采用的阿里的 fastjson 庫,另一種是采用 dubbo 中自已實現的簡單 json 庫,一般情況下,json 這種文本序列化性能不如二進制序列化。
java 序列化:主要是采用 JDK 自帶的 java 序列化實現,性能很不理想。
Kryo 和 FST:Kryo 和 FST 的性能依然普遍優於 hessian 和 dubbo 序列化。
Hessian 序列化與 Java 默認的序列化區別?
Hessian 是一個輕量級的 remoting on http 工具,采用的是 Binary RPC 協議,所以它很適合於發送二進制數據,同時又具有防火牆穿透能力。
Hessian 支持跨語言串行
比 java 序列化具有更好的性能和易用性
支持的語言比較多Protoco Buffer 是什么?
Protocol Buffer 是 Google 出品的一種輕量 & 高效的結構化數據存儲格式,性能比 Json、XML 真的強!太!多!
Protocol Buffer 的序列化 & 反序列化簡單 & 速度快的原因是:
編碼 / 解碼 方式簡單(只需要簡單的數學運算 = 位移等等)
采用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成
Protocol Buffer 的數據壓縮效果好(即序列化后的數據量體積小)的原因是:
采用了獨特的編碼方式,如 Varint、Zigzag 編碼方式等等
采用 T - L - V 的數據存儲方式:減少了分隔符的使用 & 數據存儲得緊湊
注冊中心掛了可以繼續通信嗎?
可以。Dubbo 消費者在應用啟動時會從注冊中心拉取已注冊的生產者的地址接口,並緩存在本地。每次調用時,按照本地存儲的地址進行調用。
ZooKeeper 原理是什么?ZooKeeper 有什么用?
ZooKeeper 是一個分布式應用協調系統,已經用到了許多分布式項目中,用來完成統一命名服務、狀態同步服務、集群管理、分布式應用配置項的管理等工作。
每個 Server 在內存中存儲了一份數據;
Zookeeper 啟動時,將從實例中選舉一個 leader(Paxos 協議);
Leader 負責處理數據更新等操作(Zab 協議);
一個更新操作成功,當且僅當大多數 Server 在內存中成功修改數據。
Netty 有什么用?NIO/BIO/AIO 有什么用?有什么區別?
Netty 是一個“網絡通訊框架”。
Netty 進行事件處理的流程。Channel是連接的通道,是 ChannelEvent 的產生者,而ChannelPipeline可以理解為 ChannelHandler 的集合。
IO 的方式通常分為幾種:
同步阻塞的 BIO
同步非阻塞的 NIO
異步非阻塞的 AIO 在使用同步 I/O 的網絡應用中,如果要同時處理多個客戶端請求,或是在客戶端要同時和多個服務器進行通訊,就必須使用多線程來處理。
NIO 基於 Reactor,當 socket 有流可讀或可寫入 socket 時,操作系統會相應的通知引用程序進行處理,應用再將流讀取到緩沖區或寫入操作系統。也就是說,這個時候,已經不是一個連接就要對應一個處理線程了,而是有效的請求,對應一個線程,當連接沒有數據時,是沒有工作線程來處理的。
與 NIO 不同,當進行讀寫操作時,只須直接調用 API 的 read 或 write 方法即可。這兩種方法均為異步的,對於讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入 read 方法的緩沖區,並通知應用程序;對於寫操作而言,當操作系統將 write 方法傳遞的流寫入完畢時,操作系統主動通知應用程序。即可以理解為,read/write 方法都是異步的,完成后會主動調用回調函數。
為什么要進行系統拆分?拆分不用 Dubbo 可以嗎?
系統拆分從資源角度分為:應用拆分和數據庫拆分。
從采用的先后順序可分為:水平擴展、垂直拆分、業務拆分、水平拆分。
是否使用服務依據實際業務場景來決定。
當垂直應用越來越多,應用之間交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務復用及整合的分布式服務框架(RPC)是關鍵。
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心基於訪問壓力實時管理集群容量,提高集群利用率。此時,用於提高機器利用率的資源調度和治理中心(SOA)是關鍵。
Dubbo 和 Thrift 有什么區別?
Thrift 是跨語言的 RPC 框架。
Dubbo 支持服務治理,而 Thrift 不支持。
擴展閱讀
JavaEE架構之傳統三層架構,集群架構,分布式架構,微服務架構
作者:java閘瓦
來源:https://juejin.im/post/5ccd8d606fb9a032136fda86