Redis常用数据结构

1. 底层数据结构

简单动态字符串_sds

  • 参考文献

    Redis详解(四)------ redis的底层数据结构 (一般般)

    《Redis设计与实现》读书笔记(一)——简单动态字符串(SDS)(✔)

  • SDS 结构

    在这里插入图片描述

    • 相比 C 语言多了 len 和 free 属性, C 语言求字符串长度,需要遍历计算
  • C 语言字符串

  • SDS 相比于 C 语言中字符串的好处

    • 常数复杂度获取字符串长度

      C 语言: strlen() 而 sds : len直接获取

    • 杜绝缓冲区溢出

      C 语言 拼接字符串, strcat 函数,分配空间不足,会造成缓冲区溢出

      SDS 会检查内存空间是否满足要求. 通过比较当前字符串free即将拼接字符串len 的大小,就知道是否可以拼接。如果free的值不够,会再申请内存空间,避免溢出。

    • 减少修改字符串的内存重新分配次数

      1)空间预分配 避免频繁申请空间

      • 分配的规则是,如果增长字符串新的字符串比1MB小,则额外申请字符串当前所占空间的大小作为free值;如果增长后,字符串长度超过1MB,则额外申请1MB大小。 (增长完>1MB,则额外申请1MB,否则额外申请增长完自身大小作为free)
      • 该机制,使得字符串增长n次,需要申请空间的次数,从必定为n次的情况,降为最多n次的情况。

      2)懒惰空间释放

      • 当需要缩短sds的长度时,并不立即释放空间,而是使用free来保存剩余可用长度,并等待将来使用。当有剩余空间,而有有增长字符串操作时,则又会调用空间预分配机制。
      • 当redis内存空间不足时,会自动释放sds中未使用的空间,因此也不需要担心内存泄漏问题。
    • 二进制安全 写入的是什么内容,返回的也是什么内容,并没有限制。

      SDS 的API 都是以处理二进制的方式来处理 buf 里面的元素,并且 SDS 不是以空字符串来判断是否结束,而是以 len 属性表示的长度来判断字符串是否结束。

    • 兼容部分 C 字符串函数

    image-20200806224129557

链表_list=>linkedlist

  • 参考文献

    《Redis设计与实现》读书笔记(三) ——Redis中的链表

  • 结构

    结点结构 (listNode)

    typedef struct listNode{
    	//指向前一个结点
        struct listNode *prev;
    	//指向后一个结点
        struct listNode *next;
    	//当前结点的值
        struct *value;
    }
    

    多个 listNode 组成的双向链表

    img

    链表结构 (list)

    typedef struct list{
    	//链表的头结点
        listNode *head;
    	//链表的尾结点
        listNode *tail;
    	//链表长度
        unsigned long len;
    	//结点值复制函数
        void *(*dup) (void *ptr);
    	//结点的值释放存储空间
        void *(*free) (void *ptr);
    	//比较两个两个结点的值是否相等?
        int (*match) (void *ptr, void *key);
    }
    

    img

  • 特点

    • 双向
    • 无环,head.prev = null , tail.next = null
    • 有头节点和尾结点
    • 有长度计数器,获取链表结点数 O(1)
    • 多态: 可以保存不同类型的值
    • 感觉和 Java 里得 Linked List 差不多
    • list 每个结点由 listNode 实现

