Redis(一):基础和数据类型

目录


一、谈谈Redis?

        Redis是一种以键值对形式存储数据的非关系型数据库,基于内存读写数据,速度非常快;支持持久化,广泛用于分布式缓存。而且支持丰富的数据结构,比如:String、Hash、List、Set、SortedSet、BitMap、HyperLogLog、GEO...可以满足不同场景的需要。

        并且Redis可以实现一些功能,比如:用Redis实现分布式锁、Redis+Lua实现限流、Redis实现消息队列、Redis实现延时队列。


二、Redis的数据类型

1.String

        Redis中的String是二进制安全的,可以存储任何类型的数据,比如:字符串、整数、浮点数、图片、对象。可以用来缓存对象、计数、共享session信息。

        而且呢,Redis的String类型是优化过的,Simple Dynamic String,即 “简单动态字符串”

        和C语言的字符串相比,它有几点优势:

        1.获取字符串长度的时间复杂度更低

        C语言的字符串:使用了一个长度为 n+1 的字符数据来表示长度为 n 的字符串,最后一个元素是 \0,他不会保存数组的长度,所以获取字符串长度的时间复杂度是O(n);

        SDS:使用 len 字段来表示字符串的长度,可以直接获取,时间复杂度是O(1)

        2.不会造成缓冲区溢出

         C语言的字符串:由于不记录自身长度,所以很容易造成缓冲区溢出;

         SDS:对字符串进行操作时,会借助 len 和 alloc 检查空间是否满足修改的需求,如果不够的话,会自动扩展空间,不会出现溢出的情况

        3.是二进制安全的

        C语言的字符串:因为它的最后一个字符是 \0,所以,如果中间遍历到一个字符也是'\0',就误以为走到了最后一个字符而提前结束遍历

        SDS:是二进制安全的,写入什么,读取就是什么,不会做任何的过滤和限制

        4.可以有效减少重新分配内存的次数

        C语言的字符串:底层使用字符数组实现的,所以在进行增删操作时,会改变底层数组的大小,需要重新分配内存

        SDS:使用【空间预分配】和【惰性空间释放机制】,可以有效减少内存分配的次数


2.压缩列表

        压缩列表,“压缩”嘛,顾名思义,是一种内存紧凑型的数据结构,占用一块连续的内存空间。

        它的数据结构是这样设计的:

       

        优点:

        1.根据prevlen 和 encoding字段,针对不同类型或不同长度的数据,按需分配空间,节省内存

        缺点:

        1.查找元素时,如果查找的是第一个或最后一个元素,可以直接通过 zllen 字段定位,时间复杂度是O(1),但是查询其他位置的元素时,时间复杂度是O(n),所以它不适合存储过多元素

        2.由于每一个节点都会存储前一个节点的长度,即 prevlen 字段,所以当新增元素或修改元素时,如果空间不够,就需要重新分配内存,导致后面元素的 prevlen 要发生变化,也就是说每个元素都需要重新分配空间,引发连锁更新问题


3.quickList

刚才提到压缩列表可能会有连锁更新问题,那么,救星来了,quickList 和 listPack 闪亮登场!

先说结论:

  • quickList是通过控制压缩列表的元素数量,来缓解连锁更新带来的负面影响
  • listPack是彻底解决了连锁更新的问题

quickList 是由【双向链表】+【压缩列表】实现的,quickList就是一个链表,链表中的每个元素是一个压缩列表

        往quickList中添加元素时,它并不会像普通链表那样,直接新建一个链表节点,而是会先检查一下,插入位置的压缩列表是否可以容纳这个元素?

        —如果能容纳:就直接保存到 quickListNode 结构中的压缩列表,不需要新建节点

        —如果不能容纳:再新建一个 quickListNode 节点

        所以,quickList控制了压缩列表的元素个数,压缩列表的元素越少,连锁更新带来的影响就越小


4.ListPack

为啥压缩列表会有连锁更新问题呢?看这个

        就是因为当前节点记录了前一个节点的长度,导致【当前节点】和【前一个节点】产生依赖关系,才会产生多米诺牌效应嘛。。

        如果当前节点只记录属于自己的东西,那【当前节点】和【前一个节点】不就解耦了嘛!

        没错,ListPack就是这么做的,len 字段记录的是当前节点的长度

        所以当我们向listpack添加新元素时,不会影响其他节点的字段的变化,从而避免了连锁更新的问题~~


5.BitMap

BitMap,位图,可以看作是一个存储二进制数字的数组

因为bit是计算机中的最小单位,所以使用它来存储是非常节省空间的,特别适合数据量大、使用二至统计的场景,比如在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态、判断用户的登陆状态、统计用户行为.......

写到这里我想到,之前在公司实习的时候,项目就用到 【状态压缩】 来节省内存占用,我们的订单有一个属性是:是否使用优惠券。

—如果不使用状态压缩,我们可能需要两个独立的变量,一个整数变量来表示支付状态,另一个布尔变量来表示是否使用优惠券。

—使用状态压缩的话,可以用二进制位来表示状态和属性。比如00表示未支付,01表示已支付待确认,10表示支付成功;再用一位二进制数表示是否使用优惠券,0表示未使用,1表示使用。这样的话,一个三位的二进制数就可以完整的表示状态和优惠券属性了,可以通过位运算快速地进行状态的判断和转换。nice!


6.跳表

        链表在查找元素的时候,需要逐一查找,除了首元素外的查询时间复杂度是O(n),太低效了...

        于是就有了跳表,跳表就是在链表的基础上做了改进,实现了一种 “多层” 的有序链表,其实就是添加了多级索引

        每一层可以包含多个节点,每个节点通过指针进行连接

        具体实现:跳表节点结构体中的 zSkipListLevel 结构体

        —level 数组中的元素代表跳表的一层,比如 level[0] 表示第一层,level[1] 表示第二层

        —zSkipListLevel 结构体中,还定义了【指向下一个跳表节点的指针】和【跨度】,跨度用来记录两个节点之间的距离

那跳表是怎么设置层高的呢?

跳表在创建节点时,会生成一个范围为[0,1]的随机数,如果这个随机数小于0.25(相当于概率是25%),就会增加一层;然后继续生成下一个随机数,直到随机数大于0.25时结束,最终确定这个节点的层数。

这样的做法,相当于增加一层的概率不超过25%,层高的最大限制是64


总结

2024/9/26 17:46 停笔收工~ 

今天看到一句话很喜欢:时间用它独有的刻薄方式令我们渐渐宽宏,明白不管被生活怎样对待,依然要许诺自己明日必有太阳。💖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值