Практика Redis — кардинальность статистики: HyperLogLog

Redis

учить без конец территория , а также июнь общий нежелание .

Связанные серии

Введение

HyperLogLogдаRedisРасширенная структура данных в2^64data) для ведения кардинальной статистики (статистики дедупликации). Он характеризуется высокой скоростью и небольшими габаритами (12KB). Однако в расчетах будут ошибки, и стандартная ошибка равна0.81%.HyperLogLogОн только вычисляет количество элементов на основе входного элемента и не сохраняет сам входной элемент, поэтому он не может определить, существует ли уже данный элемент.

основные инструкции

pfadd(ключ,значение...)

Добавить указанный элемент вHyperLogLog, вы можете добавить несколько элементов

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

pfcount(ключ...)

вернуть данныйHyperLogLogбазовая оценка . При подсчете более одного за разHyperLogLog, необходимоHyperLogLogструктуры сравниваются и результат объединения помещается во временнуюHyperLogLog, производительность невысокая, используйте с осторожностью

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

pfmerge(ключ назначения, ключ источника...)

поставить несколькоHyperLogLogОбъединить и поместить результат объединения в указанный HyperLogLog

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

Проверка ошибок

Тест ошибок на основе SpringBoot, инициализация 5HyperLogLog, каждый случайным образом добавляя 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

настоящий бой

Например, для подсчета популярности статьи и количества эффективных кликов пользователей. Вы можете считать тепло через счетчик Рейса и выполнять его каждый раз, когда вы проситеincrинструкция. пройти черезHyperLogLogдля подсчета количества действительных пользователей.

Реализовать идеи

пройти черезАОП и пользовательские аннотацииДля подсчета статей, которые необходимо подсчитать:

  • Добавьте в интерфейс статьи аннотации, которые необходимо учитывать
  • Установить пользовательское значение аннотацииHyperLogLogсоответствующийkey
  • Установите pointcut AOP в качестве пользовательской аннотации
  • Получить значение аннотации в АОП
  • Информация о пользователе оценивается токеном или файлом cookie в AOP.
  • Совокупная популярность и количество пользователей

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 {

    /**
     * 值为对应HyperLogLog的key
     */
    String value() default "";
}

Определить АОП

@Aspect
@Component
public class ArticleAop {

    private static final String PV_PREFIX = "PV:";

    private static final String UV_PREFIX = "UV:";

    @Autowired
    private 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 {

    @Autowired
    private 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

ежедневные комплименты

Это не легко создать, если вы найдете это полезным,попросить лайкслужба поддержки


Поиск внимания

Публичный аккаунт WeChat: Ю Дасянь