在40岁老架构师 尼恩的读者社区(50+)中,最近有小伙伴拿到了一线互联网企业如极兔、有赞、希音、百度、网易、滴滴的面试资格,遇到一几个很重要的面试题:
(1) 亿级用户场景,如何高性能统计日活?
(2) 如何实现亿级数据统计?
(3) 亿级用户日活统计,有几种方案?
等等等等…
高并发Redis的使用,是面试的重点和高频点。
尼恩作为技术中台、数据中台的架构师,致力于为大家研究出一个 3高架构知识宇宙, 所以,这里,带大家完成一个 亿级用户场景,如何高性能统计日活的架构分析和实操。
当然,作为一篇文章,仅仅是抛砖引玉,后面有机会,带大家做一下这个高质量的实操,并且指导大家写入简历。
让面试官爱到 “不能自已、口水直流”。
也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典》V65版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从文末公号 【技术自由圈】取
什么是日活?
日活跃用户数量(Daily Active User,DAU)是用于反映网站、互联网应用或网络游戏的运营情况的统计指标。
日活跃用户数量通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户)。
受统计方式限制,互联网行业使用的日均活跃用户数指在统计周期(周/月)内,该App的每日活跃用户数的平均值。
一个用户一天内多次访问一个网站只能算作一次,
怎么做存储架构呢?
大概有四种方案,可供技术选型:
将用户id 放到 Set 中,Set 的去重功能能保证不会重复记录同一个用户 ID。
一个ZSET类型的Key,它的成员数量为10000个
一般来说,这个ZSET就是bigkey
将用户 ID 作为 Hash 集合的 key,访问页面则执行 HSET
命令将 value 设置成 1。
即使用户重复访问,重复执行命令,也只会把这个 userId 的值设置成 “1"。
利用 HLEN
命令,统计 Hash 集合中的元素个数就是 UV。
一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value值总大小为100MB,
一般来说,这个 Hash 就是bigkey
使用bitmap,记录用户id的访问,则把指定key的用户id对应的bit位标记为1,
统计 日活,就是就是指定key中1的个数。
显然这里是操作位,相比2中,一个字节为8位,估算一下占用空间:96M/8=12M,
1千万用户只要 1M多的空间,但是 1亿的用户需要12M的大小即可记录。
一个String类型的Key,它的值为5MB, 就是bigkey
bitmap 底层结构,和String类型是类似的。
在输入用户数量非常大时,计算基数所需要的空间总是固定的,并且是很小的,
每个hyperloglog键需要12kb内存可以计算2^64个不同元素的基数,
这和元素基数元素越多,耗费内存越多的集合形成鲜明对比,
但是hyperloglog只会根据元素来计算基数,不会存储元素本身,所以不能像其它类型一样返回各个元素
hyperloglog是概率算法,是牺牲准确率换区空间的,
对于对精度要求不高的情况下可以使用,因为概率算法本身不直接存储数据本身,能保证误差在一定范围内,又不占用空间,误差在0.81%左右
方案1、方案2、方案3
方案4
如果允许 存在 准确性问题,就使用 hyperloglog 存储架构
如果允许 存在 bigkey 问题,或者解决 bigkey 问题,就使用 方案3 存储架构
这里优先使用 hyperloglog 存储架构
通俗易懂的讲,Big Key就是某个key对应的value很大,占用的redis空间很大,本质上是大value问题。
key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。
redis中这些Big Key对应的value值很大,在序列化/反序列化过程中花费的时间很大,因此当我们操作Big Key时,通常比较耗时,这就可能导致redis发生阻塞,从而降低redis性能。
BigKey指以Key的大小和Key中成员的数量来综合判定,用几个实际的例子对大Key的特征进行描述:
Big Key对应的value较大,我们对其进行读写的时候,需要耗费较长的时间,这样就可能阻塞后续的请求处理。Redis的核心线程是单线程,单线程中请求任务的处理是串行的,前面的任务完不成,后面的任务就处理不了。
读取Big Key耗费的内存比正常Key会有所增大,如果不断变大,可能会引发 OOM(内存溢出),或达到redis的最大内存maxmemory设置值引发写阻塞或重要Key被逐出。
读取单value较大时会占用服务器网卡较多带宽,自身变慢的同时可能会影响该服务器上的其他Redis实例或者应用。
删除一个大Key造成主库较长时间的阻塞并引发同步中断或主从切换。
HyperLogLog 是大数据基数统计中的常见方法,无论是 Redis,Spark 还是 Flink 都提供了这个功能,其目的就是在一定的误差范围内,用最小的空间复杂度来估算一个数据流的基数。
这个最小的空间,小到什么程度呢?在 Redis 中实现的 HyperLogLog,只需要12K内存就能统计2^64个数据。不过有得必有失,HyperLogLog计数存在一定的误差,误差率整体较低。标准误差为 0.81% ,不到1%。当然,误差可以被设置辅助计算因子进行降低。
HyperLogLog 是一种概率性数据结构,它是 LogLog 算法的升级版,作用是能够提供不精确的去重计数。HyperLogLog 下面简称为HLL,
Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!作为代价,其测量结果是概率性的,有小于0.81%的误差。
HLL是一种用于基数统计的算法,可以用来估计一个集合中不同元素的数量,而不需要存储这些元素本身。它可以高效地处理大规模的数据集,且占用的空间非常小,通常只需要几千个字节的存储空间就可以处理数十亿个元素。HLL算法的实现非常简单,可以使用位图和随机哈希函数来实现。
HLL算法的基本思想是利用随机哈希函数将元素映射到一个二进制字符串中,并根据哈希函数的结果将字符串分为若干个桶。在每个桶中,记录哈希值的最大前导零的数量。
对于一个大的数据集,可以对每个元素进行哈希并记录其对应桶的最大前导零的数量,然后计算所有桶的平均值,得到一个近似的基数估计值。
HLL算法的误差率可以控制在0.81/sqrt(m)以内,其中m是桶的数量。
因此,随着桶的数量增加,误差率将变得越来越小。
HLL算法的优点在于它具有极低的内存消耗和高效的计算速度,并且可以处理极大的数据集。
优势是:非常均衡的特性,精准统计,可以得到每个统计对象的状态,秒出。
缺点是:当你的统计对象数量十分十分巨大时,可能会占用到一点存储空间,但也可在接受范围内。也可以通过分片,或者压缩的额外手段去解决。
注意:bitmap是精确的
优势是:
可以统计夸张到无法想象的数量,并且占用小的夸张的内存。
缺点是:
建立在牺牲准确率的基础上,而且无法得到每个统计对象的状态。
注意:HyperLogLog 不是精确的,误差在 1%左右
HyperLogLog算法主要用于基数统计场景,即需要快速统计一个数据集中不同元素的数量的场合。在实际应用中,HyperLogLog算法通常应用于以下场景:
在大数据场景下,需要去重的数据量非常大,如果使用传统的去重算法,需要对每个元素进行存储和比对,时间和空间消耗非常高。HyperLogLog算法可以在占用极小的空间的情况下,高效地对大规模数据进行去重,提高去重效率。
在Web应用和移动应用中,需要对用户的活跃度进行统计。如果每个用户的活跃度都进行存储,需要消耗大量的存储空间。HyperLogLog算法可以在占用极小的空间的情况下,高效地统计活跃用户数量,提高统计效率。
在Web应用中,需要统计网站每日独立访客数量(即UV),但是由于数据量非常大,不能简单地直接计数,因为会导致内存不足。HyperLogLog算法可以在占用极小的空间的情况下,高效地对大规模的访问日志进行去重和统计,提高统计效率。
在社交网络中,需要对用户的兴趣爱好进行统计,以便向用户推荐相关内容。HyperLogLog算法可以在占用极小的空间的情况下,高效地对用户行为进行去重和统计,提高推荐效率。
总之,HyperLogLog算法可以在需要高效处理大规模数据集的场景下发挥作用,适用于各种基数统计场合,可以提高数据处理效率,减少存储空间的消耗。
Redis提供了多个HyperLogLog相关的指令,包括以下几个:
PFADD key element [element ...]
PFADD hll_key a b c
PFCOUNT key [key ...]
PFCOUNT hll_key
PFMERGE destkey sourcekey [sourcekey ...]
PFMERGE hll_key1 hll_key2 hll_key3
这些指令可以方便地对HyperLogLog数据结构进行添加、统计和合并操作,以满足各种基数统计场景的需求。
需要注意的是,HyperLogLog数据结构虽然占用空间非常小,但是在添加元素时需要进行哈希计算,因此添加元素的效率可能会受到影响。
因此,在java代码中,可以使用 队列缓存+ 批量写入的架构,进行批量添加, 尽量减少io时间。
在使用HyperLogLog数据结构时,需要根据实际情况评估其效率和误差率,以确定是否适合使用HyperLogLog算法。
利用HyperLogLog实现网站UV统计 ,代码如下:
测试如下:
误差计算:
1百万,减去 99 6723 ,大概在 4000左右,不到1%
通过阻塞队列,使用队列缓存+批量写入的架构
这里用了阻塞队列,这个非常常用
具体的架构图如下:
所以阻塞队列的结构,以及底层原理,大家好好掌握
BlockingDeque<String> dauList = new LinkedBlockingDeque<>();
访问记录,直接进入到 阻塞队列,参考代码如下
异步写入的参考代码如下:
尼恩作为技术中台、数据中台的架构师, 高并发是尼恩架构的重点, 所以,后面会大家从 架构到实操, 多个维度、多种场景的高并发实操。
而且尼恩指导简历的过程中,也指导过小伙伴写过 10Wqps 超高并发 网关、超高并发秒杀、超高并发物联网等等简历,里边涉及到大量的设计模式,
经过尼恩的指导之后,很多 小伙伴的简历,里边立马金光闪闪,并且顺利走上了架构师之路。
后面有机会,带大家做一下这个高质量的实操,并且指导大家写入简历。
所以,这个材料后面也会进行持续迭代,大家可以在文末找尼恩获取最新版本和技术交流。
尼恩编著的400页PDF电子书 《SpringCloud Alibaba 技术圣经》
尼恩编著的500页 PDF电子书 《Java高并发核心编程 卷2 加强版》,清华大学出版社出版
《吃透8图1模板,人人可以做架构》
《10Wqps评论中台,如何架构?B站是这么做的!!!》
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
《100亿级订单怎么调度,来一个大厂的极品方案》
《2个大厂 100亿级 超大流量 红包 架构方案》
… 更多架构文章,正在添加中
《响应式圣经:10W字,实现Spring响应式编程自由》
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
《Spring cloud Alibaba 学习圣经》
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
《Linux命令大全:2W多字,一次实现Linux自由》
《TCP协议详解 (史上最全)》
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
《Redis分布式锁(图解 - 秒懂 - 史上最全)》
《Zookeeper 分布式锁 - 图解 - 秒懂》
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《缓存之王:Caffeine 的使用(史上最全)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》
4000页《尼恩Java面试宝典 》 40个专题
以上尼恩 架构笔记、面试题 的PDF文件更新,请到《技术自由圈》公号取↓↓↓