当前位置: 首页 > 后端技术 > Node.js

浅谈Redis的五大数据类型

时间:2023-04-03 18:17:34 Node.js

本平台不定时更新,如果喜欢我的文章,请关注我的微信公众号。上一篇文章提到过,Redis中最常用的数据类型有五种:String、List、Hash、Set、SortSet。上一篇简单介绍了五种数据类型的使用说明和常用场景。本文将从这五种数据类型的底层数据结构及其常用的操作命令进行分析。作为目前最流行的Key-Value内存数据库,Redis不仅在内存中进行数据库操作,还会周期性地将数据持久化到磁盘,因此其性能远高于普通数据库。在Redis中,每个Value其实都是用一个redisObject结构体来表示的:typedefstructredisObject{unsignedtype:4;无符号编码:4;无效*指针;int引用计数;unsignedlru:}我们可以看看这几个参数的含义:type:object数据类型,一般来说就是五大数据类型。encode:redisObject对象的底层编码实现。主要的编码类型包括简单的动态字符串、链接列表、字典、跳跃列表、整数集和压缩列表。*ptr:指向底层实现数据结构的指针。refCount:计数器,当引用计数值为0时,对象将被释放。lru:最后一次访问对象的时间。String数据类型String数据结构是一种简单的Key-Value类型,是Redis中最常用的数据类型。值可以是字符串或数字。String数据类型实际上可以存储三种不同类型的值:字符串、整数和浮点数。Redis如何自动识别字符串、整数、浮点数这三种不同类型的值。Redis是用C实现的,但它并没有使用C中的字符串。其实Redis自己实现了一个结构体SDS来代替String类型:structsdshdr{//记录buf数组使用的字节长度intlen;//记录buf数组剩余空间的长度intfree;//字节数组,用于存放字符串charbuf[];};我们可以看到free参数是用来判断剩余可用空间的长度的,len表示字符串的长度,buf存储字符串的每个字符和尾部的'0'。为什么Redis要自己实现SDS结构?因为SDS结构有几个优点:由于len保存的是当前字符串的实际长度,所以获取长度的时间复杂度为O(1)。SDS在拼接前会自动调整扩展当前字符串的空间,防止当前字符串数据溢出。减少内存分配的次数。当发生SDS拼接字符串时,如果此时字符串长度len小于1M,SDS会分配一个等于len大小的未使用空间来free。如果此时字符串长度len大于1M,那么SDS会分配1M未使用的空间来free。当字符串被缩短时,缩短的空间将被添加以供后续拼接使用。String数据类型常用命令:常用命令:set、get、decr、incr、mget等String数据类型适用场景:分布式锁分布式会话:将分布式应用会话存储在Redis中商品秒杀例程计数:博客数量、数量ofreadsList数据类型List数据结构用于存储多个有序的字符串,List中的每个字符串成为一个元素。列表提供了重新排列节点和顺序访问节点的能力。在Redis中,List可以在两端push和pop元素,也可以获取指定范围内的元素列表,获取目标元素等,List数据结构主要有两种实现方式:zipList(压缩链表)和LinkedList(双向链表)。首先,我们可以看一下链表的结构:typestructlist{//表头节点listNode*head;//表尾节点listNode*tail;//包含unsignedlonglen的节点总数;};可以看到每个LinkedList都会包含一个头节点head和一个尾节点tail。在LinkedList中,每个节点都会有一个指向前一个元素的prev和一个指向下一个元素的next。每个节点的值就是该节点的值。为了实现双向链表,其实和C中的双向链表很像。另外一个实现方式zipList,是基于连续内存实现的,有点类似于数组的方式,但是它是一个bit与数组不一致是因为zipList中每个entry的大小可能不一致,需要特殊的方法来控制解决,但是在进行push和pop操作的时候会时不时的发生数据迁移,时间复杂度为O(n),所以zipList一般只在元素比较少的时候使用。我们可以看一下zipList的结构:typestructziplist{//整个压缩列表的字节数uint32_tzlbytes;//记录压缩列表从尾节点到头节点的字节数,可以直接查到节点地址uint32_tzltail_offset;//记录节点数,有很多种,默认如下uint16_tzllength;//节点列表entryX;}zipList中的每个节点都会有如下参数信息:previous_entry_length:记录前一个节点的字节长度content:节点中存储的内容,可以是字节数组或整数encoding:记录content属性中存储的数据类型及长度*List数据类型的适用场景渲染文章列表时可以使用List数据类型。一般每个用户都会有一个自己发表的文章列表。如果需要显示文章列表,可以使用List数据类型。不仅可以拥有而且可以根据索引范围查询文章列表。Set数据类型Set数据类型有点类似于List数据类型,也可以用来存储多个元素,但最大的不同是Set数据类型不允许重复元素,Set中的元素出的顺序,所以没有方法和List一样是通过索引下标获取元素,但是Set类型支持多个Set集合的交集、并集、差集,所以合理的使用Set数据类型可以解决很多实际项目开发中的问题。Set数据类型有两种数据结构:IntSet和HashTable。首先我们看一下IntSet的结构:typedefstructintset{//encodingmethoduint32_tenconding;//设置的uint32_tlength中包含的元素个数;//包含元素的数组int8_tcontents[];}intset;当Set集合中的所有元素都是整数时,Redis只会使用IntSet数据结构。需要特别注意的一点是:IntSet数据结构是有序的。因为Redis为了降低性能消耗,当Set集合的元素都是整数时,Redis会使用基于动态数组的结构,同时在压入元素时控制元素大小的顺序,使得二进制可以使用搜索算法。元素被压入和弹出,所以时间复杂度仅为O(logN)。当Set集合中的元素有非整数数据时,此时Redis会自动使用HashTable数据结构来存储数据。在哈希表中,只存储键值,不存储值值,所以在哈希表中,键值始终为null。我们可以看看HashTable的结构:typedefstructdict{//Type-specificfunctiondictType*type;//两张哈希表,一张用于实时存储,一张用于rehashdicththt[2];//rehashindexdata迁移时使用unsignedrehashidx;}设置数据类型使用场景:记录唯一值:比如登录ip,身份证号和添加标签:可以通过标签的交集和并集来计算用户偏好等数据。Redis中的Hash数据类型Hash类型是指key本身是一个键值对结构,也就是我们所说的对象,所以Hash数据类型是最适合存储对象的数据类型。Hash数据类型的编码可以是zipList或HashTable。当哈希对象中存储的所有键值对长度小于64字节且元素个数小于512时使用zipList,否则使用HashTable。zipList与刚才List数据类型中提到的zipList基本相同,唯一不同的是Hash存储条目的数量是成对增加的,所以长度必须是2的整数倍。当然,使用zipList有刚才说了push和pop的时间复杂度是O(n),所以只能在数据量不大的时候使用。HashTable其实有点类似于Java中的HashTable。HashTTable主要依赖三个结构体:dict、dicttht、entry。三种结构之间的关系可以用下图表示:Hash数据类型适用场景:存储对象数据。结合Json描述对象集合。SortSet数据类型有序集合是在Set集合的基础上,保留了Set集合中不能有重复元素的特点,但不同的是SortSet集合中的元素是可以排序的,SortSet排序和List排序都可以使用索引标记作为排序依据,因此SortSet实现了有序数据和唯一键值对的集合。SortSet的数据结构有两种:zipList和skipList+HashTable,zipList不需要太多。用于数据量较小的情况,默认顺序是从小到大元素。使用skipList+HashTable的数据结构,skipList会在保证集合顺序的情况下优化范围查找的时间复杂度,而HashTable刚才也提到了可以优化push和pop元素的时间复杂度。skipList基于有序链表,可以创建多层索引,实现空间复杂度换时间复杂度的实践,最终实现时间复杂度O(logN)的元素查询过程。当需要push或pop元素时,使用HashTable实现的时间复杂度仅为O(1)。SortSet数据类型适用于场景分数排行榜:根据分数从小到大排序得到一定范围的数据:考试80-100分的数据欢迎关注公众号:周程序员先森