定义
Redis是一个开源的,基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。
特征
-
高性能
-
支持丰富的数据类型
-
丰富的操作类型,操作是原子性的
-
支持周期性持久化
-
支持分布式
-
开源免费,社区活跃
数据类型
|------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 数据类型 | 解释 | 使用场景 | 基本命令 |
| string | 最简单的值类型。字符串存储字节序列,包括文本、序列化对象和二进制数组。单个Redis 字符串最大可以为512MB | * 缓存层 * 计数器:视频播放量,文章浏览量 | * SET:存储一个字符串值。 * SETNX:仅当键不存在时才存储字符串值。对于实现锁很有用。 * GET:检索字符串值。 * MGET:在一次操作中检索多个字符串值。 |
| hash | Redis 哈希是一种记录类型,其结构为字段值对的集合。 | * 表示基本对象 * 存储计数器分组 | * HSET:设置哈希表中一个或多个字段的值。 * HGET:返回给定字段的值。 * HMGET:返回一个或多个给定字段的值。 * HINCRBY:将给定字段的值增加提供的整数。 |
| list | Redis 列表是字符串值的链接列表 | * 实现堆栈和队列 * 为后台工作系统构建队列管理:回复评论、点赞 | * LPUSH:在列表的头部添加一个新元素; * RPUSH:添加到尾部。 * LPOP:从列表头部移除并返回一个元素; * RPOP:执行相同操作,但从列表尾部移除。 * LLEN:返回列表的长度。 * LMOVE:原子地将元素从一个列表移动到另一个列表。 * LRANGE:从列表中提取一定范围的元素。 * LTRIM:将列表缩减为指定的元素范围。 |
| set | Redis 集合是唯一字符串(成员)的无序集合 | * 跟踪唯一项目(例如,跟踪访问给定博客文章的所有唯一 IP 地址):去重、抽奖 * 表示关系(例如,具有给定角色的所有用户的集合):初始化用户池 * 执行常见的集合运算,例如交集、并集和差集。 | * SADD:向集合中添加新成员。 * SREM:从集合中删除指定成员。 * SISMEMBER:测试字符串是否为集合成员。 * SINTER:返回两个或多个集合共同拥有的成员集合(即交集)。 * SCARD:返回集合的大小(又称基数)。 |
| zset | Redis 有序集是按相关分数排序的唯一字符串(成员)的集合。当多个字符串具有相同的分数时,这些字符串将按字典顺序排序 | * 排行榜 * 速率限制器 | * ZADD:将新成员和相关分数添加到排序集中。如果该成员已存在,则更新分数。 * ZRANGE:返回在给定范围内排序的排序集的成员。 * ZRANK:返回所提供成员的排名,假设排序按升序排列。 * ZREVRANK:返回所提供成员的排名,假设排序集按降序排列。 |
| Stream | Redis Stream 是 Redis 5.0 版本引入的一种新的数据结构,Redis 可为每个流条目生成一个唯一 ID,用于实现消息队列的功能。Stream 是一种有序的、可持久化的数据结构,可以用来存储多个字段和值对的消息记录。 | * 事件源(例如跟踪用户操作、点击等) * 传感器监控(例如,现场设备的读数) * 通知(例如,将每个用户的通知记录存储在单独的流中) | * XADD:向流中添加新条目。 * XREAD:读取一个或多个条目,从给定位置开始并随时间向前移动。 * XRANGE:返回两个提供的条目 ID 之间的条目范围。 * XLEN:返回流的长度。 |
| Bitmap | 位图不是实际的数据类型,而是一组定义在字符串类型上的面向位的操作,该字符串被视为位向量。由于字符串是二进制安全的 blob,其最大长度为 512 MB,因此它们适合设置最多 2^32 个不同的位。 | * 您可以对一个或多个字符串执行按位运算 * 对于集合成员对应于整数 0-N 的情况的有效集合表示。 * 对象权限,其中每个位代表一个特定的权限,类似于文件系统存储权限的方式。 | * SETBIT将提供的偏移量处的位设置为 0 或 1。 * GETBIT返回给定偏移量的位的值。 |
| Bitfield | Redis 位域允许您设置、递增和获取任意位长度的整数值。 这些值使用二进制编码的 Redis 字符串存储。位字段支持原子读取、写入和增量操作。 | * 您可以对从无符号 1 位整数到有符号 63 位整数的任何值进行操作。 * 管理计数器和类似数值的理想选择 | * BITFIELD原子地设置、增加和读取一个或多个值。 * BITFIELD_RO是 的只读变体BITFIELD |
| Geospatial | Redis 地理空间索引可让您存储坐标并搜索它们。 | * 查找给定半径或边界框内的附近点 | * GEOADD:将位置添加到给定的地理空间索引(请注意,在此命令中经度位于纬度之前)。 * GEOSEARCH:返回具有给定半径或边界框的位置。 |
过期策略
-
惰性删除 。查询key 的时候才对key进行检测。若key已经过期则删除,若key没有过期则返回。缺点是如果过期的key 一直没有被访问,则会一直无法被删除,存在于内存中。
-
定期删除 。redis 每隔一段时间对数据库做一次检查,删除里面过期的key。由于不可能对所有key 做轮询,所以redis 会每次随机选取一些key 做检查和删除。
-
内存淘汰机制
-
volatile-lru:针对设置了过期时间的key,使用lru(最近很少使用)算法进行淘汰
-
allkeys-lru:针对所有key 使用lru算法进行淘汰
-
volatile-lfu:针对设置了过期时间的key,使用lfu(最近不经常使用)算法进行淘汰
-
allkeys-lfu:针对所有key 使用lfu 算法进行淘汰
-
volatile-random:从所有设置了过期时间的key 中使用随机淘汰方式进行淘汰
-
allkeys-random:针对所有的key 使用随机淘汰机制进行淘汰
-
volatile-ttl:针对设置了过期时间的key,越早过期的越先被淘汰
-
noeviction:不会淘汰任何数据,当使用的内存空间超过maxmemory是时,再有写请求来时返回错误
-
持久化方案
Redis 提供了一系列持久性选项包括:
-
RDB(Redis 数据库) :RDB 持久性会按照指定的时间间隔对数据集执行时间点快照。
-
AOF(仅追加文件) :AOF 持久性记录服务器收到的每个写入操作。然后可以在服务器启动时再次重放这些操作,重建原始数据集。命令使用与Redis 协议本身相同的格式进行记录。
-
无持久性 :您可以完全禁用持久性。这有时在缓存时使用。
-
RDB + AOF :您还可以在同一实例中结合使用AOF 和RDB
RDB
-
优点
-
RDB 是 Redis 数据的一个非常紧凑的单文件时间点表示。RDB 文件非常适合备份。例如,您可能希望每小时存档最近 24 小时内的 RDB 文件,并每天保存 30 天的 RDB 快照。这样,您就可以在发生灾难时轻松恢复数据集的不同版本。
-
RDB 非常适合灾难恢复,它是一个单一的紧凑文件,可以传输到远程数据中心,或者传输到 Amazon S3(可能加密)。
-
RDB 可最大程度地提高 Redis 的性能,因为 Redis 父进程为了持久化所需要做的唯一工作就是派生一个子进程,然后由子进程来完成其余所有工作。父进程永远不会执行磁盘 I/O 或类似操作。
-
与 AOF 相比,RDB 允许使用大数据集更快地重启。
-
在副本上,RDB 支持重启和故障转移后的部分重新同步。
-
-
缺点
-
如果您需要尽量减少 Redis 停止工作(例如断电后)时数据丢失的可能性,那么 RDB 并不是一个好选择。您可以配置生成 RDB 的不同保存点 (例如,在至少五分钟后对数据集进行 100 次写入后,您可以有多个保存点)。但是,您通常每五分钟或更长时间创建一个 RDB 快照,因此,如果 Redis 因任何原因而未正确关闭而停止工作,您应该准备好丢失最近几分钟的数据。
-
RDB 需要经常使用子进程 fork() 才能将其持久保存在磁盘上。如果数据集很大,fork() 可能会很耗时,如果数据集很大且 CPU 性能不佳,可能会导致 Redis 停止为客户端提供服务几毫秒甚至一秒钟。AOF 也需要 fork(),但频率较低,您可以调整重写日志的频率,而无需牺牲持久性。
-
AOF
-
优点
-
使用 AOF Redis 的持久性更强:您可以采用不同的 fsync 策略:完全不进行 fsync、每秒进行 fsync、每次查询时进行 fsync。使用每秒进行 fsync 的默认策略,写入性能仍然很好。fsync 使用后台线程执行,主线程会在没有 fsync 进行时尽力执行写入,因此您只会丢失一秒钟的写入。
-
AOF 日志是仅附加日志,因此不会出现寻道,也不会在断电时出现损坏问题。即使日志因某种原因(磁盘已满或其他原因)以半写命令结束,redis-check-aof 工具也能够轻松修复它。
-
当 AOF 太大时,Redis 能够在后台自动重写。重写是完全安全的,因为 Redis 在继续向旧文件追加内容的同时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,并且一旦第二个文件准备就绪,Redis 就会切换这两个文件并开始向新文件追加内容。
-
AOF 以易于理解和解析的格式逐一记录所有操作的日志。您甚至可以轻松导出 AOF 文件。例如,即使您不小心使用该FLUSHALL命令刷新了所有内容,只要在此期间没有执行日志重写,您仍然可以通过停止服务器、删除最新命令并重新启动 Redis 来保存数据集。
-
-
缺点
-
对于同一数据集,AOF 文件通常比等效的 RDB 文件更大
-
根据确切的 fsync 策略,AOF 可能比 RDB 慢。一般来说,将 fsync 设置为每秒一次时 ,性能仍然非常高,而禁用 fsync 时,即使在高负载下,它也应该与 RDB 一样快。即使在写入负载巨大的情况下,RDB 仍能够提供更多关于最大延迟的保证。
-
RDB/AOF 对比
|--------|------------------------------------|----------------------------------------------------------------------|
| - | RDB | AOF |
| 说明 | 把当前内存中的数据集快照写入磁盘。恢复时是将快照文件直接读到内存里。 | 通过持续不断的保存Redis 服务器所执行的更新命令来记录数据库状态,类似mysql 的 binlog。恢复数据时要从头开始回放更新命令 |
| 磁盘刷新频率 | 低 | 高 |
| 文件大小 | 小 | 大 |
| 数据恢复效率 | 高 | 低 |
| 数据安全 | 低 | 高 |
分布式方案
分片方案
|-------|---------------------------------------------------------------------------------------|
| 客户端分片 | 将分片工作放在业务程序端。不依赖于第三方分布式中间件,实现方法和代码可控,对开发人员要求高。 |
| 代理分片 | 将分片工作交给专门的代理程序来做,运维方案。代表:Twemproxy,Codis |
| 服务器分片 | Redis Cluster 将所有key映射到16384个slot 中,集群中每个redis 实例负责一部分,业务程序通过集成的redis cluster 客户端进行操作 |
主从复制
主从复制是redis 最基本、最常用的同步方式。它的原理是将主节点的数据复制到从节点,使得从节点的数据与主节点保持一致。主从复制是异步的,从节点与主节点的数据不是实时同步的。如果主节点发生故障,从节点可能会因为数据延迟而出现数据丢失的情况。
集群模式
Redis集群是Redis提供的分布式数据库方案,它将数据分散在多个节点上,每个节点都包含整个数据集的一部分。Redis集群通过分片来分布数据,每个节点负责数据集的一个片段。集群中的每个分片都可以有一个或多个从节点,用于故障转移和高可用性。
哨兵模式
除了主从复制外,Redis 还提供了哨兵模式作为高可用性解决方案。哨兵模式可以自动检测主节点的状态。当主节点发生故障时,会自动将一个从节点切换为主节点。切换过程中,从节点会先将自己的数据与主节点保持一致,然后才能成为新的主节点。因此,在哨兵模式下,数据同步的过程与主从复制类似,但是其实现方式更加自动化。
缓存方案
redis 用作缓存组件时,其基于内存的读写特性,比基于磁盘读写的数据库性能要高很多,适合缓存高频热点的数据,来提高读性能。这样可以降低对数据库服务器的查询请求,提高系统性能。
数据一致性
读写步骤
在使用缓存与数据库保持一致性的场景中,通常遵循以下读写步骤来确保数据的一致性。
- 读取操作:
- 检查缓存:客户端发起读取请求时,首先在缓存中查找所需数据。
-
缓存命中:如果缓存中有数据(缓存命中),则直接返回缓存中的数据给客户端。
-
缓存未命中:
-
如果缓存中没有数据(缓存未命中),则从数据库中读取数据。
-
将从数据库中读取的数据写入缓存,以便下次可以直接从缓存中读取。
-
返回数据库中的数据给客户端。
-
- 写入操作:
-
更新数据库:客户端发起写入请求时,首先更新数据库中的数据。
-
失效缓存:在数据库更新成功后,立即使缓存中的相关数据失效或更新缓存中的数据。这样可以确保下次读取操作时,客户端能够从数据库中获取最新的数据。
分布式锁
- 使用分布式锁来保证在更新数据库和失效缓存的过程中,不会有其他并发操作干扰。
读写串行化
- 通过队列等机制,确保对同一数据的读写操作不会并发执行。
订阅数据库变更
- 通过订阅数据库的变更日志(如 MySQL 的 Binlog),在数据变更时自动更新或失效缓存。
缓存异常
缓存穿透
-
定义
- 缓存穿透是指查询一个一定不存在的数据,由于缓存中不存在该数据,每次请求都会落到数据库上,从而可能导致数据库压力过大甚至崩溃。
-
造成原因
- 恶意攻击,造成大量访问不存在的key
-
解决方案
-
缓存空对象 :对于查询结果为空的键,也将其存入缓存,并设置一个较短的过期时间。这样,即使是不存在的数据也会被缓存,从而避免频繁查询数据库。
-
布隆过滤器 :在访问缓存之前,使用布隆过滤器检查数据是否可能存在。如果布隆过滤器判断数据不存在,则直接返回,不查询缓存和数据库。
-
接口限流 :对于访问频率极高的请求,可以使用接口限流来防止恶意攻击。
-
安全性检查 :应用程序检查key的合法性,提前拒绝不合法请求;检查请求来源IP,限制访问次数,或设置黑名单。
-
缓存雪崩
-
定义
- 缓存雪崩是指缓存中大量数据同时过期或者缓存服务突然宕机,导致大量请求直接落到数据库上,引起数据库压力骤增,甚至导致数据库崩溃。
-
造成原因
-
redis故障、比如redis 宕机
-
网络出现抖动
-
-
解决方案 :
-
设置不同的过期时间 :为缓存数据设置不同的过期时间,避免大量数据同时过期。
-
使用持久化缓存 :比如Redis的RDB或AOF持久化功能,即使服务重启,也能快速恢复缓存数据。
-
服务熔断和限流 :在缓存失效或者服务故障时,通过服务熔断和限流来保护后端系统。
-
多级缓存 :构建多级缓存架构,例如本地缓存+分布式缓存,即使分布式缓存出现问题,本地缓存仍能提供一定的保护。
-
缓存击穿
-
定义
- 缓存击穿是指某个热点数据在缓存中过期,而此时大量并发请求访问该数据,导致这些请求都落到数据库上,从而可能引起数据库压力过大。
-
造成原因
- 热点key 设置了太短的过期时间
-
解决方案
-
热点数据永不过期 :对于热点数据,可以设置永不过期或者过期时间非常长。
-
互斥锁 :在缓存失效时,通过互斥锁来保证同一时间只有一个请求去查询数据库,并更新缓存,其他请求等待缓存更新完成后再返回数据。
-
数据热加载 :使用后台线程或者定时任务,预先加载热点数据至缓存中。
-
Redis 使用Lua 脚本
Redis 允许用户在服务器上上传和执行 Lua 脚本。脚本可以使用编程控制结构,并在执行时使用大多数命令来访问数据库。由于脚本在服务器中执行,因此从脚本读取和写入数据非常高效。
-
使用脚本的优势
-
提高执行效率:减少网络开销、原子性操作和减少解析时间。
-
简化复杂操作:通过封装多个操作为一个脚本,简化了复杂的操作逻辑。
-
原子性保证:脚本的执行是原子操作,避免了在多线程环境下的竞态条件问题。
-
安全性控制:通过参数化脚本和限制脚本权限,确保脚本的安全性。
-
-
应用场景
-
原子性操作:通过使用Lua脚本,可以实现事务处理、乐观锁、排他锁等原子性操作。
-
复杂计算:Lua脚本可以进行复杂的计算,如计算统计数据、排序、过滤等。
-
批量操作:通过Lua脚本可以实现批量操作,如批量插入、批量删除等。
-
分布式锁:使用Lua脚本可以实现分布式锁,防止多个客户端同时访问共享资源。
-
-
执行步骤
-
要执行 Lua 脚本,可以使用 EVAL 命令。基本格式如下:
-
EVAL script numkeys key [key ...] arg [arg ...]
-
script:Lua 脚本内容。
-
numkeys:指定后面参数中键的数量。
-
key [key ...]:脚本中使用的 Redis 键。
-
arg [arg ...]:传递给 Lua 脚本的参数。
-
-
-
Redis 使用一个脚本缓存,这意味着相同的 Lua 脚本不需要每次执行时都重新发送。一旦脚本被缓存,它就可以通过 EVALSHA 命令通过脚本的 SHA1 校验和来调用。
- EVALSHA sha1 numkeys key [key ...] arg [arg ...]
-
要获取脚本的 SHA1 校验和,可以使用 SCRIPT LOAD 命令:
- SCRIPT LOAD "your lua script here"
-
Redis 事务
Redis 中的事务处理是通过 MULTI、EXEC、DISCARD 和 WATCH 命令来实现的。Redis 的事务允许一组命令在单个步骤中按顺序执行,确保这些命令作为一个单独的操作被处理。
-
以下是 Redis 事务处理的关键点
-
事务中的所有命令都是序列化的,并按顺序执行
-
在 Redis 事务执行过程中,其他客户端发送的请求永远不会得到处理。这保证了命令作为单个隔离操作执行。
-
-
命令
-
MULTI
- MULTI 命令用于开始一个事务。在发出 MULTI 命令后,客户端可以发出多个命令,这些命令不会立即被执行,而是被放入一个队列中。
-
EXEC
- 在 MULTI 命令之后,你可以发送多个命令到队列中。当所有的命令都添加到事务队列后,使用 EXEC 命令来执行所有队列中的命令。
-
DISCARD
- 如果你在执行事务之前改变主意,可以使用 DISCARD 命令来取消事务,并丢弃事务队列中的所有命令。
-
WATCH
- 在事务开始之前,你可以使用 WATCH 命令来监控一个或多个键。如果在事务执行前这些键被修改(通过其他客户端),则 EXEC 命令将不会执行事务中的任何命令,而是返回一个空回复。这种机制可以用作乐观锁。
-
-
注意事项
Redis 事务不支持回滚,如果事务中的某个命令执行失败,那么事务中的其他命令仍然会执行。
Redis 发布/订阅
Redis 的发布/订阅(Publish/Subscribe)模式是一种消息传递范式,允许客户端订阅一个或多个频道(channel),并接收来自这些频道的消息。在这种模式中,发送者(发布者)发布消息到一个频道,而接收者(订阅者)监听这个频道以接收消息。
-
基本概念
-
发布者(Publisher) :发布消息到频道。
-
订阅者(Subscriber) :订阅一个或多个频道,以接收来自这些频道的消息。
-
频道(Channel) :消息传递的通道,发布者和订阅者通过频道进行消息的发送和接收。
-
-
常用命令
-
SUBSCRIBE :订阅一个或多个频道。
- SUBSCRIBE channel [channel ...]
-
UNSUBSCRIBE : 退订一个或多个频道。
- UNSUBSCRIBE [channel [channel ...]]
-
PUBLISH :向指定频道发送消息。
- PUBLISH channel message
-
PSUBSCRIBE :订阅一个或多个符合给定模式的频道。
- PSUBSCRIBE pattern [pattern ...]
-
PUNSUBSCRIBE :退订一个或多个符合给定模式的频道。
- PUNSUBSCRIBE [pattern [pattern ...]]
-
-
注意事项
-
发布/订阅消息是异步的,发布者不会知道消息是否被成功接收。
-
如果没有订阅者监听某个频道,那么向该频道发布的消息将不会被任何人接收。
-
订阅者在订阅频道时处于阻塞状态,只能接收消息或通过退订来退出阻塞状态。
-
发布/订阅模式不支持消息持久化,如果 Redis 服务重启,所有订阅信息和发布过的消息都会丢失。
-
比较
Redis 与 Mysql 的区别
- redis 是No-SQL 数据库,Mysql 是关系型数据库
- Mysql 用于持久化的存储到硬盘,功能强大,速度稍慢,基于磁盘,读写速度没有Redis 快,但是不受空间容量限制,性价比高。redis 用于存储使用较为频繁的数据到缓存中,读取速度快,基于内存,读写速度快,也可做持久化,但是内存空间有限,当数据超过内存空间时,需扩充内存
- redis 是 key-value 数据库,不是传统的关系型数据库,数据主要存储在内存中。Mysql 是关系型数据库,具有事务的基本特征,数据存储在文件中;redis 作为中间件服务,主要作用于缓存,存储重要的及时数据;Mysql 数据库更偏向于数据库服务,类似一种中心仓库,负责数据的管理。
Redis 与 Memcache 的区别
- redis 数据类型多于 Memcache
- redis 可周期性做持久化,Memcache 不能持久化
- redis 支持 master-slave 模式的数据备份,Memcache 不支持数据持久化,无法进行数据备份
- Memcache 是多线程的,Redis 是单线程的;CPU 利用率 Memcache 优于 redis
- 批量读取性能 Memcache 优于 Redis
参考文献
Redis 官方文档:Develop with Redis | Docs