redis数据类型之HyperLogLog,Geospatial

HyperLogLog

  • 在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PVPageView页面访问量),可以使用Redis的incr、incrby轻松实现。

  • 但像UVUniqueVisitor,独立访客)、独立IP数搜索记录数等需要去重计数的问题如何解决?这种求集合中不重复元素个数问题称为基数问题

  • 解决基数问题有很多种方案:

    • 数据存储在MySQL表中,使用distinct count计算不重复个数
    • 使用Redis提供的hash、set、bitmaps等数据结构来处理
  • 以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

  • 为了能够降低一定的精度来平衡存储空间,Redis推出了HyperLogLog

  • HyperLogLog 是用来做基数统计的算法HyperLogLog优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

  • Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

  • 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

  • 比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集基数集为 {1, 3, 5 ,7, 8}基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

HyperLogLog介绍

Redis中,HyperLogLog(简称HLL)是一个用于统计大量数据中不同元素(基数)数量的概率性数据结构。以下是关于RedisHyperLogLog的详细解释:

基本原理

  1. 哈希函数HyperLogLog使用一个强散列函数将输入的元素映射为固定长度的二进制串。
  2. 位前导零统计:对每个元素经过哈希后的二进制串,统计从最高位开始连续零的个数(即前导零个数)。这个值反映了元素哈希值的稀有程度,间接表示了元素的独特性。
  3. 存储与计数Redis中的HyperLogLog结构内部维护了一个大小固定的桶数组,默认大小为2^14 = 16384个桶。每个桶用于存储对应的元素哈希值所观察到的最大前导零个数。当添加新的元素时,它会被哈希并找到对应的桶来更新该桶中的最大前导零计数值。
  4. 基数估算:利用统计的所有桶中最长的前导零序列,通过预定义的公式计算出一个近似的基数(唯一元素数量),这个估算值通常会非常接近实际基数,但不是精确值。标准误差大约是0.81%,这意味着对于大量数据,HyperLogLog能够以相对较小的误差估计基数。

主要特点

  1. 高效的内存使用:即使可以处理多达2^64(约18亿)个不同的元素,Redis中单个HyperLogLog键只需要大约12KB的固定内存空间。
  2. 概率估计HyperLogLog提供的结果是概率性的,而不是精确的基数计数。但由于其误差范围较小,通常在实际应用中这个误差是可接受的。
  3. 高速计算HyperLogLog可以在常量时间内计算估计的基数,无论集合的大小如何。

应用场景

  1. 网站UV统计:用于统计网站的独立访客数、独立IP数等,避免使用传统的去重方法会消耗大量的内存和时间。
  2. 数据流量分析:对数据流量中的独立元素进行统计,例如分析用户在某个时间段内访问的不同页面数、点击不同广告的用户数等。
  3. 数据去重:对重复数据进行去重,节省存储空间。
  4. 数据分布估计:估计数据集的分布情况,例如估计某个关键词在搜索引擎中的热度。

合并操作

HyperLogLog还支持多个集合的合并操作pfmerge命令),允许将多个HyperLogLog键合并成一个新的键,同时正确估算所有源键包含的唯一元素总数,这对于分布式环境下的基数统计尤为有用。

相关命令

命令语法描述
pfadd key element1 [element2...]添加指定元素HyperLogLog
pfcount key1 [key2...]返回给定HyperLogLog的基数估算值
pfmerge destkey sourcekey1 [sourcekey2...]将多个HyperLogLog 合并为一个 HyperLogLog

pfadd key element1 [element2...]

  • PFADDRedis 中的一个命令,它用于将一个或多个元素添加到指定的 HyperLogLog 集合中。如果 HyperLogLog 集合不存在,PFADD 会创建一个新的集合

命令的语法如下:

PFADD key element1 [element2 ...]
  • key 是你希望操作的 HyperLogLog 集合的键名
  • element1 [element2 ...] 是你想要添加到集合中的一个或多个元素。

PFADD 命令执行后,它会更新 HyperLogLog 集合以包含所有指定的元素,并返回 1(如果至少有一个元素被添加到集合中)或 0(如果所有元素都已经在集合中)。

这里有一个简单的例子:

