Redis从入门到精通:一文搞定环境搭建、核心命令与高性能奥秘
前言
在当今这个追求极致性能和高并发的互联网时代,Redis已然成为后端技术栈中不可或缺的“瑞士军刀”。无论是作为高速缓存、分布式锁,还是实现消息队列,它无处不在。但你是否曾好奇,小小的Redis为何能拥有如此惊人的性能?它赖以成名的单线程模型,究竟是如何“单枪匹马”地处理数以万计的并发请求的?
本文将带你开启一场Redis的深度探索之旅。我们不谈空洞的理论,而是从零开始,手把手教你如何在Linux上搭建环境、修改配置。随后,我们将深入浅出地讲解set、keys、expire等一系列核心命令,并重点剖析keys *命令在生产环境中的潜在风险。最后,我们将直面那个“百万美元”的面试题——Redis为什么这么快?通过对其底层数据结构、单线程模型和IO多路复用机制的层层剖析,让你不仅知其然,更知其所以然。准备好了吗?让我们一起揭开Redis高性能的神秘面纱!
环境搭建
我们安装的Redis5版本
在Linux中进行安装
Redis官方是不支持Windows版本的
打开Linux,先切换到root用户
然后使用apt命令来搜索Redis相关的软件包
apt search redis
这里我们已经安装好了
apt install redis
安装好了之后,我们可以输入命令进行查询Redis是否在运行中
netstat -app |grep redis
我们这里的ip是0.0.0.0说明别的主机也是可以进行访问的
但是你如果一开始是127.0.0.1的话,绑定这个127.0.0.1的ip意味着只能由当前主机上的客户端访问,跨主机就访问不了
那么我们就得进行配置文件的修改操作了,不然的话别的主机是访问不了你的redis的
我们输入命令进入到配置文件路径下
cd /etc/redis/
可以看到一个redis.conf
文件,这个就是redis的配置文件
进入到文件中,找到这个bind
改成我这个样子0.0.0.0
然后我们还需要将保护模式给关闭了
将后面改成no
这里的端口号是6379
我们的redis是不需要配置密码的
虽然我们没有密码,但是非常安全
因为我们的数据不值钱
在修改配置文件之后,如果想生成改变的话,我们得重新启动redis服务器
srevice redis-server restart
输入命令之后,看到active running
就说明你重启成功了
使用redis自带的客户端链接服务器
在终端输入命令
redis-cli
为了验证是否链接成功,我们这里发送一个ping
命令,成功的话会显示一个PONG的
如果想退出redis的话,我们输入ctrl d即可进行退出操作了
Ctrl c也可以退出
Redis的客户端也有很多的形态
1、自带了命令行客户端
redis-cli
如果想要连接别的redis的话-h
就是ip地址,-p
是端口号
redis-cli -h 127.0.0.1 -p 6379
2、图形化界面的客户端
(桌面程序,web程序)
我们谈到mysql这样的快,是相对于mysql这样的关系型数据库的
如果我们直接和内存中的操作变量相比的话,就没有优势了,甚至更慢了
使用hash mcp的话是直接操作内存
而我们使用redis是先通过网络,再操作内存
是否使用redis,要结合实际的需求来确定
未来要扩展分布式系统,使用redis是更佳的
redis核心命令
get和set
Redis中最核心的两个命令
get #根据key来取valueset #把key和value存储进去
redis是按照键值对的方式存储数据的
set key value
这里的key和value必须都是字符串
redis中的命令是不区分大小写的
get命令直接输入key就能得到value
如果key不存在的话,会返回一个nil,和null/NULL是一个意思
使用简单,学习成本很低
将redis当成一个网络版本的哈希表
keys(全局命令)
redis支持很多种数据结构
整体来说,redis是键值对结构,key固定就是字符串,valude实际上会有很多种类型(字符串、哈希表、列表、集合、有序集合)
操作不同的数据结构,就会有不同的命令
全局命令,就是能够搭配任意一个数据结构来使用的命令
keys 用来查询当前服务器上匹配的key
通过一些特殊符号(通配符)来描述key的摸样,匹配上述摸样的key就能被查询出来
语法结构
keys pattren
pattren存在的意义,是去描述另外的字符串长啥样
- ?匹配任意一个字符
我们这里插入了三个键值对,然后使用?通配符进行查找
-
- 匹配一个或者n个相同字符
- 匹配一个或者n个相同字符
-
[ae]只能匹配到a或者e,别的不行,相当于给出固定的选项
-
[^e]排除e,只有e匹配不了,其他的都能匹配
-
[a-c]a到c中间的字母都是可以的,包含a和c
keys命令的时间复杂度是O(N)
因为在执行这个命令的时候会遍历我们所有的key
所以在生产环境上,一般都会禁止使用keys命令
尤其是大杀器keys *
因为生产环境上的key可能非常多,而redis是一个单线程的服务器
执行Keys * 的时间非常长,就使redis服务器被阻塞了,无法给其他客户端提供服务
整个系统基本瘫痪
exists(判断Key是否存在)
判断某个key是否存在
返回的是key存在的个数
针对多个key来说是很有用的
时间复杂度是O(1)
reids组织这些key就是按照哈希表的方式来组织的
redis支持很多数据结构=》指的是一个value可以是一些复杂的数据结构
redis自身的键值对值通过哈希表来组织的
redis具体的某个值,又可以是一些数据结构
返回对应key的个数
上面我们是分开的写法,会产生更多轮次的网络通信,成本高,效率低
下面是合并在一起的,直接返回两个Key存在的总数值
redis的很多命令都是支持一次就能操作多个key的
del(删除key)
删除指定的key
可以一次删除一个或者多个Key
返回值是删除掉的Key的个数
如果redis存储的是全量数据的话,那么误删数据问题就很大了
如果是热点数据的话误删一两个还是无所谓的
expire(设置过期时间)
给指定的key设置过期时间
key存活的值超过这个指定的时间就会被自动删除了
很多的业务场景都是有时间限制的,比如说手机验证码
基于redis实现分布式锁,为了避免出现不能正常解锁的情况,通常都会在加锁的时候设置一下过期时间(所谓的使用redis作为分布式锁,就是给redis里写一个特殊的额key value)我们将这个Key value删除掉就相当于自动解锁
使用格式如下:
expire key seconds
如果给不存在的key进行过期时间的设置的话是会设置失败的,那么就会返回0
如果返回0就说明你设置失败了,如果是1的话就说明成功了
ttl(查询存活时间)
查看当前key的过期时间还剩多少
TTL key
返回的是过期时间
返回值是剩余过期时间
-1表示没有关联过期时间 ,-2表示key不存在
key的过期策略的实现
redis怎么知道那些Key要过期,那些key要被删除,哪些key还没过期?
如果直接遍历所有的key,他的效率是很低的
redis整体的策略是:
定期删除
比喻理解:超时老板会定期检查过期的产品,每次抽取一部分进行验证过期时间,保证这个抽取检查的过程,足够快
惰性删除
假设这个Key已经到了过期时间了,但是还没删除他,key还存在,紧接着,后面又一次访问用到这个key,于是这次访问就让redis服务器触发删除key的操作,同时再返回一个nil
比喻理解:当我买东西的时候,结账的时候老板发现过期了,然后不卖了
为什么这里对于定期删除的时间,有明确的要求呢?
因为redis是单线程的程序
如果扫描过期key消耗的时间太多了,就可能导致正常请求命令就被阻塞了(产生了类似执行与keys * 的效果)
redis为了对上述进行补充,还提供了一系列的内存淘汰策略
redis中并没有采取定时器的方式来实现过期key删除
如果有多个key过期,也可以通过一个定时器来高效/节省CPU的前提下来处理多个key
我们目前还不知道为啥redis没有采取这种定时器的方式
定时器的实现,势必就得引入多线程了
redis的早起版本就奠定了单线程的基调,引入多线程就打破了作者的初衷了
定时器的实现原理
在某个时间到达之后,执行指定的任务
基于优先级队列/堆
正常的队列是先进先出
优先级队列是啊按照指定的优先级,先出
啥叫优先级高,这个是我们自定义的
在redis过期key的场景中,就可以通过过期时间越早,就是优先级越高
针对队首元素即可,不用遍历所有的key
第二种基于时间轮实现的定时器
把时间划分为很多的小块儿
这两种方法都是比较高效的定时器实现方案
type(查看value的类型)
返回key对应的value的类型
key都是string类型的,但是value可能存在多种类型
总结
上面我们已经学习了几个简单的命令
下面就围绕每个数据结构来介绍相关的命令了
常用数据结构
redis底层在实现上述数据结构的时候,会在源码层面,针对上述实现 进行特定的优化,来达到节省时间/空间的效果
内部的具体实现的具体实现结构(编码方式),会有变数
数据结构:redis承诺给你的,也可以理解成数据类型
编码方式:redis内部底层的实现
同一个数据类型,背后可能得编码实现方式是不同的,会根据特定场景优化
raw是最基本的字符串,底层就是持有一个char数组
int类型:redis通常也可以实现一些计数这样的功能
当value就是一个整数的时候,此时可能吧redis会直接使用int来保存
embstr针对短字符串进行的特殊优化
hashtable是最基本的哈希表
ziplist叫做压缩列表:在哈希表里面元素较少的时候,可能就优化成ziplist了
压缩列表,节省空间
那么为啥要压缩呢?
redis上可能有很多的key
可能某些key的value是key
linkedlist:链表,一个节点指向另一个节点
ziplist就是压缩列表,如果列表中元素个数少,就使用ziplist
如果元素多的话就使用linkedlist
在redis3.2开始,引入了新的实现方式quicklist同时兼顾了linkedlist和ziplist的优点
quicklist就是一个链表,每个元素又是一个ziplist
这样把空间和效率都兼顾到了
类似于C++中的std::deque
intset :集合中存的都是整数的话,那么就会优化会intset
skiplist:跳表也是链表,不同于普通的链表,每个节点上有多个指针域,巧妙的搭配这些指针域的指向,就可以做到,从跳表啥啊昂查询元素的时间复杂度是O(logn)
我们可以使用object encoding key
查看key对应的value对应的编码类型
redis会自动根据当前情况选择内部的编码方式
redis单线程模型
单线程模型的工作过程
redis只使用一个线程,处理所有的命令请求
不是说一个redis服务器进程内部真的就只有一个线程
其实也有多个线程,多个线程是在处理网络IO
这里我们的两个客户端访问服务器中的Key对应的value ,让value进行自增,
在多线程中,针对于类似的场景,两个线程尝试同时对一个变量进行自增,表面上是自增两次,实际上可能是一次
当前这两个客户端,也相当于并发的发起上述的请求
此时就意味着服务器这边也会存在类似的线程安全问题呢?
幸运的是,并不会,redis服务器实际上是单线程模型,保证了当前收到的这多个请求是串行执行的
这两个请求在排队,依次进行执行,顺序执行
多个请求同时到达redis服务器,也是在队列中进行排队
redis能够使用单线程模型很好的工作,原因主要在于redis的核心业务逻辑都是短平快的,不太消耗CPU资源也不太吃多核了
redis要特别小心,某个操作占用时间长,就会阻塞其他命令的执行,比如说keys *
redis为什么这么快(重要面试题)
为什么效率这么高,速度为什么这么快
谈到快以及效率高,都是我们和其他的关系型数据库进行对比的
我们参照物是数据库
1、redis访问内存,数据库则是访问硬盘,
2、redis核心功能,比数据库的核心功能更简单
数据库对于数据的插入删除查询,都有更复杂的功能支持,这样的功能势必要花费更多的开销,针对插入删除,数据库中的各种约束,都会使数据库做额外的工作,Redis干的活少,提供的功能相较于redis也是少了不少了
3、redis采取单线程模型,避免了一些不必要的线程竞争开销
redis每个操作都是短平快的,就是简单操作一下内存数,不是特别小号cpu的操作,就算是搞多个线程,也提升不大
4、处理网络IO的时候,使用了epoll这样的IO多路复用机制
一个线程就可以管理多个socket
针对tcp来说,服务器这边每次要服务一个客户端,都需要给这个客户安排一个socket
一个服务器服务很多客户端,同时就有很多的socket
这些socket上都是无时无刻的在传输数据么?
很多情况下,每个客户端和服务器之前的通信也没有那么频繁
此时这么socket大部分时间都是静默的,上面是没有数据需要传输的
同一时刻,只有少数socket是活跃的
最开始介绍TCP服务器的时候,有一个版本就是每个客户端给分配一个线程
客户端多了,线程就多了,系统开销就大了
通过IO多路复用,一个线程来处理多个socket
总结
总的来说,本文为我们提供了一份从实践到理论的Redis核心知识图谱。
我们从最基础的环境搭建入手,详细走过了在Linux上安装、配置Redis的全过程,确保你能顺利启动并连接到自己的Redis服务器。
接着,我们深入学习了Redis的核心全局命令,包括最基本的GET/SET,用于模式匹配的KEYS(及其性能警告),判断存在的EXISTS,以及实现业务场景必不可少的EXPIRE和TTL。这些命令是日常开发中使用频率最高的利器。
随后,文章进一步揭示了Redis高效的秘密——其丰富的数据结构与巧妙的内部编码。我们了解到,Redis对外提供统一的数据类型(如List, Hash),但在底层会根据数据规模和特点,智能地选择ziplist、skiplist等最优的编码方式,以此在空间和时间效率上达到极致平衡。
最后,我们集中探讨了Redis架构的精髓——单线程模型,并完美解答了“Redis为什么这么快”这一经典面试题。其核心优势可归结为四点:
纯内存操作:所有操作均在内存中完成,速度远超磁盘I/O。
功能相对简单:核心功能聚焦于数据读写,避免了关系型数据库的复杂逻辑。
单线程模型:避免了多线程带来的上下文切换和锁竞争的开销。
I/O多路复用:采用epoll等机制,单个线程即可高效处理大量网络连接。
通过这篇文章,你不仅掌握了Redis的实用操作技巧,更深入理解了其背后的设计哲学与高性能原理,为你在实际项目应用和技术面试中增添了充足的信心。