Redis 数据结构(三) HyperLogLog、位图、地理坐标

HyperLogLog 超日志,是一个专门计算集合的基数的概率算法。可以计算出集合的近似基数,标准误差为0.81%。它进行计算的内存是固定的(12kb。2^14 * 5  / 8  = 10240 bytes ≈ 10 KB + 额外开销2kb)。

bitmap 位图,是由多个二进制位组成的数组,每个二进制位都有与之对应的偏移量,用户通过这些偏移量可以对位图中的一个或多个二进制位进行操作。它是在字符串的基础上实现的。

geo 地理坐标,用户可以将经纬度格式的地理坐标存储到Redis,并对这些坐标执行距离计算、范围操作等操作。其是在有序集合基础上实现的。

1 超日志 HyperLogLog

HyperLogLog 能够在占用极小内存的情况下估算一个集合中不重复元素的数量。所占用的内存是固定的(12kb),不会随着元素数量的变化而改变。

其主要用于需要统计大量唯一值数量但又对内存占用敏感的场景,如:统计网址一段时间的独立访客数量、估算广告的独立点击用户数。

添加

PFADD (可用于唯一判断)。

统计

PFCOUNT,其会将集合使用PFMERGE合并并存储到临时集合,操作完成后会将这个临时集合删除。

合并

PFMERGE。

表 HyperLogLog 相关操作命令

1.1 示例

某网址每日访客数量在10w以上,需求是,统计每日不同ip的访问数量。及判断某ip是否已访问过网站。

public class IpUniqueCounter {

    private final static Jedis jedis = RedisPool.getRedis();
    private final static String COUNTER_KEY = "ip_unique_counter_hyperLogLog";

    public static void addIp(String  ... ips) {
        jedis.pfadd(COUNTER_KEY,ips);
    }

    public static long count() {
        return jedis.pfcount(COUNTER_KEY);
    }

    public static boolean has(String ip) {
        return jedis.pfadd(COUNTER_KEY,ip) != 1;
    }

    public static void main(String[] args) {
        addIp("127.0.0.1","0.0.0.1","192.168.0.1");
        System.out.println(count());
        System.out.println(has("127.0.0.1"));
        System.out.println(has("127.0.0.2"));
        System.out.println(has("127.0.0.2"));
    }
}

2 位图 Bitmap

位图基于字符串数据类型构建,允许用户将字符串视为一系列的二进制位(bit),每个位可以是0或1.这种数据结构非常适合存储和操作大量布尔值状态的数据,具有高效的空间利用率和快速的位操作能力。

应用场景有:记录用户的签到情况、网站页面的访问次数,用户状态管理等。

设置与获取

GETBIT、SETBIT

统计

BITCOUNT,统计1的数量

查找

BITPOS 查找 0 或1首次出现的位置,可以指定范围。

位运算

BITOP。可以执行AND(逻辑并)、OR(逻辑或)、XOR(逻辑异或)、NOT(逻辑非)中的任意运算。

整数运算

BITFIELD,其子命令有SET、GET、INCRBY、OVERFLOW(用于处理溢出)。

表 位图相关操作命令

2.1 BITFIELD

用于处理整数相关运算。

命令格式:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP | SAT | FAIL]

type: 指定数值的类型,需要以i或u位前缀,后跟位长度。例如u8, 表示无符号8位整数。i64,有符号64位整数。

value: 用于指定被设置的整数值,这个值的类型应该和type参数指定的类型一致。如果长度超过了type参数指定的类型,那么会将高位多出的长度阶段。例如 type指定位u4。 value位 17(1 0001),那么存储的值为0001。

offset: 设置起始偏移量,从0开始。可以是负数。 例如 5,表示从第6个二进制为开始。前面加上#号表示索引,表示第几个type指定的类型。 例如 type指定为 u4,那么#2 表示第3个4位无符号整数。

2.1.1 OVERFLOW

当使用SET 或 INCRBY子命令发生溢出时(即超出type指定的范围),可以在这些命令前面使用OVERFLOW 来指定溢出控制行为。

WRAP

回绕,默认值,向上溢出的整数值将从类型的最小值开始重新计算,向下溢出的整数值从类型的最大值重新计算。

SAT

饱和运算,向上溢出的整数值将被设置为类型最大值,向下溢出的整数则会被设置为类型的最小值。

FAIL

检测到计算会引发溢出时拒绝执行计算,并返回空值。

表 OVERFLOW子命令的可选参数处理溢出

2.2 示例

用户行为记录器:根据用户id记录用户是否执行某个操作,即在位图上,id 数值位上设置为1,表示该用户进行过操作。

public class ActionRecorder {

    private final static Jedis jedis = RedisPool.getRedis();
    private final static String ACTION_KEY = "action_recorder_bitmap";
    private final static String PERSON_ID_KEY = "action_recorder_person_id_string";

    public static long getPersonId(long num) {
        return jedis.incrBy(PERSON_ID_KEY,num);
    }