字典_dict=>hashtable

  • 参考文献

    《Redis设计与实现》读书笔记(二) ——Redis中的字典(Hash)

  • 简介

    • 字典,又称符号表、关联数组、映射,是一种保存键值对的抽象数据结构。每个键(key)和唯一的值(value)关联,键是独一无二的,通过对键的操作可以对值进行增删改查。 (Java_HashMap)

    • redis中字典应用广泛,对redis数据库的增删改查就是通过字典实现的。即redis数据库的存储,和大部分关系型数据库不同,不采用B+tree进行处理,而是采用hash的方式进行处理。

    • 另外,毫无疑问,redis的hash数据类型也是通过字典方式实现。

  • 哈希表结构

    哈希表 (HashMap 中的 table)

    typedef struct dictht{
    	//一个数组,存放每个元素指向 dictEntry 结构得指针 => dictEntry 是键值对结构
        dictEntry **table;
    	//哈希表大小  也是 table 的大小
        unsigned long size;
    	//sizemask = size -1 该值与hash 值 一起决定当作 table 的哪个位置
        unsigned long sizemask;
    	//已有键值对结点数量
        unsigned long used;
    }dictht;
    

    哈希表结点 (HashMap 中的 Entry)

    typedef struct dictEntry{
    	//键 key
        void *key;
        union{
            void *val; //指针
            uint64_t u64; // uint64_t 整数
            int64_t s64; // int64_t 整数
        }v; // value
    
        struct dictEntry *next; //使用拉链法解决 hash 冲突的问题, 同 HashMap
    }dictEntry;
    

    img

  • 字典结构 dict

    dict

    typedef struct dict{
        
        dictType *type;
    	//用于存放私有数据,保存传给type内的函数的数据
        void *privdata;
    	//dictht 类型 : 每个项是一个哈希表,一般情况下只是用ht[0],只有在对ht[0]进行rehash时,才会使用ht[1]。
        dictht ht[2];
    	//是一个索引,当没有在rehash(扩容/收缩)进行时,值是-1, 设置为 0, 表示正在 rehash
        int rehashidx;
    
    }dict;
    //定义了字典的各种操作函数
    typedef struct dictType{
    	//哈希值计算函数
        unsigned int (*hashFunction) (const void *key);
    	//键复制
        void *(*keyDup) (void *privdata, const void *key);
    	//值复制
        void *(*valDup) (void *privdata, const void *obj);
    	//键比较
        int *(*keyCompare) (void *privdata, const void *key1, const void*key2);
    	//键销毁
        void *(*keyDestructor) (void *privdata, void *key);
    	//值销毁
        void *(*valDestructor) (void *privdata, void *obj);
    }dictType;
    
    image-20200807101430248
  • hash 算法

    //redis实现哈希的代码是:
    hash =dict->type->hashFunction(key); // 计算 key 的 hash 值
    index = hash& dict->ht[x].sizemask; //hash & sizemask,(sizemask = size -1) 和 HashMap 找落在哪个桶是一样的
    
  • 键冲突解决 => 拉链法解决, 插在尾部, 即尾插

  • 渐进式rehash: 什么时候 rehash? => 一个哈希表保存的键太多或者太少

    rehash 过程:

    • 判断是扩展还是收缩.

      ​ 扩展的话 ht[1] 的分配空间大小为 第一个大于等于 ht[0].used x 2 的 2^n 的值. h[0].used = 30 => 64

      ​ 收缩的话 ht[1] 的分配空间大小为 第一个大于等于 ht[0].used 的 2^n 的值. h[0].used = 30 => 32

    • 将 ht[0] 上所有的键值重新 rehash 到 ht[1], 然后释放 ht[0] ,并将 ht[1] 修改为 ht[0], 载创建一个新的空的 ht[1], 用于之后的 rehash(扩容/收缩) . ht[1] 就是一个临时的空间,默认存放 ht[0]

    rehash 条件: (used => 键值对数目, size => table 数组的长度)

    • load_factor =ht[0].used / ht[0].size 即 ht[0].used = ht[0].size x load_factor ( 类似 HashMap )

    • 自动扩容:

      ​ 服务器目前没有在执行BGSAVE或者BGREWRITEAOF命令,且负载因子大于等于1

      ​ 服务器目前正在在执行BGSAVE或者BGREWRITEAOF命令,且负载因子大于等于5

    • 自动收缩

      ​ 当负载因子小于0.1时,redis自动开始哈希表的收缩工作。

    copy-on-write简介

    • 写时复制技术,是操作系统层面,为了提升性能而进行的一个策略。

      策略如下:每次写文件操作,都写在特定大小的一块内存中(磁盘缓存),并不是直接写到磁盘中。只有当我们关闭文件时,才写到磁盘上(这就是为什么如果文件不关闭,所写的东西会丢失的原因)。更有甚者是文件关闭时都不写磁盘,而一直等到关机或是内存不够时才写磁盘,Unix就是这样一个系统,如果非正常退出,那么数据就会丢失,文件就会损坏。

      写时复制技术,大大降低了磁盘I/O的次数,而I/O往往是性能瓶颈,这样一来就最大程度上避免了此瓶颈。

    • 为什么自动 扩容时要检查 bgsave 是否在执行?

      因为 bgsave / bgwriteaof 执行过程中, redis 需要创建当前服务器进程的子线程 (?不懂有什么影响),而多数OS都是使用 copy-on-write 技术优化子进程使用效率. 如果此时频繁扩容, 会出现 ht[1] 建立完 ht[0] 还未清空,占用两份内存的问题,浪费内存.

    渐进式 rehash

    • ht[0] => ht[1] 不是 一次性完成的,而是渐进式分多次完成

    • 在rehash期间,对哈希表的查找、修改、删除,会先在ht[0]进行。如果ht[0]中没找到相应的内容,则会去ht[1]查找,并进行相关的修改、删除操作。而增加的操作,会直接增加到ht[1]中,目的是让ht[0]只减不增,加快迁移的速度。

    • 过程:

      image-20200807110543109

  • 小结: 一个字典, 有两个 哈希表, 一个哈希表正常使用,另一个在 rehash 时候使用.(扩容/收缩)

