Redis当中的BitMap,实现github打卡功能

本文介绍了Redis中的BitMap数据结构以及其提供的SETBIT、GETBIT、BITCOUNT和BITOP命令的应用。并通过实例展示了如何利用这些命令实现GitHub打卡功能,通过二进制位数组记录用户每日提交次数。

写在开头

本文参考原文

BitMap

Redis中的BitMap同我们所使用的算法当中的bitmap是相同的道理。简单来说就是使用二进制位,每一位的0/1状态表示信息。
二进制表示的优势:
大大节省空间。如两位二进制位00、01、10、11可以表示四种状态

Redis当中提供了bitmap的数据结构,并有相关的操作命令。通过这命令的搭配,可以实现不同的逻辑应用。

Redis中BitMap命令

Redis提供了SETBIT、GETBIT、BITCOUNT、BITOP四个常用命令用于处理二进制位数组。

SETBIT:为位数组指定偏移量上的二进制位设置值,偏移量从0开始计数,二进制位的值只能为0或1。返回原位置值。 GETBIT:获取指定偏移量上二进制位的值。 BITCOUNT:统计位数组中值为1的二进制位数量。 BITOP:对多个位数组进行按位与、或、异或、取反运算(对应具体的操作为:ADD、OR、XOR,还有NOT)。

具体例子如下:


127.0.0.1:6379> SETBIT first 0 1    # 0000 0001
(integer) 0
127.0.0.1:6379> SETBIT first 3 1    # 0000 1001
(integer) 0
127.0.0.1:6379> SETBIT first 0 0    # 0000 1000
(integer) 1
 
127.0.0.1:6379> GETBIT first 0
(integer) 0
127.0.0.1:6379> GETBIT first 3
(integer) 1
 
127.0.0.1:6379> BITCOUNT first      # 0000 1000
(integer) 1
127.0.0.1:6379> SETBIT first 0 1    # 0000 1001
(integer) 0
127.0.0.1:6379> BITCOUNT first      # 0000 1001
(integer) 2
127.0.0.1:6379> SETBIT first 1 1    # 0000 1011
(integer) 0
127.0.0.1:6379> BITCOUNT first      # 0000 1011
(integer) 3
 
127.0.0.1:6379> SETBIT x 3 1        
(integer) 0
127.0.0.1:6379> SETBIT x 1 1        
(integer) 0
127.0.0.1:6379> SETBIT x 0 1        # 0000 1011
(integer) 0
127.0.0.1:6379> SETBIT y 2 1        
(integer) 0
127.0.0.1:6379> SETBIT y 1 1        # 0000 0110
(integer) 0
127.0.0.1:6379> SETBIT z 2 1        
(integer) 0
127.0.0.1:6379> SETBIT z 0 1        # 0000 0101
(integer) 0
 
127.0.0.1:6379> BITOP AND andRes x y z    #0000 0000
(integer) 1
127.0.0.1:6379> BITOP OR orRes x y z      #0000 1111
(integer) 1
127.0.0.1:6379> BITOP XOR x y z           #0000 1000
(integer) 1
 
# 对给定的位数组进行按位取反
127.0.0.1:6379> SETBIT value 0 1
(integer) 0
127.0.0.1:6379> SETBIT value 3 1            #0000 1001
(integer) 0
127.0.0.1:6379> BITOP NOT notValue value    #1111 0110
(integer) 1

具体每种命令的详解可见参考原文。

实现github打卡功能

功能分析:

GitHub当中的打卡签到为一年,每天提交的次数决定了当前天签到颜色的深浅。
向bitmap的结构中思考。
1、我们对于每个用户,都有一个打卡表,所以可以将用户的唯一标识作为bitmap的key,当前是哪一天作为bitmap的offset,提交次数作为bit。
2、按照以上思考的大概是没问题的,但是我们的提交次数肯定不能只限定在0次或1次。
3、所以解决此问题的方法,就是我们多使用几位bit来表示提交次数,并且,因为多使用了几位bit,需要我们在设置offset也就是日期,做相应的逻辑转换。

整体的逻辑为:set userID Date times
使用一个字节(8bit)表示提交次数,所以offset(date)每次移动8位
如:0000 0011 0000 1011,后八位表示一天(如11月26日),提交次数为11次,前八位表示相邻一天(11月27日)提交次数3次

