在游戲服務器中,線程的管理是重中之重,在上一篇文章中,已經簡單說明了,在游戲服務器開多少線程合適的問題,點擊這里查看 ,因為線程數量不能太多,所以為了提高游戲服務器並發性,就需要在線程處理業務的速度要快,不能長時間卡住線程,比如,不能有網絡io,磁盤IO等耗時的操作。所以我們會把有限的線程數進配按需分配。線程的主要分配方式如下:
1,與客戶端的IO線程
負責接收客戶端消息,和向客戶端發送消息。
2,處理玩家業務的邏輯線程
負責業務邏輯的處理與計算。
3,處理rpc或數據庫同步的網絡線程。
負責不同服務之間的通信
4,處理日志的磁盤IO 線程
相應的線程分別做自己該做的時間。但是這時候問題來了,既然按用途分配了這些線程,那不可免費的就會出現不同線程之間的數據交互了。舉個例子來說,比如玩家登陸和排隊,假如說一個區一台物理機,處理與客戶端的IO線程占1個,業務邏輯線程16個,請求數據庫的線程3個。其它的先忽略不算。這個時候,服務器收到客戶端登陸的請求,業務線程收到這個請求,第一件事情是去數據庫查用戶的信息,因為業務邏輯線程是多個用戶之間共用的,所以你不能在這里等待數據庫請求的返回,要不然會卡別的業務邏輯處理。最好的做法就是,把這個請求封裝成一個事件,發送到數據庫同步線程中去處理這個事。等待數據庫線程處理完了,再把結果告訴業務邏輯線程,然后再處理業務,這個時候處理的數據都在內存中了,所以速度非常快,處理完之后再返回客戶端。
有人說,那這樣還不是卡登陸嗎?是的,必卡登陸,因為處理數據庫的線程就那么3個。對於mysql的查詢性能測試如下:

我們3個線程按1500個計算,那么也只有登陸並發達到1500的時候,才會覺得服務慢一些,(實際情況可能會再底一些),如果並發能達到這么高,說明游戲很火了,可以等待數錢了。如果再多的人,我們就可以使用排隊的功能的。可以查詢到數據庫線程中正在等待執行任務的數量,如果達到某個值,就可以在業務服務中給客戶端返回正在排隊的,讓客戶端過一會再來請求即可。
回到正題,那么游戲服務器線程之間該如何傳遞數據呢,可能每個人的做法不一樣,這里只列舉中一個,希望給大家給帶來些參考,就當是交流學習了。如果你有更好的方法,也希望您評論分享。
在Java中,多線程之間交互數據,即A線程給B線程一個請求事件,A線程還要獲取B線程的執行結果,比如登陸,請數據庫線程去查庫,查完之后告訴邏輯線程。Java提供了一個Future/Callable的機制,詳細大家可以自動百度它們的用法,但是它們有一個缺點,就是A線程在獲取結果時(調用future.get())的時候是同步的,如果B線程沒有執行完,還是會卡A線程。這顯示不是我們想要的。對於這一點,我們偉大的異步框架,netty的作者給出了一個解決方法,那就是Future / promise模式。這里對它的源碼暫不做分皙了,有時間另寫文章說明吧,有興趣的同學可以自己查看。這里只說一下它的用法。
此代碼只是模擬,真實應用中還需要自己設計,首先是數據庫查詢的管理類:
package com.xinyue.demo.future; import io.netty.util.concurrent.DefaultEventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; public class MysqlManager { DefaultEventExecutor threadB = new DefaultEventExecutor(); // 獲取當前任務數量
public int getTaskCount() { return threadB.pendingTasks(); } public Future<User> queryUser(String name, String pasword, Promise<User> promise) { threadB.execute(() -> { // 這里在B線程中執行果查詢數據的操作
User user = null;// 這里由查詢結果賦值
promise.setSuccess(user); }); return promise; } }
然后是業務管理類:
package com.xinyue.demo.future; import io.netty.channel.ChannelHandlerContext; import io.netty.util.concurrent.DefaultEventExecutor; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; public class LogicManager { EventExecutor threadA = new DefaultEventExecutor(); private MysqlManager mysqlManager; public LogicManager(MysqlManager mysqlManager) { this.mysqlManager = mysqlManager; } /** * 這里模擬的就是業務線程收到登陸的請求之后,然后業務線程要查數據庫,但是查詢數據庫的操作是在另外一個線程中執行的,查詢結果出來之后,再返回結果。 * @param name * @param password * @param ctx */
public void login(String name,String password,ChannelHandlerContext ctx) { //這里創建Promise的時候,傳入線程A,表示這個promise中的任何操作都會在線程A中發生。
Promise<User> promise = new DefaultPromise<>(threadA); //這里面是一個異步調用,所以不會卡當前的業務線程A.
Future<User> future = mysqlManager.queryUser(name, password, promise); future.addListener(new GenericFutureListener<Future<User>>() { @Override public void operationComplete(Future<User> future) throws Exception { //這里會在promise調用setSuccess的時候時候執行,而且這個監聽方法的執行是在A線程中。
User user =future.get(); //這里就拿到了用戶的信息了,然后可以做一系列的業務操作,完成之后,再返回給客戶端消息。
ctx.writeAndFlush("登陸成功"); } }); } }
通過這個例子,其它關於多線程交互數據的功能實現,大家就可以舉一返回三了。
歡迎加群交流,QQ群:66728073,197321069,398808948 還可以掃描博客左上角二維碼,關注游戲技術網公眾號