跳跃表_skiplist

  • 参考文献

    《Redis设计与实现》读书笔记(四)

    Redis(2)——跳跃表(通俗易懂✔—nice)

  • 简介

    • 跳跃表(skiplist)是一种有序的数据结构,它通过每个节点中维持多个指向其他节点的指针,从而实现快速访问。跳跃表平均O(logN),最坏O(N),支持顺序遍历查找。

    • 在redis中,有序集合(sortedset)的其中一种实现方式就是跳跃表。

    • 本质是解决查找问题

    • 时间复杂度分析

      跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近;


      为什么不选择红黑树?

      • 插入,删除,查找,有序输出所有元素效率和跳表相同
      • 但是 按照范围区间查找元素,红黑树的效率远远不如跳表,跳表找到开头的那个之后依次遍历即可

      image-20200807164319964

  • 结构

    跳跃表节点数据结构

    typedef struct zskiplistNode{
    	//表示 层 L1 就是第一层, L2 就是第二层
        struct zskiplistLevel{
    		//前进指针, 用于访问跳表尾部方向
            struct zskiplistNode *forward;
    		//跨度: 
            unsigned int span;
        }level[]; 
        //指向当前节点的前一个节点,后退指针不会指向头节点。
        struct zskiplistNode *backward;
    	//各节点中的数字就是分值,跳跃表中,节点按照分值从小到大排列。
        double score;
    	//指向存储着节点的具体内容的redis的sds类型的字符串
        robj *obj;
    
    }zskiplistNode;
    

    层的意义 (有点类似索引的感觉)

    • 层的作用是加快访问速度,这也是跳跃表的核心思想。每次创建跳跃表后,根据幂次定律,越大的数字出现的概率越小,随机生成一个1~32之间的值作为level数组的大小,这个就是层的高度。

    • 插入数据选择层数时,不要求上下相邻两层链表之间的节点个数有严格的对应关系. 每个节点随机出一个层数(level). 这样就避免了插入时调整层数而导致的效率低了.

      随机层数 : 直观上期望的目标是 50% 的概率被分配到 Level 1,25% 的概率被分配到 Level 2,12.5% 的概率被分配到 Level 3,以此类推…有 2-63 的概率被分配到最顶层,因为这里每一层的晋升率都是 50%。

    跳表结构

    typedef struct zskiplist{
        struct zskiplistNode *header,*tail;
    	//跳跃表的长度(即跳跃表节点的数量,不含头结点)
        unsigned long length;
    	//层数最大节点的层数(不计算表头结点)。
        int level;
    }zskiplist;
    
    • 跳表结构

      img

    • 从跳表中查找数据 ①->…->⑥

      img

    • 跳表插入数据

      img

