基于Tars高并发IM系统的设计与实现-基础篇2

news2024/11/13 10:36:49

基于Tars高并发IM系统的设计与实现-基础篇2

三大指标

高可用

分为服务高可用与存储高可用。

服务高可用

服务高可用要做到高可用必须具备两个特点:

  • 负载均衡
  • 可横行扩展

当服务的请求量比较高的时候,一台服务不能满足需求,这时候需要多台机器提供同样的服务,将所有请求分发到不同机器上。

高可用架构中应该具有丰富的负载均衡策略和易调节负载的方式。
甚至可以自动化智能调节,例如由于机器性能的原因,响应时间可能不一样,这时候可以向性能差的机器少一点分发量,保证各个机器响应时间的均衡。

负载均衡;
  • 外部负载均衡

    • 由nginx负责,nginx本是一个反向代理服务器,但由于丰富的负载均衡策略,常常被用于客户端可真实的服务器之间,作为负载均衡的实现。
    • 用nginx做实现服务的高可用,nginx本身可能成为单点,遇见的两种解决方案,一种是公司搭建自己的DNS,将请求解析到不同的NGINX,另一种是配合keepalive实现服务的存活检测。
  • 内部负载均衡
    IM子服务之间RPC调用的负载均衡,由Tars框架的注册发现服务来负责,根据设定策略进行服务节点维护和负载均衡操作;

横向扩展

当用户量越来越多,已有服务不能承载更多的用户的时候,便需要对服务进行扩展,扩展的方式最好是不触动原有服务,对于服务的调用者是透明的。

要达到这个目标,需要做到每个服务可以动态扩容,根据业务流量的需要,随时都能过增加/减少部署相应服务节点,要做到这点,整个系统的服务要无状态。

无状态很重要!无状态很重要!无状态很重要!重要的事情说三遍。 
长链接如何做到无状态

有朋友就问了,对于长连接如何做到无状态,服务做到无状态,连接状态信息和数据可以存储到redis集群中就可以了;有服务用此信息直接从redis集群中获取即可。

存储高可用

  • 存储高可用比较简单,redis作为IM系统的核心存储部件,可以采用集群或者主从模式,用户量较小时可以采用主从模式,较大时建议采用集群模式;
  • mysql数据存储作为冷存储用主从模式,读写分离进行操作即可解决问题;
  • IM服务要做到即使redis,mysql挂了,在线发送消息也要正常,这个就需要消息流转系统的设计和建设不能对redis,mysql进行强依赖。

高并发

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

提高系统并发能力的方式主要有3种:

  • 纵向扩展
  • 横向扩展
  • 提高服务处理能力

纵向扩展

纵向扩展可以通过提升单机硬件性能,或者提升单机架构性能,来提高并发性,但单机性能总是有极限的,互联网分布式架构设计高并发终极解决方案还是后者:横向扩展和提高服务请求处理速度。
横向扩展前文已经描述,此处不再赘述。

如何提高服务请求处理速度?

影响服务器处理速度一般有如下原因

  • 大批量数据处理,比如群聊消息分发
  • 频繁访问I/O;
  • RPC同步请求等等
  • 服务异常导致雪崩

针对以上原因有逐个击破进行解决:

  • 大批量数据处理
    大批量数据处理耗时较长可以进行异步线程处理;比如500人的群,每发一条消息都需要分发给500人,逐个操作可以放到一个线程或者线程池进行处理。
    对于一些高频调用接口,可以考虑对请求数据合法性验证后,将数据先缓冲起了,后续异步进行处理,提高对外接口的吞吐能力。

  • 频繁访问I/O
    减少IO的访问,能不用直接访问IO尽量不直接访问IO,优先访问内存和缓存进行处理;比如保存数据到mysql中,可以先将数据保存到redis中,由一个独立的进程进行redis到mysql同步或者异步调用该进程进行写入mysql服务中
    读取mysql数据,如果该调用量比较大,需要根据情况将数据放到redis或者内存中比较合适。

  • RPC同步请求
    在一个请求中不可避免要通过RPC调用别的服务接口,如果非必要尽量采用异步调用来解决问题,这样服务进程不会等待占用资源,增加服务吞吐量;

  • 服务异常导致雪崩
    每个服务都有处理能力,服务要有限流和熔断机制,避免由于局部异常或者流量突然暴增导致整个系统崩溃。

低延时

