惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
有赞技术团队
有赞技术团队
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
L
LINUX DO - 最新话题
T
Troy Hunt's Blog
博客园_首页
量子位
Jina AI
Jina AI
S
SegmentFault 最新的问题
IT之家
IT之家
Hacker News - Newest:
Hacker News - Newest: "LLM"
大猫的无限游戏
大猫的无限游戏
N
News | PayPal Newsroom
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
S
Securelist
Google Online Security Blog
Google Online Security Blog
P
Privacy International News Feed
博客园 - Franky
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
C
Cisco Blogs
V
Vulnerabilities – Threatpost
腾讯CDC
The Hacker News
The Hacker News
K
Kaspersky official blog
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Security Archives - TechRepublic
Security Archives - TechRepublic
A
About on SuperTechFans
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
Scott Helme
Scott Helme
B
Blog
Security Latest
Security Latest
Last Week in AI
Last Week in AI
Google DeepMind News
Google DeepMind News
W
WeLiveSecurity
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tenable Blog
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Schneier on Security

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
重构登录逻辑
重归混沌 · 2019-11-05 · via 重归混沌的BLOG

终于又甩掉了一个包袱, 这是我重构完第一句想说的话。

从我入职开始, 就因为这套登录逻辑的复杂性而略为不满, 其间还坑过我一次,但是因为一直勉强能用,所以也没有理由对它动手。

这一次终于因为不能够满足新需求,而让我有理由可以重构这段代码了。


旧的登录流程大致如下:

    $UST=(uid,session,token)
    Client-------------AuthServer---------------------GameServer-----------DBProxy
     *--"账号密码认证" ---> * 
                     检查账号密码,
                     生成session和token
                          *----------发送$UST----------->*
                                                     记录$UST
                          *<----------成功--------------*
    *<------$UST----------*
    *-------使用$UST登陆-------------------------------->*
                                                     校验$UST是否匹配
                                                        *------请求加载------->*
                                                                      加载玩家数据
                                                        *<---返回玩家数据------*
    *<---------------------------返回登陆成功------------*

每一个认证流程都会生成一个惟一session,类似tcp连接. 甚至加载玩家数据也会带着同样的session请求。如果此次数据加载与当前登陆session不符,则拒绝使用,直接抛弃。

session的存在使的整个“认证-登陆”流程可以做到精确无比。

然而,这种精确会带来其他问题,例如客户端出于某种bug多发了一次认证,就会导致整个登陆流程失败,并且会导致重复加载玩家数据(事实上我真的被这个问题坑过,查了好久才发现问题)。


经过这次重构后,我去掉了session机制,并将GameServer的整个登陆流程切分成三块相对独立的逻辑,即“认证逻辑”,“加载逻辑”,“登陆逻辑”。

  1. 当AuthServer检查账号密码正确后,即颁发token, 将uid,token发往GameServer。
  2. 当GameServer的“认证逻辑”到消息uid,token之后,记下token并随即调用“加载逻辑”进行数据加载。
  3. 当GameServer的“登陆逻辑”收到协议后会检查uid,token是否匹配,如果匹配则等待数据加载完成,然后向客户端返回登陆成功。

“认证逻辑”在收到AuthServer的消息后,仅简单的记录每一个要登陆的uid对应的最新的token,并踢掉当前在线的链接。

“加载逻辑”则需要对加载请求进行过滤,防止重复加载。当收到加载请求后,如果数据已经加载途中或已经加载完成,则直接扔掉加载请求。为了防止DBProxy挂掉,而造成加载卡死。每一个玩家会有一个30秒的加载超时时间(即超过30s后即使DBProxy还没有返回数据,下次收到加载数据请求,依然会再次向DBProxy请求)。

加载数据返回后,总是检查当前内存中是否已经有加载成功的数据,如果没有才使用当前DBProxy返回的玩家数据,如果已经有数据,则直接抛弃。

之所以这样做,是为了数据一致性问题。来看一下如下操作序列。

GameServer视角:加载数据请求A—〉等待30秒—>加载数据请求B—->加载数据请求A返回—>玩家开始操作,修改玩家数据—>向DBProxy更新数据—>另载数据请求B返回。

DBProxy视角: 加载数据A->返回加载数据A->加载数据B->返回加载数据B->修改数据

如果此时应用数据B,则在A返回和B返回对玩家数据的修改则全部会丢失。而旧的登陆逻辑,之所以在加载数据时依然带着session,应该也是为了防止数据一致性问题,但是我们玩家数据都是采用Write-Through方式来将一个玩家的所有数据都cache内存之后才进行修改的。因此我认为只取第一份成功加载的数据就可以解决一致性问题。

重构之后的登陆流程大概如下:

    $UT=(uid,token)
    Client-------------AuthServer---------------------GameServer-----------DBProxy
     *--"账号密码认证" ---> * 
                     检查账号密码,
                     生成session和token
                          *----------发送$UT----------->*
                                                     记录$UT
                          *<----------成功--------------*--------请求加载----->*
    *<------$UT----------*                                         加载玩家数据
                                                         | <-----返回玩家数据--*
    *-------使用$UST登陆--------------------------------> |
                                                         |
    *<------返回登陆成功----------------------------------* 

本来“加载逻辑”还要负责登陆超时的淘汰操作,即数据加载之后,客户端迟迟不向GameServer发送登陆请求,这时需要将加载出来的数据从GameServer淘汰掉。

但是由于我们专为手游实现了半弱联网机制。在socket断掉之后,N分钟以内会保留玩家数据。这可以保证在地铁或电梯等信号不好处链接断掉后,可以在信号良好时快速登陆。

因此“加载逻辑”将数据加载完成之后,直接交给原本的数据淘汰逻辑即可。


除了登陆流程以外,这次重构我还修改了"登陆"的语义。

重构前, 对AuthServer发起认证时,会先尝试对账号进行注册,再发送登陆协议。虽然这样可以避免在新号时连着发送三条认证相关的协议。但总感觉有点bad taste。

重构后,我将"登陆"定义为"如果账号不存在则先执行注册, 然后执行登陆逻辑"。

重构前,对GameServer发起登陆时,如果uid还没有对应的游戏数据,同样会返回错误。然后客户端根据错误码去发初始化消息进行初始化, 在初始化的过程中,如果由于某种原因连续多发,同样会出现问题。

重构后,当GameServer收到$UT准备加载数据前,会先检查uid是否被初始化过数据,如果没有,则直接向DBProxy请求创建。DBProxy创建玩家数据与DBProxy加载玩家数据具有相同的返回。

这样整个"认证-登陆"逻辑相关的协议,由4条("注册","认证","创建","登陆")降为2条("认证","登陆")。

这里会有一个问题,刚开服时,可能有很多玩家只注册完就卸载走人了。这样会浪费我们的服务器资源。因为他只要发一条"认证",我们就帮他把数据创建好了。

幸运的是,我们新实现里, 恰好提前实现了删号功能。即一个低级玩家多久没登陆过之后,过一段时间会自动被清理。

ps.重构之后,个人觉得对于整个流程,服务端和客户端都各种更内聚了,因为整个登陆流程的判断都被挪到了服务端。