4、redis新类型-bitmap

1、先看看大厂真实面试需求+面试反馈

面试题一:

  1. 手机APP中的每天的用户登录信息:1天对应1系列用户ID或移动设备ID;
  2. 电商网站上商品的用户评论列表:1商品对应了1系列的评论
  3. 用户在手机APP上的签到打卡信息:1天对应1系列用户的签到记录;
  4. 应用网站上的网页访问信息:1个网页对应1系列的访问点击

面试题二:

  1. 记录对集合中的数据进行统计
  2. 在移动应用中,需要统计每天的新增用户数和第2天的留存用户数;
  3. 在电商网站的商品评论中,需要统计评论列表中的最新评论;
  4. 在签到打卡中,需要统计一个月内连续打卡的用户数
  5. 在网页访问记录中,需要统计独立访客(UniqueVisitor ,UV)量

痛点:类似今日头条、抖音、淘宝这样的用户访问级别都是亿级别,请问如何处理?

需求痛点:

  1. 亿级数据的收集+统计
  2. 一句话: 存的进+取得快+多统计
  3. 真正有价值的是统计。。。。。

2、统计的类型有哪些

常见的四种统计:

2.1、聚合统计

统计多个集合元素的聚合结果,就是前面讲解过的交差并等集合统计

交并差集和聚合函数的应用

2.2、排序统计

抖音视频最新评论留言的场景,请你设计一个展现列表。考察你的数据结构和设计思路

设计案例和回答示例:

以抖音vcr最新的留言评价为案例,所有评论需要两个功能,按照时间排序+分页显示

能够排序+分页显示的redis数据结构是什么合适?

方案一:

list

每个商品评价对应一个List集合,这个List包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,

每来一个新评论就用LPUSH命令把它插入List的对头。但是,如果在演示第二页前,又产生了一个新评论,

第2页的评论不一样了。原因:

List是通过元素在List中的位置来排序的,当有一个新元素插入时,原先的元素在List中的位置都往后移了一位,

原来在第1位的元素现在排在了第2位,当LRANAGE读取时,就会读到旧元素。

4、redis新类型-bitmap_第1张图片

方案二:

zset:

4、redis新类型-bitmap_第2张图片

 

结论:

在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用ZSet,不用list

2.3、二值统计

集合元素的取值就只有0和1两种。在钉钉上班签到打卡的场景中,我们就只用记录有签到(1)或签到(0),见bitmap

2.4、基数统计

指统计一个集合中不重复的元素个数,见hyperloglgo

4、bitmap

4.1、是什么?

由0和1状态表现的二进制位的bit数组

4、redis新类型-bitmap_第3张图片

说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型

位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们可以称之为一个索引或者位格)。Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512M内存就可以存储多大42.9亿的字节信息(2^32=4294967296)

4.2、能干嘛?

①用于统计状态:Y,N,类似AtomicBoolean

②看需求:1)用户是否登陆过Y,N,比如京东每日签到送京豆

2)电影、广告是否被点击播放过

3)钉钉打卡上下班,签到统计

③大厂真实案例

1)日活统计

2)连续签到打卡

3)最近一周的活跃用户

4)统计指定用户一年之中的登陆天数

5)某用户按照一年365天,哪几天登陆过?哪几天没有登陆?全年中登陆的天数共计多少?

4.3、京东签到领取京豆

4.3.1、需求说明

4、redis新类型-bitmap_第4张图片

 

4、redis新类型-bitmap_第5张图片

签到日历仅展示当月签到数据

签到日历需展示最近连续签到天数

假设当前日期是20210618,且20210616未签到

若20210617已签到且0618未签到,则连续签到天数为1

若20210617已签到且0618已签到,则连续签到天数为2

连续签到天数越多,奖励越大

所有用户均可签到

截至2020年3月31日的12个月,京东年度活跃用户数3.87亿,同比增长24.8%,环比增长超2500万,此外,2020年3月移动端日均活

跃用户数同比增长46%假设10%左右的用户参与签到,签到用户也高达3千万。。。。。。

4.3.2、小厂方法,传统mysql方式

建表sql

CREATE TABLE user_sign
(
  keyid BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  user_key VARCHAR(200),#京东用户ID
  sign_date DATETIME,#签到日期(20210618)
  sign_count INT #连续签到天数
)

INSERT INTO user_sign(user_key,sign_date,sign_count)
VALUES ('20210618-xxxx-xxxx-xxxx-xxxxxxxxxxxx','2020-06-18 15:11:12',1);

SELECT
    sign_count
FROM
    user_sign
WHERE
    user_key = '20210618-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    AND sign_date BETWEEN '2020-06-17 00:00:00' AND '2020-06-18 23:59:59'
ORDER BY
    sign_date DESC
    LIMIT 1;

 

困难和解决思路

方法正确但是难以落地实现。

签到用户量较小时这么设计能行,但京东这个体量的用户(估算3000W签到用户,一天一条数据,一个月就是9亿数据)

对于京东这样的体量,如果一条签到记录对应着当日用记录,那会很恐

如何解决这个痛点?

1、一条签到记录对应一条记录,会占据越来越大的空间。

2、一个月最多31天,刚好我们的int类型是32位,那这样一个int类型就可以搞定一个月,32位大于31天,当天来了位是1没来就是0

3、一条数据直接存储一个月的签到记录,不再是存储一天的签到记录。

4.3.3、大厂方法,基于Redis的Bitmaps实现签到日历

建表-按位-redis bitmap

4.3.4、说明

在签到统计时,每个用户一天的签到用1个bit位就能表示,

一个月(假设是31天)的签到情况用31个bit位就可以,一年的签到也只需要用365个bit位,根本不用太复杂的集合类型

4.4、基本命令

命令

作用

时间复杂度

