Redis高级(四)、Redis新类型bitmap、hyperloglog、geo详解

221 阅读12分钟

觉得对你有益的小伙伴记得点个赞+关注

后续完整内容持续更新中

希望一起交流的欢迎发邮件至javalyhn@163.com

在学习了Redis五种基本数据类型了以后,肯定有小伙伴说,这五种数据类型还不够用吗?答案是肯定的。

五种基本数据类型详解 juejin.cn/post/719089…

在大需求量的环境下,一定会有这五种基本数据类型搞不定的,况且现在有许多问你亿级需求量下的场景你该用什么方法解决。

1. 为什么出现这三种数据类型

在移动应用中,需要统计每天的新增用户数和第2天的留存用户数

在签到打卡中,需要统计一个月内连续打卡的用户数

在网页访问记录中,需要统计UV量

通过观察上述问题,要是用户访问量小的,基本数据类型还能适用、那如果在日用户访问级别都是亿级的平台,你怎么解决,如何处理呢?

综上所述,这些需求的痛点就是 亿级数据的收集+统计

因此,数据存的进+数据取得快+多统计 这就是我们需要思考的设计方案。而这三点,对我们来说,真正有价值的是统计

2. 亿级系统下,常见的四种统计

2.1 聚合统计

聚合统计:统计多个集合元素的聚合结果,也就是交差并等集合统计

2.2 排序统计

首先给你一个需求:请你设计一个某平台最新评论展现列表,说出你思考的数据类型以及设计思路。

我:你好,对于最新评论展现列表,需要两个功能,时间排序和分页功能。

这两种功能在Redis里能用list和zset解决,但是zset是我所选择的数据类型。

如果每一个商品的评价对应一个list集合,那么list中一定会包含对这个商品的所有评论,而且会按照时间评论保存这些评论。这时,有一条新的评论就使用lpush命令将它插入到list的对头。但是如果在演示第二页的时候,又产生了一个新的评论,第二页的评论就会有重复的部分,原因是list是通过元素在list中的位置来排序的,当一个新元素的插入,原先的元素在list中的位置都后移了一位,原来在第一位的元素现在排到了第二位,当用lrange读取时,就会读到旧元素。

image.png

但是如果我用zset,首先zset本身自带插入元素时附带插入分数的优点,所以在我们添加最新评论时,可以将最新评论的时间戳(分数)一起插入,另外再取出元素时,也可以倒序取,顺序取,相比list灵活、方便很多。

image.png

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

2.3 二值统计

二值:对于科班的小伙伴来说一看就能想到0,1这两个数字。二值统计能用来统计只有两种情况的事情,比如上班签到打卡,我们有记录为1(已签到)、0(未签到)。

那么在Redis怎么进行二值统计呢,那么就是接下来要讲的bitmap了

2.4 基数统计

基数统计:只统计一个集合中不重复的元素个数。 不重复?set set set完美解决,是吗?大流量呢? 那么就是下面将介绍的hyperloglog了。

3. bitmap

3.1 什么是bitmap

一句话,就是由0和1状态表现的二进制位的bit数组 详细一点:bit arrays(or simply bitmaps),可以叫做位图

image.png

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

image.png

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

3.2 bitmap能干嘛

用于状态统计 Y/N 类似AtomincBoolean

  1. 用户是否登陆过,签到送积分
  2. 电影、广告是否被点击过
  3. 签到统计
  4. 日活统计
  5. 连续签到打卡
  6. 最近一周活跃的用户
  7. 统计用户一年之中的登录天数
  8. 某用户按照一年365天,哪几天登陆过?哪几天没有登陆?全年中登录的天数共计多少?
  9. .......

3.3 签到案例

  1. 需求说明
  • 签到日历仅展示当月签到数据
  • 签到日历需展示最近连续签到天数
  • 假设当前日期是20210618,且20210616未签到
  • 若20210617已签到且0618未签到,则连续签到天数为1
  • 若20210617已签到且0618已签到,则连续签到天数为2
  • 连续签到天数越多,奖励越大
  • 所有用户均可签到
  1. 基于mysql实现(小型公司使用)
  • 建表
CREATE TABLE user_sign
(
  keyid BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  user_key VARCHAR(200),#用户ID
  sign_date DATETIME,#签到日期(20230119)
  sign_count INT #连续签到天数
)
 
INSERT INTO user_sign(user_key,sign_date,sign_count)
VALUES ('20230121-xxxx-xxxx-xxxx-xxxxxxxxxxxx','2021-01-11 15:11:12',1);
 
SELECT
    sign_count
FROM
    user_sign
WHERE
    user_key = '20230121-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    AND sign_date BETWEEN '2023-01-11 00:00:00' AND '2023-01-18 23:59:59'
ORDER BY
    sign_date DESC
    LIMIT 1;
  • 困难与解决思路

方法正确但是很难实现,签到用户量较少的时候可以使用、但是大体量的用户(3000w签到用户,一天一条数据,一个月9亿条数据),mysql不给你用爆了。

如何解决:

① 一条签到记录对应一条记录,会占据越来越大的空间 ② 一个月最多31天,刚好我们的int类型是32位,那这样一个int类型就可以搞定一个月,32位大于31天,当天来了就是1没来就是0 ③ 一条数据直接存储一个月的签到记录,不再是存储一天的签到记录

  1. 基于Redis的bitmap实现签到日历

在签到统计时,每个用户一天的签到用1个bit位就能表示, 一个月(假设是31天)的签到情况用31个bit位就可以,一年的签到也只需要用365个bit位,根本不用太复杂的集合类型

3.4 bitmap基本命令

