Go: IM系统技术架构梳理

概述

  • 整个IM系统的一般架构如下
  • 我们这张图展示了整个IM系统的一般架构可见分为四层
  • 那最上面这一层是前端,包括哪些东西呢?
    • 它包括两部分,第一部分是跟用户直接交互的
    • 比如说各种IOS APP, 各种安卓 APP
    • 还有各种 web APP 在浏览器里面打开的
    • 以及windows上面跑的那种客户端
    • 第二部分是跟我们程序相关的SDK,API,Websocket
    • 这些我们都统称为前端
  • 第二个是接入层,这里展示了几种常用的接入协议
    • TCP,HTTPS, HTTPS2, Websocket
    • 实际上还会用到Mqtt, Xmpp 等各种协议
    • 这是接入层
  • 然后, 第三层是逻辑层,逻辑层里面比较熟悉的群聊单聊登录消息下发
    • 整个消息下发是整个系统应用的重点,
  • 最后,第四层是存储层,存储层,包括 Mysql,Redis, Mongodb 等
    • 这是用来做消息持久化用的,用来存储消息的历史记录
    • Hbase, Hive这些是我们做大数据存储用的
    • 当我们后面的数据量越来越大的时候可能会用到这是存储方式
    • 后面有一个文件服务器,为了提升系统的抗并发能力
    • 我们将应用服务跟文件服务相互分离
    • 一些服务器可能用第三方云来提供
    • 这样来提升我们系统的抗并发能力
  • 这就是我们整个IM系统的一般架构

网络结构

  • 我们整个网络结构也可以分为三部分
  • 第一部分是 Hybrid APP 浏览器各种微信环境,通过ws(s) 或 http(s) 协议, 接入到我们应用服务
  • 第二部分是应用服务,通过其他的网络途径读写我们的数据库(第三部分)
  • 这就是我们整个网络架构
  • 值得注意的是:
    • HTTP提供的是API服务比如说我们用户输入用名密码,点击登录这个调用的就是我们的HTTP服务。
    • 然后 websocket 提供的是长链接,比如说用户发送信息给对方,为了保持这个消息的及时性通过websocket 建立一个长链接,这个是用来做消息推送用的

Websocket 的使用


1 )选型

  • github.com/gorilla/websocket (生态案例多,推荐)
  • golang.org/x/net/websocket

2 )安装 gorilla 的 websocket

  • 注意,gorilla 包依赖 x/net 包,要先安装 x/net 包
  • 因为网络问题,x/net 包装不了,按照下面处理
  • $ cd $GOPATH
  • $ mkdir -p golang.org/x/net
  • $ cd golang.org/x/net
  • $ go get -u github.com/golang/net/websocket
  • $ go get github.com/gorilla/websocket

3 )Websocket 的鉴权

3.1 鉴权成功

3.2 鉴权失败

  • 如果是我们每个系统里面的用户,才能够接入聊天系统
  • 如果不是我们系统里面的用户,我们应该拒绝他,
  • 他不能对我们这个应用发送任何消息,这里涉及到鉴权的问题
  • 如上,这个鉴权有两个参数,一个是id,一个是token
  • id 相当于我们的QQ号,token 是每一个用户登录所产生的唯一的一个标识
  • 我们鉴权的思路就是
    • id跟token相互匹配,如果匹配,他就成功,这里往前走的就是101
    • 如果 id 跟 token 不一致, 我们返回的是 403
    • 也就是说以后这个链接,不能通过这个ws发送信息
    • 因为它拒绝了,还没有接到我们系统里面去
    • 这个就是鉴权失败的一个标识

4 ) 用户的基本结构

  • 主要关注 id 和 token, 其他的字段,我们不做过多的一个关注
  • 这是接入的用户信息表

5 ) 接入鉴权

  • 后端怎么实现这个鉴权的呢,如上,拿到了 id 和 token
  • 在这个 CheckOrigin 函数中,验证 id 和 token 是否匹配
  • 如果匹配返回 true ,对应200 (101);否则,返回false, 对应 403
  • 最后会拿到 conn,是我们每个客户端的标识,基于此来发送信息和读取信息