代码实现:

生成模拟数据

public void genTestData() {
    if(redisUtils.isExist(CommonConstant.KEY)){
        return;
    }
    // 获取当前年的总天数
    int days = getDays();
    for (int i = 0; i < days; i++) {
        int random = ThreadLocalRandom.current().nextInt(64);
        // 生成随机数表示每天的PR次数
        String binaryString = Integer.toBinaryString(random);
        if (binaryString.length() < 8) {
            // 填充0
            if(binaryString.length() == 0){binaryString = "00000000";}
            else if(binaryString.length() == 1){binaryString = "0000000"+binaryString;}
            else if(binaryString.length() == 2){binaryString = "000000"+binaryString;}
            else if(binaryString.length() == 3){binaryString = "00000"+binaryString;}
            else if(binaryString.length() == 4){binaryString = "0000"+binaryString;}
            else if(binaryString.length() == 5){binaryString = "000"+binaryString;}
            else if(binaryString.length() == 6){binaryString = "00"+binaryString;}
            else if(binaryString.length() == 7){binaryString = "0"+binaryString;}
        }
        char[] chars = binaryString.toCharArray();
        for (int j = 0; j < chars.length; j++) {
            // 设置BitMap
            redisUtils.setBit(CommonConstant.KEY,i*8+j,chars[j]);
        }
    }
}
 
/**
 * 获取当前年的总天数
 * @return days 总天数
 */
private int getDays(){
    Calendar calOne = Calendar.getInstance();
    int year = calOne.get(Calendar.YEAR);
    System.out.println(year);
    Calendar calTwo = new GregorianCalendar(year, 11, 31);
    return calTwo.get(Calendar.DAY_OF_YEAR);
}

获取Redis bitmap中的数据

public List<String> getPushData() {
    List<String> res = new ArrayList<>(366);
    // 没有数据就先造数据
    genTestData();
    int days = getDays();
    for(long i=0;i<days;i++){
        StringBuilder sb = new StringBuilder();
        for (int j = 0; j < 8; j++) {
            String bit = redisUtils.getBit(CommonConstant.KEY, i * 8 + j);
            sb.append(bit);
        }
        // 直接返回二进制串,前端转换为十进制
        res.add(sb.toString());
    }
    return res;
}

前端展示

<script type="text/javascript">
    var chartDom = document.getElementById('main');
    var myChart = echarts.init(chartDom);
    var option;
 
    function getVirtulData(year) {
        var date = +echarts.number.parseDate(year + '-01-01');
        var end = +echarts.number.parseDate(+year + 1 + '-01-01');
        var dayTime = 3600 * 24 * 1000;
        var data = [];
        $.ajax({
            "url":'http://localhost:8080/test/getPushData',
            "async":false, // ajax同步获取
            success:function (res){
                for (let time = date,k=0; time < end && k < res.data.length; time += dayTime,k++) {
                    data.push([
                        echarts.format.formatTime('yyyy-MM-dd', time),
                        parseInt(res.data[k],2)//客户端完成进制转换,不放在服务端完成
                    ]);
                }
            }
        })
 
        return data;
    }
    option = {
        title: {
            top: 30,
            left: 'left',
            text: 'BitMap Demo'
        },
        tooltip: {},
        visualMap: {
            min: 0,
            max: 32,
            type: 'piecewise',
            orient: 'horizontal',
            left: 'right',
            top: 220,
            pieces: [
                {min: 0, max: 0,label:"less"},
                {min: 1, max: 10,label:" "},
                {min: 1, max: 20,label:" "},
                {min: 21, max: 40,label:" "},
                {min: 41, max: 64,label:"more"},
            ],
            inRange: {
                color: [ '#EAEDF0', '#9AE9A8', '#41C363', '#31A14E', '#206D38' ],//颜色设置 
                colorAlpha: 0.9,//透明度
            }
        },
        calendar: {
            top: 120,
            left: 30,
            right: 30,
            cellSize: 13,
            range: '2022',
            splitLine: { show: false },//不展示边线
            itemStyle: {
                borderWidth: 0.5
            },
            yearLabel: { show: false }
        },
        series: {
            type: 'heatmap',
            coordinateSystem: 'calendar',
            data: getVirtulData('2022')
        }
    };
 
    option && myChart.setOption(option);