# 假设我们有一个空的 HyperLogLog 集合名为 "myHyperLogLog"

# 使用 PFADD 命令添加一些元素
redis> PFADD myHyperLogLog user1 user2 user3
(integer) 1

# 尝试再次添加相同的元素,但 PFADD 不会返回错误,它只返回是否至少添加了一个新元素
redis> PFADD myHyperLogLog user1 user4
(integer) 1

# 使用 PFCOUNT 命令查看集合中的唯一元素数量(这是一个估计值)
redis> PFCOUNT myHyperLogLog
(integer) 4

# 注意:PFCOUNT 返回的是估计值,而不是精确值

请注意,尽管 HyperLogLog 可以为大量数据提供非常准确的基数估计,但它仍然是一个概率数据结构,因此返回的计数是一个近似值,而不是精确值。然而,这个误差范围通常很小,足以满足大多数应用的需求。

pfcount key1 [key2...]

  • PFCOUNTRedis 中的一个命令,用于获取一个或多个 HyperLogLog 集合的基数估计值(即集合中不同元素的近似数量)。如果指定了多个键PFCOUNT 会将它们视为一个单独HyperLogLog 集合,并返回这些集合的并集基数估计值。

命令的语法如下:

PFCOUNT key1 [key2 ...]
  • key1 [key2 ...] 是你想要获取基数估计值的 HyperLogLog 集合的键名。

PFCOUNT 命令执行后,它会返回一个整数,表示指定 HyperLogLog 集合(或集合的并集)中不同元素的近似数量。

这里有一个简单的例子:

# 假设我们有两个 HyperLogLog 集合,分别名为 "users:set1" 和 "users:set2"

# 使用 PFADD 命令向 "users:set1" 添加一些元素
redis> PFADD users:set1 user1 user2 user3
(integer) 1

# 使用 PFADD 命令向 "users:set2" 添加一些元素,包括与 "users:set1" 相同的元素和新的元素
redis> PFADD users:set2 user2 user3 user4 user5
(integer) 1

# 使用 PFCOUNT 命令分别获取两个集合的基数估计值
redis> PFCOUNT users:set1
(integer) 3
redis> PFCOUNT users:set2
(integer) 4

# 使用 PFCOUNT 命令获取两个集合并集的基数估计值
redis> PFCOUNT users:set1 users:set2
(integer) 5

# 注意:PFCOUNT 返回的是估计值,而不是精确值

在上面的例子中,虽然 “users:set1” 和 “users:set2” 分别有 3 个和4 个不同的元素,但它们的并集只有 5 个不同的元素(因为 “user2” 和 “user3” 在两个集合中都出现了)。PFCOUNT 命令能够正确地返回这个并集的基数估计值。

pfmerge destkey sourcekey1 [sourcekey2...]

  • PFMERGERedis 中的一个命令,它用于将多个 HyperLogLog 集合合并为一个新的 HyperLogLog 集合。这个新的集合会包含所有源集合中的不同元素,但是它会消耗额外的内存空间,并且只能用于进一步执行基数估计操作,而不能直接添加新的元素。

命令的语法如下:

PFMERGE destkey sourcekey1 [sourcekey2 ...]
  • destkey 是合并后新的 HyperLogLog 集合的键名。
  • sourcekey1 [sourcekey2 ...] 是你想要合并的源 HyperLogLog 集合的键名。

PFMERGE 命令执行后,它会创建一个新的 HyperLogLog 集合(如果 destkey 不存在的话),并将所有源集合中的不同元素合并到这个新的集合中。然后,你可以使用 PFCOUNT 命令来获取这个新集合的基数估计值。

这里有一个简单的例子:

# 假设我们有两个 HyperLogLog 集合,分别名为 "users:set1" 和 "users:set2"

# 使用 PFADD 命令向 "users:set1" 添加一些元素
redis> PFADD users:set1 user1 user2 user3
(integer) 1

# 使用 PFADD 命令向 "users:set2" 添加一些元素,包括与 "users:set1" 相同的元素和新的元素
redis> PFADD users:set2 user2 user3 user4 user5
(integer) 1

