1. 程式人生 > >net.sz.framework 框架 登入伺服器架構 單服2 萬 TPS(QPS)

net.sz.framework 框架 登入伺服器架構 單服2 萬 TPS(QPS)

前言

無論我們做什麼系統,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;
  5
import 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 }
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 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("");