fcm 服務器接入 golang_Golang 寫的即時通訊服務器 im(服務組件形式)

簡要介紹

im是一個即時通訊服務器,代碼全部使用golang完成。主要功能

1.支持tcp,websocket接入

2.離線消息同步

3.單用戶多設備同時在線

4.單聊,群聊,以及超大群聊天場景

5.支持服務水平擴展

gim和im有什么區別?gim可以作為一個im中臺提供給業務方使用,而im可以作為以業務服務器的一個組件, 為業務服務器提供im的能力,業務服務器的user服務只需要實現user.int.proto協議中定義的GRPC接口,為im服務 提供基本的用戶功能即可,其實以我目前的認知,我更推薦這種方式,這種模式相比于gim,我認為最大好處在于 以下兩點:

1.im不需要考慮多個app的場景,相比gim,業務復雜度降低了一個維度

2.各個業務服務可以互不影響,可以做到風險隔離

使用技術:

數據庫:MySQL+Redis

通訊框架:GRPC

長連接通訊協議:Protocol Buffers

日志框架:Zap

ORM框架:GORM

安裝部署

1.首先安裝MySQL,Redis

2.創建數據庫im,執行sql/create_table.sql,完成初始化表的創建(數據庫包含提供測試的一些初始數據)

3.修改config下配置文件,使之和你本地配置一致

4.分別切換到cmd的tcp_conn,ws_conn,logic,user目錄下,執行go run main.go,啟動TCP連接層服務器, WebSocket連接層服務器,邏輯層服務器,用戶服務器

(注意:tcp_conn只能在linux下啟動,如果想在其他平臺下啟動,請安裝docker,執行run.sh)

項目目錄簡介

項目結構遵循 https://github.com/golang-standards/project-layout

cmd:          服務啟動入口config:       服務配置internal:     每個服務私有代碼pkg:          服務共有代碼sql:          項目sql文件test:         長連接測試腳本

服務簡介

1.tcp_conn

維持與客戶端的TCP長連接,心跳,以及TCP拆包粘包,消息編解碼

2.ws_conn

維持與客戶端的WebSocket長連接,心跳,消息編解碼

3.logic

設備信息,好友信息,群組信息管理,消息轉發邏輯

4.user

一個簡單的用戶服務,可以根據自己的業務需求,進行擴展

網絡模型

TCP的網絡層使用linux的epoll實現,相比golang原生,能減少goroutine使用,從而節省系統資源占用

單用戶多設備支持,離線消息同步

每個用戶都會維護一個自增的序列號,當用戶A給用戶B發送消息時,首先會獲取A的最大序列號,設置為這條消息的seq,持久化到用戶A的消息列表, 再通過長連接下發到用戶A賬號登錄的所有設備,再獲取用戶B的最大序列號,設置為這條消息的seq,持久化到用戶B的消息列表,再通過長連接下發 到用戶B賬號登錄的所有設備。

假如用戶的某個設備不在線,在設備長連接登錄時,用本地收到消息的最大序列號,到服務器做消息同步,這樣就可以保證離線消息不丟失。

讀擴散和寫擴散

首先解釋一下,什么是讀擴散,什么是寫擴散

讀擴散

簡介:群組成員發送消息時,先建立一個會話,都將這個消息寫入這個會話中,同步離線消息時,需要同步這個會話的未同步消息

優點:每個消息只需要寫入數據庫一次就行,減少數據庫訪問次數,節省數據庫空間

缺點:一個用戶有n個群組,客戶端每次同步消息時,要上傳n個序列號,服務器要對這n個群組分別做消息同步

寫擴散

簡介:在群組中,每個用戶維持一個自己的消息列表,當群組中有人發送消息時,給群組的每個用戶的消息列表插入一條消息即可

優點:每個用戶只需要維護一個序列號和消息列表

缺點:一個群組有多少人,就要插入多少條消息,當群組成員很多時,DB的壓力會增大

消息轉發邏輯選型以及特點

普通群組:

采用寫擴散,群組成員信息持久化到數據庫保存。支持消息離線同步。

超大群組:

采用讀擴散,群組成員信息保存到redis,不支持離線消息同步。

核心流程時序圖

長連接登錄

54f10824b6e13043969b35d0feca5874.png

離線消息同步

b3798b50e347a1a9ba12213b151ed5b9.png

心跳

f8b329f23749571294c78d1bab73793c.png

消息單發

c1.d1和c1.d2分別表示c1用戶的兩個設備d1和d2,c2.d3和c2.d4同理

2106f69d13ff331d530bb4d84318e8d4.png

小群消息群發

c1,c2.c3表示一個群組中的三個用戶

5547a15f6b41a516dfa417cf56b1298d.png