conn的维护

  • 我们怎么来维护这个 conn 呢?
  • 这里给出一个最简单的一个维护方案
    • userid 和 conn 形成一个映射关系
    • 最后形成一个map,这就是我们定义的 ClientMap
    • 可见,对应的 userid 是 int64 类型
  • 但是往往在实际的生产过程中,这些关系是远远不够的
    • 我们需要定义一个结构体,这个结构体包含这个conn
    • 也可能包含其他的一些属性,比如说用户的头像,昵称,性别
    • 这样的一些东西,放在这个 ClientNode 里面
    • 也就是 userid 和 ClientNode 然后形成一个映射关系
  • map的维护,还有其他一些技巧
    • 比如说这里面会加锁,以及其他的东西,考虑并发性的需求

6 )消息的发送

  • 消息发送之前,先回顾一下消息体的一个格式
  • Id 是消息的id
  • Userid 表示哪个用户发的
  • Cmd 是代表群聊还是私聊
    • 如果是群聊,Dstid 是 群id
    • 如果是私聊,Dstid 是 目标用户id
  • 如果 Dstid 是群id
    • 则需要通过 Dstid 获取加入群的所有用户id
    • 然后通过这个用户ID 获取我们的换取我们的 ClientNode, 拿到里面的 conn
  • 如果 Dstid 是userid, 这里就不赘述,参考上面获取 conn
  • 其他字段就先略过

7 )消息的接收

  • 启动 websocket 的时候,我们要启动一个协程
  • 在这个协程里面它是有一个循环,一个阻塞
  • readMessage一直在这里,等待一有消息发过来
  • 它就把这个数据读到 message 这个字符串里面
  • 然后再对这个字符串进行解析到 msg 类型的对象里面去
  • 这个就是我们最核心的API readMessage 写它readmessage
  • 最后注意这里有一个 go dispatch,也是也是利用协程的一个属性
  • 为了让我们整个系统啊跑起来更流畅

8 )消息的发送

  • 首先是需要将这个消息的Json对象转成 []byte 类型的 msg 字符串
  • 然后, 再通过这个 conn.WriteMessage(websocket.TextMessage, msg) 方法把这个 msg 输出
  • 注意这第一个参数是 TextMessage,代表这个地方是文本格式

9 ) 前端 JS 打开 websocket

// 火狐,chrome
var websocket = new WebSocket(url);// 打开事件回调
websocket.onopen = function(ev) {// 启用心跳
}
  • 首先是通过 new WebSocket 方法, 传入 URL, 这个url 就是有userid 和 token 的url
  • 然后这里有一个onopen方法,如果我们打开成功了,它就会调用这个onopen方法, 这是一个事件回调
  • 在这个回调内启动心跳,什么是心跳呢?
    • 在传统的网络结构里面,前端跟后端通信的时候
    • 如果我们在一定时间内,比如说一百秒以内
    • 没有数据在这个管道里面传输,那么系统就认为这个网络已经是空闲状态了
    • 它会把这个网络回收掉,具体这个时间是多少,是由系统配置的
    • 有些服务器,它有这个参数可以配置的
    • 为了保持这个网络,是一个正常的状态,不让服务器回收它
    • 我们需要往里面发送一些特殊字符,这个服务器接收到,就认为这个网络还是连接的
    • 这样一个字符,就叫做心跳

