学 无 止 境 ,与 君 共 勉 。

简介

HyperLogLogRedis中的高级数据结构,它主要用于对海量数据(可以统计2^64个数据)做基数统计(去重统计数量)。它的特点是速度快,占用空间小(12KB)。但是计算存会在误差,标准误差为0.81%HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以他并不能判断给定的元素是否已经存在了。

基本指令

pfadd(key,value…)

将指定的元素添加到HyperLogLog中,可以添加多个元素

    public void pfAdd(String key, String... value) {stringRedisTemplate.opsForHyperLogLog().add(key, value);}

pfcount(key…)

返回给定HyperLogLog的基数估算值。当一次统计多个HyperLogLog时,需要对多个HyperLogLog结构进行比较,并将并集的结果放入一个临时的HyperLogLog,性能不高,谨慎使用

    public Long pfCount(String... key) {return stringRedisTemplate.opsForHyperLogLog().size(key);}

pfmerge(destkey, sourcekey…)

将多个HyperLogLog进行合并,将并集的结果放入一个指定的HyperLogLog中

    public void pfMerge(String destKey, String... sourceKey) {stringRedisTemplate.opsForHyperLogLog().union(destKey, sourceKey);}

误差测试

基于SpringBoot的进行误差测试,初始化5个HyperLogLog,每个随机添加10000个元素,然后调用pfcount查看具体误差:

@RestController
@RequestMapping("/redis/hll")
public class HyperController {private final RedisService redisService;public HyperController(RedisService redisService) {this.redisService = redisService;}@GetMapping("/init")public String init() {for (int i = 0; i < 5; i++) {Thread thread = new Thread(() -> {String name = Thread.currentThread().getName();Random r = new Random();int begin = r.nextInt(100) * 10000;int end = begin + 10000;for (int j = begin; j < end; j++) {redisService.pfAdd("hhl:" + name, j + "");}System.out.printf("线程【%s】完成数据初始化,区间[%d, %d)\n", name, begin, end);},i + "");thread.start();}return "success";}@GetMapping("/count")public String count() {long a = redisService.pfCount("hhl:0");long b = redisService.pfCount("hhl:1");long c = redisService.pfCount("hhl:2");long d = redisService.pfCount("hhl:3");long e = redisService.pfCount("hhl:4");System.out.printf("hhl:0 -> count: %d, rate: %f\n", a, (10000 - a) * 1.00 / 100);System.out.printf("hhl:1 -> count: %d, rate: %f\n", b, (10000 - b) * 1.00 / 100);System.out.printf("hhl:2 -> count: %d, rate: %f\n", c, (10000 - c) * 1.00 / 100);System.out.printf("hhl:3 -> count: %d, rate: %f\n", d, (10000 - d) * 1.00 / 100);System.out.printf("hhl:4 -> count: %d, rate: %f\n", e, (10000 - e) * 1.00 / 100);return "success";}
}

初始化数据,调用接口:http://localhost:8080/redis/hll/init

线程【4】完成数据初始化,区间[570000, 580000)
线程【2】完成数据初始化,区间[70000, 80000)
线程【0】完成数据初始化,区间[670000, 680000)
线程【1】完成数据初始化,区间[210000, 220000)
线程【3】完成数据初始化,区间[230000, 240000)

查看具体统计数,计算误差:http://localhost:8080/redis/hll/count

hhl:0 -> count: 10079, rate: -0.790000
hhl:1 -> count: 9974, rate: 0.260000
hhl:2 -> count: 10018, rate: -0.180000
hhl:3 -> count: 10053, rate: -0.530000
hhl:4 -> count: 9985, rate: 0.150000

实战

比如要统计文章的热度和有效用户点击数。可以通过Reis的计数器来统计热度,每次请就执行incr指令。通过HyperLogLog来统计有效用户数。

实现思路

