Redis 高级特性之 Bitmap 使用姿势及应用场景介绍

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

bitmap 基本使用

日活统计应用场景中 bitmap 使用姿势

点赞去重应用场景中 bitmap 使用姿势

布隆过滤器 bloomfilter 基本原理及体验 case

I. 基本使用

福利 福利  福利 免费领取Java架构技能地图  注意了是免费送 

点击免费领取

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 主要就三个操作命令,setbit,getbit以及 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,通过插件的形式提供,项目源码地址为: github.com/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 属于易用,巧用的数据结构,用得好即能节省内存也可以提高效率,用得不好貌似也不会带来太大的问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352