【Redis】初识Redis

文章目录

一、Redis基础

1.1 认识Redis

Redis 全称 REmote Dictionary Server(远程字典服务器),是当下互联网行业使用最广泛的内存 NoSQL 数据库。不同于普通的键值数据库,Redis 的 value 并非只能存储简单字符串,而是支持字符串、哈希、列表、集合、有序集合等丰富的数据结构,同时拓展了位图、HyperLogLog、GEO 地理位置、Stream 流等高级能力。

Redis 将绝大部分数据存放于内存,带来极高的读写性能,官方标称可达10 万 QPS。同时提供 RDB、AOF 两套持久化方案,可以把内存数据落地磁盘,规避断电、机器宕机带来的数据丢失风险。除此之外还具备键过期、发布订阅、事务、流水线 Pipeline、Lua 脚本、主从复制、哨兵、集群等全套能力。

Redis 官方原生并不支持 Windows 操作系统,Windows 版本是微软团队维护的分支。生产环境一律使用 Linux 操作系统运行 Redis

1.2 Redis 特性

  1. 性能极高,读写速度快

    • 全部数据存储在内存中,内存访问速度远高于磁盘,这是最根本原因。谷歌硬件性能统计,内存访问约100ns,磁盘寻道需要10ms级别,相差十万倍。
    • Redis核心功能就是操作内存中的数据结构,都是比较简单的逻辑
    • 网络上,Redis使用了IO多路复用的方式(epoll),也就是使用一个线程管理多个socket。
    • 单线程执行命令 。Redis 6.0版本引入多线程,但多线程仅仅负责网络IO读写、协议解析,所有数据命令依旧是单线程执行。单线程规避多线程锁竞争、线程上下文切换的巨大开销。
    • Redis使用C语言编写,代码贴近操作系统底层,没有高级语言额外开销。
  2. 基于键值对的数据结构服务器:普通KV数据库value只能存字符串,Redis value支持多种复杂数据结构:string、hash、list、set、zset,衍生Bitmaps、HyperLogLog、GEO、Stream。开发者可以直接使用内置数据结构完成业务,不需要自己做二次封装,大幅提升开发效率。

  3. 功能丰富,除基础数据结构外,配套大量实用能力:

    • 键过期:key设置TTL过期时间,天然适合缓存场景;
    • 发布订阅:实现简单消息广播;
    • Lua脚本:把多条命令打包在脚本中执行,保证原子执行;
    • 简易事务;
    • Pipeline流水线:把多条命令打包一次性发给服务端,减少网络往返RTT。
  4. 简单稳定

    • 代码量小:早期版本代码约2万行,3.0加入集群后约5万行,对比其他NoSQL体量很小,开发者可以通读理解源码。
    • 单线程模型,服务端、客户端逻辑都更简单。
    • 不依赖第三方系统库,事件驱动模型全部自己实现。
    • 生产环境稳定性很高,极少因为Redis自身bug发生宕机。
  5. 支持多语言客户端:Redis使用简单的TCP通信协议,Java、C++、Python、PHP、NodeJS、Go等主流编程语言都拥有成熟客户端

  6. 持久化:内存数据易失,断电数据丢失。Redis提供RDB、AOF两种持久化,把内存数据保存硬盘(相当于把内存中的数据备份),重启可以加载文件恢复数据。

  7. 主从复制:支持主从复制,一台主节点的数据自动同步到多台从节点,生成多份数据副本。主从复制是哨兵、集群的底层基础。可以实现数据备份、读写分离。

  8. 高可用与分布式

    • Redis Sentinel哨兵:实现故障自动发现、故障转移,解决主节点宕机人工切换,实现高可用;
    • Redis Cluster集群:官方分布式分片方案,实现横向扩容,提升存储容量与读写吞吐量。

1.3 Redis的适用场景