# 使用 PFMERGE 命令将两个集合合并为一个新的集合 "users:merged"
redis> PFMERGE users:merged users:set1 users:set2
OK

# 使用 PFCOUNT 命令获取合并后集合的基数估计值
redis> PFCOUNT users:merged
(integer) 5

# 注意:PFMERGE 命令不会返回任何值(除了 OK),你需要使用 PFCOUNT 来获取结果
# 并且 "users:merged" 是一个新的集合,你可以继续用它进行其他操作,但不能向它添加新元素

在上面的例子中,“users:set1” 和 “users:set2” 分别有 3 个和 4 个不同的元素,但是它们的并集只有 5 个不同的元素(因为 “user2” 和 “user3” 在两个集合中都出现了)。PFMERGE 命令能够创建一个新的集合 “users:merged”,它包含了这两个源集合中的所有不同元素,并且 PFCOUNT 命令能够正确地返回这个新集合的基数估计值。

Geospatial

  • Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

Geospatial介绍

Redis中的Geospatial地理位置)功能是一个强大的工具,主要用于存储和查询地理位置信息。以下是关于Redis Geospatial的详细介绍:

  1. 版本引入

    • Redis在3.2版本中引入了Geospatial数据类型,允许用户存储和查询与地理位置相关的数据。
  2. 数据结构

    • Geospatial功能并没有引入新的底层数据结构,而是直接使用了Sorted Set(有序集合)来实现。
    • Sorted Set中,地理位置的经度纬度被编码为分数,而成员信息(如用户ID、商铺名称等)则作为元素存储。
  3. 常用命令

    • GEOADD:将指定的地理位置(经度、纬度、名称)添加到指定的key中。
    • GEOPOS:返回指定地理位置的经纬度。
    • GEODIST:计算两个地理位置之间的距离。
    • GEORADIUS:根据给定的经纬度查询指定半径内的地理位置。
    • GEORADIUSBYMEMBER:根据给定的成员(如用户ID、商铺名称)查询指定半径内的地理位置。
  4. 应用场景

    • LBS(基于位置的服务):如搜索附近的餐馆、叫车等。
    • 智能推荐:基于用户的地理位置推荐相关内容或产品。
    • 出行规划:计算两地之间的距离,规划最优路线。
  5. 数据存储

    • 使用GEOADD命令时,可以将经纬度编码为分数,并存入zset中。例如,使用ZADD city_geo_location 116.4074 39.9042 "北京"可以将北京的经纬度信息添加到city_geo_location这个key中。
  6. 性能

    • 由于Geospatial功能是基于Sorted Set实现的,因此它具有很高的查询效率。
    • 在存储和查询大量地理位置数据时,RedisGeospatial功能能够提供快速且可靠的性能。
  7. 注意事项

    • 在实际开发中,需要注意数据的准确性和完整性,确保存储的地理位置信息准确无误。
    • 同时,也需要考虑查询的性能和效率,避免对系统造成过大的压力。

相关命令

命令语法描述
geoadd key longitude latitude member [longitude latitude member...]添加地理位置(经度,纬度,名称)
geopos key member [member...]获得指定地区的坐标值
geodist key member1 member2 [m|km|ft|mi]获取两个位置之间的直线距离
georadius key longitude latitude radius [m|km|ft|mi]以给定的经纬度为中心,找出某一半径内的元素
georadiusbymember key member radius [m|km|ft|mi]以给定某一成员为中心,找出某一半径内的元素

geoadd key longitude latitude member [longitude latitude member...]

是的,GEOADDRedis 中用于向键(key)添加地理位置信息的命令。该命令将一个或多个地理位置(经度和纬度)以及与之关联的成员(member)添加到指定的键中。每个键都是一个地理位置集合。

命令的语法如下:

GEOADD key longitude latitude member [longitude latitude member ...]
  • key:指定存储地理位置信息的键名。
  • longitude:指定地理位置的经度。
  • latitude:指定地理位置的纬度。
  • member:与经纬度关联的成员名称,可以是任何字符串。

你可以在 GEOADD 命令后跟上多组经纬度和成员,一次性添加多个地理位置。

下面是一个示例:

# 添加北京的地理位置
GEOADD cities 116.4074 39.9042 "beijing"

