Redis特殊类型数据结构Bitmap、HyperLogLog、GEO的使用及场景分析

 更新时间:2025年12月27日 11:42:42   作者:程可爱  
文章介绍了Redis的三种特殊数据类型:Bitmap、HyperLogLog和GEO,分别用于不同的场景,Bitmap适合存储大量二进制数据,HyperLogLog在大数据场景下能高效统计基数,而GEO则用于地理空间信息的管理和查询,本文介绍的非常详细,感兴趣的朋友跟随小编一起通过本文学习吧

1.概述

上文讲解了Redis五种基础数据类型的使用及场景,本文将分析Redis的3中特殊数据类型(Bitmap、HyperLogLog、GEO),这三种类型在特定场景下能有效提升数据处理效率、存储效率等。

2.数据类型详解

2.1 Bitmap

Bitmap是一个由位(bit)组成的图(map)。在计算机科学中,位一般只有两种状态:0或1,通常用来表示布尔值的真(true)或假(false)。Redis中的BitMap是基于String类型实现的,一个字符串的每个字节(8位)可以表示8个不同位,从而实现了位数组的功能。

2.1.1 Bitmap常用指令

命令说明
SETBIT key offset value设置指定offset位置的值
GETBIT key offset获取指定offset位置的值
BITCOUNT key start end统计指定范围内值为 1 的元素个数
BITPOS key bit start end返回第一个被设置为bit值的位的位置
BITOP operation destkey key1 key2 …设置指定offset位置的值

2.1.2 Bitmap指令实测

> SETBIT sign 5 1
0
> SETBIT sign 3 1
0
> GETBIT sign 0
0
> GETBIT sign 3
1
> BITCOUNT sign 0 5
2
> BITPOS sign 1 0 5
3
> GETBIT sign 3
1
> SETBIT sign1 3 1
0
> SETBIT sign1 4 1
0
> BITOP AND sign2 sign sign1
1
> GETBIT sign2 3
1
> GETBIT sign2 4
0
> GETBIT sign2 5
0
> BITOP OR sign3 sign sign1
1
> GETBIT sign3 4
1

2.1.3 Bitmap使用场景

1.‌活跃用户统计
例如,可以用来记录网站的访问次数、用户登录次数等。
使用场景:使用日期作为 key,然后用户 id 为 offset,如果当日活跃过就设置为1。 ‌
2.用户行为统计
例如,文章评论、点赞等行为统计。
使用场景:用文章id作为key,用户id为offset,如果当日评论、点赞过就设置为1。 ‌
3.实现布隆过滤器
布隆过滤器是一种空间效率高的概率性数据结构,用于判断元素是否存在于集合中。它在大数据、缓存穿透防护、垃圾邮件过滤等场景中广泛应用。布隆过滤器可能存在误判,但它能以极小的内存代价完成高效的查询。

2.2 HyperLogLog(基数统计)

2.2.1 HyperLogLog常用指令

Redis在2.8.9版本引入了HyperLogLog 结构,HyperLogLog做数据统计的优势在于:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且占用内存很小。
HyperLogLog 是一种有名的基数计数概率算法,并非是redis独有,redis只是基于该算法提供了一些通用API,并且对 HyperLogLog 的存储进行了优化,在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间。
基数计数概率算法为了节省内存并不会直接存储元数据,而是通过一定的概率统计方法预估基数值(集合中包含元素的个数)。因此, HyperLogLog 的计数结果并不是一个精确值,存在一定的误差(标准误差为 0.81% )

命令说明
PFADD key element1 element2 …添加一个或多个元素到 HyperLogLog 中
PFCOUNT key1 key2获取一个或者多个 HyperLogLog 的唯一计数
PFMERGE destkey sourcekey1 sourcekey2 …将多个 HyperLogLog 合并到 destkey 中,destkey 会结合多个源,算出对应的唯一计数

2.2.2 HyperLogLog指令实测

> PFADD chars a b c d e
1
> PFADD chars f g
1
> PFCOUNT chars
7
> PFADD nums 1 2 3
1
> PFCOUNT chars nums
10
> PFMERGE destination chars nums
OK
> PFCOUNT destination
10

2.2.3 HyperLogLog使用场景

