自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

高并發(fā)服務(wù)優(yōu)化篇:從RPC預(yù)熱轉(zhuǎn)發(fā)看服務(wù)端性能調(diào)優(yōu)

開發(fā) 架構(gòu)
本篇從RPC的預(yù)熱轉(zhuǎn)發(fā)功能,引出了其背后的理論依據(jù)--JIT優(yōu)化。闡述了JIT的基本概念,并用一個(gè)實(shí)例說明了代碼編寫風(fēng)格對(duì)JIT優(yōu)化的實(shí)際影響。

[[413783]]

之前的文章中,我們?cè)敿?xì)闡述了RPC的調(diào)用過程,分析了其耗時(shí)組成,為我們?nèi)粘P阅苷{(diào)優(yōu)提供了理論支持。

為了更好的體驗(yàn)和更優(yōu)的性能,其實(shí)RPC悄悄的做了很多工作,本篇就帶大家來看下RPC的一些高級(jí)特性和其背后的原因。(還是以開源的dubbo和sofa為例來說明)

Part1 RPC為了性能做了哪些努力

1.1 Provider分組和直連

路由尋址,負(fù)載均衡是很好,可以保證流量均勻從而保護(hù)服務(wù)節(jié)點(diǎn)穩(wěn)定。

但是,我們有的時(shí)候其實(shí)不希望我們的請(qǐng)求亂跑,最好能打到指定的機(jī)器上。比如聯(lián)調(diào)和測(cè)試的時(shí)候,直連功能就顯得很重要了。

只有經(jīng)歷過多方合作聯(lián)調(diào)時(shí)請(qǐng)求到處亂跑的痛,才知道分組和直連的功能對(duì)開發(fā)是多么的友好。

  1. //以sofa為例 
  2. @Extension(value = "directUrl"order = -20000) 
  3. @AutoActive(consumerSide = true
  4. public class DirectUrlRouter extends Router { 
  5.   //... 

我們可以看到直連路由策略的order屬性,被賦予了一個(gè)極小的值,變成了優(yōu)先級(jí)最高的路由策略,所以只要配置的直連列表,則會(huì)優(yōu)先走配置中的列表地址。

摘自:www.sofastack.tech

1.2 異步調(diào)用

Future異步調(diào)用

異步調(diào)用對(duì)服務(wù)性能和并發(fā)的支持起到很大的作用。

一般異步調(diào)用有Futurn和callback等方式,這里我們說下Future的原理:

調(diào)用下游之后,先返回一個(gè)Future,上游通過Future.get()方法對(duì)結(jié)果進(jìn)行獲取,如果結(jié)果未返回則會(huì)讓出CPU資源進(jìn)入等待,直到結(jié)果到達(dá)或超時(shí)后觸發(fā)回調(diào)方法才被喚醒。由于篇幅問題,F(xiàn)uture的核心邏輯的相關(guān)注釋就不放了,之前的消息消費(fèi)順序保障的文章中也有敘述,有興趣的同學(xué)可以看下~

1.3 本地優(yōu)先、遠(yuǎn)程優(yōu)先

很多時(shí)候,我們會(huì)遇到消費(fèi)端和服務(wù)端可能都是自己的情況。這個(gè)時(shí)候,在常規(guī)的路由尋址之外,又提供給我們一種調(diào)用的可能性,就是直接調(diào)用當(dāng)前服務(wù)器上的程序,這樣做的好處比較明顯,省去了網(wǎng)絡(luò)傳輸?shù)葧r(shí)間損耗,效率更高。

  1. List<ProviderInfo> localProviderInfo = new ArrayList<ProviderInfo>(); 
  2. // 解析IP,看是否和本地一致 
  3. for (ProviderInfo providerInfo : providerInfos) {  
  4.     if (localhost.equals(providerInfo.getHost())) { 
  5.         localProviderInfo.add(providerInfo); 
  6.     } 
  7. // 命中本機(jī)的服務(wù)端 
  8. if (CommonUtils.isNotEmpty(localProviderInfo)) {  
  9.     return super.doSelect(invocation, localProviderInfo); 
  10. else {  
  11.   // 沒有命中本機(jī)上的服務(wù)端 
  12.    return super.doSelect(invocation, providerInfos); 

當(dāng)然,也需要看業(yè)務(wù)和內(nèi)部服務(wù)路由的實(shí)際情況,比如在阿里的單元化部署下,需要根據(jù)用戶ID路由到對(duì)應(yīng)的zone進(jìn)行處理,如果還是優(yōu)先本機(jī),那就可能在操作數(shù)據(jù)庫的時(shí)候涉及到跨zone調(diào)用,比走遠(yuǎn)程rpc更加耗時(shí)。因此這種情況下就需要禁用本機(jī)優(yōu)先策略。

1.4 延遲暴露

很多時(shí)候,我們的服務(wù)需要依賴一些其他內(nèi)容才可以正常提供服務(wù),比如緩存預(yù)熱、線程池預(yù)熱等等,所以,在服務(wù)真正就緒之后再注冊(cè)到配置中心是很有必要的。

  1. //服務(wù)注冊(cè)之前,先延遲 
  2. public void export() { 
  3.     // 根據(jù)配置延遲加載 
  4.     if (providerConfig.getDelay() > 0) {  
  5.         Thread thread = factory.newThread(new Runnable() { 
  6.          @Override 
  7.          public void run() { 
  8.              try { 
  9.                   Thread.sleep(providerConfig.getDelay()); 
  10.              } catch (Throwable ignore) {  
  11.              } 
  12.               //真正的服務(wù)注冊(cè)邏輯 
  13.               doExport(); 
  14.          } 
  15.       }); 
  16.       thread.start(); 
  17.    } else { 
  18.        doExport(); 
  19.    } 

1.5 粘滯連接

問: 我們需要每次都進(jìn)行路由尋址和負(fù)載均衡來確定服務(wù)地址么?

答: 大部分情況是有利的,不過有些特殊的場(chǎng)景,更希望多次請(qǐng)求連接到同一臺(tái)服務(wù)器。

比如,有狀態(tài)的服務(wù)(很多帶數(shù)據(jù)功能的服務(wù)都是有狀態(tài)的,比如很久之前的帶登陸session的Tomcat服務(wù)、存儲(chǔ)集群服務(wù)等),其實(shí)希望每次請(qǐng)求都連接到相同的服務(wù)器。

這就用到了粘滯連接功能。

  1. protected ProviderInfo select(...)throws SofaRpcException { 
  2.     // 判斷isSticky 粘滯連接配置 
  3.     if (consumerConfig.isSticky()) { 
  4.         //如果最后一次使用的provider不為空,則使用 
  5.         if (lastProviderInfo != null) { 
  6.             ProviderInfo providerInfo = lastProviderInfo;         
  7.             //獲取對(duì)應(yīng)連接 
  8.             ClientTransport lastTransport = connectionHolder.getAvailableClientTransport(providerInfo); 
  9.             if (lastTransport != null && lastTransport.isAvailable()) { 
  10.                checkAlias(providerInfo, message); 
  11.                return providerInfo; 
  12.             } 
  13.         } 
  14.     } 
  15.     ... 

1.6 預(yù)熱轉(zhuǎn)發(fā)

前面扯了那么多,其實(shí),這個(gè)才是我們今天想說的重點(diǎn)。

預(yù)熱轉(zhuǎn)發(fā)是針對(duì)服務(wù)節(jié)點(diǎn)的負(fù)載均衡來說的。因?yàn)樵诜?wù)剛啟動(dòng)的時(shí)候,如果請(qǐng)求過多可能會(huì)影響機(jī)器性能和正常業(yè)務(wù),如果將處于預(yù)熱期的機(jī)器的請(qǐng)求轉(zhuǎn)發(fā)到集群內(nèi)其它機(jī)器,過了預(yù)熱期之后再恢復(fù)正常,則可以保證服務(wù)節(jié)點(diǎn)的性能和服務(wù)整體的可用性。

那么這個(gè)功能是怎么實(shí)現(xiàn)的呢?--帶權(quán)重的隨機(jī)負(fù)載均衡。

摘自sofastack:權(quán)重隨機(jī)的原理

  1.  //累加總權(quán)重totalWeight,代碼忽略。。。 
  2.   
  3.  //在總權(quán)重內(nèi)隨機(jī)得到一個(gè)值 
  4.  int offset = random.nextInt(totalWeight); 
  5.   
  6.  //確定隨機(jī)值落在哪個(gè)片斷上 
  7.  for (int i = 0; i < size; i++) { 
  8.      offset -= getWeight(providerInfos.get(i)); 
  9.      if (offset < 0) { 
  10.         providerInfo = providerInfos.get(i); 
  11.         break; 
  12.      } 

配置示例:

  1. core_proxy_url=weightStarting:0.2,during:60,weightStarted:0.2,address:x.x.x.x,uniqueId:core_unique 

如上,預(yù)熱權(quán)重20%,預(yù)熱持續(xù)時(shí)長(zhǎng)60s。這樣,按照上述計(jì)算方式,權(quán)重小的服務(wù)節(jié)點(diǎn)被選到的幾率就相對(duì)小,以此達(dá)到權(quán)重隨機(jī)的效果。

那么,為什么剛發(fā)布的服務(wù)需要預(yù)熱呢?預(yù)熱可以起到什么作用呢?

Part2 什么是JIT優(yōu)化

都說C++快,Java慢,都是高級(jí)語言,是什么導(dǎo)致了運(yùn)行速度的差別呢?

這個(gè)涉及到了兩種執(zhí)行方式:解釋執(zhí)行 和 編譯執(zhí)行。

相對(duì)于C++直接將代碼編譯成機(jī)器碼運(yùn)行的方式,Java為了實(shí)現(xiàn)跨平臺(tái)、高度抽象等特性,增加了虛擬機(jī)層來實(shí)現(xiàn)Java代碼到機(jī)器碼的轉(zhuǎn)換,Java程序先是被編譯成符合虛擬機(jī)規(guī)范的.class字節(jié)碼逐條將字節(jié)碼翻譯成機(jī)器碼然后執(zhí)行,所以,速度上就慢一些。

雖然,JVM的加入,給Java的運(yùn)行速度增加了不少損耗,但是好處也很多,除了跨平臺(tái),還為我們實(shí)現(xiàn)了諸如內(nèi)存管理、垃圾回收等容器級(jí)通用功能,讓研發(fā)人員可以更加聚焦業(yè)務(wù)。

不過,Java也是要面子的,我允許自己慢,但我不允許自己慢那么多!

怎么辦呢?遵循二八原則,是不是可以找尋程序當(dāng)中的貢獻(xiàn)了大部分調(diào)用量的核心代碼,把這部分編譯成機(jī)器碼,提升其速度,不就把整體的速度提上去了么,JVM也是這么做的~

所以,JVM兼容了解釋執(zhí)行和編譯執(zhí)行兩種方式,也就是我們常說的即時(shí)編譯。

前面的問題到這里其實(shí)就可以回答了。為什么需要預(yù)熱轉(zhuǎn)發(fā)呢?是為了用小流量對(duì)程序進(jìn)行預(yù)熱,目的是為了讓核心代碼進(jìn)行及時(shí)編譯,提高峰值運(yùn)行速率,提升服務(wù)響應(yīng)~

下面讓我們?cè)敿?xì)看下JIT。

2.1 即時(shí)編譯器

為了權(quán)衡編譯時(shí)間和執(zhí)行效率,JVM設(shè)置了多種即時(shí)編譯器:

  • C1(Client 編譯器):基于字節(jié)碼完成部分優(yōu)化,如方法內(nèi)聯(lián)、常量傳遞,相對(duì)于C2,速度快,但性能稍差。
  • C2(Server 編譯器):耗時(shí)較長(zhǎng)的全局優(yōu)化,如無用代碼消除、重排序、循環(huán)展開、公共子表達(dá)式替代、常量傳播等等。
  • Graal(新的JIT編譯器):側(cè)重于性能和語言操作性。在一些負(fù)載上提供比傳統(tǒng)編譯器更好的峰值性能;用 Graal 執(zhí)行的語言可以互相調(diào)用,可以使用來自其他語言的庫。

2.2 JIT優(yōu)化觸發(fā)條件

前面我們說過,JVM其實(shí)是希望找到承擔(dān)更多調(diào)用請(qǐng)求的代碼塊進(jìn)行優(yōu)化,那,怎么來確認(rèn)哪些代碼時(shí)優(yōu)化目標(biāo)呢?--熱點(diǎn)探測(cè)

基于采樣的熱點(diǎn)探測(cè):

周期采樣,檢測(cè)各線程棧頂方法,經(jīng)常出現(xiàn)的方法即為熱點(diǎn)方法。好處是簡(jiǎn)單高效,缺點(diǎn)是不精確,容易受線程運(yùn)行狀態(tài)的影響。

基于計(jì)數(shù)的熱點(diǎn)探測(cè):

(包括方法調(diào)用計(jì)數(shù)器和回邊計(jì)數(shù)器)每個(gè)方法建立計(jì)數(shù)器,用來統(tǒng)計(jì)調(diào)用次數(shù)。如果該方法執(zhí)行次數(shù)超過閾值,則該方法被認(rèn)定為熱點(diǎn)方法。好處是足夠精確。缺點(diǎn)是空間損耗大,且實(shí)現(xiàn)較難。

另外,可以通過如XX:CompileThreshold等參數(shù)來修改閾值,不過,沒有絕對(duì)把握,還是不要?jiǎng)訛楹谩?/p>

Part3 JIT指導(dǎo)代碼優(yōu)化

3.1 方法內(nèi)聯(lián)

為什么我們?cè)趧倢懘a的時(shí)候,總是被建議不要寫很大的方法體?方法內(nèi)聯(lián)的JIT優(yōu)化策略就是其中一個(gè)重要的原因。(還有GC友好等原因)

JVM內(nèi)的每一次方法調(diào)用,都是棧幀在內(nèi)存中出棧入棧的過程,方法多了性能損耗自然大,所以要進(jìn)行方法內(nèi)聯(lián),即把方法執(zhí)行邏輯直接復(fù)制到調(diào)用方內(nèi)部,避免方法調(diào)用。

但是,方法內(nèi)聯(lián)是有方法大小限制的,超過了一定大小的方法,沒法做內(nèi)聯(lián)優(yōu)化。所以,平常應(yīng)該注意,盡量避免寫很大很冗長(zhǎng)的方法。

讓我們來舉個(gè)栗子實(shí)際感受一下~

兩種書寫風(fēng)格的大數(shù)相加。

如上圖所示,兩個(gè)字符串型整數(shù)相加,都能實(shí)現(xiàn)功能,前一種寫法,把中間過程全都拆開,羅列在的方法內(nèi),整個(gè)方法雖然理解起來稍微方便些,但整體顯得冗長(zhǎng);第二種方法,把各個(gè)條件都囊括在了for循環(huán)條件內(nèi),三行代碼完成整體操作。

如果要去評(píng)價(jià),我覺得大部分人都會(huì)說第二種寫的好,但是,第二種的好難道真的局限于優(yōu)雅么?

  1. //添加JVM啟動(dòng)參數(shù),用于打印代碼執(zhí)行過程中的編譯詳情 
  2. //-XX:+PrintCompilation 
  3. String num1 = "12345"
  4. String num2 = "23456"
  5. //循環(huán)15000次,因?yàn)?.8分層編譯下,各層閾值不一樣,我們?nèi)∽畲箝撝?nbsp;
  6. for (int i=0;i<15001;i++) { 
  7.     rejectionLB1.stringAdd(num1, num2); 
  8.     //rejectionLB1.stringAdd2(num1, num2); 
  9.  } 

執(zhí)行15000次寫法1

(圖中編譯層次這一列中,3代表C1編譯,4代表C2編譯)

我們看到,隨著代碼的執(zhí)行次數(shù)的增加,一些方法,進(jìn)行了C1編譯,如我們的主方法stringAdd,而少數(shù)方法,從C1編譯提升到了C2編譯,如AbstractStringBuilder::append方法。

執(zhí)行15000次寫法2

我們看到了什么,stringAdd2 居然在進(jìn)行到運(yùn)行后期執(zhí)行了C2編譯,而且很明顯,方法二的C2編譯的方法,比方法一要多不少。所以,平常寫代碼該注意些什么,是不是顯而易見了。。。

3.2 其他優(yōu)化

方法內(nèi)聯(lián)雖然只是一種簡(jiǎn)單優(yōu)化,但是,是后續(xù)其他優(yōu)化的基石。

而JVM的分層優(yōu)化涉及的點(diǎn)非常多[1]:

局部?jī)?yōu)化:關(guān)注局部數(shù)據(jù)流分析,數(shù)組越界檢查消除;寄存器優(yōu)化,優(yōu)化跳轉(zhuǎn)、循環(huán)、異常處理等;代碼簡(jiǎn)化,如公共表達(dá)式提取等等等。