典型适用业务场景

  1. 缓存(Cache) 缓存是Redis最经典场景。把热点数据放到Redis,请求优先读取Redis,缓存未命中才查询后端MySQL数据库。既加速接口响应,又大幅降低数据库压力。同时Redis支持key过期、内存淘汰策略,完美适配缓存业务。
  2. 计数器应用 视频播放量、文章浏览数、点赞数。高并发场景,数据库做自增压力巨大,Redis的INCR系列命令可以高性能完成计数。计数数据可以异步落地到数据库。
  3. 社交网络业务 点赞、踩、粉丝关系、共同好友、消息推送。社交业务访问量大,关系型数据库处理关系类数据成本高,Redis的数据结构可以轻松实现。
  4. 简易消息队列 利用List的阻塞弹出brpop/blpop实现阻塞队列;也可以使用发布订阅完成消息广播。但它不是专业MQ,没有完备的消息确认、死信队列,适合简单业务。

不适合的场景

Redis数据主要放在内存,内存硬件成本远高于磁盘。

海量冷数据不适合存入Redis。像冷数据这样访问频率极低的数据,全部放在Redis会带来极高硬件成本,冷数据应该存MySQL、HDFS这类磁盘存储。

原则:Redis优先存放热数据。冷热分离,热数据Redis加速,冷数据磁盘存储。

1.4 Redis版本规则与特性

Redis版本命名借鉴Linux:版本第二位偶数代表稳定版本,奇数代表开发测试版本。生产环境务必选用偶数稳定版。

  • 2.6:支持Lua脚本、毫秒级key过期、位图命令bitcount;
  • 2.8:部分复制PSYNC雏形,Sentinel哨兵正式生产可用;
  • 3.0:正式发布Redis Cluster集群;
  • 3.2:新增GEO地理位置,list底层使用quicklist;
  • 4.0:模块扩展能力、PSYNC2.0完整部分复制、LFU淘汰算法、unlink异步删除、RDB‑AOF混合持久化;
  • 5.0:新增Stream流数据类型;
  • 6.0:IO多线程、ACL权限管控、SSL加密、客户端缓存;
  • 7.0:AOF改为多文件存储,RDB格式升级。

1.5 Redis基础命令行

Redis官方不支持Windows,Windows版本由微软开源小组维护,生产环境全部部署Linux。

Redis重要程序文件

文件 功能
redis‑server Redis服务主程序
redis‑cli Redis命令行交互客户端
redis‑benchmark 性能压测工具
redis‑check‑aof 修复损坏AOF持久化文件
redis‑check‑rdb 修复损坏RDB快照文件
redis‑sentinel 哨兵程序,实际是redis‑server软链接
  • 配置文件:/etc/redis.conf
  • 持久化文件目录默认:/var/lib/redis/
  • 日志目录默认:/var/log/redis/

命令行客户端

redis‑cli两种使用模式:

  1. 交互式:进入客户端之后逐行输入命令
bash 复制代码
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set key hello 
OK 
127.0.0.1:6379> get key 
"hello"
  1. 命令模式,直接执行命令输出结果,适合脚本调用

    redis-cli -h 127.0.0.1 -p 6379 get key

本地默认端口6379,不写‑h、‑p默认连接本机6379。

1.6 Redis客户端与服务器交互过程

Redis 使用TCP 协议 进行通信,默认端口 6379,通信协议是RESP(Redis 序列化协议)

  1. 建立 TCP 连接 客户端向 Redis 服务端发起 TCP 三次握手,建立 socket 连接。一个客户端对应一条 socket 通道,多个客户端可以同时连服务器。
  2. 客户端构造 RESP 协议请求 客户端(Java 的 Jedis/Lettuce 等)把我们调用的命令,例如set key value,封装成RESP 格式字符串,通过 socket 发送给 Redis 服务器。
  3. Redis 服务器读取请求 Redis 是单线程事件模型(IO 多路复用),监听所有 socket 事件。读到客户端发来 RESP 请求,解析出真实 Redis 命令。
  4. 执行命令 解析完成,调用内部函数执行这条命令,操作内存数据。同一时刻只执行一条命令,命令串行执行
  5. 封装 RESP 响应结果 把执行后的返回值(成功、字符串、nil、数字等)包装成 RESP 响应报文。
  6. 回写给客户端 通过 socket 把 RESP 响应发送回客户端。
  7. 客户端解析 RESP 响应 客户端解析 RESP 报文,转换成编程语言的数据对象,给到业务代码使用。
  8. 连接维持 / 关闭
  • 如果是长连接:socket 保持,复用来反复收发命令(连接池就是利用长连接)
  • 如果短连接:交互完成后关闭 TCP 连接,四次挥手断开。