整数集合_intset

  • 参考文献

    《Redis设计与实现》读书笔记(五) ——Redis中的整数集合

  • 简介

    • 整数集合(intset)是redis数据结构集合(set)的底层实现之一,如果set中只包含整数元素,且元素个数不多时,redis会使用整数集合作为set的底层实现
    • 整数集合是redis保存整数值集合的底层实现,可以保存 int16_t、int32_t、int64_t 的整数值,且集合中每个值都不一样(不允许重复)
  • 结构

    typedef struct intset{
    	//编码方式, INTSET_ENC_INT16 = > int16_t
        uint32_t encoding;
    	//数组中元素的个数
        uint32_t length;
    	//保存元素的数组
        int8_t contents[];
    }intset;
    

    int16_t, int32_t, int64_t:

    这些数据类型中都带有_t, _t 表示这些数据类型是通过 typedef 定义的,而不是新的数据类型。也就是说,它们其实是我们已知的类型的别名。

    typedef short int               int16_t; //2字节 16位
    typedef int                     int32_t; //4字节 32位
    typedef long long int           int64_t; //8字节 64位
    
  • 整数升级

    升级过程:

    • 触发条件: 当新元素添加到 contents 数组中,该元素类型比现有元素类型长,则会对 contents 元素进行升级.
    • 升级什么:将底层所有元素都转换成新类型,放在原位置上,保持大小顺序不变
    • 迁移过程:如果新元素最大,则放在最后一个位置, 如果新元素最小,则放在最前面的位置,之后的元素,依次向后移动,和数组插入后数组迁移是相同的操作.
    • 为什么要升级: 节约内存(需要升级在升级), 灵活性: C 语言本身是不支持的, 现在 redis 支持自动升级
    • redis 不支持降级,一旦升级后,即使大类型元素被删除,仍会保持原来的状态

压缩列表_ziplist

  • 参考文献

    《Redis设计与实现》读书笔记(六) ——Redis中的压缩列表

  • 简介

    压缩列表(ziplist)是列表键(list)和哈希键(hash)底层的实现之一。当列表项(list)较少,且每项要么是小的整数值,要么是长度比较短的字符串,则使用ziplist。当哈希的键值对较少,且每个键值对都是小整数或短字符串,也是使用 ziplist。

  • 压缩列表结构

    img

    • zlbytes: uint32_t 类型,4字节,记录整个 ziplist 占字节数
    • zltail: uint32_t 类型, 记录表尾结点(entry) 距离 头结点 (entry) 多少字节,这样可以通过偏移量计算出尾结点(entry)位置
    • zllen: uint16_32, < 65535 就是 ziplist 中 entry 结点数量,大于 65535 则需要遍历计算
    • entry x: 列表结点类型
    • zlend: 0xFF, 标记 ziplist 的结尾

    entry 结构:

    img

    • previous_entry_length : 记录 ziplist 前一个结点的长度 (1 字节 = 1111 1111 => max = 255) ,用于计算出该节点前一个节点的内存位置,以便于从表尾向表头进行遍历。

      ​ 前一个结点长度小于 254 字节,该属性为 1 字节. 如 前一结点长度为 5 字节, 则 previous_entry_length = 0x05;

      ​ 前一个结点长度大于 254 字节,则该属性为 5 字节,如 前一结点长度为 10086 字节

      ​ 则 previous_entry_length = 0xFE 00 00 27 66; (后 8 位是真实的长度)

    • encoding: 记录了节点content的属性所保存的类型和长度

    • content 保存节点具体的值,可以是一个字节数组或者一个整数,值的类型和长度由上面的encoding决定。

  • 存在的问题 : 连锁更新

    ziplist中有多个连续的、长度在250253字节的节点e1eN,则所有节点的previous_entry_length属性都是1字节。

    此时,将一个大于254字节的节点插入到 e1 之前,则 e1 的 previous_entry_length 需要扩充到5字节

    而这样会造成一个麻烦,此时由于e1增加了4个字节导致其从250253字节变成了254257字节,则e2的previous_entry_length也需要扩充到5字节。这样的更新会一直连锁到eN为止。上述情况称为连锁更新(cascadeupdate)

2. Redis 常用数据结构

