前言
无论我们做什么系统,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类
package net.sz.test; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.Table; /** * 用户信息表 * * <br> * author 失足程序员<br> * blog http://www.cnblogs.com/ty408/<br> * mail 492794628@qq.com<br> * phone 13882122019<br> */ @Table(name = "UserInfo") public class UserInfo implements Serializable { private static final long serialVersionUID = -8907709646630947645L; @Id private long id; /*账户名*/ private String userName; /*账户名小写副本*/ private String userNameLowerCase; /*密码*/ @Column(nullable = false) private String userPwd; /*电话*/ @Column(nullable = false) private String userPhone; /*邮件*/ @Column(nullable = false) private String userMail; /*创建时间*/ @Column(nullable = false) private long createTime; /*最后登录时间*/ @Column(nullable = false) private long lastLoginTime; /*状态,1正常,2表示不可登录*/ @Column(nullable = false) private int Status; /*登录后生成的*/ private transient String token; /*生成 token 的时间*/ private transient long tokenTime; /* 玩家当前登录服务器ID */ private int loginPlayerServerId; /* 逻辑服务器传递过来的同步时间 */ private transient long lastUplogintime; public UserInfo() { } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserNameLowerCase() { return userNameLowerCase; } public void setUserNameLowerCase(String userNameLowerCase) { this.userNameLowerCase = userNameLowerCase; } public String getUserPwd() { return userPwd; } public void setUserPwd(String userPwd) { this.userPwd = userPwd; } public String getUserPhone() { return userPhone; } public void setUserPhone(String userPhone) { this.userPhone = userPhone; } public String getUserMail() { return userMail; } public void setUserMail(String userMail) { this.userMail = userMail; } public long getCreateTime() { return createTime; } public void setCreateTime(long createTime) { this.createTime = createTime; } public long getLastLoginTime() { return lastLoginTime; } public void setLastLoginTime(long lastLoginTime) { this.lastLoginTime = lastLoginTime; } public int getStatus() { return Status; } public void setStatus(int Status) { this.Status = Status; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public long getLastUplogintime() { return lastUplogintime; } public void setLastUplogintime(long lastUplogintime) { this.lastUplogintime = lastUplogintime; } public long getTokenTime() { return tokenTime; } public void setTokenTime(long tokenTime) { this.tokenTime = tokenTime; } public int getLoginPlayerServerId() { return loginPlayerServerId; } public void setLoginPlayerServerId(int loginPlayerServerId) { this.loginPlayerServerId = loginPlayerServerId; } @Override public String toString() { 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 + '}'; } }
用来记录账户数据的;
登录功能划分设计
渠道登录脚本接口设计
package net.sz.game.login.logins.iscript; import net.sz.framework.nio.http.NioHttpRequest; import net.sz.framework.scripts.IBaseScript; /** * * <br> * author 失足程序员<br> * blog http://www.cnblogs.com/ty408/<br> * mail 492794628@qq.com<br> * phone 13882122019<br> */ public interface ILoginScriptPlatform extends IBaseScript { /** * 处理登录 平台登录 * * @param platform 平台ID * @param channelId 渠道ID * @param request 请求 * @return */ boolean login(int platform, int channelId, NioHttpRequest request); }
最终本地登录脚本接口设计
package net.sz.game.login.logins.iscript; import net.sz.framework.nio.http.NioHttpRequest; import net.sz.framework.scripts.IBaseScript; /** * * <br> * author 失足程序员<br> * blog http://www.cnblogs.com/ty408/<br> * mail 492794628@qq.com<br> * phone 13882122019<br> */ public interface ILoginScript extends IBaseScript { /** * 返回错误码 * * @param code * @param msg * @return */ String getErrorCode(int code, int msg); /** * 最终登录 * * @param username * @param userpwd * @param platform * @param channelId * @param request */ void _login(String username, String userpwd, int platform, int channelId, NioHttpRequest request); }
最终登录脚本需要反向引用,不能通过脚本调用
package net.sz.game.login.logins; import net.sz.game.login.logins.iscript.ILoginScript; /** * 登录管理类 * <br> * author 失足程序员<br> * blog http://www.cnblogs.com/ty408/<br> * mail 492794628@qq.com<br> * phone 13882122019<br> */ public class LoginManager { private static final LoginManager instance = new LoginManager(); public static LoginManager getInstance() { return instance; } public ILoginScript loginScript; }
在脚本里面加入
@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 492794628@qq.com<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; } }
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 492794628@qq.com<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; } }
这时模拟以后接取渠道不同处理形式,
比如ios,360,91,豌豆荚等(拒绝广告);
NettyHttpServer nioHttpServer = NettyPool.getInstance().addBindHttpServer("0.0.0.0", ServerHttpPort); //如果需要加入的白名单 //nioHttpServer.addWhiteIP("192.168"); nioHttpServer.addHttpBind((url, request) -> { ArrayList<IHttpAPIScript> evts = ScriptManager.getInstance().getBaseScriptEntry().getEvts(IHttpAPIScript.class); for (int i = 0; i < evts.size(); i++) { IHttpAPIScript get = evts.get(i); /*判断监听*/ if (get.checkUrl(url)) { /*处理监听*/ get.run(url, request); return; } } }, 20, "*");
开启http监听状态;这里可能需要你阅读之前的文章了解底层库支持;
package net.sz.game.login.scripts.logins; import java.util.List; import net.sz.framework.nio.http.NioHttpRequest; import net.sz.framework.scripts.IInitBaseScript; import net.sz.framework.szlog.SzLogger; import net.sz.framework.utils.GlobalUtil; import net.sz.framework.utils.JsonUtil; import net.sz.framework.utils.MD5Util; import net.sz.framework.utils.StringUtil; import net.sz.game.login.data.DataManager; import net.sz.game.login.logins.LoginManager; import net.sz.game.login.logins.iscript.ILoginScript; import net.sz.game.login.service.ServerManager; import net.sz.game.pmodel.po.loginsr.data.ServerInfo; import net.sz.game.pmodel.po.loginsr.data.UserInfo; /** * 登录本地系统 操作数据库 * <br> * author 失足程序员<br> * blog http://www.cnblogs.com/ty408/<br> * mail 492794628@qq.com<br> * phone 13882122019<br> */ public class LoginScript implements ILoginScript, IInitBaseScript { private static final SzLogger log = SzLogger.getLogger(); private static final String LOGINPWDSIGN = "af0ca5ee6203e02ec076aa8b84385d08"; @Override public void _init() { //反向注册 LoginManager.getInstance().loginScript = this; } @Override public String getErrorCode(int code, int msg) { String ret = "{" + "\"code\":" + code + ", \"msg\":" + msg + "}"; return ret; } @Override public void _login(String username, String userpwd, int platform, int channelId, NioHttpRequest request) { long currentTimeMillis = System.currentTimeMillis(); if (100 != (platform)) { username = platform + "_" + username; } log.info("登录耗时 " + username + " 1 :" + (System.currentTimeMillis() - currentTimeMillis)); boolean flag = true; String usernameLowerCase = username.toLowerCase(); if (!StringUtil.checkFilter(username, StringUtil.PATTERN_ABC_0) || !StringUtil.checkFilter(userpwd, StringUtil.PATTERN_ABC_PWD)) { if (log.isInfoEnabled()) { log.info("用户:" + username + " 账号或者密码非法字符!!!"); } request.addContent(getErrorCode(10, 830510)); flag = false; } if (!(100 == platform || request.getIp().startsWith("192.168.") || request.getIp().startsWith("127.0.0.1"))) { if (usernameLowerCase.startsWith("robot")) { if (log.isInfoEnabled()) { log.info("用户:" + username + " 并非特殊平台,不允许此账号!!!"); } request.addContent(getErrorCode(10, 830511)); flag = false; } } log.info("登录耗时 " + username + " 2 :" + (System.currentTimeMillis() - currentTimeMillis)); if (flag) { try { /*优先获取缓存状态*/ UserInfo userinfo = DataManager.getInstance().getUserInfoMap().get(usernameLowerCase); if (userinfo == null) { /*数据库操作之前,加锁*/ synchronized (this) { if (log.isInfoEnabled()) { log.info("用户:" + username + " 不存在缓存用户!!!"); } /*再次获取缓存状态,存在并发,那么获得锁权限以后有几率以及得到数据了*/ userinfo = DataManager.getInstance().getUserInfoMap().get(usernameLowerCase); if (userinfo != null) { if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 缓存用户!!!"); } } else { log.info("登录耗时 " + username + " 3 :" + (System.currentTimeMillis() - currentTimeMillis)); userinfo = DataManager.getInstance().getDataDao().getObjectByWhere(UserInfo.class, "where `userNameLowerCase` = ?", usernameLowerCase); log.info("登录耗时 " + username + " 4 :" + (System.currentTimeMillis() - currentTimeMillis)); if (userinfo == null) { if (DataManager.getInstance().getUserNameLowerCaseSet().contains(usernameLowerCase)) { request.addContent(getErrorCode(31, 830512)); if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 注册用户失败,重名!!!"); } return; } else { if ("robottroy".equalsIgnoreCase(usernameLowerCase)) { request.addContent(getErrorCode(31, 830513)); if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 注册用户失败,,特殊账号不能注册!!!"); } return; } if (log.isInfoEnabled()) { log.info("用户:" + username + " 数据库不存在!!!创建用户"); } userinfo = new UserInfo(); userinfo.setId(GlobalUtil.getId()); userinfo.setUserName(username); userinfo.setUserNameLowerCase(usernameLowerCase); userinfo.setUserPwd(userpwd); userinfo.setCreateTime(System.currentTimeMillis()); userinfo.setLastLoginTime(System.currentTimeMillis()); userinfo.setStatus(1); userinfo.setUserMail(""); userinfo.setUserPhone(""); userinfo.setPlatformId(platform); userinfo.setChannelId(channelId); DataManager.getInstance().getcUDThread().insert_Sync(userinfo); } } DataManager.getInstance().getUserNameLowerCaseSet().add(usernameLowerCase); DataManager.getInstance().getUserInfoMap().put(usernameLowerCase, userinfo); log.info("登录耗时 " + username + " 5 :" + (System.currentTimeMillis() - currentTimeMillis)); } } } else { if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 缓存用户!!!"); } } if (userinfo == null || !userinfo.getUserName().equals(username) || !userinfo.getUserPwd().equals(userpwd)) { request.addContent(getErrorCode(3, 830514)); if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户名或密码错误!!!"); } } else { //token生成之后3分钟 long md5time = System.currentTimeMillis(); //String token = MD5Util.md5Encode('=', userinfo.getId() + "", username, request.getIp(), md5time + "", MyAttributeKey.TOKENKEY); String token = MD5Util.md5Encode('=', userinfo.getId() + "", username, "", md5time + "", LOGINPWDSIGN); //更新token userinfo.setToken(token); //更新token生成时间 userinfo.setTokenTime(md5time); //更新最后同步时间 userinfo.setLastUplogintime(md5time); userinfo.getLastLoginTime(); userinfo.getLastUplogintime(); log.info("登录耗时 " + username + " 6 :" + (System.currentTimeMillis() - currentTimeMillis)); String serverInfo = ServerManager.getInstance().serverInfoScript.getServerInfo(platform, channelId, request, userinfo); log.info("登录耗时 " + username + " 7 :" + (System.currentTimeMillis() - currentTimeMillis)); Ret ret = new Ret(0, 0); ret.setToken(token); ret.setTime(md5time); ret.setUserName(username); ret.setUid(userinfo.getId()); String toJSONString = ret.showString(serverInfo); log.info("登录耗时 " + username + " 8 :" + (System.currentTimeMillis() - currentTimeMillis)); if (log.isDebugEnabled()) { log.debug("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户登录完成!!!同步服务器信息:" + toJSONString); } request.addContent(toJSONString); log.info("登录耗时 " + username + " 8 :" + (System.currentTimeMillis() - currentTimeMillis)); if (log.isInfoEnabled()) { log.info("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 用户登录完成!!!"); } } } catch (Exception e) { log.error("平台:" + platform + ", ip:" + request.getIp() + ", 用户:" + username + " 登录发生错误信息", e); request.addContent(getErrorCode(500, 830515)); } } } public static void main(String[] args) { 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:\"\"}]}"; jsonString = new LoginScript().getErrorCode(10, 830510); Ret parseObject = JsonUtil.parseObject(jsonString, Ret.class); log.error(parseObject.toString()); } static class Ret { private int code; private String token; private String userName; private int msg; private long time; private long uid; private ServerInfo[] infos; public Ret(int code, int msg) { this.code = code; this.msg = msg; } public Ret() { } public String showString(String serverinfos) { return "{" + "\"code\":" + code + ", \"token\":\"" + token + "\", \"userName\":\"" + userName + "\", \"msg\":\"" + msg + "\", \"time\":" + time + ", \"uid\":" + uid + ", \"infos\":" + serverinfos + "}"; } @Override public String toString() { return "{" + "code=" + code + ", token=" + token + ", userName=" + userName + ", msg=" + msg + ", time=" + time + ", uid=" + uid + ", infos=" + infos + '}'; } /** * @return the code */ public int getCode() { return code; } /** * @param code the code to set */ public void setCode(int code) { this.code = code; } /** * @return the token */ public String getToken() { return token; } /** * @param token the token to set */ public void setToken(String token) { this.token = token; } /** * @return the userName */ public String getUserName() { return userName; } /** * @param userName the userName to set */ public void setUserName(String userName) { this.userName = userName; } /** * @return the msg */ public int getMsg() { return msg; } /** * @param msg the msg to set */ public void setMsg(int msg) { this.msg = msg; } /** * @return the time */ public long getTime() { return time; } /** * @param time the time to set */ public void setTime(long time) { this.time = time; } /** * @return the uid */ public long getUid() { return uid; } /** * @param uid the uid to set */ public void setUid(long uid) { this.uid = uid; } /** * @return the infos */ public ServerInfo[] getInfos() { return infos; } /** * @param infos the infos to set */ public void setInfos(ServerInfo[] infos) { this.infos = infos; } } }
整个最后登录流程。设计;
整个登录流程
http 请求 -》 流向 http api -》 httploginscript -》 loginscript渠道登录 -》 loginscript 登录 -》缓存验证 -》 数据库验证 -》 返回结果;
C#代码测试调用
using Net.Sz.Framework.Netty.Http; using Net.Sz.Framework.Util; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace CApp_CheckLoginTps { class Program { static List<int> idList = new List<int>(); static IntegerSSId ids = new IntegerSSId(); static void Main(string[] args) { Console.WriteLine("准备就绪"); while (true) { Console.ReadLine(); Console.WriteLine("注册登录"); test(); Console.ReadLine(); Console.WriteLine("缓存登录"); test2(); } Console.ReadLine(); } static void test() { Program.idList.Clear(); int tcount = 2; for (int i = 1; i <= tcount; i++) { new Thread(() => { System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); int id = ids.GetId(); Program.idList.Add(id); 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"); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); }).Start(); } } static void test2() { int tcount = Program.idList.Count; for (int i = 0; i < tcount; i++) { new Thread(new ParameterizedThreadStart((object obj) => { System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); 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"); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); })).Start(Program.idList[i]); } } } }
测试结果:
[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
加到并发效果试试
[04-12 15:28:34:648:INFO :LoginScript._login():95] 登录耗时 328340007 4 :34 [04-12 15:28:34:648:INFO :LoginScript._login():114] 用户:328340007 数据库不存在!!!创建用户 [04-12 15:28:34:648:INFO :LoginScript._login():136] 登录耗时 328340007 5 :34 [04-12 15:28:34:648:INFO :LoginScript._login():164] 登录耗时 328340007 6 :34 [04-12 15:28:34:648:INFO :LoginScript._login():166] 登录耗时 328340007 7 :34 [04-12 15:28:34:648:INFO :LoginScript._login():173] 登录耗时 328340007 8 :34 [04-12 15:28:34:648:INFO :LoginScript._login():178] 登录耗时 328340007 8 :34 [04-12 15:28:34:648:INFO :LoginScript._login():180] 平台:100, ip:192.168.2.235, 用户:328340007 用户登录完成!!! [04-12 15:28:34:649:INFO :HttpLoginScript.run():50] 处理一个登陆耗时:35
当并发加到10的时候,处理登录耗时就出现了;
我把数据库记录手动加到200多万条数据库再次测试一下;
再次尝试注册登录请求的时候
直接导致线程并发死锁;
[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秒;
好吧,数据库原因导致了查询耗时;
通过软件查询,也依然是耗时的,排除程序查询代码性能问题;
然后我们通过分析userinfo类
我们通过对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多万测试
在改造了数据库索引后我们
并发下我们还是看出了,登录耗时情况;
看到这里,我们登录的操作,已经是加入缓存处理,数据库索引,提供查询等操作;可并发下还是会耗时呢?
仔细看代码发现
其实我们登录操作, 注册和查询数据库的时候,是需要加锁,保证唯一;
但是我们忽略了一个问题,加锁的时候,其实值加锁,账户的小写副本字符串就可以达到效果了;我这里加入了整个对象锁;锁的范围过大;
/*数据库操作之前,加锁,锁定账户小写副本,就一定能针对单账户锁定*/ 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、数据库写入操作,上文一直没讲;这里描述。
以上代码数据库写入操作都是异步的,保证了数据在内存验证通过后,创建对象,异步写入数据库一定能通过数据库验证写入数据库中;
采用集中批量提交数据库方案,提高写入优化功能;