1.‌活跃用户统计
例如,计算网站的日活、7日活、月活数据等。
使用场景:将关键字+时间作为key(DAYLIVE+20251217),将活跃用户userId作为element,计算某一天的日活,只需要执行 DAYLIVE+20251217即可。每个月的第一天,执行 PFMERGE 将上一个月的所有数据合并成一个 HyperLogLog(MONTHLIVE_202512),再执行 PFCOUNT MONTHLIVE_202512,就得到了 12 月的月活数据。
2.统计注册 IP 数、统计在线用户、统计用户每天搜索不同词条的个数这些场景利用HyperLogLog均能实现,原理类似

2.3 GEO

Redis 的 Geospatial 基于 Sorted Set 实现提供了一种有效的方式来存储地理空间信息,例如地理位置坐标(经度和纬度)以及与之相关的数据。通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。

2.3.1 常用指令

命令说明
GEOADD key longitude latitude member …将一个或多个成员的地理位置(经度和纬度)添加到指定的有序集合中
GEOPOS key member1 member2 …返回指定元素的经纬度信息
GEODIST key member1 member2 M/KM/FT/MI返回两个给定元素之间的距离,M/KM/FT/MI: 指定半径的单位,可以是米(m)、千米(km)、英里(mi)、或英尺(ft)
GEORADIUS key longitude latitude radius M/KM/FT/MI获取给定的经纬度为中心, 返回与中心的距离不超过给定最大距离的所有位置元素,支持 ASC(由近到远)、DESC(由远到近)、Count(数量) 等参数
GEORADIUSBYMEMBER key member radius distance找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定

2.3.2 指令实测

> GEOADD location 116.33 39.89 user1 116.34 39.90 user2 116.35 39.88 user3 119.35 41.22 user4
3
> GEOPOS location user1
116.3299986720085144
39.89000061669732844
> GEODIST location user1 user2 km
1.4018
> GEODIST location user1 user4 km
294.9606
> GEORADIUS location 116.33 39.87 3 km
user3
user1
> GEORADIUS location 116.33 39.87 5 km
user3
user1
user2
> GEORADIUSBYMEMBER location user1 3 km
user3
user1
user2
> GEORADIUSBYMEMBER location user1 2 km
user1
user2

2.3.2 使用场景

1.‌需要管理地理位置的场景
例如,寻找附近的人。
使用场景:通过GEORADIUS获取当前用户指定距离范围内的人,如QQ、微信附近的人。

3.代码实现

package com.eckey.lab.service.util;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
    private static final Logger LOG = LoggerFactory.getLogger(RedisUtil.class);
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "strRedisTemplate")
    private RedisTemplate<String, String> stringRedisTemplate;
	 /**
	     * 创建布隆过滤器
	     *
	     * @param size          位数组大小
	     * @param hashFunctions 哈希函数数量
	     * @param value         元素值
	     */
    private List<Long> getHashPositions(String value, int hashFunctions, int size) {
        List<Long> positions = new ArrayList<>(hashFunctions);
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(value.getBytes(StandardCharsets.UTF_8));
            // 使用同一个MD5值生成多个哈希位置
            for (int i = 0; i < hashFunctions; i++) {
                long hashValue = 0;
                for (int j = i * 4; j < i * 4 + 4; j++) {
                    hashValue <<= 8;
                    int index = j % bytes.length;
                    hashValue |= (bytes[index] & 0xFF);
                }
                positions.add(Math.abs(hashValue % size));
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
        return positions;
    }
    public void bloomFilterAdd(String key, String value, int hashFunctions, int size) {
        for (long position : getHashPositions(value, hashFunctions, size)) {
            stringRedisTemplate.opsForValue().setBit(key, position, true);
        }
    }
    public boolean bloomFilterContains(String key, String value, int hashFunctions, int size) {
        for (long position : getHashPositions(value, hashFunctions, size)) {
            if (Boolean.FALSE.equals(stringRedisTemplate.opsForValue().getBit(key, position))) {
                return false;
            }
        }
        return true;
    }
    public void hyperAdd(String key, String value) {
        stringRedisTemplate.opsForHyperLogLog().add(key, value);
    }
    public void hyperAdd(String key, String... values) {
        stringRedisTemplate.opsForHyperLogLog().add(key, values);
    }
    public Long hyperSize(String key) {
        return stringRedisTemplate.opsForHyperLogLog().size(key);
    }
    public void hyperDel(String key) {
        stringRedisTemplate.opsForHyperLogLog().delete(key);
    }
    public Long hyperUnion(String destKey, String srcKey1, String srcKey2) {
        return stringRedisTemplate.opsForHyperLogLog().union(destKey, srcKey1, srcKey2);
    }
    public Long hyperUnion(String destKey, String... srcKeys) {
        return stringRedisTemplate.opsForHyperLogLog().union(destKey, srcKeys);
    }
  public Long geoAdd(String key, double lat, double lon, String member) {
        return stringRedisTemplate.opsForGeo().add(key, new Point(lat, lon), member);
    }
    public List<Point> geoGet(String key, String member) {
        return stringRedisTemplate.opsForGeo().position(key, member);
    }
    public Distance geoDistance(String key, String member1, String member2, Metric metric) {
        return stringRedisTemplate.opsForGeo().distance(key, member1, member2, metric);
    }
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadius(String key, double longitude, double latitude, double radius, RedisGeoCommands.DistanceUnit unit) {
        Point point = new Point(longitude, latitude);
        Circle circle = new Circle(point, new Distance(radius, unit));
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending();
        return stringRedisTemplate.opsForGeo().radius(key, circle, args);
    }
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoNearByMember(String key, String member, double radius, RedisGeoCommands.DistanceUnit unit) {
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending();
        return stringRedisTemplate.opsForGeo().radius(key, member, new Distance(radius, unit), args);
    }
}

