redis基础

一、相关文档
redis入门级:redis入门

跳跃表(redis的有序集合sorted-set):跳跃表相关

redis雪崩穿透一致性:一致性

Redis 命令参考:基础命令

try Redis(不用安装Redis即可体验Redis命令):http://try.redis.io/
二、学习笔记
为什么要用redis?
从上面可知:Redis是基于内存,常用作于缓存的一种技术,并且Redis存储的方式是以key-value的形式。

我们可以发现这不就是Java的Map容器所拥有的特性吗,那为什么还需要Redis呢?

Java实现的Map是本地缓存,如果有多台实例(机器)的话,每个实例都需要各自保存一份缓存,缓存不具有一致性

Redis实现的是分布式缓存,如果有多台实例(机器)的话,每个实例都共享一份缓存,缓存具有一致性。

Java实现的Map不是专业做缓存的,JVM内存太大容易挂掉的。一般用做于容器来存储临时数据,缓存的数据随着JVM销毁而结束。Map所存储的数据结构,缓存过期机制等等是需要程序员自己手写的。

Redis是专业做缓存的,可以用几十个G内存来做缓存。Redis一般用作于缓存,可以将缓存数据保存在硬盘中,Redis重启了后可以将其恢复。原生提供丰富的数据结构、缓存过期机制等等简单好用的功能。

为什么要用缓存?
如果我们的网站出现了性能问题(访问时间慢),按经验来说,一般是由于数据库撑不住了。因为一般数据库的读写都是要经过磁盘的,而磁盘的速度可以说是相当慢的(相对内存来说)

redis的数据结构
redis底层是用c语言写的,常见的数据类型为string、list、hash、set、sortset。存储时,redis用k-v(key-value)形式存储,其中key的数据类型均为字符串,而value的数据类型可以为刚才提到的五种常见数据类型。
但要值得注意的是:Redis并没有直接使用这些数据结构来实现key-value数据库,而是基于这些数据结构创建了一个对象系统。

简单来说:Redis使用对象来表示数据库中的键和值。每次我们在Redis数据库中新创建一个键值对时,至少会创建出两个对象。一个是键对象,一个是值对象。

Redis中的每个对象都由一个redisObject结构来表示:

在这里插入图片描述
简单来说就是Redis对key-value封装成对象,key是一个对象,value也是一个对象。每个对象都有type(类型)、encoding(编码)、ptr(指向底层数据结构的指针)来表示。

redis中的字符串:简单动态字符串(Simple dynamic string,SDS)
Redis使用sdshdr结构来表示一个SDS值:
在这里插入图片描述
使用SDS字符串的优点(和c语言比较)

sdshdr数据结构中用len属性记录了字符串的长度。那么获取字符串的长度时,时间复杂度只需要O(1)。

SDS不会发生溢出的问题,如果修改SDS时,空间不足。先会扩展空间,再进行修改!(内部实现了动态扩展机制)。

SDS可以减少内存分配的次数(空间预分配机制)。在扩展空间时,除了分配修改时所必要的空间,还会分配额外的空闲空间(free 属性)。

SDS是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据。

redis中的链表存储方式
首先,redis使用listNode结构来表示每个节点:
在这里插入图片描述
然后通过list来持有listNode组成链表:
在这里插入图片描述
结构如图所示:
在这里插入图片描述
redis链表的特性:
无环双向链表

获取表头指针,表尾指针,链表节点长度的时间复杂度均为O(1)

链表使用void *指针来保存节点值,可以保存各种不同类型的值

redis实现的哈希表:
redis中,key-value结构的底层实现就是用的哈希表,实际上可以简单理解哈希表就就是数组+链表的结构。
哈希表的结构用dictht来定义,然后调用每个节点的定义dictEntry,并且在最上层又封装了一层dict。总体结构如下图:在这里插入图片描述
从代码实现和示例图上我们可以发现,Redis中有两个哈希表:

ht[0]:用于存放真实的key-vlaue数据

ht[1]:用于扩容(rehash)

Redis中哈希算法和哈希冲突跟Java实现的差不多,它俩差异就是:

Redis哈希冲突时:是将新节点添加在链表的表头。

JDK1.8后,Java在哈希冲突时:是将新的节点添加到链表的表尾。

下面来具体讲讲Redis是怎么rehash的,因为我们从上面可以明显地看到,Redis是专门使用一个哈希表来做rehash的。这跟Java一次性直接rehash是有区别的。

在对哈希表进行扩展或者收缩操作时,reash过程并不是一次性地完成的,而是渐进式地完成的。

Redis在rehash时采取渐进式的原因:数据量如果过大的话,一次性rehash会有庞大的计算量,这很可能导致服务器一段时间内停止服务。

Redis具体是rehash时这么干的:

