net.sz.framework 框架 登入伺服器架構 單服2 萬 TPS(QPS)
阿新 • • 發佈:2018-12-30
前言
無論我們做什麼系統,95%的系統都離不開註冊,登入;
而遊戲更加關鍵,頻繁登入,併發登入,導量登入;如果登入承載不起來,那麼遊戲做的再好,都是徒然,進不去啊;
序言
登入所需要的承載,包含程式和資料儲存瓶頸,統一都可以看成io瓶頸;
我的登入伺服器,操作只是做登入註冊和返回伺服器列表功能(只要其他負載均衡不講解,軟負載,硬負載);
登入伺服器,分不同渠道登入驗證,本地渠道驗證,如果登入賬戶不存在,直接註冊賬戶,然後返回token碼;
其他伺服器只認token登入需求;減少其他伺服器的資料庫驗證,網路傳輸驗證,io等開銷;
我的登入伺服器設計只接受 http 登入請求;
http不是通過web發出的;只是一個http監聽協議而已;
本文,測試結果,
本機測試伺服器標準是 I7 8C + 16G,Windows 10,
建立賬號消耗4毫秒左右;理論上登入和建立賬號是一致結果;
快取登入,由於減少了資料庫檢束;
消耗基本是1毫秒左右;
也就說說
登、注的qps= 5000 =1000 / 4 * 20;
快取登入 qps= 20000 = 1000 / 1 * 20;
--5000 註冊,5000 登入,2萬快取登入 qps
資料庫設計
userinfo類
1 package net.sz.test; 2 3 import java.io.Serializable; 4 import javax.persistence.Column; 5View Codeimport javax.persistence.Id; 6 import javax.persistence.Table; 7 8 /** 9 * 使用者資訊表 10 * 11 * <br> 12 * author 失足程式設計師<br> 13 * blog http://www.cnblogs.com/ty408/<br> 14 * mail [email protected]<br> 15 * phone 13882122019<br> 16 */ 17 @Table(name = "UserInfo")18 public class UserInfo implements Serializable { 19 20 private static final long serialVersionUID = -8907709646630947645L; 21 @Id 22 private long id; 23 /*賬戶名*/ 24 private String userName; 25 /*賬戶名小寫副本*/ 26 private String userNameLowerCase; 27 /*密碼*/ 28 @Column(nullable = false) 29 private String userPwd; 30 /*電話*/ 31 @Column(nullable = false) 32 private String userPhone; 33 /*郵件*/ 34 @Column(nullable = false) 35 private String userMail; 36 /*建立時間*/ 37 @Column(nullable = false) 38 private long createTime; 39 /*最後登入時間*/ 40 @Column(nullable = false) 41 private long lastLoginTime; 42 /*狀態,1正常,2表示不可登入*/ 43 @Column(nullable = false) 44 private int Status; 45 /*登入後生成的*/ 46 private transient String token; 47 /*生成 token 的時間*/ 48 private transient long tokenTime; 49 /* 玩家當前登入伺服器ID */ 50 private int loginPlayerServerId; 51 /* 邏輯伺服器傳遞過來的同步時間 */ 52 private transient long lastUplogintime; 53 54 public UserInfo() { 55 } 56 57 public long getId() { 58 return id; 59 } 60 61 public void setId(long id) { 62 this.id = id; 63 } 64 65 public String getUserName() { 66 return userName; 67 } 68 69 public void setUserName(String userName) { 70 this.userName = userName; 71 } 72 73 public String getUserNameLowerCase() { 74 return userNameLowerCase; 75 } 76 77 public void setUserNameLowerCase(String userNameLowerCase) { 78 this.userNameLowerCase = userNameLowerCase; 79 } 80 81 public String getUserPwd() { 82 return userPwd; 83 } 84 85 public void setUserPwd(String userPwd) { 86 this.userPwd = userPwd; 87 } 88 89 public String getUserPhone() { 90 return userPhone; 91 } 92 93 public void setUserPhone(String userPhone) { 94 this.userPhone = userPhone; 95 } 96 97 public String getUserMail() { 98 return userMail; 99 } 100 101 public void setUserMail(String userMail) { 102 this.userMail = userMail; 103 } 104 105 public long getCreateTime() { 106 return createTime; 107 } 108 109 public void setCreateTime(long createTime) { 110 this.createTime = createTime; 111 } 112 113 public long getLastLoginTime() { 114 return lastLoginTime; 115 } 116 117 public void setLastLoginTime(long lastLoginTime) { 118 this.lastLoginTime = lastLoginTime; 119 } 120 121 public int getStatus() { 122 return Status; 123 } 124 125 public void setStatus(int Status) { 126 this.Status = Status; 127 } 128 129 public String getToken() { 130 return token; 131 } 132 133 public void setToken(String token) { 134 this.token = token; 135 } 136 137 public long getLastUplogintime() { 138 return lastUplogintime; 139 } 140 141 public void setLastUplogintime(long lastUplogintime) { 142 this.lastUplogintime = lastUplogintime; 143 } 144 145 public long getTokenTime() { 146 return tokenTime; 147 } 148 149 public void setTokenTime(long tokenTime) { 150 this.tokenTime = tokenTime; 151 } 152 153 public int getLoginPlayerServerId() { 154 return loginPlayerServerId; 155 } 156 157 public void setLoginPlayerServerId(int loginPlayerServerId) { 158 this.loginPlayerServerId = loginPlayerServerId; 159 } 160 161 @Override 162 public String toString() { 163 return "UserInfo{" + "id=" + id + ", userName=" + userName + ", userNameLowerCase=" + userNameLowerCase + ", userPwd=" + userPwd + ", userPhone=" + userPhone + ", userMail=" + userMail + ", createTime=" + createTime + ", lastLoginTime=" + lastLoginTime + ", Status=" + Status + ", token=" + token + ", tokenTime=" + tokenTime + ", loginPlayerServerId=" + loginPlayerServerId + ", lastUplogintime=" + lastUplogintime + '}'; 164 } 165 166 }
用來記錄賬戶資料的;
登入功能劃分設計
渠道登入指令碼介面設計
1 package net.sz.game.login.logins.iscript; 2 3 import net.sz.framework.nio.http.NioHttpRequest; 4 import net.sz.framework.scripts.IBaseScript; 5 6 /** 7 * 8 * <br> 9 * author 失足程式設計師<br> 10 * blog http://www.cnblogs.com/ty408/<br> 11 * mail [email protected]<br> 12 * phone 13882122019<br> 13 */ 14 public interface ILoginScriptPlatform extends IBaseScript { 15 16 /** 17 * 處理登入 平臺登入 18 * 19 * @param platform 平臺ID 20 * @param channelId 渠道ID 21 * @param request 請求 22 * @return 23 */ 24 boolean login(int platform, int channelId, NioHttpRequest request); 25 }View Code
最終本地登入指令碼介面設計
1 package net.sz.game.login.logins.iscript; 2 3 import net.sz.framework.nio.http.NioHttpRequest; 4 import net.sz.framework.scripts.IBaseScript; 5 6 /** 7 * 8 * <br> 9 * author 失足程式設計師<br> 10 * blog http://www.cnblogs.com/ty408/<br> 11 * mail [email protected]<br> 12 * phone 13882122019<br> 13 */ 14 public interface ILoginScript extends IBaseScript { 15 16 /** 17 * 返回錯誤碼 18 * 19 * @param code 20 * @param msg 21 * @return 22 */ 23 String getErrorCode(int code, int msg); 24 25 /** 26 * 最終登入 27 * 28 * @param username 29 * @param userpwd 30 * @param platform 31 * @param channelId 32 * @param request 33 */ 34 void _login(String username, String userpwd, int platform, int channelId, NioHttpRequest request); 35 36 }View Code
最終登入指令碼需要反向引用,不能通過指令碼呼叫
1 package net.sz.game.login.logins; 2 3 import net.sz.game.login.logins.iscript.ILoginScript; 4 5 /** 6 * 登入管理類 7 * <br> 8 * author 失足程式設計師<br> 9 * blog http://www.cnblogs.com/ty408/<br> 10 * mail [email protected]<br> 11 * phone 13882122019<br> 12 */ 13 public class LoginManager { 14 15 private static final LoginManager instance = new LoginManager(); 16 17 18 public static LoginManager getInstance() { 19 return instance; 20 } 21 22 public ILoginScript loginScript; 23 24 }View Code
在腳本里面加入
@Override public void _init() { //反向註冊 LoginManager.getInstance().loginScript = this; }
直接通過例項物件引用而不再是指令碼物件集合呼叫形式;
指令碼登入區分,
100渠道登入
package net.sz.game.login.scripts.logins; import net.sz.framework.nio.http.NioHttpRequest; import net.sz.framework.szlog.SzLogger; import net.sz.game.login.logins.LoginManager; import net.sz.game.login.logins.iscript.ILoginScriptPlatform; /** * 100渠道登入 * <br> * author 失足程式設計師<br> * blog http://www.cnblogs.com/ty408/<br> * mail [email protected]<br> * phone 13882122019<br> */ public class LoginScript100 implements ILoginScriptPlatform { private static final SzLogger log = SzLogger.getLogger(); //http://127.0.0.1:7073/login?platform=100&channel=100&username=ROBOTsz111&password=1 //http://192.168.2.235:7073/login?platform=100&channel=100&username=ROBOT111&password=1&version=1&mac64=jdjdjjd&os=ios&fr=0202125 //http://192.168.2.219:7073/login?platform=100&channel=100&username=ROBOT111&password=1&version=1&mac64=jdjdjjd&os=ios&fr=0202125 @Override public boolean login(int platform, int channelId, NioHttpRequest request) { if (100 == platform) { String username = request.getParam("username"); String password = request.getParam("password"); LoginManager.getInstance().loginScript._login(username, password, platform, channelId, request); return true; } return false; } }View Code
200渠道登入
package net.sz.game.login.scripts.logins; import net.sz.framework.nio.http.NioHttpRequest; import net.sz.framework.szlog.SzLogger; import net.sz.game.login.logins.LoginManager; import net.sz.game.login.logins.iscript.ILoginScriptPlatform; /** * 200渠道登入 * <br> * author 失足程式設計師<br> * blog http://www.cnblogs.com/ty408/<br> * mail [email protected]<br> * phone 13882122019<br> */ public class LoginScript200 implements ILoginScriptPlatform { private static final SzLogger log = SzLogger.getLogger(); //http://127.0.0.1:7073/login?platform=100&username=ROBOT111&userpwd=1 //http://182.150.21.45:7073/login?platform=200&username=ROBOT111&password=1&version=1&mac64=jdjdjjd&os=ios&fr=0202125 @Override public boolean login(int platform, int channelId, NioHttpRequest request) { if (200 == platform) { String username = request.getParam("username"); String password = request.getParam("password"); LoginManager.getInstance().loginScript._login(username, password, platform, channelId, request); return true; } return false; } }View Code
這時模擬以後接取渠道不同處理形式,
比如ios,360,91,豌豆莢等(拒絕廣告);
1 NettyHttpServer nioHttpServer = NettyPool.getInstance().addBindHttpServer("0.0.0.0", ServerHttpPort); 2 //如果需要加入的白名單 3 //nioHttpServer.addWhiteIP("192.168"); 4 nioHttpServer.addHttpBind((url, request) -> { 5 6 ArrayList<IHttpAPIScript> evts = ScriptManager.getInstance().getBaseScriptEntry().getEvts(IHttpAPIScript.class); 7 for (int i = 0; i < evts.size(); i++) { 8 IHttpAPIScript get = evts.get(i); 9 /*判斷監聽*/ 10 if (get.checkUrl(url)) { 11 /*處理監聽*/ 12 get.run(url, request); 13 return; 14 } 15 } 16 17 }, 20, "*");
開啟http監聽狀態;這裡可能需要你閱讀之前的文章瞭解底層庫支援;
1 package net.sz.game.login.scripts.logins; 2 3 import java.util.List; 4 import net.sz.framework.nio.http.NioHttpRequest; 5 import net.sz.framework.scripts.IInitBaseScript; 6 import net.sz.framework.szlog.SzLogger; 7 import net.sz.framework.utils.GlobalUtil; 8 import net.sz.framework.utils.JsonUtil; 9 import net.sz.framework.utils.MD5Util; 10 import net.sz.framework.utils.StringUtil; 11 import net.sz.game.login.data.DataManager; 12 import net.sz.game.login.logins.LoginManager; 13 import net.sz.game.login.logins.iscript.ILoginScript; 14 import net.sz.game.login.service.ServerManager; 15 import net.sz.game.pmodel.po.loginsr.data.ServerInfo; 16 import net.sz.game.pmodel.po.loginsr.data.UserInfo; 17 18 /** 19 * 登入本地系統 操作資料庫 20 * <br> 21 * author 失足程式設計師<br> 22 * blog http://www.cnblogs.com/ty408/<br> 23 * mail [email protected]<br> 24 * phone 13882122019<br> 25 */ 26 public class LoginScript implements ILoginScript, IInitBaseScript { 27 28 private static final SzLogger log = SzLogger.getLogger(); 29 30 private static final String LOGINPWDSIGN = "af0ca5ee6203e02ec076aa8b84385d08"; 31 32 @Override 33 public void _init() { 34 //反向註冊 35 LoginManager.getInstance().loginScript = this; 36 } 37 38 @Override 39 public String getErrorCode(int code, int msg) { 40 String ret = "{" + "\"code\":" + code + ", \"msg\":" + msg + "}"; 41 return ret; 42 } 43 44 @Override 45 public void _login(String username, String userpwd, int platform, int channelId, NioHttpRequest request) { 46 long currentTimeMillis = System.currentTimeMillis(); 47 if (100 != (platform)) { 48 username = platform + "_" + username; 49 } 50 log.info("登入耗時 " + username + " 1 :" + (System.currentTimeMillis() - currentTimeMillis)); 51 boolean flag = true; 52 String usernameLowerCase = username.toLowerCase(); 53 54 if (!StringUtil.checkFilter(username, StringUtil.PATTERN_ABC_0) || !StringUtil.checkFilter(userpwd, StringUtil.PATTERN_ABC_PWD)) { 55 if (log.isInfoEnabled()) { 56 log.info("使用者:" + username + " 賬號或者密碼非法字元!!!"); 57 } 58 request.addContent(getErrorCode(10, 830510)); 59 flag = false; 60 } 61 62 if (!(100 == platform 63 || request.getIp().startsWith("192.168.") 64 || request.getIp().startsWith("127.0.0.1"))) { 65 if (usernameLowerCase.startsWith("robot")) { 66 if (log.isInfoEnabled()) { 67 log.info("使用者:" + username + " 並非特殊平臺,不允許此賬號!!!"); 68 } 69 request.addContent(getErrorCode(10, 830511)); 70 flag = false; 71 } 72 } 73 log.info("登入耗時 " + username + " 2 :" + (System.currentTimeMillis() - currentTimeMillis)); 74 if (flag) { 75 try { 76 77 /*優先獲取快取狀態*/ 78 UserInfo userinfo = DataManager.getInstance().getUserInfoMap().get(usernameLowerCase); 79 80 if (userinfo == null) { 81 /*資料庫操作之前,加鎖*/ 82 synchronized (this) { 83 if (log.isInfoEnabled()) { 84 log.info("使用者:" + username + " 不存在快取使用者!!!"); 85 } 86 /*再次獲取快取狀態,存在併發,那麼獲得鎖許可權以後有機率以及得到資料了*/ 87 userinfo = DataManager.getInstance().getUserInfoMap().get(usernameLowerCase); 88 if (userinfo != null) { 89 if (log.isInfoEnabled()) { 90 log.info("平臺:" + platform + ", ip:" + request.getIp() + ", 使用者:" + username + " 快取使用者!!!"); 91 } 92 } else { 93 log.info("登入耗時 " + username + " 3 :" + (System.currentTimeMillis() - currentTimeMillis)); 94 userinfo = DataManager.getInstance().getDataDao().getObjectByWhere(UserInfo.class, "where `userNameLowerCase` = ?", usernameLowerCase); 95 log.info("登入耗時 " + username + " 4 :" + (System.currentTimeMillis() - currentTimeMillis)); 96 if (userinfo == null) { 97 if (DataManager.getInstance().getUserNameLowerCaseSet().contains(usernameLowerCase)) { 98 request.addContent(getErrorCode(31, 830512)); 99 if (log.isInfoEnabled()) { 100 log.info("平臺:" + platform + ", ip:" + request.getIp() + ", 使用者:" + username + " 註冊使用者失敗,重名!!!"); 101 } 102 return; 103 } else { 104 105 if ("robottroy".equalsIgnoreCase(usernameLowerCase)) { 106 request.addContent(getErrorCode(31, 830513)); 107 if (log.isInfoEnabled()) { 108 log.info("平臺:" + platform + ", ip:" + request.getIp() + ", 使用者:" + username + " 註冊使用者失敗,,特殊賬號不能註冊!!!"); 109 } 110 return; 111 } 112 113 if (log.isInfoEnabled()) { 114 log.info("使用者:" + username + " 資料庫不存在!!!建立使用者"); 115 } 116 117 userinfo = new UserInfo(); 118 userinfo.setId(GlobalUtil.getId()); 119 userinfo.setUserName(username); 120 userinfo.setUserNameLowerCase(usernameLowerCase); 121 userinfo.setUserPwd(userpwd); 122 userinfo.setCreateTime(System.currentTimeMillis()); 123 userinfo.setLastLoginTime(System.currentTimeMillis()); 124 userinfo.setStatus(1); 125 userinfo.setUserMail(""); 126 userinfo.setUserPhone("");