低延迟是指计算机系统或通信网络中的较短时间延迟;低延时并不是无延时,只要延时在可接受的时间范围内就ok。

要做到低延时,主要有如下几个考虑要点:

  • 每个子服务尽量简单
  • 操作尽量在内存中进行,减少IO的访问
  • 尽量异步处理
  • 耗时操作尽量放在后台线程或者进程进行处理
  • 网络传输数据尽量少

基于以上几点进行服务设计和开发基本能做到低延迟,当然低延迟是相对概念,跟系统的负载能力和用户量有很大关系,做到低延时需要在很多细节上进行检测调整,建议开发系统时做好相应的数据收集,以便后续出现高耗时操作时方便优化。

四大模块

四大模块包含

  • 连接管理
  • 用户及好友管理
  • 消息管理
  • 离线推送

连接管理

连接管理主要包含消息处理,连接处理器逻辑,用户登录记录,消息流转

消息处理流程

当客户端A需要将消息发送给客户端B时需要经历如下步骤:

  1. 客户端A,客户端B分别建立一个TCP连接到接入服务S,S会有多台,不同用户可能会连接到不同的S
  2. 服务端S检查客户端A是否合法,如果合法,将其记录到连接服务中的长连接管理器中,如果失败直接返回前端相应错误信息,并关闭连接;
  3. 客户端A 发消息到接入服务S,S根据业务类型RPC调用相应的消息处理服务S1,S1对消息进行处理并存储,同时发送ACK给客户端A表示该消息S1已经收到;
  4. S1根据A消息中目标用户B,在长连接管理器中寻找到B的长连接,将A发的消息推送给B;

如图:
在这里插入图片描述

  • 连接管理功能特点
    上文中提到的TCP长连接管理器就是负责管理维护用户长连接;
    连接管理器有以下功能:
    • 记录每个在线用户连接及相关信息:
    • 收发消息需要的相关信息都需要保存到管理器中,比如用户id,连接id,设备id,设备类型,登录时间,连接所在服务id(可以根据该id投送消息到某个服务器);
    • 根据用户id获取该连接信息;
    • 消息投送时,根据目标用户id能够快速找到连接信息,并将消息数据发送出去;
    • 根据服务id获取当前服务上上所有连接信息;
    • 当服务升级重启时,能够清理连接信息,防止错误的在线数据导致其他逻辑问题。
    • 离线用户删除相关长连接记录;
    • 用户离线后,将不能再接收任何数据,准确的在线连接数据是IM系统的基石,如果在线用户连接数据不准确,会导致整个IM系统低效运行。
    • 连接保活(心跳处理);
    • 确保用户长连接能够最大限度维持,需要按照客户端按照一定时间频率发送心跳报文到连接管理器来说明客户端还在继续工作,还活着;
    • 超时连接清理:如果超过2个心跳周期还未收到某个客户端的心跳或者正常请求数据,说明该客户端已掉线或者非正常结束,服务端会认为该客户端已经死掉,将主动关闭连接并从连接管理器中移除相应连接;
    • 根据连接id找到连接信息,

连接器处理逻辑

连接管理器使用频率很高,所以在性能上需要很高要求,查询速度要快,跨机器查询访问;支持高并发;为了达到这个目标,采用内存双hashmap+redis的数据存储设计;
内存双hashmap:一个是userId-连接信息,另一个是连接id-连接信息;
redis使用到两个数据结构:
hash保存每个连接的信息,key是“connect:+userId”,value为:连接相关信息;
Set保存某个服务器上的登录用户信息:key是“connectUsers:+服务id,value为当前服务上所有在线用户userId;
在这里插入图片描述

用户登录记录

在这里插入图片描述

消息流转

w200

用户及好友相关模块

该模块主要有用户认证,用户资料记录维护,用户好友关系维护
该部分相对来说逻辑比较简单,主要进行数据的存取,本系统对与数据的存取采用两级存储模式:redis+mysql;
主要存储一下数据:

  • 用户资料
  • 用户id,名称,头像,出生年月,地区,性别等等
  • 好友关系

消息处理管理

消息处理管理主要负责消息顺序时序处理,消息存储,消息转发,历史消息获取,消息撤回,未读数计算等;

  • 消息处理流程
    主要流程如图:
    w200

消息时序处理