二、全局通用命令

redis按照键值对的形式存储数据,key是字符串类型,""''可以加上也可以省略。value可以有多种类型,包括字符串,哈希表,列表,集合和有序集合等。

全局命令就是指可以操作任意数据类型的命令。

redis中命令不区分大小写。

2.1 get/set

get/set用于存取键值对

语法:

bash 复制代码
set key value
get key

注意: get命令只能用于value为String类型的key

2.2 keys

通过通配符描述一个key,符合描述的key就可以被查询出来

语法

bash 复制代码
keys pattern   

通配符:

  • ?:匹配任意一个字符
  • *:匹配任意数量的任意字符
  • [字符1 字符2...]:给出固定选项,指定匹配其中一个字符
  • [^字符]:排除字符,其余的字符都可以匹配
  • [字符1-字符2]匹配字符1到字符2之间任意一个字符

时间复杂度: O(N)

注意: 如果使用keys *就相当于查询所有的key,生产环境中key的数量非常多,而redis是单线程的服务器,会阻塞整个Redis。因此生产环境中禁止使用keys命令

2.3 exists

判断key是否存在,一次查询多个key,返回存在key的数量。

语法:

bash 复制代码
EXISTS key [key ...]

示例:

bash 复制代码
redis> SET key1 "Hello" 
"OK" 
redis> EXISTS key1 
(integer) 1 
redis> EXISTS nosuchkey 
(integer) 0 
redis> SET key2 "World" 
"OK" 
redis> EXISTS key1 key2 nosuchkey 
(integer) 2

时间复杂度: redis是按照 哈希表 来组织键值对的,对于单个key,时间复杂度是O(1),官方文档给出的时间复杂度是O(N),N是指待查询key的数量。

为什么redis要设计一个命令可以操作多个key?

redis是客户端-服务器结构的程序,客户端和服务器之间使用网络 通信。如果分开操作,就会产生多个轮次的通信。而网络通信与内存相比,效率低,成本高,每一次网络通信都涉及封装分用

因此redis很多命令都支持多种操作,从而减少客户端和服务器的交互次数。

2.4 del

删除一个或多个key,返回成功删除个数

语法:

bash 复制代码
DEL key [key ...]

时间复杂度: O(1)

当Redis作为缓存 ,与MySQL中的dropdelete命令相比,Redis中的del命令危险程度并不大。

Redis主要应用场景就是缓存,此时Redis中存储热点数据,全量数据在MySql中,随时间推移Redis中可能存在没用的key,因此删除少量的key影响并不大,可以再从MySQL中读取数据;但是如果大批量删除数据,大量的请求就直接打给MySQL,此时问题就比较严重了。

当Redis作为数据库 ,Redis中就保存全量数据,此时与MySQL一样,del命令就要谨慎操作

2.5 expire/pexpire

expire可以为key设置过期时间,超出时间key就会自动删除

语法:

bash 复制代码
expire key seconds

可以使用pexpire设置过期时间,单位是毫秒。

返回值:

  • 1:设置成功
  • 0:设置失败
    只能为已经存在的key设置过期时间,如果key不存在则返回0

时间复杂度: O(1)

