RPC是個老概念,五花八門的實(shí)現(xiàn)非常多垦江。在14年我剛轉(zhuǎn)到基礎(chǔ)架構(gòu)部時,其實(shí)是不想做RPC框架的。我的想法可能和很多工程師一樣:之前做了那么多系統(tǒng)比吭,現(xiàn)在就讓我來搞個編程框架绽族?而且這能做出什么花頭?但事實(shí)很快證明我錯了衩藤,編程上的事真的需要實(shí)踐吧慢,否則看問題就很淺。像搞深度學(xué)習(xí)赏表,vgg rcnn gan嘴上可以說得不停检诗,但只要沒在真正嚴(yán)肅的項(xiàng)目中調(diào)過參數(shù),你就是門外漢瓢剿。
RPC的深度在于現(xiàn)代的互聯(lián)網(wǎng)公司中幾乎所有服務(wù)都是使用RPC的逢慌,大部分工程師和它打交道。如果你能看到其中的痛點(diǎn)间狂,提高了效率攻泼,那么整個公司的開發(fā)效率都會有明顯的提升。大家都是從學(xué)生時代過來的鉴象,心里清楚一個東西在正確的條件下正確運(yùn)行很容易忙菠,但要在所有情況下能正確運(yùn)行就非常困難。前兩天我修了個問題:brpc在fedora 26下一個weak function莫名其妙地沒有被tcmalloc中的對應(yīng)版本覆蓋纺弊,導(dǎo)致heap profiler啟用不了牛欢,ubuntu,centos下都是好的淆游。這種問題往往和系統(tǒng)或ld有關(guān)氢惋,要精確定位很麻煩,最后我找到了一個workaround稽犁。但這個事情耗了我?guī)讉€小時焰望,因?yàn)樾枰诤芏嘞到y(tǒng)上驗(yàn)證沒有regression。RPC里大量此類東西已亥,雖然麻煩但能提高用戶體驗(yàn)熊赖。那個問題其實(shí)和brpc對tcmalloc的支持方式有關(guān),brpc默認(rèn)不鏈接tcmalloc虑椎,但用戶在程序中鏈接tcmalloc后震鹉,我們希望cpu和heap profiler要自動開啟(這兩個功能依賴tcmalloc的API),同時用戶不用重編brpc捆姜。所以我們得在brpc中動態(tài)判定是否鏈接了tcmalloc传趾,這就沒那么容易了。對我們很麻煩泥技,但用戶的體驗(yàn)更好了浆兰,甚至用戶會覺得理所當(dāng)然。
知識是需要大量實(shí)踐的,你也許可以在正確的條件下用dlsym有效地覆蓋一個glibc中的函數(shù)簸呈,但你可能不知道dlsym在有多版本符號存在時可能無效榕订,或dlsym和一些庫合用時(比如用于展開棧的libunwind)會死鎖,或dlsym對靜態(tài)鏈接是無效的除非編譯加了-rdynamic蜕便。你也許可以基于一些上下文切換庫三下五除二搞出個libcoroutine劫恒,但你可能不知道的是JNI會檢查stack layout而不能使用自定義棧,或程序運(yùn)行在valgrind中需要注冊棧地址才不會報(bào)錯轿腺,或一個棧跑到另一個LWP上展開時會觸發(fā)gcc4以上版本的thread-local誤優(yōu)化两嘴。這些知識,成千上萬條這種知識族壳,通過實(shí)踐才會深深地刻畫在腦中溶诞,構(gòu)成一個工程師真正的競爭力。
我一直堅(jiān)信所有的用戶體驗(yàn)都是端到端的决侈,只有站在用戶的角度,把整個流程以既高效又不失擴(kuò)展性的方式走通喧务,才是最好的選擇赖歌。良好的文檔正是這種理念的體現(xiàn):給新用戶鋪好路能快速上手,讓老用戶知其所以然更上一層樓功茴。這種想法也體現(xiàn)在代碼中的方方面面:每個選項(xiàng)都有合理的默認(rèn)值庐冯,用戶不設(shè)也能用;在注釋中提示best practice坎穿,避免用戶走彎路展父;用戶界面、日志內(nèi)容不啰嗦玲昧,讓用戶一眼看清楚問題的全貌栖茉。不做并不意味著我們沒能力做,而是早已被事實(shí)證明可能出現(xiàn)非常subtle的bug而被淘汰掉的選擇孵延。知道的越多吕漂,你就越會有一種責(zé)任感,需要幫助用戶修一條好路尘应,避免陷到你已經(jīng)踩過的成百上千個坑中惶凝。
說到性能,RPC的性能評估其實(shí)很像VC投資初創(chuàng)公司:每家都在說自己的東西好犬钢,并能拿出數(shù)據(jù)苍鲜,可真的好不好天曉得。所以VC只能看團(tuán)隊(duì)玷犹,查背景混滔,憑感覺,這錢花出去了能不能拿回來心里都慌的很。RPC其實(shí)也這樣遍坟,每個實(shí)現(xiàn)都有大量獨(dú)特的設(shè)計(jì)和接口拳亿,用戶不太可能輕易地從一個RPC切換到另一個RPC,并在完全相同的環(huán)境下進(jìn)行對比愿伴。每個RPC實(shí)現(xiàn)都在說自己高性能肺魁,輕量級。這是個自賣自夸的游戲隔节,用戶只能看臉鹅经。但就像我們奇怪古人連那么簡單的東西都不知道一樣,人的認(rèn)知就是這樣怎诫,內(nèi)行的常識可能對外行非常困難瘾晃,甚至這個常識非常簡單。在很多年以前幻妓,我們對“高性能”的認(rèn)識還停留在“極限QPS”和“延時”兩個維度的時候蹦误,被一個復(fù)雜系統(tǒng)中的擁塞問題搞的焦頭爛額,大家就覺得莫名其妙啊肉津,每個環(huán)節(jié)都很快强胰,這延時怎么就嘩嘩嘩地漲上去了。最后在反反復(fù)復(fù)的思考和分析后才發(fā)現(xiàn)妹沙,QPS和延時的乘積與程序的最大服務(wù)能力緊密相連偶洋。我們搞了個概念叫volume,發(fā)現(xiàn)串行系統(tǒng)的volume可以相加距糖,并行系統(tǒng)的volume可以求min玄窝,然后一層層地迭代上去從而計(jì)算出復(fù)雜系統(tǒng)同時能處理的最大請求數(shù),并解決了擁塞問題悍引。
不過就是個乘法恩脂。
今天我們知道這個原理是little's law,tcp中的BDP也是類似的道理趣斤。我們在文檔中描述了相關(guān)的知識东亦。但即使是這樣,根據(jù)我們在百度內(nèi)的支持經(jīng)驗(yàn)(沒人會否認(rèn)百度研發(fā)的整體素質(zhì)吧)唬渗,大部分RPC的用戶對這個乘法理解還是有困難的典阵,更別提理解串行相加,并行求min镊逝,在系統(tǒng)設(shè)計(jì)中活學(xué)活用了壮啊。一個乘法尚且同此,更深入的可想而知撑蒜。普通用戶是很難看明白性能測試的道道的歹啼。我們團(tuán)隊(duì)里有個老梗:“處處是熱點(diǎn)玄渗,處處不是瓶頸”。這說的是如果整個程序?qū)懙亩己艽直├暄郏豢紤]性能藤树,最后用profiler一跑,發(fā)現(xiàn)每個點(diǎn)都只有1%拓萌,2%岁钓,然后得出結(jié)論,“性能非常好微王,優(yōu)化空間已經(jīng)不大”屡限。但實(shí)際上你去分析下hot path,會發(fā)現(xiàn)有太多可以大幅提高的點(diǎn)了炕倘。性能就是這樣钧大,設(shè)計(jì)確保了流程是最優(yōu)化的,但實(shí)現(xiàn)也非常重要罩旋,細(xì)節(jié)全靠摳啊央。brpc上關(guān)鍵路徑上的代碼多一次new都需要討論,最熱的路徑上甚至不允許出現(xiàn)申明一個可能無用的空std::string涨醋,因?yàn)槔习姹緂libc中的空string是要加引用計(jì)數(shù)的瓜饥,對cache有影響。
摳細(xì)節(jié)的背后需要工程師對性能的深入理解东帅。一個函數(shù)的性能是可以估算出來的,測試只是驗(yàn)證球拦。如果不符合預(yù)期靠闭,你就要深入地去看,最終理解背后的原因坎炼。為什么一次激烈的cacheline同步大約是700ns愧膀?或是一次調(diào)度延時至少是3us,99%以內(nèi)是20us谣光?或是linux下的timed condition有60us的延時檩淋?或是一次上下文切換可以在200ns內(nèi)做完?或是無競爭的mutex可以實(shí)現(xiàn)為兩條20ns左右wait-free的原子指令萄金?掌握了這些知識蟀悦,你才能抓大放小,把精力放在最關(guān)鍵的事情上氧敢,并把它做到世界上最好的水平日戈。
但即使到現(xiàn)在,brpc中仍然有一些極具挑戰(zhàn)性的問題孙乖,比如bthread的調(diào)度如何能更好地保持cache locality浙炼,如何在NUMA機(jī)器上跑得更好份氧,如何盡量消除內(nèi)核調(diào)度的延時,如何更高效率地重用棧...如此種種弯屈。我們把brpc開源出來蜗帜,正是為了讓感興趣的伙伴一起加入進(jìn)來,做出一個更上一層樓的RPC框架资厉。與大家共勉厅缺。