前言
HSF是一個(gè)分布式的遠(yuǎn)程服務(wù)調(diào)用框架熔恢,其實(shí)我更喜歡把分布式幾個(gè)字去掉轻姿,因?yàn)镠SF本身并不是一個(gè)單獨(dú)的服務(wù)(指一個(gè)進(jìn)程)筑累,他是附屬在你的應(yīng)用里的一個(gè)組件袱蜡,一個(gè)RPC組件(遠(yuǎn)程過程調(diào)用——Remote Procedure Call,是一種通過網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請求服務(wù)慢宗,而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議坪蚁。在OSI網(wǎng)絡(luò)通信模型中,RPC跨越了傳輸層和應(yīng)用層镜沽。RPC使得開發(fā)分布式應(yīng)用更加容易)敏晤,當(dāng)然HSF完全的內(nèi)容肯定不止這些。
說了那么久HSF全稱是什么呢缅茉?High-Speed Service Framework
RPC
我們先來看一張圖:
很多同學(xué)看了這張圖可能會(huì)覺得這跟http的過程有什么區(qū)別嘴脾?
有這么一個(gè)場景(本來想舉一個(gè)便具體業(yè)務(wù)的例子,想想還是已技術(shù)實(shí)現(xiàn)相關(guān)的比較好)蔬墩,監(jiān)控平臺(tái):監(jiān)控所有主機(jī)的狀態(tài)译打,這時(shí)候每臺(tái)主機(jī)上有一個(gè)agent,每個(gè)幾秒向監(jiān)控平臺(tái)上傳一次數(shù)據(jù)(主機(jī)內(nèi)存使用率拇颅、硬盤狀況奏司、CPU、load樟插、進(jìn)程信息等等)韵洋。
可能在開發(fā)的時(shí)候最簡單的方式就是監(jiān)控平臺(tái)有一個(gè)http接口,agent每隔幾秒請求一次岸夯,能夠滿足需求麻献,但是如果主機(jī)數(shù)快速增長了很多、監(jiān)控項(xiàng)越來越多猜扮、請求體越來越大,你會(huì)發(fā)現(xiàn)http的傳輸效率下降了监婶,每一次調(diào)用的耗時(shí)增加了旅赢。
這時(shí)我們會(huì)去研究http協(xié)議,想去優(yōu)化這個(gè)過程惑惶,發(fā)現(xiàn)http的過程是:建立連接煮盼、發(fā)送請求信息、發(fā)送響應(yīng)信息带污、關(guān)閉連接僵控,看到這個(gè)過程首先想優(yōu)化的就是能不能不要每次都去建立連接關(guān)閉連接,因?yàn)閿?shù)據(jù)上報(bào)是個(gè)持續(xù)的過程鱼冀;緊接著去研究http頭报破,發(fā)現(xiàn)很多協(xié)議用不到悠就,繁雜,白白增加了消息體充易;后來又覺得http的協(xié)議解析還原過程很復(fù)雜梗脾,可以自己開發(fā)一個(gè)提升性能......
RPC來了,他能滿足這些需求盹靴,但是前提是需要開發(fā)炸茧,需要前期成本,所以想項(xiàng)目設(shè)計(jì)時(shí)就要去衡量稿静,不過沒事梭冠,我們有HSF啊。
我們將上圖稍微改造一下:
現(xiàn)在從圖中可以看著改备,client和server之間有一條長連接妈嘹,并且我們有自己的協(xié)議體:RpcRequest和RpcResponse。
RPC就講到這里绍妨,畢竟重點(diǎn)是HSF润脸,想要更多的了解RPC,可以上wiki或者網(wǎng)上查詢他去。
HSF架構(gòu)
其實(shí)在我們的應(yīng)用中毙驯,一般情況下你的應(yīng)用不僅僅是client,也是server灾测,因?yàn)槟悴粌H需要去調(diào)用其他應(yīng)用提供的服務(wù)爆价,也提供服務(wù)給其他應(yīng)用,所以這樣一來媳搪,整個(gè)hsf的服務(wù)調(diào)用鏈路也會(huì)很復(fù)雜铭段。
從上面兩幅圖中我們很顯然的發(fā)現(xiàn)一個(gè)問題,就是服務(wù)提供者如何告知客戶端他提供的服務(wù)秦爆,所以需要有一個(gè)服務(wù)注冊與發(fā)現(xiàn)的地方序愚,在HSF架構(gòu)中提供這個(gè)功能的是configserver,如下圖:
從上圖可以看出server端啟動(dòng)的時(shí)候會(huì)向configserver注冊自己提供的服務(wù)等限,client會(huì)向configserver訂閱需要的服務(wù)爸吮,configserver通過訂閱信息將相關(guān)服務(wù)提供者的地址以及其他關(guān)鍵信息推送給client。
上面已經(jīng)實(shí)現(xiàn)了基本的能力望门,但是如何動(dòng)態(tài)配置負(fù)載(線程池大行谓俊)、默認(rèn)配置(configserver地址等)筹误、還有一些特性功能(如路由規(guī)則)桐早,這時(shí)候就需要有一個(gè)持久化配置中心,如下圖:
client和server啟動(dòng)的時(shí)候會(huì)先去diamond獲取需要的配置信息,如最關(guān)鍵的服務(wù)注冊中心的類型和地址哄酝,除此之外之外還有服務(wù)治理的類型和地址等友存。
重點(diǎn)說一下路由規(guī)則,舉個(gè)例子:通過路由規(guī)則配置在服務(wù)調(diào)用的時(shí)候只調(diào)用同機(jī)房的server炫七,這樣子服務(wù)調(diào)用的耗時(shí)肯定比跨機(jī)房的耗時(shí)短爬立。除此之外hsf里還單獨(dú)寫了unitService進(jìn)行服務(wù)單元發(fā)布來區(qū)分中心發(fā)布,這些番外的東西以后有時(shí)間再寫個(gè)番外篇万哪,這里就不過多闡述了侠驯,畢竟這些有點(diǎn)偏場景偏業(yè)務(wù)的內(nèi)容以后可能就改成別的方式了。
相信大家都用過hsf服務(wù)治理網(wǎng)站奕巍,通過這個(gè)網(wǎng)站可以看到有哪些服務(wù)吟策、服務(wù)提供者的地址是多少、有多少提供者的止、具體的消費(fèi)者是誰檩坚,hsf通過configserver、redis诅福、diamond里的存儲(chǔ)信息獲取到這些信息匾委。
redis功能:HSF使用Redis存儲(chǔ)元數(shù)據(jù),每一個(gè)HSF Consumer/Provider 都會(huì)在啟動(dòng)后氓润、每隔一段時(shí)間向redis上報(bào)元數(shù)據(jù)赂乐,這些元數(shù)據(jù)采集起來又提供給HSFOPS做服務(wù)治理,包括應(yīng)用名和服務(wù)的映射咖气、服務(wù)的元數(shù)據(jù)等挨措。
服務(wù)的注冊與發(fā)布
接下來我們把這個(gè)server解開,看看里面是怎么樣的崩溪。
<bean id="hsfTestService"
class="com.test.service.impl.HsfTestServiceImpl" />
<bean class="com.taobao.hsf.app.spring.util.HSFSpringProviderBean"
init-method="init">
<property name="serviceName" value="hsfTestService" />
<property name="target" ref="hsfTestService" />
<property name="serviceInterface">
<value>com.test.service.HsfTestService
</value>
</property>
<property name="serviceVersion">
<value>${hsf.common.provider.version}</value>
</property>
</bean>
相信同學(xué)們對上面這段配置代碼很熟悉浅役,那么服務(wù)到底是怎么注冊的呢,為什么這里配置了這個(gè)服務(wù)就可以被調(diào)用了呢伶唯?
從配置文件看到有個(gè)關(guān)鍵的bean——HSFSpringProviderBean觉既,還有個(gè)關(guān)鍵的初始化方法init,其實(shí)init的過程就是服務(wù)發(fā)布的過程抵怎,我們來看看HSFSpringProviderBean中的部分代碼:
public void init() throws Exception {
// 避免被初始化多次
if (!providerBean.getInited().compareAndSet(false, true)) {
return;
}
LoggerInit.initHSFLog();
SpasInit.initSpas();
providerBean.checkConfig();
publishIfNotInSpringContainer();
}
private void publishIfNotInSpringContainer() {
if (!isInSpringContainer) {
LOGGER.warn("[SpringProviderBean]不是在Spring容器中創(chuàng)建, 不推薦使用");
providerBean.publish();
}
}
從代碼中很明顯的看到服務(wù)發(fā)布providerBean.publish()奋救,先來看大致類圖,類圖中有些不是很關(guān)鍵的先省略了:
大致對類圖進(jìn)行解釋一下反惕,這也是服務(wù)發(fā)布的一個(gè)過程:
- 服務(wù)初始化,首先需要有一個(gè)提供服務(wù)的service實(shí)現(xiàn)類(spring bean)和接口演侯;
- 初始化HSFSpringProviderBean姿染,從配置文件獲取服務(wù)名稱、接口、實(shí)現(xiàn)類悬赏、版本等等狡汉;
- providerBean是HSFApiProviderBean在HSFSpringProviderBean中的變量,HSFSpringProviderBean會(huì)將從配置文件獲取的服務(wù)名稱闽颇、接口盾戴、實(shí)現(xiàn)類、版本等等賦值給providerBean兵多;
- providerBean中有個(gè)服務(wù)實(shí)體類ServiceMetadata尖啡,providerBean會(huì)將服務(wù)發(fā)布的所有信息放在這里,如接口剩膘、實(shí)現(xiàn)類衅斩、版本等等,在整個(gè)發(fā)布過程中怠褐,ServiceMetadata是所有對象之間的傳輸對象畏梆;
- 這里先來解釋一下為什么有HSFSpringProviderBean和HSFApiProviderBean,其實(shí)兩個(gè)可以合并成一個(gè)奈懒,但是為什么要分開呢奠涌?我的理解是對于不同環(huán)境的不同實(shí)現(xiàn),比如現(xiàn)在用的是spring環(huán)境磷杏,那就需要有個(gè)spring適配類HSFSpringProviderBean來獲取配置信息溜畅,假如是其他環(huán)境那么就會(huì)有另一個(gè)適配類,最終把信息統(tǒng)一轉(zhuǎn)成給HSFApiProviderBean茴丰,HSFApiProviderBean是來具體操作實(shí)現(xiàn)达皿;
- 當(dāng)執(zhí)行providerBean.publish()時(shí),會(huì)調(diào)用ProcessService的publish方法贿肩,具體實(shí)現(xiàn)類是ProcessComponent峦椰;
- 發(fā)布的具體流程就是ProcessComponent里:
- 第一步,調(diào)用rpcProtocolService來注冊發(fā)布RPC服務(wù)汰规,這個(gè)動(dòng)作是在server本地發(fā)布一個(gè)線程池汤功,每一個(gè)服務(wù)都會(huì)申請一個(gè)線程池,當(dāng)請求過來時(shí)從線程池獲取executor進(jìn)行執(zhí)行并返回溜哮;
- 第二步滔金,檢查單元化發(fā)布,就unitService在發(fā)布前檢查是中心發(fā)布還是單元發(fā)布茂嗓,對ServiceMetadata設(shè)置不同的發(fā)布路由餐茵;
- 第三步,通過metadataService將ServiceMetadata發(fā)布到ConfigServer上述吸;
- 第四步忿族,通過metadataInfoStoreService將ServiceMetadata保存到redis供服務(wù)治理或者其他用途。
服務(wù)注冊發(fā)布大致就是這么一個(gè)過程。
HSF的Client
現(xiàn)在來看看client是如何去調(diào)用服務(wù)的道批。
<bean id="hsfTestService" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean" init-method="init">
<property name="interfaceName" value="com.test.service.hsfTestService"/>
<property name="version" value="1.0.0.daily"/>
</bean>
上面一段配置文件相信在項(xiàng)目中肯定也非常常見错英,那么他是怎么運(yùn)作的呢?在spring注入的時(shí)候并沒有具體的實(shí)現(xiàn)類啊隆豹,只有一個(gè)接口椭岩?怎么實(shí)現(xiàn)調(diào)用的呢?
其實(shí)這是我一個(gè)好奇心的地方璃赡,我想去看個(gè)究竟判哥,hsf到底是用何種方式去實(shí)現(xiàn)的。
我們先來思考一個(gè)問題鉴吹,那就是沒有具體實(shí)現(xiàn)類姨伟,hsf是如何實(shí)現(xiàn)在spring中注冊服務(wù)的呢?答案就是動(dòng)態(tài)代理豆励,類似mybatis的方式夺荒,mybatis在寫dao層的時(shí)候只是寫了個(gè)接口,并沒有具體實(shí)現(xiàn)良蒸,hsf跟這種方式很相像技扼。
客戶端分兩部分來講解:服務(wù)的訂閱和被推送,服務(wù)的調(diào)用嫩痰。
服務(wù)的訂閱和被推送
先來看類圖:
一樣我們通過類圖來看服務(wù)的訂閱和接收過程:
服務(wù)初始化剿吻,首先需要引入服務(wù)接口相關(guān)的pom,然后寫配置文件串纺;
-
將需要被調(diào)用的服務(wù)注冊成spring bean丽旅,即上面配置文件中的內(nèi)容。
這里用到了動(dòng)態(tài)代理纺棺,通過類圖我們可以看到HSFSpringConsumerBean實(shí)現(xiàn)了FactoryBean榄笙;
FactoryBean:是一個(gè)Java Bean,但是它是一個(gè)能生產(chǎn)對象的工廠Bean祷蝌,通過getObject方法返回具體的bean茅撞,在spring bean實(shí)例化bean的過程中會(huì)去判斷是不是FactoryBean,如果不是就返回bean巨朦,否則返回FactoryBean生產(chǎn)的bean米丘,具體同學(xué)們可以去看AbstractBeanFactory的doGetBean方法,里面會(huì)調(diào)用getObjectForBeanInstance方法糊啡,這個(gè)方法里有具體實(shí)現(xiàn)拄查;
-
HSFSpringConsumerBean實(shí)現(xiàn)了FactoryBean,那么getObject方法具體返回了什么呢棚蓄?怎么返回的呢靶累?
@Override public Object getObject() throws Exception { return consumerBean.getObject(); }
從代碼看得出是調(diào)用了consumerBean(HSFApiConsumerBean)的getObject方法返回的腺毫,那么我們再來看getObject方法:
public Object getObject() throws Exception { return metadata.getTarget(); }
這個(gè)方法返回的是metadata(ServiceMetadata)的target癣疟,那么target是怎么獲取的呢挣柬?下面重點(diǎn)說明;
-
HSFSpringConsumerBean的init方法調(diào)用了consumerBean(HSFApiConsumerBean)的init方法睛挚,我們來看consumerBean里init方法的某一段代碼:
ProcessService processService = HSFServiceContainer.getInstance(ProcessService.class); try { metadata.setTarget(processService.consume(metadata)); LOGGER.warn("成功生成對接口為[" + metadata.getInterfaceName() + "]版本為[" + metadata.getVersion() + "]的HSF服務(wù)調(diào)用的代理邪蛔!"); } catch (Exception e) { LOGGER.error("", "生成對接口為[" + metadata.getInterfaceName() + "]版本為[" + metadata.getVersion() + "]的HSF服務(wù)調(diào)用的代理失敗", e); // since 2007,一旦初始化異常就拋出 throw e; } int waitTime = metadata.getMaxWaitTimeForCsAddress(); if (waitTime > 0) { try { metadata.getCsAddressCountDownLatch().await(waitTime, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // ignore } }
這一段代碼包含了動(dòng)態(tài)代理對象的具體生成和服務(wù)訂閱以及服務(wù)信息接收扎狱;
先說了一下代碼邏輯侧到,服務(wù)的訂閱和服務(wù)信息的接收(被推送)在processService中執(zhí)行,動(dòng)態(tài)代理對象在processService中生成淤击,下面的wait我推測是用來等目標(biāo)服務(wù)信息的推送(當(dāng)收到訂閱的目標(biāo)具體服務(wù)實(shí)現(xiàn)匠抗,接下來的調(diào)用過程才能走通);
-
看來processService是一個(gè)很重要的組件污抬,這邊通過processService.consume(metadata)這樣的方法調(diào)用實(shí)現(xiàn)了那么多步驟汞贸,target也在這里面生成,說一下這個(gè)方法內(nèi)的邏輯:
首先去緩存中找是否之前target有生成印机,有就返回矢腻;
沒有就通過java Proxy生成對象;
訂閱服務(wù)信息(返回的可調(diào)用地址)射赛;
保存客戶端metadata到redis多柑,返回target。
target.png
到此為止楣责,服務(wù)代理對象的生成竣灌,服務(wù)的訂閱都完成了,接下來看看服務(wù)的調(diào)用秆麸。
服務(wù)的調(diào)用
其實(shí)通過上面兩個(gè)部分整個(gè)框架已經(jīng)定好了初嘹,服務(wù)信息已經(jīng)注冊發(fā)布,客戶端也獲取到了服務(wù)的調(diào)用地址蛔屹,接下去就是調(diào)用就行削樊,調(diào)用呢就是真正的rpc請求了,hsf的rpc是通過netty實(shí)現(xiàn)的兔毒。
直接上類圖:
之前說了動(dòng)態(tài)代理漫贞,那么在方法執(zhí)行時(shí)就行進(jìn)入代理類執(zhí)行,執(zhí)行HSFServiceProxy的invoke方法育叁,invoke方法會(huì)調(diào)用trueInvoke方法:
在trueInvoke里調(diào)用RPCProtocolTemplateService迅脐,在這里封裝HSFRequest,執(zhí)行具體的invoke方法豪嗽;
具體的invoke方法調(diào)用RPCProtocolService谴蔑,在這里主要是根據(jù)invokeType來確定具體的InvokeService實(shí)現(xiàn)豌骏,最基本的我們知道hsf服務(wù)有同步調(diào)用和異步調(diào)用,具體實(shí)現(xiàn)就在這里隐锭;
最后在具體的實(shí)現(xiàn)類的獲取NettyClient窃躲,跟server進(jìn)行通信,返回HSFResponse钦睡。
簡單說下服務(wù)端的流程:
服務(wù)端會(huì)啟動(dòng)nettyServer蒂窒,具體由NettyServerHandler來處理所有rpc請求;
NettyServerHandler會(huì)根據(jù)HSFRequest找到具體的handler荞怒,這邊是RPCServerHandler洒琢,除此之外還有心跳啊等等handler;
通過handler獲取具體執(zhí)行的executor(這個(gè)在之前服務(wù)注冊那邊有講褐桌,每個(gè)服務(wù)本地會(huì)申請線程池衰抑,threadpoolexecutor);
new一個(gè)HandlerRunnable放進(jìn)executor執(zhí)行executor.execute(new HandlerRunnable);
最終在handler里調(diào)用ProviderProcessor,ProviderProcessor會(huì)找到具體的服務(wù)實(shí)現(xiàn)類并執(zhí)行荧嵌,將執(zhí)行結(jié)果封裝成HSFResponse呛踊,向client返回HSFResponse。
寫在最后
我在這里講得更多的是主鏈路完丽,里面有很多具體的細(xì)節(jié)比如路由恋技、鷹眼追蹤、日志逻族、負(fù)載等等沒有展開講蜻底,其實(shí)每個(gè)點(diǎn)拿出來都可以寫一篇文章,可能對于hsf的開發(fā)同學(xué)來說聘鳞,每一個(gè)點(diǎn)都會(huì)有一個(gè)很好玩的故事薄辅,那么關(guān)于HSF就先講到這里。