setbit key offset val

给指定key的值的第offset赋值val

O(1)

getbit key offset

获取指定key的第offset位

O(1)

bitcount key start end

返回指定key中[start,end]中为1的数量

O(n)

bitop operation destkey key

对不同的二进制存储数据进行位运算(AND,OR,NOT,XOR)

O(n)

4.4.1、setbit

setbit key offset value 
setbit 键  偏移位  只能0或者1

127.0.0.1:6379> setbit sign:u1:202105 0 1 //设置202105月份第0天 打卡
(integer) 0
127.0.0.1:6379> setbit sign:u1:202105 0 1
(integer) 1
127.0.0.1:6379> setbit sign:u1:202105 0 1
(integer) 1
127.0.0.1:6379> setbit sign:u1:202105 0 1
(integer) 1
127.0.0.1:6379> setbit sign:u1:202105 0 1
(integer) 1
127.0.0.1:6379> setbit sign:u1:202105 1 1 //设置202105月份第1天 打卡
(integer) 0
127.0.0.1:6379> setbit sign:u1:202105 2 1 //设置202105月份第2天 打卡
(integer) 0
127.0.0.1:6379> setbit sign:u1:202105 3 1 //设置202105月份第3天 打卡
(integer) 0
127.0.0.1:6379> setbit sign:u1:202105 30 1 //设置202105月份第30天 打卡
(integer) 0
127.0.0.1:6379> getbit sign:u1:202105 1 //获取202105月份第1天 打卡
(integer) 1
127.0.0.1:6379> getbit sign:u1:202105 2 //获取202105月份第2天 打卡
(integer) 1
127.0.0.1:6379> getbit sign:u1:202105 3 //获取202105月份第3天 打卡
(integer) 1
127.0.0.1:6379> getbit sign:u1:202105 4 //获取202105月份第4天 未打卡
(integer) 0
127.0.0.1:6379> getbit sign:u1:202105 15 //获取202105月份第15天 未打卡
(integer) 0
127.0.0.1:6379> getbit sign:u1:202105 30 //获取202105月份第30天 打卡
(integer) 1


127.0.0.1:6379> setbit sign:u1:202105 3 77 //设置超过1的值报异常
(error) ERR bit is not an integer or out of range

 

保存到redis后的数据就是下边这个样子

4、redis新类型-bitmap_第6张图片

Bitmap的偏移量是从零开始算的

4.4.2、getbit

getbit key offset

 

4.4.3、setbit和getbit案例说明

按照天:见上

按照年

按年去存储一个用户的签到情况,365天只需要365/8≈46Byte,1000W用户量一年也只需要44MB就足够了。

假如是亿级的系统,工

每天使用1个1亿位的 Bitmap约占12MB的内存(10^8/8/1024/1024),10天 bitmap的的内存开销约

为120MB,内存压力不算太高。

在实际使用时,最好对 Bitmap设置过期时间,让 Redis自动删除不再需要的签到记录以节省内存开销。

4.4.4、bitmap的底层编码说明,get命令操作如何

实质是二进制的ASCII编码对应

redis里用type命令看看bitmap实质是什么类型?? String

4.4.5、strlen

127.0.0.1:6379> strlen sign:u1:202105
(integer) 4

不是字符串长度而是占据几个字节,超过8位后自己按照8位一组一byte再扩容

4.4.6、bitcount

全部键里面含有1的有多少个?

127.0.0.1:6379> bitcount sign:u1:202105
(integer) 5

一年365天,全年天天登陆占用多少字节

127.0.0.1:6379> setbit k1 364 1
(integer) 0
127.0.0.1:6379> strlen k1
(integer) 46     //=365/8

 

4.4.7、bitop

BITOP operation destkey key [key ...]

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:

BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。

BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。

BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。

BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。

连续2天都签到的用户

加入某个网站或者系统,它的用户有1000W,做个用户id和位置的映射

比如0号位对应用户id:uid-092iok-lkj

比如1号位对应用户id:uid-7388c-xxx

.....

0

1

0

1

1

0

1

0

0

1

1

1

1

0

BITOP AND

0

0

0

1

1

0

0

127.0.0.1:6379> setbit bits-1 0 0
(integer) 0
127.0.0.1:6379> setbit bits-1 1 1
(integer) 0
127.0.0.1:6379> setbit bits-1 2 0
(integer) 0
127.0.0.1:6379> setbit bits-1 3 1
(integer) 0
127.0.0.1:6379> setbit bits-1 4 1
(integer) 0
127.0.0.1:6379> setbit bits-1 5 0
(integer) 0
127.0.0.1:6379> setbit bits-1 6 1
(integer) 0
127.0.0.1:6379> setbit bits-2 0 0
(integer) 0
127.0.0.1:6379> setbit bits-2 1 0
(integer) 0
127.0.0.1:6379> setbit bits-2 2 1
(integer) 0
127.0.0.1:6379> setbit bits-2 3 1
(integer) 0
127.0.0.1:6379> setbit bits-2 4 1
(integer) 0
127.0.0.1:6379> setbit bits-2 5 1
(integer) 0
127.0.0.1:6379> setbit bits-2 6 0
(integer) 0
127.0.0.1:6379> bitop and  and-result bits-1 bits-2
(integer) 1
127.0.0.1:6379> getbit and-result 0
(integer) 0
127.0.0.1:6379> getbit and-result 1
(integer) 0
127.0.0.1:6379> getbit and-result 2
(integer) 0
127.0.0.1:6379> getbit and-result 3
(integer) 1
127.0.0.1:6379> getbit and-result 4
(integer) 1
127.0.0.1:6379> getbit and-result 5
(integer) 0
127.0.0.1:6379> getbit and-result 6
(integer) 0

你可能感兴趣的:(缓存,redis)