Cacheline
1.背景:cpu的L1和L2級cache為每個核獨有押搪,cpu的L3cache為所有核心共享
2.原因:核寫入自己的L1級cache是極快的树酪,但當另一核讀寫同一處內(nèi)存時,由于每個核有l(wèi)ocal cache大州,需要進行一致性同步续语,確保內(nèi)存和所有l(wèi)ocal cache的數(shù)據(jù)是一致的,這個復(fù)雜的硬件算法使得原子操作變得很慢
3.例子:所有線程頻繁修改一個計數(shù)器厦画,性能就會很差疮茄,讓每個線程修改獨立的thread_local變量,在需要時再同步
4.解決方法:避免共享力试,變量按訪問規(guī)律排列,頻繁修改的線程要放在獨立的cacheline中
Memory fence
1.背景:cpu和編譯器重排指令導(dǎo)致讀寫順序的變化
Non-blocking wait-free& lock-free
1.Non-blocking:一個線程的失敗或掛起不會導(dǎo)致其他線程的失敗或掛起排嫌。 通過Non-blocking實現(xiàn)高并發(fā)
2.Non-blocking算法如果保證每個線程始終在做有用的事畸裳,那么就是wait-free,如果保證至少有一個線程在做有用的事淳地,那就是lock-free
3.wait-free&lock-free并不保證高性能怖糊,因為需要處理更多更復(fù)雜的race condition和ABA problem
4.mutex:鎖導(dǎo)致低性能的原因是臨界區(qū)過大,或者上下文切換頻繁
5.使用鏈表記錄 處理多線程寫的問題颇象,通過swap(head)的方式伍伤,競爭寫權(quán)限,如果返回null遣钳,則成功扰魂,否則swap(head)->next 。如果數(shù)據(jù)量較大,會啟動keep Write寫線程批量寫出
Socket
1.Create/Address/SetFailed
2.Socket類似shared_ptr劝评,SocketId類似于weak_ptr姐直,SetFailed可以再需要時確保Socket不能被繼續(xù)Address而最終引用計數(shù)歸0.
3.存儲SocketUniquePtr還是SocketId取決于是否需要強引用。如果需要大量數(shù)據(jù)交互蒋畜,存放的是SocketUniquePtr简肴。epoll主要提醒對應(yīng)fd上發(fā)生了事件,如果socket回收了百侧,那這個事件可有可無,所以存放SocketId
自適應(yīng)限流
1.背景:服務(wù)能力有上限能扒,請求速度超過處理速度佣渴,服務(wù)會過載。通過設(shè)置最大并發(fā)能直接拒絕一部分請求初斑。
2.原因:當服務(wù)數(shù)量多辛润,拓撲復(fù)雜,且處理能力會逐漸變化的局面下见秤,固定最大并發(fā)數(shù)帶了了巨大測試工作量
3.Little' Law: 服務(wù)穩(wěn)定時:concurrency = qps*latency砂竖, noload_latency 單純?nèi)蝿?wù)處理的延時,peak_qps是處理或回復(fù)的qps
3.解決方案:besk_max_concurrrency = noload_latency * peak_qps鹃答。 自適應(yīng)限流就是要找到服務(wù)的noload_latency 和 peak_qps乎澄,并將最大并發(fā)設(shè)置為靠近兩者乘積的一個值
max_concurrency = max_qps * ((2+alpha) * min_latency - lantency)。 不斷對請求進行采樣测摔,通過窗口采樣獲得樣本的平均延遲和當前qps置济, 計算出下一個窗口的max_concurrency。 通過定期降低max_concurrency
為避免流量損失太多的情況 1.請求數(shù)量足夠多時锋八,直接提交當前采樣窗口 2.計算公式方面浙于,當current_qps > max_qps時,直接進行更新挟纱,不進行平滑處理羞酗。 2秒左右將qps打滿
雪崩
1.解決方法:1)設(shè)置合理的max_concurrency,最大qps*非擁塞時的延時來評估最大并發(fā)紊服。2)注意重試設(shè)置檀轨。只有當連接錯誤時重試,而不是連接超時時重試围苫。如果需要連接時重試裤园,可以設(shè)置backup request,這種重試只有一次剂府,放大程度降低到最低拧揽。3)brpc默認在bthread中處理請求,個數(shù)是軟件限制
backup request
1.設(shè)置backup_request_ms,先發(fā)送請求淤袜,經(jīng)過backup_request_ms后再發(fā)送一次請求痒谴,之后誰先返回取哪一個
bthread
單核reator,libevent等為典型铡羡,實質(zhì)是把多段邏輯按事件觸發(fā)順序交織在一個系統(tǒng)線程中
N:1 線程庫积蔚,所有協(xié)程運行于一個系統(tǒng)線程中,計算能力與各種eventloop庫等價烦周。 由于不跨線程尽爆,可以高效的利用 local cache, 無上下文切換代價读慎,但不能高效的利用多核漱贱,代碼必須非阻塞。 更適合寫運行時間確定的IO服務(wù)器夭委,典型如http server
多核reator幅狮,以boost::asio為典型,一般由一個或者多個線程分別運行event dispatcher株灸,待事情發(fā)生后把event_handler交給一個worker線程崇摄。由于cache一致性,未必能獲取線性與核心數(shù)的性能慌烧。
M:N 線程庫逐抑,用戶態(tài)的實現(xiàn)以go為主。一個bthread被卡住不會影響其他bthread杏死,關(guān)鍵技術(shù)兩點: work stealing調(diào)度和butex
lambda是一種語法糖泵肄,可以讓異步編程沒那么麻煩,但無法降低多線程編碼難度
bvar
多線程環(huán)境下的計數(shù)器類庫淑翼,方便記錄和查看用戶程序中各類數(shù)值腐巢,利用local cache減少了cache bouncing。本質(zhì)是將寫時的競爭轉(zhuǎn)移到了讀玄括,讀得合并所有寫過的線程數(shù)據(jù)冯丙,不可避免的慢了
適合: 寫多讀少
不適合:基于最新值做邏輯判斷,讀多寫多
方式:定期把監(jiān)控項目打入固定目錄下的文件
內(nèi)存管理
內(nèi)存管理都存在如下兩點權(quán)衡
1.線程間競爭小遭京,resource pool通過批量發(fā)送和歸還避免全局競爭胃惜,并降低單次的開銷
2.浪費的空間少,需要共享內(nèi)存
多線程框架廣泛地通過傳遞對象的ownership來讓問題異步化哪雕,如何讓分配這些小對象的開銷變得更小是值得研究的問題:大多數(shù)結(jié)構(gòu)是等長的
對象可以被歸還船殉,但歸還后并沒有被刪除,也沒有被析構(gòu)斯嚎,而是僅僅進入freelist
一個pool中所有的bthread的棧必須一樣大
ABA問題
A->B->A 通過加入版本號解決
A1->B2->A3
id通過 (32位偏移量 + 32位版本)決定 利虫,偏移量相當于指針
DoublyBufferData
背景:讀遠多于寫的數(shù)據(jù)結(jié)構(gòu)挨厚。一個方法是雙緩沖,實現(xiàn)無鎖查找1)數(shù)據(jù)分前臺和后臺2)檢索線程只讀前臺糠惫,不用加鎖3)另一個寫線程:修改后臺數(shù)據(jù)疫剃,切換前后臺,睡眠一段時間硼讽,以確保老前臺不再被檢索線程訪問
解決方案:讀拿一把thread-local鎖巢价,寫需要拿所有的thread-local鎖。
1.數(shù)據(jù)分前臺和后臺
2.讀拿到自己所在線程的thread-local鎖固阁,執(zhí)行查詢邏輯后釋放
3.同時只有一個寫壤躲,修改后臺數(shù)據(jù),切換前后臺备燃,挨個獲得所有thread-local鎖并立刻釋放柒爵,結(jié)束后再改一遍新后臺(保證之前的讀線程所在的thread-local已結(jié)束,即所有讀線程都看到了新前臺)
健康檢查
只要一個節(jié)點不從NamingService刪除赚爵,它要么是正常的,要么在做健康檢查
cpu profile
在定期被調(diào)用的SIGPROF handler中采用所在線程的棧法瑟,cpu profiler在運行一段時間后能以很大概率采集到所有活躍線程中的活躍函數(shù)冀膝,最后根據(jù)棧代表的函數(shù)調(diào)用關(guān)系匯總為調(diào)用圖,并把地址轉(zhuǎn)為符號
heap profiler
每分配滿一些內(nèi)存就采樣被調(diào)用處的棧