技术实现
请求首页数据的时候,会自动捕捉请求ip,并将ip存入HyperLogLog中用于统计总的用户访问量,将每日的访问次数以日期为key,访问次数为value存入redis的hashmap中,并每日定时将这两个数据持久化到数据库中。
HyperLogLog是用来做基数统计的算法,它提供不精确的去重计数方案(这个不精确并不是非常不精确),标准误差是0.81%,对于UV这种统计来说这样的误差范围是被允许的。HyperLogLog的优点在于,输入元素的数量或者体积非常大时,基数计算的存储空间是固定的。在Redis中,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同的基数。
但是:HyperLogLog只能统计基数的大小(也就是数据集的大小,集合的个数),他不能存储元素的本身,不能向set集合那样存储元素本身,也就是说无法返回元素,满足我们统计pv的需求
HyperLogLog基本思想
HLL 的基本思想是利用集合中数字比特串中第一个 1 出 现位置的最大值来预估整体基数,但是这种预估方法存在较 大误差。因此,为了改善误差情况,在 HLL 中引入分桶平均的概念。 分桶平均的基本原理是将统计数据划分为 m 个桶,每 个桶分别统计各自 kmax 并能得到各自的基数预估值 Hi ,最终对这些 Hi 求调和平均得到整体的基数估计值,从而消减因偶然性带来的误差。调和平均数的优点是可以过滤掉不健康的统计值,计算公式为:
其中 m 是桶的数量,const 是修正参数,它的取值会随 m 的变化而变化。 Rj 为第j个桶中最大连续零的数量,2 Rj 为每个桶的估计 值,括号内的部分为m个桶的调和平均数。 关于分桶大小的确定方法:很显然分桶数m只能是 2 的 整数次幂。如果分桶越多,那么估计的精度就会越高,统计 学上用来衡量估计精度的一个指标是“相对标准误差”(RSD), 从直观上理解,RSD 的值其实就是每次估计的值在估计均值 上下的波动占估计均值的比例。当我们确定一个目标 RSD 值, 分桶数m也就随之确定了,RSD 的值与分桶数 m 存在如下 的计算关系:
Redis中HyperLogLog原理:
它在redis的实现中,设有 16384 个桶,即:2^14 = 16384,每个桶有 6 位,每个桶可以表达的最大数字是:2^5+2^4+…+1 = 63 ,二进制为: 111 111 。
对于命令:pfadd key value
在存入时,value 会被 hash 成 64 位,即 64 bit 的比特字符串,前 14 位用来分桶,前 14 位的二进制转为 10 进制就是桶标号。
之所以选 14位 来表达桶编号是因为,分了 16384 个桶,而 2^14 = 16384,刚好地,最大的时候可以把桶利用完,不造成浪费。假设一个字符串的前 14 位是:00 0000 0000 0010,其十进制值为 2。那么 index 将会被转化后放到编号为 2 的桶。
index 的转化规则:
首先因为完整的 value 比特字符串是 64 位形式,减去 14 后,剩下 50 位,那么极端情况,出现 1 的位置,是在第 50 位,即位置是 50。此时 index = 50。此时先将 index 转为 2 进制,它是:110010 。
因为16384 个桶中,每个桶是 6 bit 组成的。刚好 110010 就被设置到了第 2 号桶中去了。请注意,50 已经是最坏的情况,且它都被容纳进去了。那么其他的不用想也肯定能被容纳进去
根据上面的做法,不同的 value,会被设置到不同桶中去,如果出现了在同一个桶的,即前 14 位值是一样的,但是后面出现 1 的位置不一样。那么比较原来的 index 是否比新 index 大。是,则替换。否,则不变。
最终地,一个 key 所对应的 16384 个桶都设置了很多的 value 了,每个桶有一个k_max。此时调用 pfcount 时,按照前面介绍的估算方式,便可以计算出 key 的设置了多少次 value,也就是统计值。
value 被转为 64 位的比特串,最终被按照上面的做法记录到每个桶中去。64 位转为十进制就是:2^64,HyperLogLog 仅用了:16384 * 6 /8 / 1024 K 存储空间就能统计多达 2^64 个数。
实现步骤
- 访问首页时对接口进行拦截,并进行权限校验,无权限则提示请登录
- 获取请求ip,并存入redis的HyperLogLog中,并对访问次数加一
- 如果获取不到key,则自动生成新的key存入Redis
- 从Redis和MySQL中获取首页的相关数据,返回给前端
- 如果Redis中的key失效了,或者不存在,会去MySQL中去获取,并重新缓存一份数据到Redis中
- Redis会定时将数据持久化到MySQL中
相关的key值和数据结构
key | 数据结构 | 返回样式(JSON) |
---|---|---|
PageCount | HyperLogLog | { “PageCount”:200 } |
DailyCount | HashMap(每日时间为key,访问量为value,结构为String,Int) | { “DailyCount”: { “20220512”:20, “20220513”:20, “20220514”:20, “20220515”:20 } } |
参考:https://juejin.cn/post/6844903785744056333#heading-11
参考文献 :利用HyperLogLog基数估法进行DDoS攻击预警 汤琛 中国电信湖北业务支撑中心数据运营与稽核部