文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Redis五种数据类型详解

2023-04-25 10:40

关注

什么是 Redis

Redis 是基于内存的 K-V 数据库,常用于缓存消息队列分布式锁等场景,并且提供了常见的数据结构:字符串、哈希、列表、集合、带排序的集合

前置知识

Redis中的任意数据类型的键和值都会被封装为一个 RedisObject

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; 
    int refcount;
    void *ptr;
} robj;

type:指具体的对象类型

encoding:底层编码方式

lru:记录最后一次被访问的时间

refcount:对象引用计数器,计数器为 0 时表示没有被引用,可以回收

ptr:实际存储的值

五种数据结构

Redis中会根据存储的数据类型不同,选择不同的编码方式。每种数据类型的使用的编码方式如下:

数据类型编码方式
OBJ_STRINGint、embstr、raw
OBJ_LISTLinkedList和ZipList(3.2以前)、QuickList(3.2以后)
OBJ_SETintset、HT
OBJ_ZSETZipList、HT、SkipList
OBJ_HASHZipList、HT

Redis 的编码方式

Redis 中会根据存储的数据类型不同,选择不同的编码方式,共包含 11 种不同类型:

编号编码方式说明
0OBJ_ENCODING_RAWraw编码动态字符串
1OBJ_ENCODING_INTlong类型的整数的字符串
2OBJ_ENCODING_HThash表(字典dict)
3OBJ_ENCODING_ZIPMAP已废弃
4OBJ_ENCODING_LINKEDLIST双端链表
5OBJ_ENCODING_ZIPLIST压缩列表
6OBJ_ENCODING_INTSET整数集合
7OBJ_ENCODING_SKIPLIST跳表
8OBJ_ENCODING_EMBSTRembstr的动态字符串
9OBJ_ENCODING_QUICKLIST快速列表
10OBJ_ENCODING_STREAMStream流

String

介绍

字符串 (string) 其实是最简单的数据结构,不仅可以存储字符串,还可以存储整数、浮点数、二进制数据等;

常用命令

set key value:添加一个 string 类型的键值对

get key:获取 key 对应的 value

mset:批量设置对个键值对

mget:批量获取多个键值对

incr:让一个整形自增 1

setnx:添加一个 string 类型的字符串,前提是这个 key 不存在

使用场景

缓存对象

直接缓存整个 json 字符串对象 SET user:1 '{"name":"ezreal", "age":18}'

缓存更新策略

分布式锁

使用 setnx 指令可以实现分布式锁,若设置值成功则表示获取锁成功,设置失败则获取锁失败,注意要设置该 key 的过期时间,防止一直抢占该锁

常规计数

可以通过 incr 指令来统计数量,前提设置的 value 需要是整形

共享 session

我们可以把多台服务器上的 session 统一放到 redis 中,无论向集群哪台服务器发送消息,都保证去 redis 中获取 session 的信息

底层实现

redis 的底层实现是 SDS 字符串

SDS 结构体

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; 
    uint8_t alloc; 
    unsigned char flags; 
    char buf[];
};

len:该字符串的 长度

alloc:buf 申请的 总字节数

buf:实际存储数据的位置

flag:SDS 头类型,用来控制SDS头大小 :SDS_TYPE_5SDS_TYPE_8SDS_TYPE_16SDS_TYPE_32SDS_TYPE_64

Redis五种数据类型详解

整形的存储

如果存储的是一个整形,则则直接存储在 RedisObject 中即可;

SDS 动态扩容

假如现在在 name 字符串后加入 ezreal,形成新的字符串 'nameezreal'

List

介绍

列表是一个有序的字符串集合,可以通过下标索引来访问指定位置的字符串,还可以在列表头部或者尾部来添加或删除元素

常用命令

LPUSH KEY:向列表左侧插入元素

RPUSH KEY:向列表右侧插入元素

LPOP KEY:向列表左侧弹出元素

RPOP KEY:向列表右侧弹出元素

LRANGE KEY star end:获取指定范围内的 key

BLPOP / BRPOP:与 LPOP / RPOP 类似,只不过在没有元素时会等待