大群消息群發

b7f1daa1edec915d260fd838f1f5363a.png

錯誤處理,鏈路追蹤,日志打印

系統中的錯誤一般可以歸類為兩種,一種是業務定義的錯誤,一種就是未知的錯誤,在業務正式上線的時候,業務定義的錯誤的屬于正常業務邏輯,不需要打印出來, 但是未知的錯誤,我們就需要打印出來,我們不僅要知道是什么錯誤,還要知道錯誤的調用堆棧,所以這里我對GRPC的錯誤進行了一些封裝,使之包含調用堆棧。

func WrapError(err error) error {if err == nil {return nil}s := &spb.Status{Code:    int32(codes.Unknown),Message: err.Error(),Details: []*any.Any{{TypeUrl: TypeUrlStack,Value:   util.Str2bytes(stack()),},},}return status.FromProto(s).Err()}// Stack 獲取堆棧信息func stack() string {var pc = make([]uintptr, 20)n := runtime.Callers(3, pc)var build strings.Builderfor i := 0; i < n; i++ {f := runtime.FuncForPC(pc[i] - 1)file, line := f.FileLine(pc[i] - 1)n := strings.Index(file, name)if n != -1 {s := fmt.Sprintf(" %s:%d ", file[n:], line)build.WriteString(s)}}return build.String()}

這樣,不僅可以拿到錯誤的堆棧,錯誤的堆棧也可以跨RPC傳輸,但是,但是這樣你只能拿到當前服務的堆棧,卻不能拿到調用方的堆棧,就比如說,A服務調用 B服務,當B服務發生錯誤時,在A服務通過日志打印錯誤的時候,我們只打印了B服務的調用堆棧,怎樣可以把A服務的堆棧打印出來。我們在A服務調用的地方也獲取 一次堆棧。

func WrapRPCError(err error) error {if err == nil {return nil}e, _ := status.FromError(err)s := &spb.Status{Code:    int32(e.Code()),Message: e.Message(),Details: []*any.Any{{TypeUrl: TypeUrlStack,Value:   util.Str2bytes(GetErrorStack(e) + " --grpc-- " + stack()),},},}return status.FromProto(s).Err()}func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {err := invoker(ctx, method, req, reply, cc, opts...)return gerrors.WrapRPCError(err)}var LogicIntClient   pb.LogicIntClientfunc InitLogicIntClient(addr string) {conn, err := grpc.DialContext(context.TODO(), addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(interceptor))if err != nil {logger.Sugar.Error(err)panic(err)}LogicIntClient = pb.NewLogicIntClient(conn)}

像這樣,就可以獲取完整一次調用堆棧。 錯誤打印也沒有必要在函數返回錯誤的時候,每次都去打印。因為錯誤已經包含了堆棧信息

// 錯誤的方式if err != nil {logger.Sugar.Error(err)return err}// 正確的方式if err != nil {return err}

然后,我們在上層統一打印就可以

func startServer {    extListen, err := net.Listen("tcp", conf.LogicConf.ClientRPCExtListenAddr)    if err != nil {    panic(err)    }extServer := grpc.NewServer(grpc.UnaryInterceptor(LogicClientExtInterceptor))pb.RegisterLogicClientExtServer(extServer, &LogicClientExtServer{})err = extServer.Serve(extListen)if err != nil {panic(err)}}func LogicClientExtInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {defer func() {logPanic("logic_client_ext_interceptor", ctx, req, info, &err)}()resp, err = handler(ctx, req)logger.Logger.Debug("logic_client_ext_interceptor", zap.Any("info", info), zap.Any("ctx", ctx), zap.Any("req", req),zap.Any("resp", resp), zap.Error(err))s, _ := status.FromError(err)if s.Code() != 0 && s.Code() < 1000 {md, _ := metadata.FromIncomingContext(ctx)logger.Logger.Error("logic_client_ext_interceptor", zap.String("method", info.FullMethod), zap.Any("md", md), zap.Any("req", req),zap.Any("resp", resp), zap.Error(err), zap.String("stack", gerrors.GetErrorStack(s)))}return}

這樣做的前提就是,在業務代碼中透傳context,golang不像其他語言,可以在線程本地保存變量,像Java的ThreadLocal,所以只能通過函數參數的形式進行傳遞,im中,service層函數的第一個參數 都是context,但是dao層和cache層就不需要了,不然,顯得代碼臃腫。

最后可以在客戶端的每次請求添加一個隨機的request_id,這樣客戶端到服務的每次請求都可以串起來了。

