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

news2024/12/24 22:17:19

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;
            }
        }
    // redisGetReply

    return 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.coloradmin.cn/o/1931603.html

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

相关文章

大数据技术基础

一、大数据平台 1.大数据平台方案步骤&#xff1a; ①市场上有哪些大数据平台 ②硬件、系统、业务增长等方面 ③方案是否通过 通过后&#xff1a;按照一期目标投入 先虚拟环境部署联系&#xff0c;再实际部署 《大数据架构介绍》《Hadoop架构解析》《Hadoop集群规划》 《H…

已有nodejs的情况下安装nvm

文章目录 前言一、下载地址二、使用方法1.已安装nodejs2.未安装过nodejs3.注意事项4.测试是否安装成功5.切换nodejs版本方式 前言 作为一个前端开发人员&#xff0c;在一开始入行就安装了nodejs&#xff0c;这也是必不可少的开发环境&#xff0c;但总会遇到某些插件和当前node…

【保姆级】Python项目部署到Linux生产环境(uwsgi+python+flask+nginx服务器)

1.安装python 我这里是3.9.5版本 安装依赖&#xff1a; yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make -y 根据自己的需要下载对应的python版本&#xff1a; cd /usr/local wget https://www.python.or…

洛谷 P1056 [NOIP2008 普及组 T2]:排座椅 ← 贪心算法

【题目来源】https://www.luogu.com.cn/problem/P1056https://www.acwing.com/problem/content/436/【题目描述】 上课的时候总有一些同学和前后左右的人交头接耳&#xff0c;这是令小学班主任十分头疼的一件事情。 不过&#xff0c;班主任小雪发现了一些有趣的现象&#xff0c…

云动态摘要 2024-07-16

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起&#xff01; [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造&…

【Flask从入门到精通:第十二课:常用模块、蓝图 Blueprint】

常用模块 Faker 文档: https://faker.readthedocs.io/en/master/locales/zh_CN.html 批量生成测试数据: https://github.com/joke2k/faker pip install faker -i https://pypi.douban.com/simple代码&#xff1a; from flask import Flask from flask_sqlalchemy import S…

Nginx介绍、安装、使用

更多优质内容欢迎访问我的个人博客网站&#xff1a;www.zpf0000.com Nginx官网 官网&#xff1a;nginx news 什么是Nginx&#xff1f; Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访…

exif格式及解析库easyexif使用介绍

1. JPEG文件结构和EXIF数据的信息 JPEG文件以字符串"0xFFD8"开头表示图像信息开始,以字符串"0xFFD9"结尾表示图像信息结束。 在JPEG文件头中有一系列"0xFF??"格式的数据段,称为"标识",用来标记JPEG文件的信息段。 0xFFE0-0xFFEF之间…

SQL 获取employees中的first_name

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 现有employee…

在 PostgreSQL 里如何处理数据的存储优化和数据库备份的时间窗口冲突?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 在 PostgreSQL 中处理数据存储优化和数据库备份时间窗口冲突 在 PostgreSQL 中处理数据存储优化和数据…

智能测评养号系统:解决效率与成本难题,让测评效率翻倍

各位跨境电商从业者好,我是陈哥。今天我想和大家分享一下关于测评养号系统的一些见解和经验。 近期,不少做测评工作室的朋友向我咨询,希望能找到一种新型的测评养号系统,以解决当前面临的诸多问题,如成本高昂、效率低下、账号纯净度不够、操作不便捷等。 众所周知,测评养号系统…

某客户管理系统Oracle RAC节点异常重启问题详细分析记录

一、故障概述 某日10:58分左右客户管理系统数据库节点1所有实例异常重启&#xff0c;重启后业务恢复正常。经过分析发现&#xff0c;此次实例异常重启的是数据库节点1。 二、故障原因分析 1、数据库日志分析 从节点1的数据库日志来看&#xff0c;10:58:49的时候数据库进程开始…

Qt实现IP地址输入框-自定义控件

在 许多应用程序中&#xff0c;我们经常需要使用IP地址。为了方便用户输入和处理&#xff0c;一个好的解决方案是使用自定义控件。本示例代码使用Qt编写一个名为“IPAddress”的自定义控件来实现IP地址的输入功能。通过使用此控件&#xff0c;用户可以方便地输入和处理IP地址。…

【源码交付】一站式自助数据分析解决方案(JVS-BI):系统架构蓝图

1.引言 JVS-BI是一体化、自助式的数据分析平台&#xff0c;它采用的高度集成化的思路&#xff0c;针对企业级用户&#xff0c;提供集中仓库便捷分析的企业级数据开发套件&#xff0c;解决企业各种需要数据分析的场景&#xff0c;多种数据库、多种业务系统、跨库关联、离线数据…

新版网页无插件H.265播放器EasyPlayer.js如何测试demo视频?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff1b;支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#xff0…

【病毒分析】Babyk加密器分析-NAS篇

1.前情提要 继上篇分析了关于Babyk加密器在Windows环境的行为特征&#xff0c;本篇是针对NAS系统的相关分析。 2.总体行为 3.密钥下发&#xff08;Builder.exe) 这里可以通过VS生成了Builder.exe来实现对其Builder过程进行分析&#xff0c;可以看到主要是对这部分文件的处理…

Excel 学习手册 - 精进版(包括各类复杂函数及其嵌套使用)

作为程序员从未想过要去精进一下 Excel 办公软件的使用方法&#xff0c;以前用到某功能都是直接百度&#xff0c;最近这两天跟着哔哩哔哩上的戴戴戴师兄把 Excel 由里到外学了一遍&#xff0c;收获良多。程序员要想掌握这些内容可以说是手拿把掐&#xff0c;对后续 Excel 的运用…

Elastic 线下 Meetup 将于 2024 年 7 月 27 号在深圳举办

2024 Elastic Meetup 深圳站活动&#xff0c;由 Elastic、腾讯、新智锦绣联合举办&#xff0c;现诚邀广大技术爱好者及开发者参加。 时间地点 2024年 7 月 27 日 13:30-18:00 活动地点 中国深圳 南山区海天二路 33 号腾讯滨海大厦 北塔 3 楼多功能厅 ​ 活动流程 14:00-15…

如何轻松统管虚拟化和容器环境?一文了解 SmartX 虚拟化容器融合基础设施

随着越来越多的企业完成应用容器化改造&#xff0c;应用负载的运行环境也变得越来越复杂——近 60% 的企业正在或计划同时采用虚拟化环境和容器环境运行应用系统*&#xff0c;以满足不同业务在性能和敏捷性等方面的不同需求。不过&#xff0c;虚拟化和容器平台通常由不同的厂商…

基于单片机STC89C52和GSM实现的远程拨号开锁设计(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于单片机STC89C52和GSM实现的远程拨号开锁设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 摘要 仿真图 单片机系统流程图 实物图 代码 系统论文 资源下载 摘要 本文介…