net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)

前言

无论我们做什么系统,95%的系统都离不开注册,登录;

而游戏更加关键,频繁登录,并发登录,导量登录;如果登录承载不起来,那么游戏做的再好,都是徒然,进不去啊;

序言

登录所需要的承载,包含程序和数据存储瓶颈,统一都可以看成io瓶颈;

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第1张图片

我的登录服务器,操作只是做登录注册和返回服务器列表功能(只要其他负载均衡不讲解,软负载,硬负载);

登录服务器,分不同渠道登录验证,本地渠道验证,如果登录账户不存在,直接注册账户,然后返回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  * 
12 * author 失足程序员
13 * blog http://www.cnblogs.com/ty408/
14 * mail [email protected]
15 * phone 13882122019
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  * 
9 * author 失足程序员
10 * blog http://www.cnblogs.com/ty408/
11 * mail [email protected]
12 * phone 13882122019
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  * 
9 * author 失足程序员
10 * blog http://www.cnblogs.com/ty408/
11 * mail [email protected]
12 * phone 13882122019
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  * 
8 * author 失足程序员
9 * blog http://www.cnblogs.com/ty408/
10 * mail [email protected]
11 * phone 13882122019
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渠道登录
 * 
* author 失足程序员
* blog
http://www.cnblogs.com/ty408/
* mail [email protected]
* phone 13882122019
*/ 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渠道登录
 * 
