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类
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、数据库写入操作,上文一直没讲;这里描述。
以上代码数据库写入操作都是异步的,保证了数据在内存验证通过后,创建对象,异步写入数据库一定能通过数据库验证写入数据库中;
采用集中批量提交数据库方案,提高写入优化功能;
net.sz.framework 框架 登录服务器架构 单服2 万 TPS(QPS)的更多相关文章
- net.sz.framework 框架 轻松搭建服务---让你更专注逻辑功能---初探
前言 在之前的文章中,讲解过 threadmodel,socket tcp ,socket http,log,astart ,scripts: 都是分片讲解,从今天开始,将带大家,一窥 net.sz. ...
- net.sz.framework 框架 轻松搭建数据服务中心----读写分离数据一致性,滑动缓存
前言 前文讲述了net.sz.framework 框架的基础实现功能,本文主讲 net.sz.framework.db 和 net.sz.framework.szthread; net.sz.fram ...
- net.sz.framework 框架 ORM 消消乐超过亿条数据排行榜分析 天王盖地虎
序言 天王盖地虎, 老婆马上生孩子了,在家待产,老婆喜欢玩消消乐类似的休闲游戏,闲置状态,无聊的分析一下消消乐游戏的一些技术问题: 由于我主要是服务器研发,客户端属于半吊子,所以就分析一下消消乐排行榜 ...
- Spring Framework(框架)整体架构 变迁
Spring Framework(框架)整体架构 2018年04月24日 11:16:41 阅读数:1444 标签: Spring框架架构 更多 个人分类: Spring框架 版权声明:本文为博主 ...
- Android源码分析(五)-----如何从架构师的角度去设计Framework框架
一 : 架构与程序 软件架构是一种思维方式,而程序只是实现思维方式的一种手段,代码固然重要,但是若没有整体的思维架构,一切程序都如水中浮萍. 二 : 框架如何设计 暂时抛开Android Framew ...
- Django之Rest Framework框架
一.什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角度 ...
- [转]MMORPG服务器架构
MMORPG服务器架构 一.摘要 1.网络游戏MMORPG整体服务器框架,包括早期,中期,当前的一些主流架构2.网络游戏网络层,包括网络协议,IO模型,网络框架,消息编码等.3.网络游戏的场景管理,A ...
- 无服务器架构(Faas/Serverless)
摘要无服务器架构(Faas/Serverless),是软件架构领域的热门话题. AWS,Google Cloud和Azure - 在无服务器上投入了大量资金,已经在看到了大量专门针对Faas/Serv ...
- 网络游戏MMORPG服务器架构
转载于:http://justdo2008.iteye.com/blog/1936795 1.网络游戏MMORPG整体服务器框架,包括早期,中期,当前的一些主流架构 .关键词 网络协议 网络IO 消息 ...
随机推荐
- Html5与CSS3权威指南 百度云下载
Html5与CSS3权威指南 百度云下载 链接:http://pan.baidu.com/s/1hq6Dlvm 密码:php3
- chrome浏览器美化插件:让你的浏览器页面冒水泡, 游小鱼儿
下载插件和效果图 这是一个让你的浏览器冒泡泡的插件, 浏览网页的时候仿佛置身于海底世界: 插件下载地址:http://files.cnblogs.com/files/diligenceday/chro ...
- RHL 6.0学习日记, 先记下来,以后整理。
今天又遇到哪些问题呢? 1.配置网络的问题,我把网络配置文件胡乱改了,然后就上不了网了 因为一直都没怎么用到网络,网线都不怎么连接的,今天只是突然想ping一下,于是就Ping 了一下,一开始ping ...
- windows 安装 Scrapy的套路
我最近在琢磨scrapy爬虫框架,在windows中安装scrapy遇到了不少坑:直接 pip install scrapy 安装不成功的,百度说要安装vc2008+等等,安装这些时间太长,最后找到一 ...
- 妙用 `package.json` 快速 `import` 文件(夹)
前言 import router from './router'; import router from '../../router'; import router from './../../../ ...
- 在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理(转)
在之前一篇随笔<在.NET项目中使用PostSharp,实现AOP面向切面编程处理>介绍了PostSharp框架的使用,试用PostSharp能给我带来很多便利和优势,减少代码冗余,提高可 ...
- wemall开源商城免费商城系统部分代码(内含代码地址)
wemall开源商城免费商城系统部分代码,下面分享部分代码,供学习者学习: 开源版把install文件夹下的install.lock删除之后可进行自动安装 后台访问地址:http:// www.xxx ...
- 1432: [ZJOI2009]Function
1432: [ZJOI2009]Function Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 710 Solved: 528[Submit][Stat ...
- jquery写日期选择器
跟上我的脚步,让我们来领略代码的世界! 使用jquery做一个日期时间选择器,最好使用bootstrap弹窗 实现: (1)点击文本框弹出窗口: (2)弹窗里面显示日期时间选择下拉 (3)年份取当前年 ...
- 千呼万呼使出来Gogland (jetBrains发布的golang IDE)
由于之前一直在用PyCharm在开发, 已经习惯了这个IDE. 转golang开发后一直没找到合适的debug功能的IDE,忽然听说jetBrains发布测试版golang IDE: Gogland带 ...