当前位置: 首页 > article >正文

【黑马点评日记】:用户签到功能详解——从Bitmap入门到避坑指南

个人主页北极的代码欢迎来访作者简介java后端学习者❄️个人专栏苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言接着前面的内容我们继续对黑马点评项目进行完善。摘要本文详细介绍了黑马点评项目中用户签到功能的Redis位图实现方案。针对传统MySQL方案存在的数据量爆炸和内存浪费问题提出使用Redis位图存储签到状态仅需4字节/用户/月。文章重点剖析了签到功能实现中的五大关键点Key设计策略、偏移量索引、空指针处理、位运算逻辑和无符号右移操作并提供了完整的签到与统计连续签到天数的代码实现。通过对比MySQL和Redis方案的优缺点强调了架构设计中的权衡思维为高并发场景下的签到功能提供了高效解决方案。一、 踩坑点为什么签到功能不简单前面我们刚刚学完SpringBootMySQL可能会想“签到不就是往数据库插一条记录有什么难度。如果我们仅仅是这么想我们就没有考虑到实际情况所带来的性能陷阱。假设我们设计一张tb_sign表包含user_id,year,month,date字段。数据量爆炸如果黑马点评有1000万用户每人每月签到10次一年就是1亿条数据。内存浪费签到其实只有是/否两种状态用一条表记录约22字节来存一个是太浪费了。难点一如何在极其节省内存的前提下记录海量用户的签到状态二、 解决方案Redis Bitmap位图既然只有“是/否”两个状态为什么不用1个比特位来表示呢核心思想把一个月最多31天想象成一条由31个格子组成的纸条每天签到了就在格子里涂黑1没签到就留白0。空间计算一个用户一个月占用31 bit≈4字节。对比MySQL存一条记录要22字节Bitmap只要4字节。内存占用降低约80%。三、 Bitmap 核心命令速览在写代码前我们得先知道Redis中的这四个命令否则代码会看不懂SETBIT将某一位设为1。GETBIT获取某一位的值。BITFIELD一次获取一段长度的位图数据这是统计连续签到的关键。BITCOUNT统计有多少个1总签到天数。四、 业务实现签到与统计我们将分两步走点一下签到和看一眼统计连续天数。1. 功能一用户签到业务流程用户点击签到 - 后端获取当前用户ID和日期 - 计算偏移量 - 将Redis中的对应位设为1。代码详解与坑点java Override public Result sign() { // 1. 获取当前登录用户 Long userId UserHolder.getUser().getId(); // 2. 获取当前日期时间 LocalDateTime now LocalDateTime.now(); // 坑点1Key的设计策略 // 千万不能只用一个Key存所有用户也不能不分月份。 // 标准格式sign:用户ID:年月 // 好处方便按月清理/统计防止Key过大。 String keySuffix now.format(DateTimeFormatter.ofPattern(:yyyyMM)); String key USER_SIGN_KEY userId keySuffix; // 3. 获取今天是本月的第几天 // 注意1号是第1天但在位图中offset是从0开始的 int dayOfMonth now.getDayOfMonth(); // 坑点2偏移量的索引 // 如果今天是1号dayOfMonth1但位图第0位才代表1号。 // 所以必须 dayOfMonth - 1 int offset dayOfMonth - 1; // 4. 写入Redis // true 表示签到即设置为1false表示未签到0 stringRedisTemplate.opsForValue().setBit(key, offset, true); return Result.ok(); }2. 功能二统计连续签到天数这是最难的部分也是面试最爱问的。需求是计算当前用户截止今天连续签到了几天核心逻辑从今天最后一位往前数遇到第一个0就停。技术难点如何一次性拿到本月1号到今天的所有签到数据不能循环调用30次GETBIT那样效率太差。解决方案BITFIELD命令。命令BITFIELD key GET u[天数] 0解释u表示无符号整数。它会返回一个十进制数字这个数字的二进制表示就是我们这N天的签到情况。代码详解与避坑java Override public Result signCount() { Long userId UserHolder.getUser().getId(); LocalDateTime now LocalDateTime.now(); String key USER_SIGN_KEY userId now.format(DateTimeFormatter.ofPattern(:yyyyMM)); int dayOfMonth now.getDayOfMonth(); // 1. 获取本月的签到十进制数 // 这里指定了 unsigned(dayOfMonth)意思是取从0到dayOfMonth位的值 ListLong result stringRedisTemplate.opsForValue().bitField( key, BitFieldSubCommands.create() .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)) .valueAt(0) ); // 坑点3空指针与默认值 // 如果用户从来没签过到result可能为null或空集合此时直接返回0 if (result null || result.isEmpty()) { return Result.ok(0); } Long num result.get(0); if (num null || num 0) { return Result.ok(0); } // 2. 核心算法位运算计数 // 此时 num 是一个十进制数比如 3 (二进制 11) 代表前两天都签到了。 int count 0; while (true) { // 坑点4与运算逻辑 // (num 1) 是取出二进制最右边的一位 // 如果是 0说明这天没签到中断循环 if ((num 1) 0) { break; } else { count; } // 坑点5无符号右移 // 必须使用 而不是 // 因为 会在左边补符号位如果是负数补1导致死循环。 // 无论正负左边都补0保证数据正确归零。 num 1; } return Result.ok(count); }五、 深度避坑点总结月份变化Key中必须包含年月yyyyMM。如果不包含月份跨月时位图会乱套2月的28号和3月的1号会冲突。索引对齐Redis位图的offset是从0开始的而现实中日期是从1开始的。dayOfMonth - 1这步绝对不能省。右移运算符在统计连续天数时一定要用无符号右移。如果用有符号右移一旦数字是负数高位会一直补1导致num永远不等于0程序死循环。BITFIELD 的返回类型unsigned一定要用对。如果用有符号signed当最高位为1时数字会变成负数破坏后面的位运算逻辑。六、 追问问如果要统计年度或连续签到奖励比如连续7天送积分该怎么改造答连续7天检测可以在签到成功后或使用定时任务利用BITFIELD获取最近7天的数据组成二进制串。判断1111111十进制127是否等于该串。跨月连续这是难点。比如 9月30日 和 10月1日 连续。我的思路是当查询今天如10月1日连续签到时如果发现10月1日签到了但10月之前的位断了就递归/循环去查上个月的位图看上个月月底是否是签满的直到遇到未签到的那一天。七、 整体回顾通过这个签到功能我们希望帮助你建立一种意识架构设计就是做选择题。用MySQL逻辑简单但面对海量数据IO压力大资源浪费严重。用Redis Bitmap逻辑稍复杂需要位运算但内存占用极低一个用户一个月仅占4字节适合高并发场景。结语如果对你有帮助请点赞关注收藏你的支持就是我最大的鼓励

相关文章:

【黑马点评日记】:用户签到功能详解——从Bitmap入门到避坑指南

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或…...

gpt-image-2怎么用?一篇讲清楚最实用的使用方法

最近在(c.877ai.cn)库拉这类AI模型聚合平台上第一时间把GPT-Image-2的API接入跑通了,发布两周踩了不少坑。今天从架构原理、核心功能、API接入、实战技巧四个维度,全方位拆解GPT-Image-2的使用方法。无论你是前端开发者、设计师还…...

【LeetCode刷题日记】一口气搞定三道层序遍历!从N叉树到二叉树,BFS核心思想一网打尽

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或…...

Lazytainer:基于模糊匹配的Docker容器智能管理工具实战

1. 项目概述:一个为容器化工作流“减负”的智能工具如果你和我一样,日常工作中需要频繁地与Docker容器打交道,那么你一定对下面这些场景深有感触:为了调试一个服务,你得先docker ps找到容器ID,再docker exe…...

视觉触觉融合的机器人可变形物体追踪技术

1. 视觉触觉模仿学习在可变形物体追踪中的技术解析在机器人操作领域,可变形物体(如电缆、布料等)的追踪一直是个棘手问题。这类物体具有近乎无限的自由度,传统方法往往需要精确建模物体动力学特性,难以适应不同几何形状…...

从Airflow到Flyte:新一代云原生MLOps编排平台的核心优势与实践

1. 从Airflow到Flyte:为什么我们需要新一代的MLOps编排器?如果你在数据科学或机器学习工程领域摸爬滚打超过三年,大概率用过或者至少听说过Airflow。它几乎是过去十年里任务编排领域的代名词,用Python写DAG,用Celery做…...

GPIO端口扩展器在翻盖手机中的设计与应用

1. GPIO端口扩展器在翻盖手机中的核心价值翻盖手机的设计一直面临着空间和成本的严格限制。作为硬件工程师,我们经常需要在有限的主板面积上实现尽可能多的功能。GPIO端口扩展器正是解决这一矛盾的利器。通过IC或SPI接口,单个GPIO扩展器可以提供8-16个额…...

HTML函数工具是否支持雷蛇等游戏外设_RGB同步汇总【汇总】

HTML无法直接控制雷蛇等外设RGB灯光,需通过Razer Chroma SDK Web API、WebSocket本地代理或Electron封装调用原生模块实现;其他品牌如罗技、海盗船、华硕亦需各自SDK与手动启用API权限。如果您希望在网页开发中通过HTML函数工具实现雷蛇等游戏外设的RGB灯…...

AdamW与Muon优化器在FFN中的谱崩溃对比研究

1. 项目背景与问题定义在深度神经网络训练过程中,优化器的选择直接影响模型收敛速度和最终性能。AdamW和Muon作为两种主流的自适应优化算法,在各类神经网络结构中表现出不同的特性。本项目聚焦于它们在Feed-Forward Network(FFN)层…...

SenCache:扩散模型推理加速技术解析

1. 项目概述SenCache是一种针对扩散模型(Diffusion Models)的推理加速技术,其核心思想是通过分析模型对不同输入区域的敏感性差异,实现计算资源的动态分配。这项技术特别适合需要实时生成高质量图像的场景,比如游戏内容…...

Gemini CLI扩展开发:构建标准化AI工作流提升开发效率

1. 项目概述:一个为Gemini CLI深度定制的命令集 如果你和我一样,日常开发工作重度依赖命令行,并且最近开始尝试用Gemini CLI来提升效率,那你可能已经发现了一个痛点:原生的 gemini 命令虽然强大,但面对一…...

OpenClaw VS Code扩展:AI辅助编码与安全审计的深度集成实践

1. 项目概述:OpenClaw VS Code 扩展如果你和我一样,每天大部分时间都泡在 VS Code 里,同时又在探索如何让 AI 更深度地融入开发工作流,那么 OpenClaw 这个 VS Code 扩展绝对值得你花时间研究。它不是一个简单的聊天机器人插件&…...

ClawSwap SDK:一站式DEX聚合器集成方案与实战指南

1. 项目概述:一个为去中心化交易聚合而生的SDK最近在开发一个需要深度集成去中心化交易(DEX)功能的项目,我花了不少时间研究市面上的各种工具。在这个过程中,我发现了WarTech9/clawswap-sdk这个仓库。简单来说&#xf…...

Python 正则表达式实战:从入门到精通

Python 正则表达式实战:从入门到精通 引言 大家好,我是一名正在从Rust转向Python的后端开发者。在日常开发中,字符串处理是必不可少的环节,而正则表达式就是处理字符串的一把利器。作为从Rust过来的开发者,我发现Pyt…...

GameVault Inspector:开源游戏库元数据自动化同步工具实战指南

1. 项目概述与核心价值最近在折腾游戏库管理的时候,发现了一个挺有意思的开源项目,叫game-vault-inspector。乍一看名字,你可能会觉得它是个游戏“金库”的检查工具,实际上,它瞄准的是一个更具体、更“硬核”的痛点&am…...

基于模块化设计的AI聊天机器人框架:从核心原理到生产部署

1. 项目概述:一个开箱即用的AI聊天机器人框架最近在GitHub上闲逛,发现了一个叫marcusschiesser/ai-chatbot的项目,点进去一看,好家伙,又是一个AI聊天机器人。这年头,基于大语言模型(LLM&#xf…...

Rust FFI与C交互:跨语言编程实践

Rust FFI与C交互:跨语言编程实践 引言 大家好,我是一名正在从Rust转向Python的后端开发者。在实际项目中,我们经常需要与其他语言进行交互,特别是C语言。Rust提供了强大的FFI(Foreign Function Interface&#xff09…...

轻量级SFT框架SWE-Lego:高效解决软件工程任务

1. 项目背景与核心价值去年在参与一个大型企业级代码审查系统开发时,我们团队遇到了一个典型困境:传统的监督微调(SFT)方法在解决复杂软件工程问题时,要么需要庞大的计算资源,要么难以保持专业领域的准确性。正是这次经历让我开始…...

LLSA:高效稀疏注意力机制在长序列处理中的应用

1. 从密集到稀疏:注意力机制的计算效率革命在自然语言处理和计算机视觉领域,注意力机制已经成为现代深度学习架构的核心组件。传统注意力机制(如Transformer中的自注意力)虽然功能强大,但其计算复杂度随着序列长度呈二…...

QClaw自动化脚本:一键集成Crazyrouter路由与GPT-5.4模型

1. 项目概述:一键切换QClaw路由的自动化脚本如果你正在使用QClaw,并且对内置的qclaw/modelroute路由方案感到性能或稳定性上有所不足,想要尝试更灵活、功能更强大的第三方路由服务,那么你很可能已经听说过crazyrouter.com。这是一…...

LLSA稀疏注意力机制:从原理到工程实践

1. 从密集到稀疏:注意力机制的效率革命在自然语言处理领域,注意力机制早已成为Transformer架构的核心组件。但传统自注意力机制那O(n)的复杂度,就像一场永远无法避免的交通拥堵——随着序列长度增加,计算资源消耗呈平方级增长。三…...

Echo-Server:HTTP请求调试与API模拟的轻量级Docker工具

1. 项目概述:一个为开发者而生的“回音壁”服务器在开发和运维的日常工作中,我们经常需要一个简单、可控的服务器来模拟后端行为,用于测试、调试或演示。无论是验证客户端的网络请求是否正常发送,还是模拟一个API接口返回特定的状…...

可训练对数线性稀疏注意力机制:原理与工程实践

1. 项目背景与核心价值在深度学习领域,注意力机制已经成为Transformer架构的核心组件。然而传统注意力机制的计算复杂度随着序列长度呈平方级增长,这严重限制了模型处理长序列的能力。我们团队开发的"可训练对数线性稀疏注意力机制"正是为了解…...

构建AI智能体长期记忆系统:向量检索与分层存储实战

1. 项目概述:一个为AI智能体打造的“记忆宫殿”如果你最近在折腾AI智能体,比如用Cursor、Claude或者GPT-4的API来构建一些自动化工作流,那你大概率会遇到一个头疼的问题:上下文遗忘。智能体就像一个记忆力只有几页纸的“金鱼”&am…...

别再乱用vector的insert和erase了!C++ STL迭代器失效的坑我帮你踩完了(附VS2022调试实录)

从崩溃现场到完美避坑:VS2022调试实战揭秘vector迭代器失效的真相 第一次在循环中调用v.erase(it)导致程序崩溃时,我盯着调试器里那个0xDDDDDDDD的地址值发呆了十分钟。作为从C转战C的开发者,这种内存错误似曾相识却又截然不同——它背后隐藏…...

告别VMWare!用VirtualBox 7.0.6给CentOS 7.6装个桌面,保姆级避坑指南

告别VMWare!用VirtualBox 7.0.6打造高效CentOS 7.6桌面环境全攻略 在开源工具日益成熟的今天,VirtualBox作为一款轻量级、跨平台的虚拟机解决方案,已经成为开发者搭建测试环境的首选。特别是对于需要频繁创建、销毁实验环境的Linux学习者而言…...

从小学数学竖式到FPGA硬件:图解4位乘法器是如何‘搭’出来的

从小学数学竖式到FPGA硬件:图解4位乘法器是如何‘搭’出来的 记得小学三年级第一次接触乘法竖式时,老师用粉笔在黑板上画出的那些错位相加的格子吗?当时我们或许不会想到,这些看似简单的计算步骤,竟与当今最先进的芯片…...

用AT32F437的QSPI给项目扩容:手把手实现W25N01G NAND Flash的文件系统移植(FatFs)

基于AT32F437的QSPI扩展存储实战:从NAND Flash驱动到FatFs文件系统全解析 在嵌入式系统开发中,存储扩展常常是提升产品竞争力的关键。AT32F437系列微控制器凭借其高性能QSPI接口,为开发者提供了连接大容量NAND Flash的便捷途径。本文将深入探…...

Arm Neoverse V3AE核心架构与电源管理技术解析

1. Arm Neoverse V3AE核心架构概述Arm Neoverse V3AE是基于Armv9.2-A架构设计的高性能处理器核心,主要面向数据中心和云计算工作负载优化。作为Arm Neoverse产品线的最新成员,V3AE在保持高性能计算能力的同时,通过创新的电源管理技术实现了显…...

LVGL界面布局避坑指南:为什么你的lv_obj_align_to总对不齐?

LVGL界面布局避坑指南:为什么你的lv_obj_align_to总对不齐? 在嵌入式GUI开发中,LVGL凭借其轻量级和跨平台特性成为许多开发者的首选。然而,当新手尝试构建复杂界面时,往往会遇到一个令人抓狂的问题——明明调用了对齐函…...