使用场景

消息队列

消息队列实现的三大要素:消息保序处理重复的消息保证消息的可靠性

我们可以基于 LPUSH / RPOP 的方法来实现一个队列,消息先进先出,保证消息的顺序;

Redis五种数据类型详解

当有一个缺点,消费者只能不断的轮询该队列是否有消息,因为消费者新增一条消息后是不能通知消费者的,所以消费者要不断轮询 while(1),不断的占用 CPU。我们可以使用 BRPOP 来优化,消费者没有获取到消息时,会自动阻塞,直到生产者放入新的消息。

我们可以为每个消息生成一个 唯一 的消息 ID,每次消费消息时,从数据库中判断该消息是否已经被处理了,防止消息的重复消息。

当消息被消费者获取后,没有处理完消息,机器就宕机了,这样不仅消息没有完全被消费,List 中的消息也丢失了,从而导致这个任务永远丢失了;

我们可以使用 Redis 中的BRPOPLPUSH命令,作用是把该消息从 List 中拿出来,同时还会把该消息放到另一个 List 中作留存。

底层实现

在 Redis 3.2 之前,List 的底层实现是压缩列表 ( ZipList) 或 双向链表

在 Redis 3.2 之后,List 的底层实现是 QuickList

ZipList

ZipList 又叫压缩列表,类似 "双向链表",但其不是链表。它由一段连续的内存块组成,可以在列表头部或者尾部添加或者删除元素。

Redis五种数据类型详解

zlbytes:整个 ZipList 的 内存字节数,占用 4 字节

zltail:从 ZipList 的起始地址到最后一个 entry 的内存字节数,占用 4 字节

zllen:ZipList 中 entry 的个数,占用 2 字节

zlend:结束标识 0xff,占用 1 字节

ZipEntry结构

Redis五种数据类型详解

previous_entry_length:上一个 entry 的长度,占用 1个 或 5个字节

encoding:记录 content 的类型 [ 整形 or 字符串 ] 和长度,占用 1个,2个 或者 5个字节

content:实际的内容

Encoding 编码

Entry 中的 encoding 编码分为字符串和整数两种:
字符串:如果 encoding 是以“00”“01”或者“10”开头,则证明 content 是字符串

Redis五种数据类型详解

整数:如果encoding是以“11”开始,则证明content是整数,且encoding固定只占用1个字节

编码编码长度整数类型
110000001int16_t(2 bytes)
110100001int32_t(4 bytes)
111000001int64_t(8 bytes)
11110000124位有符整数(3 bytes)
1111111018位有符整数(1 bytes)
1111xxxx1直接在xxxx位置保存数值,范围从0001~1101,减1后结果为实际值

可见,ZipList 通过记录上一个entry 的长度 和 自身entry 的长度(length + encoding + content),从而可以快速得到上一个 entry 和下一个 entry ,从而实现从头到尾 或 从尾到头的遍历。

QuickList

QuickList 是一个双向链表,它的每一个节点都是 ZipList

Redis五种数据类型详解

为了避免 QuickList 中的每个 ZipList 中 entry 过多,Redis 提供了一个配置项:list-max-ziplist-size来限制。

如果值为,则代表 ZipList 的允许的 entry 个数的最大值;

如果值为,则代表ZipList的最大内存大小,分5种情况:

其默认值为 -2:

以下是 QuickList 的和 QuickListNode 的结构源码:

QuickList

