统计用户在线时长

x33g5p2x  于2022-02-07 转载在 其他  
字(3.2k)|赞(0)|评价(0)|浏览(270)

一 需求描述

一款在线游戏,玩家登录后可以选择挂机和游戏两种状态。

  • 挂机状态:玩家不操纵游戏,游戏由系统托管。
  • 游戏状态:玩家主动操纵游戏。

玩家刚登录时直接进入游戏状态,玩家的游戏状态如下图所示。

在玩家的游戏面板上展示自上线后的挂机的累计时长和游戏的累计时长。此功能仅作为展示使用,目的是让玩家对挂机时间和主动游戏时间有一个概念,合理分配不同状态的时间。

允许统计时间有一分钟内的时延。

服务器端支持显示功能,计算好两个时长数据,供客户端展示。

二 项目背景

该游戏服务器已经有了两个存储玩家相关数据的数据库表。

1 玩家状态表

存储玩家的当前状态,是挂机还是游戏状态。

该表实现游戏逻辑,一些逻辑模块会读写该表的数据来实现游戏逻辑。

该表包含的字段:

  • 玩家当前状态
  • 玩家标识id

内容如下

| <br>玩家状态表<br> |
| <br>玩家标识id<br> | <br>玩家当前状态<br> |
| <br>123<br> | <br>主动游戏<br> |
| <br>456<br> | <br>离线<br> |
| <br>789<br> | <br>挂机<br> |

2 玩家状态流水表

每次玩家状态切换就存储一条日志,记录状态切换发生的时间、切换前和切换后的状态。

该表仅用于记录日志,线上系统不使用该表数据,该表一般用于查询问题,或者离线统计玩家数据。

该表包含字段:

  • 玩家标识id
  • 玩家切换前状态
  • 玩家切换后状态
  • 状态切换时刻

内容如下

| 玩家状态流水表 |
| <br>玩家标识id<br> | <br>切换前状态<br> | <br>切换后状态<br> | <br>切换状态时刻<br> |
| <br>123<br> | <br>离线<br> | <br>主动游戏<br> | <br>8:00:00<br> |
| <br>123<br> | <br>主动游戏<br> | <br>挂机<br> | <br>8:01:00<br> |
| <br>123<br> | <br>挂机<br> | <br>离线<br> | <br>8:02:00<br> |
| <br>456<br> | <br>主动游戏<br> | <br>离线<br> | <br>8:00:00<br> |
| <br>789<br> | <br>主动游戏<br> | 挂机 | <br>8:00:00<br> |

三 需求分析

该需求并不影响游戏的主要逻辑,是一个分支逻辑的需求,在开发的时候允许有时延。相对来说,保证游戏稳定实现最重要。统计时长的可用性优先级更低一些。在设计需求的时候,要尽量规避对线上已有逻辑的影响,更多的是加一个不影响游戏主干逻辑的分支逻辑。而且要控制好优先级,当出现问题时可以降低该模块的优先级。

归纳需求分析后得出的初步结论:

  • 该需求是分支逻辑,不要影响游戏的主干逻辑
  • 可以利用一分钟时延来降低计算频率
  • 该功能点的优先级低,在异常情况下,可以做柔性处理

四 实现方案

1 方案一——服务器轮询计算时长

客户端每分钟访问一次服务器,询问用户的挂机时长和主动游戏时长。服务器受到请求后,查询玩家状态流水表,获取用户从登录到当前时间的全部流水数据。然后根据每次状态的持续时间计算出累计时长并返回给前端。

这种方法实现起来最直观,在数据量小的时候能满足用户需求。如果用户数据很多,或者表中有很多用户信息的时候,那么从流水表搜索出的内容就会很多,查询一个用户所用的时间就会变长。

服务器轮询计算时长的架构和逻辑如下图所示:

2 优化计算时长算法

方案一虽然直观,但有很多重复计算,每次查询时都要对从前的所有记录进行计算,很多已经计算过的时长并不需要重新计算。

为了不重复计算,可以利用空间换时间,把已经计算的结果保存到新建的表中,建立一个新的表,叫作统计时长表。

该表包含内容如下

| <br>统计时长表<br> |
| <br>玩家标识id<br> | <br>累计主动游戏时长<br> | <br>累计挂机时长<br> | <br>最近一次切换到的状态<br> | <br>最近一次切换状态时刻<br> |
| <br>123<br> | <br>10<br> | <br>5<br> | <br>主动游戏<br> | <br>8:05:00<br> |
| <br>456<br> | <br>1<br> | <br>3<br> | <br>挂机<br> | <br>8:01:00<br> |
| <br>789<br> | <br>0<br> | <br>0<br> | <br>离线<br> | <br>8:00:00<br> |

当有请求过来时,服务器查询统计时长表,然后根据最近一次状态和当前时间把上次状态切换时刻到当前时刻的累计时间加到结果中,最后返回给前端。

优化计算时长算法的架构和逻辑如下图:

伪代码

// 获取当前累计时长 timePlay(主动游戏时长)
// timeOff(挂机时长)
// latestStatus(最近一次切换的状态)
// 最近一次切换到 latestStatus 的时刻 timeStampSwitch
// timeNow 表示当前时刻
if(latestStatus == "主动游戏"){
    timePlay += timeNow - timeStampSwitch
}
if(latestStatus == "挂机"){
    timeOff += timeNow - timeStampSwitch
}

3 客户端本地实现

如果需求的前提是仅在客户端展示,那么该需求可以不用服务器参与,只要客户端本地计算结果并展示即可。

客户端侧计算逻辑和方案二类似。

用户上线后,客户端保存两个变量:存储挂机时长和主动游戏时长。当用户操作客户端状态切换时候,客户端把该状态累计的时间增加到对应的变量上,然后更新当前状态即可。

方案三的处理逻辑如下图

该方案有一个前提条件:客户端使用的时间不能被用户终端影响,否则用户调整终端时间会影响展示结果。

因为客户端不能信任用户的本地时间,所以需要在客户端和服务端之间时间校对功能。

所以,方案三也是可行的。从工作量和实现代价等方面综合考虑,方案三是当前的最优方案。

五 案例总结

通过对比以上三个方案,可以发现最直观的方案并不一定是最优的——要思考是否有更好的算法来抽象模型,让整体开发更简单。方案二经过了算法优化,要优于方案一。

作为后端架构师,并不一定要通过后端的实现方案来解决问题,而要从目标入手来实现需求,达到整体最优即可。所以,综合考虑该需求的解决方案,方案三步需要在服务端做任何修改,只要修改客户端即可实现功能,规避了分布式要处理的通用问题。

相关文章