一个模拟的负载均衡系统的实现

一、标题

一个模拟的负载均衡系统的实现

二、题目

为了构建可伸缩的,高可用的网络服务,很多大型网站都采用了负载均衡技术。

利用负载均衡技术,可以将多台廉价的、低性能的服务器,组合成一台性能强劲的,高可用的虚拟服务器。

负载均衡的常见实现方式大致如下:

将网络服务的地址(如公网IP地址、tcp套接字等)部署在负载均衡器上,而不是真实的服务器上;

将负载均衡器作为网络服务的总入口,接收用户的所有访问请求;

负载均衡器接收到用户的访问请求后,将访问请求按照一定的策略分发给某一台真实的服务器进行处理;

真实服务器,对访问请求进行处理后,将处理结果发送给负载均衡器;

负载均衡器接收到真实服务器的处理结果,将他发送给用户。

下图展示了一个负载均衡系统的组网结构,图中包含了1台负载均衡器,3台真实的服务器。公网IP配置在负载均衡器上,负载均衡器与真实服务器之间,则通过私网地址进行通讯。

在这里插入图片描述

图1 负载均衡组网结构

有关负载均衡的技术原理及更多详细信息,请上网查阅相关资料或阅读相关书籍。

本题的任务,是在PC机上实现一个模拟的负载均衡系统。

他包含如下3个可执行程序:

服务端 (server.exe)――辅助程序,通过UDP端口,提供时间查询服务。

负载均衡器(LB.exe) ――核心程序,用于实现负载均衡功能。

客户端 (client.exe)――辅助程序,通过UDP端口,访问时间查询服务。

3个程序的协作关系如下图所示,其中客户端与服务端程序,需要起多个进程。

在这里插入图片描述
图 2 系统协作
上图中,每一个方框表示一个进程。每一个进程拥有一个唯一的id(注意,这是由用户配置的id,并非操作系统为进程分配的pid),进程之间一律通过UDP协议进行通信。系统运行起来之后,客户端通过UDP协议向负载均衡器发送“时间请求”消息,负载均衡器通过UDP协议将消息分发给某个服务端进行处理。服务端返回“时间应答”消息给负载均衡器,负载均衡器将“时间应答”消息返回给客户端。

为了简化实现,本模拟系统中,所有消息,都采用如下结构体进行封装。通过msg_type字段的值,来区分不同类型的消息。

typedef struct

{
   

    /* 消息的发送进程是谁,就填谁的id */

    unsigned src_id;

/* 消息的接收进程是谁,就填谁的id */

    unsigned dst_id;

/* 发送“时间请求”消息时填写,

回复“时间应答”消息时,其值要与请求消息保持一致。 */

    unsigned usr_id;

/* 消息类型:0, 时间请求;1, 时间答应;2, 心跳请求;3, 心跳应答 */

    unsigned msg_type;

/* 服务端回复“时间应答”消息时,

在data中填入当前时间的字符串,形式如“2013-06-20 13:56:28”即可  */

    char data[32];

} t_msg;

三、功能需求

a)服务端程序

服务端程序,需要起多个进程,每个进程拥有一个唯一的id,绑定到一个唯一的UDP端口上。每个服务端进程通过自己绑定的UDP端口接收“时间请求”消息,如果消息中的dst_id等于自己的id,就向对端发送“时间应答”消息。否则,就丢弃此消息。

每个服务端进程的id、udp端口号,可以通过命令行参数传入,可以通过配置文件配置,也可以在进程运行时指定。三种方式,只要支持任意一种就行了。

服务端程序,需要具备一个调试开关。在运行过程中,可以打开/关闭调试开关。当调试开关打开后,服务端进程需要将自己接收/发送的每一个消息,都实时显示给用户看。

服务端程序,需要具备统计功能。在运行过程中,可以随时可看,每个服务端进程接收了多少条消息(正确的多少条,错误的多少条),应答了多少条消息。

b)负载均衡程序

负载均衡程序,只要启动一个进程即可。此进程拥有一个唯一的id,绑定到两个不同的UDP端口上。一个UDP端口(下文称为client_udp_port)用于收发客户端的消息,一个UDP端口(下文称为server_udp_port)用于收发服务端的消息。

负载均衡进程的id是多少,绑定的两个udp端口号是多少,支持多少个服务端,每个服务端的id、udp端口各是多少,均通过配置文件进行配置的。负载均衡进程启动时读入这些信息,运行过程中,不会改变。

对于客户端有多少个,每个客户端的id是多少,UDP端口号是多少,负载均衡进程对这些信息是一无所知的,也是无法预测的。

负载均衡进程通过client_udp_port接收到客户端的“时间请求”消息后,如果消息中的dst_id不等于自己的id,就丢弃此消息。否则,就按照轮转算法选出一个服务端,将时间请求消息中的dst_id改成此服务端的id后,将消息通过server_udp_port分发给该服务端处理。

