做爲一個分佈式的服務框架,服務器的負載均衡,將是一個很重要的性能指標,將可以最大限度的利用多個服務器資源,爲服務的高性能,高可擴展性提供最直接的有力支持。在這篇文章中,咱們就來看看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的服務器處理策略,可以在服務端重啓,短暫出現異常的時候,很快自我恢復,這是一個服務通訊框架最基本的要求,不然咱們將陷入複雜的系統維護上。