# 添加上海和深圳的地理位置(一次性添加多个)
GEOADD cities 121.4737 31.2304 "shanghai" 114.0578 22.5433 "shenzhen"

在上面的示例中,cities 是存储地理位置信息的键名,后面的参数是多个经纬度对和与之关联的成员名称。

一旦你添加了地理位置信息,你就可以使用其他 Geospatial 命令(如 GEOPOSGEODISTGEORADIUSGEORADIUSBYMEMBERGEOHASH)来查询和操作这些信息了。

geopos key member [member...]

  • GEOPOSRedis 中的一个命令,用于获取一个或多个地理位置的经度纬度。它接受一个键(key)和一个或多个成员(member)作为参数,并返回与这些成员关联的经纬度。

命令的语法如下:

GEOPOS key member [member ...]
  • key:包含地理位置信息的键名。
  • member:与地理位置关联的成员名称。

命令返回一个数组,其中包含每个请求成员的经纬度。如果成员不存在,那么它的位置将被表示为 [nil nil]

下面是一个示例:

# 假设我们已经使用 GEOADD 命令添加了北京和上海的位置信息
# GEOADD cities 116.4074 39.9042 "beijing" 121.4737 31.2304 "shanghai"

# 使用 GEOPOS 命令获取北京和上海的经纬度
GEOPOS cities "beijing" "shanghai"

# 可能的输出(取决于 Redis 的版本和格式设置)
# 1) 1) "116.40739999999998"
#    2) "39.90419999999999"
# 2) 1) "121.47370000000002"
#    2) "31.230399999999996"

在上面的示例中,GEOPOS 命令返回了两个数组,分别对应于北京和上海的经纬度。每个数组中的第一个元素是经度,第二个元素是纬度。如果请求的成员在键中不存在,那么它的位置将被表示为 [nil nil]

请注意,返回的经纬度可能有一些微小的精度损失,因为 Redis 内部使用 52 位精度来存储经纬度信息。这通常对于大多数应用来说是足够的,但如果你需要更高的精度,可能需要考虑其他解决方案。

geodist key member1 member2 [unit]

是的,GEODIST 是 Redis 中用于计算两个地理位置之间距离的命令。这个命令接受一个键(key)和两个成员(member1 和 member2)作为参数,并返回这两个成员之间的距离。

命令的语法如下:

GEODIST key member1 member2 [unit]
  • key:包含地理位置信息的键名。
  • member1member2:与地理位置关联的成员名称。
  • unit(可选):距离的单位,可以是 m(米)、km(千米)、ft(英尺)或 mi(英里)。如果不指定单位,则默认返回以米为单位的结果。

命令返回一个浮点数,表示两个成员之间的距离。

下面是一个示例:

# 假设我们已经使用 GEOADD 命令添加了北京和上海的位置信息
# GEOADD cities 116.4074 39.9042 "beijing" 121.4737 31.2304 "shanghai"

# 使用 GEODIST 命令计算北京和上海之间的距离,以千米为单位
GEODIST cities "beijing" "shanghai" km

# 可能的输出(取决于实际的地理位置和 Redis 的版本)
# "1066.2408"

在上面的示例中,GEODIST 命令返回了北京和上海之间的距离,单位为千米。这个距离是近似值,具体取决于 Redis 内部用于计算距离的算法和地理数据的精度。

请注意,如果 member1member2 在指定的键中不存在,或者指定的键不存在,那么 GEODIST 命令将返回一个错误。

georadius key longitude latitude radius [m|km|ft|mi]

  • GEORADIUSRedis 中的一个命令,用于查询指定经纬度范围内地理位置信息。该命令会返回与给定经纬度距离在指定半径内的所有地理位置成员。

命令的语法如下:

GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

参数说明:

  • key:包含地理位置信息的键名。
  • longitudelatitude:指定查询的中心点的经度和纬度。
  • radius:指定查询的半径。
  • [m|km|ft|mi]:半径的单位,可以是米(m)、千米(km)、英尺(ft)或英里(mi)。