消息顺序处理在消息收到服务端之后立马进行,有序列号seqId和时间戳字段;
时间戳并且获取当前时间戳(毫秒级)即可;
序列号从序列号服务中获取一个唯一递增的序列号,序列号如何生成,有专门文章有介绍;
有同学问了,序列号直接用时间戳不行?当然不行,分布式系统中时间可能不能保证完全同步,可能会造成消息顺序不准确;消息顺序在消息逻辑中很重要,要确保准确;

消息存储

包含缓存和冷存储,缓存直接调用redis接口存储即可;redis与mysql如何进行结合,此处不再赘述,做专项介绍。

消息转发

  • 单聊/服务通知消息转发步骤:
  1. 在redis中查找目标用户是否在线;
  2. 如果在线,将消息直接投送到用户在线的连接服务器上,
  3. 连接服务器推送到用户客户端;
  • 群聊消息转发步骤:
  1. 获取到群的群成员;
  2. 在redis中查找群每个成员是否在线;
  3. 如果成员在线,将消息直接投送到群成员在线的连接服务器上,
  4. 连接服务器推送到群成员客户端;

历史消息获取

当客户端需要查看某个会话中的聊天记录时,客户端的聊天信息有可能并不完整,需要从服务段根据情况实时获取;由于整个消息系统支持多端,每个客户端本地存储的消息数量不一定相同,所以需要设计一套机制来保障客户端获取消息重复率低而且不能漏掉消息;
MQTT协议本身不具备消息历史记录功能,用户登录时将离线时的消息全部推送到客户端,这种方法针对轻量级的控制类系统使用尚且可以,针对IM重聊天数据的系统显然不合适;我们早期使用MQTT协议时碰到过一个真实案例,一个6000人的群,产生20万条消息,当客户端登录时采用全部更新,app启动后无法使用;所以要解决离线消息历史记录问题,此方案断然不能采用。

从根本上讲,历史消息的获取就是广义上的数据同步方法,提到数据同步方法,无非就两种:全量同步和部分同步;

  • 全量同步:
    由客户端发起请求,从服务端获取该会话的所有聊天记录;当然客户端获取时需要明确告知服务端是全量数据同步;
    此方法实现看起来起来简单,在IM的实际场景中会有很多局限,单聊相对来说数据比较重要,数据量不是很大倒也无妨,群聊的数据量比较大,如果一个300人的群,这个群又比较活跃,一两天不上线会有很多消息,如果全量同步的话将耗费大量时间和客户端资源,未等到数据全部到位,用户已经没有耐心直接关闭app了;

  • 部分同步:
    从用户使用app的习惯角度来看,用户每次最多看几十条消息,着实没有必要一次性把所有消息都拉下来,把所有消息都全部同步下来也是一种浪费;
    客户端根据需要从服务端获取历史消息比较合理;当用户需要查看更多时调用接口获取一定数量的历史消息;
    客户端可以根据业务场景对本地数据进行灵活处理;

消息撤回

在IM系统中,消息撤回是一个很重要的功能,一个消息发出后,发现有错误,在一定时间内可以撤回;
这种撤回操作作为目前刚性需求几乎是每个IM系统必备的功能;
完成撤回消息功能,本质就是撤回消息方发出一条指令给目标用户,告诉前面发的某条消息需要撤回;
执行撤回指令需要考虑几种情况进行处理:

  • 对方未收到消息;针对这种情况,服务端收到撤回指令后,对原消息进行修改即可。
  • 对方已收到消息,且目前在线;针对这种情况,除了完成A的操作,还需要将撤回指令推送给对方,对方客户端也需要修改已收到的消息和展示方式。
  • 对方已收到消息,切目前已离线;针对这种情况完成A后,要确保对方客户端上线后第一时间能收到撤回指令,并修改已收到的消息和展示方式。
  • 至于如何确保上线后第一时间能收到撤回指令,这就牵扯到高优先级消息,后续专门介绍相关设计逻辑。

未读数计算

未读数的显示对于IM系统来说也是一个基本功能,首先对未读数进行一个定义:

  • 用户未阅读的消息数量
  • 每个会话有个独立数量
  • 只要用户在某一个设备上阅读某个会话的消息,都算已读

要实现以上未读数功能,有两种方式:

  • 客户端计数
    客户端计数实现针对多个设备上的数据同步比较复杂,需要 客户端-》服务端->客户端;不能保证数据最大的准确性;
    服务端计数只需要维护服务端的数据正确性,每个客户端根据情况进行数据同步就行,少一步 客户端向服务器的同步操作,别小看这一步,针对一个多端多活的异步系统,能减少很多数据同步和完整性的维护操作,也减少很多逻辑。
  • 服务端计数
    服务端未读数存储采用redis 中zset结构,key每个用户id+会话id,数据为未读消息id,seqid
    增加未读数:有新消息时向该key数据结构增加新消息的packId,seqid
    计算未读数:获取该key中的数量;
    减少未读数:通过已读消息的seqid,删除<=seqid所有数据