负载均衡进程通过server_udp_port接收到客户端的“时间应答”消息后,将消息中的src_id改成自己的id,然后将消息通过client_udp_port发送给消息中的dst_id所指示的客户端。

负载均衡程序,需要具备一个调试开关。在运行过程中,可以打开/关闭调试开关。当调试开关打开后,程序需要将自己接收/发送的每一个消息,都实时显示给用户看。

负载均衡程序,需要具备统计功能。在运行过程中,可以随时可看,本进程从客户端接收了多少条消息(正确的多少条,错误的多少条),向客户端发送了多少条消息,从服务端接收了多少条消息(正确的多少条,错误的多少条),向服务端发送了多少条消息。

负载均衡程序,还需要具备日志功能。在运行过程中,如果出现异常事件(如UDP接收、发送失败等),需要记录日志,供后续分析查看。日志中,尽当尽可能包含详细的信息,如异常事件发生的时间、事件描述、事件原因等。

c)客户端程序

客户端程序,需要起多个进程,每个进程拥有一个id,一个usr_id,绑定到一个默认分配的UDP端口上。每个客户端进程启动后,通过自己绑定的UDP端口向负载均衡器发送n条“时间请求”消息,并接收相应的时间应答消息。时间请求消息中的src_id填写自己的id,usr_id填写自己的usr_id,dst_id填写负载均衡器进程的id。

每个客户端进程的id、usr_id,发送的消息条数n,可以通过命令行参数传入,可以通过配置文件配置,也可以在进程启动时指定。三种方式,只要支持任意一种就行了。

注意,不同客户端进程的id可以相同、但usr_id不可以相同。

客户端进程,在运行过程中,需要将自己接收/发送的每一个消息,都实时显示给用户看。客户端进程完成自己的任务后,显示一下相关的统计信息,即可退出。统计信息包括本进程发送了多少条消息,接收了多少条消息(正确的多少条,错误的多少条)。

四、代码实现

服务器端

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <signal.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;


#define IPSTR "127.0.0.1"
//定义一个通用的数据结构
struct msg
{
   
    int src_id;         //发送者的id
    int des_id;         //接受者的id
    
    int usr_id;
    //消息的类型,
    int msg_type;

    //数据部分
    char data[32];
};


class CServer
{
   
public:
    //创建对象的时候必须给定id 和端口号
    CServer(int i,int p):id(i),port(p){
   }

    //将服务器的id 和 port 写入配置文件
    void InitCnf();

    //初始化套接字,创建套接字并绑定端口
    int InitSock(); 

    //保存日志文件
    void CreatLog(msg &data);

    //实时显示用户的请求信息与服务器的发送信息
    void display(msg &data);

    //处理socket端的程序
    void dealSock(int sockfd, bool &flag);

    //接受负载均衡器发来的数据
    bool request(int sockfd, msg &data, struct sockaddr_in &caddr);

    //当接受到正确的信息后,服务器向负载均衡器回复数据包
    void response(int sockfd, msg &data, struct sockaddr_in &addr);

    //调试器开关 通过监控键盘终端的文件描述符,输入数据为debug 时开关打开
    void debug(bool &flag);

    //拼接回复的信息
    void creatData(msg &data);

    //负责总的调用过程
    void mainfun();
private:
    int id;         //记录服务器的id
    int port;       //记录服务器的端口
};

void CServer::dealSock(int sockfd, bool &flag)
{
   
    msg data;
    struct sockaddr_in addr;
    if(request(sockfd, data, addr))
    {
   
        if(flag)
        {
   
            display(data);
            //接受数据放入日志文件
            CreatLog(data);
            creatData(data);
            //回复数据加入日志文件
            CreatLog(data);
            display(data);
        }
        else
        {
   
            CreatLog(data);
            creatData(data);
            //回复数据加入日志文件
            CreatLog(data);
        }
        response(sockfd, data, addr);
    }
}

//在这之前首先初始化配置文件,创建一个对象
void  CServer::mainfun()
{
   
    fd_set fdset;
    FD_ZERO( &fdset );
    int fd = 0;
    int sockfd = InitSock();

    FD_SET(fd, &fdset);
    FD_SET(sockfd, &fdset);
    //默认不开启调试器
    bool flag = false;
    while(1)
    {
   
        int n = select(sockfd+1, &fdset, NULL, NULL, NULL);
        assert(n != -1);
        
        if(FD_ISSET(fd, &fdset))
        {
   
            debug(flag);    
        }
        if(FD_ISSET(sockfd, &fdset))
        {
   
            //处理sock的数据
            dealSock(sockfd, flag);
        }
        FD_ZERO( &fdset );
        FD_SET(fd, &fdset);
        FD_SET(sockfd, &fdset);
    }
}

//通过终端控制调试器
void CServer::debug(bool &flag)
{
   
    char buff[128] = {
   0};
    int n = read(0, buff, 127);
    if(strncmp(buff, "debug", 5) == 0)
    {
   
        //进入调试状态
        cout << "===============进入debug模式============" << endl;
        f
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值