/* QQ: 2#4#2#1#0#6#7#6#4 #表示为空 Mail: lin_style#foxmail.com #替换成@ */
行为
难点
邮局路由流程图
邮局路由详细图
实例代码目录说明
算法的示例代码
行为
对于客户端:
1. 接受客户端的短连接
2. 返回给客户端一个密匙
对于邮局服务器:
1. 接受邮局服务器的主动连接并记录,理论上可以动态
2. 接受邮局服务器的定时更新
3. 筛选出邮局服务器的负载信息返回
难点
粗略思考下,有这么几个纠结的地方
1. 如何做到每次都是返回最小的服务器结构
2. 客户端每次请求都是并行的(udp线程根据CPU数量启动,可以视为一个资源竞争)
3. 服务器资源的更新会不会和第一条产生资源竞争
当然,你也可以把这块做成单线程,直接进行一个排序后算出,也非常的简单。但是我的观点是,东西总是越做越极限,虽然简单不一定高效的方法可以解决,但是还有更简单更高效更新一层的东西来等你挖掘。当然,前提是有时间。
设计的几个方案都无可避免的要发生资源竞争。最终采取的如下,非常的优雅,不管多少并行下都不带锁。其核心毫不夸张的说只有5行左右。那就是“概率”。
举一个简单的例子,假设有4台机器,分别是400,300,200,100人。那么它们的比例就是4:3:2:1,那么在负载均衡的情况下,假设投入 1000人,那么每台机器分配到的人数应该是100,200,300,400人。如果我们事先分配好这些比例,并且给出一个按此比例的随机函数,是不是可以非常轻松的解决所谓的高并发锁的困扰呢?比如随机1000次的实验,在0的位置上出现100次,1的位置上出现200次。。。。当然可以!再设想一下,假如我们不要求每个服务器的负载都是非常的精确话,以下的伪码就可以表现出一个流程:
接收到客户请求,fun 随机返回一个概率比例值,send
接受到更新请求,fun 比例重新设置
注意,在“fun比例重新设置”函数中是不加锁的,每台服务器都有对应的维护对象;而“fun随便返回一个概率比率值”函数,虽然要根据服务器人数进行重新设置比例,但是这些精度的损耗可以忽略不计(更新人数时仅仅是一个赋值过程)。
我给出的demo中采取的是rand函数(取值范围是0-32767)。关于这个函数的缺陷有如下:
- 假如你求余10000的话,你会发现前面几千的概率非常高
- 加入你求余1000的话,你会发现前面700多的概率非常高
原因很简单,最大值不是你求余数的倍数。虽然这些可以忽略,但我还是做了处理。在进行比例计算的时候,最后一个值会有一些误差(就比如10/3这样的整取),当随机到这些忽略值时我们默认给最后一台。而rand的这种缺陷,恰好使得767(我取了 1000的精度)以后的值概率较低,互相弥补了下。
在UDP的这一块,根据CPU的个数产生对应的线程来绑定不同的端口.反正上文的方法是无锁的,跑得肯定畅快。而这些端口的信息当然是交给更新服务器给客户端,客户端也是根据一个概率来选择连接。
邮局路由流程图
该程序里虽然用到了2个协议UDP和TCP,但是执行的动作都很简单。TCP负责在指定时间内更新自己服务器信息,UDP负责反馈这些信息给用户。在采用UDP上,我从这几个方面考虑:
- 需求上,客户单只需要获得一个要连接的信息包即可,那么发起的动作只是简单的请求-接受这么个回合。即使UDP包出错,那么在1秒内完成这样的回合可以是十个左右(最佳情况),即时不是,那延迟个2-3秒,从登陆这个需求来说也是完全可以的。
- 效率上,只是这一个简单的回合,建立起一个TCP花费的效率都比其高,更重要的是为这样的小回合再进行一个机器部署不合算,并且也很容易在打规模登陆的时候宕掉。UDP,无限的并发可能。即时处理不过来,也仍然屹立不倒。
邮局路由详细图
实例代码目录说明
以上的代码目录是邮局路由图,也大致体现了上文所说的框架大体样貌。因为整个流转的流程是这样的:
先来简单说下各个目录里的文件:
源文件/
PostofficeRoute.cpp:是个main程序,启动各种线程和网络库。其中UDP是根据CPU的数量来自动创建线程,能达到最高效使用。启动完毕后,就没main的什么事了,要做的只是等待各个线程的返回。
CRoutePublic.cpp:是一些main里公用的函数,比如拦截一些退出键,取得系统信息等
CConfigManager.cpp:配置文件
Standalone/
CRouteRand.cpp:一个随机的比例抽取对象,内容详见上一篇
下面是最主要的三个目录
Bridge/ 抽像NetWork和Logic之间的接口,因为邮局路由比较简单,所以没做队列的中间转换。
CUserLogicBridge/ CUserNetBridge:用户的网络接口和逻辑接口。网络接口包含了一些比如端口信息,sockaddr_in的结构信息等等,而逻辑接口里包含了对象的内存地址等等信息。这两个接口主要是为了一些信息的冗余和预留。
Logic/
CWorldRouteClient.cpp:网络数据提交至此的一个逻辑处理。该类里主要是一个成员函数的数组,根据协议的编号来实行自动跳转执行
NetWord/
CPublicSocket.cpp: 目前只包括一些协议正确与否的检测
CRouteClientUDP.cpp:包含一个UDP的网络处理程序和若干个跳转逻辑
CRouteServerTCP.cpp:同上
接下来模拟下数据流,当收到一个客户端UDP的请求后:
UDP的线程之一收到请求后,进行CPublicSocket的检测,检测通过,取到该连接的逻辑接口和网络接口,加上协议然后交给CWorldRouteClient.cpp的跳转函数到具体的实现函数里执行。
算法的示例代码
在UDP的这一块,根据CPU的个数产生对应的线程来绑定不同的端口.反正上文的方法是无锁的,跑得肯定畅快。而这些端口的信息当然是交给更新服务器给客户端,客户端也是根据一个概率来选择连接。
/* VS2008下编译通过 如有BUG和错误,请给在下一个消息,感激不尽 QQ: 2#4#2#1#0#6#7#6#4 #表示为空 Mail: lin_style#foxmail.com #替换成@ */ // 0xtiger_Rand.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" // //测试的次数 const int TEST_COUNT = 40000; // //服务器台数 const int SERVER_NUMBER_OF = 5; // //服务器单台最大人数 const int SERVER_PEOPLE_SIZE_MAX = 25000; // // const int PEOPLE_PRICISION_PROPORTION = 1000; // //总人数 int ServerSumPeople=0; // //服务器信息结构体 //int ServerInfo[SERVER_NUMBER_OF]; int ServerInfo[SERVER_NUMBER_OF]; // //服务器人数的比例存放 int ServerProportion[SERVER_NUMBER_OF]; // //服务器人数计算比例概率的偏移值 int ServerProportionOffset[SERVER_NUMBER_OF]; // //记录被选了多少次 int ServerSelectRecord[SERVER_NUMBER_OF]; void Tracer_ServerSelectRecord() { cout<<"server select record"<<endl; for(int i=0; i<SERVER_NUMBER_OF; ++i) { cout<<"num "<<i<<":"<<ServerSelectRecord[i]<<endl; } cout<<"*************end*************"<<endl<<endl; } void Tracer_ServerInfo() { cout<<"server pepole"<<endl; cout<<"now sum people:"<<ServerSumPeople<<endl; cout<<"max people size of a server:"<<SERVER_PEOPLE_SIZE_MAX<<endl; for(int i=0; i<SERVER_NUMBER_OF; ++i) { cout<<"num "<<i<<":"<<ServerInfo[i]<<endl; } cout<<"*************end*************"<<endl<<endl; } void Tracer_ServerProportion() { int i; cout<<"server Proportion"<<endl; cout<<"max people_pricision_proportion:"<<PEOPLE_PRICISION_PROPORTION<<endl; for(i=0; i<SERVER_NUMBER_OF; ++i) { cout<<"num "<<i<<":"<<ServerProportion[i]<<endl; } cout<<"max people_pricision_proportion offset:"<<endl; for(i=0; i<SERVER_NUMBER_OF; ++i) { cout<<"num "<<i<<":"<<ServerProportionOffset[i]<<endl; } cout<<"*************end*************"<<endl<<endl; } void InitServerInfo() { ServerInfo[0] = 1; ServerInfo[1] = 2; ServerInfo[2] = 500; ServerInfo[3] = 0; ServerInfo[4] = 0; int i; for(i=0; i<SERVER_NUMBER_OF; ++i) { ServerSumPeople+=ServerInfo[i]; } } // //计算比例 void CtrlProportion() { int i; int nSumServerProportion=0; double d; for(i=0; i<SERVER_NUMBER_OF; ++i) { // //判断是否超出单台上限 if( SERVER_PEOPLE_SIZE_MAX<ServerInfo[i] ) { ServerProportion[i] = 0; } else { ServerProportion[i] = (SERVER_PEOPLE_SIZE_MAX-ServerInfo[i]) / (double)SERVER_PEOPLE_SIZE_MAX * PEOPLE_PRICISION_PROPORTION; } nSumServerProportion += ServerProportion[i]; } for(i=0; i<SERVER_NUMBER_OF; ++i) { ServerProportion[i] = ServerProportion[i] / (double)nSumServerProportion * PEOPLE_PRICISION_PROPORTION; } ServerProportionOffset[0]=ServerProportion[0]; for(i=1; i<SERVER_NUMBER_OF; ++i) { ServerProportionOffset[i] = ServerProportionOffset[i-1]+ServerProportion[i]; } } int GetRandObject(int nRandBase) { for(int i=0; i<SERVER_NUMBER_OF; ++i) { int nBegin = ServerProportionOffset[i] - ServerProportion[i]; int nEnd = ServerProportionOffset[i]; if( nRandBase>=nBegin&& nRandBase<nEnd ) { return i; } } return SERVER_NUMBER_OF-1; } int _tmain(int argc, _TCHAR* argv[]) { srand( (unsigned)time( NULL ) ); InitServerInfo(); CtrlProportion(); Tracer_ServerInfo(); Tracer_ServerProportion(); int i; for(i=0; i<TEST_COUNT; ++i) { int nRecord; // //rand 0-32767 nRecord = GetRandObject( rand()%PEOPLE_PRICISION_PROPORTION ); ServerSelectRecord[nRecord]++; ServerInfo[nRecord]++; ServerSumPeople++; //CtrlProportion(); //是否每次都进行比例纠正 } Tracer_ServerSelectRecord(); cout<<"now server people:"<<endl; Tracer_ServerInfo(); return 0; } /* out put: 测试一: 在注释掉这句情况下//是否每次都进行比例纠正 测试次数为10000次,每台上限为2500次。(因为算法以上限数进行比例计算,超出则失衡) 初始人数 ServerInfo[0] = 1; ServerInfo[1] = 2; ServerInfo[2] = 500; ServerInfo[3] = 0; ServerInfo[4] = 0; 得出结果 now server people: server pepole now sum people:10503 max people size of a server:2500 num 0:2086 num 1:2151 num 2:2132 num 3:2116 num 4:2018 虽然最高值偏差到140人,差不多是5%-7%偏差(我没计算错吧),具体还跟rand()这个值有关。不过我已经非常满意 这样的分布了。 测试二: 开启注释的//是否每次都进行比例纠正 测试次数改为20000,进行超出测试 now server people: server pepole now sum people:20503 max people size of a server:2500 num 0:2498 num 1:2498 num 2:2498 num 3:2498 num 4:10511 发现千分之一的人数误差。因为超出的默认都在最后一台所以人数偏大 */