10 )WS的心跳机制

  • 心跳应该是隔多长时间发一次,还有每次发心跳,要发什么样的一个格式
  • 我们这里有几种方案
    • 1 )隔30秒发一次,非常简单,非常机械的一个方案,但能达到目的
    • 但是我们不建议隔30秒发一次
    • 2 )我们建议在距离最近一次发送的时间30秒以内或者45秒或者自己设置秒来重发
    • 也就是说你最后一次发送的时候,我们将当前的最后一次发送时间记录下来
    • 然后随着这个时间增加,如果在30秒以内,比如说增加了27秒的时候,
    • 有数据发送有数据更新,那这时候我们可以将这个当前的最后一次发送这个时间清零, 这时候我们又从零开始往前计数
    • 这个就是最近一次发送的30秒这个有效范围内发送,这个是心跳机制 (重要)
  • 心跳机制在物联网应用里面,心跳设置的这时间间隔会影响你整个系统的复杂程度,也会影响整个系统的负载和抗并发的能力
  • 打个比方,如果线下有一批设备都停电了,后续统一都连上来了
  • 这时候服务器在一瞬间,各种数据,各种心跳一下子都来了,而且都在同样一个时间段过来了,服务器承受的负担是非常大的。
  • 但是如果它是非常均匀的啊,比如说一到五秒是十台设备
  • 五到十秒是另外十台设备,然后是非常均匀的部署,后端服务器的这个负担是非常轻的

11 )前端发送消息

data = JSON.stringify(msg对象)
websocket.send(data)// 队列发送
  • 这里有一个API是 send,参数 data 是一个序列化之后的字符串
  • 这里还有一个技巧,就是用队列发送
  • 比如说有些消息是讲究时序的,也就是先后顺序
  • 可以对它进行用队列来发送,这里还有一个简单demo
  • 这里定义了一个数组叫做 dataqueue
  • push方法就是往这个队列里面添加数据
  • 然后pop就是把这个数据从队列里取出来
  • 可以通过一个 while true 的循环,不断的pop,然后那边添加进来push进去
  • 这就是我们简单的一个队列
  • 上面是消息发送的格式,对象序列化以后,就是上述字符串
  • 这里,content 是 你好, media 是 1 代表文字
  • 然后这个 cmd 是 10 代表点对点的单聊模式
  • userid 代表谁发的消息
  • dstid 就是目标用户id

12 )前端接收的消息

websocket.onmessage = function(event) {// 处理 eventdata = JSON.parse(event.data)
}

整个消息流程梳理


我们看下,A 如何发送消息给B?

1 )A 尝试打开 websocket,路径 /chat?id=xxx&token=sdsdfss
2 ) 后端通过鉴权,建立 userid => websocket 的映射
3 )启用协程,通过 conn.ReadMessage 等待和读取消息
4 )A 发送 Json 字符串消息,里面携带了目标用户 dstid
5 ) 如果是群消息,则分解成群用户ID,进行群发处理
6 ) 后端通过 ClientMap[userid]获得目的用户的 conn
7 ) conn.WriteMessage,这时候在存在连接的情况下就发送给客户端的B了

设计高质量代码


1 )优化Map

  • 现在,我们探讨下对单机性能的优化,如何支持高并发
  • 那为什么要用map呢?因为我们对map的频繁读写,就会导致map的一个安全性问题
  • 这里,我们需要加锁,典型的就是读写锁,读写锁非常适合的场景是什么?
    • 读的次数非常多,写的次数非常少的一个场景
    • 所以这跟我们的业务正好非常切合,我们写的场景就是用户的接入
    • 我们读的次数非常多,每次群发都需要通过这个map来获得用户的 conn
  • 我们map不能太大
  • 一个map为十万个用户, 已经非常不错了,维护一百万个用户是没有意义的
  • 这是我们的map相应的这一个优化

2 ) 突破系统的瓶颈, 优化最大的连接数

  • 典型的我们普遍认为 linux 系统会 优于 windows系统, 我们要把系统迁移到 linux
  • 但是,linux 下有一个最大的文件数,他会直接影响的我们网络的连接数
  • 我们要把最大的文件数先解除掉,这是我们对系统的一个优化

3 )优化这个CPU资源的使用率

  • 典型的占用CPU资源的编码,出现在对JSON的编码上
  • 每次我们对JSON进行编码占用的CPU资源非常多
  • 要尽量降低这种编码的使用频次,做到一次编码多次使用