typedef struct quicklist {
    quicklistNode *head;		// 头结点指针
    quicklistNode *tail;		// 尾结点指针
    unsigned long count;    	
    unsigned long len;          
    int fill : QL_FILL_BITS;              
    unsigned int compress : QL_COMP_BITS; 
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

QuickListNode

typedef struct quicklistNode {
    struct quicklistNode *prev; // 前一个指针
    struct quicklistNode *next; // 后一个指针
    unsigned char *zl;  		// 当前结点的ziplist指针
    unsigned int sz;             
    unsigned int count : 16;     
    unsigned int encoding : 2;   
    unsigned int container : 2;  
    unsigned int recompress : 1; 
    unsigned int attempted_compress : 1; 
    unsigned int extra : 10; 
} quicklistNode;

整体结构

Redis五种数据类型详解

Hash

介绍

Hash 是一个键值对的集合,形如:key:{field1: value1,field1: value1},非常适合存储一个对象

Redis五种数据类型详解

常用命令

# 存储一个哈希表key的键值
HSET key field value   
# 获取哈希表key对应的field键值
HGET key field

# 在一个哈希表key中存储多个键值对
HMSET key field value [field value...] 
# 批量获取哈希表key中多个field键值
HMGET key field [field ...]       
# 删除哈希表key中的field键值
HDEL key field [field ...]    

# 返回哈希表key中field的数量
HLEN key       
# 返回哈希表key中所有的键值
HGETALL key 

# 为哈希表key中field键的值加上增量n
HINCRBY key field n

使用场景

缓存对象

例如下图,对于使用 string + json 还是用 hash 来存储,建议优先选择 string + json ,对于某些字段经常改变的话,可以考虑使用 hash

Redis五种数据类型详解

购物车

以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的 3 个要素,如下图所示。

Redis五种数据类型详解

涉及的命令如下:

当前仅仅是将商品 ID 存储到了 Redis 中,在回显商品具体信息的时候,还需要拿着商品 id 查询一次数据库,获取完整的商品的信息。

底层实现

Hash 的底层实现可以是 ZipList 或者 Dict

Dict

Dict 中维护一个 哈希表 DitHashtable,里面有一个 DictEntry 数组,用来存储实际的 key-value,每个槽位下的 DictEntry 都会形成一个链表,处理哈希冲突

Redis五种数据类型详解

向 Dict 插入一个元素时,先会根据 key 计算出一个哈希值 h,然后计算 h & sizemask 得到数组下标,最后加入该 DictEntry 即可

数据结构体

字典(Dict)

typedef struct dict {
    dictType *type; 		// dict 类型,不同的哈希函数
    void *privdata;  		// 私有数据,做特殊hash的时候用
    dictht ht[2]; 			// 包含两个hash表,一个是当前数据,另个为空,rehash的时候用
    long rehashidx; 		// rehash的进度
    int16_t pauserehash;	// rehash是否暂停 
} dict;

哈希表(DictHashTable)

typedef struct dictht {
    dictEntry **table;   		// 指向entry 的数组
    unsigned long size;  		// 哈希表大小
    unsigned long sizemask;   	// 掩码:size - 1
    unsigned long used;  		// entry 的个数
} dictht;

哈希节点(DictEntry)

typedef struct dictEntry {
    void *key;  // 键
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;// 值
    struct dictEntry *next;// 下一个结点
} dictEntry;

Dict 的 rehash

不管是扩容还是收缩,必定会创建新的哈希表,导致哈希表的 size 和 sizemask 变化,而 key 的查询与 sizemask 有关。因此必须对哈希表中的每一个 key 重新计算索引,插入新的哈希表,这个过程称为 rehash。过程是这样的:

Redis五种数据类型详解

Set

介绍

Set 是 Redis 中的无序集合,可以保证元素的唯一性,但不一定保证元素的有序性,可以对 set 取交集,并集等操作

常用命令

Set 常用操作

# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
# 从集合key中删除元素
SREM key member [member ...] 
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key

# 判断member元素是否存在于集合key中
SISMEMBER key member

# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]

Set 集合操作

# 交集运算
SINTER key [key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]

# 并集运算
SUNION key [key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]

# 差集运算
SDIFF key [key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...]

使用场景

点赞

使用 set 集合来存储,key 为文章 id,value为 用户 id 即可,获取该文章的点赞数量取该 set 中 id 的总数即可

关注

使用 set 集合来存储,key 为用户 id,value 为该用户关注的用户的 id,可以对两个 set 取并集,就可以找出它们的共同关注

抽奖

使用 set 集合来存储,key 为活动 id,value 为参见该活动的用户

底层实现

Set 底层是通过 IntSet(整数集合)或者 Dict 来实现的

Intset

Intset 是 Redis 中的一种实现模式,基于整数数组来实现,具备长度可变有序的特征

结构体

typedef struct intset {
    uint32_t encoding; 	// 编码方式
    uint32_t length; 	// 长度
    int8_t contents[]; 	// 存储位置
} intset;

encoding:表示存储整数的大小


#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

length:数组的长度

contents:存储实际的内容

Redis五种数据类型详解

encoding:4字节

length:4字节

contents:2字节 * 3 = 6 字节

扩容过程

我们向该其中添加一个数字:50000,这个数字超出了 int16_t 的范围,intset 会自动升级编码方式到合适的大小。
以当前案例来说流程如下:

Redis五种数据类型详解

小结:

ZSet

介绍

ZSet 是一个有序集合类型,比 Set 多出了一个 score 字段,该字段用来对 value 值进行排序,保证 ZSet 中的值是有序的

所以每一个元素都有两个值,一个 score,用来排序,一个 value 存储实际的值

Redis五种数据类型详解

常用命令

ZSet 常用操作

# 往有序集合key中加入带分值元素
ZADD key score member [[score member]...]   
# 往有序集合key中删除元素
ZREM key member [member...]                 
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key 

# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member 

# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]

# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]

ZSet 运算操作

# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...] 
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...]