(1:在字典中维持一个索引计数器变量rehashidx,并将设置为0,表示rehash开始。

(2:在rehash期间每次对字典进行增加、查询、删除和更新操作时,除了执行指定命令外;还会将ht[0]中rehashidx索引上的值rehash到ht[1],操作完成后rehashidx+1。

(3:字典操作不断执行,最终在某个时间点,所有的键值对完成rehash,这时将rehashidx设置为-1,表示rehash完成

(4:在渐进式rehash过程中,字典会同时使用两个哈希表ht[0]和ht[1],所有的更新、删除、查找操作也会在两个哈希表进行。例如要查找一个键的话,服务器会优先查找ht[0],如果不存在,再查找ht[1],诸如此类。此外当执行新增操作时,新的键值对一律保存到ht[1],不再对ht[0]进行任何操作,以保证ht[0]的键值对数量只减不增,直至变为空表。

redis的跳跃表shiplist
https://www.jianshu.com/p/dc252b5efca6
跳跃表(shiplist)是实现sortset(有序集合)的底层数据结构之一!

Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成。其中zskiplist保存跳跃表的信息(表头,表尾节点,长度),zskiplistNode则表示跳跃表的节点。

按照惯例,我们来看看zskiplistNode跳跃表节点的结构是怎么样的:
在这里插入图片描述
最后就会形成下面这样带不同层高的节点:
在这里插入图片描述
zskiplist的结构如下:
在这里插入图片描述
所以整个跳跃表的结构就是:
在这里插入图片描述
整数集合intset
整数集合是set(集合)的底层数据结构之一。当一个set(集合)只包含整数值元素,并且元素的数量不多时,Redis就会采用整数集合(intset)作为set(集合)的底层实现。

整数集合(intset)保证了元素是不会出现重复的,并且是有序的(从小到大排序),intset的结构是这样子的:
在这里插入图片描述
intset存放数据示例图:
在这里插入图片描述
说明:虽然intset结构将contents属性声明为int8_t类型的数组,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值:

INTSET_ENC_INT16

INTSET_ENC_INT32

INTSET_ENC_INT64

从编码格式的名字我们就可以知道,16,32,64编码对应能存放的数字范围是不一样的。16明显最少,64明显最大。

如果本来是INTSET_ENC_INT16的编码,想要存放大于INTSET_ENC_INT16编码能存放的整数值,此时就得编码升级(从16升级成32或者64)。步骤如下:

1)根据新元素类型拓展整数集合底层数组的空间并为新元素分配空间。

2)将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放到正确的位上,需要维持底层数组的有序性质不变。

3)将新元素添加到底层数组。

另外一提:只支持升级操作,并不支持降级操作。

压缩列表ziplist
压缩列表(ziplist)是list和hash的底层实现之一。如果list的每个都是小整数值,或者是比较短的字符串,压缩列表(ziplist)作为list的底层实现。

压缩列表(ziplist)是Redis为了节约内存而开发的,是由一系列的特殊编码的连续内存块组成的顺序性数据结构。

压缩列表结构图例如下:
每个节点的结构图:
在这里插入图片描述
压缩列表从表尾节点倒序遍历,首先指针通过zltail偏移量指向表尾节点,然后通过指向节点记录的前一个节点的长度依次向前遍历访问整个压缩列表。

三、杂项小记
线下环境redis使用:

docker中 ~/redis/bin/redis-cli 回车即可访问,api使用6379端口,故使用默认即可。使用redis的常用命令即可查询。
如:keys * 可以查询所有key string类型的key可以使用get获取内容。

~/redis/bin/redis-cli -p 6378 是查询duse的redis内容,duse使用端口号6378。

redis的单线程相关问题(redis只有中心命令模块是单线程的,因为他是消费队列,只能单线程消费)
Redis的单线程为什么这么快?
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
使用多路I/O复用模型,非阻塞I/O;(但是其实这个多路复用IO模型是多线程运行的!)
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
为什么不采用多进程或多线程处理?
多线程处理可能涉及到锁
多线程处理会涉及到线程切换而消耗CPU
单线程处理的缺点?
耗时的命令会导致并发的下降,不只是读并发,写并发也会下降
无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
Redis不存在线程安全问题?
Redis采用了线程封闭的方式,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作(即:多个Redis操作命令)的复合操作来说,依然需要锁,而且有可能是分布式锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于学习Redis基础知识,可以按照以下思路进行学习: 1. 了解Redis的概念和特点:首先需要了解Redis是什么,它的主要特点是什么,它为什么被广泛应用于缓存、消息队列、会话管理等场景。 2. 安装和配置Redis:根据你的操作系统,安装Redis并进行相关配置。可以参考Redis官方文档或其他教程来完成这一步。 3. 学习Redis的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合和有序集合等。了解每种数据结构的特点、用途和操作命令,并通过实际操作来加深理解。 4. 掌握Redis的常用命令:学习Redis的常用命令,如get、set、hget、hset、lpush、lrange、sadd、smembers等,了解每个命令的具体用法和参数含义。 5. 理解Redis的持久化机制:了解Redis的RDB和AOF两种持久化方式,以及它们的优缺点。学习如何进行备份和恢复数据。 6. 学习Redis的事务和Lua脚本:了解Redis事务的基本概念和使用方法,以及如何使用Lua脚本来进行复杂的操作。 7. 深入了解Redis的性能优化和高可用方案:学习如何优化Redis的性能,包括配置调优、使用合适的数据结构、合理地使用缓存等。同时了解Redis的高可用方案,如主从复制、哨兵模式和集群模式。 8. 学习Redis与其他技术的结合:了解Redis如何与其他技术进行结合,如与Python、Java等编程语言的配合使用,以及与Spring、Django等框架的整合。 以上是学习Redis基础知识的一个思路,你可以根据自己的实际情况和需求进行学习和拓展。推荐参考一些经典的Redis教程和实战案例,通过实际操作和项目实践来提升自己的技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值