⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7529035047552335907 「天天摸鱼的java工程师」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

一、为什么要统计用户在线时长?

在业务系统中,统计用户在线时长是一个非常常见的需求。不同的业务场景对在线时长有不同的理解,比如:

  • IM系统: 判断用户是否在线,及其活跃时长。
  • 学习平台: 统计用户每天的学习时间。
  • 游戏系统: 记录用户每日在线时长,用于反作弊或计算活跃奖励。
  • SaaS系统: 用于客户活跃度分析。

那如何设计一个灵活、可扩展、性能良好的“用户在线时长”系统呢?

二、业务场景分析

从业务角度来看,在线时长可以按以下几个维度进行统计:

  • 按日统计: 用户每天在线了多久。
  • 按会话统计: 每次登录-退出的在线时间。
  • 实时在线状态: 用户此刻是否在线。
  • 跨设备: 用户可能在多个设备登录。

此外,还要考虑:

  • 网络断线、异常退出情况。
  • 数据存储和后期分析。

三、技术选型

基于多年经验,我们可以从以下几个方案中选择:

1. 使用心跳机制 + Redis缓存

  • 前端每隔30秒发送一次心跳请求。
  • 使用Redis记录最后心跳时间。
  • 判断“当前时间 - 最后心跳时间 < 超时时间”,则视为在线。

优点: 实时性好,性能高

缺点: 需要前端配合,断线后需要容错逻辑

2. 登录登出打点

  • 记录用户登录时间和退出时间
  • 每次会话都记录一条数据

优点: 简单、易分析

缺点: 断线等异常退出难处理

3. 混合方案(推荐)

  • 登录登出打点 + 心跳补充
  • 每天定时汇总Redis心跳日志 → MySQL

四、核心数据结构设计

我们使用Redis + MySQL结合的方案:

Redis结构示例

Key: online:user:{userId}
Value: 时间戳(最后心跳时间)
Type: String(或Hash)
TTL: 5分钟自动过期

MySQL表结构设计

CREATE TABLE user_online_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
session_id VARCHAR(64),
login_time DATETIME,
logout_time DATETIME,
duration_seconds INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

五、Java核心实现

1. 心跳接口

@RestController
@RequestMapping("/api/online")
publicclassOnlineController {

privatestaticfinalStringONLINE_KEY_PREFIX="online:user:";
privatestaticfinallongHEARTBEAT_EXPIRE_SECONDS=300; // 5分钟

@Autowired
private StringRedisTemplate redisTemplate;

/**
* 用户心跳接口
*/
@PostMapping("/heartbeat")
public ResponseEntity<String> heartbeat(@RequestParam Long userId) {
Stringkey= ONLINE_KEY_PREFIX + userId;
redisTemplate.opsForValue().set(key, String.valueOf(System.currentTimeMillis()), HEARTBEAT_EXPIRE_SECONDS, TimeUnit.SECONDS);
return ResponseEntity.ok("heartbeat received");
}
}

2. 登录/登出打点

@Service
publicclassUserSessionService {

@Autowired
private UserOnlineLogRepository repository;

privatefinal Map<Long, UserOnlineLog> sessionMap = newConcurrentHashMap<>();

/**
* 用户登录,记录登录时间
*/
publicvoidlogin(Long userId, String sessionId) {
UserOnlineLoglog=newUserOnlineLog();
log.setUserId(userId);
log.setSessionId(sessionId);
log.setLoginTime(LocalDateTime.now());
sessionMap.put(userId, log);
}

/**
* 用户登出,记录登出时间并计算在线时长
*/
publicvoidlogout(Long userId) {
UserOnlineLoglog= sessionMap.remove(userId);
if (log != null) {
log.setLogoutTime(LocalDateTime.now());
longseconds= Duration.between(log.getLoginTime(), log.getLogoutTime()).getSeconds();
log.setDurationSeconds((int) seconds);
repository.save(log);
}
}
}

3. 定时任务汇总在线时长

@Component
publicclassOnlineStatisticsJob {

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private UserDailyOnlineRepository dailyRepository;

@Scheduled(cron = "0 0 1 * * ?")// 每天凌晨1点
publicvoidcollectDailyOnlineTime() {
Set<String> keys = redisTemplate.keys("online:user:*");
if (keys == null) return;

for (String key : keys) {
LonguserId= Long.valueOf(key.split(":")[2]);
// 模拟一个5分钟的在线时长
UserDailyOnlineonline=newUserDailyOnline();
online.setUserId(userId);
online.setDate(LocalDate.now().minusDays(1));
online.setDurationSeconds(300); // 实际应根据时间差统计
dailyRepository.save(online);
}
}
}

六、总结与优化建议

  • 心跳频率: 建议30-60秒一次,平衡实时性与性能。
  • Redis TTL机制: 可自动判断用户是否掉线。
  • 异常退出处理: 通过定时任务弥补。
  • 跨设备问题: Redis key可记录多个sessionId。

七、写在最后

用户在线时长看似简单,实则涉及多个技术点和业务判断。作为一个有8年经验的Java开发者,我建议:从业务需求出发,结合Redis的高性能与MySQL的持久化能力,构建一个稳定、可扩展的在线时长系统。

文章目录
  1. 1. 一、为什么要统计用户在线时长?
  2. 2. 二、业务场景分析
  3. 3. 三、技术选型
    1. 3.0.1. 1. 使用心跳机制 + Redis缓存
    2. 3.0.2. 2. 登录登出打点
    3. 3.0.3. 3. 混合方案(推荐)
  • 4. 四、核心数据结构设计
    1. 4.0.0.1. Redis结构示例
    2. 4.0.0.2. MySQL表结构设计
  • 5. 五、Java核心实现
    1. 5.0.1. 1. 心跳接口
    2. 5.0.2. 2. 登录/登出打点
    3. 5.0.3. 3. 定时任务汇总在线时长
  • 6. 六、总结与优化建议
  • 7. 七、写在最后