1、先看看大厂真实面试需求+面试反馈
面试题一:
面试题二:
痛点:类似今日头条、抖音、淘宝这样的用户访问级别都是亿级别,请问如何处理?
需求痛点:
2、统计的类型有哪些
常见的四种统计:
2.1、聚合统计
统计多个集合元素的聚合结果,就是前面讲解过的交差并等集合统计
交并差集和聚合函数的应用
2.2、排序统计
抖音视频最新评论留言的场景,请你设计一个展现列表。考察你的数据结构和设计思路
设计案例和回答示例:
以抖音vcr最新的留言评价为案例,所有评论需要两个功能,按照时间排序+分页显示
能够排序+分页显示的redis数据结构是什么合适?
方案一:
list
每个商品评价对应一个List集合,这个List包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,
每来一个新评论就用LPUSH命令把它插入List的对头。但是,如果在演示第二页前,又产生了一个新评论,
第2页的评论不一样了。原因:
List是通过元素在List中的位置来排序的,当有一个新元素插入时,原先的元素在List中的位置都往后移了一位,
原来在第1位的元素现在排在了第2位,当LRANAGE读取时,就会读到旧元素。
方案二:
zset:
结论:
在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用ZSet,不用list
2.3、二值统计
集合元素的取值就只有0和1两种。在钉钉上班签到打卡的场景中,我们就只用记录有签到(1)或签到(0),见bitmap
2.4、基数统计
指统计一个集合中不重复的元素个数,见hyperloglgo
4、bitmap
4.1、是什么?
由0和1状态表现的二进制位的bit数组
说明:用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、需求说明
签到日历仅展示当月签到数据
签到日历需展示最近连续签到天数
假设当前日期是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后的数据就是下边这个样子
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