4 )降低IO资源的使用

  • 怎么会降低IO资源的使用呢?比如说我们用户访问数据库,数据库连接需要时间
  • 另外数据库访问是一个非常耗时的过程,所以我们在这个过程中,整个IO也是处于占用状态
  • 那这应该怎么办呢?那很简单,我们需要合并这个写数据库的这个次数
  • 以前是一秒钟写五次,我们可以让他五秒钟写一次,只是把这个数据合并起来,再写
  • 以前每次要去数据库里面读数据,比如用户的头像信息,我们可以引入缓存
  • 这样就可以直接先去缓存中去查,降低对数据库的依赖,这是对IO资源的这样一个优化

5 )对应用服务和资源分布服务相互分离的这一个优化策略

  • 我们应用服务是指什么呢?
    • 是指提供动态的这样一个服务
    • 比如说用户注册,登录这样一个服务
  • 然后我们资源服务是指什么呢?
    • 是指我们的图片文件CSS等文件
    • 我们把这个资源服务部署到我们的云服务上,由第三方服务提供,比如 OSS
    • 这样就可以极大的降低资源服务对我们服务器的这样一个压力
    • 这样就能够提升我们整个系统的性能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/46710.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网对抗-基石框架篇域树林域森林架构信任关系多域成员层级信息收集环境搭建

知识点: 1、基石框架篇-域树&域林架构-权限控制-用户和网络 2、基石框架篇-域树&域林架构-环境搭建-准备和加入 3、基石框架篇-域树&域林架构-信息收集-手工和工具1、工作组(局域网) 将不同的计算机按照功能分别列入不同的工作组。想要访问某个部门的…

MySQL篇:事务