接下来是可选的参数:

  • WITHCOORD:返回每个位置成员的经纬度。
  • WITHDIST:返回每个位置成员与中心点的距离。
  • WITHHASH:返回每个位置成员的 geohash 字符串。
  • COUNT count:返回结果的最大数量。
  • ASCDESC:按照距离中心点的距离升序或降序返回结果。
  • STORE key:将结果存储到另一个键中,而不是直接返回。
  • STOREDIST key:将结果和对应的距离存储到另一个键中,每个结果是一个包含成员和距离的数组。

下面是一个示例:

# 假设我们已经使用 GEOADD 命令添加了多个城市的位置信息
# ...

# 使用 GEORADIUS 查询距离上海 100 公里内的所有城市,返回结果包括距离和经纬度
GEORADIUS cities 121.4737 31.2304 100 km WITHDIST WITHCOORD

# 可能的输出(取决于实际的地理位置和 Redis 的版本)
# 1) 1) "shanghai"
#    2) "0.0000"
#    3) 1) "121.47373437011719"
#       2) "31.23043441772461"
# 2) 1) "city_nearby"
#    2) "99.9573"
#    3) 1) "121.48000000000002"
#       2) "31.220000000000003"
# ...

在上面的示例中,GEORADIUS 命令返回了距离上海(经度为 121.4737,纬度为 31.2304)100 公里内的所有城市,并且每个结果都包含了与中心点的距离和经纬度。注意,上海本身也被包含在结果中,并且距离显示为 0。

georadiusbymember key member radius [m|km|ft|mi]

  • GEORADIUSBYMEMBERRedis 中的一个命令,用于根据指定位置成员和距离,在指定的键中查找附近的位置。这个命令与 GEORADIUS 命令类似,但不同的是,GEORADIUSBYMEMBER 的中心点是由给定的位置成员决定的,而不是直接输入经纬度。
命令语法
GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
参数说明
  • key:用于指定存储地理位置信息的键。
  • member:位置成员的名称,表示查询的中心点。
  • radius:范围半径,表示以中心点为圆心,查找的半径范围。
  • [m|km|ft|mi]:距离单位,可以是米(m)、千米(km)、英尺(ft)或英里(mi)。
可选参数
  • WITHCOORD:返回每个位置成员的经纬度。
  • WITHDIST:返回每个位置成员与中心点的距离。
  • WITHHASH:返回每个位置成员的 geohash 字符串。
  • COUNT count:返回结果的最大数量。
  • ASCDESC:按照距离中心点的距离升序或降序返回结果。
  • STORE key:将结果存储到另一个键中,而不是直接返回。
  • STOREDIST key:将结果和对应的距离存储到另一个键中,每个结果是一个包含成员和距离的数组。
示例

假设我们在名为 Sicily 的键中存储了多个城市的地理位置信息,并且想要查找距离 “Agrigento100 公里内的所有城市,我们可以使用以下命令:

GEORADIUSBYMEMBER Sicily Agrigento 100 km WITHDIST

可能的输出(取决于实际的地理位置和 Redis 的版本):

1) 1) "Agrigento"
   2) "0.0000"  # Agrigento 到自己的距离是 0
   3) 1) "13.5833333"
      2) "37.316667"
2) 1) "Palermo"
   2) "70.5374"  # 假设 Palermo 到 Agrigento 的距离是 70.5374 公里
   3) 1) "13.361389"
      2) "38.115556"
...
注意事项
  • GEORADIUSBYMEMBER 命令的时间复杂度是 O(log(N)+M),其中 N 是指定范围之内的元素数量,而 M 是被返回的元素数量。
  • 如果 member 在指定的 key 中不存在,那么 GEORADIUSBYMEMBER 命令将返回一个错误。
  • 当使用 STORESTOREDIST 选项时,原始键(key)中的位置信息不会被修改。新的结果将被存储在新的键中。
  • Redis 中的地理位置索引使用 geohash 算法进行编码,以支持高效的范围查询。

redis有效经度有效纬度

  • 有效的经度-180度到180度。
  • 有效的纬度-85.05112878度到85.05112878度。
  • 当坐标位置超出指定范围时,该命令将会返回一个错误。
  • 已经添加的数据,是无法再次往里面添加的。
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

^~^前行者~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值