通过AOP和自定义注解来对需要统计的文章进行统计:

  • 在需要统计的文章接口上加上注解
  • 设置自定义注解值为HyperLogLog对应的key
  • 将AOP的切入点设为自定义注解
  • AOP中获取注解值
  • AOP中通过token或者cookie判断用户信息
  • 累计热度和用户量

pom

引入redisaop

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis Lettuce 模式 连接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

定义自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Article {/*** 值为对应HyperLogLog的key*/String value() default "";
}

定义AOP

@Aspect
@Component
public class ArticleAop {private static final String PV_PREFIX = "PV:";private static final String UV_PREFIX = "UV:";@Autowiredprivate RedisService redisService;/*** 定义切入点*/@Pointcut("@annotation(org.ylc.note.redis.hyperloglog.annotation.Article)")private void statistics() {}@Around("statistics()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// 获取注解Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();Article visitPermission = method.getAnnotation(Article.class);String value = visitPermission.value();// 获取请求信息ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 这里用来模拟,直接通过参数传入。实际项目中可以根据token或者cookie来实现String userId = request.getParameter("userId");// 热度redisService.incr(PV_PREFIX + value);// 用户量redisService.pfAdd(UV_PREFIX + value, userId);// 执行具体方法return proceedingJoinPoint.proceed();}
}

定义接口

在需要统计的接口上加上@Article()注解

@RestController
@RequestMapping("/redis/article")
public class ArticleController {@Autowiredprivate RedisService redisService;@Article("it")@GetMapping("/it")public String it(String userId) {String pv = redisService.get("PV:it");long uv = redisService.pfCount("UV:it");return String.format("当前用户:【%s】,当前it类热度:【%s】,访问用户数:【%d】", userId, pv, uv);}@Article("news")@GetMapping("/news")public String news(String userId) {String pv = redisService.get("PV:news");long uv = redisService.pfCount("UV:news");return String.format("当前用户:【%s】,当前news类热度:【%s】,访问用户数:【%d】", userId, pv, uv);}@GetMapping("/statistics")public Object statistics() {String pvIt = redisService.get("PV:it");long uvIt = redisService.pfCount("UV:it");String pvNews = redisService.get("PV:news");long uvNews = redisService.pfCount("UV:news");redisService.pfMerge("UV:merge", "UV:it", "UV:news");long uvMerge = redisService.pfCount("UV:merge");Map<String, String> result = new HashMap<>();result.put("it", String.format("it类热度:【%s】,访问用户数:【%d】;", pvIt, uvIt));result.put("news", String.format("news类热度:【%s】,访问用户数:【%d】", pvNews, uvNews));result.put("merge", String.format("合并后访问用户数:【%d】", uvMerge));return result;}
}

访问源码

所有代码均上传至Github上,方便大家访问

>>>>>> Redis实战 — HyperLogLog <<<<<<

日常求赞

创作不易,如果各位觉得有帮助,求点赞 支持


求关注

微信公众号: 俞大仙


Redis修行 — 基数统计:HyperLogLog相关推荐

  1. Redis基数统计——HyperLogLog小内存大用处

    2019独角兽企业重金招聘Python工程师标准>>> 摘自本人 http://irfen.me/redis-hyperloglog-intro/ 我们一直都知道,redis几大常用 ...

  2. Redis站点流量统计HyperLogLog

    在我们做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(即页面浏览量:page view),那么我们最常见的处理方式就是用户点击一次就插入一条数据到数据库,统计的 ...

  3. Redis(十):Redis特殊类型之Hyperloglog基数统计

    redis 2.8.9版本就更新了Hyperloglog数据结构! Hyperloglog:基数统计算法!0.81%的错误率,不过统计大量数据可以忽略! 在 Redis 里面,每个 HyperLogL ...

  4. 如何用redis做活跃用户统计-HyperLoglog

    原文在这里: 如何用redis做活跃用户统计-HyperLoglog 网站经常有这样的需求:统计日活用户数,有哪些实现方式呢? 第一种做法:用redis的set集合. 用户登录以后,把用户id添加到r ...

