Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
2024-8-8 17:25:16 Author: blog.axiaoxin.com(查看原文) 阅读量:2 收藏

在上一篇文章中,我们详细介绍了如何在 Golang 中使用 go-redis 操作 Redis 的 GEO 地理空间数据类型。如果你还没有阅读过,可以点击这里进行回顾。本篇文章,我们将深入探讨 Redis 中一个非常实用但相对不太为人所知的数据类型——HyperLogLog,以及如何在 Golang 中使用 go-redis 进行相关操作。

👉 点击查看 go-redis 使用指南目录

在《go-redis 使用指南》系列文章中,我们将详细介绍如何在 Golang 项目中使用 redis/go-redis 库与 Redis 进行交互。以下是该系列文章的全部内容:

  1. Golang 操作 Redis:快速上手 - go-redis 使用指南
  2. Golang 操作 Redis:连接设置与参数详解 - go-redis 使用指南
  3. Golang 操作 Redis:基础的字符串键值操作 - go-redis 使用指南
  4. Golang 操作 Redis:如何设置 key 的过期时间 - go-redis 使用指南
  5. Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南
  6. Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南
  7. Golang 操作 Redis:为 Hash 中的字段设置过期时间 - go-redis 使用指南
  8. Golang 操作 Redis:List 列表数据类型操作用法 - go-redis 使用指南
  9. Golang 操作 Redis:SortedSet 有序集合数据类型操作用法 - go-redis 使用指南
  10. Golang 操作 Redis:bitmap 数据类型操作用法 - go-redis 使用指南
  11. Golang 操作 Redis:事务处理操作用法 - go-redis 使用指南
  12. Golang 操作 Redis:地理空间数据类型操作用法 - go-redis 使用指南
  13. Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
  14. Golang 操作 Redis:Pipeline 操作用法 - go-redis 使用指南

Redis HyperLogLog 简介

HyperLogLog 是一种概率数据结构,用于估算基数(即去重后元素的数量)。它在提供极小空间消耗的同时,能够在一定误差范围内高效地计算基数。相较于传统的计数数据结构,HyperLogLog 能在处理海量数据时保持极低的内存消耗。

什么是基数估算

基数就是指一个集合中不同值的数目,比如 a, b, c, d 的基数就是 4,a, b, c, d, a 的基数还是 4。虽然 a 出现两次,只会被计算一次。

基数估算指的是对一个集合中唯一元素数量的估算。传统的计算方法往往需要记录每个元素,从而消耗大量内存。而 HyperLogLog 则通过概率算法提供了一种高效的估算方法,使其能够在有限的内存中处理海量数据。

HyperLogLog 算法基本实现原理

HyperLogLog 算法基于哈希函数和桶(或称寄存器)的概念。其基本实现原理如下:

  1. 哈希化:将每个输入元素通过哈希函数映射为一个哈希值。
  2. 桶划分:将哈希值的前若干位用于确定桶的位置。桶的数量是固定的,通常是 2 的幂次方。
  3. 桶计数:对于每个桶,记录哈希值后若干位的最大前导零位数。这个值用来估算桶中元素的数量。
  4. 基数估算:通过对所有桶的前导零位数进行统计,并使用特定的算法将结果转换为基数的估算值。

HyperLogLog 的核心优势在于其在计算基数时只需固定的内存空间,且随着数据量的增加,内存使用不会显著增加。

HyperLogLog 的应用场景

HyperLogLog 主要的应用场景就是进行基数统计,比如:

  • 网站独立用户统计:统计某一时间段内访问网站的独立用户数量。
  • 日志分析:分析日志中的独特 IP 地址数量,评估独立访客数。
  • 社交网络分析:统计社交网络中独特用户的互动数,例如点赞、评论等。

HyperLogLog 与其他数据结构的差别

使用 Redis 统计集合的基数一般有三种方法,分别是使用 Redis 的 Set, HashMap,BitMap 和 HyperLogLog。前面几个数据结构在集合的数量级增长时,所消耗的内存会大大增加,但是 HyperLogLog 则不会。

Redis 的 HyperLogLog 通过牺牲准确率来减少内存空间的消耗,只需要 12K 内存,在标准误差 0.81%的前提下,能够统计 2^64 个数据。所以 HyperLogLog 是否适合在比如统计日活月活此类的对精度要不不高的场景。

  • Set:Redis 的 Set 数据结构用于存储唯一元素,但在存储大量元素时会消耗大量内存。HyperLogLog 则通过概率算法来大幅减少内存消耗,即使在存储数百万级别的元素时,也能保持低内存使用。
  • HashMap:HashMap 用于存储键值对,并且可以有效地统计唯一键的数量。然而,HashMap 也需要大量内存来存储数据,而 HyperLogLog 则可以在固定的内存空间中完成基数估算。
  • Bitmap:Bitmap 是一种位图数据结构,适用于处理稀疏数据的基数统计,但对于大规模数据的处理不如 HyperLogLog 高效。Bitmap 通常在处理小范围的数据时较为有效,而 HyperLogLog 在处理大规模数据时表现更优。
  • HyperLogLog:存在一定误差,占用内存少,稳定占用 12k 左右内存,可以统计 2^64 个元素

go-redis 中 HyperLogLog 操作的方法

以下是 go-redis 提供的 HyperLogLog 操作方法及其功能描述:

  • PFAdd - 将指定元素添加到 HyperLogLog
  • PFCount - 返回给定 HyperLogLog 的基数估算值
  • PFMerge - 将多个 HyperLogLog 合并为一个

go-redis HyperLogLog 操作方法详细讲解及示例代码

PFAdd