控制流優(yōu)化:專注于代碼重排序、循環(huán)縮減、循環(huán)展開、異常定位優(yōu)化等等等。

全局優(yōu)化:主要關(guān)注冗余消除,如方法調(diào)用、鎖;逃逸分析;GC和內(nèi)存分配優(yōu)化等等等。

Part4 總結(jié)

本篇從RPC的預(yù)熱轉(zhuǎn)發(fā)功能,引出了其背后的理論依據(jù)--JIT優(yōu)化。闡述了JIT的基本概念,并用一個(gè)實(shí)例說明了代碼編寫風(fēng)格對(duì)JIT優(yōu)化的實(shí)際影響。

JIT相關(guān)的優(yōu)化實(shí)現(xiàn)起來非常難,不過其原理和作用對(duì)我們普通研發(fā)也不是特別難理解,學(xué)習(xí)JIT優(yōu)化的目的,在于了解JVM底層的運(yùn)行邏輯和實(shí)現(xiàn),讓我們可以更加信任托管,聚焦業(yè)務(wù)邏輯,同時(shí)在編寫代碼時(shí),盡量用JVM友好的方式進(jìn)行,從而達(dá)到更好看、更高效的目的。

本文轉(zhuǎn)載自微信公眾號(hào)「Coder的技術(shù)之路」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Coder的技術(shù)之路公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: Coder的技術(shù)之路
相關(guān)推薦