使用场景

排行榜

以文章排行榜为例子,使用 article:rank 作为 key,点赞数量作为 score,文章的 id 作为 value 即可,根据 score 从大到小进行排序,则可以得到一个 文章热度的排行榜。

底层实现

ZSet 使用 压缩列表 或者 跳表 + 字典来实现

Ziplist 本身是没有排序功能的,而且没有键值对的概念,需要通过编码来实现:

Redis五种数据类型详解

同时满足一下条件时,会选择使用 Ziplist

否则使用 跳表 +Dict

使用 Dict 存储 valuescore 的映射关系;

使用 跳表 来有序的存储 score 和 value 值,便于范围查找

Redis五种数据类型详解

SkipList

跳表是一个并联多个有序链表的数据结构,它可以在 O(logn) 的时间复杂度内完成插入、删除、增加的操作;

Redis五种数据类型详解

特点

跳表代码

public class SkipList {

    private final int MAX_LEVEL = 32;

    private final double factor = 0.25;

    private int level = 1;

    
    private final Node head = new Node(null, MAX_LEVEL);

    static class Node {
        Integer value;
        Node[] next;
        int size;

        public Node(Integer value, int size) {
            this.value = value;
            this.size = size;
            this.next = new Node[size];
        }
    }

    public boolean search(int value) {

        Node p = head;
        for (int i = level - 1; i >= 0; i--) {
            while (p.next[i] != null && p.next[i].value <= value) {
                if (p.next[i].value == value) {
                    return true;
                }
                p = p.next[i];
            }
        }
        return false;
    }

    public void insert(int value) {
        int randomLevel = randomLevel();
        Node newNode = new Node(value, randomLevel);

        Node p = head;
        for (int i = level - 1; i >= 0; i--) {
            while (p.next[i] != null && p.next[i].value <= value) {
                p = p.next[i];
            }

            if (p.next[i] == null) {
                p.next[i] = newNode;
            } else {
                newNode.next[i] = p.next[i];
                p.next[i] = newNode;
            }
        }

        if (randomLevel > level) {
            for (int i = level ; i < randomLevel ; i++) {
                head.next[i] = newNode;
            }
            level = randomLevel;
        }
    }

    public boolean delete(int value) {
        boolean contain = search(value);
        Node p = head;
        Node h = null;
        if (contain) {
            for (int i = level - 1; i >= 0; i--) {
                while (p.next[i] != null && p.next[i].value <= value) {
                    h = p;
                    p = p.next[i];
                }
                h.next[i] = p.next[i];
            }
        }

        return contain;
    }

    private int randomLevel() {
        int level = 1;

        Random random = new Random();
        while (random.nextDouble() <= factor && level <= MAX_LEVEL) {
            level++;
        }
        return level;
    }
}

以上就是Redis五种数据类型详解的详细内容,更多关于Redis数据类型的资料请关注我们其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