将指定元素添加到 HyperLogLog 中。如果 HyperLogLog 不存在,将创建一个新的。

方法签名

PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd

参数说明

  • ctx:上下文,用于控制请求的生命周期。
  • key:HyperLogLog 的键名。
  • els:要添加的元素,可以是一个或多个。

返回结果说明

  • 返回一个 *IntCmd,结果为 1 表示 HyperLogLog 被修改,0 表示没有修改。

示例代码

package main

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
)

func main() {
	ctx := context.Background()
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	key := "hll_example"
	// 添加元素到 HyperLogLog
	result, err := rdb.PFAdd(ctx, key, "user1", "user2", "user3").Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("PFAdd result: %d\n", result)
    // 输出:PFAdd result: 1
}

PFCount

返回给定 HyperLogLog 的基数估算值。

方法签名

PFCount(ctx context.Context, keys ...string) *IntCmd

参数说明

  • ctx:上下文,用于控制请求的生命周期。
  • keys:一个或多个 HyperLogLog 的键名。

返回结果说明

  • 返回一个 *IntCmd,结果为基数估算值。

示例代码

package main

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
)

func main() {
	ctx := context.Background()
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	key := "hll_example"
	// 获取 HyperLogLog 的基数估算值
	count, err := rdb.PFCount(ctx, key).Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("PFCount result: %d\n", count)
    // 输出:PFCount result: 3
}

PFMerge

将多个 HyperLogLog 合并为一个。

方法签名

PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd

参数说明

  • ctx:上下文,用于控制请求的生命周期。
  • dest:目标 HyperLogLog 的键名。
  • keys:要合并的 HyperLogLog 的键名列表。

返回结果说明

  • 返回一个 *StatusCmd,表示合并操作的状态。

示例代码

package main

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
)

func main() {
	ctx := context.Background()
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	key1 := "hll_example1"
	key2 := "hll_example2"
	destKey := "hll_merged"
	// 添加元素到不同的 HyperLogLog
	rdb.PFAdd(ctx, key1, "user1", "user2")
	rdb.PFAdd(ctx, key2, "user3", "user4")

	// 合并两个 HyperLogLog
	status, err := rdb.PFMerge(ctx, destKey, key1, key2).Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("PFMerge status: %s\n", status)
    // 输出:PFMerge status: OK

	// 获取合并后的 HyperLogLog 的基数估算值
	count, err := rdb.PFCount(ctx, destKey).Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("Merged PFCount result: %d\n", count)
    // 输出:Merged PFCount result: 4
}

完整示例:使用 HyperLogLog 实现网站独立用户统计

假设你在一个网站上统计每天的独立访客数。为了优化内存使用,你选择使用 Redis 的 HyperLogLog 数据结构。这个示例包含以下功能:

  1. 记录每日独立访客:使用 PFAdd 将每日访问的用户添加到 HyperLogLog 中。
  2. 计算独立访客数:使用 PFCount 获取每天的独立用户数量。
  3. 合并统计数据:使用 PFMerge 合并来自不同时间段的独立访客统计数据。

示例代码:

package main

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"time"
)

func main() {
	ctx := context.Background()
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	// 示例数据
	days := []string{"2024-08-01", "2024-08-02", "2024-08-03"}
	usersPerDay := map[string][]string{
		"2024-08-01": {"user1", "user2", "user3"},
		"2024-08-02": {"user2", "user3", "user4"},
		"2024-08-03": {"user3", "user4", "user5"},
	}

	// 记录每日独立访客
	for _, day := range days {
		key := "hll:" + day
		users := usersPerDay[day]
		_, err := rdb.PFAdd(ctx, key, users).Result()
		if err != nil {
			fmt.Printf("PFAdd error: %v\n", err)
			return
		}
		fmt.Printf("Added users for %s\n", day)
	}

	// 计算每一天的独立访客数
	for _, day := range days {
		key := "hll:" + day
		count, err := rdb.PFCount(ctx, key).Result()
		if err != nil {
			fmt.Printf("PFCount error: %v\n", err)
			return
		}
		fmt.Printf("Unique visitors on %s: %d\n", day, count)
	}

	// 合并统计数据
	destKey := "hll:merged"
	keys := []string{}
	for _, day := range days {
		keys = append(keys, "hll:"+day)
	}

	_, err := rdb.PFMerge(ctx, destKey, keys...).Result()
	if err != nil {
		fmt.Printf("PFMerge error: %v\n", err)
		return
	}
	fmt.Println("Merged HyperLogLog keys")

	// 获取合并后的独立访客数
	totalCount, err := rdb.PFCount(ctx, destKey).Result()
	if err != nil {
		fmt.Printf("PFCount (merged) error: %v\n", err)
		return
	}
	fmt.Printf("Total unique visitors: %d\n", totalCount)
}

运行输出:

Added users for 2024-08-01
Added users for 2024-08-02
Added users for 2024-08-03
Unique visitors on 2024-08-01: 3
Unique visitors on 2024-08-02: 3
Unique visitors on 2024-08-03: 3
Merged HyperLogLog keys
Total unique visitors: 5

结语

通过本文的详细介绍,我们不仅学习了 HyperLogLog 数据结构的基本原理和实际应用,还掌握了在 Golang 中使用 go-redis 进行 HyperLogLog 操作的方法。HyperLogLog 在处理大规模数据时提供了高效的基数估算,并能显著减少内存消耗。希望这篇文章对你有所帮助!点击 go-redis 使用指南 可查看更多相关教程!如果你有任何问题或建议,欢迎在评论区留言!


文章来源: https://blog.axiaoxin.com/post/go-redis-hll/
如有侵权请联系:admin#unsafe.sh