集群聊天服务器项目(四)——项目总结

news2024/12/26 11:24:09

集群聊天服务器项目总结

首先是就是项目介绍集群聊天服务器项目(零)——项目介绍中的内容,就不再次copy过来了

项目简单介绍

技术栈

环境和库依赖

按模块介绍整个项目

程序的主要模块是网络模块、业务模块、数据模块、Json、redis发布订阅消息队列模块以及nginx负载均衡模块

网络模块

网络模块底层采用的是陈硕的muduo库,其采用的是 one loop per thread + nonblocking IO 的网络事件模型,其基于epoll的事件处理机制能够高效地处理网络事件,提供了高效的事件处理和内存管理方式。有一个较高muduo通过事件驱动的方式实现了异步I/O,能够支持上万的并发连接。用户只需要关注连接到来socket消息到来时的业务处理。

使用muduo库作为项目的核心网络模块,提供高并发以及高可用网络IO服务,解耦网络和业务模块的代码,提高了系统的可维护性和可扩展

在这里插入图片描述

业务模块

这一模块主要完成相应的业务处理,如

  1. 客户端新用户注册

  2. 客户端用户登录

  3. 添加好友和添加群组

  4. 一对一好友聊天

  5. 群组聊天

  6. 离线消息存储

其工作方式是通过解析收到json数据根据消息类型来调用对应的业务函数

数据模块

数据模块主要就是对MySQL数据库的表和操作一系列封装。

本项目的数据库表为:User表、Friend表、AllGroup表、GroupUser表、OfflineMessage

本项目对MySQL的CRUD基本操作封装为一个类MySQL,然后将表的字段封装为一个类并提供对应的 getset 方法也就是加入 ORM(object Relation Model)类,业务层操作的都是对象,DAO层(数据访问层)即xxxmodel类才访问数据,对表的业务操作的封装(如往表中插入新用户的数据、根据id查询用户信息等)。

比如:user表有id、name、passwd字段,将其封装为User类(ORM层),有对应的数据成员和函数成员getId、getName、setId、setName等,然后封装具体的用户表的操作类UserModel,里面提供插入新用户方法insert(User &user)、查询用户信息方法query(int id)以及更新用户状态的成员函数updateState(User user)

这么做的好处是解耦了业务层和数据层。

其中表的规模大概是5w行以内的数据量。

Json

本项目使用json来序列化和反序列化消息作为私有通信协议

Json是一种轻量级的数据交换格式(也叫数据序列化方式)。Json采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 Json 成为理想的数据交换语言。

未来计划改进为protobuf

nginx负载均衡模块

该模块的主要作用是实现集群服务器的功能,使得客户端可以连接到不同的服务器上,从而提高整个聊天服务器的并发量(单端口可达6w并发量)。其中配置nginx基于权重的负载/轮询均衡算法。

在这里插入图片描述

nginx的主要用处或优点如下:

  • client请求按负载算法分发到具体业务服务器Chatserver
  • 能和ChatServer保持心跳机制,检测ChatServer保持心跳机制,检测ChatServer故障
  • 能发现新添加的ChatServer设备方便服务器扩展数量

redis发布订阅消息队列

redis发布订阅消息队列的主要作用是在使用了集群服务器之后实现跨服务器聊天。该消息队列在程序中的工作流程大致如下:

用户c1在某个服务器上连接后,要把该连接在redis队列上订阅一个通道号为c1(通道号为用户id)

别的客户端c2在不同的服务器上登录要给c1发消息,会直接发到redis队列上的通道号c1,消息队列会把信息发送到正在订阅通道c1的redis连接上 (阻塞等待消息中),客户端c1在其对应的接收信息线程中可接受到c2发来的信息

每个连接到服务器的用户要开一个单独线程进行监听通道上的事件,有消息给业务层上报。

当服务器发现发送的对象id没有在自己的_userConnMap上,就要往消息队列上publish,消息队列就会把消息发布给订阅者。

