Redis对象篇--使用+数据结构底层速览

108 阅读8分钟

REDIS知识点汇总

Redis快速使用

  • 在windows安装目录下 打开cmd 输入redis-server.exe redis.windows.conf运行,不能关
  • 本机的默认用户和密码为:root、123456
  • 通过客户端Another Redis Desktop Manager进行图形化连接使用,测试常见的redis命令正确性

Redis数据结构

Redis Object

Object是什么?

Redis是双列存储的NoSQL非关系型数据库,key和value都能抽象成对象,key只能是String型的对象,而value可以各种类型主要有String、List、SET、Zset、Hash、Stream等

Object底层数据结构?

Redis是基于C语言实现的,结构体主要包括:type类型、encoding编码方式、refcount引用指针、ptr内容指针等

String类型

String常见操作

  • 不带参数的创建和更新: set

        set testString1 123
    
  • 带参数的创建和更新

    如果不存在就创建 setnx(set not exist)

     setnx testString1 123
    

设置单位为s的过期时间 setex(时间写在value前面)

设置单位为ms的过期时间 setpx

SETEX testString1 10 abc
  • 获取参数 get、mget

    get testString1
    mget teStString1 testString2
    
  • 删除 del

     del testString1
    

String的编码方式

说明:上文提到object结构体中有几个数据段 此时type为String encoding方式为下面三种

  • long范围类的整数时 为INT
  • 小于等于阈值时 是EMBSTR(这个阈值在3.2之前是39,之后是44)
  • 大于阈值时 是RAW

set一个已有对象会发生什么?

如果是同一个类型对象,会更新这个值并覆盖或擦除过期时间,如果是不同类型的值,就会删除原来类型的值,新建一个指定类型的值

浮点型会以什么编码?

浮点型会以EMBSTR或者RAW编码

String可以是多大?

我当前使用的是5.05版本,源码中注明是512MB

SDS有什么用?

SDS:simple dynamic string 简单动态字符串,用C语言指定了一个字符串结构体包含 长度 占用大小和指向byte指针

C语言原来的字符是char数组 以\0结尾,二进制不安全,SDS二进制安全

SDS可以直接获得String字符串大小获取时间是O(1),原来要遍历

SDS有预留空间

List类型

List说明

List是双端队列,首部和尾部都可以进入和输出

List常见操作

  • 左右添加元素,返回总元素个数:lpush、rpush
  • 左右移出并返回第一个元素:lpop、rpop

  • 删除list中等于某个value的key,返回个数:lrem

     lrem testList1 0 abcd//0表示删除所有,大于0表示从左往右,小于0表示1从右往左
    
  • 同步删除 del key、异步删除unlike key
  • 返回值 lrange key start stop
  • 返回长度 len key

编码方式

object字段结构体主要有type、encoding、ptr(指向ziplist、linkedlist、quicklist)、refcount(引用指针)

ZIPLIST结构体包含的字段:ZBYTES(总长度)、ZTAIL(尾巴节点) ZLEN(数据节点个数)、 ENTRY1(数据节点1)、 ENTRY2(数据节点2) 、ENTRY3(数据节点3)、ZLEND(结束标志)

ZIPLIST中的ENTRY结构体包括:PRELEN[上一个节点长度,如果上一个节点小于等于254就是1B,如果大于255就是5B{1B标识、4B存储大小}]、ENCODING[编码方式(String,int之类的),包含正向遍历的长度信息]、ENTRY-DATA(数据节点)

当OBJECT的TYPE为LIST时,当长度大于64B或者512个时,变成LINKEDLIST

ZIPLIST有什么优势?

相比于LINKEDLIST,ZIPLIST数据节点存储更加紧凑,更加节约内存,方便一次性分配空间,遍历时空间局部性好

ZIPLIST是如何从前往后和从后往前遍历的?

ZIPLIST从前往后遍历可以根据ENTRY-DATA中的ENCODING中的当前节点长度来从前往后遍历,ZIPLIST从后往前可以根据ZTAIL,ENTRY-DATA中的PRELEN来从后往前遍历。

ZIPLIST查询节点个数时间?

ZIPLIST中有ZLEN字段,可以直接查询节点个数,但这个节点只有2B

ZIPLIST插入时间复杂度是多少?

数组O(N)

ZIPLIST连续更新有什么问题?

ZIPLIST结构体字段的ENTRY-DATA中记录了PRELEN,这里PRELEN如果小于254 就占1B 否则占5B ,所以一旦一个节点长度发生变化,就会一系列地都发生变化

如何解决ZIPLIST连锁更新的问题?

使用LISTPACK结构替代ENTRY-DATA结构体,LISTPACK结构体包含ENCODING-TYPE ,ELEMENT-DATA,ELEMENT-TOT-LEN(整个节点除它自身长度之外的长度,每1B 第一位0结束,1继续)

关键是ELEMENT-TOT-LEN,从后往前找到每个字节第一个为0的字节,计算前面这个节点的长度。

