zoukankan      html  css  js  c++  java
  • Redis的基础类型(二)

    Hash哈希

    存储类型
    hash用来存储多个无序的键值对。最大存储数量2^32-1(40亿左右)

    注意:前面我们说Redis所有的KV本身就是键值对,用dictEntry实现的,叫做外层的哈希、现在我们分析的是内层哈希。

    同样是存储字符串,Hash与String的主要区别?
    1.把所有相关的值聚集集中到一个key中,节省内存空间;
    2.只是用一个key,减少key冲突;
    3.当需要批量获取值的时候,只需要使用一个命令,减少内存IO/CPU的消耗
    Hash不适合的场景:
    1.Field不能单独设置过期时间
    2.需要考虑数据量分布的问题(field非常多的时候,无法分布到多个节点)

    操作指令

    hset h1 f 6
    hset h1 e 5
    hmset h1 a 1 b 2 c 3 d 4 
    
    hget h1 a 
    hmget h1 a b c d 
    hkeys h1
    hvals h1
    hgetall h1
    
    # key操作
    hdel h1 a
    hlen h1
    

    存储原理
    Redis的Hash本身也是一个KV的结构。是不是与外层的哈希一样,用dictEntry实现呢?

    内存的哈希底层可以使用两种数据结构实现:
    ziplist: OBJ_ENCODING_ZIPLIST(压缩列表)
    hashTable:OBJ_ENCODING_HT(哈希表)

    hset h2 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    hset h3 f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    object encoding h2
    object endoding h3
    

    ziplist压缩列表

    ziplist是一个经过特殊编码的,由连续内存块组成的双向链表。

    它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度。这样读写可能会慢一些,因为要计算长度,但是可以节省内存,是一种时间换空间的思想。

    ziplist的内部结构?源码ziplist.c第16行的注释:

    • ...

    那么上面entry的内容呢?ziplist.c

    typedef struct zlentry {
        unsigned int prevrawlensize; /* 存储上一个链表节点的长度数值所需要的字节数 */
        unsigned int prevrawlen;     /* 上一个链表节点占用的长度 */
        unsigned int lensize;        /* 存储当前链表节点长度数值所需要的字节数 */
        unsigned int len;            /* 当前链表节点占用的长度 */
        unsigned int headersize;     /* 当前链表节点的头部大小(prevrawlensize + lensize.),即非数据域的大小 */
        unsigned char encoding;      /* 编码方式 */
        unsigned char *p;            /* 压缩链表以字符串的形式保存,该指针指向当前节点起始位置 */
    } zlentry;
    

    所以展开应该是这个样子的:

    编码有哪些?

    #define ZIP_STR_06B(0<<6) //长度小于等于63字节
    #define ZIP_STR_14B(1<<6) //长度小于等于16383字节
    #dedine ZIP_STR_32B(2<<6) //长度小于等于4294967295字节
    

    什么时候使用ziplist存储?

    当hash对象同事满足以下两个条件的时候,使用ziplist编码:
    1)哈希对象保存的键值对数量小于512个;
    2)所有的键值对的键和值的字符串长度都小于64byte(一个英文字母一个字节)。

    src/redis.conf配置

    hash-max-ziplist-value 64 //ziplist中能最大存放的值长度
    hash-max-ziplist-entries 512 //ziplist中最多能存放的entry节点数量
    

    如果超过这两个阈值的任何一个,存储结构就会转换成hashTable。
    总结:字段个数少,字段值少,用ziplist。

    hashTable(dict)

    在Redis中,hashTable被称为字典(dictionary)。
    前面分析,Redis的KV结构是通过一个dictEntry来实现的。
    在hashTable中,又对dictEntry进行了多层的封装。

    源码位置:dict.h 47行。

    typedef struct dictEntry {
        void *key; /* key关键字定义 */
        union {
            void *val;
            uint64_t u64; /* value定义 */
            int64_t s64;
            double d;
        } v;
        struct dictEntry *next; /* 指向下一个键值对节点 */
    } dictEntry;
    

    dictEntry放到了dictht(hashTable里面)

    /* This is our hash table structure. Every dictionary has two of this as we
    * implement incremental rehashing, for the old to the new table. */
    typedef struct dictht {
    dictEntry **table; /* 哈希表数组 */
    unsigned long size; /* 哈希表大小 */
    unsigned long sizemask; /* 掩码大小,用于计算索引值。总是等于size-1 */
    unsigned long used; /* 已有节点数 */
    } dictht;
    

    ht放到了dict里面:

    typedef struct dict {
    dictType *type; /* 字典类型 */
    void *privdata; /* 私有数据 */
    dictht ht[2]; /* 一个字典有两个哈希表 */
    long rehashidx; /* rehash 索引 */
    unsigned long iterators; /* 当前正在使用的迭代器数量 */
    } diet;
    

    从底层到最高层dictEntry---dictht--dict。他是一个数组+链表的结构。
    哈希的整体存储结构:

    dictht 后面是NULL说明第二个ht还没用到。dictEntry*后面是NULL说明没有hash到这个地址。dictEntry后面是NULL说明没有发生哈希冲突。

    QA:为什么要定义两个哈希表,其中一个不用呢?

    redis的hash默认使用的是ht[0], ht[1]不会初始化和分配空间。
    哈希表dictht是用链地址法来解决碰撞问题的。在这种情况下,哈希表的性能取决于它的大小(size属性)和它所保存的节点数量(userd属性)之间的比率:
    - 比率在1:1时(一个哈希表ht只存储一个节点entry),哈希表的性能最好;
    - 如果节点数量比哈希表的大小要大很多的话(这个比例用ratio表示,5表示平均一个ht存储5歌entry),那么哈希表就会退化成多个链表,哈希表本身的性能就不再存在。
    
    如果单个哈希表的节点数量过多,哈希表的大小需要扩容。Redis里面的这种操作叫做rehash。
    rehash的步骤:
    1、为字符ht[1]哈希表分配空间。ht[1]的大小为第一个大于等于ht[0].used *2的2的N次方幂。比如已经使用了10000,那就是16384.
    2、将所有的ht[0]上的节点rehash到ht[1]上,重新计算hash值和索引,然后放入指定的位置。
    3、当ht[0]全部迁移到ht[1]之后,释放ht[0]的空间,将ht[1]设置为ht[0]表,并创建新的ht[1],为下次rehash做准备。
    
    

    QA:什么时候触发扩容?

    负载因子(源码dict.c)

    static int dict_can_resize = 1; //是否需要扩容
    static unsigned int dict_force_resize_ratio = 5; //扩容因子
    

    扩容判断和扩容操作类似HashMap,也有缩容。
    总结:Redis的Hash类型,可以用zipList和hashTable实现。

    应用场景

    和string一样

    String可以做的事情,Hash都可以做。

    存储对象类型的数据

    比如对象或者一张表的数据,比String节省了更多key的空间,也更加便于集中管理。

    购物车的操作

    key:用户id; field :商品id; value :商品数量。

  • 相关阅读:
    庄家试盘的K线形态
    股票基本知识入门提纲
    我与猫
    夜雨不眠时
    快速排序
    由float转std::string的方法
    BugFree + EasyPHP在Windows平台搭建步骤详解
    安装VS2008的时候Windows Mobile 5.0 SDK R2 for pocket pc错误解决方案
    收集WCF文章
    linq to ef(相当于sql中in的用法)查询语句
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14249682.html
Copyright © 2011-2022 走看看