集群服务器如何解决跨服务器通信?大量并发通信问题?

Nginx tcp负载均衡模块:

1.将client的请求按照 负载均衡算法 分发到服务器

2.负载均衡器与服务器保持心跳机制监测故障、保障服务可靠性

3.可以发现添加新的服务器,方便扩展服务器集群的数量

Nginx反向代理用途:

2.4 用途

  • 隐藏服务器真实ip:使用反向代理,可以对客户端隐藏服务器的ip地址
  • 负载均衡:根据所有真实服务器的负载情况,将客户端请求分发到不同的服务器上
  • 提高访问速度:反向代理服务器可以对静态内容及短时间内有大量访问请求的动态内容提供缓存服务,提高访问速度
  • 提供安全保障:反向代理服务器可以作为应用层防火墙,为网站提供对基于web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等还可以为后端服务器统一提供加密和SSL加速(如SSL终端代理),提供HTTP访问认证等。
Nginx 的配置:

Nginx 的软件包中,我们需要在conf目录下的 nginx.conf文件进行应用配置

#nginx tcp loadbalance config
stream{upstream MyServer{server 127.0.0.1:6000 weight=1 max_fails=3 fail_timeout=30s;server 127.0.0.1:6002 weight=1 max_fails=3 fail_timeout=30s;#服务器配置:地址:端口号 权重 最多失败次数 每次超时事件,超过视为失败(>实现了心跳机制,保障服务可靠性)#若扩展新的服务器,则在此处继续添加即可 }server{proxy_connect_timeout 1s;#若第一次握手时间超过1s,视为失败#proxy_timeout 3s;#连接3s后自动断开(短连接),该服务器聊天需要长连接所以将此注释listen 8000;#监听8000端口,Nginx的反向代理监听端口(客户端直接连接的端口proxy_pass MyServer;#标记需要代理的服务器集群tcp_nodelay on;

基于weight 的权重配置,当所有权重均一致时,实行轮询分发给各个服务器

由服务器的需求和硬件限制时,权重随其相应变化,达到最佳性能

配置完成后:

nginx -s reload //重新加载配置文件启动

./nginx -s reload //平滑重启

Nginx默认安装到 /usr/local/下的nginx目录,进入nginx/sbin内,以管理员身份运行可执行文件

可以通过netstat -tanp 查看nginx的运行情况,nginx服务器为http服务器,为80端口

跨平台通信流程:(Redis 观察者模式)

  1. client1分配在ChatServer1后,ChatServer1 在Redis消息队列中subscribe:订阅client1的信息,client2分配在ChatServer2后,ChatServer2 在Redis消息队列中subscribe:订阅client2的信息
  2. client1 在Charserver1 上登录后,需要给好友client2发送信息,而好友由负载均衡算法分配到Chat Server2上,在ChatServer1的用户登录map表内没有client2 的信息,继而查询用户数据库client2的数据登录显示好友已经登陆
  3. 将需发送的信息publish chat _json:发布到Redis消息队列中,Redis收到后将信息notify:提示给ChatServer2(订阅client2的信息)实现跨服务器通信,而ChatServer2 从Redis 消息队列中获取client1 的信息

从而实现跨服务器的通信(Redis:)

subscribe +s :订阅序号为s的信息后阻塞监听状态,仅接收订阅的信息

publish +s :发布关于序号s的信息,发布成功后订阅方即可接收

Redis 配置文件

.h文件

#ifndef REDIS_H
#define REDIS_H#include <hiredis/hiredis.h>
#include <thread>
#include <functional>
using namespace std;
class Redis
{
public:Redis();~Redis();// 连接redis服务器 bool connect();// 向redis指定的通道channel发布消息bool publish(int channel, string message);// 向redis指定的通道subscribe订阅消息bool subscribe(int channel);// 向redis指定的通道unsubscribe取消订阅消息bool unsubscribe(int channel);// 在独立线程中接收订阅通道中的消息void observer_channel_message();// 初始化向业务层上报通道消息的回调对象void init_notify_handler(function<void(int, string)> fn);private:// hiredis同步上下文对象,负责publish消息redisContext *_publish_context;// hiredis同步上下文对象,负责subscribe消息redisContext *_subcribe_context;// 回调操作,收到订阅的消息,给service层上报function<void(int, string)> _notify_message_handler;
};#endif

Redis.cpp

#include "redis.hpp"
#include <iostream>
using namespace std;Redis::Redis()
: _publish_context(nullptr), _subcribe_context(nullptr)
{
}Redis::~Redis()
{if (_publish_context != nullptr){redisFree(_publish_context);}if (_subcribe_context != nullptr){redisFree(_subcribe_context);}
}bool Redis::connect()
{// 负责publish发布消息的上下文连接_publish_context = redisConnect("127.0.0.1", 6379);if (nullptr == _publish_context){cerr << "connect redis failed!" << endl;return false;}// 负责subscribe订阅消息的上下文连接_subcribe_context = redisConnect("127.0.0.1", 6379);if (nullptr == _subcribe_context){cerr << "connect redis failed!" << endl;return false;}// 在单独的线程中,监听通道上的事件,有消息给业务层进行上报thread t([&]() {observer_channel_message();});t.detach();cout << "connect redis-server success!" << endl;return true;
}// 向redis指定的通道channel发布消息
bool Redis::publish(int channel, string message)
{redisReply *reply = (redisReply *)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());if (nullptr == reply){cerr << "publish command failed!" << endl;return false;}freeReplyObject(reply);return true;
}// 向redis指定的通道subscribe订阅消息
bool Redis::subscribe(int channel)
{// SUBSCRIBE命令本身会造成线程阻塞等待通道里面发生消息,这里只做订阅通道,不接收通道消息// 通道消息的接收专门在observer_channel_message函数中的独立线程中进行// 只负责发送命令,不阻塞接收redis server响应消息,否则和notifyMsg线程抢占响应资源if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "SUBSCRIBE %d", channel)){cerr << "subscribe command failed!" << endl;return false;}// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)int done = 0;while (!done){if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done)){cerr << "subscribe command failed!" << endl;return false;}}// redisGetReplyreturn true;
}// 向redis指定的通道unsubscribe取消订阅消息
bool Redis::unsubscribe(int channel)
{if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "UNSUBSCRIBE %d", channel)){cerr << "unsubscribe command failed!" << endl;return false;}// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)int done = 0;while (!done){if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done)){cerr << "unsubscribe command failed!" << endl;return false;}}return true;
}// 在独立线程中接收订阅通道中的消息
void Redis::observer_channel_message()
{redisReply *reply = nullptr;while (REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply)){// 订阅收到的消息是一个带三元素的数组if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr){// 给业务层上报通道上发生的消息_notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);}freeReplyObject(reply);}cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}void Redis::init_notify_handler(function<void(int,string)> fn)
{this->_notify_message_handler = fn;
}

Redis 问题:

问题背景:在集群服务器中采用Redis发布订阅功能作为消息中间件,解耦合服务器的消息通信,实现跨服务器之间的通信,以hiredis 作为客户端编程

1. 问题一:Publish 向中间件发布信息不成功

?hiredis提供发布消息的接口函数redisCommend 在发布-订阅命令需要在不同的上下文环境中执行

 redisReply *reply = (redisReply *)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());

