做为一个分布式的服务框架,服务器的负载均衡,将是一个很重要的性能指标,将可以最大限度的利用多个服务器资源,为服务的高性能,高可扩展性提供最直接的有力支持。在这篇文章中,咱们就来看看Gaea是如何作到负载均衡,如何可以经过简单的添加机器,解决系统问题。 java
首先全部的server是被放入到一个List中的。 算法
public List<Server> GetAllServer() { return ServerPool; }在这个Server中,Gaea除了封装了服务器的IP和端口觉得,还设置了许多重要的参数,用于控制服务器的连接
private String name; private String address; private int port; private int weight; private float weightRage; private ServerState state; private ScoketPool scoketpool; private int currUserCount; private int deadTimeout; private long deadTime; private boolean testing = false;
其中state是客户端重连机制的最主要的参数。接下来咱们慢慢的会讲解其中的一些参数是的意义,最主要的是在客户端中起到一个什么样的做用。state的几种状态: 服务器
public enum ServerState { Dead, Normal, Busy, Disable, Reboot, Testing }
服务器的建立是在客户端建立代理的加载配置文件的时候就已经建立好了,建立过程 负载均衡
for (ServerProfile ser : config.getServers()) { if (ser.getWeithtRate() > 0) { Server s = new Server(ser); if (s.getState() != ServerState.Disable) { ScoketPool sp = new ScoketPool(s, config); s.setScoketpool(sp); ServerPool.add(s); } } }
在这里咱们能够看到使用了weithtRate这个参数,若是这个参数配置<=0,那么这个服务器就不会被建立,其中哦在建立的时候,咱们还会根据这个weithtRate参数,肯定这个server的状态 框架
this.weightRage = config.getWeithtRate(); if (this.weightRage >= 0) { this.state = ServerState.Normal; } else { this.state = ServerState.Disable; }
看到这里,程序好像有点小bug,在建立Server的时候,彷佛不可能被标记为disable。建立Server的时候,咱们相应的会给每一个Server建立对应的线程池SocketPool. socket
服务器的选择是在类Dispatcher中,能够看到在GetServer中,Gaea使用了一种取模的方式作到负载均衡。如何取模呢?这里是利用客户端的请求次数取模于服务器的个数,再根据服务器的当前状态,获取服务。 分布式
int count = ServerPool.size(); int start = requestCount.get() % count; if (requestCount.get() > 10) { requestCount.set(0); } else { requestCount.getAndIncrement(); } for (int i = start; i < start + count; i++) { int index = i % count; Server server = ServerPool.get(index);
利用这种算法,那么取服务器将是按照每一次请求,对服务器进行轮询访问,这样就能作到落在每台服务器的请求数量同样多,从而作到负载均衡。 函数
大概的画了一个Gaea服务器状态转换图,关于重连机制,全由这流程图决定。 性能
不大会作图,简单的用ppt作了一个,简单的围绕这幅图,来讲说Gaea服务的重连机制。 this
在GetServer中,当Gaea取到一个Server的时候,去看它的状态,若是是dead或者reboot的时候,修改其状态为testing后返回,等于给再给这个服务器一次机会,让它去尝试。
if (server.getState() == ServerState.Dead && (System.currentTimeMillis() - server.getDeadTime()) > server.getDeadTimeout()) { synchronized (this) { if (server.getState() == ServerState.Dead && (System.currentTimeMillis() - server.getDeadTime()) > server.getDeadTimeout()) { result = server; server.setState(ServerState.Testing); server.setDeadTime(0); logger.warning("find server that need to test!host:" + server.getAddress()); break; } } } if(server.getState() == ServerState.Reboot && (System.currentTimeMillis() - server.getDeadTime()) > server.getDeadTimeout()){ synchronized (this) { if (server.getState() == ServerState.Reboot && (System.currentTimeMillis() - server.getDeadTime()) > server.getDeadTimeout()) { result = server; server.setState(ServerState.Testing); server.setDeadTime(0); requestCount.getAndDecrement(); logger.warning("find server that need to test!host:" + server.getAddress()); break; } } }在代码中咱们能够看到,在检测其状态的时候,还必需要求这个服务器在deatTimeout之内,若是超过这一范围,那么此服务器将失去再次尝试连接的机会,永久再也不使用。每次取得状态为dead和reboot的以后,都会将其重职位testing,再将死亡时间置为0,增长下次重试的机会。
在收发数据失败的时候,咱们将对其状态作一些更改,以便给他再一次机会,重连服务。这部分的状态变化,大部分实在catch语句块中执行的。
catch (IOException ex) { logger.error("io exception", ex); if (socket == null || !socket.connecting()) { if (!test()) { markAsDead(); } } throw ex; } catch (Throwable ex) { logger.error("request other Exception", ex); throw ex; } finally { if (state == state.Testing) { markAsDead(); } if (socket != null) { socket.unregisterRec(p.getSessionID()); } decreaseCU(); }
以上是收发函数request中的catch部分,也就是收发失败,通常这种状况就是IO的异常致使收发数据失败,因此在IOException中,Gaea作了相应的处理。if(!test()) 这句对服务端作了探测,若是探测失败,则标记此服务器的状态为dead。探测的时候,简单的作了connect操做。
Socket socket = new Socket(); socket.connect(new InetSocketAddress(this.address, this.port), 100); socket.close(); result = true;若是发送失败,Gaea会去再取下一个服务器。循环的次数有一个简单的算法决定,若是都返回异常,则就向服务调用着抛出异常。
ioreconnect = serverCount - 1; count = requestTime; if(ioreconnect > requestTime){ count = ioreconnect; }以上serverCount是服务器个数,requestTime是配置文件中配置的请求次数。最终循环count次,用来访问服务器。
当收到重启协议的时候,Gaea就将其服务器状态改成reboot,并再次去调用服务。用以获取正常数据
else if(receiveP.getSDPType() == SDPType.Reset){ //服务重启 logger.info(server.getName()+" server is reboot,system will change normal server!"); this.createReboot(server); return invoke(returnType, typeName, methodName, paras);
Gaea收到正常数据的时候,服务器的状态可分为两种状况,Normal和Testing。收到normal固然是很正常的状态,这里再也不多说,而当此时的状态是Testing的时候,那么就是咱们获取Server时,dead和reboot状态的服务器而来的,此时既然能收到正常数据,那么就能肯定,这台Server已经正常,所以将其状态改成normal。
Protocol result =Protocol.fromBytes(buffer,socket.isRights(),socket.getDESKey()); if (this.state == ServerState.Testing) { relive(); }当收到数据,改其状态为normal。
以上为Gaea的服务器处理策略,可以在服务端重启,短暂出现异常的时候,很快自我恢复,这是一个服务通讯框架最基本的要求,不然咱们将陷入复杂的系统维护上。