假设用户id为22,会话id为s1,未读数变化如图:

在这里插入图片描述

离线消息推送

离线消息推送主要处理客户端不在线的情况,客户端不在线,可以根据用户最近一次登录的设备类型,利用厂商通道进行消息推送,不通厂商提供不通的API和SDK;

  • 苹果设备通过APNS;
  • Android有FCM,华为,小米,vivo,oppo等渠道,根据业务需要进行逐个对接即可;

当然市场上也有一些第三方厂商比如umeng,极光等可以接入。

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

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

相关文章

sklearn.preprocessing模块介绍

数据预处理 Binarizer: 二值化 用于将数值特征二值化。它将特征值与给定的阈值进行比较&#xff0c;并将特征值转换为布尔值&#xff08;0 或 1&#xff09;&#xff0c;取决于特征值是否超过阈值 Binarizer(*, threshold0.0, copyTrue)参数&#xff1a; threshold&#xf…

AGI—从GPT和大型语言模型中汲取的经验教训

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2306.08641.pdf 计算机视觉研究院专栏 Column of Computer Vision Institute 人工智能…

【计算机视觉 | 图像分类】arxiv 计算机视觉关于图像分类的学术速递(6月 29 日论文合集)

文章目录 一、分类|识别相关(12篇)1.1 Pseudo-Bag Mixup Augmentation for Multiple Instance Learning Based Whole Slide Image Classification1.2 Improving Primate Sounds Classification using Binary Presorting for Deep Learning1.3 Challenges of Zero-Shot Recognit…

万物分割SAM家族 越发壮大!HQ-SAM、FastSAM 和 FasterSAM(MobileSAM)

卧剿&#xff0c;6万字&#xff01;30个方向130篇&#xff01;CVPR 2023 最全 AIGC 论文&#xff01;一口气读完。 1、&#xff08;更高质量&#xff09;Segment Anything in High Quality 最近的 Segment Anything Model (SAM) 代表了分割模型的一大飞跃&#xff0c;有强大的零…

从零实现深度学习框架——Seq2Seq机器翻译实战

引言 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架&#xff0c;该框架类似PyTorch能实现自动求导。 &#x1f4a1;系列文章完整目录&#xff1a; &#x1f449;点此&#x1f448; 要深入理解…

【你哥电力电子】 THE BUCK-BOOST 升降压斩波电路2

BUCK-BOOST电路2 2023年1月30日 nige in Tongji University #elecEngeneer 上链 文章目录 BUCK-BOOST电路26. CCM非理想能量守恒平均分析6.1 CCM非理想大信号平均模型6.2 CCM等效大信号平均模型6.3 CCM的DC电路模型6.4 CCM的小信号线性电路模型6.5 CCM非理想小信号传递函数6.…

【SaaS】多租户系统设计

文章目录 多租户系统设计一、SaaS 的系统分级二、应用程序必须支持多租户三、数据隔离方案3.1、独立应用独立库3.2、同一个应用程序&#xff0c;每个租户一个库3.3、同一个应用程序&#xff0c;同一个数据库3.4、分片多租户 四、我们的模型选择4.1、开发实践4.2、元数据/配置驱…

vue路由传参+案例(使用mock模拟后端数据)

路由传参 跳转路由时&#xff0c;可以给路由对应的组件内传参 声明式导航 /path?参数名值 /path/值 —需要路由对象提前配置 path: ‘/path/:参数名’ 对应的页面组件接收传递过来的值 $route.query.参数名 $route.params.参数名 router/index.js import Vue from vue // 1. …

解析matlab的audioread()输入输出参数

目录 一、API简介 二、实验 1. matlab 2. C语言 一、API简介 链接如下&#xff1a; 读取音频文件 - MATLAB audioread- MathWorks 中国 也可以浏览最新的英文版API说明&#xff1a; 简单说明如下&#xff1a; 1. 读取wav格式的文件&#xff0c;会自动跳过44个字节的文件…

初识React/JSX/组件/state/受控组件

