开发者

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

开发者 https://www.devze.com 2025-12-28 08:57 出处:网络 作者: 程可爱
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录1.概述2.数据类型详解2.1 Bitmap2.1.1 Bitmap常用指令2.1.2 Bitmap指令实测2.1.3 Bitmap使用场景2.2 HyperlogLog(基数统计)2.2.1 HyperLogLog常用指令2.2.2 HyperLogLog指令实测2.2.3 HyperLogLog使用场景2.3
目录
  • 1.概述
  • 2.数据类型详解
    • 2.1 Bitmap
      • 2.1.1 Bitmap常用指令
      • 2.1.2 Bitmap指令实测
      • 2.1.3 Bitmap使用场景
    • 2.2 HyperlogLog(基数统计)
      • 2.2.1 HyperLogLog常用指令
      • 2.2.2 HyperLogLog指令实测
      • 2.2.3 HyperLogLog使用场景
    • 2.3 GEO
      • 2.3.1 常用指令
      • 2.3.2 指令实测
      • 2.3.2 使用场景
  • 3.代码实现
    • 4.小结
      • 5.参考文献

        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
        &g编程客栈t; 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 mpythonember 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.shttp://www.devze.comecurity.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)php;
                }
                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().i编程客栈ncludeDistance().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 的区别内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0
        价值2999元 Java视频教程限时免费下载
        专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
        立即下载

        精彩评论

        暂无评论...
        验证码 换一张
        取 消