goim解讀(Bucket篇)

閱讀開源作品霉翔,是快速提升自身水平的最好方式之一鹏浅。好的開源作品濃縮了高手的智慧和高超的編程技巧和思想,長期閱讀會讓這些珍貴的經(jīng)驗(yàn)和技巧不知不覺回饋到我們的日常工作中柬唯。所以,閱讀開源作品是必須掌握的一項(xiàng)技能圃庭。本人會在此篇開始逐漸記錄閱讀的點(diǎn)點(diǎn)滴滴锄奢,分享給讀者朋友失晴,和我一起進(jìn)步!

GOIM簡介

goimbilibili公司技術(shù)總監(jiān)毛劍創(chuàng)作拘央,用于B站生產(chǎn)線上的IM服務(wù)框架涂屁,其框架原理圖如下,有興趣的同學(xué)可以轉(zhuǎn)到官網(wǎng)查看介紹堪滨,由于側(cè)重點(diǎn)原因胯陋,這里暫不多做介紹蕊温。

goim架構(gòu)圖

解讀思路

我會用庖丁解牛的方式解讀袱箱,而非自上而下的方式。

自上而下的優(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類關(guān)系圖

由圖中可以看出,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è),后期見八毯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搓侄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子话速,更是在濱河造成了極大的恐慌讶踪,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泊交,死亡現(xiàn)場離奇詭異乳讥,居然都是意外死亡柱查,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門云石,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唉工,“玉大人,你說我怎么就攤上這事汹忠×芟酰” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵宽菜,是天一觀的道長谣膳。 經(jīng)常有香客問我,道長赋焕,這世上最難降的妖魔是什么参歹? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任仰楚,我火速辦了婚禮隆判,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘僧界。我一直安慰自己侨嘀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布捂襟。 她就那樣靜靜地躺著咬腕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葬荷。 梳的紋絲不亂的頭發(fā)上涨共,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音宠漩,去河邊找鬼举反。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扒吁,可吹牛的內(nèi)容都是我干的火鼻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼雕崩,長吁一口氣:“原來是場噩夢啊……” “哼魁索!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盼铁,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤粗蔚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饶火,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹏控,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冬念,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牧挣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片急前。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瀑构,靈堂內(nèi)的尸體忽然破棺而出裆针,到底是詐尸還是另有隱情,我是刑警寧澤寺晌,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布世吨,位于F島的核電站,受9級特大地震影響呻征,放射性物質(zhì)發(fā)生泄漏耘婚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一陆赋、第九天 我趴在偏房一處隱蔽的房頂上張望沐祷。 院中可真熱鬧,春花似錦攒岛、人聲如沸赖临。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兢榨。三九已至,卻和暖如春顺饮,著一層夾襖步出監(jiān)牢的瞬間吵聪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工兼雄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吟逝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓君旦,卻偏偏與公主長得像澎办,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子金砍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評論 25 707
  • 用兩張圖告訴你局蚀,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,732評論 2 59
  • 小時(shí)候的夢想是做一名戰(zhàn)地記者恕稠,哪兒危險(xiǎn)跑哪兒報(bào)道琅绅,后來…慢慢發(fā)現(xiàn)新聞?wù)娴暮茈y做呀,連看個(gè)新聞都這么難… 在這個(gè)信息...
    帥比2松閱讀 272評論 0 1
  • #幸福是需要修出來的~每天進(jìn)步1%~幸福實(shí)修09班~18-馬榮 20170808(22/30)09班 【幸福三朵玫...
    幸福實(shí)修09班馬榮閱讀 149評論 0 0
  • 人物介紹:杰夫·貝佐斯(Jeff Bezos)鹅巍,創(chuàng)辦了全球最大的網(wǎng)上書店Amazon(亞馬遜)千扶,1999年當(dāng)...
    心哲匯閱讀 693評論 0 2