func getCtx() context.Context {token, _ := util.GetToken(1, 2, 3, time.Now().Add(1*time.Hour).Unix(), util.PublicKey)return metadata.NewOutgoingContext(context.TODO(), metadata.Pairs("app_id", "1","user_id", "2","device_id", "3","token", token,"request_id", strconv.FormatInt(time.Now().UnixNano(), 10)))}

github

https://github.com/alberliu/im

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/530070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/530070.shtml
英文地址,請注明出處:http://en.pswp.cn/news/530070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

html 手機分辨率,移動端各種分辨率手機屏幕----適配方法集錦

1.使用viewport&#xff0c;html文件中在內添加meta&#xff0c;簡單粗暴&#xff1a;2.加判斷復雜點的viewport&#xff0c;可以封裝成一個js&#xff0c;每次新建html時調用.js即可&#xff1a;var phoneWidth parseInt(window.screen.width);var phoneScale phoneWidth/75…

橡膠支座抗壓彈性模量計算公式_隔震支座的這些力學特性您都掌握了嗎?

隔震支座疊層橡膠隔震支座&#xff0c;是隔震設計中應用最為廣泛的隔震裝置。了解隔震支座的力學特性&#xff0c;在軟件中設置正確的參數&#xff0c;是做好隔震設計最基本的前提。那么&#xff0c;隔震支座的這些特性您都掌握了嗎&#xff1f;疊層橡膠隔震支座疊層橡膠隔震支…

綜合基礎知識計算機,計算機綜合基礎知識總結.doc

