Redis【实战篇】---- 用户签到

news2024/11/15 21:04:21

Redis【实战篇】---- 用户签到

  • 1. 用户签到 - BitMap功能演示
  • 2. 用户签到 - 实现签到功能
  • 3. 用户签到 - 签到统计
  • 4. 额外加餐 - 关于使用BitMap来解决缓存穿透的方案

1. 用户签到 - BitMap功能演示

我们针对签到功能完全可以通过mysql来完成,比如说以下这张表

在这里插入图片描述

用户一次签到,就是一条记录,假如有1000万用户,平均每人每年签到次数为10次,则这张表一年的数据量为 1亿条

每签到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共22 字节的内存,一个月则最多需要600多字节

我们如何能够简化一点呢?其实可以考虑小时候一个挺常见的方案,就是小时候,咱们准备一张小小的卡片,你只要签到就打上一个勾,我最后判断你是否签到,其实只需要到小卡片上看一看就知道了

我们可以采用类似这样的方案来实现我们的签到需求。

我们按月来统计用户签到信息,签到记录为1,未签到则记录为0.

把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。这样我们就用极小的空间,来实现了大量数据的表示

Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 2^32个bit位。

在这里插入图片描述

BitMap的操作命令有:

  • SETBIT:向指定位置(offset)存入一个0或1
  • GETBIT :获取指定位置(offset)的bit值
  • BITCOUNT :统计BitMap中值为1的bit位的数量
  • BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
  • BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
  • BITOP :将多个BitMap的结果做位运算(与 、或、异或)
  • BITPOS :查找bit数组中指定范围内第一个0或1出现的位置

2. 用户签到 - 实现签到功能

需求:实现签到接口,将当前用户当天签到信息保存到Redis中

思路:我们可以把年和月作为bitMap的key,然后保存到一个bitMap中,每次签到就到对应的位上把数字从0变成1,只要对应是1,就表明说明这一天已经签到了,反之则没有签到。

我们通过接口文档发现,此接口并没有传递任何的参数,没有参数怎么确实是哪一天签到呢?这个很容易,可以通过后台代码直接获取即可,然后到对应的地址上去修改bitMap。

在这里插入图片描述

代码

UserController

    @PostMapping("/sign")
    public Result sign() {
        return userService.sign();
    }

UserServiceImpl

    @Override
    public Result sign() {
        // 1. 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2. 获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3. 拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4. 获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5. 写入Redis SETBIT key offset 1
        stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
        return Result.ok();
    }

3. 用户签到 - 签到统计

问题1: 什么叫做连续签到天数?
从最后一次签到开始向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续签到天数。

在这里插入图片描述

Java逻辑代码:获得当前这个月的最后一次签到数据,定义一个计数器,然后不停的向前统计,直到获得第一个非0的数字即可,每得到一个非0的数字计数器+1,直到遍历完所有的数据,就可以获得当前月的签到总天数了

问题2: 如何得到本月到今天为止的所有签到数据?

BITFIELD key GET u[dayOfMonth] 0

假设今天是10号,那么我们就可以从当前月的第一天开始,获得到当前这一天的位数,是10号,那么就是10位,去拿这段时间的数据,就能拿到所有的数据了,那么这10天里边签到了多少次呢?统计有多少个1即可。

问题3:如何从后向前遍历每个bit位?

注意:bitMap返回的数据是10进制,哪假如说返回一个数字8,那么我哪儿知道到底哪些是0,哪些是1呢?我们只需要让得到的10进制数字和1做与运算就可以了,因为1只有遇见1 才是1,其他数字都是0 ,我们把签到结果和1进行与操作,每与一次,就把签到结果向右移动一位,依次内推,我们就能完成逐个遍历的效果了。

需求:实现下面接口,统计当前用户截止当前时间在本月的连续签到天数

有用户有时间我们就可以组织出对应的key,此时就能找到这个用户截止这天的所有签到记录,再根据这套算法,就能统计出来他连续签到的次数了

在这里插入图片描述

代码

UserController

    @GetMapping("/sign/count")
    public Result signCount() {
        return userService.signCount();
    }

UserServiceImpl

    @Override
    public Result signCount() {
        // 1. 获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2. 获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3. 拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4. 获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
        List<Long> result = stringRedisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (result == null || result.isEmpty()) {
            // 没有签到结果
            return Result.ok();
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return Result.ok(0);
        }
        // 6.循环遍历
        int count = 0;
        while (true) {
            // 6.1.让这个数字与1做与运算,得到数字的最后一个bit位  // 判断这个bit位是否为0
            if ((num & 1) == 0) {
                // 如果为0,说明未签到,结束
                break;
            }else {
                // 如果不为0,说明已签到,计数器+1
                count++;
            }
            // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
            num >>>= 1;
        }
        return Result.ok(count);
    }

4. 额外加餐 - 关于使用BitMap来解决缓存穿透的方案

回顾缓存穿透

发起了一个数据库不存在的,redis里边也不存在的数据,通常你可以把他看成一个攻击