  5. 5redis-----------redis高级--GEO-查询附近的人、基数统计算法HLL 、布隆过滤器、缓存雪崩穿透击穿-------全栈式开发44

    redis高级 一.GEO查询附近的人 二.基数统计算法-HyperLogLog 三.布隆过滤器 四.缓存雪崩&缓存穿透 (一)缓存雪崩 (二)缓存穿透 (三)缓存击穿 一.GEO查询附近的人 ...

  6. Redis高级类型(统计全站访问量,日活跃用户)

    HyperLogLog(超级日志)(统计访客) 采用一种基数算法,用于完成独立总数的统计 占据空间小,无论统计多少个数据,只占12K的内存空间 不精确的统计算法,标准误差为0.81% Bitmap(位 ...

  7. 基数估算HyperLogLog

    HyperLogLog HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值: • 基数:集合中不同元素的数量.比如 {'apple', 'banana', 'cherry', ...

  8. 大聪明教你学Java | Spring Boot 整合 Redis 实现访问量统计

    前言 之前开发系统的时候客户提到了一个需求:需要统计某些页面的访问量,记得当时还纠结了一阵子,不知道怎么去实现这个功能,后来还是在大佬的带领下借助 Redis 实现了这个功能.今天又回想起了这件事,正 ...

  9. es cardinality cumulative_cardinality 基数统计

    es 版本 7.11 基数统计小笔记 基数统计的概念,可以先百度一下.然后再结合案例数据来理解.基本上是可以理解过来的. 我之前不管怎么看视频和文档,理解始终是 不怎么样,最终是通过自己来添加数据,才 ...

最新文章

  1. 关于Adodb.Stream的使用说明
  2. Linux之grep命令
  3. Vue.js-Day06-AM【项目实战(附带 完整项目源码)-day01-am:移动端响应式(响应式尺寸、视口问题、实现rem变化、rem设计)、实战项目搭建(初始化项目、处理rem、搭建路由)】
  4. Centos安装、配置nginx
  5. 用java求直角三角形的面积_JAVA 已知三角形的三个边判断 是否为直角三角形,如果是求面积!...
  6. vsftpd 启动不了vsftp 报错:config file not owned by correct useror not a file
  7. python堆排序算法_Python算法学习之堆和堆排序
  8. TensorFlow-谷歌深度学习库 数据读取器
  9. GPS从入门到放弃(十三) --- 接收机自主完好性监测(RAIM)
  10. Magento的主题Shaeng为网上时装店,配件商店,鞋专卖店创造
  11. java实现代理服务器
  12. java 仿易企秀_鲁班H5(开源可视化搭建系统, 可以理解为开源版本易企秀)核心实现原理解析...
  13. 阿昆同学的Java学习日记Day1
  14. C#在vs中吧数据流直接打印到txt文件
  15. 计算机网络——物理层2
  16. 【周志华机器学习】十四、概率图模型
  17. 微信小程序对接快递鸟接口返回格式有误的解决
  18. uniapp——解决checkBox组件无法全选无法选中的bug
  19. 未来创业的四种商业模式
  20. zdhadljaljdjadajdjald

热门文章

  1. DDN公司为日本最新人工智能基础设施ABGCI提供大容量存储解决方案
  2. android模拟器转方向,android开发 使用夜神模拟器 屏幕旋转问题
  3. 互联网职场中95后女程序员有哪些兴趣爱好?
  4. 二手车 电商+互联网金融的三种新玩法
  5. 【矩阵论】1.准备知识——复数域上的内积域正交阵
  6. DirectX、Direct3D、OpenGL的区别(DX、D3D、OpenGL)
  7. 马化腾对C语言的重视
  8. goLang 位左移
  9. 视频号扩展链接一键转换文章链接
  10. 为什么程序员找不到工作:4 个恐怖故事