场景题:如何实现亿级用户在线状态统计?

开发 前端
QQ 在线状态的统计功能就是亿级的,它的特征是:数据量大、内存占用高、实时性要求高,因此我们使用常规的解决方案是不能实现的。

近两年不知道大家有没有发现,现在的面试中《场景题》问的越来越多了,一方面是就业市场竞争者较多所带来的必然结果;另一方面是随着时间的推移,公司对于应聘者的技术要求也越来越高了,这时候只会八股文就不够了,你还得会更难的场景题才行。

所以,今天我们就来盘 Java 中的常见面试题《如何实现亿级用户在线状态统计?》,这个时候有人就会说了:“亿级?你确定你们公司有亿级用户同时在线的场景?”“我会亿级系统的设计还会来你们公司应聘吗?可笑”。

哈哈哈,确实如此,这些质疑都是合理的。但是话说回来,面试的难度本来就比实际工作的难度大很多;其次,你来应聘是想拿到高薪的 Offer,而不是和面试官干仗来的,对吧?所以,搞明白这道题的答案才是我们关注的重点。

1.亿级用户在线场景分析

例如,QQ 在线状态的统计功能就是亿级的,它的特征是:数据量大、内存占用高、实时性要求高,因此我们使用常规的解决方案是不能实现的。例如,在数据库中给每个用户中添加一个在线状态,上线设为 1,下线设为 0,通过统计状态为 1 的数据,获取在线人数。该方案无法承受大规模用户频繁上、下线操作,会给数据库带来巨大 IO 压力,且实时统计需不断刷新查询,易拖垮数据库性能,因此不可取。

2.解决方案

此时,我们的统计实现可分为以下两类:

  1. 基于总数的统计方案:设置一个总在线人数,上线 +1、下线 -1,从而实现上线总人数的统计。
  1. 优点:实现简单、效率高、内存占用少。
  2. 缺点:不精准,没办法精确的查找某些用户某个时刻的在线状态;且在异常退出应用的情况下,后续基于在线监测机制的重复下线判断很难实现。
  1. 基于具体用户详情的统计方案:将用户的标识(如 QQ 号)和上线状态都存储在集合中。
  2. 优点:统计精准,可以查找某些用户某个时刻的在线状态;且在异常退出应用的情况下,后续基于在线监测机制可以精准的实现下线用户的去重功能。
  3. 缺点:内存占用大、效率较低。

3.具体实现

3.1 基于总数的统计方案

基于总数的统计,我们可以使用以下两种方式:

  1. 基于 Redis 的 incr(+1)和 decr(-1)操作实现,如下图所示:

图片图片

  1. 基于 Redis 的 HyperLogLog 实现,HyperLogLog (下文简称为 HLL) 是 Redis 2.8.9 版本添加的数据结构,它用于高性能的基数 (去重) 统计功能,它的缺点就是存在极低的误差率(0.81%)。它只需要 12KB 空间就能统计 2^64(约 18 亿)的数据。

图片图片

此实现方案不能移除元素、存在误差,但空间占用率非常低。

3.2 基于用户的统计实现

基于用户标识(QQ)我们可以采用 Redis 中提供的 Bitmap(位数组)来实现,位数组结构如下:

图片图片

其中每个下标就可以表示一个具体的数字,例如以上图片标识 1、3 数字存在,如果值为 0 表示不存在,这样的话 10 亿数字占用的位数组空间位 10 亿 bit,也就是 1000000000/1024/1024/1024/8=0.116 ****GB,可以看出它的空间占用量是非常小的。

用户上线时使用 SetBit 命令将对应位置设为 1 表示在线,下线时设为 0 。判断用户是否在线用 GetBit 命令,统计在线用户数用 BigCount 命令,具体操作命令如下图所示:

图片图片

在 Spring Boot 项目中,我们可以使用 RedisTemplate 实现用户的上、下线设置,以及在线个数统计,具体实现代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class BitmapService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 设置Bitmap中的位
     * @param key 键
     * @param offset 偏移量
     * @param value 值(0或1)
     */
    public void setBit(String key, long offset, boolean value) {
        redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 获取Bitmap中的位
     * @param key 键
     * @param offset 偏移量
     * @return 位的值(0或1)
     */
    public boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 计算Bitmap中值为1的位的数量
     * @param key 键
     * @return 值为1的位的数量
     */
    public Long bitCount(String key) {
        return redisTemplate.opsForValue().bitCount(key);
    }
}


责任编辑:武晓燕 来源: 磊哥和Java
相关推荐

2021-01-22 15:33:58

iOS微信iPhone

2009-05-06 09:19:53

Silverlight检测微软

2021-01-24 08:20:55

微信微信8.0.1移动应用

2021-05-24 08:58:34

Redis Bitmap 数据统计

2021-04-25 08:44:23

QQ腾讯在线状态

2021-06-08 08:51:50

Redis 数据类型数据统计

2025-01-08 07:00:00

MySQL数据库判重

2014-10-11 11:15:05

云平台容联云通讯

2017-10-24 10:15:05

CDN突发池系统架构

2024-08-30 17:14:34

2020-09-01 07:49:14

JVM流量系统

2023-12-07 07:46:21

MySQL写入点LSN

2020-11-10 09:05:45

用户画像苏宁

2019-08-22 15:42:03

2009-06-29 15:25:21

SessionJSP

2015-04-30 16:55:33

有道

2023-09-18 16:59:06

数据布隆过滤器

2021-08-04 17:55:38

keysRedis数据库

2021-01-18 06:53:31

QQ牛角图标聊天软件

2018-11-01 13:23:02

网关APIHTTP
点赞
收藏

51CTO技术栈公众号