_publish_context(发布的上下文环境)

_subscribe_conntext(订阅的上下文环境)

2. 问题2:subscribe 订阅失败,客户端无响应

查看出现问题的服务器线程号

stu@stu-VMware-Virtual-Platform:~/obj_chat$ ps -ef | grep Server
stu       5904   1529  0 11:08 pts/1    00:00:00 ./Server.out 127.0.0.1 9000
stu       5909   1442  0 11:08 pts/0    00:00:00 ./Server.out 127.0.0.1 9002
stu       6004   5991  0 11:13 pts/4    00:00:00 grep --color=auto Server

在登录端口号9002的服务器 进程号为5909的服务器出现问题,通过gdb调试:用info threads可以输出当前进程所有线程的信息,可以看到:

Server.out是主线程,也就是muduo库的I/O线程,现在处理epoll_wait状态,等待新用户的连接;

  • 而EventLoop事件循环有三个线程,分别是ChatServer0、ChatServer1、ChatServer2,
  • 其中ChatServer1和ChatServer2处在epoll_wait状态,等待已连接用户的读写事件,
  • 但是ChatServer0却阻塞在__libc_recv函数处不能继续处理逻辑业务,不能给客户端回复响应,导致客户端无应答。

线程池里面的redisGetReply抢了上面订阅subscribe的redisCommand底层调用的redisGetReply的响应消息,导致ChatServer0线程阻塞在这个接口调用上,无法再次回到epoll_wait处了,这个线程就废掉了,如果工作线程全部发生这种情况,最终服务器所有的工作线程就全部停止工作了!

解决方案

从hiredis的redisCommand源码上可以看出,它实际上相当于调用了这三个函数:

  • redisAppendCommand 把命令写入本地发送缓冲区
  • redisBufferWrite 把本地缓冲区的命令通过网络发送出去
  • redisGetReply 阻塞等待redis server响应消息

