zoukankan      html  css  js  c++  java
  • 【SpringBoot DB 系列】Redis 高级特性之 Bitmap 使用姿势及应用场景介绍

    【SpringBoot DB 系列】Redis 高级特性之 Bitmap 使用姿势及应用场景介绍

    前面介绍过 redis 的五种基本数据结构,如 String,List, Set, ZSet, Hash,这些属于相对常见了;在这些基本结果之上,redis 还提供了一些更高级的功能,如 geo, bitmap, hyperloglog,pub/sub,本文将主要介绍 Bitmap 的使用姿势以及其适用场景,主要知识点包括

    • bitmap 基本使用
    • 日活统计应用场景中 bitmap 使用姿势
    • 点赞去重应用场景中 bitmap 使用姿势
    • 布隆过滤器 bloomfilter 基本原理及体验 case

    I. 基本使用

    1. 配置

    我们使用 SpringBoot 2.2.1.RELEASE来搭建项目环境,直接在pom.xml中添加 redis 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    

    如果我们的 redis 是默认配置,则可以不额外添加任何配置;也可以直接在application.yml配置中,如下

    spring:
      redis:
        host: 127.0.0.1
        port: 6379
        password:
    

    2. 使用姿势

    bitmap 主要就三个操作命令,setbitgetbit以及 bitcount

    a. 设置标记

    setbit,主要是指将某个索引,设置为 1(设置 0 表示抹去标记),基本语法如下

    # 请注意这个index必须是数字,后面的value必须是0/1
    setbit key index 0/1
    

    对应的 SpringBoot 中,借助 RestTemplate 可以比较容易的实现,通常有两种写法,都可以

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 设置标记位
     *
     * @param key
     * @param offset
     * @param tag
     * @return
     */
    public Boolean mark(String key, long offset, boolean tag) {
        return redisTemplate.opsForValue().setBit(key, offset, tag);
    }
    
    public Boolean mark2(String key, long offset, boolean tag) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setBit(key.getBytes(), offset, tag);
            }
        });
    }
    

    上面两种写法的核心区别,就是 key 的序列化问题,第一种写法使用默认的 jdk 字符串序列化,和后面的getBytes()会有一些区别,关于这个,有兴趣的小伙伴可以看一下我之前的博文: RedisTemplate 配置与使用#序列化问题

    b. 判断存在与否

    getbit key index,如果返回 1,表示存在否则不存在

    /**
     * 判断是否标记过
     *
     * @param key
     * @param offest
     * @return
     */
    public Boolean container(String key, long offest) {
        return redisTemplate.opsForValue().getBit(key, offest);
    }
    

    c. 计数

    bitcount key,统计和

    /**
     * 统计计数
     *
     * @param key
     * @return
     */
    public long bitCount(String key) {
        return redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.bitCount(key.getBytes());
            }
        });
    }
    

    3. 应用场景

    前面的基本使用比较简单,在介绍 String 数据结构的时候也提过,我们重点需要关注的是 bitmap 的使用场景,它可以干嘛用,什么场景下使用它会有显著的优势

    • 日活统计
    • 点赞
    • bloomfilter

    上面三个场景虽有相似之处,但实际的应用场景还是些许区别,接下来我们逐一进行说明

    a. 日活统计

    统计应用或网站的日活,这个属于比较常见的 case 了,如果是用 redis 来做这个事情,首先我们最容易想到的是 Hash 结构,一般逻辑如下

    • 根据日期,设置 key,如今天为 2020/10/13, 那么 key 可以为 app_20_10_13
    • 其次当用户访问时,设置 field 为 userId, value 设置为 true
    • 判断日活则是统计 map 的个数hlen app_20_10_13

    上面这个逻辑有毛病么?当然没有问题,但是想一想,当我们的应用做的很 nb 的时候,每天的日活都是百万,千万级时,这个内存开销就有点吓人了

    接下来我们看一下 bitmap 可以怎么做

    • 同样根据日期设置 key
    • 当用户访问时,index 设置为 userId,setbit app_20_10_13 uesrId 1
    • 日活统计 bitcount app_20_10_13

    简单对比一下上面两种方案

    当数据量小时,且 userid 分布不均匀,小的为个位数,大的几千万,上亿这种,使用 bitmap 就有点亏了,因为 userId 作为 index,那么 bitmap 的长度就需要能容纳最大的 userId,但是实际日活又很小,说明 bitmap 中间有大量的空白数据

    反之当数据量很大时,比如百万/千万,userId 是连续递增的场景下,bitmap 的优势有两点:1.存储开销小, 2.统计总数快

    c. 点赞

    点赞的业务,最主要的一点是一个用户点赞过之后,就不能继续点赞了(当然某些业务场景除外),所以我们需要知道是否可以继续点赞

    上面这个 hash 当然也可以实现,我们这里则主要讨论一下 bitmap 的实现逻辑

    • 比如我们希望对一个文章进行点赞统计,那么我们根据文章 articleId 来生成 redisKey=like_1121,将 userId 作为 index
    • 首先是通过getbit like_1121 userId 来判断是否点赞过,从而限制用户是否可以操作

    Hash 以及 bitmap 的选择和上面的考量范围差不多

    d. 布隆过滤器 bloomfilter

    布隆过滤器可谓是大名鼎鼎了,我们这里简单的介绍一下这东西是啥玩意

    • 底层存储为一个 bitmap
    • 当来一个数据时,经过 n 个 hash 函数,得到 n 个数值
    • 将 hash 得到的 n 个数值,映射到 bitmap,标记对应的位置为 1

    如果来一个数据,通过 hash 计算之后,若这个 n 个值,对应的 bitmap 都是 1,那么表示这个数据可能存在;如果有一个不为 1,则表示这个数据一定不存在

    请注意:不存在时,是一定不存在;存在时,则不一定

    从上面的描述也知道,bloomfilter 的底层数据结构就是 bitmap,当然它的关键点在 hash 算法;根据它未命中时一定不存在的特性,非常适用于缓存击穿的问题解决

    体验说明

    Redis 的布隆过滤器主要针对>=4.0,通过插件的形式提供,项目源码地址为: https://github.com/RedisBloom/RedisBloom,下面根据 readme 的说明,简单的体验一下 redis 中 bloomfilter 的使用姿势

    # docker 方式安装
    docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
    
    # 通过redis-cli方式访问
    docker exec -it redis-redisbloom bash
    
    # 开始使用
    # redis-cli
    127.0.0.1:6379> keys *
    (empty array)
    127.0.0.1:6379> bf.add newFilter hello
    (integer) 1
    127.0.0.1:6379> bf.exists newFilter hello
    (integer) 1
    127.0.0.1:6379> bf.exists newFilter hell
    (integer) 0
    

    bloomfilter 的使用比较简单,主要是两个命令bf.add添加元素,bf.exists判断是否存在,请注意它没有删除哦

    4. 小结

    bitmap 位图属于一个比较精巧的数据结构,通常在数据量大的场景下,会有出现的表现效果;redis 本身基于 String 数据结构来实现 bitmap 的功能支持,使用方式比较简单,基本上就下面三个命令

    • setbit key index 1/0: 设置
    • getbit key index: 判断是否存在
    • bitcount key: 计数统计

    本文也给出了 bitmap 的三个常见的应用场景

    • 日活统计:主要借助bitcount来获取总数(后面会介绍,在日活十万百万以上时,使用 hyperLogLog 更优雅)
    • 点赞: 主要借助setbit/getbit来判断用户是否赞过,从而实现去重
    • bloomfilter: 基于 bitmap 实现的布隆过滤器,广泛用于去重的业务场景中(如缓存穿透,爬虫 url 去重等)

    总的来讲,bitmap 属于易用,巧用的数据结构,用得好即能节省内存也可以提高效率,用得不好貌似也不会带来太大的问题

    II. 其他

    0. 项目

    系列博文

    工程源码

    1. 一灰灰 Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

  • 相关阅读:
    原生js实现购物车相关功能
    js+css让背景图片动起来
    彻底搞清楚rgba与opacity/filter的区别
    国家对五险一金的详细缴纳说明
    原生js+css实现二级伸缩菜单
    原生js实现table表格的各行变色功能
    原生js实现二级导航功能
    app下载文件,保存文件,展示文件(以图片文件为例)
    实现锚点跳转的两种方式及注意事项
    vue刷新页面及注意事项
  • 原文地址:https://www.cnblogs.com/yihuihui/p/13832529.html
Copyright © 2011-2022 走看看