Redis对象

  • 参考文献

    《Redis设计与实现》读书笔记(七) ——Redis对象综述及字符串对象实现原理

  • 简介

    • Redis 不是简单利用 sds, dict, skiplist, intset, ziplist 等数据结构,而是基于这些数据结构构建了一个对象系统, 这个对象系统包括 redis 客户端可以直接使用的 5 种: String, list, hash, set, zset.
    • 通过这五种对象,redis在执行命令前,会判断对象是否可以执行命令针对不同的场景(数据量、数据类型),redis可以给对象用不同的数据结构实现,达到最优化。
    • redis基于引用计数的内存回收机制,当不再需要对象时,自动释放相应内存;还通过引用计数实现对象的共享。
    • redis对象还带有访问时间信息,该信息可以计算数据库键的空转时长,在服务器启用memory功能的情况下,空转时长大的,内存不足时会优先被回收
  • 结构

    redis用对象存储键值对,因此每当创建一个键值对,至少会创建两个对象,一个是键对象,一个是值对象。 例如set msg ‘a’,创建了一个msg的键对象,一个a的值对象。

    //redis 每个对象都是由 redisObject 结构表示
    typedef struct redisObject {
        // 类型
        unsigned type:4;
        // 编码
        unsigned encoding:4;
        // 对象最后一次被访问的时间
        unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
        // 引用计数
        int refcount;
        // 指向实际值的指针
        void *ptr;
    } robj;
    

    使用 redisobject 的好处:

    • 根据对象类型判断一个对象是否可以执行给定的命令
    • 可以在不同的场景使用不同的实现方式,优化内存和查询速度

    《Redis设计与实现第二版》63页


  • 5 大数据结构由什么数据结构编码实现?

    每个值对象至少有两种不同的编码(底层使用至少两种数据结构实现)

    • string: 整数值(int) 字符串长度小于等于 32 字节(embstr) 字符串长度大于 32 字节(raw)

    • list: 列表对象中字符串元素长度小于64字节且元素数量小于 512 时 使用 ziplist 其余情况使用 linkedlist(即链表 list)

    • hash: 列表对象中字符串元素长度小于64字节且元素数量小于 512 时 使用 ziplist ``其余情况使用 hashtable(字典)`

    • set: 所有元素都是 整数 且元素数量小于 512 使用 整数集合(intset) 其余情况使用 hashtable(字典)

    • zset: 元素数量小于128 且 所有元素长度小于 64 使用 ziplist 其余情况使用 跳表(skiplist)

    • 在redis客户端,用object encoding命令,可以看到键对应的值的编码方式。

string 对象

  • int : 当字符串对象保存的是整数(只有整数,不含浮点数),并可以用long类型表示,则对象会将整数值直接保存在字符串对象ptr属性,并且把void类型改成long。这是唯一一种ptr属性直接保存值的情况,其他情况下ptr都是指向某个地址。

  • embstr, raw: 字符串长度小于等于 32 字节(embstr) 字符串长度大于 32 字节(raw)

    image-20200808095119451

  • 编码转换

    • 当对现有的字符串对象进行操作,重新赋值以后,如果新的值不满足原来的类型,如原来int后面变成string,则编码方式会转换。

    • 另外,由于redis的 embstr 编码方式没有任何修改的程序,因此embstr可以认为是只读的。因此,当embstr编码的对象进行任何的修改命令,都会将embstr编码转为raw编码(即使修改没有使string的字节超过32字节)。

list 对象

hash 对象

  • 结构

    image-20200808101628529

set 对象

  • 结构

    image-20200808101938215

zset 对象

  • 结构

    -通过 ziplist

    image-20200808102239869

    • 通过 skiplist + hashtable

      • skiplist: 适合范围查询
      • hashtable: 适合等值查询.
      • 使用两者结合,结合了范围查询和等值的优点

      image-20200808102628775

键空间

  • 结构

    typedef struct redisDb {
        // 数据库键空间,保存着数据库中的所有键值对.   键都是字符串对象,而值可以是 5 种对象中的任一种. 
        dict *dict;                 /* The keyspace for this DB */
        // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
        dict *expires;              /* Timeout of keys with a timeout set */
        // 正处于阻塞状态的键
        dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */
        // 可以解除阻塞的键
        dict *ready_keys;           /* Blocked keys that received a PUSH */
        // 正在被 WATCH 命令监视的键
        dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    
        struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
        // 数据库号码
        int id;                     /* Database ID */
        // 数据库的键的平均 TTL ,统计信息
        long long avg_ttl;          /* Average TTL, just for stats */
    } redisDb;
    

    image-20200808141300843

  • redis 读/写键过程

    redis对于读写键空间,还会有相应的维护操作。其中,写操作都会先读键,因此下列的读,也包括写之前的读操作。主要如下:

    1)读取一个键以后,服务器会根据键是否存在,来更新服务器中的键空间命中次数(hit)或未命中次数(miss)。这两个值可以在info status命令的keyspace_hits属性和keyspace_misses属性查看。

    2)读取一个键以后,服务器会更新键的LRU,即最后访问时间,redis可以通过当前时间减去lru,确认该键的闲置时间。可以通过命令object ideltime key来查看key键的当前闲置时间。

    3)读取一个键以后,发现该键已经过期,会先删除该键,然后再进行后续的操作。

    4)如果客户端使用watch命令监视某个键,则修改键的值后,键会被标记为脏(dirty),从而让事务程序注意到该键已经被修改过。

    5)服务器每次修改一个键后,都会对脏键计数器值增1,这个值会触发服务器持久化以及复制操作

    6)如果服务器开启数据库通知功能,则修改键后,服务器将按照配置,发送相应的数据库通知。

redis 数据库

  • 结构:

    typedef structredisServer{
    
    	//省略其他内容....
    	//存放数据库的数组
        redisDb *db;
    	//数据库的数量.初始化服务器的时候,会根据此值创建数据库个数。该属性由配置文件中的database选项决定。
        int dbnum;
    };
    

    image-20200808142315379

  • 切换数据库

    typedef structredisClient{
    	//如果此时在客户端select 0,则redisClient的db指针又会指向db[0]。
        //因此,select命令的原理,就是通过修改redisClient的db指针的指向,来实现数据库的切换。
        redisDb *db;
    
    }redisClient;
    

    image-20200808142437458

3. 位图

简介

  • Bitmaps 本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的进行操作。

  • Redis 中的命令 setbit, getbit, bitcount 详解

    setbit key offset value : 给一个指定 key 的值 的 第 offset 位 赋值为 value value : bool / int 返回值是 Long 0/1

    set andy a  // a => 97 => 0110 0001
    setbit andy 6 1 // 0110 0011
    setbit andy  // 0110 0010 => b
    get andy // b
    bitcount andy //3 bitcount 统计字符串中 1 的个数,  byte
    getbit andy 7 // 0 => 和 setbit 一样, 单位 bit,注意和 bitcount 之间的区别
    

    getbit key offset

    bitcount [start] [end] : bitcount key 0 -1 : key 对应值中 1 的个数, bitcount key 0 0:key 对应值中 第一个字节(8位) 1 的个数

    bitop op destkey key[key…] : 对不同的 bitmaps 进行运算, 结果存在destkey 中. 运算有: and(交集), or (并集), not (非), xor (异或)

    bitpos key targetBit [start] [end] : 计算Bitmaps中第一个值为targetBit的偏移量

使用场景

  • 统计活跃量:

    将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1没有访问的用户记做0,用偏移量作为用户的id。

    image-20200809102950986

    分析优劣:

    假设网站有1亿用户: (用户 id = 5 登录, 设置 第 4 位 为 1,表示用户登录过)

    • 每天独立访问的用户有5千万,如果每天用集合类型和Bitmaps分别存储活跃用户

      image-20200809104618950

    • 假如该网站每天的独立访问用户很少,例如只有10万(大量的僵尸用户)

      image-20200809104650867

    • bitmaps 上大部分位 为 0, 浪费空间,此时不适合使用 bitmaps 存储

4. HyperLogLog

  • HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计
  • HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

5. GEO

  • 支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能

  • Redis 使用 geohash 将二维经纬度转换为一维字符串

  • geohash有如下特点:

    GEO的数据类型为zset,Redis将所有地理位置信息的geohash存放在zset 中。

    字符串越长,表示的位置更精确,表3-8给出了字符串长度对应的精度,例如geohash长度为9时,精度在2米左右

    两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配算法实现相关的命令

    geohash编码和经纬度是可以相互转换的

  • geohash

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值