Jaeger tchannel-go原始碼閱讀——Peer
peer
peer 中文翻譯為:對等體, 它在 tchannel-go 中表示 rpc 呼叫或者被呼叫的一方。
peer 相關錯誤碼:
ErrInvalidConnectionState // 連線處於無效狀態
ErrNoPeers // 沒有可用的peer
ErrPeerNoFound // 沒有發現peer
ErrNoNewPeers // 沒有新的可用peer
相關 peer 程式碼:
// peer使用該interface,建立connection。peer所擁有的channel實現了該interface
type Connectable interface {
Connect(ctx context.Context, hostPort string) (*Connection, error)
Logger() Logger
}
// channel擁有所有建立連線的Peers
type PeerList struct {
sync.RWMutex
// parent:後續補充
parent *RootPeerList
// channel所有連線,對於每個遠端hostPort所對應的peer
peersByHostPort map[string]*peerScore
// peerHeap: 後續補充, 它基於peers的score,來維護peers得最小heap
peerHeap *peerHeap
// channel所有連線的peers score計算
scoreCalculator ScoreCalculator
// lastSelected: 後續補充
lastSelected uint64
}
// 建立PeerList,並初始化。RootPeerList後續補充
func newPeerList(root *RootPeerList) *PeerList {
reutrn &PeerList{
parent: root,
peersByHostPort: make(map[string]*peerScore),
scoreCalculator: newPreferIncomingCalculator(),
peerHeap: newPeerHeap(),
}
}
// 設定channel所有連線peers的score計算方式
// 最後並重新計算當前channel下所有peers的score
func (l *PeerList) SetStrategy(sc ScoreCalculator) {
l.Lock()
defer l.Unlock()
l.scoreCalculator = sc
for _, ps := range l.peersByHostPort {
newScore := l.scoreCalculator.GetScore(ps.Peer)
// 更新peer的score
l.updatePeer(ps, newScore)
}
}
// 根據peerList建立一個它的兄弟PeerList。
func (l *PeerList) newSibling() *PeerList {
sib := newPeerList(l.parent)
return sib
}
// 在channel的PeerList中增加一個peer, 這個方法引出了後記
func (l *PeerList) Add(hostPort string) *Peer {
// 如果hostPort已存在,則不需要增加peer
if ps, ok := l.exists(hostPort); ok {
return ps.Peer
}
l.Lock()
defer l.Unlock()
// 再次校驗是否已經存在peer
if p, ok := l.peersByHostPort[hostPort]; ok {
return p.Peer
}
// 確定不存在peer,建立peer, 增加RootPeerList的peersByHostPort對映關係
p := l.parent.Add(hostPort)
// 增加subchannel的引用計數
p.addSC()
// 根據peer建立,peerScore物件
ps := newPeerScore(p, l.scoreCalculator.GetScore(p))
// 通過channel的peersByHostPort儲存對映關係
l.peersByHostPort[hostPort] = ps
// push peer到peer heap中,後面再介紹
l.peerHeap.addPeer(ps)
return p
}
後記
第一個討論點
我們在看 tchannel-go 原始碼過程中經常遇到一個這樣的寫法,大家可以注意下:
tips: 下面我們都假設YYY已經初始化過了。
type XXX struct {
sync.Mutex
YYY map[string]interface{}
}
func (x *XXX) Add(key string, value interface{}) {
x.RLock()
if _, ok := x.YYY[key]; ok {
return
}
x.RUnlock()
x.Lock()
if _, ok := x.YYY[key]; ok {
return
}
x.YYY[key]=value
x.Unlock()
}
在其他專案中也會存在這種寫法。首先對於存在競態的資料,在讀寫操作時需要加鎖互斥操作。所以,下面這種做法也是經常見到的。
func (x *XXX) Add(key string, value interface{}) {
x.Lock()
defer x.Unlock()
if _, ok := x.YYY[key]; ok {
return
}
x.YYY[key] = value
return
}
對比這兩者的寫法,沒有太多不同。這裡要考慮的第一點:
讀鎖和讀寫鎖,時效性明顯不同。對於讀鎖,只有在寫時阻塞,可以併發讀;對於讀寫鎖,讀寫都互斥。後者明顯時耗大一些,如果併發量夠大,則效果更明顯
我們考慮第二點,為何第一種寫法,在嘗試讀取後,下一個鎖操作內還要取嘗試取一次資料,失敗然後再進行儲存操作?
因為每一次鎖操作是一個完整操作,如果在臨界區間,goroutine 被排程,則其他想要操作該鎖也得等待該鎖釋放,最後該 goroutine 被排程,完成釋放鎖後;如果該 goroutine 又被排程,則其他 goroutine 可能會操作該互斥 YYY 變數,所以下次進入臨界區時,需要嘗試再取一次資料,如果沒有則進行儲存操作
第二個討論點
對於 PeerList 的 Add 方法,或者說整個 tchannel-go 有一點做得不太好的地方是,方法裡的鎖操作太多,而且沒有標識,比如l.parent.Add
和p.addSC
方法都存在鎖操作,如果外界直接呼叫,鎖相同則會導致死鎖。在 go 有關的很多專案都是這樣寫的:
func (x *XXX) AddLock() {
x.Lock()
defer x.Unlock()
... // other operations
}
func (x *XXX) AddNoLock() {
... // other operations
}
或者
func (x *XXX) Add() {
... // other operations
}
這個方法顯示的告訴大家,方法內是否存在鎖操作,這樣儘量減少死鎖反應。
更多原創文章乾貨分享,請關注公眾號
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Jaeger tchannel-go —— readme原始碼閱讀Go原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- ReactorKit原始碼閱讀React原始碼
- Vollery原始碼閱讀(—)原始碼
- NGINX原始碼閱讀Nginx原始碼
- ThreadLocal原始碼閱讀thread原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- Runtime 原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- AmplifyImpostors原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- fuzz原始碼閱讀原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- AQS原始碼閱讀AQS原始碼
- Mux 原始碼閱讀UX原始碼
- ConcurrentHashMap原始碼閱讀HashMap原始碼
- HashMap原始碼閱讀HashMap原始碼
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 使用OpenGrok閱讀原始碼原始碼
- express 原始碼閱讀(全)Express原始碼
- Kingfisher原始碼閱讀(一)原始碼
- 如何閱讀框架原始碼框架原始碼
- 如何閱讀jdk原始碼?JDK原始碼
- ArrayList原始碼閱讀(增)原始碼
- snabbdom 原始碼閱讀分析原始碼
- Appdash原始碼閱讀——reflectAPP原始碼
- React原始碼閱讀:setStateReact原始碼
- 如何快速閱讀原始碼原始碼
- 原始碼閱讀工具-understand原始碼