解决方案:

  • 判断id<0

  • 如果数据库是空,那么就可以直接往redis里边把这个空数据缓存起来

第一种解决方案:遇到的问题是如果用户访问的是id不存在的数据,则此时就无法生效

第二种解决方案:遇到的问题是:如果是不同的id那就可以防止下次过来直击数据

所以我们如何解决呢?

我们可以将数据库的数据,所对应的id写入到一个list集合中,当用户过来访问的时候,我们直接去判断list中是否包含当前的要查询的数据,如果说用户要查询的id数据并不在list集合中,则直接返回,如果list中包含对应查询的id数据,则说明不是一次缓存穿透数据,则直接放行。

在这里插入图片描述

现在的问题是这个主键其实并没有那么短,而是很长的一个 主键

哪怕你单独去提取这个主键,但是在11年左右,淘宝的商品总量就已经超过10亿个

所以如果采用以上方案,这个list也会很大,所以我们可以使用bitmap来减少list的存储空间

我们可以把list数据抽象成一个非常大的bitmap,我们不再使用list,而是将db中的id数据利用哈希思想,比如:

id % bitmap.size = 算出当前这个id对应应该落在bitmap的哪个索引上,然后将这个值从0变成1,然后当用户来查询数据时,此时已经没有了list,让用户用他查询的id去用相同的哈希算法, 算出来当前这个id应当落在bitmap的哪一位,然后判断这一位是0,还是1,如果是0则表明这一位上的数据一定不存在, 采用这种方式来处理,需要重点考虑一个事情,就是误差率,所谓的误差率就是指当发生哈希冲突的时候,产生的误差。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/729045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.数据类型

1、课后作业 1.给同桌讲讲交换两个变量的值 算法&#xff08;不管他愿不愿听&#xff09; 2.依次咨询问并获取用户的姓名、年龄、性别&#xff0c;并打印用户信息 可以先自己写一下&#xff0c;在参考一下我的代码&#xff1a; <!DOCTYPE html> <html lang"en&q…

【SLAM14讲】05 李群与李代数

在 SLAM 中位姿是未知的&#xff0c;而我们需要解决什么样的相机位姿最符合当前观测数据这样的问题。 一种典型的方式是把它构建成一个优化问题&#xff0c;求解最优的 R, t&#xff0c;使得误差最小化。 旋转矩阵自身是带有约束的&#xff08;正交且行列式为 1&#xff09;。它…

Zhong__Linux系统磁盘空间扩容和转移

时间&#xff1a;2023.07.07 环境&#xff1a;Ubuntu/Centos 目的&#xff1a;分配闲置空间到指定分区/将分区空间转移到指定分区 说明&#xff1a; 作者&#xff1a;Zhong QQ交流群&#xff1a;121160124 欢迎加入&#xff01; 在安装Ubuntu/Centos/Stream等系统时 有时对…

测试3年经验不到,来面试开口要25K,面完连10K都不想给···

前言 近期公司发展的不错&#xff0c;打算扩招&#xff0c;也面试了不少人&#xff0c;由于公司不是很大所以公司大部分的人员都是我面试的。 前两天来了一个测试工作才3年不到的小伙儿面试&#xff0c;前面问了一点测试基础的东西&#xff0c;还是能答上来的&#xff0c;不过…

趟路:centos7.6安装opengauss5.0.0企业版

版本选取 # 下载opengauss安装介质&#xff0c;截止2023年7月份最新版本长期支持版&#xff08;LTS&#xff1a;Long Term Support&#xff09;是5.0.0版本&#xff1b;此外&#xff0c;还有预览版3.1.1&#xff1b;这里建议安装openGauss 5.0.0 (LTS)。企业版&#xff1a;更像…

Windows故障转移集群

Windows2012作为根域 两台Windows2008加入域 创建三台Windows 分别是Windows2012和两台Windows2008 并选择其环境 创建三个十g的卷&#xff0c;连接至Windows2012 清理三台主机的后台数据 然后修改三台主机的IP 本机IP要相对应 Windows2008-1主机IP 第二块网卡不需要填写网…

液滴接触角边界曲线识别—巧用Ovito

关注 M r . m a t e r i a l , \color{Violet} \rm Mr.material\ , Mr.material , 更 \color{red}{更} 更 多 \color{blue}{多} 多 精 \color{orange}{精} 精 彩 \color{green}{彩} 彩&#xff01; 主要专栏内容包括&#xff1a; †《LAMMPS小技巧》&#xff1a; ‾ \textbf…

Navicat 连接orcal11g

Navicat 连接orcal11g orcal12g 在不安装客户端的情况下是可以连接上的&#xff0c;orcal11g不行 1、下载客户端 官网传送门 传送门 csdn下载 传送门 2、配置客户端 解压下载的文件到D:\Program Files\PremiumSoft\Navicat Premium 16\instantclient_11_2 相同的可以备份原…

基于springboot+Mybatis+mysql+vue疗养中心管理系统