SET类型

SET类型常见操作

说明:set是无序集合

  • 写操作:

    • SADD KEY MEMBER1 MEMBER2 MEMBER3...【返回值是集合数量】
    • SREM KEY MEMBER1 MEMBER2 MEMBER3...【删除为key的集合中MEMBER1的set,返回删除数量】
  • 读操作:

    • SISMEMBER KEY MEMBER1 【查看集合key中是否有MEMBER1】
    • SCARD KEY【查看集合KEY中有多少个元素】
    • SMEMEBERS KEY【查看KEY中所有的元素】
    • SSCAN key cursor [MATCH pattern] [COUNT count]SSCAN命令返回的结果是一个包含两个元素的数组,第一个元素是下一个迭代器的游标,第二个元素是一个数组,包含了迭代到的成员】
    • SINTER KEY1[KEY2,KEY3.....]【求交集,返回在KEY1中,又在KEY2,KEY3中所有的元素】
    • SDIF KEY1[KEY2,KEY3.....]【返回在KEY1中,但不在KEY2,KEY3中所有的元素】
    • SUNION KEY1[KEY2,KEY3.....]【求并集】

SET编码方式

说明:REDIS的所有OBJECT的结构体字段包含:TYPE(这里是SET型) ENCODING REFCOUNT PTR(内容指针)

  • 当元素小于等于512个时,采用INTESET(整数集合),节约内存,二分查找时间复杂度O(log2N)
  • 当元素大于512时,采用HASHTABLE(字典),查找时间复杂度O(1)

ZSET类型

说明:ZSET是带分数的有序集合

ZSET的常见操作

  • 写操作:

    • ZADD KEY SCORE1 MEMBER1[SCORE2 MEMEBER2.....]
    • ZREM KEY MEMBER1 [MEBER2 MEMBER2.....]
  • 读操作:

    • ZCARD KEY 【返回有序集合数量】
    • ZCOUNT KEY MIN MAX 【计算SCORE在MIN-MAX之间的集合】
    • ZSCORE KEY MEMBER1【返回MEMBER1的分数】
    • ZRANK KEY MEMBER1 【返回MEMEBER1的SCORE排名】
    • ZRANGE/ZRERANGE KEY START STOP [WITHSCORES]【返回指定的有序集合,可选带分数,和list的lrange用法类似】【ZRANGE从小到大 ZRERANGE从大到小】

ZSET的编码方式

  • 当元素个数不超过128个,小于64B时用ZIPLIST KEY和SCORE两个连在一起的ENTRY-DATA/LISTPACK
  • 当元素超过128时,用SIPLIST+HASHTABLE(字典)

SKIPLIST

跳表:多层链表,最底层链表步长为1,依次往上二级索引步长为2,三级索引步长为3,每层按照SCORE升序来排列,所以从最上层开始找,因为从小到大排列,所以如果目标比当前节点小,就继续在这一层查找,如果目标比当前节点大,就进入下一层,因为每个节点都由指向下一层的指针,所以下一层不用从头开始查找。

REDIS中SKIPLIST单个节点的level

REDIS用概率均衡策略,每个节点是否增加一层的概率是25%,REDIS5中是64层,REDIS7中是32层,调表的时间复杂度是O(LOG2N)

SKIPLIST时间复杂度

插入不会影响层高,层高在创建就确定了,查找节点总数时间是O(1),查找某个值的分数时间是O(LOG2N)

HASHTABLE(字典)下查总数时间复杂度

O(1)

HASHTABLE结构

和Java中的一样,数组+链表,通过HASH值和数组长度得到数组下标

HASHTABLE扩容和缩容

  • 负载因子=节点数量/数组长度,当负载因子<0.1就缩容
  • 负载因子>5强制扩容,负载因子>=1时看是否在执行BGSAVE和BGREWRITEAOF
  • HASHTABLE执行rehash期间,会新分配1号表,每次执行增删改查,就会将当前指针元素复制到1号表,直到全部复制结束,1号表变成0号表,0变成1,大小是2的幂次
  • 缩容和扩容机制一样,不过大小是按使用节点的最近2的幂次来决定的,比如used=6 那就是8,最小是4

HASH类型

说明:双列

  • 写操作:

    • HSET/HMSET KEY FILED1 VALUE1 FIELD2 VALUE2......P【老版本的Hset只能设置单值】
    • HSETNX KEY FILED1 VALUE1 FIELD2 VALUE2......
    • HDEL FIELD1 FIELD2
    • DEL KEY
  • 读操作:

    • HGETALL KEY
    • HGET KEY FIELD1
    • HLEN KY
    • HSCAN key cursor [MATCH pattern] [COUNT count]SSCAN命令返回的结果是一个包含两个元素的数组,第一个元素是下一个迭代器的游标,第二个元素是一个数组,包含了迭代到的成员】

HASH 编码方式

  • 当小于512个时 用ZIPLIST
  • 大于512时 用HASHTABLE