你要的應該是 Reids 或 Memcached 這些緩存服務,在 Go 語言中的客戶端工具。
創新互聯公司10多年成都定制網頁設計服務;為您提供網站建設,網站制作,網頁設計及高端網站定制服務,成都定制網頁設計及推廣,對成都玻璃貼膜等多個行業擁有多年的網站制作經驗的網站建設公司。
GitHub 上有個 repo 叫 awesome-go(GitHub - avelino/awesome-go: A curated list of awesome Go frameworks, libraries and software),整理了常見的 Go 框架或代碼庫,其中就有 Redis 和 Memcached 的客戶端。
目前websocket技術已經很成熟,選型Go語言,當然是為了節省成本以及它強大的高并發性能。我使用的是第三方開源的websocket庫即gorilla/websocket。
由于我們線上推送的量不小,推送后端需要部署多節點保持高可用,所以需要自己做集群,具體架構方案如圖:
Auth Service:鑒權服務,根據Token驗證用戶權限。
Collect Service:消息采集服務,負責收集業務系統消息,存入MongoDB后,發送給消息分發服務。
Dispatch Service:消息分發服務,根據路由規則分發至對應消息推送服務節點上。
Push Service:消息推送服務,通過websocket將消息推送給用戶。
集群推送的關鍵點在于,web端與服務端建立長連接之后,具體跟哪個推送節點保持長連接的,如果我們能夠找到對應的連接節點,那么我們就可以將消息推送出去。下面講解一下集群的大致流程:
1. web端用戶登錄之后,帶上token與后端推送服務(Push Service)保持長連接。
2. 推送服務收到連接請求之后,攜帶token去鑒權服務(Auth Service)驗證此token權限,并返回用戶ID。
3. 把返回的用戶ID與長連接存入本地緩存,保持用戶ID與長連接綁定關系。
4. 再將用戶ID與本推送節點IP存入redis,建立用戶(即長連接)與節點綁定關系,并設置失效時間。
5. 采集服務(Collect Service)收集業務消息,首先存入mongodb,然后將消息透傳給分發服務(Dispatch Service)。
6. 分發服務收到消息之后,根據消息體中的用戶ID,從redis中獲取對應的推送服務節點IP,然后轉發給對應的推送節點。
7. 推送服務節點收到消息之后,根據用戶ID,從本地緩存中取出對應的長連接,將消息推送給客戶端。
其他注意事項:
在go http每一次go serve(l)都會構建Request數據結構。在大量數據請求或高并發的場景中,頻繁創建銷毀對象,會導致GC壓力。解決辦法之一就是使用對象復用技術。在http協議層之下,使用對象復用技術創建Request數據結構。在http協議層之上,可以使用對象復用技術創建(w,*r,ctx)數據結構。這樣即可以回快TCP層讀包之后的解析速度,也可也加快請求處理的速度。
先上一個測試:
結論是這樣的:
貌似使用池化,性能弱爆了???這似乎與net/http使用sync.pool池化Request來優化性能的選擇相違背。這同時也說明了一個問題,好的東西,如果濫用反而造成了性能成倍的下降。在看過pool原理之后,結合實例,將給出正確的使用方法,并給出預期的效果。
sync.Pool是一個 協程安全 的 臨時對象池 。數據結構如下:
local 成員的真實類型是一個 poolLocal 數組,localSize 是數組長度。這涉及到Pool實現,pool為每個P分配了一個對象,P數量設置為runtime.GOMAXPROCS(0)。在并發讀寫時,goroutine綁定的P有對象,先用自己的,沒有去偷其它P的。go語言將數據分散在了各個真正運行的P中,降低了鎖競爭,提高了并發能力。
不要習慣性地誤認為New是一個關鍵字,這里的New是Pool的一個字段,也是一個閉包名稱。其API:
如果不指定New字段,對象池為空時會返回nil,而不是一個新構建的對象。Get()到的對象是隨機的。
原生sync.Pool的問題是,Pool中的對象會被GC清理掉,這使得sync.Pool只適合做簡單地對象池,不適合作連接池。
pool創建時不能指定大小,沒有數量限制。pool中對象會被GC清掉,只存在于兩次GC之間。實現是pool的init方法注冊了一個poolCleanup()函數,這個方法在GC之前執行,清空pool中的所有緩存對象。
為使多協程使用同一個POOL。最基本的想法就是每個協程,加鎖去操作共享的POOL,這顯然是低效的。而進一步改進,類似于ConcurrentHashMap(JDK7)的分Segment,提高其并發性可以一定程度性緩解。
注意到pool中的對象是無差異性的,加鎖或者分段加鎖都不是較好的做法。go的做法是為每一個綁定協程的P都分配一個子池。每個子池又分為私有池和共享列表。共享列表是分別存放在各個P之上的共享區域,而不是各個P共享的一塊內存。協程拿自己P里的子池對象不需要加鎖,拿共享列表中的就需要加鎖了。
Get對象過程:
Put過程:
如何解決Get最壞情況遍歷所有P才獲取得對象呢:
方法1止前sync.pool并沒有這樣的設置。方法2由于goroutine被分配到哪個P由調度器調度不可控,無法確保其平衡。
由于不可控的GC導致生命周期過短,且池大小不可控,因而不適合作連接池。僅適用于增加對象重用機率,減少GC負擔。2
執行結果:
單線程情況下,遍歷其它無元素的P,長時間加鎖性能低下。啟用協程改善。
結果:
測試場景在goroutines遠大于GOMAXPROCS情況下,與非池化性能差異巨大。
測試結果
可以看到同樣使用*sync.pool,較大池大小的命中率較高,性能遠高于空池。
結論:pool在一定的使用條件下提高并發性能,條件1是協程數遠大于GOMAXPROCS,條件2是池中對象遠大于GOMAXPROCS。歸結成一個原因就是使對象在各個P中均勻分布。
池pool和緩存cache的區別。池的意思是,池內對象是可以互換的,不關心具體值,甚至不需要區分是新建的還是從池中拿出的。緩存指的是KV映射,緩存里的值互不相同,清除機制更為復雜。緩存清除算法如LRU、LIRS緩存算法。
池空間回收的幾種方式。一些是GC前回收,一些是基于時鐘或弱引用回收。最終確定在GC時回收Pool內對象,即不回避GC。用java的GC解釋弱引用。GC的四種引用:強引用、弱引用、軟引用、虛引用。虛引用即沒有引用,弱引用GC但有空間則保留,軟引用GC即清除。ThreadLocal的值為弱引用的例子。
regexp 包為了保證并發時使用同一個正則,而維護了一組狀態機。
fmt包做字串拼接,從sync.pool拿[]byte對象。避免頻繁構建再GC效率高很多。
名稱欄目:go語言本地緩存 golang channel 有緩存但是close掉
網站地址:http://m.newbst.com/article30/dddojso.html
成都網站建設公司_創新互聯,為您提供網站營銷、服務器托管、做網站、搜索引擎優化、外貿建站、商城網站
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