4.小结

1.Bitmap其实就是一个存储二进制数字(0 和 1)的数组,通过一个bit位可以表示某个元素对应的值或者状态,key 就是对应元素本身 。一个字节(byte)占8个bit,因此Bitmap能极大节省存储空间。
2.HyperLogLog数据结构在Redis中占用固定空间,因此不适合存储大量数据。同时它只能提供近似值,对于精确度要求较高的场景不太适用。Bitmap适合存储大量数据,但对于少量数据而言不够高效。
3.Geospatial index(地理空间索引)主要用于存储地理位置信息,适用于根据距离进行查询、统计等一系列场景。

5.参考文献

1.https://juejin.cn/post/6844903785744056333
2.https://javaguide.cn/database/redis/redis-data-structures-02.html
3.https://www.cnblogs.com/lykbk/p/15871615.html
4.https://hogwartsrico.github.io/2020/06/08/BloomFilter-HyperLogLog-BitMap/index.html

到此这篇关于Redis三种特殊类型数据结构(Bitmap、HyperLogLog、GEO)的文章就介绍到这了,更多相关Redis 中 RDB 与 AOF 的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis缓存的主要异常及解决方案实例

    Redis缓存的主要异常及解决方案实例

    这篇文章主要为大家介绍了Redis缓存的主要异常及解决方案实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Redis BigKey的问题解决

    Redis BigKey的问题解决

    本文主要介绍了Redis BigKey的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • Redis设置database不生效的解决方案

    Redis设置database不生效的解决方案

    最近在做redis缓存设置的时候,发现即使已经设置了database, 但是存数据的时候还是用的默认0数据库,所以本文就给大家介绍了Redis设置database不生效的解决方案,需要的朋友可以参考下
    2023-08-08
  • Redis6.0搭建集群Redis-cluster的方法

    Redis6.0搭建集群Redis-cluster的方法

    这篇文章主要介绍了Redis6.0搭建集群Redis-cluster的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 完美解决linux上启动redis后配置文件未生效的问题

    完美解决linux上启动redis后配置文件未生效的问题

    今天小编就为大家分享一篇完美解决linux上启动redis后配置文件未生效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • redis list类型命令的实现

    redis list类型命令的实现

    本文主要介绍了redis list类型命令的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 查看redis的缓存时间方式

    查看redis的缓存时间方式

    这篇文章主要介绍了查看redis的缓存时间方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • YII2框架手动安装Redis扩展的过程

    YII2框架手动安装Redis扩展的过程

    这篇文章主要介绍了YII2框架手动安装Redis扩展的过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • 线上Redis一直报连接超时该如何解决

    线上Redis一直报连接超时该如何解决

    这篇文章主要为大家详细介绍了项目开发时如果出现线上Redis一直报连接超时的问题该如何解决,文中的示例代码简洁易懂,需要的小伙伴可以借鉴一下
    2023-08-08
  • 一文详解消息队列中为什么不用redis作为队列

    一文详解消息队列中为什么不用redis作为队列

    这篇文章主要介绍了消息队列中为什么不用redis作为队列的相关资料,文章详细介绍了Redis的List和Pub/Sub数据类型在队列中的应用,包括如何处理消息积压和数据丢失等问题,通过代码介绍的非常详细,需要的朋友可以参考下
    2024-12-12

最新评论