3.4.1 setbit

  • setbit key offset value
  • setbit 键 偏移位 0/1

image.png

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

3.4.2 getbit

getbit key offset

3.4.3 setbit 和 getbit案例说明

image.png

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

假如是亿级的系统, 每天使用1个1亿位的Bitmap约占12MB的内存(10^8/8/1024/1024),10天的Bitmap的内存开销约为120MB,内存压力不算太高。在实际使用时,最好对Bitmap设置过期时间,让Redis自动删除不再需要的签到记录以节省内存开销。

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

  1. 实质是二进制assii编码对应
  2. redis里用type命令看看bitmap实质是什么类型??? string
  3. man ascii

image.png

  1. 设置命令

image.png

image.png

3.4.5 strlen

统计字节数占用多少

image.png

不是字符串的长度占多少而是占据几个字节(1B = 8bit),超过8位后自己按照8位一组再扩容

3.4.6 bitcount

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

image.png

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

image.png

3.4.7 bitop operation destkey key [key ...]

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

案例:统计连续两天都签到的用户

image.png

4. hyperloglog

4.1 什么是UV、PV、DAU、MAV

UV:Unique Vistor,独立访客,一般理解为客户端ip,需要去重考虑

PV:Page View,页面浏览量,不需要去重

DAU:Daily Active User 日活跃用户量,登录或者使用了某个产品的用户数(去重复登录的用户),常用于反映网站、互联网应用或者网络游戏的运营情况

MAV:MonthIy Active User 月活跃用户量

4.2 hyperloglog能干嘛

  • 统计某个网站的UV、统计某个文章的UV
  • 用户搜索网站关键词的数量
  • 统计用户每天搜索不同词条个数

4.3 hyperloglog是什么

去重复统计功能的基数估计算法-就是HyperLogLog

image.png

基数:是一种数据集,去重复后的真实个数

案例:

image.png

基数统计:用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算

一句话就是去重复数之后的真实数据

4.4 hyperloglog是如何做的?如何演化的?

4.4.1 去重复统计你能想到哪些方法

  • HashSet
  • bitmap 如果数据显较大亿级统计,使用bitmaps同样会有问题。

bitmap是通过用位bit数组来表示各元素是否出现,每个元素对应一位,所需的总内存为N个bit。

基数计数则将每一个元素对应到bit数组中的其中一位,比如bit数组010010101(按照从零开始下标,有的就是1、4、6、8)。

新进入的元素只需要将已经有的bit数组和新加入的元素进行按位或计算就行。这个方式能大大减少内存占用且位操作迅速。

But,假设一个样本案例就是一亿个基数位值数据,一个样本就是一亿 如果要统计1亿个数据的基数位值,大约需要内存100000000/8/1024/1024约等于12M,内存减少占用的效果显著。

这样得到统计一个对象样本的基数值需要12M。

如果统计10000个对象样本(1w个亿级),就需要117.1875G将近120G,可见使用bitmaps还是不适用大数据量下(亿级)的基数计数场景,

但是bitmaps方法是精确计算的。

结论:样本元素越多内存消耗急剧增大,难以管控+各种慢, 对于亿级统计不太合适,大数据害死人

办法:概率算法

通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用,因为概率算法不直接存储数据本身, 通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不储存数据故此可以大大节约内存。

HyperLogLog就是一种概率算法的实现。

4.4.2 原理说明

  1. 只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
  2. 有误差,非准确统计,牺牲准确率来换取空间,误差仅仅是0.81%左右
  3. 0.81%这个误差哪里来的?论文地址和出处 antirez.com/news/75

antirez回答:

image.png

image.png

4.4.3 为啥Redis集群最大槽数是16384?

因为本章重点是数据类型介绍,所以详细介绍请看 juejin.cn/post/719102…

4.5 基本命令

image.png

image.png

5. geo

5.1 简介 www.redis.cn/commands/ge…

移动互联网时代LBS应用越来越多,外卖软件中附近的美食店铺、打车软件附近的车辆等等,那这种附近各种形形色色的XXX地址位置选择是如何实现的?

地球上的地理位置是使用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90, 90],只要我们确定一个点的经纬度就可以名曲他在地球的位置。 例如滴滴打车,最直观的操作就是实时记录更新各个车的位置, 然后当我们要找车时,在数据库中查找距离我们(坐标x0,y0)附近r公里范围内部的车辆

使用如下SQL即可:

select taxi from position where x0-r < x < x0 + r and y0-r < y < y0+r

但是这样会有什么问题呢?

1.查询性能问题,如果并发高,数据量大这种查询是要搞垮数据库的

2.这个查询的是一个矩形访问,而不是以我为中心r公里为半径的圆形访问。

3.精准度的问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会有很大误差

5.2 Redis在3.2版本以后增加了地理位置的处理

5.3 原理

核心思想就是将球体转换为平面,区块转换为一点

image.png

有兴趣了解GeoHash详细原理解析的可以参考www.cnblogs.com/LBSer/p/331…

5.4 命令

5.4.1 geoadd

image.png

image.png

中文乱码如何处理

image.png

5.4.2 geopos

image.png

image.png

5.4.3 geohash

image.png

geohash算法生成的base32编码值

image.png

5.4.4 geodist

image.png

5.4.5 georadius (用的最多)

georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

georadius city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc

WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。

WITHCOORD: 将位置元素的经度和维度也一并返回。

WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

COUNT:限定返回的记录数。

image.png

5.4.6 georadiusbymember

image.png

那么至此,bitmap、hyperloglog、geo就讲完了,欢迎小伙伴们补充。