* author 失足程序员
* blog
http://www.cnblogs.com/ty408/
* mail [email protected]
* phone 13882122019
*/ 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 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  * 
21 * author 失足程序员
22 * blog http://www.cnblogs.com/ty408/
23 * mail [email protected]
24 * phone 13882122019
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(""); 127 userinfo.setPlatformId(platform); 128 userinfo.setChannelId(channelId); 129 DataManager.getInstance().getcUDThread().insert_Sync(userinfo); 130 } 131 } 132 133 DataManager.getInstance().getUserNameLowerCaseSet().add(usernameLowerCase); 134 135 DataManager.getInstance().getUserInfoMap().put(usernameLowerCase, userinfo); 136 log.info("登录耗时 " + username + " 5 :" + (System.currentTimeMillis() - currentTimeMillis)); 137 } 138 } 139 } else { 140 if (log.isInfoEnabled()) { 141 log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 缓存用户!!!"); 142 } 143 } 144 145 if (userinfo == null || !userinfo.getUserName().equals(username) || !userinfo.getUserPwd().equals(userpwd)) { 146 request.addContent(getErrorCode(3, 830514)); 147 if (log.isInfoEnabled()) { 148 log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户名或密码错误!!!"); 149 } 150 } else { 151 //token生成之后3分钟 152 long md5time = System.currentTimeMillis(); 153 //String token = MD5Util.md5Encode('=', userinfo.getId() + "", username, request.getIp(), md5time + "", MyAttributeKey.TOKENKEY); 154 String token = MD5Util.md5Encode('=', userinfo.getId() + "", username, "", md5time + "", LOGINPWDSIGN); 155 //更新token 156 userinfo.setToken(token); 157 //更新token生成时间 158 userinfo.setTokenTime(md5time); 159 //更新最后同步时间 160 userinfo.setLastUplogintime(md5time); 161 162 userinfo.getLastLoginTime(); 163 userinfo.getLastUplogintime(); 164 log.info("登录耗时 " + username + " 6 :" + (System.currentTimeMillis() - currentTimeMillis)); 165 String serverInfo = ServerManager.getInstance().serverInfoScript.getServerInfo(platform, channelId, request, userinfo); 166 log.info("登录耗时 " + username + " 7 :" + (System.currentTimeMillis() - currentTimeMillis)); 167 Ret ret = new Ret(0, 0); 168 ret.setToken(token); 169 ret.setTime(md5time); 170 ret.setUserName(username); 171 ret.setUid(userinfo.getId()); 172 String toJSONString = ret.showString(serverInfo); 173 log.info("登录耗时 " + username + " 8 :" + (System.currentTimeMillis() - currentTimeMillis)); 174 if (log.isDebugEnabled()) { 175 log.debug("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户登录完成!!!同步服务器信息:" + toJSONString); 176 } 177 request.addContent(toJSONString); 178 log.info("登录耗时 " + username + " 8 :" + (System.currentTimeMillis() - currentTimeMillis)); 179 if (log.isInfoEnabled()) { 180 log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户登录完成!!!"); 181 } 182 } 183 } catch (Exception e) { 184 log.error("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 登录发生错误信息", e); 185 request.addContent(getErrorCode(500, 830515)); 186 } 187 } 188 } 189 190 public static void main(String[] args) { 191 String jsonString = "{code:0, token:\"af0ca5ee6203e02ec076aa8b84385d08\", userName:\"ROBOTsz111\", msg:\"\", time:1469087482055, uid:197, infos:[{zoneId:100, serverGroup:\"测试大区\", serverId:\"1003\", serverName:\"服务器(刘富顺)\", tcpIp:\"182.150.21.45\", tcpPort:8084, httpIP:\"182.150.21.45\", httpPort:9094, idenIcon:\"\", startTime:\"1\", otherString:\"\", serverState:\"维护\", nextOpenTime:\"\"},{zoneId:200, serverGroup:\"测试专区\", serverId:\"1\", serverName:\"终焉之时\", tcpIp:\"182.150.21.45\", tcpPort:8083, httpIP:\"182.150.21.45\", httpPort:9093, idenIcon:\"new\", startTime:\"1\", otherString:\" \", serverState:\"维护\", nextOpenTime:\" \"},{zoneId:100, serverGroup:\"测试大区\", serverId:\"1001\", serverName:\"服务器(陈飞)\", tcpIp:\"182.150.21.45\", tcpPort:8084, httpIP:\"182.150.21.45\", httpPort:9094, idenIcon:\"\", startTime:\"1\", otherString:\"\", serverState:\"维护\", nextOpenTime:\"\"},{zoneId:100, serverGroup:\"测试大区\", serverId:\"1002\", serverName:\"服务器(吴复全)\", tcpIp:\"182.150.21.45\", tcpPort:8084, httpIP:\"182.150.21.45\", httpPort:9094, idenIcon:\"\", startTime:\"1\", otherString:\"\", serverState:\"维护\", nextOpenTime:\"\"},{zoneId:100, serverGroup:\"测试大区\", serverId:\"2\", serverName:\"客户端\", tcpIp:\"182.150.21.45\", tcpPort:7075, httpIP:\"182.150.21.45\", httpPort:9094, idenIcon:\"xingxing\", startTime:\"1\", otherString:\"\", serverState:\"维护\", nextOpenTime:\"\"}]}"; 192 jsonString = new LoginScript().getErrorCode(10, 830510); 193 Ret parseObject = JsonUtil.parseObject(jsonString, Ret.class); 194 log.error(parseObject.toString()); 195 } 196 197 static class Ret { 198 199 private int code; 200 private String token; 201 private String userName; 202 private int msg; 203 private long time; 204 private long uid; 205 private ServerInfo[] infos; 206 207 public Ret(int code, int msg) { 208 this.code = code; 209 this.msg = msg; 210 } 211 212 public Ret() { 213 } 214 215 public String showString(String serverinfos) { 216 return "{" + "\"code\":" + code + ", \"token\":\"" + token + "\", \"userName\":\"" + userName + "\", \"msg\":\"" + msg + "\", \"time\":" + time + ", \"uid\":" + uid + ", \"infos\":" + serverinfos + "}"; 217 } 218 219 @Override 220 public String toString() { 221 return "{" + "code=" + code + ", token=" + token + ", userName=" + userName + ", msg=" + msg + ", time=" + time + ", uid=" + uid + ", infos=" + infos + '}'; 222 } 223 224 /** 225 * @return the code 226 */ 227 public int getCode() { 228 return code; 229 } 230 231 /** 232 * @param code the code to set 233 */ 234 public void setCode(int code) { 235 this.code = code; 236 } 237 238 /** 239 * @return the token 240 */ 241 public String getToken() { 242 return token; 243 } 244 245 /** 246 * @param token the token to set 247 */ 248 public void setToken(String token) { 249 this.token = token; 250 } 251 252 /** 253 * @return the userName 254 */ 255 public String getUserName() { 256 return userName; 257 } 258 259 /** 260 * @param userName the userName to set 261 */ 262 public void setUserName(String userName) { 263 this.userName = userName; 264 } 265 266 /** 267 * @return the msg 268 */ 269 public int getMsg() { 270 return msg; 271 } 272 273 /** 274 * @param msg the msg to set 275 */ 276 public void setMsg(int msg) { 277 this.msg = msg; 278 } 279 280 /** 281 * @return the time 282 */ 283 public long getTime() { 284 return time; 285 } 286 287 /** 288 * @param time the time to set 289 */ 290 public void setTime(long time) { 291 this.time = time; 292 } 293 294 /** 295 * @return the uid 296 */ 297 public long getUid() { 298 return uid; 299 } 300 301 /** 302 * @param uid the uid to set 303 */ 304 public void setUid(long uid) { 305 this.uid = uid; 306 } 307 308 /** 309 * @return the infos 310 */ 311 public ServerInfo[] getInfos() { 312 return infos; 313 } 314 315 /** 316 * @param infos the infos to set 317 */ 318 public void setInfos(ServerInfo[] infos) { 319 this.infos = infos; 320 } 321 322 } 323 324 }
View Code

 

 整个最后登录流程。设计;

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第2张图片

整个登录流程

http 请求 -》 流向 http api -》 httploginscript -》 loginscript渠道登录 -》 loginscript 登录 -》缓存验证 -》 数据库验证 -》 返回结果;

C#代码测试调用

 1 using Net.Sz.Framework.Netty.Http;
 2 using Net.Sz.Framework.Util;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 using System.Threading;
 8 using System.Threading.Tasks;
 9 
10 
11 namespace CApp_CheckLoginTps
12 {
13 
14     class Program
15     {
16 
17         static List<int> idList = new List<int>();
18         static IntegerSSId ids = new IntegerSSId();
19 
20         static void Main(string[] args)
21         {
22             Console.WriteLine("准备就绪");
23             while (true)
24             {
25                 Console.ReadLine();
26                 Console.WriteLine("注册登录");
27                 test();
28                 Console.ReadLine();
29                 Console.WriteLine("缓存登录");
30                 test2();
31             }
32             Console.ReadLine();
33         }
34 
35 
36         static void test()
37         {
38             Program.idList.Clear();
39             int tcount = 2;
40             for (int i = 1; i <= tcount; i++)
41             {
42                 new Thread(() =>
43                 {
44                     System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
45                     watch.Start();
46                     int id = ids.GetId();
47                     Program.idList.Add(id);
48                     string ret = HttpClient.UrlGet("http://192.168.2.235:7073/login?platform=100&channel=100&username=" + (id) + "&password=1&version=1&mac64=jdjdjjd&os=ios&fr=0202125");
49                     watch.Stop();
50                     Console.WriteLine(watch.ElapsedMilliseconds);
51                 }).Start();
52             }
53         }
54 
55         static void test2()
56         {
57             int tcount = Program.idList.Count;
58 
59             for (int i = 0; i < tcount; i++)
60             {
61                 new Thread(new ParameterizedThreadStart((object obj) =>
62                 {
63                     System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
64                     watch.Start();
65                     string ret = HttpClient.UrlGet("http://192.168.2.235:7073/login?platform=100&channel=100&username=" + (obj) + "&password=1&version=1&mac64=jdjdjjd&os=ios&fr=0202125");
66                     watch.Stop();
67                     Console.WriteLine(watch.ElapsedMilliseconds);
68                 })).Start(Program.idList[i]);
69             }
70         }
71     }
72 
73 }
View Code

 

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第3张图片

测试结果:

[04-12 15:26:06:408:INFO :LoginScript._login():95] 登录耗时 326060000 4 :5
[04-12 15:26:06:408:INFO :LoginScript._login():114] 用户:326060000 数据库不存在!!!创建用户
[04-12 15:26:06:408:INFO :LoginScript._login():136] 登录耗时 326060000 5 :5
[04-12 15:26:06:408:INFO :LoginScript._login():164] 登录耗时 326060000 6 :5
[04-12 15:26:06:408:INFO :LoginScript._login():166] 登录耗时 326060000 7 :5
[04-12 15:26:06:408:INFO :LoginScript._login():173] 登录耗时 326060000 8 :5
[04-12 15:26:06:408:INFO :LoginScript._login():178] 登录耗时 326060000 8 :5
[04-12 15:26:06:408:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:326060000 用户登录完成!!!
[04-12 15:26:06:409:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:6
[04-12 15:26:07:043:INFO :LoginScript._login():50] 登录耗时 326060000 1 :0
[04-12 15:26:07:043:INFO :LoginScript._login():50] 登录耗时 326060001 1 :0
[04-12 15:26:07:043:INFO :LoginScript._login():73] 登录耗时 326060000 2 :0
[04-12 15:26:07:043:INFO :LoginScript._login():73] 登录耗时 326060001 2 :0
[04-12 15:26:07:043:INFO :LoginScript._login():141] 平台:100, ip:192.168.2.235, 用户:326060000 缓存用户!!!
[04-12 15:26:07:043:INFO :LoginScript._login():141] 平台:100, ip:192.168.2.235, 用户:326060001 缓存用户!!!
[04-12 15:26:07:043:INFO :LoginScript._login():164] 登录耗时 326060000 6 :0
[04-12 15:26:07:043:INFO :LoginScript._login():164] 登录耗时 326060001 6 :0
[04-12 15:26:07:043:INFO :LoginScript._login():166] 登录耗时 326060000 7 :0
[04-12 15:26:07:043:INFO :LoginScript._login():173] 登录耗时 326060000 8 :0
[04-12 15:26:07:043:INFO :LoginScript._login():178] 登录耗时 326060000 8 :0
[04-12 15:26:07:043:INFO :LoginScript._login():166] 登录耗时 326060001 7 :0
[04-12 15:26:07:043:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:326060000 用户登录完成!!!
[04-12 15:26:07:043:INFO :LoginScript._login():173] 登录耗时 326060001 8 :0
[04-12 15:26:07:043:INFO :LoginScript._login():178] 登录耗时 326060001 8 :0
[04-12 15:26:07:043:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:326060001 用户登录完成!!!
[04-12 15:26:07:043:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:1
[04-12 15:26:07:044:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:2

 

 加到并发效果试试

1 [04-12 15:28:34:648:INFO :LoginScript._login():95] 登录耗时 328340007 4 :34
2 [04-12 15:28:34:648:INFO :LoginScript._login():114] 用户:328340007 数据库不存在!!!创建用户
3 [04-12 15:28:34:648:INFO :LoginScript._login():136] 登录耗时 328340007 5 :34
4 [04-12 15:28:34:648:INFO :LoginScript._login():164] 登录耗时 328340007 6 :34
5 [04-12 15:28:34:648:INFO :LoginScript._login():166] 登录耗时 328340007 7 :34
6 [04-12 15:28:34:648:INFO :LoginScript._login():173] 登录耗时 328340007 8 :34
7 [04-12 15:28:34:648:INFO :LoginScript._login():178] 登录耗时 328340007 8 :34
8 [04-12 15:28:34:648:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:328340007 用户登录完成!!!
9 [04-12 15:28:34:649:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:35

 

当并发加到10的时候,处理登录耗时就出现了;

我把数据库记录手动加到200多万条数据库再次测试一下;

 

 再次尝试注册登录请求的时候

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第4张图片

直接导致线程并发死锁;

[04-12 15:41:03:665:INFO :LoginScript._login():95] 登录耗时 340290009 4 :34059
[04-12 15:41:03:665:INFO :LoginScript._login():114] 用户:340290009 数据库不存在!!!创建用户
[04-12 15:41:03:665:INFO :LoginScript._login():136] 登录耗时 340290009 5 :34059
[04-12 15:41:03:666:INFO :LoginScript._login():84] 用户:340290003 不存在缓存用户!!!
[04-12 15:41:03:666:INFO :LoginScript._login():93] 登录耗时 340290003 3 :34056
[04-12 15:41:03:667:INFO :LoginScript._login():164] 登录耗时 340290009 6 :34061
[04-12 15:41:03:668:INFO :LoginScript._login():166] 登录耗时 340290009 7 :34062
[04-12 15:41:03:668:INFO :LoginScript._login():173] 登录耗时 340290009 8 :34062
[04-12 15:41:03:668:INFO :LoginScript._login():178] 登录耗时 340290009 8 :34062
[04-12 15:41:03:668:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:340290009 用户登录完成!!!
[04-12 15:41:03:671:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:34065

 

我们从抛错和和打印日志情况分析,出现的情况为当操作来了以后,发现缓存不存在,然后进入锁状态,去操作数据库查询;

我们看到登录耗时 4 打印,情况发现查询数据库直接咯嘣;

查询数据是否存在居然耗时34秒;

好吧,数据库原因导致了查询耗时;

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第5张图片

通过软件查询,也依然是耗时的,排除程序查询代码性能问题;

然后我们通过分析userinfo类

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第6张图片

我们通过对userinfo类的分析,我们只对id字段加入了主键;那么数据库默认对id这个字段加入了索引;

然后我们每一次请求登录的时候数据库检索只能通过userNameLowerCase 字段进行检索;那么考虑对字段加入索引情况;

    @Id
    @Column(nullable = false, unique = true)
    private long id;
    /**
     *
     */
    @Column(nullable = false, unique = true)
    private String userName;
    /**
     *
     */
    @Column(nullable = false, unique = true)
    private String userNameLowerCase;

 

我考虑在id,username  userNameLowerCase 三个字段都加入唯一键索引;

我先删除掉数据库,再收到把数据加到200多万测试

在改造了数据库索引后我们

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第7张图片

并发下我们还是看出了,登录耗时情况;

看到这里,我们登录的操作,已经是加入缓存处理,数据库索引,提供查询等操作;可并发下还是会耗时呢?

仔细看代码发现

net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)_第8张图片

其实我们登录操作, 注册和查询数据库的时候,是需要加锁,保证唯一;

但是我们忽略了一个问题,加锁的时候,其实值加锁,账户的小写副本字符串就可以达到效果了;我这里加入了整个对象锁;锁的范围过大; 

                    /*数据库操作之前,加锁,锁定账户小写副本,就一定能针对单账户锁定*/
                    synchronized (usernameLowerCase) {
[04-12 16:11:58:123:INFO :LoginScript._login():95] 登录耗时 411580006 4 :3
[04-12 16:11:58:123:INFO :LoginScript._login():114] 用户:411580006 数据库不存在!!!创建用户
[04-12 16:11:58:124:INFO :LoginScript._login():136] 登录耗时 411580006 5 :4
[04-12 16:11:58:124:INFO :LoginScript._login():95] 登录耗时 411580009 4 :3
[04-12 16:11:58:124:INFO :LoginScript._login():114] 用户:411580009 数据库不存在!!!创建用户
[04-12 16:11:58:124:INFO :LoginScript._login():164] 登录耗时 411580006 6 :4
[04-12 16:11:58:124:INFO :LoginScript._login():136] 登录耗时 411580009 5 :3
[04-12 16:11:58:124:INFO :LoginScript._login():164] 登录耗时 411580009 6 :3
[04-12 16:11:58:124:INFO :LoginScript._login():166] 登录耗时 411580006 7 :4
[04-12 16:11:58:124:INFO :LoginScript._login():173] 登录耗时 411580006 8 :4
[04-12 16:11:58:124:INFO :LoginScript._login():178] 登录耗时 411580006 8 :4
[04-12 16:11:58:124:INFO :LoginScript._login():166] 登录耗时 411580009 7 :3
[04-12 16:11:58:124:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:411580006 用户登录完成!!!
[04-12 16:11:58:124:INFO :LoginScript._login():173] 登录耗时 411580009 8 :3
[04-12 16:11:58:124:INFO :LoginScript._login():178] 登录耗时 411580009 8 :3
[04-12 16:11:58:124:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:411580009 用户登录完成!!!
[04-12 16:11:58:124:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:3
[04-12 16:11:58:124:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:4

 

现在可以看的出来,我们注册登录耗时,大约4毫秒了;

[04-12 16:12:55:717:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:2
[04-12 16:12:55:717:INFO :LoginScript._login():166] 登录耗时 411580009 7 :0
[04-12 16:12:55:717:INFO :LoginScript._login():173] 登录耗时 411580009 8 :0
[04-12 16:12:55:717:INFO :LoginScript._login():178] 登录耗时 411580009 8 :0
[04-12 16:12:55:717:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:411580009 用户登录完成!!!
[04-12 16:12:55:719:INFO :LoginScript._login():166] 登录耗时 411580006 7 :3
[04-12 16:12:55:719:INFO :LoginScript._login():173] 登录耗时 411580006 8 :3
[04-12 16:12:55:719:INFO :LoginScript._login():178] 登录耗时 411580006 8 :3
[04-12 16:12:55:719:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:2

 

缓存登录情况;

总结

本次优化的地方,重点在于;

1、防止重复注册依赖数据库检查的是时候,锁对象划分;我们正对账号的小写副本(String) 加锁,是一定能锁定的;

2、加入缓存情况,当前账号登录后,加入滑动缓存,2小时候清理对象;

3、优化数据库方案,加入索引;

4、数据库写入操作,上文一直没讲;这里描述。

以上代码数据库写入操作都是异步的,保证了数据在内存验证通过后,创建对象,异步写入数据库一定能通过数据库验证写入数据库中;

采用集中批量提交数据库方案,提高写入优化功能;

 

你可能感兴趣的:(net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS))