zoukankan      html  css  js  c++  java
  • 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 492794628@qq.com<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 492794628@qq.com<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 492794628@qq.com<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 492794628@qq.com<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 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;
        }
    }
    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 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;
        }
    }
    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 492794628@qq.com<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("");
    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

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

    整个登录流程

    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

    测试结果:

    [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多万条数据库再次测试一下;

     

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

    直接导致线程并发死锁;

    [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、数据库写入操作,上文一直没讲;这里描述。

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

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

  • 相关阅读:
    docker vm 性能优劣
    Jeecg-Boot Spring Boot
    MyBatis-Plus
    Eclipse lombok java
    MySQL 高可用性—keepalived+mysql双主
    如何使用 Docker 来限制 CPU、内存和 IO等资源?
    Requires: libc.so.6(GLIBC_2.14)(64bit)
    项目管理、软件、禅道 VS JIRA
    解决Window安全中心对Kitematic-0.17.3-Ubuntu.zip提示病毒,但无法删除的问题。
    正则表达式 贪婪模式
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/6699792.html
Copyright © 2011-2022 走看看