1.四大特性 首先,事务的四大特性:ACID(原子性,一致性,隔离性,持久性) 在InnoDB引擎中,是怎么来保证这四个特性的呢? 持久性是通过 redo log (重做日志&…

使用Nginx OpenResty与Redis实现高效IP黑白名单管理

1、引言 在当今数字化时代,网络安全已成为企业和个人用户关注的焦点。IP黑白名单作为一种有效的网络安全策略,允许我们精确控制对Web资源的访问权限。通过白名单,我们可以确保只有可信的IP地址能够访问敏感资源;而黑名单则可以阻…

嵌入式人工智能(2-树莓派4B开发板硬件环境搭建)

1.硬件开发环境(T型板) 树莓派4B开发板需要搭配面包板,T型板将40个GPIO口引出,再将T型板插到面包板上面。这个地方需要注意插接的方向,由于插树莓派引脚的排线没有防呆设计,因此,请注意方向&am…

第二证券:电影暑期档持续升温 农机自动驾驶驶入快车道

农机自动驾驶打开驶入快车道 得益于农机补贴、土地流通、高标准农田制造等方针引导,叠加技术突围和用户降本增效的内生需求,我国正处于农业2.0向农业3.0的过渡阶段。其间农机自动驾驶系统是结束农业3.0(即自动化)的要害并迎来快速…

maptalks点聚合切换坐标系后点聚合消失

原因:因为坐标系投影问题 解决方法:切换坐标系后,重新把请求到的点位数据插入到的图层删除掉,重新插入图层就可以解决了 // 先移除已存在的同名图层 this.map.removeLayer(cluster);// 然后添加新的图层this.map.addLayer(this.c…

装饰模式:动态扩展对象的功能

在软件设计中,我们常常需要在不改变现有代码的基础上,给对象添加新的功能或责任。装饰模式(Decorator Pattern)是一种常用的设计模式,它允许我们在运行时动态地给对象添加新的行为,而无需修改原有的类结构。…

Linux/C++:Json--网络编程中的奇妙小工具

目录 一、什么是Json 二、Josn基本结构 2.1Josn对象 2.2C使用Josn 2.3.1解析Josn格式的数据 2.4Linux编程(vscode)使用Josn 一、什么是Json JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,它是基于JavaScript语言的子集,但是独立于…

PyCharm软件初始化配置

安装完pycharm后,需要对其进行个性化设置,分别设置方法如下 目录 一、修改主题二、修改默认字体和大小三、设置拖动滚轮改变字体大小四、常见快捷键 一、修改主题 1、界面右上角点击红框的内容 2、选择Theme选项 3、选择对应的主题 第一二个是白色主题…

树莓派配置vsftpd被动模式使用frp外网端口映射实现外网连接ftp

sudo apt-get install vsftpd /etc/vsftpd.conf anonymous_enableNO # 禁用匿名用户 local_enableYES # 允许本地用户登录 write_enableYES # 允许修改权限 #chroot_local_userYES # 将用户限制在其主目录 百度关键字 frp vsftp 关闭主动模式登录 connect_from_port_…

电池技术的未来:BMS的创新与应用

目录 一、什么是BMS? 二、BMS的核心功能 三、为什么BMS如此重要? 四、应用领域 五、未来展望 随着电动汽车、储能系统以及各种便携式设备的普及,电池技术的发展变得至关重要。而在这一领域中,电池管理系统(BMS&am…

直播美颜工具开发教学:视频美颜SDK集成详解

本篇文章,笔者将详细介绍如何在直播应用中集成视频美颜SDK,让你的直播画面焕然一新。 一、什么是视频美颜SDK? 视频美颜SDK是一种软件开发工具包,提供了视频处理和图像增强功能。通过集成视频美颜SDK,开发者可以轻松…

可视化作品集(14)智慧旅游和智慧景区,洞悉一切。

智慧旅游和智慧景区的可视化大屏可以带来以下几个方面的好处: 1. 提升游客体验: 通过可视化大屏,游客可以方便地获取到景区地图、交通信息、景点介绍、活动安排等信息,帮助游客更好地规划行程,提升游览体验。 2. 提供…

超越99%动画!我测试了Luma AI视频的首尾帧,流畅度NO.1?

关键帧通常用于控制动画中的运动、形状变化、颜色变化、透明度等属性,以及视频和音频编辑中的剪辑、效果和音频级别。 最近一段时间,玩可灵AI玩得比较多(国产免费速度快),luma上回写了一篇文章后就没有接着使用(排队生…

2024年上半年信息系统项目管理师——综合知识真题题目及答案(第1批次)(2)

2024年上半年信息系统项目管理师 ——综合知识真题题目及答案(第1批次)(2) 第21题:在一个大型信息系统项目中,项目经理发现尽管已经建立了沟通机制,但团队间的沟通依然不畅,项目风险…

【python模块】Selenium

声明:本文档或演示材料仅供教育和教学目的使用,任何个人或组织使用本文档中的信息进行非法活动,均与本文档的作者或发布者无关。 文章目录 Selenium库功能介绍环境准备示例代码 Selenium库 Selenium库是一个强大的Web自动化工具,…

堆、栈和队列(数据结构)

堆、栈和队列(数据结构) 这里写目录标题 堆、栈和队列(数据结构)**栈****队列**堆(Heap)()队列(Queue)(FIFO)栈(Stack&…

【Rust】字符串String类型学习

什么是String Rust的核心语言中只有一个String类型,那就是String slice,str通常被当作是&str的借用。String类型是通过标准库提供的,而不是直接编码到核心语言中,它是一个可增长的、可变的、utf-8编码的类型。str和String都是utf-8编码的…

Faiss原理和使用

参考自https://github.com/facebookresearch/faiss/wiki,https://blog.csdn.net/Kaiyuan_sjtu/article/details/121551473 Faiss Faiss是一个用于高效相似性搜索和密集向量聚类的库。它包含搜索任意大小的向量集(大小由RAM决定)的算法。它还…

【vue教程】一. 环境搭建与代码规范配置

目录 引言Vue 框架概述起源与设计理念核心特性优势 Vue 开发环境搭建环境要求安装 Vue CLI创建 Vue 项目项目结构介绍运行与构建 组件实例基础模板响应式更新 代码规范为什么要使用代码规范在 Vue 项目中使用 ESLint 、PrettierESLint配置 ESLintrules 自定义错误级别 Prettier…