    public static void performBy(long userId) {
        jedis.setbit(ACTION_KEY,userId,true);
    }

    public static boolean isPerformedBy(long userId) {
        return jedis.getbit(ACTION_KEY,userId);
    }

    public static long countPerformed() {
        return jedis.bitcount(ACTION_KEY);
    }

    public static void main(String[] args) {
        performBy(getPersonId(1));
        performBy(getPersonId(5));
        performBy(getPersonId(5));

        System.out.println(isPerformedBy(1));
        System.out.println(isPerformedBy(0));
        System.out.println(isPerformedBy(6));
        System.out.println(isPerformedBy(11));
        System.out.println(isPerformedBy(12));
        System.out.println(countPerformed());
    }
}

布尔矩阵:由布尔值组成的矩阵,通常用于表示散列结构。

public class ZeroOneMatrix {

    private final static Jedis jedis = RedisPool.getRedis();
    private final static String MATRIX_KEY = "matrix_bitmap2";
    private final static long ROW_COUNT = 5;
    private final static long COL_COUNT = 6;

    private static long calculatePos(long row,long col) {
        if (row >= ROW_COUNT) throw new RuntimeException("行数超出界限");
        if (col >= COL_COUNT) throw new RuntimeException("列数超出界限");
        return row * COL_COUNT + col;
    }

    public static void setVal(long row,long col,boolean val) {
        jedis.setbit(MATRIX_KEY,calculatePos(row,col),val);
    }

    public static boolean getVal(long row,long col) {
        return jedis.getbit(MATRIX_KEY,calculatePos(row, col));
    }

    public static void show() {
        for (int i = 0; i < ROW_COUNT; i++) {
            StringBuilder sb = new StringBuilder();
            sb.append("row").append(i).append(":");
            for (int j = 0; j < COL_COUNT; j++) {
                sb.append(getVal(i,j)).append(" ");
            }
            System.out.println(sb);
        }
    }

    public static void main(String[] args) {
        setVal(0,4,true);
        setVal(1,5, true);
        setVal(4,0,true);
        show();
    }
}

3 地理坐标 GEO

本质是有序集合。每个元素包含三个属性:位置名称、经度、纬度。 经度和纬度会转换为一个hash值,这个相当于有序集合中的分值。

存储与获取

GEOADD、GEOPOS

距离

GEODIST

查找

GEORADIUS、GEORADIUSBYMEMBER(根据位置名称及半径范围查找)

hash值

GEOHASH

表 GEO 相关命令

3.1 示例

用户地理位置:用于存储用户位置(经纬度)信息,能计算用户直接的距离,用户周围的其他用户等。

public class GeoLocation {

    private final static Jedis jedis = RedisPool.getRedis();
    private final static String LOCATION_KEY = "location_geo";

    public static void setLocation(String city,double longitude,double latitude) {
        jedis.geoadd(LOCATION_KEY,longitude,latitude,city);
    }

    public static void showLocation(String city) {
        System.out.println(jedis.geopos(LOCATION_KEY,city));
    }

    public static void showDist(String city1,String city2) {
        System.out.println(jedis.geodist(LOCATION_KEY,city1,city2, GeoUnit.KM));
    }

    public static void nearby(String city,double radius) {
        System.out.println(jedis.georadiusByMember(LOCATION_KEY,city,radius,GeoUnit.KM));
    }

    public static void main(String[] args) {
        setLocation("北京",116.28,39.54);
        setLocation("上海",121.29,31.14);
        setLocation("广州",113.17,23.06);
        setLocation("深圳",114.03,22.34);

        showLocation("上海");
        showDist("北京","深圳");
        showDist("广州","深圳");
        showDist("深圳","深圳");

        nearby("深圳",200);
        nearby("深圳",1300);
    }
}

