用户在线时长怎么设计 —— 一个Java开发者的实战总结

用户在线时长怎么设计 ------ 一个Java开发者的实战总结

作者:Java老码农(8年经验)

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

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

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

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


二、业务场景分析

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

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

此外,还要考虑:

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

三、技术选型

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

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

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

优点 :实时性好,性能高
缺点:需要前端配合,断线后需要容错逻辑

2. 登录登出打点

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

优点 :简单、易分析
缺点:断线等异常退出难处理

3. 混合方案(推荐)

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

四、核心数据结构设计

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

Redis结构示例

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

MySQL表结构设计

sql 复制代码
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. 心跳接口

less 复制代码
@RestController
@RequestMapping("/api/online")
public class OnlineController {

    private static final String ONLINE_KEY_PREFIX = "online:user:";
    private static final long HEARTBEAT_EXPIRE_SECONDS = 300; // 5分钟

    @Autowired
    private StringRedisTemplate redisTemplate;

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

2. 登录/登出打点

scss 复制代码
@Service
public class UserSessionService {

    @Autowired
    private UserOnlineLogRepository repository;

    private final Map<Long, UserOnlineLog> sessionMap = new ConcurrentHashMap<>();

    /**
     * 用户登录,记录登录时间
     */
    public void login(Long userId, String sessionId) {
        UserOnlineLog log = new UserOnlineLog();
        log.setUserId(userId);
        log.setSessionId(sessionId);
        log.setLoginTime(LocalDateTime.now());
        sessionMap.put(userId, log);
    }

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

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

typescript 复制代码
@Component
public class OnlineStatisticsJob {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserDailyOnlineRepository dailyRepository;

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

        for (String key : keys) {
            Long userId = Long.valueOf(key.split(":")[2]);
            // 模拟一个5分钟的在线时长
            UserDailyOnline online = new UserDailyOnline();
            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的持久化能力,构建一个稳定、可扩展的在线时长系统。

欢迎评论区交流你们的实现方案!

相关推荐
ZC1111K10 分钟前
maven(配置)
java·maven
brzhang1 小时前
颠覆你对代码的认知:当程序和数据只剩下一棵树,能读懂这篇文章的人估计全球也不到 100 个人
前端·后端·架构
慕y2741 小时前
Java学习第五十八部分——设计模式
java·学习·设计模式
躲在云朵里`1 小时前
SpringBoot的介绍和项目搭建
java·spring boot·后端
喵个咪2 小时前
Golang微服框架Kratos与它的小伙伴系列 - 分布式事务框架 - DTM
后端·微服务·go
菜还不练就废了2 小时前
7.19-7.20 Java基础 | File类 I/O流学习笔记
java·笔记·学习
Yweir2 小时前
Elastic Search 8.x 分片和常见性能优化
java·python·elasticsearch
设计师小聂!2 小时前
尚庭公寓--------登陆流程介绍以及功能代码
java·spring boot·maven·mybatis·idea
brzhang2 小时前
我见过了太多做智能音箱做成智障音箱的例子了,今天我就来说说如何做意图识别
前端·后端·架构
心平愈三千疾3 小时前
学习秒杀系统-页面优化技术
java·学习·面试