基于Redis实现的分布式锁,为了防止出现不能正确解锁现象,通常会在加锁的时候设置过期时间。所谓的使用Redis作为分布式锁,就是给Redis里写一个特殊的key-value,超出时间后删除特殊的key

2.6 ttl/pttl

ttl可以获取key剩余存活秒数,返回-1表示永不过期,返回-2表示key不存在

语法:

bash 复制代码
ttl key    

pttl可以获取key剩余存活时间,单位是毫秒

时间复杂度: O(1)

复制代码
TYPE key          # 查看key对外数据类型
SCAN 0            # 渐进式遍历key,分批返回,不会阻塞Redis,生产替代KEYS

KEYS会扫描全部key,实例key数量几十万上百万的时候,会阻塞Redis全部命令。线上遍历key必须用SCAN

Redis中key的过期删除策略

Redis采用惰性删除+定期删除两种策略搭配使用。

  • 惰性删除:Redis在访问或修改key时都会先检查key是否过期,如果已经过期,就会直接删除key,返回nil给客户端;如果没有过期就会把数据返回给客户端。
  • 定期删除:每隔一段时间随机抽取一部分key进行检查,删除其中过期的key。

定期删除中抽取检查的过程是非常快的,因为Redis是单线程程序,处理命令和扫描过期key都在同一个线程中,如果一次扫描了大批量的key,就会导致正常的处理请求命令被阻塞。

2.7 type

type命令用于返回key对应的value的数据类型。

语法:

bash 复制代码
type key

时间复杂度: O(1)

返回值:

  • none:key不存在
  • string
  • list
  • set
  • zset
  • stream:当redis作为消息队列时,使用这个类型的value

使用示例:

bash 复制代码
redis> SET key1 "value" 
"OK" 
redis> LPUSH key2 "value" 
(integer) 1 
redis> SADD key3 "value" 
(integer) 1 
redis> TYPE key1 
"string" 
redis> TYPE key2 
"list" 
redis> TYPE key3 
"set"

三、数据类型与内部编码

我们用type key看到的是对外暴露的数据类型;Redis底层有一套内部编码,Redis会根据数据的数量、大小,自动切换底层编码,兼顾内存占用与读写性能。

数据类型 vs 数据结构:这里的数据类型和数据结构与Java语言中的概念不一样。Redis 的数据类型是对外提供给用户使用的,是业务层面概念;底层数据结构是 Redis 内部实现存储的工具。一个 Redis 数据类型,在不同条件下可以切换不同底层数据结构,切换过程对使用者透明,用户无感知。底层数据结构对应用户是不可见的,没有操作命令。

3.1 数据类型

Redis内置了很多数据类型,其中最常用的有五种:

  1. String 字符串
  2. Hash 哈希
  3. List 列表
  4. Set 集合
  5. ZSet 有序集合:内部存储了score,表示权重

3.2 内部编码

针对上述的数据类型,源码层面会进行特定的优化,来达到节省时间/空间的效果。这些优化就要依靠内部具体实现的数据结构,也就是编码方式。

举个例子:对于外部来说,hash的作用就是存储键值对,保证O(1)的时间复杂度就可以找到;但是对于内部来说,不一定使用标准的哈希表,在某些特定场景下可能使用不同的数据结构来实现功能,用户感知不到这些变化。

object encoding key命令可以查看key的底层内部编码:

bash 复制代码
127.0.0.1:6379> set hello world 
OK 
127.0.0.1:6379> lpush mylist a b c 
(integer) 3 
127.0.0.1:6379> object encoding hello 
"embstr" 
127.0.0.1:6379> object encoding mylist 
"quicklist"

四、单线程架构

4.1 单线程模型的工作过程

假设有三个客户端同时向redis服务器发送请求,宏观上这三个请求是并发的:

而redis服务器是单线程模型(6.0引入IO多线程,命令执行依旧是单线程),所有请求到达服务器就要在队列里排队,再等待redis服务器取出命令执行,因此微观上来讲,redis服务器就是串行执多个命令的。