<think>嗯,用户想了解Redis 6.0支持的数据结构及其特性。这个问题很专业,需要系统整理Redis核心数据结构在6.0版本中的实现和优化。 首先回忆Redis 6.0的重要更新,虽然数据结构本身没有新增类型,但内存管理和线程模型有重大改进。用户引用的资料[1][4]提到6.0版本提升了性能和扩展性,特别是客户端缓存特性[^4],这对数据结构的使用效率有间接影响。 从基础数据结构开始梳理: 字符串(String)作为最基础的类型,在6.0中embstr编码优化了短字符串存储,引用[2]提到的list结构体其实也适用于所有数据结构——Redis都采用类似的设计模式:用专属结构体管理元数据(如len字段),再指向具体存储节点。哈希表(Hash)的ziplist转hashtable的阈值值得说明,这个直接影响内存效率。 列表(List)要重点讲quicklist结构,这是3.2版本引入的,但6.0的线程IO优化使其并发性能提升。用户引用[2]的list结构体代码很关键,其中dup和free函数指针说明Redis支持深拷贝[^2]。集合(Set)的整数集合升级机制需要举例说明,比如$SADD$命令触发intset转hashtable的过程。 有序集合(ZSet)的ziplist+skiplist双结构是难点,要解释跳表如何实现$O(logN)$查询。引用[4]的客户端缓存特性[^4]对频繁访问的有序集合特别有利,因为可以避免重复查询。位图(Bitmap)HyperLogLog虽然基于字符串,但它们的位操作指令如$SETBIT$需要单独说明。 地理空间索引(Geospatial)本质是有序集合,但GEOADD命令的坐标编码机制值得展开。流(Stream)作为5.0新增结构,在6.0中稳定性增强,要强调消息ID的时序性和消费者组特性。 最后补充6.0的线程安全改进:虽然数据结构操作本身还是单线程,但网络IO多线程化提升了大value操作的吞吐量。引用[3]的bind配置[^3]看似无关,实则影响数据结构服务稳定性——错误的网络配置可能导致数据丢失。 用户可能混淆了数据结构版本差异,需要明确6.0是优化而非新增结构。相关问题的设计要覆盖使用场景(如为什么选Hash而不是String存储对象)和性能对比(如ZSet的跳表查询复杂度证明)。</think>Redis 6.0 延续了其丰富的数据结构支持,以下是核心数据结构及其特性的详细解析: ### 一、核心数据结构 1. **字符串(String)** - 存储文本、二进制数据(如图片)或数值 - 最大容量 512MB - 特殊操作: ```bash INCR counter # 原子计数器 APPEND log "msg" # 追加操作 BITCOUNT user:1 # 位运算 ``` 2. **哈希(Hash)** - 存储字段-值映射表,适合对象存储 - 底层实现: - `ziplist`(元素少时,内存紧凑) - `hashtable`(元素多时,$O(1)$ 复杂度) - 示例: ```bash HSET user:1000 name "Alice" age 30 HGETALL user:1000 ``` 3. **列表(List)** - 双向链表结构,支持左右插入/弹出 - 关键特性: - 可实现队列(LPUSH/RPOP)或栈(LPUSH/LPOP) - 阻塞操作:`BLPOP` 实现消息队列 - 底层结构:`quicklist`(ziplist 链表,平衡内存和速度) 4. **集合(Set)** - 无序唯一元素集合 - 支持交并差集运算($SINTER/SUNION/SDIFF$) - 底层实现: - `intset`(纯整数时) - `hashtable`(其他类型) 5. **有序集合(Sorted Set)** - 元素关联分值,按分值排序 - 底层结构: - `ziplist`(小规模) - **跳表(skiplist)+ 字典**(大规模,$O(logN)$ 复杂度) - 范围操作: ```bash ZRANGEBYSCORE players 1000 2000 # 按分值检索 ``` ### 二、高级数据结构 6. **位图(Bitmap)** - 基于字符串的位操作 - 适用场景:实时用户签到统计 ```bash SETBIT user:2024:sign 15 1 # 记录第15天签到 BITCOUNT user:2024:sign # 统计总签到 ``` 7. **HyperLogLog** - 基数估算(误差约 0.81%) - 内存仅需 12KB ```bash PFADD ip_log 192.168.1.1 PFCOUNT ip_log # 估算独立IP数 ``` 8. **地理空间索引(Geospatial)** - 存储经纬度坐标 - 支持距离计算(GEODIST)和半径查询(GEORADIUS) ```bash GEOADD cities 116.40 39.90 Beijing GEOSEARCH cities FROMLONLAT 116.4 39.9 BYRADIUS 100 km ``` 9. **流(Stream)** - 消息队列实现(Redis 5.0+) - 特性: - 消息持久化 - 消费者组(Consumer Groups) - 消息回溯 ```bash XADD orders * product "Book" price 29.9 XREAD GROUP group1 consumer1 COUNT 1 STREAMS orders > ``` ### Redis 6.0 的增强特性 1. **多线程网络 I/O** - 处理网络请求使用多线程(执行命令仍单线程) - 提升高并发场景吞吐量 2. **客户端缓存(Client-side Caching)** - 服务端跟踪客户端缓存(`RESP3` 协议支持) - 主动通知缓存失效(避免脏读)[^4] 3. **ACL 权限控制** - 细粒度命令权限管理 ```bash ACL SETUSER alice ON >pass123 +@read ~cache:* ``` 4. **TLS 加密传输** - 增强客户端-服务端通信安全[^3] ### 四、数据结构选择建议 | 场景 | 推荐结构 | 原因 | |------|----------|------| | 对象属性存储 | Hash | 字段独立存取 | | 排行榜 | Sorted Set | 天然排序能力 | | 消息队列 | Stream | 消息持久化和消费组 | | UV统计 | HyperLogLog | 低内存消耗 | > Redis 6.0 通过优化内存分配(jemalloc 改进)和网络层多线程,显著提升了大型数据结构(如数十万元素的 Sorted Set)的操作效率[^1][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值