閱讀開源作品霉翔,是快速提升自身水平的最好方式之一鹏浅。好的開源作品濃縮了高手的智慧和高超的編程技巧和思想,長期閱讀會讓這些珍貴的經(jīng)驗(yàn)和技巧不知不覺回饋到我們的日常工作中柬唯。所以,閱讀開源作品是必須掌握的一項(xiàng)技能圃庭。本人會在此篇開始逐漸記錄閱讀的點(diǎn)點(diǎn)滴滴锄奢,分享給讀者朋友失晴,和我一起進(jìn)步!
GOIM簡介
goim是bilibili公司技術(shù)總監(jiān)毛劍創(chuàng)作拘央,用于B站生產(chǎn)線上的IM服務(wù)框架涂屁,其框架原理圖如下,有興趣的同學(xué)可以轉(zhuǎn)到官網(wǎng)查看介紹堪滨,由于側(cè)重點(diǎn)原因胯陋,這里暫不多做介紹蕊温。
解讀思路
我會用庖丁解牛的方式解讀袱箱,而非自上而下的方式。
自上而下的優(yōu)點(diǎn)是:呈框架體系的方式進(jìn)行閱讀义矛。缺點(diǎn)也很明顯:會在前期灌輸大量的概念和設(shè)計(jì)发笔,比如Bucket、Comet凉翻、Proto等了讨。這樣就會在一開始就打擊閱讀者的積極性。
所以采用了細(xì)化分層的方式解讀制轰,關(guān)注每一個(gè)部位細(xì)節(jié)前计,偶有其他模塊的擴(kuò)展,最后再把所有的模塊串聯(lián)起來垃杖,達(dá)到"解讀之后男杈,未嘗見全牛也"的程度。
代碼解讀(Bucket)
注:此處的代碼是我手動敲上去的调俘,記錄了自己的思維伶棒,也為了加深理解和方便解讀。由于版本原因彩库,建議讀者朋友轉(zhuǎn)到github拉取源碼對照解讀肤无。
package main
import(
"goim/libs/define"
"goim/libs/proto" //libs包含了對bufio,bytes,net等原生包的改寫,
//個(gè)人覺得還是很考驗(yàn)閱讀能力的
"sync"
"sync/atomic"
)
type BucketOptions struct { //Bucket操作
ChannelSize int
RoomSize int
RoutineAmount uint64 //Size和Amount這些貌似是固定大小的
}
type Bucket struct {
cLock sync.RWMutex //確保chs的協(xié)程安全
chs map[string]*Channel //訂閱字符為key骇钦,channel實(shí)體為value
boptions BucketOptions
rooms map[int32]*Room
routines []chan *proto.BoardcaseRoomArg //用于廣播給bucket下所有的room
routinesNum uint64
}
//創(chuàng)建Bucket實(shí)體
func NewBucket(boptions BucketOptions) (b *Bucket){
b = new(Bucket)
b.chs = make(map[string]*Channel, boptions.ChannelSize)
b.boptions = boptions
//room
b.rooms = make(map[int32]*Room, boptions.RoomSize)
//為廣播房間數(shù)創(chuàng)建對應(yīng)數(shù)量的gorounite?
b.routines = make([]chan *proto.BoardcastRoomArg, boptions.RoutineAmount)
for i:= uint64(0); i < boptions.RoutineAmount; i++ {
c := make(chan *proto.BoardcastRoomArg, boptions.RoutineSize)
b.routines[i] = c
go b.roomproc(c)
}
return
}
//bucket中Channel個(gè)數(shù)
func (b *Bucket) ChannelCount() int {
return len(b.chs)
}
//bucket中Room個(gè)數(shù)
func (b *Bucket) RoomCount() int {
return len(b.rooms)
}
//sub key為key宛渐,ch為channel,rid是Room號眯搭,一并初始化并put到Bucket中
func (b *Bucket) Put(key string, rid int32, ch *Channel) (err error){
var (
room *Room
ok bool
)
//map非線程安全皇忿,故加鎖保護(hù)
b.cLock.Lock()
b.chs[key] = ch
if rid != define.NoRoom { //define.NoRoom = -1,意思是沒有房間信息
if room, ok = b.rooms[rid]; !ok {
room = NewRoom(rid)
b.rooms[rid] = room
}
ch.Room = room
}
b.cLock.Unlock()
if room != nil {
err = room.Put(ch)
}
return
}
//刪除channel和room
//思考坦仍,channel和room有什么樣的關(guān)系
//猜想鳍烁,一個(gè)channel只對應(yīng)唯一room,一個(gè)room有多個(gè)channel
func (b *Bucket) Del(key string) {
var (
ok bool
ch *Channel
room *Room
)
//
b.cLock.Lock()
if ch, ok = b.chs[key]; ok {
room = ch.Room
delete(b.chs, key)
}
b.cLock.Unlock()
if room != nil && room.Del(ch){
//空room必須從bucket中刪除
b.DelRoom(room)
}
}
//Channel get a channel by sub key
func (b *Bucket) Channel(key string) (ch *Channel) {
b.cLock.RLock()//Lock用于讀寫不確定的情況下,有強(qiáng)制性
//RLock用于讀多寫少的情況繁扎,這就是使用RLock的原因
//也可理解互斥鎖和讀寫鎖的區(qū)別
//課后作業(yè)幔荒,到底底層區(qū)別在哪
ch = b.chs[key]
b.cLock.RUnlock()
return
}
//廣播消息給Bucket下所有的channels
//由此可見糊闽,Proto是消息單位體
func (b *Bucket) Broadcast(p *proto.Proto) {
var ch *Channel
b.cLock.RLock()
for _, ch = range b.chs {
ch.Puch(p)
}
b.cLock.RUnlock()
}
//get a room by rid
func (b *Bucket) Room(rid int32) (room *Room) {
b.cLock.RLock()
room, _ = b.rooms[rid]
b.cLock.RUnlock()
return
}
//delete a room of bucket by room pointer
func (b *Bucket) DelRoom(room *Room) {
b.cLock.Lock()
delete(b.rooms, room.Id)
b.cLock.Unlock()
room.Close()
return
}
//向Bucket下所有Room廣播信息
func (b *Bucket) BroadcastRoom(arg *proto.BoardcastRoomArg) {
//原子增加,最終的數(shù)量不超過RountineAmount
//課后作業(yè)爹梁,原子操作和加鎖保護(hù)的區(qū)別在哪右犹?
num := atomic.AddUint64(&b.routinesNum, 1) % b.boptions.RoutineAmount
b.routines[num] <- arg
}
//獲取在線數(shù)據(jù)大于1(Online > 1)的所有room
func (b *Bucket) Rooms() (res map[int32]struct{}) {
var (
roomId int32
room *Room
)
res = make(map[int32]struct{})
b.cLock.RLock()
for roomId, room = range b.rooms {
if room.Online > 0 {
res[roomId] = struct{}{}
}
}
b.cLock.RUnlock()
return
}
func (b *Bucket) roomproc(c chan *proto.BoardcastRoomArg) {
for {
var (
arg *proto.BoardcastRoomArg
room *Room
)
arg = <- c
if room = b.Room(arg.RoomId); room != nil {
room.Push(&arg.P)
}
}
}
圖解
由圖中可以看出,Bucket管理者Rooms和Channel姚垃,都是以map數(shù)據(jù)結(jié)構(gòu)保存念链,room是以rid(roomId)為key,room實(shí)體指針為value积糯,Channel是subkey為key掂墓,channel實(shí)體指針為value。一個(gè)channel維護(hù)著一個(gè)長鏈接用戶看成,對應(yīng)著唯一的room君编,而同一個(gè)room擁有多條channel,后續(xù)解讀這條論證川慌。
Bucket提供了常用的del吃嘿,put以及廣播給旗下的channel或room消息等接口,比較好理解梦重。
這里需要注意下roomproc接口
在創(chuàng)建一個(gè)Bucket(NewBucket())之后兑燥,分配一個(gè)大小為boptions.RoutineSize的BoardcastRoomArg,共RoutineAmount個(gè)gorountine琴拧,每一個(gè)gorountine維護(hù)一個(gè)roomproc接口降瞳,一直阻塞監(jiān)聽,只要有BoardcastRoomArg塞入艾蓝,即自動廣播給room力崇。
課后作業(yè)
源碼中經(jīng)常出現(xiàn)RUnlock和RLock讀寫鎖,讀寫鎖和互斥鎖用途的主要區(qū)別在哪赢织?分別適應(yīng)什么樣的場景亮靴?底層源碼實(shí)現(xiàn)又是有怎樣的區(qū)別?
這是我的提升點(diǎn)之一于置,也歡迎讀者朋友們一起探索回答茧吊,下一篇將首先解讀課后作業(yè),后期見八毯。