Redis
Redis
池慕睡不着简介
Redis的场景:数据库DB、缓存Cache、消息队列MQ
产生原因:
- Mysql的技术瓶颈:磁盘IO,速度慢
- Redis:支持从内存读取数据
Redis支持的数据结构
- 字符串String
- 列表Lsit
- 集合Set
- 有序集合SortedSet
- 哈希Hash
- 消息队列Stream
- 地理空间Geospatial
- HyperLogLog
- 位图Bitmap
- 位域Bitfield
使用方式(命令行、API、图形化界面)
- CLI(Command Line Interface)
- API(Application Programming Interface)
- GUI(Graphical User Interface)
Redis的优势
- 性能高
- 数据类型丰富,单键值对最大支持512MB大小的数据
- 简单易用,支持所有主流编程语言
- 支持数据持久化、主从复制、哨兵模式等高可用特性
Redis 为什么这么快?
Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
- Redis 基于内存,内存的访问速度比磁盘快很多;
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。
- Redis 通信协议实现简单且解析高效。
1. Redis 基于内存
内存存储的优势:
- 内存的访问速度比硬盘快几个数量级。通过将数据存储在内存中,Redis 能够提供极低的延迟和高吞吐量,适合对性能有严格要求的应用,如实时分析和推荐系统。
- 内存的特性使得Redis 能够处理高负载场景,但请注意,内存有限,数据持久化机制也是Redis设计的重要考虑。
2. 基于Reactor模式的事件处理模型
Reactor模式的设计:
- Redis采用了单线程的事件循环模型,这意味着所有的请求都在一个线程中处理,从而避免了多线程带来的复杂性和上下文切换的开销。
- IO多路复用(如
epoll
和select
)使得Redis可以高效地处理多个客户端的并发连接。通过一个事件循环,Redis能够在单个线程内高效管理输入和输出操作,减少了在多数情况下的资源消耗。
单线程优越性:
- 单线程可以简化代码逻辑,避免由于锁争用导致的性能瓶颈。
- Redis通过高效的事件驱动机制,能支持大量并发连接而不会出现典型多线程程序的性能问题。
3. 多种优化过后的数据类型/结构
内置数据类型:
- Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。每种数据结构都经过优化,能够高效地执行常见操作。
- 例如,哈希结构非常适合于存储对象,集合和有序集合提供了快速的去重和排序功能。
性能优化的目的:
- 通过专门优化的数据结构,Redis能够针对特定场景,得到更快的响应时间和更高的数据处理能力,极大提高了整体的系统性能。
4. 简单且高效的通信协议
通信协议:
- Redis使用了一种简单的文本协议,称为RE-dis Serialization Protocol(RESP)。该协议使得客户端可以轻松地与服务器交互,并且解析效率很高。
高效性的效果:
- 简单的协议格式降低了理解和实现的复杂度,同时也使得网络数据序列化与反序列化的速度提高。这意味着客户端和服务器之间的通信延迟很低,有助于提高整体响应速度。
小结
Redis的设计充分考虑了高性能和高并发需求,结合内存存储、事件驱动模型、优化的数据结构及高效的通信协议,使其成为现代高性能应用程序的理想选择。同时,开发者在使用时也需注意数据持久化和内存管理,以确保数据的安全性和持久性。
Redis 除了做缓存,还能做什么?
- 分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:分布式锁详解 。
- 限流:一般是通过 Redis + Lua 脚本的方式来实现限流。如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的
RRateLimiter
来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。 - 消息队列:Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
- 延时队列:Redisson 内置了延时队列(基于 Sorted Set 实现的)。
- 分布式 Session :利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
- 复杂业务场景:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。
事务
Redis支持事务,即在一次请求中执行多个命令。步骤如下。
- MULTI开启一个事务,将多个命令放入一个队列,入队到事务中,最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。(DISCARD取消事务)
关系型数据库的事务一般是原子性操作(要么全部成功,要么全部失败)
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
Redis不能保证事务中 的命令全部执行成功,但是可以提供一下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
Redis 线程模型(重要)
对于读写命令来说,Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
Redis 单线程模型了解吗?
持久化
redis的数据全部在内存中,如果突然宕机,数据就会全部丢失,未保证redis的数据在发生突发状况时不会丢失、或者只丢失少量,必须根据一些策略来把redis内存中的数据写到磁盘中,这样当redis服务重启时,就会将硬盘中的数据恢复到内存中。Redis持久化的意义就是为了保证突然宕机,内存数据不会全部丢失。
详细:Redis专题:万字长文详解持久化原理 - 码路印记 - SegmentFault 思否
主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower) 。数据的复制是单向的,只能从主节点复制到父节点。Master以写为主,Slave以读为主。
默认情况下,每台redis服务器都是主节点,且一个主节点能有多个从节点,但一个从节点只能有一个主节点。
作用
- 读写分离:主节点写,从节点读,提高服务器的读写负载能力
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 ; 实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载 ; 尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
详情: Redis——Redis主从复制(工作流程详解)_redis主从复制过程-CSDN博客
哨兵模式
哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于Master主服务器是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。
概述
1.1、为什么要启动哨兵模式
当我们的redis主服务器宕机后,手动切换主从模式的这种人工干预,费事费力,并且会造成我们的服务一段时间不可用。所以我们通过哨兵模式来解决这个问题。
1.2、什么是哨兵模式
哨兵模式是对redis系统运行情况的监控,它是一个独立的进程,功能有二个:
- 监控主机redis和从机redis是否正常运行
- 主机故障后主动将从机转换为主机
1.3、哨兵工作原理
单个哨兵的工作原理:
单个哨兵,只需要监控主Redis,就可能得到从Redis。
多个哨兵的工作原理:
多个哨兵,不仅同时监控主从Redis,而且哨兵之间互为监控。
多个哨兵,防止哨兵单点故障。
1.4、哨兵功能
集群监控:负责监控主从集群中的Master和Slave进程是否正常工作。
故障转移(failover):如果Master宕机,会自动从Slave中选举出新的Master,进行主从自动切换。
配置中心:如果发生了故障转移,Sentinel负责通知客户端新的Master的地址。
消息通知:如果某个redis节点有故障,那么Sentsinel会发送报警消息给系统管理员。
常用命令
服务器相关命令
- ping : 检测连接是否存活
- echo: 在命令行打印一些内容
- quit、exit: 退出客户端
- shutdown: 退出服务器端
- info: 返回redis相关信息
- config get dir/* 实时传递接收的请求
- showlog: 显示慢查询
- select n: 切换到数据库n,redis默认有16个数据库(DB 0~DB 15),默认使用的第0个
- dbsize: 查看当前数据库大小
- move key n: 不同数据库之间数据是不能互通的,move移动键到指定数据库
- flushdb: 清空当前数据库中的键值对。
- flushall: 清空所有数据库的键值对。
key相关命令
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
常用命令:
- keys * :查看当前数据库中所有的key
- dbsize: 键总数
- exists key: 检查键是否存在
- del key [key …]: 删除键
- expire key seconds: 键过期
- ttl key: 获取键的有效时长
- persist key: 移除键的过期时间
- type key: 键的数据结构类型
- randomkey: 随机返回数据库中一个键
- rename key1 key2 : 重命名
- renamex key1 key2 : 当key2不存在时,key1重命名
代码示例:
五大数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。其通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
- 字符串类型: string
- 哈希类型: hash
- 列表类型: list
- 集合类型: set
- 有序集合类型: sortedset(zset)
String(字符串)
字符串类型是Redis最基础的数据结构,其它的几种数据结构都是在字符串类型基础上构建的,字符串的值可以是:字符串、数字、二进制,但其值最大不能超过512M。
使用场景: 缓存、计数器、对象存储缓存(共享session)、限速
常用命令:
- set key value: 设置一个key的value值
- setnx key value: 仅当key不存在时进行set
- setex key seconds value: set 键值对并设置过期时间
- mset key value [key value …]: 设置多个key value
- msetnx key1 value1 [key2 value2…]: 批量设置键值对,仅当参数中所有的key都不存在时执行,原子性操作,一起成功,一起失败
- get key: 返回key的value
- mget key [key …] : 批量获取多个key保存的值
- exists key [key …]: 查询一个key是否存在
- decr/incr key: 将指定key的value数值进行+1/-1(仅对于数字)
- incrby/decrbyB key n: 按指定的步长对数值进行加减
- incrbyfloat key n: 为数值加上浮点型数值
- append key value: 向指定的key的value后追加字符串
- strlen key: 返回key的string类型value的长度。
- getset key value: 设置一个key的value,并获取设置前的值,如果不存在则返回null
- setrange key offset value: 设置指定位置的字符
- getrange key start end: 获取存储在key上的值的一个子字符串
代码示例:
List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),也可以获取指定范围指定下标的元素等。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
两个特点:
1.列表中的元素是有序的,可以通过索引下标获取某个元素霍某个某个范围内的元素列表
2.列表中的元素可以是重复的
使用场景: 消息队列、栈、文章列表等。
常用指令:
- 添加操作
- lpush/rpush key value1[value2…]: 从左边/右边向列表中PUSH值(一个或者多个)
- lpushx/rpushx key value: 向已存在的列名中push值(一个或者多个),list不存在 lpushx失败
- linsert key before|after pivot value: 在指定列表元素的前/后 插入value
- 查找操作
- lindex key index: 通过索引获取列表元素
- lrange key start end: 获取list 起止元素 (索引从左往右 递增)
- llen key: 查看列表长度
- 删除操作
- lpop/rpop key: 从最左边/最右边移除值 并返回
- lrem key count value: count >0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。
- ltrim key start end: 通过下标截取指定范围内的列表
- rpoplpush source destination: 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部
- 修改操作
- lset key index value: 通过索引为元素设值
- 阻塞操作
- blpop/brpop key1[key2] timout: 移出并获取列表的第一个/最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
- brpoplpush source destination timeout: 和rpoplpush功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
代码示例:
Set(集合)
Redis的Set是string类型的无序集合,我们不能通过索引获取元素。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
应用场景: 标签(tag)
常用命令:
- 集合内操作
- sadd key member1[member2…]: 向集合中无序增加一个/多个成员
- srem key member1[member2…]: 移除集合中一个/多个成员
- scard key: 获取集合的成员数
- smembers key: 返回集合中所有的成员
- sismember key member: 查询member元素是否是集合的成员,若存在返回1,不存在返回0
- srandmember key [count]: 随机返回集合中count个成员,count缺省值为1
- spop key [count]: 随机移除并返回集合中count个成员,count缺省值为1
- 集合间操作
- sinter key1 [key2…]: 返回所有集合的交集
- sinterstore destination key1[key2…]: 在SINTER的基础上,存储结果到集合中。覆盖
- sunion key1 [key2…]: 返回所有集合的并集
- sunionstore destination key1 [key2…]: 在SUNION的基础上,存储结果到及和张。覆盖
- sdiff key1[key2…]: 返回所有集合的差集 key1- key2 - …
- sdiffstore destination key1[key2…]: 在SDIFF的基础上,将结果保存到集合中。覆盖
- smove source destination member: 将source集合的成员member移动到destination集合
- sscan key [MATCH pattern] [COUNT count]: 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
代码示例:
Hash(哈希)
几乎所有的编程语言都提供了哈希(hash)结构,Redis中 hash 是一个string类型的field和value的映射表value=