觉得对你有益的小伙伴记得点个赞+关注
后续完整内容持续更新中
希望一起交流的欢迎发邮件至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读取时,就会读到旧元素。
但是如果我用zset,首先zset本身自带插入元素时附带插入分数的优点,所以在我们添加最新评论时,可以将最新评论的时间戳(分数)一起插入,另外再取出元素时,也可以倒序取,顺序取,相比list灵活、方便很多。
总结就是:在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用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),可以叫做位图
说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型
位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们可以称之为一个索引或者位格)。Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512M内存就可以存储多大42.9亿的字节信息(2^32 = 4294967296)
3.2 bitmap能干嘛
用于状态统计 Y/N 类似AtomincBoolean
- 用户是否登陆过,签到送积分
- 电影、广告是否被点击过
- 签到统计
- 日活统计
- 连续签到打卡
- 最近一周活跃的用户
- 统计用户一年之中的登录天数
- 某用户按照一年365天,哪几天登陆过?哪几天没有登陆?全年中登录的天数共计多少?
- .......
3.3 签到案例
- 需求说明
- 签到日历仅展示当月签到数据
- 签到日历需展示最近连续签到天数
- 假设当前日期是20210618,且20210616未签到
- 若20210617已签到且0618未签到,则连续签到天数为1
- 若20210617已签到且0618已签到,则连续签到天数为2
- 连续签到天数越多,奖励越大
- 所有用户均可签到
- 基于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 ③ 一条数据直接存储一个月的签到记录,不再是存储一天的签到记录
- 基于Redis的bitmap实现签到日历
在签到统计时,每个用户一天的签到用1个bit位就能表示, 一个月(假设是31天)的签到情况用31个bit位就可以,一年的签到也只需要用365个bit位,根本不用太复杂的集合类型
3.4 bitmap基本命令
3.4.1 setbit
- setbit key offset value
- setbit 键 偏移位 0/1
- Bitmap的偏移量是从零开始算的
3.4.2 getbit
getbit key offset
3.4.3 setbit 和 getbit案例说明
如果我们按年去存储一个用户的签到情况,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命令操作如何
- 实质是二进制assii编码对应
- redis里用type命令看看bitmap实质是什么类型???
string
- man ascii
- 设置命令
3.4.5 strlen
统计字节数
占用多少
不是字符串的长度占多少而是占据几个字节(1B = 8bit),超过8位后自己按照8位一组再扩容
3.4.6 bitcount
- 全部键里面含有1的有多少个
- 一年365天,全年天天登陆占用多少字节
3.4.7 bitop operation destkey key [key ...]
对一个或多个保存二进制位的字符串key进行位元操作,并将结果保存到destkey中
案例:统计连续两天都签到的用户
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
基数
:是一种数据集,去重复后的真实个数
案例:
基数统计
:用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算
一句话就是去重复数之后的真实数据
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 原理说明
- 只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
- 有误差,非准确统计,
牺牲准确率来换取空间,误差仅仅是0.81%左右
- 0.81%这个误差哪里来的?论文地址和出处 antirez.com/news/75
antirez回答:
4.4.3 为啥Redis集群最大槽数是16384?
因为本章重点是数据类型介绍,所以详细介绍请看 juejin.cn/post/719102…
4.5 基本命令
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 原理
核心思想就是将球体转换为平面,区块转换为一点
有兴趣了解GeoHash详细原理解析的可以参考www.cnblogs.com/LBSer/p/331…
5.4 命令
5.4.1 geoadd
中文乱码如何处理
5.4.2 geopos
5.4.3 geohash
geohash算法生成的base32编码值
5.4.4 geodist
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:限定返回的记录数。
5.4.6 georadiusbymember
那么至此,bitmap、hyperloglog、geo就讲完了,欢迎小伙伴们补充。