基于springbootMybatismysqlvue疗养中心管理系统 一、系统介绍二、功能展示1.登陆2.信息管理3.膳食管理4.护理管理5.床位管理6.后勤管理7.后勤管理 三、其它系统实现四.获取源码 一、系统介绍 系统主要功能&#xff1a; 信息管理模块&#xff1a;包括入住登记、退住登记、档案…

锐浪报表 Grid++Report 打印数据表图像

锐浪报表 GridReport 打印数据表时&#xff0c;特别是需要在Cell中打印图像时。例如&#xff1a; 二、图像的保存方式 1、图像以文件形式。保存在指定目录中。 2、数据表中&#xff0c;图像字段&#xff0c;仅保存图像的完整文件名&#xff08;指定目录&#xff09;。 3、打印表…

Windows Server 配置(七)VPN服务器的安装

VPN服务器的安装 VPN服务器是双网卡或多网卡的配置&#xff0c;一块网卡连接内网&#xff0c;另一块连接外网&#xff0c;同时外网或远程的客户端可以通过建立VPN连接访问到内网资源。 两块网卡分别设置好地址&#xff0c;外网网卡的地址是否能做的&#xff0c;或者是在路由器…

19 区域生长用于图像分割(matlab程序)

1.简述 区域生长法 区域生长的基本思想是将具有相似性质的像素集中起来构建成分割区域。以一组种子点开始&#xff0c;将与种子性质相似(如灰度级)的领域像素附加到生长区域的每个种子上 算法步骤 a.随机选取图像中的一个像素作为种子像素&#xff0c;并将其表示出来 b.检索种…

说一说spring boot服务的健康检测

一、判断服务的健康状态 服务健康与否&#xff0c;对我们的重要性&#xff0c;主要是体现在应用部署与服务调用。具体可以是如下&#xff1a; consul/nacos 服务注册中心api网关docker/k8s 容器部署发版结果应用监控 服务注册中心要对外提供服务&#xff0c;仅限于健康的节点…

天猫厨房大电市场分析(淘宝天猫数据)

如今&#xff0c;消费者对于厨房电器的需要不断增长&#xff0c;厨房电器领域的发展规模也越来越大。在国内市场中&#xff0c;由于中国人在烹饪时喜欢煎炒烹炸&#xff0c;油烟较重&#xff0c;因此&#xff0c;以油烟机和燃气灶为代表的厨房大电也成为千家万户不可少的厨用电…

2023黑马头条.微服务项目.跟学笔记(四)

2023黑马头条.微服务项目.跟学笔记 四 自媒体文章-自动审核今日内容介绍1.自媒体文章自动审核流程2.内容安全第三方接口2.1 概述2.2 准备工作2.3 文本内容审核接口2.4 图片审核接口2.5 项目集成 3.app端文章保存接口3.1 表结构说明3.2 分布式id3.3 思路分析3.4 feign接口 4.自媒…

常用的 34 个 Linux Shell 脚本,一定能帮到你!

作为一名 Linux 工程师&#xff0c;会写好的脚本不仅能提高工作效率&#xff0c;还能有更多的时间做自己的事。最近在网上冲浪的时候&#xff0c;也注意收集一些大佬写过的脚本&#xff0c;汇总整理一下&#xff0c;欢迎收藏&#xff0c;与君共勉&#xff01; &#xff08;1&a…

antd-React Popover 点击空白不隐藏

1.问题原因&#xff1a;自己写的点击事件把默认事件覆盖掉了&#xff0c;所以点击会不生效 2.解决方案&#xff1a;给按钮在添加一个焦点事件即可&#xff0c;当失去焦点的时候取反 3.代码如下 const [closeVisible, setCloseVisible] useState(false);<Popover content{c…

SSM学习笔记-------Spring(三)

SSM学习笔记-------Spring&#xff08;三&#xff09; Spring_day031、AOP简介1.1 什么是AOP?1.2 AOP作用1.3 AOP核心概念 2、AOP入门案例2.1 需求分析2.2 思路分析2.3 环境准备2.4 AOP实现步骤步骤1:添加依赖步骤2:定义接口与实现类步骤3:定义通知类和通知步骤4:定义切入点 步…

在任何文件夹下打开jupyter 内核都是同一个文件夹

我在 D:\anaconda实例代码 下打开jupyter notebook 无论我在那个文件下打开jupyter 都是同一个文件这是因为你可能在jupyter notebook 的那个配置文件中设置固定的路径 假如你的.py 文件在D:\anaconda实例代码 下 你想通过jupyter notebook 运行它很简单 上传到jupyter notebo…

Linux系统运行时参数命令(性能监控、测试)(2)虚拟内存和物理内存、内存性能监控、文件IO性能监控

目录 3. 内存性能监控3.1 内存是什么-虚拟内存和物理内存3.1.1 为什么需要有虚拟内存3.1.2 虚拟内存的原理 3.2 内存中的buffer和cache3.2.1 buff/cache3.2.2 **free 与 available** 4.文件IO性能监控4.1 IO的两种方式4.1.1 缓存IO4.1.2 直接IO 4.2 监控磁盘IO的命令4.2.1 iost…