日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

Redis修行 — 基数统计:HyperLogLog

發(fā)布時(shí)間:2023/12/14 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis修行 — 基数统计:HyperLogLog 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

學(xué) 無(wú)

簡(jiǎn)介

HyperLogLog是Redis中的高級(jí)數(shù)據(jù)結(jié)構(gòu),它主要用于對(duì)海量數(shù)據(jù)(可以統(tǒng)計(jì)2^64個(gè)數(shù)據(jù))做基數(shù)統(tǒng)計(jì)(去重統(tǒng)計(jì)數(shù)量)。它的特點(diǎn)是速度快,占用空間小(12KB)。但是計(jì)算存會(huì)在誤差,標(biāo)準(zhǔn)誤差為0.81%。HyperLogLog只會(huì)根據(jù)輸入元素來(lái)計(jì)算基數(shù),而不會(huì)儲(chǔ)存輸入元素本身,所以他并不能判斷給定的元素是否已經(jīng)存在了。

基本指令

pfadd(key,value…)

將指定的元素添加到HyperLogLog中,可以添加多個(gè)元素

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

pfcount(key…)

返回給定HyperLogLog的基數(shù)估算值。當(dāng)一次統(tǒng)計(jì)多個(gè)HyperLogLog時(shí),需要對(duì)多個(gè)HyperLogLog結(jié)構(gòu)進(jìn)行比較,并將并集的結(jié)果放入一個(gè)臨時(shí)的HyperLogLog,性能不高,謹(jǐn)慎使用

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

pfmerge(destkey, sourcekey…)

將多個(gè)HyperLogLog進(jìn)行合并,將并集的結(jié)果放入一個(gè)指定的HyperLogLog中

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

誤差測(cè)試

基于SpringBoot的進(jìn)行誤差測(cè)試,初始化5個(gè)HyperLogLog,每個(gè)隨機(jī)添加10000個(gè)元素,然后調(diào)用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】完成數(shù)據(jù)初始化,區(qū)間[%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";} }

初始化數(shù)據(jù),調(diào)用接口:http://localhost:8080/redis/hll/init

線程【4】完成數(shù)據(jù)初始化,區(qū)間[570000, 580000) 線程【2】完成數(shù)據(jù)初始化,區(qū)間[70000, 80000) 線程【0】完成數(shù)據(jù)初始化,區(qū)間[670000, 680000) 線程【1】完成數(shù)據(jù)初始化,區(qū)間[210000, 220000) 線程【3】完成數(shù)據(jù)初始化,區(qū)間[230000, 240000)

查看具體統(tǒng)計(jì)數(shù),計(jì)算誤差: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

實(shí)戰(zhàn)

比如要統(tǒng)計(jì)文章的熱度和有效用戶點(diǎn)擊數(shù)。可以通過(guò)Reis的計(jì)數(shù)器來(lái)統(tǒng)計(jì)熱度,每次請(qǐng)就執(zhí)行incr指令。通過(guò)HyperLogLog來(lái)統(tǒng)計(jì)有效用戶數(shù)。

實(shí)現(xiàn)思路

通過(guò)AOP和自定義注解來(lái)對(duì)需要統(tǒng)計(jì)的文章進(jìn)行統(tǒng)計(jì):

  • 在需要統(tǒng)計(jì)的文章接口上加上注解
  • 設(shè)置自定義注解值為HyperLogLog對(duì)應(yīng)的key
  • 將AOP的切入點(diǎn)設(shè)為自定義注解
  • AOP中獲取注解值
  • AOP中通過(guò)token或者cookie判斷用戶信息
  • 累計(jì)熱度和用戶量

pom

引入redis和aop

<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 {/*** 值為對(duì)應(yīng)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;/*** 定義切入點(diǎn)*/@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();// 獲取請(qǐng)求信息ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 這里用來(lái)模擬,直接通過(guò)參數(shù)傳入。實(shí)際項(xiàng)目中可以根據(jù)token或者cookie來(lái)實(shí)現(xiàn)String userId = request.getParameter("userId");// 熱度redisService.incr(PV_PREFIX + value);// 用戶量redisService.pfAdd(UV_PREFIX + value, userId);// 執(zhí)行具體方法return proceedingJoinPoint.proceed();} }

定義接口

在需要統(tǒng)計(jì)的接口上加上@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("當(dāng)前用戶:【%s】,當(dāng)前it類熱度:【%s】,訪問(wèn)用戶數(shù):【%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("當(dāng)前用戶:【%s】,當(dāng)前news類熱度:【%s】,訪問(wèn)用戶數(shù):【%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】,訪問(wèn)用戶數(shù):【%d】;", pvIt, uvIt));result.put("news", String.format("news類熱度:【%s】,訪問(wèn)用戶數(shù):【%d】", pvNews, uvNews));result.put("merge", String.format("合并后訪問(wèn)用戶數(shù):【%d】", uvMerge));return result;} }

訪問(wèn)源碼

所有代碼均上傳至Github上,方便大家訪問(wèn)

>>>>>> Redis實(shí)戰(zhàn) — HyperLogLog <<<<<<

日常求贊

創(chuàng)作不易,如果各位覺(jué)得有幫助,求點(diǎn)贊 支持


求關(guān)注

微信公眾號(hào): 俞大仙


總結(jié)

以上是生活随笔為你收集整理的Redis修行 — 基数统计:HyperLogLog的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。