JSX 推荐使用小括号包裹jsx 使用函数创建组件 使用类创建组件 抽离组件 事件绑定 事件对象 有状态和无状态组件/state 抽离事件处理程序 表单元素 受控组件 多表单优化 非受控组件(了解即可)

vhost-net-原理-初始化流程-数据传输流程-vhost-net后端

文章目录 1.vhost net2.vhost-net的初始化流程vhost net设置vhost dev设置vhost vring设置 3.数据收发流程分析3.1 数据发送3.2 数据接收 4ioventfd和irqfd的通知机制4.1ioeventfdqemu侧kvm侧总体效果 4.2irqfdqemu侧kvm侧总体效果 参考&#xff1a; 1.vhost net 传统的virtio…

ChatGPT Plugins内幕、源码及案例实战(一)

ChatGPT Plugins内幕、源码及案例实战 6.1 ChatGPT Plugins的工作原理 本节主要跟大家谈ChatGPT的插件(Plugins),这个内容非常重要。现在很多企业级的开发,一般都会基于ChatGPT 插件进行一些服务的封装,相当于开发了一个代理(Agent),把一些服务或者API封装在里面,然后…

eclipse编辑器汉化;eclipse安装中文插件

eclipse IDE默认是英文环境&#xff0c;使用起来略微不便&#xff0c;汉化还是很有必要的&#xff1b;下面记录一下安装中文插件的过程: 文章目录 一、 选择安装包地址二、 在eclipse安装中文插件2.1 在线安装2.2 手动下载安装包2.3 导入到eclipse 三、汉化插件介绍 一、 选择安…

实例005 可以拉伸的菜单界面

实例说明 如果管理程序功能菜单非常多&#xff0c;而用户只使用一些常用菜单&#xff0c;这时&#xff0c;可以将主菜单项下的不常用菜单隐藏起来。此种显示方式类似于对菜单进行拉伸。使用时&#xff0c;只需单击展开菜单&#xff0c;即可显示相应菜单功能。运行本例&#xf…

python matplotlib中colorbar的位置设置

colorbar单独设置一个轴对象&#xff0c;再对轴对象进行灵活设置 import numpy as np import matplotlib.pyplot as plt# 创建一个二维随机数组 data np.random.rand(10, 10)# 创建一个图形和一个子图 fig, ax plt.subplots()# 绘制热力图 heatmap ax.imshow(data, cmaphot…

在linux中快速安装Redis数据库

Redis中文网 点击该链接下载最5.0.4版本的Redis的压缩包 使用Xftp工具将Redis安装包上传到linux中 1.将压缩包解压到/opt目录下: tar -zxvf redis-5.0.4.tar.gz 2. 更新yun: sudo yum makecache fast 3.安装gcc: yum -y install gcc 4.安装完成通过输入 : gcc -v …

tiny tool - get_file_path_name_by_drop_file

文章目录 tiny tool - get_file_path_name_by_drop_file概述工程效果收获的知识点vs2022工程, 必须自己设置对话框可以接受文件的风格vs2022建立的工程, 默认是unicode编码, 设置剪贴板数据时, 必须要设置为unicode的格式, 否则剪切板中只有第一个字符工程主要实现END tiny too…

短信压力测试系统,支持自定义接口

短信压力测试系统,支持自定义接口 支持卡密充值&#xff0c;短信压力测试系统&#xff0c;解决一切骚扰电话&#xff0c;教程在压缩包里面 可多个服务器挂脚本分担压力&#xff0c;套了cdn导致无法正常执行脚本可以尝试添加白名单 这边建议使用MySQL方式 同服务器下直接配置…

MySQL生产环境高可用架构实战

分布式技术MongoDB 1. MySQL高可用集群介绍1.1 数据库主从架构与分库分表1.2 MySQL主从同步原理 2. 动手搭建MySQL主从集群2.1 基础环境搭建2.2 安装MySQL服务2.2.1 初始化MySQL2.2.2 启动mysql2.2.3 连接MySQL 2.3 搭建主从集群2.3.1 配置master主服务2.3.2 配置slave从服务主…

Radzen Blazor Studio 1.12 Crack

Radzen Blazor Studio 是一款桌面工具&#xff0c;使 开发人员 能够创建精美的商业 Blazor 应用程序。快速地。 开放技术栈 没有供应商锁定。生成的源代码是人类可读的&#xff0c;您可以使用免费工具构建它。 Radzen 由流行的开源技术 - ASP.NET Core、Blazor、Bootstrap 提供…