消息队列是长连接跨服务器聊天通用方法

消息队列这种中间件是典型的观察者模式的应用实践

遇到的问题

服务器异常退出没有将用户状态置为offline

解决方法:服务器main.cpp 中设置一个信号处理函数来设置用户状态

// chat/src/server/main.cpp
void resetHandler(int)
{
    ChatService::instance()->reset();
    exit(0);
}

客户端异常退出没有将用户状态置为offline

解决方法:连接断开时,调用业务层提供的方法来将对应用户的映射信息从服务器的_userConnMap中删除,并且设置用户状态为offline

// ChatServer.cpp
void ChatServer::onConnection(const TcpConnectionPtr &conn)
{
    if(!conn->connected())
    {
        ChatService::instance()->clientCloseException(conn);
        conn->shutdown();
    }
}

客户端登录后输入logout 退出登录服务端出错

错误信息:

{“id”:4,"

exception caught in Thread ChatServer1

reason: [json.exception.parse_error.101] parse error at line 1, column 10: syntax error while parsing object key - invalid string: missing closing quote; last read: ‘"’; expected string literal

Aborted

排查和解决过程:

gdb调试检查服务端接收到的json序列化的字符串,发现收到的数据不完整,那么可能是发送方发送不完整,定位到发送语句:

send(clientfd, buffer.c_str(), sizeof(buffer.c_str()) + 1, 0);

并且通过测试发现:

sizeof(buffer.c_str()) = 8

strlen(buffer.c_str()) = 31

所以不应该使用 sizeof(buffer.c_str()) 而应该换成 strlen

一个用户没法存储多条离线消息,上线时只能收到一条消息

通过查看MySQL表,发现原因是离线消息表offlinemessage 中的 useridmessage都设置为了 unique,这样导致同一个用户只能有一条离线消息记录到表中,

解决方法:

offlinemessage表 字段都不要设置为unique,因为一个userid可以对应条离线消息

客户端登录时接收一条以上的离线消息导致直接终止

错误信息:

在这里插入图片描述

排查过程

先是检查自己有没有输入中文或者中文字符

然后通过打印登录时从服务器获得的json串,发现输出不完整

这里是个有趣的问题,我之前做百万并发测试的时候把内核的rmemwmem都调到了512,导致了这里read一轮读出的json序列化串不完成造成解析失败。

解决方法:

sudo modprobe ip_conntrack

sudo vim etc/sysctl.conf

按如下修改

在这里插入图片描述

修改之后输入:sudo sysctl -p

nginx编译安装错误

错误1

src/os/unix/ngx_user.c:36:7: error: ‘struct crypt_data’ has no member named ‘current_salt’

36 | cd.current_salt[0] = ~salt[0];

解决方法:

# vim src/os/unix/ngx_user.c

将对应代码注释掉

在这里插入图片描述

错误2

error: cast between incompatible function types from ‘size_t (*)(ngx_http_script_engine_t *)’ {aka ‘long unsigned int (*)(struct <anonymous> *)’} to ‘void (*)(ngx_http_script_engine_t *)’ {aka ‘void (*)(struct <anonymous> *)’} [-Werror=cast-function-type]

在这里插入图片描述

解决方法:

输入 vim objs/Makefile 把 -Werror删掉 (-Werror,它要求GCC将所有的警告当成错误进行处理)

错误3:

mv: cannot move ‘/usr/local/nginx/sbin/nginx’ to ‘/usr/local/nginx/sbin/nginx.old’: Permission denied

这表示移动文件没权限

解决方法:

将目标目录的权限更改为当前用户拥有的权限

sudo chown -R $(whoami) /usr/local/nginx

项目面试可能问题

你这里数据都是明文传输,不安全怎么解决?

进行加密

对称加密码算法:加解密效率高,AES加解密算法

非对称加密:公钥和私钥,加密复杂,效率慢,但是安全、RSA算法

实践方法:第一次用非对称加密发送对称密钥,后面双方都用对称密钥加密信息

客户端消息如何按序显示

消息添加序列号seq,接收方维护一个下一次应该接收消息的序列号,如果后发的提前到了,则会将消息缓存起来。加序号不仅可以保证消息按序到达,还可实现其他功能,如消息撤回

如果给消息添加一个时间戳,到达客户端再按时间排序,但是有问题,比如以1s为周期进行消息显示,很可能最先发的消息没有和它相邻的消息一起进行排序,即无法实现全局有序显示。

不能用短链接吗?

http就是B/S 无状态、短链接,无法主动推消息,只能被动响应

服务端要主动推消息,常用websocket

集群聊天服务器需要处理大量的客户端连接请求,而使用短链接会导致频繁的连接和断开操作,增加服务器的负担。当客户端需要发送消息时,短链接需要重新建立连接,而在连接的建立和断开时会产生额外的网络开销和延迟,影响系统的性能。

通常IM即时聊天都在服务器上有长连接模块

如果网络拥塞严重,ChatServer端如何感知客户端在线还是掉线

客户端发FIN包,服务器recv 得到0表示client下线

心跳机制设计:

listen socket 8080 通用业务处理

UDP socket 8080 心跳业务处理

启动一个心跳计时器(server启动),超时1s,把所有账号心跳计数+1

connect成功的client分配一个心跳计数(heartbeatcnt )

如设计的消息格式为: userid:zhangsan1, heartbeatcnt :4

若账号心跳计数 》 5,即判定client掉线,拆除client所有连接及其他资源(业务层的一些数据)

若从TCP 协议分析,传输层,keepalive,用于确定对方没说话还是掉线可以吗?

不可以,因为keepalive 默认关闭,setsockopt开启,默认每隔两小时发送一个空报文段,探测对方是否在线。 若探测无响应,延迟75s继续发送探测包,依旧没有则再探测9次 (75 * 9)。但是若应用层死锁了,传输层检测意义不大了。

所以,基于长连接业务通常都是在业务层自己设计心跳保持机制

怎么保证消息的可靠传输

应用层实现消息确认机制

为什么tcp的消息确认机制不能保证消息可靠传输?

超时重传可能失败

send(fd, buf, buf_size, 0); 返回>0 只是将用户空间数据buf 拷贝到内核空间的TCP发送缓冲区中,不代表发送成功。这是tcp的消息确认机制不能保证消息可靠传输原因之一

最终由内核TCP协议栈 将数据发送出去。

TCP IP MAC最大数据 MTU 1500字节,但TCP和IP头都占20B,故实际携带数据为1460字节

只要是数据传输失败,或是返回ACK失败,C端都认为数据发送失败,启动超时重传定时器,连发几次后若还不行,就发一个RST包。

最终还是要在业务上实现可靠传输:

客户要发送的消息都缓存起来,如下

在这里插入图片描述

历史消息如何存储

主要有两种存储位置:

本地消息存储 和 消息存储

本地

好友qq号作为文件夹,

SQLite 嵌入式数据库(嵌入到当前进程里),方便查询

云消息存储

存储到mysql,若为长时间的聊天数据可以存储到文件服务器上即 dump-> fileserver,因为mysql超过千万行后索引空间占用大,磁盘操作很慢。

除了redis,还知道其他组件能完成相应的功能(消息队列)?

redis功能 : 缓存数据库,k-v,数据持久化;分布式锁、发布订阅channel

服务器中间件:放在后端中间,不能单独跑。如MQ消息队列,kafka, zeromq,rabbitmq,rocketmq

redis运行不稳定,挂了怎么办?

redis消息积累的过快,消费消息过慢,可能导致挂

消息的消费不可靠。

非核心业务、流量不是非常大,可以用redis的发布订阅功能

为什么要用redis作为跨服务器通信的组件,为什么各个server不能相互直接通信呢?

若各个server直连,则服务器还要承担客户端职责,服务器间耦合性高。需要和每个server相互连接,还要不断心跳

【TODO】压力测试

【TODO】按典型程序流程介绍整个项目

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

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

相关文章

Anaconda环境闭着眼睛安装tensorflow2.0-GPU

1.创建conda环境 conda create -n tf2 python3.7 2.进入conda环境 conda activate tf2 3.输入 nvidia-smi 查看有没有显卡驱动。(没有安一个&#xff0c;不管是windows/linux) 4. 安装cudatoolkit 和 cuDNN conda install cudatoolkit10.0 cudnn 5. 安装tensorflow pip ins…

【最佳实践】OAuth标准和基于OAuth2.0实现Github 授权单点登录的保姆级教程

【最佳实践】OAuth标准和基于OAuth2.0实现Github 授权单点登录的保姆级教程 第一章&#xff1a;OAuth基础知识1.1 OAuth起源1.2 OAuth简介1.3 OAuth的角色1.4 OAuth的授权流程1.5 OAuth的安全性1.6 OAuth标准的历史版本 第二章&#xff1a;OAuth2.0的工作原理2.1 OAuth2.0简介2…

前端--移动端布局--1移动web开发流式布局

目标&#xff1a; 能够知道移动web的开发现状 能够写出标准的viewport 能够使用移动web的调试方法 能够说出移动端常见的布局方案 能够描述流式布局 能够独立完成京东移动端首页 目录&#xff1a; 移动端基础 视口 二倍图 移动端调试 移动端技术解决方案 移动端常…

【全屏导航栏菜单】

提示&#xff1a;全屏导航栏菜单,炫酷的全局动画和导航切换动画 前言 提示&#xff1a;以下是本篇文章的代码内容,供大家参考,相互学习 一、html代码 <!DOCTYPE html> <html><head><meta http-equiv"content-type" content"text/html; c…

浅尝GoWeb开发之Gin框架

一、框架简介 gin 目前应用最广泛的golang框架&#xff0c;甚至已经变成了golang的官方框架&#xff0c;但它主要是一个RESTFul的框架。封装比较优雅&#xff0c;API友好&#xff0c;源码注释比较明确。个人比较推荐。 beego 国内最早的golang框架&#xff0c;也是最全的MV…

opencv (二十二) 创建滑动条

滑动条(Trackbar)是OpenCV动态调节参数特别好用的一种工具,它依附于窗口存在。 创建滑动条:createTrackbar()函数 createTrackbar函数用于创建一个可以调整数值的滑动条(也称轨迹条),并将滑动条附加到指定的窗口上,它往往会和一个回调函数配合起来使用。 int createT…

你的GPT跟ChatGPT可能只差了一个DPU

“人类永远不会嫌网络太快&#xff0c;就像永远不会嫌高铁太快&#xff0c;你只会嫌它慢&#xff0c;希望它更快些。” 一个月内&#xff0c;百度、阿里、腾讯、商汤、讯飞、360等国内大厂扎堆发布“中国版 GPT ”&#xff0c;这家的名字还没记清楚&#xff0c;另一家的又蹦了出…

python逝练系列(终章)

目录 1、(最大数的出现)编写程序读取整数,找出它们中的最大值&#xff0c;然后计算它的出现次数。假设输入以数字0结束。假设你输入的是“352555 0";程序找出的最大数是5&#xff0c;而5的出现次数是4。(提示:维护两个变量max和 count。变量max存储的是当前最大数&#xf…

TypeScript自学文档

目录 1.什么是Ts? 1.1 设计公司&#xff1a;微软 1.2 TS概述 1.3 TS是静态类型 JS是动态类型 1.4 TS是强类型语言 JS是弱类型语言 2.TypeScript编译器 2.1 安装 2.2 TS自动编译和编译选项设置 3.TS的数据类型 3.1 基础数据类型number、string、boolean 3.2 Arrays&a…

【svn】如何批量忽略文件和文件夹

目录 一、通过svn:ignore 1、文件夹空白处右键 TortoiseSVN → Properties 打开 2、New → Other 3、global-ignores属性的值&#xff0c;即需要要忽略的文件 点击OK 4、取消忽略的文件 选中 Rmove &#xff0c;提交就可以看到idea文件了 二、svn:global-ignores 1、右键…

高通开发系列 - msm-4.9中usb初始化流程和adb功能问题

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回高通开发系列 - 总目录 目录 USB功能集android平台adb使能流程Linux系统使能流程USB时钟USB供电U盘不能正常使用分析adb设备无法识别问…

三、JS03 DOM 操作

三、DOM 操作 3.1 JavaScript DOM 操作 DOM (Document Object Model) 为文档对象模型&#xff0c;是 HTML 和 XML 文档的编程接口 DOM 提供了对整个文档的访问模型&#xff0c;将文档作为一个树形结构 树的每个节点表示了一个 HTML 标签或标签内的文本 3.1.1 DOM 操作分类 使…

SSM整合————单表操作基础版

一、创建数据库&#xff1a; 1. 创建一个 web 项目&#xff0c;并部署到 tomcat 服务器中测试项目 是否能够正常加载并访问首页。 2. 完善项目的结构并导入 SSM 相关的jar包 3.创建SSM框架对应的配置文件 springMVC配置文件&#xff1a;1.扫描controller&#xff1b;2.配置视图…

ERROR org.springframework.web.context.ContextLoader

项目启动时报错&#xff1a; ERROR org.springframework.web.context.ContextLoader - Context initialization failed java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.clearCache() 原因分析 这个错误的原因可能是因为 Spring 的不同…

VGG网络简介

1. 背景 VGG是常见的用于大型图片识别的极深度卷积网络&#xff0c; 这里主要介绍VGG网络预测在ImageNet数据集上的训练及预测。 2. ImageNet图像数据集简介 ImageNet包含了145W张224*224像素的三通道彩色图像数据集&#xff0c;图像划分为1000个种类。其中训练集130W张&…

Contest3070 - 计科2101~2104算法设计与分析上机作业05

问题 A: 最小平均等待时间 题目描述 有n个顾客同时在等待一项服务&#xff0c;顾客i需要的服务时间为ti&#xff0c;1≤i≤n。要安排一个服务次序使得平均等待时间最小&#xff08;平均等待时间是n个顾客等待服务时间的总和除以n&#xff09;。请编写算法&#xff0c;计算最小…

大文件上传接口响应超时

背景 开发了一个内容管理发布系统&#xff0c;在后台发布内容信息时&#xff0c;上传了一个较大的视频&#xff08;较大文件≥200M&#xff09;&#xff0c;以往未上传过如此大文件&#xff0c;然后出现报错 Status Code 413 问题&处理过程 1. status code&#xff1a;4…

如何在Linux使用 chattr 命令更改文件或目录的扩展属性?

在 Linux 操作系统中&#xff0c;chattr 命令用于更改文件或目录的扩展属性&#xff0c;包括可写性、可执行性和删除性等。本文将介绍 chattr 命令的使用方法以及常见的参数。 1. chattr 命令的基本语法 chattr 命令的基本语法如下&#xff1a; chattr [选项] [文件或目录]选…

论文阅读---《人类活动识别的准周期时间序列聚类》

论文地址&#xff1a;Quasi-Periodic Time Series Clustering for Human Activity Recognition | SpringerLink 相空间知识概念&#xff1a;相空间相关概念以及轨迹生成_末世灯光的博客-CSDN博客 摘要&#xff1a; 本文利用移动加速度计对时间序列中的周期信号进行分析&…

[python][学习]turtle.circle()函数

turtle.circle()函数 定义&#xff1a;turtle.circle(radius, extentNone) 作用&#xff1a;根据半径radius绘制extent角度的弧形 参数&#xff1a; radius &#xff1a;弧形半径 当radius值为正数时&#xff0c;圆心在…