最后
本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以送给我的读者朋友们:
目录:
Java面试核心知识点
一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!
Java面试核心知识点
所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。
如何选择合适的数据集合,我们首先要了解常用的统计模式,并运用合理的数据来解决实际问题。
四种统计类型:
-
二值状态统计;
-
聚合统计;
-
排序统计;
-
基数统计。
本文将用到 String、Set、Zset、List、hash 以外的拓展数据类型 Bitmap
、HyperLogLog
来实现。
文章涉及到的指令可以通过在线 Redis 客户端运行调试,地址:https://try.redis.io/,超方便的说。
基数统计
====
❝
基数统计:统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)。
实现基数统计最直接的方法,就是采用集合(Set)这种数据结构,当一个元素从未出现过时,便在集合中增加一个元素;如果出现过,那么集合仍保持不变。
当页面访问量巨大,就需要一个超大的 Set 集合来统计,将会浪费大量空间。
另外,这样的数据也不需要很精确,到底有没有更好的方案呢?
这个问题问得好,Redis 提供了 HyperLogLog
数据结构就是用来解决种种场景的统计问题。
HyperLogLog
是一种不精确的去重基数方案,它的统计规则是基于概率实现的,标准误差 0.81%,这样的精度足以满足 UV 统计需求了。
关于 HyperLogLog 的原理过于复杂,如果想要了解的请移步:
-
https://www.zhihu.com/question/53416615
-
https://en.wikipedia.org/wiki/HyperLogLog
网站的 UV
通过 Set 实现
一个用户一天内多次访问一个网站只能算作一次,所以很容易就想到通过 Redis 的 Set 集合来实现。
用户编号 89757 访问 「Redis 为什么这么快 」时,我们将这个信息放到 Set 中。
SADD Redis为什么这么快:uv 89757
当用户编号 89757 多次访问「Redis 为什么这么快」页面,Set 的去重功能能保证不会重复记录同一个用户 ID。
通过 SCARD
命令,统计「Redis 为什么这么快」页面 UV。指令返回一个集合的元素个数(也就是用户 ID)。
SCARD Redis为什么这么快:uv
通过 Hash 实现
❝
码老湿,还可以利用 Hash 类型实现,将用户 ID 作为 Hash 集合的 key,访问页面则执行 HSET 命令将 value 设置成 1。
即使用户重复访问,重复执行命令,也只会把这个 userId 的值设置成 “1"。
最后,利用 HLEN
命令统计 Hash 集合中的元素个数就是 UV。
如下:
HSET redis集群:uv userId:89757 1
// 统计 UV
HLEN redis集群
HyperLogLog 王者方案
❝
码老湿,Set 虽好,如果文章非常火爆达到千万级别,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。同理,Hash数据类型也是如此。咋办呢?
利用 Redis 提供的 HyperLogLog
高级数据结构(不要只知道 Redis 的五种基础数据类型了)。这是一种用于基数统计的数据集合类型,即使数据量很大,计算基数需要的空间也是固定的。
每个 HyperLogLog
最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。
Redis 对 HyperLogLog
的存储进行了优化,在计数比较小的时候,存储空间采用系数矩阵,占用空间很小。
只有在计数很大,稀疏矩阵占用的空间超过了阈值才会转变成稠密矩阵,占用 12KB 空间。
PFADD
将访问页面的每个用户 ID 添加到 HyperLogLog
中。
PFADD Redis主从同步原理:uv userID1 userID 2 useID3
PFCOUNT
利用 PFCOUNT
获取 「Redis主从同步原理」页面的 UV值。
PFCOUNT Redis主从同步原理:uv
PFMERGE 使用场景
HyperLogLog
除了上面的 PFADD
和 PFCOIUNT
外,还提供了 PFMERGE
,将多个 HyperLogLog
合并在一起形成一个新的 HyperLogLog
值。
语法
PFMERGE destkey sourcekey [sourcekey …]
使用场景
比如在网站中我们有两个内容差不多的页面,运营说需要这两个页面的数据进行合并。
其中页面的 UV 访问量也需要合并,那这个时候 PFMERGE
就可以派上用场了,也就是同样的用户访问这两个页面则只算做一次。
如下所示:Redis、MySQL 两个 Bitmap 集合分别保存了两个页面用户访问数据。
PFADD Redis数据 user1 user2 user3
PFADD MySQL数据 user1 user2 user4
PFMERGE 数据库 Redis数据 MySQL数据
PFCOUNT 数据库 // 返回值 = 4
将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。
user1、user2 都访问了 Redis 和 MySQL,只算访问了一次。
排序统计
====
Redis 的 4 个集合类型中(List、Set、Hash、Sorted Set),List 和 Sorted Set 就是有序的。
-
List:按照元素插入 List 的顺序排序,使用场景通常可以作为 消息队列、最新列表、排行榜;
-
Sorted Set:根据元素的 score 权重排序,我们可以自己决定每个元素的权重值。使用场景(排行榜,比如按照播放量、点赞数)。
最新评论列表
❝
码老湿,我可以利用 List 插入的顺序排序实现评论列表
比如微信公众号的后台回复列表(不要杠,举例子),每一公众号对应一个 List,这个 List 保存该公众号的所有的用户评论。
每当一个用户评论,则利用 LPUSH key value [value ...]
插入到 List 队头。
LPUSH 码哥字节 1 2 3 4 5 6
接着再用 LRANGE key star stop
获取列表指定区间内的元素。
LRANGE 码哥字节 0 4
1) “6”
2) “5”
3) “4”
4) “3”
5) “2”
注意,并不是所有最新列表都能用 List 实现,对于因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉。
比如当前评论列表 List ={A, B, C, D}
,左边表示最新的评论,D 是最早的评论。
LPUSH 码哥字节 D C B A
展示第一页最新 2 个评论,获取到 A、B:
LRANGE 码哥字节 0 1
1) “A”
2) “B”
按照我们想要的逻辑来说,第二页可通过 LRANGE 码哥字节 2 3
获取 C,D。
如果在展示第二页之前,产生新评论 E,评论 E 通过 LPUSH 码哥字节 E
插入到 List 队头,List = {E, A, B, C, D }。
现在执行 LRANGE 码哥字节 2 3
获取第二页评论发现, B 又出现了。
LRANGE 码哥字节 2 3
1) “B”
2) “C”
出现这种情况的原因在于 List 是利用元素所在的位置排序,一旦有新元素插入,List = {E,A,B,C,D}
。
原先的数据在 List 的位置都往后移动一位,导致读取都旧元素。
List最新列表
小结
只有不需要分页(比如每次都只取列表的前 5 个元素)或者更新频率低(比如每天凌晨统计更新一次)的列表才适合用 List 类型实现。
对于需要分页并且会频繁更新的列表,需用使用有序集合 Sorted Set 类型实现。
另外,需要通过时间范围查找的最新列表,List 类型也实现不了,需要通过有序集合 Sorted Set 类型实现,如以成交时间范围作为条件来查询的订单列表。
排行榜
❝
码老湿,对于最新列表的场景,List 和 Sorted Set 都能实现,为啥还用 List 呢?直接使用 Sorted Set 不是更好,它还能设置 score 权重排序更加灵活。
原因是 Sorted Set 类型占用的内存容量是 List 类型的数倍之多,对于列表数量不多的情况,可以用 Sorted Set 类型来实现。
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
t 和 Sorted Set 都能实现,为啥还用 List 呢?直接使用 Sorted Set 不是更好,它还能设置 score 权重排序更加灵活。
原因是 Sorted Set 类型占用的内存容量是 List 类型的数倍之多,对于列表数量不多的情况,可以用 Sorted Set 类型来实现。
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
[外链图片转存中…(img-k1SU1aRt-1715005087737)]