2017-11-27 14:58:01

MySQL高并發(fā)優(yōu)化性能調(diào)優(yōu)

2020-10-16 16:40:26

Linux高并發(fā)命令

2018-07-18 12:12:20

Spark大數(shù)據(jù)代碼

2020-08-18 13:50:04

Tomcat高并發(fā)Java

2023-08-16 11:39:19

高并發(fā)調(diào)優(yōu)

2019-09-25 09:01:53

高并發(fā)架構(gòu)分布式

2019-12-17 11:18:37

高并發(fā)分布式架構(gòu)

2020-02-10 19:16:52

服務(wù)端高并發(fā)架構(gòu)

2021-03-04 08:39:21

SparkRDD調(diào)優(yōu)

2020-09-03 14:30:40

Tomcat 拆解調(diào)優(yōu)

2021-01-13 05:27:02

服務(wù)器性能高并發(fā)

2022-04-03 19:51:38

linux服務(wù)性能

2022-09-14 22:58:58

Push 推薦Java 開發(fā)vivo

2022-05-17 11:46:48

高并發(fā)服務(wù)數(shù)據(jù)庫

2020-06-15 08:13:42

Linux服務(wù)端并發(fā)數(shù)

2020-06-15 08:25:35

Linux 系統(tǒng) 數(shù)據(jù)

2019-06-28 10:55:04

預(yù)熱高并發(fā)并發(fā)高

2012-03-09 09:51:35

2012-06-01 09:54:03

2021-07-07 14:20:15

高并發(fā)服務(wù)數(shù)據(jù)庫
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)