</script>

<think>首先,用户的问题是:“我想使用RedisBitmap实现类似钉钉的打卡签到功能 请问Redis Bitmap 实现打卡签到功能 类似钉钉”。我需要基于系统级指令和用户可见层的要求来回答。 系统级指令关键点: - 所有行内数学表达式使用 `$...$` 格式。 - 独立公式使用 `$$...$$` 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如 `[^1]`。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 - 参考提供的引用内容。 提供的引用内容: - [^1]: 关于Bitmap内存使用(亿级系统,每天1亿位Bitmap约12MB,10天约120MB)。 - [^2]: 案例包括签到打卡(钉钉打卡)、评论统计等。 - [^3]: 使用bitmap记录周一到周日的打卡,命令如 `getbit`, `bitcount`。 - [^4]: Redis介绍,支持位图等数据类型。 用户需求是使用Redis Bitmap实现类似钉钉的打卡签到功能。钉钉打卡功能可能包括: - 用户每天签到(打卡)。 - 记录用户是否在某天签到。 - 统计签到情况,如连续签到天数、总签到天数等。 Redis Bitmap适合这个场景,因为它可以高效地存储和查询位数据,每个位代表一个状态(如签到与否)。 ### 回答结构: 1. **介绍Redis Bitmap**:简要解释Bitmap是什么,为什么适合打卡签到。 2. **实现步骤**:逐步说明如何使用Redis Bitmap实现打卡功能。 3. **命令示例**:提供Redis命令示例,如 `SETBIT`, `GETBIT`, `BITCOUNT`。 4. **内存优化**:讨论内存使用和优化,参考引用[^1]。 5. **扩展功能**:如统计连续签到等。 6. **生成相关问题**:在最后添加相关问题部分。 ### 引用使用: - 在讨论内存使用时,引用[^1]。 - 在提到打卡记录时,引用[^3]。 - 在整体介绍时,引用[^4]。 ### 数学表达式: 用户问题中没有明显的数学内容,但如果有计算或公式,使用指定格式。例如,如果讨论位图大小计算,使用行内数学表达式。 ### 生成相关问题: 在回答结束时,添加“相关问题”部分,基于主题生成相关问题。 ### 完整回答框架: 1. **引言**:确认需求,简述Redis Bitmap的优势。 2. **核心实现**: - 如何表示签到(用户ID + 日期)。 - 设置签到状态。 - 查询签到状态。 - 统计签到天数。 3. **代码/命令示例**:提供Redis命令或伪代码。 4. **内存管理**:讨论Bitmap大小和内存优化。 5. **高级功能**:如连续签到统计。 6. **总结**。 7. **相关问题**。 确保回答清晰、逐步指导。</think>### Redis Bitmap 实现钉钉式打卡签到功能 我将逐步解释如何使用 Redis Bitmap 实现类似钉钉的打卡签到功能Redis Bitmap 是一种高效的数据结构,它通过位(bit)来存储状态(例如,0 表示未签到,1 表示已签到),特别适合处理海量用户的每日签到记录,因为它占用内存小、查询速度快。下面基于引用内容,结合 Redis 特性,详细说明实现步骤。 --- #### 1. **Redis Bitmap 基础概念** - BitmapRedis 中以字符串形式存储,每个位(bit)代表一个状态。 - 每个用户对应一个 Bitmap 键(key),键名通常设计为 `user:sign:<user_id>`(例如,`user:sign:1001` 表示用户 1001 的签到记录)。 - 日期映射到位偏移量(offset):例如,将日期转换为一年中的第几天(1-365),作为位偏移量。2023 年 1 月 1 日对应偏移量 0,1 月 2 日对应偏移量 1,依此类推。 - 优势: - **内存效率高**:Bitmap 每个位仅占 1 bit。例如,存储 1 亿用户的每日签到记录,每天只需约 12MB 内存(计算:$10^8 \text{ bits} / 8 / 1024 / 1024 \approx 12\text{MB}$)。10 天记录约 120MB,适合高并发场景[^1]。 - **操作快速**:支持 O(1) 复杂度的位设置和查询。 --- #### 2. **实现打卡签到功能的步骤** 以下是核心功能实现方法,使用 Redis 命令操作 Bitmap。 ##### 步骤 1: 用户签到 - 当用户打卡时,设置对应日期的位为 1。 - 使用 `SETBIT` 命令:`SETBIT key offset 1`。 - `key`: 用户签到键,如 `user:sign:1001`。 - `offset`: 日期偏移量。例如,2023 年 10 月 1 日是一年中的第 274 天(假设非闰年),则 offset = 273(从 0 开始计数)。 - **示例命令**: ```bash # 用户 1001 在 2023-10-01 签到(offset = 273) SETBIT user:sign:1001 273 1 ``` ##### 步骤 2: 查询签到状态 - 检查用户某天是否签到,使用 `GETBIT` 命令:`GETBIT key offset`。 - 返回 1 表示已签到,0 表示未签到。 - **示例命令**: ```bash # 查询用户 1001 在 2023-10-01 的签到状态 GETBIT user:sign:1001 273 # 返回 (integer) 1 表示已签到 ``` 类似钉钉的“查看某天是否打卡功能,可直接集成到 App 中[^3]。 ##### 步骤 3: 统计签到天数 - 计算用户总签到天数,使用 `BITCOUNT` 命令:`BITCOUNT key`。 - 这会统计 Bitmap 中值为 1 的位数量。 - **示例命令**: ```bash # 统计用户 1001 的总签到天数 BITCOUNT user:sign:1001 # 返回 (integer) 3 表示已签到 3 天 ``` 适用于生成月度报告或奖励连续签到用户[^3]。 ##### 步骤 4: 高级统计(如连续签到) - Redis 原生不支持直接查询连续签到,但可通过组合命令实现: - 使用 `BITFIELD` 命令批量获取多日状态。 - 在应用层代码(如 Python)中解析连续天数。 - **伪代码示例**(Python): ```python import redis r = redis.Redis() user_id = "1001" key = f"user:sign:{user_id}" # 获取最近 7 天的签到状态(offset 从 270 到 276) bits = r.execute_command('BITFIELD', key, 'GET', 'u7', 270) # 解析位状态,计算连续签到天数 status_list = [int(bit) for bit in bin(bits[0])[2:].zfill(7)] # 转换为二进制列表 streak = 0 for status in reversed(status_list): # 从最近一天开始检查 if status == 1: streak += 1 else: break print(f"连续签到天数: {streak}") ``` --- #### 3. **内存管理与优化** - **设置过期时间**:避免无限期存储旧数据。使用 `EXPIRE` 命令设置键的过期时间(例如,1 年后自动删除): ```bash EXPIRE user:sign:1001 31536000 # 365 天的秒数 ``` 这能节省内存,尤其在亿级用户系统中,10 天数据仅约 120MB,过期后 Redis 自动清理[^1]。 - **键设计优化**:如果用户量巨大,采用分片键(如 `user:sign:<user_id>:<year>`),减少单个 Bitmap 的大小。 - **监控内存**:使用 Redis 命令 `INFO MEMORY` 跟踪内存使用,确保系统稳定。 --- #### 4. **完整应用场景示例** 假设实现类似钉钉的打卡系统: - **签到流程**:用户点击 App 签到按钮 → 调用 `SETBIT` 记录。 - **统计报表**:后台定时运行 `BITCOUNT` 生成每日活跃用户(DAU)报告。 - **通知提醒**:基于连续签到天数(通过上述伪代码计算),发送奖励通知。 - **扩展功能**:结合 HyperLogLog 统计 UV(独立访客),或 GEO 存储位置信息,确保签到真实性[^2][^4]。 Redis Bitmap 简单高效,但需注意:它适合二进制状态记录(是/否签到),复杂逻辑(如补签)需额外处理。 --- #### 5. **总结** 使用 Redis Bitmap 实现打卡签到功能,核心步骤包括: 1. 用 `SETBIT` 记录签到。 2. 用 `GETBIT` 查询状态。 3. 用 `BITCOUNT` 统计总天数。 4. 优化内存通过设置过期时间和分片键。 这方案内存占用低(亿级用户每天约 12MB),响应快,适合高并发系统如钉钉[^1][^3]。Redis 作为开源内存数据库,支持丰富数据类型,是此类场景的理想选择[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BiuPsYao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值