关于负载均衡和过载保护的一些想法和实现
最近需要给一个现网server增加过载保护的功能,借此机会也思考了很多,简单谈谈我对这两个概念的理解和实现方法。
一.负载均衡
简单来说,就是按照目标server的参数进行合理分配,这个参数可以是失败率,也可以是响应时间,也可以是请求量,甚至是随机数。
我们来按照从简单到复杂逐个看一下几种实现。
1.轮询式
逻辑比较简单,直接看代码:
1 2 3 4 5 6 |
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多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//假设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%放量来试。也避免滚雪球效应的发生。
我们来看一下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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++。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
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,就是这样,大家如果有不同的想法欢迎提出。
建议继续学习:
- 解析nginx负载均衡 (阅读:14526)
- 腾讯后台开发技术总监浅谈过载保护 小心雪崩效应 (阅读:6199)
- 使用HAProxy对MySQL进行负载均衡和状态监控 (阅读:5597)
- 趣图三幅:负载均衡算法需要改进 (阅读:3897)
- 服务框架演变过程 (阅读:3673)
- 超级负载均衡 (阅读:3689)
- “集群和负载均衡”的通俗版解释 (阅读:3527)
- “集群和负载均衡”在实战当中的运用技巧 (阅读:3375)
- 使用Apache做负载均衡 (阅读:2640)
- RAC的负载均衡 (阅读:2601)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Dante 来源: Vimer
- 标签: 负载均衡 过载保护
- 发布时间:2010-10-30 08:01:37
- [44] 界面设计速成
- [42] Oracle MTS模式下 进程地址与会话信
- [41] 如何拿下简短的域名
- [41] 图书馆的世界纪录
- [41] android 开发入门
- [39] IOS安全–浅谈关于IOS加固的几种方法
- [39] 视觉调整-设计师 vs. 逻辑
- [37] 【社会化设计】自我(self)部分――欢迎区
- [37] 程序员技术练级攻略
- [35] 读书笔记-壹百度:百度十年千倍的29条法则