插: 前些天發(fā)現(xiàn)了一個巨牛的人工智能學習網(wǎng)站,通俗易懂,風趣幽默塞蹭,忍不住分享一下給大家。點擊跳轉(zhuǎn)到網(wǎng)站讶坯。
堅持不懈番电,越努力越幸運,大家一起學習鴨~~~
前言
什么是RPC辆琅?它的原理是什么漱办?它有什么特點?如果讓你實現(xiàn)一個RPC框架婉烟,你會如何實現(xiàn)娩井?帶著這些問題,開始今天的學習似袁。
2|0RPC概述
2|1什么是RPC洞辣?
RPC*****是遠程過程調(diào)用(Remote Procedure Call)。 RPC 的主要功能目標是讓構(gòu)建分布式計算(應用)更容易昙衅,在提供強大的遠程調(diào)用能力時不損失本地調(diào)用的語義簡潔性扬霜。為實現(xiàn)該目標,RPC 框架需提供一種透明調(diào)用機制而涉,讓使用者不必顯式地區(qū)分本地調(diào)用和遠程調(diào)用著瓶。*
2|2優(yōu)點
- 分布式設(shè)計
- 部署靈活
- 解構(gòu)服務
- 擴展性強
2|3有哪些RPC框架
Dubbo:國內(nèi)最早開源的 RPC 框架,由阿里巴巴公司開發(fā)并于 2011 年末對外開源啼县,僅支持 Java 語言材原。
Motan:微博內(nèi)部使用的 RPC 框架,于 2016 年對外開源季眷,僅支持 Java 語言华糖。
Tars:騰訊內(nèi)部使用的 RPC 框架,于 2017 年對外開源瘟裸,僅支持 C++ 語言客叉。
Spring Cloud:國外 Pivotal 公司 2014 年對外開源的 RPC 框架,提供了豐富的生態(tài)組件。
gRPC:Google 于 2015 年對外開源的跨語言 RPC 框架兼搏,支持多種語言卵慰。
Thrift:最初是由 Facebook 開發(fā)的內(nèi)部系統(tǒng)跨語言的 RPC 框架,2007 年貢獻給了 Apache 基金佛呻,成為Apache 開源項目之一裳朋,支持多種語言。
2|4特性
1吓著、RPC框架一般使用長鏈接鲤嫡,不必每次通信都要3次握手,減少網(wǎng)絡(luò)開銷绑莺。
2暖眼、RPC框架一般都有注冊中心,有豐富的監(jiān)控管理纺裁。發(fā)布诫肠、下線接口、動態(tài)擴展等欺缘,對調(diào)用方來說是無感知栋豫、統(tǒng)一化的操作協(xié)議私密,安全性較高
3谚殊、RPC 協(xié)議更簡單內(nèi)容更小丧鸯,效率更高,服務化架構(gòu)嫩絮、服務化治理骡送,RPC框架是一個強力的支撐。
2|5架構(gòu)
2|6調(diào)用流程
具體步驟:
- 服務消費者(client客戶端)通過本地調(diào)用的方式調(diào)用服務絮记。
- 客戶端存根(client stub)接收到請求后負責將方法摔踱、入?yún)⒌刃畔⑿蛄谢ńM裝)成能夠進行網(wǎng)絡(luò)傳輸?shù)南?br> 體。
- 客戶端存根(client stub)找到遠程的服務地址怨愤,并且將消息通過網(wǎng)絡(luò)發(fā)送給服務端派敷。
- 服務端存根(server stub)收到消息后進行解碼(反序列化操作)。
- 服務端存根(server stub)根據(jù)解碼結(jié)果調(diào)用本地的服務進行相關(guān)處理撰洗。
- 本地服務執(zhí)行具體業(yè)務邏輯并將處理結(jié)果返回給服務端存根(server stub)篮愉。
- 服務端存根(server stub)將返回結(jié)果重新打包成消息(序列化)并通過網(wǎng)絡(luò)發(fā)送至消費方。
- 客戶端存根(client stub)接收到消息差导,并進行解碼(反序列化)试躏。
- 服務消費方得到最終結(jié)果。
涉及到的技術(shù)
- 動態(tài)代理:生成Client Stub(客戶端存根)和Server Stub(服務端存根)的時候需要用到j(luò)ava動態(tài)代理技術(shù)设褐。
- 序列化 在網(wǎng)絡(luò)中颠蕴,所有的數(shù)據(jù)都將會被轉(zhuǎn)化為字節(jié)進行傳送泣刹,需要對這些參數(shù)進行序列化和反序列化操作。目前主流高效的開源序列化框架有Kryo犀被、fastjson椅您、Hessian、Protobuf等寡键。
- NIO通信:Java 提供了 NIO 的解決方案掀泳,Java 7 也提供了更優(yōu)秀的 NIO.2 支持∥餍可以采用Netty或者mina框架來解決NIO數(shù)據(jù)傳輸?shù)膯栴}员舵。開源的RPC框架Dubbo就是采用NIO通信,集成支持netty藕畔、mina马僻、grizzly。
- 服務注冊中心:通過注冊中心劫流,讓客戶端連接調(diào)用服務端所發(fā)布的服務。主流的注冊中心組件:Redis丛忆、Zookeeper祠汇、Consul、Etcd熄诡。Dubbo采用的是ZooKeeper提供服務注冊與發(fā)現(xiàn)功能可很。
- 負載均衡:在高并發(fā)的場景下,需要多個節(jié)點或集群來提升整體吞吐能力凰浮。
- 健康檢查:健康檢查包括我抠,客戶端心跳和服務端主動探測兩種方式。
3|0RPC深入解析
3|1序列化技術(shù)
序列化作用
在網(wǎng)絡(luò)傳輸中袜茧,數(shù)據(jù)必須采用二進制形式菜拓, 所以在RPC調(diào)用過程中, 需要采用序列化技術(shù)笛厦,對入?yún)ο蠛头祷刂祵ο筮M行序列化與反序列化纳鼎。
如何序列化
自定義二進制協(xié)議來實現(xiàn)序列化:
序列化的處理要素
序列化的處理要素
- 解析效率:序列化協(xié)議應該首要考慮的因素,像xml/json解析起來比較耗時裳凸,需要解析doom樹贱鄙,二進制自定義協(xié)議解析起來效率要快很多。
- 壓縮率:同樣一個對象姨谷,xml/json傳輸起來有大量的標簽冗余信息逗宁,信息有效性低,二進制自定義協(xié)議占用的空間相對來說會小很多梦湘。
- 擴展性與兼容性:是否能夠利于信息的擴展瞎颗,并且增加字段后舊版客戶端是否需要強制升級件甥,這都是需要考慮的問題,在自定義二進制協(xié)議時候言缤,要做好充分考慮設(shè)計嚼蚀。
- 可讀性與可調(diào)試性:xml/json的可讀性會比二進制協(xié)議好很多,并且通過網(wǎng)絡(luò)抓包是可以直接讀取管挟,二進制則需要反序列化才能查看其內(nèi)容轿曙。
- 跨語言:有些序列化協(xié)議是與開發(fā)語言緊密相關(guān)的,例如dubbo的Hessian序列化協(xié)議就只能支持Java的RPC調(diào)用僻孝。
- 通用性:xml/json非常通用导帝,都有很好的第三方解析庫,各個語言解析起來都十分方便穿铆,二進制數(shù)據(jù)的處理方面也有Protobuf和Hessian等插件您单,在做設(shè)計的時候盡量做到較好的通用性。
常用序列化技術(shù)
- JDK原生序列化荞雏,通過實現(xiàn)Serializable接口虐秦。通過ObjectOutPutSream和ObjectInputStream對象進行序列化及反序列化.
- JSON序列化。一般在HTTP協(xié)議的RPC框架通信中凤优,會選擇JSON方式悦陋。JSON具有較好的擴展性、可讀性和通用性筑辨。但JSON序列化占用空間開銷較大,沒有JAVA的強類型區(qū)分俺驶,需要通過反射解決,解析效率和壓縮率都較差棍辕。如果對并發(fā)和性能要求較高暮现,或者是傳輸數(shù)據(jù)量較大的場景,不建議采用JSON序列化方式楚昭。
- Hessian2序列化栖袋。Hessian 是一個動態(tài)類型,二進制序列化抚太,并且支持跨語言特性的序列化框架栋荸。Hessian 性能上要比 JDK、JSON 序列化高效很多凭舶,并且生成的字節(jié)數(shù)也更小晌块。有非常好的兼容性和穩(wěn)定性,所以 Hessian 更加適合作為 RPC 框架遠程通信的序列化協(xié)議帅霜。
...
User user = new User();
user.setName("laowang");
//user對象序列化處理
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(user);
output.flushBuffer();
byte[] data = bos.toByteArray();
bos.close();
//user對象反序列化處理
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
User user = (User) input.readObject();
input.close();
System.out.println(user);
...
Hessian自身也存在一些缺陷匆背,大家在使用過程中要注意:
- 對Linked系列對象不支持,比如LinkedHashMap身冀、LinkedHashSet 等钝尸,但可以通過CollectionSerializer類修復括享。
- Locale 類不支持,可以通過擴展 ContextSerializerFactory 類修復珍促。
- Byte/Short 在反序列化的時候會轉(zhuǎn)成 Integer铃辖。
3|2動態(tài)代理
RPC的調(diào)用內(nèi)部核心技術(shù)采用的就是動態(tài)代理。
JDK動態(tài)代理如何實現(xiàn)猪叙?
public class JdkProxyTest {
/**
* 定義用戶的接口
*/
public interface User {
String job();
}
/**
* 實際的調(diào)用對象
*/
public static class Teacher {
public String invoke(){
return "i'm Teacher";
}
}
/**
* 創(chuàng)建JDK動態(tài)代理類
*/
public static class JDKProxy implements InvocationHandler {
private Object target;
JDKProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] paramValues) {
return ((Teacher)target).invoke();
}
}
public static void main(String[] args){
// 構(gòu)建代理器
JDKProxy proxy = new JDKProxy(new Teacher());
ClassLoader classLoader = ClassLoaderUtils.getClassLoader();
// 生成代理類
User user = (User) Proxy.newProxyInstance(classLoader, new Class[]{User.class},
proxy);
// 接口調(diào)用
System.out.println(user.job());
}
}
JDK動態(tài)代理實現(xiàn)原理:
代理類 $Proxy里面會定義相同簽名的接口娇斩,然后內(nèi)部會定義一個變量綁定JDKProxy代理對象,當調(diào)用User.job接口方法穴翩,實質(zhì)上調(diào)用的是JDKProxy.invoke()方法犬第。
Cglib
3|3服務注冊發(fā)現(xiàn)
注冊與發(fā)現(xiàn)流程
服務注冊:服務提供方將對外暴露的接口發(fā)布到注冊中心內(nèi),注冊中心為了檢測服務的有效狀態(tài)芒帕,一般會建立雙向心跳機制歉嗓。
服務訂閱:服務調(diào)用方去注冊中心查找并訂閱服務提供方的 IP,并緩存到本地用于后續(xù)調(diào)用背蟆。
如何實現(xiàn):基于ZK
A. 在 ZooKeeper 中創(chuàng)建一個服務根路徑鉴分,可以根據(jù)接口名命名(例
如:/micro/service/com.laowang.orderService),在這個路徑再創(chuàng)建服務提供方與調(diào)用方目錄(server带膀、
client)志珍,分別用來存儲服務提供方和調(diào)用方的節(jié)點信息。
B. 服務端發(fā)起注冊時本砰,會在服務提供方目錄中創(chuàng)建一個臨時節(jié)點碴裙,節(jié)點中存儲注冊信息钢悲。
C. 客戶端發(fā)起訂閱時点额,會在服務調(diào)用方目錄中創(chuàng)建一個臨時節(jié)點,節(jié)點中存儲調(diào)用方的信息莺琳,同時watch 服務提供方的目錄(/micro/service/com.laowang.orderService/server)中所有的服務節(jié)點數(shù)據(jù)还棱。當服務端產(chǎn)生變化時ZK就會通知給訂閱的客戶端。
ZooKeeper方案的特點:
強一致性惭等,ZooKeeper 集群的每個節(jié)點的數(shù)據(jù)每次發(fā)生更新操作珍手,都會通知其它 ZooKeeper 節(jié)點同時執(zhí)行更新。
3|4健康監(jiān)測
為什么需要做健康監(jiān)測?
比如網(wǎng)絡(luò)中的波動辞做,硬件設(shè)施的老化等等琳要。可能造成集群當中的某個節(jié)點存在問題秤茅,無法正常調(diào)用稚补。
健康監(jiān)測實現(xiàn)分析
心跳檢測的過程總共包含以下狀態(tài):健康狀態(tài)、波動狀態(tài)框喳、失敗狀態(tài)课幕。
完善的解決方案
(1)閾值: 健康監(jiān)測增加失敗閾值記錄。
(2)成功率: 可以再追加調(diào)用成功率的記錄(成功次數(shù)/總次數(shù))。
(3)探針: 對服務節(jié)點有一個主動的存活檢測機制抠忘。
3|5網(wǎng)絡(luò)IO模型
3|6零拷貝
什么是零拷貝
系統(tǒng)內(nèi)核處理 IO 操作分為兩個階段:等待數(shù)據(jù)和拷貝數(shù)據(jù)解幼。
等待數(shù)據(jù),就是系統(tǒng)內(nèi)核在等待網(wǎng)卡接收到數(shù)據(jù)后润绎,把數(shù)據(jù)寫到內(nèi)核中撬碟。
拷貝數(shù)據(jù),就是系統(tǒng)內(nèi)核在獲取到數(shù)據(jù)后凡橱,將數(shù)據(jù)拷貝到用戶進程的空間中
所謂的零拷貝小作,就是取消用戶空間與內(nèi)核空間之間的數(shù)據(jù)拷貝操作,應用進程每一次的讀寫操作稼钩,都可以通過一種方式顾稀,讓應用進程向用戶空間寫入或者讀取數(shù)據(jù),就如同直接向內(nèi)核空間寫入或者讀取數(shù)據(jù)一樣坝撑,再通過 DMA 將內(nèi)核中的數(shù)據(jù)拷貝到網(wǎng)卡静秆,或?qū)⒕W(wǎng)卡中的數(shù)據(jù) copy 到內(nèi)核。
RPC框架的零拷貝應用
Netty 框架是否也有零拷貝機制巡李?
Netty 的零拷貝則有些不一樣抚笔,他完全站在了用戶空間上,也就是基于 JVM 之上侨拦。
Netty當中的零拷貝是如何實現(xiàn)的殊橙?
RPC 并不會把請求參數(shù)作為一個整體數(shù)據(jù)包發(fā)送到對端機器上,中間可能會拆分狱从,也可能會合并其他請求膨蛮,所以消息都需要有邊界。接收到消息之后季研,需要對數(shù)據(jù)包進行處理敞葛,根據(jù)邊界對數(shù)據(jù)包進行分割和合并,最終獲得完整的消息与涡。
Netty零拷貝主要體現(xiàn)在三個方面:
1惹谐、Netty的接收和發(fā)送ByteBuffer是采用DIRECT BUFFERS,使用堆外的直接內(nèi)存(內(nèi)存對象分配在JVM中堆以外的內(nèi)存)進行Socket讀寫驼卖,不需要進行字節(jié)緩沖區(qū)的二次拷貝氨肌。如果采用傳統(tǒng)堆內(nèi)存(HEAP BUFFERS)進行Socket讀寫,JVM會將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中酌畜,然后寫入Socket中怎囚。
2、Netty提供了組合Buffer對象檩奠,也就是CompositeByteBuf 類桩了,可以將 ByteBuf 分解為多個共享同一個存儲區(qū)域的 ByteBuf附帽,避免了內(nèi)存的拷貝。
3井誉、Netty的文件傳輸采用了FileRegion 中包裝 NIO 的 FileChannel.transferT o() 方法蕉扮,它可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標Channel,避免了傳統(tǒng)通過循環(huán)write方式導致的內(nèi)存拷貝問題颗圣。
零拷貝帶來的作用就是避免沒必要的 CPU 拷貝喳钟,減少了 CPU 在用戶空間與內(nèi)核空間之間的上下文切換,從而提升了網(wǎng)絡(luò)通信效率與應用程序的整體性能在岂。
3|7時間輪
為什么需要時間輪奔则?
在Dubbo中,為增強系統(tǒng)的容錯能力蔽午,會有相應的監(jiān)聽判斷處理機制易茬。比如RPC調(diào)用的超時機制的實現(xiàn),消費者判斷RPC調(diào)用是否超時及老,如果超時會將超時結(jié)果返回給應用層抽莱。在Dubbo最開始的實現(xiàn)中,是將所有的返回結(jié)果(DefaultFuture)都放入一個集合中骄恶,并且通過一個定時任務食铐,每隔一定時間間隔就掃描所有的future,逐個判斷是否超時僧鲁。
這樣的實現(xiàn)方式雖然比較簡單虐呻,但是存在一個問題就是會有很多無意義的遍歷操作開銷。比如一個RPC調(diào)用的超時時間是10秒寞秃,而設(shè)置的超時判定的定時任務是2秒執(zhí)行一次斟叼,那么可能會有4次左右無意義的循環(huán)檢測判斷操作。
為了解決上述場景中的類似問題蜕该,Dubbo借鑒Netty犁柜,引入了時間輪算法洲鸠,減少無意義的輪詢判斷操作堂淡。
時間輪原理
對于以上問題, 目的是要減少額外的掃描操作就可以了扒腕。比如說一個定時任務是在5 秒之后執(zhí)行绢淀,那么在 4.9秒之后才掃描這個定時任務,這樣就可以極大減少 CPU開銷瘾腰。這時我們就可以利用時鐘輪的機制了皆的。
時鐘輪的實質(zhì)上是參考了生活中的時鐘跳動的原理,那么具體是如何實現(xiàn)呢蹋盆?
在時鐘輪機制中费薄,有時間槽和時鐘輪的概念硝全,時間槽就相當于時鐘的刻度;而時鐘輪就相當于指針跳動的一個周期楞抡,我們可以將每個任務放到對應的時間槽位上伟众。
如果時鐘輪有 10 個槽位,而時鐘輪一輪的周期是 10 秒召廷,那么我們每個槽位的單位時間就是 1 秒凳厢,而下一層時間輪的周期就是 100 秒,每個槽位的單位時間也就是 10 秒竞慢,這就好比秒針與分針先紫, 在秒針周期下, 刻度單位為
秒筹煮, 在分針周期下遮精, 刻度為分。
假設(shè)現(xiàn)在我們有 3 個任務败潦,分別是任務 A(0.9秒之后執(zhí)行)仑鸥、任務 B(2.1秒后執(zhí)行)與任務 C(12.1秒之后執(zhí)
行),我們將這 3 個任務添加到時鐘輪中变屁,任務 A 被放到第 0 槽位眼俊,任務 B 被放到第 2槽位,任務 C 被放到下一
層時間輪的第2個槽位粟关。
通過這個場景我們可以了解到疮胖,時鐘輪的掃描周期仍是最小單位1秒,但是放置其中的任務并沒有反復掃描闷板,每個
任務會按要求只掃描執(zhí)行一次澎灸, 這樣就能夠很好的解決CPU 浪費的問題。
Dubbo中的時間輪原理是如何實現(xiàn)的?
主要是通過Timer遮晚,Timeout性昭,TimerT ask幾個接口定義了一個定時器的模型,再通過HashedWheelTimer這個類
實現(xiàn)了一個時間輪定時器(默認的時間槽的數(shù)量是512县遣,可以自定義這個值)糜颠。它對外提供了簡單易用的接口,只
需要調(diào)用newTimeout接口萧求,就可以實現(xiàn)對只需執(zhí)行一次任務的調(diào)度其兴。通過該定時器,Dubbo在響應的場景中實現(xiàn)
了高效的任務調(diào)度夸政。
時間輪在RPC的應用
調(diào)用超時: 上面所講的客戶端調(diào)用超時的處理元旬,就可以應用到時鐘輪,我們每發(fā)一次請求,都創(chuàng)建一個處理請求超時的定時任務放到時鐘輪里匀归,在高并發(fā)坑资、高訪問量的情況下,時鐘輪每次只輪詢一個時間槽位中的任務穆端,這樣會節(jié)省大量的 CPU盐茎。
啟動加載: 調(diào)用端與服務端啟動也可以應用到時鐘輪,比如說在服務啟動完成之后要去加載緩存徙赢,執(zhí)行定時任務等字柠, 都可以放在時鐘輪里
定時心跳檢測: RPC 框架調(diào)用端定時向服務端發(fā)送的心跳檢測,來維護連接狀態(tài)狡赐,我們可以將心跳的邏輯封裝為一個心跳任務窑业,放到時鐘輪里。心跳是要定時重復執(zhí)行的枕屉,而時鐘輪中的任務執(zhí)行一遍就被移除了常柄,對于這種需要重復執(zhí)行的定時任務我們該如何處理呢?我們在定時任務邏輯結(jié)束的最后搀擂,再加上一段邏輯西潘, 重設(shè)這個任務的執(zhí)行時間,把它重新丟回到時鐘輪里哨颂。這樣就可以實現(xiàn)循環(huán)執(zhí)行喷市。
4|0PRC高級應用
4|1異步處理機制
為什么要采用異步?
如果采用同步調(diào)用, CPU 大部分的時間都在等待而沒有去計算威恼,從而導致 CPU 的利用率不夠品姓。RPC 請求比較耗時的原因主要是在哪里?在大多數(shù)情況下箫措,RPC 本身處理請求的效率是在毫秒級的腹备。RPC 請求的耗時大部分都是業(yè)務耗時。
調(diào)用端如何實現(xiàn)異步
常用的方式就是Future 方式斤蔓,它是返回 Future 對象植酥,通過GET方式獲取結(jié)果;或者采用入?yún)?Callback 對象的回調(diào)方式弦牡,處理結(jié)果友驮。
基于RPC的DUBBO框架是如何實現(xiàn)異步調(diào)用呢?
服務端如何實現(xiàn)異步?
為了提升性能喇伯,連接請求與業(yè)務處理不會放在一個線程處理喊儡, 這個就是服務端的異步化拨与。服務端業(yè)務處理邏輯加入異步處理機制稻据。在RPC 框架提供一種回調(diào)方式,讓業(yè)務邏輯可以異步處理,處理完之后調(diào)用 RPC 框架的回調(diào)接口
RPC 框架的異步策略主要是調(diào)用端異步與服務端異步捻悯。調(diào)用端的異步就是通過 Future 方式匆赃。服務端異步則需要一種回調(diào)方式,讓業(yè)務邏輯可以異步處理今缚。這樣就實現(xiàn)了RPC調(diào)用的全異步化
4|2路由和負載均衡
為什么要用路由
真實的環(huán)境中一般是以集群的方式提供服務算柳,對于服務調(diào)用方來說,一個接口會有多個服務提供方同時提供服務姓言,所以 RPC 在每次發(fā)起請求的時候瞬项,都需要從多個服務節(jié)點里面選取一個用于處理請求的服務節(jié)點。這就需要在RPC應用中增加路由功能何荚。
如何實現(xiàn)路由
服務注冊發(fā)現(xiàn)方式:
通過服務發(fā)現(xiàn)的方式從邏輯上看是可行囱淋,但注冊中心是用來保證數(shù)據(jù)的一致性。通過服務發(fā)現(xiàn)方式來實現(xiàn)請求隔離并不理想餐塘。
RPC路由策略:
從服務提供方節(jié)點集合里面選擇一個合適的節(jié)點(負載均衡)妥衣,把符合我們要求的節(jié)點篩選出來。這個就是路由策略:接收請求-->請求校驗-->路由策略-->負載均衡-->
有些場景下戒傻,可能還需要更細粒度的路由方式税手,比如說根據(jù)SESSIONID要落到相同的服務節(jié)點上以保持會話的有效性;
RPC框架中的負載均衡
RPC 的負載均衡是由 RPC 框架自身提供實現(xiàn),自主選擇一個最佳的服務節(jié)點需纳,發(fā)起 RPC 調(diào)用請求芦倒。
RPC 負載均衡策略一般包括:輪詢、隨機不翩、權(quán)重熙暴、最少連接等。Dubbo默認就是使用隨機負載均衡策略慌盯。
自適應的負載均衡策略
RPC 負載均衡完全由 RPC 框架自身實現(xiàn)周霉,通過所配置的負載均衡組件,自主選擇合適的服務節(jié)點亚皂。這個就是自適應的負載均衡策略俱箱。
具體如何實現(xiàn)?這就需要判定服務節(jié)點的處理能力灭必。
主要步驟:
(1)添加計分器和指標采集器狞谱。
(2)指標采集器收集服務節(jié)點 CPU 核數(shù)、CPU 負載以及內(nèi)存占用率等指標禁漓。
(3)可以配置開啟哪些指標采集器跟衅,并設(shè)置這些參考指標的具體權(quán)重。
(4)通過對服務節(jié)點的綜合打分播歼,最終計算出服務節(jié)點的實際權(quán)重伶跷,選擇合適的服務節(jié)點。
4|3熔斷限流
在實際生產(chǎn)環(huán)境中,每個服務節(jié)點都可能由于訪問量過大而引起一系列問題叭莫,就需要業(yè)務提供方能夠進行自我保護蹈集,從而保證在高訪問量、高并發(fā)的場景下雇初,系統(tǒng)依然能夠穩(wěn)定拢肆,高效運行。
在Dubbo框架中靖诗, 可以通過Sentinel來實現(xiàn)更為完善的熔斷限流功能郭怪,服務端是具體如何實現(xiàn)限流邏輯的?
方法有很多種刊橘, 最簡單的是計數(shù)器移盆,還有平滑限流的滑動窗口、漏斗算法以及令牌桶算法等等伤为。Sentinel采用是滑動窗口來實現(xiàn)的限流咒循。
調(diào)用方的自我保護
一個服務 A 調(diào)用服務 B 時,服務 B 的業(yè)務邏輯又調(diào)用了服務 C绞愚,這時服務 C 響應超時叙甸,服務 B 就可能會因為堆積大量請求而導致服務宕機,由此產(chǎn)生服務雪崩的問題位衩。
熔斷機制:
熔斷器的工作機制主要是關(guān)閉裆蒸、打開和半打開這三個狀態(tài)之間的切換。
Sentinel 熔斷降級組件它可以支持以下降級策略:
平均響應時間 ( DEGRADE_GRADE_RT ):當 1s 內(nèi)持續(xù)進入 N 個請求糖驴,對應時刻的平均響應時間(秒級)均超過閾值( count 僚祷,以 ms 為單位),那么在接下的時間窗口( DegradeRule 中的 timeWindow 贮缕,以 s為單位)之內(nèi)辙谜,對這個方法的調(diào)用都會自動地熔斷(拋出 DegradeException )。注意 Sentinel 默認統(tǒng)計的 RT 上限是 4900 ms感昼,超出此閾值的都會算作 4900 ms装哆,若需要變更此上限可以通過啟動配置項Dcsp.sentinel.statistic.max.rt=xxx 來配置。
異常比例 ( DEGRADE_GRADE_EXCEPTION_RATIO ):當資源的每秒請求量 >= N(可配置)定嗓,并且每秒異惩汕伲總數(shù)占通過量的比值超過閾值( DegradeRule 中的 count )之后,資源進入降級狀態(tài)宵溅,即在接下的時間窗口( DegradeRule 中的 timeWindow 凌简,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地返回恃逻。異常比率的閾值范圍是 [0.0, 1.0] 雏搂,代表 0% - 100%藕施。
異常數(shù) ( DEGRADE_GRADE_EXCEPTION_COUNT ):當資源近 1 分鐘的異常數(shù)目超過閾值之后會進行熔斷。注意由于統(tǒng)計時間窗口是分鐘級別的畔派,若 timeWindow 小于 60s铅碍,則結(jié)束熔斷狀態(tài)后仍可能再進入熔斷狀態(tài)润绵。
4|4優(yōu)雅啟動
什么是啟動預熱
啟動預熱就是讓剛啟動的服務线椰,不直接承擔全部的流量,而是讓它隨著時間的移動慢慢增加調(diào)用次數(shù)尘盼,最終讓流量緩和運行一段時間后達到正常水平憨愉。
如何實現(xiàn)
首先要知道服務提供方的啟動時間,有兩種獲取方法
- 一種是服務提供方在啟動的時候卿捎,主動將啟動的時間發(fā)送給注冊中心配紫;
- 另一種就是注冊中心來檢測, 將服務提供方的請求注冊時間作為啟動時間午阵。
調(diào)用方通過服務發(fā)現(xiàn)獲取服務提供方的啟動時間躺孝, 然后進行降權(quán),減少被負載均衡選擇的概率底桂,從而實現(xiàn)預熱過程植袍。
在Dubbo框架中也引入了"warmup"特性,核心源碼是在" com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance.java"中:
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
// 先得到Provider的權(quán)重
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(),
Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
if (weight > 0) {
// 得到provider的啟動時間戳
long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
// provider已經(jīng)運行時間
int uptime = (int) (System.currentTimeMillis() ‐ timestamp);
// 得到warmup的值籽懦,默認為10分鐘
int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY,
Constants.DEFAULT_WARMUP);
// provider運行時間少于預熱時間于个,那么需要重新計算權(quán)重weight(即需要降權(quán))
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight;
}
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
// 隨著provider的啟動時間越來越長,慢慢提升權(quán)重weight
int ww = (int) ( (float) uptime / ( (float) warmup / (float) weight ) );
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
優(yōu)雅關(guān)閉
為什么需要優(yōu)雅關(guān)閉
調(diào)用方會存在以下情況:目標服務已經(jīng)下線;目標服務正在關(guān)閉中暮顺。
如何實現(xiàn)優(yōu)雅關(guān)閉
當服務提供方正在關(guān)閉厅篓,可以直接返回一個特定的異常給調(diào)用方。然后調(diào)用方把這個節(jié)點從健康列表挪出捶码,并把其他請求自動重試到其他節(jié)點羽氮。如需更為完善, 可以再加上主動通知機制惫恼。
在Dubbo框架中乏苦, 在以下場景中會觸發(fā)優(yōu)雅關(guān)閉:
JVM主動關(guān)閉( System.exit(int) ; JVM由于資源問題退出( OOM )尤筐; 應用程序接受到進程正常結(jié)束的信號:SIGTERM 或 SIGINT 信號汇荐。
優(yōu)雅停機是默認開啟的,停機等待時間為10秒盆繁∠铺裕可以通過配置 dubbo.service.shutdown.wait 來修改等待時間。Dubbo 推出了多段關(guān)閉的方式來保證服務完全無損油昂。
5|0如何實現(xiàn)一個RPC框架
思路:
- 服務設(shè)計:客戶端革娄、服務端倾贰、ZK注冊中心,獲取訂單接口拦惋。
怎么知道服務端的信息匆浙? 如何去調(diào)用的? - 先啟動服務端: 將接口信息注冊至ZK厕妖。(ServicePushManager.registerIntoZK方法)
- 啟動客戶端: 從ZK拉取服務端接口信息首尼。(ServicePullManager.pullServiceFromZK方法)
- Rpc調(diào)用處理流程:
客戶端->通過動態(tài)代理調(diào)用服務端接口(ProxyHelper.doIntercept)-> 選取不同的調(diào)用策略-> 異步方式調(diào)用(通過MAP存儲記錄channel,rpcRequestPool.fetchResponse獲取結(jié)果)-> 服務端(根據(jù)請求信息調(diào)用對應的接口言秸, RpcRequestHandler.channelRead0)-> 客戶端監(jiān)聽接收結(jié)果(RpcResponseHandler.channelRead0)-> 關(guān)閉連接(RpcRequestManager.destroyChannelHolder關(guān)閉連接)