計算機綜合基礎知識總結計算機專業知識試題計算機專業知識試題1.???? 一個字節包含的二進制位數是(?? )。A. 8位???? B. 16位???????????????? C. 32位???????????????? D. 256位2.???? 既可作為輸入又可作為輸出設備的是(???…

asp.net button創建控件時出錯_Tkinter Radiobutton控件

單選按鈕是一種可在多個預先定義的選項中選擇出一項的 Tkinter 控件 。單選按鈕可顯示文字或圖片&#xff0c;顯示文字時只能使用預設字體&#xff0c;該控件可以綁定一個 Python 函數或方法&#xff0c;當單選按鈕被選擇時&#xff0c;該函數或方法將被調用。單選按鈕&#xf…

從網絡訪問計算機不能添加用戶名,windows7 和 XP 能互相用\\ip訪問,但是不能通過計算機名訪問...

只要你的二臺機器(winxp、win7)都能PING通對方的IP地址&#xff0c;并且能夠通過IP正常訪問&#xff0c;只是不能通過同一工作組的計算機名訪問(其實&#xff0c;你先PING過一次要使用的對方的計算機名&#xff0c;就能直接在地址欄中輸入\\計算機名 來訪問了 只是在 網絡 中仍…

jitter 如何優化網絡_抖音優化該怎么做?

抖音在國內是比較知名的短視頻運營商&#xff0c;也有很大的流量市場。這塊優化好了&#xff0c;不管是對于用戶還是企業來說&#xff0c;在品牌宣傳上來說&#xff0c;作用還是蠻大的。抖音優化和電腦端優化&#xff0c;還是有所區別的&#xff0c;這里參考的是點贊數、評論以…

性價比高臺式計算機,2021年臺式電腦什么牌子好,性價比高?

大家都知道買臺式電腦最主要是性價比高&#xff0c;用最少的價錢買到最好的性能配置&#xff0c;但那些大老牌子為了賺平民百姓的錢售價實在貴得太離譜了&#xff0c;性價比非常低今年什么臺式電腦性價比最高&#xff1f;臺式電腦配置推薦 組裝電腦配置單 電腦配置方案-裝機...…

golang 數組組合成最小的整數_golang數組-----尋找數組中缺失的整數方法

問題&#xff1a;由n-1個整數組成的未排序數組&#xff0c;元素都是1~n的不同整數&#xff0c;找出其中缺失的整數方法一&#xff1a;思路&#xff1a;是原數組的和 減去 丟失元素后的數組的和&#xff0c;就得到丟失的元素了代碼如下&#xff1a;package mainimport ("er…

計算機專業線性代數教學大綱,《線性代數》課程教學大綱

一、課程簡介線性代數是討論代數學中線性關系經典理論的課程&#xff0c;它具有較強的抽象性與邏輯性&#xff0c;是高等學校工科本科各專業的一門重要的基礎理論課&#xff0c;也是碩士研究生入學全國統一考試中必考的數學課程之一。由于線性問題廣泛存在于科學技術的各個領域…

yuv420p 詳解_圖文詳解YUV420數據格式

一.YUV格式與RGB格式的換算RGB 轉換成 YUVY (0.257 * R) (0.504 * G) (0.098 * B) 16Cr V (0.439 * R) - (0.368 * G) - (0.071 * B) 128Cb U -( 0.148 * R) - (0.291 * G) (0.439 * B) 128YUV 轉換成 RGBB 1.164(Y - 16) 2.018(U - 128)G 1.164(Y - 16) - 0.813…

計算機存儲器發展歷史,存儲器及其發展歷史與前景(4頁)-原創力文檔

存儲器及其發展歷史與前景高興(電子信息科學與技術專業12-1班)摘 要&#xff1a;存儲器件是計算機系統的重要組成部分&#xff0c;現代計算機的內存儲器多采用半導體存儲器。由于RAM的存取速度比ROM快的多&#xff0c;其成為內存的主要組成元件。內存發展主要經歷了FP DRAM(快…

jq父級綁定事件的意義_用jQuery.delegate()將事件綁定在父元素上面

1.先看看官方的示例&#xff1a;$(document).ready(function(){$("div").delegate("button","click",function(){$("p").slideToggle();});});這是一個段落。請點擊這里2.該方法的定義如下&#xff1a;$(selector).delegate(childSele…

計算機師范類算師范教育類嗎,師范教育類專業和計算機類專業,兩者相比,哪個更適合自考生報讀...

原標題&#xff1a;師范教育類專業和計算機類專業&#xff0c;兩者相比&#xff0c;哪個更適合自考生報讀一、師范教育類專業大部分畢業生的就業方向都是在教育行業&#xff0c;互聯網的發展&#xff0c;教學教育有了網絡課&#xff0c;讓人們隨時隨地都可以學習&#xff0c;也…

android 繪畫筆跡回放_隨時記錄分享書寫筆跡,EverPEN高級版套裝體驗

看到筆記本就想起小時候&#xff0c;老師為了提高學生們的寫作能力&#xff0c;而要求大家每周寫3篇日記&#xff0c;幾年下來慢慢的讓自己養成了每周會抽出點時間&#xff0c;手寫些生活中的點滴趣事。但隨著筆記本保持時間有限&#xff0c;而且有時查找起來也不方便&#xff…

flash 文件計算機課件,計算機軟件及應用Flash.ppt

計算機軟件及應用Flash.pptFlash CS3 提供了一種用幻燈片屏幕創建演示文稿的新方法。它可以將媒體放在幻燈片屏幕上&#xff0c;添加從其它幻燈片繼承媒體的嵌套幻燈片&#xff0c;并且在運行時使用內置控件在幻燈片之間導航。其實&#xff0c;這和PPT基本模式一樣。 6.8 Flash…

12c集群日志位置_Kubernetes(k8s)那些套路之日志收集

準備關于容器日志Docker的日志分為兩類&#xff0c;一類是 Docker引擎日志&#xff1b;另一類是容器日志。引擎日志一般都交給了系統日志&#xff0c;不同的操作系統會放在不同的位置。本文主要介紹容器日志&#xff0c;容器日志可以理解是運行在容器內部的應用輸出的日志&…

數字攝像頭測試軟件,圖像測量軟件(Camera Measure)

Camera Measure是一款簡單易用的數字圖像測量工具&#xff0c;該軟件可用于顯微鏡圖像測量、測繪等專業領域或者普通用戶日常的各種測量&#xff0c;可以播放Windows相機并拍照或錄制視頻&#xff0c;打開圖片或視頻文件&#xff0c;并在畫面中進行實時的高性能圖像測量。軟件功…

過濾特征_機器學習深度研究:特征選擇中幾個重要的統計學概念

機器學習深度研究&#xff1a;特征選擇過濾法中幾個重要的統計學概念————卡方檢驗、方差分析、相關系數、p值問題引出當我們拿到數據并對其進行了數據預處理&#xff0c;但還不能直接拿去訓練模型&#xff0c;還需要選擇有意義的特征&#xff08;即特征選擇&#xff09;&am…

win10計算機優化技巧,讓Win10系統運行更流暢的優化技巧

雖然Win10系統對配置要求不高&#xff0c;在普通條件電腦的環境中也能流暢運行。但是用戶總不會介意進一步優化Win10&#xff0c;讓Win10在流暢的基礎上變得更流暢一些。本文就來介紹一下讓Win10系統更流暢的優化技巧。Win10優化技巧1、用360優化win10后開不了機的問題原因是禁…

c include 多層目錄_python+C、C++混合編程的應用

TIOBE每個月都會新鮮出爐一份流行編程語言排行榜&#xff0c;這里會列出最流行的20種語言。排序說明不了語言的好壞&#xff0c;反應的不過是某個軟件開發領域的熱門程度。語言的發展不是越來越common&#xff0c;而是越來越專注領域。有的語言專注于簡單高效&#xff0c;比如p…