4.2 单线程为什么这么快

  1. redis主要是访问内存,而MySQL主要是访问硬盘,内存访问速度远高于磁盘,这是最根本原因。谷歌硬件性能统计,内存访问约100ns,磁盘寻道需要10ms级别,相差十万倍。
  2. 逻辑简单,Redis核心功能就是操作内存中的数据结构,都是比较简单的逻辑;而MySQL的增删改查涉及到各种约束,也提供了丰富的功能(多表联查,排序,视图等),这些操作都需要更复杂的功能支撑,就会导致更多的开销。
  3. 单线程执行命令 。Redis 6.0版本引入多线程,但多线程仅仅负责网络IO读写、协议解析,所有数据命令依旧是单线程执行。单线程规避多线程锁竞争、线程上下文切换的巨大开销。
  4. 使用epoll这样的IO多路复用机制,一个线程可以管理多个socket。

一个服务器对应多个客户端,就有多个socket,而服务器和客户端之间的通信不一定都很频繁,大部分的socket都是静默的,没有数据需要传输。也就是说同一时刻只有少数的socket是活跃的。

可以通过多线程,为每一个客户端分配一个线程,但多个线程的创建调度以及销毁的开销都很大,并且大部分线程是空闲的,因此通过IO多路复用,让一个线程来处理多个socket。

IO多路复用也是操作系统实现的,操作系统向外提供一套API。Redis采用了epoll,相当于事件通知/回调机制。epoll适合在事件交互不频繁的场景下使用。

注意:

  • 这里"快"的概念是与MySQL这样的关系型数据库做比较,但是与直接在内存中操作变量相比,redis就慢得多。因为redis是先通过网络再操作内存。
  • 在学习多线程时我们认为多线程可以一定程度上提高效率,但是这里却说单线程是Redis高效率的原因。这是因为多线程可以在CPU密集型的任务 中发挥优势,使用多个线程可以充分利用CPU多核资源。但是对于Redis来说,核心任务主要就是操作内存中的数据结构,这些操作不会吃很多CPU,也就是说单个核心就可以解决问题了,如果这个时候引入多线程,并不会明显的提高效率,反而会因为线程安全,锁竞争,线程阻塞等问题降低效率。

单线程的风险是一个慢命令会阻塞全部请求 。 如果一次性读取几十万元素的集合、大量数据的hgetall,会造成整个服务卡顿。

对策:

  1. 避免大key;
  2. scan/hscan/sscan/zscan渐进式分批遍历;
  3. 业务拆分,不要把海量数据放到同一个key。
相关推荐
Wang's Blog2 小时前
Java 项目实战: 外卖平台-数据库环境搭建与十一张表结构解析
java·服务器·redis
喜欢的名字被抢了3 小时前
01-Redis 基础篇:从定位到 Key 管理
redis·教程
Anastasiozzzz3 小时前
重新定义 Agent 基建:Redis 在现代 AI 与智能体系统中的工程实践
java·人工智能·redis·ai
溪语流沙5 小时前
Django + Vue电商项目第001讲:开篇|注册登录加增删改查,那不是电商
redis·python·mysql·docker·typescript·django·vue
努力努力再努力wz5 小时前
【Redis进阶系列】:从主从复制到 Sentinel,一文建立故障检测、Leader 选举与 Failover 的完整心智模型
数据库·redis·缓存
Wang's Blog5 小时前
Java 项目实战: 外卖平台-后台系统登录功能开发
java·服务器·redis
Wang's Blog7 小时前
Java 项目实战: 外卖平台-后台退出功能与首页iframe架构
java·服务器·redis
海天一色y7 小时前
生产级医疗AI Agent:Multi-Agent RAG架构解析
redis·milvus·multi-agent
Wang's Blog7 小时前
Java 项目实战: 外卖平台-软件开发流程与项目整体介绍
java·服务器·redis