关于负载均衡和过载保护的一些想法和实现

最近需要给一个现网server增加过载保护的功能,借此机会也思考了很多,简单谈谈我对这两个概念的理解和实现方法。
一.负载均衡
简单来说,就是按照目标server的参数进行合理分配,这个参数可以是失败率,也可以是响应时间,也可以是请求量,甚至是随机数。
我们来按照从简单到复杂逐个看一下几种实现。
1.轮询式
逻辑比较简单,直接看代码:

 

 vector<Server*> vecServer;

while(1){ 

      Server* server = vecServer[curIndex % vecServer.size()];   

      curIndex ++;

 

}

如上代码就是一个简单的轮询式分配方法,这种方法优点实现简单,cpu计算少,缺点就是无法动态判断server的状态,当后端有一台server挂掉的时候,会至少1/vecServer.size()的请求。(最为严重的情况是由于单台后端server的超时导致前段全部挂死)。而且这种分配方法有一个bug,就是当每次请求结束后就释放内存,那么curIndex永远都只会为0,即每次都请求第一个server。

2.定死权重式
这种方式适用于那种需要实现就规定后端server的权重,比如A比Bserver的响应速度快,我们希望A接受的请求比B多。

//假设A,B, C server的权重分别为 10 5 2
typedef struct _serverinfo{   
 //server 指针    Server* server;   
 //权重    int weight;}ServerInfo;
vector<ServerInfo*> vecServer;
vecServer.push_back(A);
vecServer.push_back(B);
vecServer.push_back(C);
 vector<int> vecWeight;
for (unsigned i = 0; i < vecServer.size(); i++)
{
          vecWeight.insert(vecWeight.end(),vecServer[i]->weight,i);
}
while(1){  
     index = vecWeight[random() % vecWeight.size()]; 
    Server* server = vecServer[index].server;
         }
 
上面的代码也比较好理解,一共有两个数组,一个是server信息数组vecServer,一个是权重数组vecWeight。在分配server时,先通过权重数组vecWeight获取到server信息数组的下标,然后分配server。
这样的做法在我1年半写的一个项目是有使用的,经过统计效果是很不错的,基本是访问量是严格按照权重比分配的。这样做的cpu消耗也不高,但是缺点也是显而易见的,就是还是没有办法动态调整权重,需要人为去修改。所以我们接下来看第三种。

3.动态调整权重
要讨论这种方法前,我们先要明确几个希望使用他的原因:

1.我们希望server能够自动按照运行状态进行按照权重的选择
2.我们不希望手工去配置权重变化

然后是我们实现方法,很明显,我们需要一个基准来告诉我们这台server是否是正常的。这个基准是什么,是历史累计的平均值。比如如果是按照响应时间分配权重,那么就是所有后端server历史累计的平均响应时间,如果是错误率也是如此。
那么一旦调整了权重,我们什么时候来调整权重呢,调整比例是怎样呢?按照我的经验,一般是隔一段固定时间才进行调整,如果正常但是权重过低,那么就按照20%的比例恢复;如果server不正常,那么直接按照当前server响应时间/历史平均响应时间进行降权。这里的逻辑之所以不一样是有原因的,因为服务出现问题的时候,我们是能够知道这坏的程度有多少的,就是当前server响应时间/历史平均响应时间进行降权;但是要恢复的时候,你并不能保证server能够支撑到多大的访问量,所以只能按照20%放量来试。也避免滚雪球效应的发生。
我们来看一下代码。

typedef struct _serverinfo
{
    unsigned        _svr_ip;            //目标主机
    float           _cfg_wt;            //配置的权重
    float           _cur_wt;            //当前实际权重
    int             _req_count;         //请求数
    float           _rsp_time;    //请求总响应时间
    float           _rsp_avg_time;      //请求平均响应时间
    int             _rsp_error;         //请求错误数
}ServerInfo;
 
vector<ServerInfo*> vecServer;
int total_rsp_time = 0;
int total_req_count = 0;
 
unsigned int comWeight = 100;
unsigned int MaxWeight = 1000;
while(1)
{
    //按照文中第二种方式进行server分配
    serverInfo._req_count++;
    serverInfo._rsp_time+=rsp_time;//响应时间
 
    total_req_count++;
    total_rsp_time += rsp_time;
 
    if(! 需要重建权重)
    {
        continue;  
    }
 
    float total_rsp_avg_time = (float)total_rsp_time / (float)total_req_count;
    for(vector<ServerInfo*>::iterator it = vecServer.begin();it!=vecServer.end();++it)
    {
        it->_rsp_avg_time = (float)it->_rsp_time / (float)it->_req_count;
        if(it->_rsp_avg_time > total_rsp_avg_time)
        {
            it->_cur_wt = int(comWeight*total_rsp_avg_time/it->_rsp_avg_time); 
        }
        else
        {
            it->_cur_wt *= 1.2;
        }
        it->_cur_wt = it->_cur_wt < MaxWeight ? it->_cur_wt : MaxWeight;
    }
 
    //按照文中第二种方式重建权重数组
}

以上基本展示了动态调整的过程,代码可能只是起演示作用,很多比如越界的检测都没有做,大家参照就好~

OK,到这里我们基本就结束了负载均衡的讨论了,但是还有一个话题:过载保护。
二.过载保护
关于过载保护其实经常适合负载均衡结合在一起使用的,但有两个问题:

1.过载参照的基准是谁。
是上面代码中的total_rsp_avg_time吗?
不是,因为除非所有机器的正常性能完全一样,
否则不可以拿total_rsp_avg_time来作为某台机器的负载基准。
而能拿来做参照的,只有这台server自身的历史累计值。
2.怎么实现过载保护。其实很简单,我们定义两个值_cur_max_queue_cnt和_queue_req_cnt,
意义分别是这个server上在一段时间内允许分配的最多次数和当前已经排队的个数。
_cur_max_queue_cnt值是通过当前时间段响应时间和历史累计时间算出来的。
每次使用分配的server前,都要判断一下_queue_req_cnt是否达到了_cur_max_queue_cnt,
如果达到了,分配失败。否则分配成功并且_queue_req_cnt++。

代码如下:

typedef struct _serverinfo
{
    unsigned        _svr_ip;            //目标主机
    float           _cfg_wt;            //配置的权重
    float           _cur_wt;            //当前实际权重
    int             _req_count;         //请求数
    float           _rsp_time;    //请求总响应时间
    float _rsp_avg_time;      //请求平均响应时间
    int             _rsp_error;         //请求错误数
 
    int             _total_req_count;           //总的请求数
    float _total_rsp_avg_time;        //总的请求平均响应时间
    int             _total_rsp_error;           //总的请求错误数
    float           _total_rsp_time;        //总的请求时间
 
    float           _cur_max_queue_cnt; //当前实际允许的最大排队请求数
    int             _queue_req_cnt;         //当前排队请求数
 
}ServerInfo;
 
float comxQueueSize = 1000;
float maxQueueSize = 10000;
while(1)
{
    //按照文中第二种方式进行server分配
 
    if(serverInfo._queue_req_cnt > serverInfo._cur_max_queue_cnt)
    {
        //分配失败
        continue;
    } serverInfo._queue_req_cnt ++;
 
    serverInfo._req_count++;
    serverInfo._rsp_time+=rsp_time;//响应时间
 
    serverInfo._total_req_count++;
    serverInfo._total_rsp_time+=rsp_time;//响应时间
 
    total_req_count++;
    total_rsp_time += rsp_time;
 
    if(! 需要重建权重)
    {
        continue;  
    }
 
    //按照第三种方法重新分配权重
    //按照文中第二种方式重建权重数组
 
    //其实和上面的循环合并成一个
    for(vector<ServerInfo*>::iterator it = vecServer.begin();it!=vecServer.end();++it)
    {
        it->_total_rsp_avg_time = (float)it->_total_rsp_time / (float)it->_total_req_count;
        if(it->_rsp_avg_time > it->_total_rsp_avg_time)
        {
            it->_cur_max_queue_cnt = int(comxQueueSize*it->_total_rsp_avg_time/it->_rsp_avg_time); 
        }
        else
        {
            it->_cur_max_queue_cnt *= 1.2;
        }
        it->_cur_max_queue_cnt = it->_cur_max_queue_cnt < maxQueueSize ? it->_cur_max_queue_cnt : maxQueueSize;
        it->_queue_req_cnt = 0;
    }
 

上面的代码为了演示方便,所以把两个for循环拆开了,实际上是应该合到一个里面写的。
OK,就是这样,大家如果有不同的想法欢迎提出。

原文:

http://www.vimer.cn/2010/10/%e5%85%b3%e4%ba%8e%e8%b4%9f%e8%bd%bd%e5%9d%87%e8%a1%a1%e5%92%8c%e8%bf%87%e8%bd%bd%e4%bf%9d%e6%8a%a4%e7%9a%84%e4%b8%80%e4%ba%9b%e6%83%b3%e6%b3%95%e5%92%8c%e5%ae%9e%e7%8e%b0.html

MySQL数据库从入门实战课

12-31
限时福利1:购课进答疑群专享柳峰(刘运强)老师答疑服务。 限时福利2:购课后添加学习助手(微信号:csdn590),按消息提示即可领取编程大礼包! 注意:原价129的课程,最后2天限时秒杀仅需49元!! 为什么说每一个程序员都应该学习MySQL? 根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。 使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能! 【课程设计】 在本课程中,刘运强老师会结合自己十多年来对MySQL的心得体会,通过课程给你分享一条高效的MySQL入门捷径,让学员少走弯路,彻底搞懂MySQL。 本课程包含3大模块:  一、基础篇: 主要以最新的MySQL8.0安装为例帮助学员解决安装与配置MySQL的问题,并对MySQL8.0的新特性做一定介绍,为后续的课程展开做好环境部署。 二、SQL语言篇: 本篇主要讲解SQL语言的四大部分数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL,学会熟练对库表进行增删改查等必备技能。 三、MySQL进阶篇: 本篇可以帮助学员更加高效的管理线上的MySQL数据库;具备MySQL的日常运维能力,语句调优、备份恢复等思路。  
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值