既然在muduo库的ThreadPool中单独开辟了一个线程池,接收this->_context上下文的响应消息,因此subcribe订阅消息只做消息发送,不做消息接收就可以了,如下:

// /订阅通道
void subscribe(int channel)
{// 只负责发送命令,不阻塞接收redis server响应消息,否则和notifyMsg线程抢占响应资源if (REDIS_ERR == redisAppendCommand(this->_context, "SUBSCRIBE %d", channel)){LOG_ERROR << "subscribe [" << channel << "] error!";return;}// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)int done = 0;while (!done) {if (REDIS_ERR == redisBufferWrite(this->_context, &done)){LOG_ERROR << "subscribe [" << channel << "] error!";return;}}LOG_INFO << "subscribe [" << channel << "] success!";

3. Redis 的动态库调用问题:

编译器只会使用/lib和/usr/lib这两个目录下的库文件,通常通过源码包进行安装时,如果不指定--prefix,会将库安装在/usr/local/lib目录下;当运行程序需要链接动态库时,提示找不到相关的.so库,会报错。也就是说,/usr/local/lib目录不在系统默认的库搜索目录中,需要将目录加进去。

1、首先打开/etc/ld.so.conf文件:sudo vi /etc/ld.so.conf

2、加入动态库文件所在的目录:执行vi /etc/ld.so.conf,在"include ld.so.conf.d/*.conf"下方增加"/usr/local/lib"。

3. 运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,可能会找不到刚安装的库。sudo ldconfig

一定要执行ldconfig。否则可能目录下已经有.so文件也可能会报找不到的错误。

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

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

相关文章

在golang中Sprintf和Printf 的区别

最近一直在学习golang这个编程语言&#xff0c;我们这里做一个笔记就是 Sprintf和Printf 的区别 fmt.Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。 fmt.Printf 根据格式化参数生成格式化的字符串并写入标准输出。由上面就可以知道&#xff0c;fmt.Sprintf返回的…

php随机海量高清壁纸系统源码,数据采集于网络,使用很方便

2022 多个分类随机海量高清壁纸系统源码&#xff0c;核心文件就两个&#xff0c;php文件负责采集&#xff0c;html负责显示&#xff0c;很简单。做流量工具还是不错的。 非第三方接口&#xff0c;图片数据采集壁纸多多官方所有数据&#xff01; 大家拿去自行研究哈&#xff0…

在 Windows 上开发.NET MAUI 应用_2.生成你的第一个应用

先决条件 Visual Studio 2022 17.8 或更高版本&#xff0c;并安装了 .NET Multi-platform App UI 工作负载。 可参考上一篇文章&#xff1a;http://t.csdnimg.cn/n38Yy 创建应用 1.启动 Visual Studio 2022。 在开始窗口中&#xff0c;单击“创建新项目”以创建新项目&#…

【B树、B-树、B+、B*树】

目录 一、B-树&#xff08;即B树&#xff09;的定义及操作1.1、定义1.2、操作1.2.1、查找1.2.2、插入1.2.3、删除 二、B树的定义及操作2.1、定义2.2、操作2.2.1、查找2.2.2、插入2.2.3、删除 三、B*树 一、B-树&#xff08;即B树&#xff09;的定义及操作 1.1、定义 B-tree即…

【c++11】什么情况下需要封装set/get

文章目录 一、平凡类型与非平凡类型什么时候使用set/get1.平凡类型2.非平凡类型 二、构造函数参数较多解决办法1.把所有参数放到一个结构体里面2.使用build设计模式 三、如果构造函数众多&#xff08;参数很多&#xff09;1.模仿make_unique&#xff0c;就地构造2.基于build设计…

Missing script:‘dev‘

场景&#xff1a; npm run dev 原因&#xff1a;没有安装依赖&#xff0c;可用镜像安装&#xff08;详见下图ReadMe 蓝色字体&#xff09;&#xff0c;没安装依赖可从package-lock.json文件是否存在看出&#xff0c;存在则有依赖 解决&#xff1a;

二叉树、B树/B-树

二叉树 在中文语境中,节点结点傻傻分不清楚,故后文以 node 代表 "结点",root node 代表根节点,child node 代表 “子节点” 二叉树是诸多树状结构的始祖,至于为什么不是三叉树,四叉树,或许是因为计算机只能数到二吧,哈哈,开个玩笑。二叉树很简单,每个 no…

useState函数

seState是一个react Hook(函数)&#xff0c;它允许我们像组件添加一个状态变量&#xff0c;从而控制影响组件的渲染结果 数据驱动试图 本质&#xff1a;和普通JS变量不同的是&#xff0c;状态变量一旦发生变化组件的视图UI也会随着变化(数据驱动试图) 使用 修改状态 注意&am…

单链表算法 - 链表分割

链表分割_牛客题霸_牛客网现有一链表的头指针 ListNode* pHead&#xff0c;给一定值x&#xff0c;编写一段代码将所有小于x的。题目来自【牛客题霸】https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70思路: 代码: /* struct ListNode {int val;struct List…

英福康INFICON TranspectorWare v3 RGA软件操作说明

英福康INFICON TranspectorWare v3 RGA软件操作说明

Python一对一辅导答疑|Rust 德国

你好&#xff0c;我是悦创。 下面是答疑内容。 在 Rust 中&#xff0c;方法的调用方式通常取决于它们是如何定义的。在你的例子中&#xff0c;print_drink方法最初是作为一个接受Drink类型实例作为参数的关联函数&#xff08;类似于静态方法&#xff09;定义的。后来&#xff…

供应链管理(SCM):如何在颜值和体验上发力

要在供应链管理系统&#xff08;SCM&#xff09;中在颜值和体验上发力&#xff0c;让用户感觉耳目一新&#xff0c;可以采取以下措施&#xff1a; 界面设计优化&#xff1a; 对供应链管理系统的界面进行优化&#xff0c;注重界面的美观、简洁和易用性。采用现代化的设计风格、…

技能 | postman接口测试工具安装及使用

哈喽小伙伴们大家好!今天来给大家分享一款轻量级,高效好用的接口测试工具-postman. Postman是一个流行的API开发工具&#xff0c;主要用于测试、开发和文档化API。以下是关于Postman的介绍及其主要使用场景&#xff1a; Postman介绍&#xff1a; 1. 功能丰富的API客户端&#…

在SpringCloud中如何轻松实现微服务间的通信

在Spring Cloud中&#xff0c;实现微服务间的通信非常简单。Spring Cloud提供了多种方式来进行微服务之间的通信&#xff0c;包括使用RestTemplate、Feign、Ribbon、Eureka等组件。下面我将详细介绍这些方式的使用方法。 使用RestTemplate进行通信&#xff1a; RestTemplate是S…

django报错(一):python manage.py makemigrations,显示“No changes detected”

执行python manage.py makemigrations命令无任何文件生成&#xff0c;结果显示“No changes detected”。 解决方案一&#xff1a; 1、执行命令&#xff1a;python manage.py makemigrations –empty appname 2、删除其中的0001_initial.py文件&#xff08;因为这个文件内容是…

【docker 部署springboot项目】

一、docker安装 1.检查Linux内核版本高于3.10才可安装 uname -r 2. 卸载旧版本 sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 3. 使用docker仓库进行安装 安装所需的软…

Qt MV架构-委托类

一、基本概念 与MVC模式不同&#xff0c;MV视图架构中没有包含一个完全分离的组件来处理与用户的交互。 一般地&#xff0c;视图用来将模型中的数据显示给用户&#xff0c;也用来处理用户的输入。为了获得更高的灵活性&#xff0c;交互可以由委托来执行。 这些组件提供了输入…

Python入门------pycharm加载虚拟环境

pycharm虚拟环境配置&#xff1a; 在按照前面的办法&#xff0c;配置好虚拟环境后,如果我们需要到虚拟环境开发&#xff0c;就需要给编译器配置虚拟环境 1.打开编译器&#xff0c;点击右下角的interpreter选项 2. 点击ADD Interpreter,添加虚拟环境 3. 因为我们使用的是原始…

欧式空间、傅里叶级数与希尔伯特空间的解释

欧式空间&#xff08;欧几里得空间&#xff09; 欧几里得几何就是中学学的平面几何、立体几何&#xff0c;在欧几里得几何中&#xff0c;两平行线任何位置的间距相等。 而中学学的几何空间一般是2维&#xff0c;3维&#xff08;所以&#xff0c;我们讨论余弦值、点间的距离、内…

数据库管理的艺术(MySQL):DDL、DML、DQL、DCL及TPL的实战应用(下:数据操作与查询)

文章目录 DML数据操作语言1、新增记录2、删除记录3、修改记录 DQL数据查询语言1、查询记录2、条件筛选3、排序4、函数5、分组条件6、嵌套7、模糊查询8、limit分页查询 集合操作union关键字和运算符in关键字any关键字some关键字all关键字 联合查询1、广义笛卡尔积2、等值连接3、…