螞蟻金服近期開源了研發多年的SOFA一籃子框架,其中就有一個很是核心的RPC框架,它叫SOFA-BOLT。小編今天花了近一天的時間仔細閱讀研究它的源碼,閱讀過程當中遇到了很多問題,螞蟻金服的相關技術人員都很是耐心的及時解答了個人疑難。這裏將我從中學到的知識點一併分享給你們。程序員
SOFA-BOLT基於開源的Netty框架,同時提供了服務器和客戶端的實現。它的源碼很是值得一讀,結構簡單,考慮周全,毫不是一個普通的玩具。它沒有濫用設計模式,源碼閱讀起來比較直接,沒有太多繞來繞去的複雜結構。算法
一個節點既能夠同時既是RPC服務器又是客戶端,做爲客戶端該節點須要其它節點提供服務,做爲服務器它能夠爲其它節點提供服務。不過上面這張圖並非合理的結構,由於兩個服務相互耦合了,我須要你,你也須要我,就成了雞蛋問題。比較合理的結構通常以下圖所示,它們之間不構成環。設計模式
通信協議是客戶端和服務器之間交流的語言,SOFA定義了本身的一套通信協議,它的編碼解碼分爲二層,第一層是消息體對象的二進制序列化,這部分默認由開源的Hession協議庫序列化完成,第二層是負責給序列化的消息體增長一系列包裝字段,造成一個完整的消息。包括請求ID、消息體的長度、協議版本號和CRC32校驗位等等安全
若是但願進一步優化網絡性能,SOFA還提供了Snappy壓縮協議,能夠在現有的兩層協議基礎上增長第三層,能顯著下降網絡傳輸負擔。壓縮是時間換空間,提高網絡性能的同時,它也會加劇CPU計算,因此在使用時須要適當進行權衡。bash
客戶端和服務器之間通常須要創建多個鏈接,可是也不能每一個請求都創建一個鏈接。通常是經過維護一個鏈接池,限定最大鏈接數。客戶端經過有限的鏈接來和服務器進行通訊。服務器
咱們在使用Jedis客戶端和Redis服務器進行通訊時,也是經過鏈接池來獲取鏈接的。Jedis的鏈接必須是線程獨佔的,由於它不是線程安全的。從鏈接池中獲取鏈接時,其它線程就暫時拿不到這個鏈接了,待當前線程處理完畢後,要將鏈接歸還給線程池,這樣其它線程才能夠繼續使用這個鏈接。網絡
Redis的客戶端請求和應答是順序性的,一問一答,因此請求和應答不須要惟一ID就能夠創建起關聯。多線程
Bolt不同,它的問答是亂序的,問和答之間是必須經過請求的惟一ID來創建起關聯。Bolt的客戶端是線程安全的,它能夠同時傳遞多個請求,鏈接對象會維護一個正在處理的RPC請求對象字典。當客戶端想要發起RPC請求時,它不是從鏈接池中摘出一個獨佔鏈接,而是隨意選擇一個鏈接來傳遞本身的請求,這個鏈接也能夠被其它線程同時使用。app
客戶端提供了多種複雜均衡的實現,阿里默認使用帶權重的隨機算法(RandomLoadBalancer),此外還有負載均衡
服務器採用傳統netty多線程模型,一個acceptor線程專門用來接收鏈接,而後扔給io線程處理讀消息並解碼成請求對象,最後扔給業務線程池進行處理。
客戶端和服務器之間會有定時心跳檢測鏈接的存活,默認30s來一次。tcp的關閉是經過FIN包來通知對方的,若是由於網絡問題,對方連FIN包都收不到,那麼即便一邊關閉了套接字,另外一邊可能還覺得鏈接正常。因此心跳檢測存活機制在長鏈接應用裏很是廣泛。若是客戶端連續發了三次心跳都沒有收到服務器的回覆,那麼就認爲鏈接已經關閉。服務器也會有鏈接存活檢測,若是一個客戶端鏈接90s內沒有任何消息進來,那麼也認爲該鏈接已經斷開。服務器不會主動發送心跳消息。
RPC通常是由客戶端向服務器發起一個請求,而後收到服務器的應答。Bolt的RPC是雙工通訊,服務器也能夠向客戶端主動發起請求,它們共享一個TCP鏈接。TCP鏈接自己就是雙工的,因此這也不算什麼奇蹟。只是服務器在什麼業務場景須要向客戶端主動發起請求,這個螞蟻並無進行詳細說明。
客戶端做爲主動鏈接方,它要負責重連和發起心跳消息。服務器做爲被動方,它不須要處理重連,若是鏈接斷開,它就直接將鏈接從集合中移除就行,不須要作特殊的處理,可是它會檢測心跳消息,若是在必定時間內鏈接通道沒有任何消息到來,它就會主動關閉。
客戶端的重連策略是一個單獨的模塊,有兩個地方會成爲重連的入口。一個是正常鏈接斷開觸發channelInActive回調,另外一個就是重連鏈接不能創建成功時須要進行重試。Bolt有一個單獨的重連線程,全部須要重連的鏈接會被包裝成一個任務塞進這個線程的任務隊列,該線程不斷地從隊列裏拿任務進行重連處理,若是重連失敗會嘗試再將任務從新包裝進隊列延後繼續處理。默認是1s鍾處理一個重連任務。
RPC鏈接是延遲創建的,它在第一次客戶端發送RPC請求時嘗試進行鏈接,若是鏈接失敗,它會當即繼續重連最多默認兩次。若是三次嘗試鏈接後仍是沒有創建成功,就向上層爆出異常。它不須要包裝一個重連任務塞進ReconnectManager,由於後續客戶端請求會繼續觸發鏈接。
RPC一般是一應一答,客戶端能夠同步等待響應,也能夠提供回調接口等待結果通知。Bolt除了提供應答模式以外,還提供了oneway單向消息,這種消息服務器收到後不用回覆,客戶端發送請求以後就當即返回了也不須要等待結果。
oneway消息通常用於不那麼重要的日誌類消息,它不能保證服務器必定能收到,因此此種業務消息應該是那種容許丟失的消息,形式上相似於UDP,它在犧牲可靠性的前提下能大幅提高消息的吞吐量。
Bolt提供了回調接口,方便監控系統能夠對請求的調用情況進行分析。監控的客戶端能夠經過實現該接口,註冊進RPC的客戶端和服務器進行打點收集日誌,而後發送到日誌分析系統。
interface Tracer {
void startRpc(SofaRequest request);
void serverReceived(SofaRequest request);
void serverSend(SofaRequest request, SofaResponse response, Throwable throwable);
void clientReceived(SofaRequest request, SofaResponse response, Throwable throwable);
...
}
複製代碼
Bolt是一個成熟的比較複雜的RPC系統,這篇小文章只講解了其中一部分,內部還有大量的實現細節有待去挖掘。
閱讀相關文章,請關注公衆號「碼洞」,SOFA開源系列代碼是Java程序員的大寶藏,它有太多的項目和代碼會讓不少用戶感到望而生畏,後續我會陸續放出SOFA開源系列其它模塊的分析文章,幫助你們輕鬆理解SOFA項目。