Redis简介

Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统。它以键值对形式存储数据,支持多种数据结构,它可以用作数据库、缓存和消息中间件。 它基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一。

  • MySQL:数据关系型数据库,使用的是表结构。
  • Redis:非关系型数据库,使用的是key-value

核心特性

  1. 支持持久化功能,也就是将数据从内存保存到磁盘中。
  2. 支持丰富的数据类型,包括:string、set、sort set、list、hash
  3. 支持数据的备份,也就是主从复制。

Redis的优点

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持publish/subscribe, 通知, key过期等等特性。

常用命令

单进程模型来处理客户端的请求,对读写等事件的响应通过对epoll的包装来做到。

默认16个数据库,编号0-15,可以通过select + num切换数据库

DEL key	删除key 
DBSIZE 查看当前数据库的key的数量
Flushdb清空当前数据库,flushall清空所有数据库
Keys *,keys k? 问号表示占位符
Move key [num] 把key从当前库移动到目标库
Exists key 判断key是否存在
Ttl key 查看key的剩余有效时间,-1表示永不过期,-2表示已过期
Expire key seconds 为给定的key设置过期的时间
Type key 查看key的类型

基本数据结构

类型 简介 特性 场景
String(字符串) 二进制安全 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M
Hash(字典) 键值对集合,即编程语言中的Map类型 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) 存储、读取、修改用户属性
List(列表) 链表(双向链表) 增删快,提供了操作某一段元素的API 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列
Set(集合) 哈希表实现,元素不重复 1、添加、删除、查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐
Sorted Set(有序集合) 将Set中的元素增加一个权重参数score,元素按score有序排列 数据插入集合时,已经进行天然排序 1、排行榜 2、带权重的消息队列

1. 字符串(String)

  • 最基本的类型,你可以理解成一个key对应一个value,string 类型的值最大能存储512MB 。

  • 存储文本或二进制数据

  • 常用命令:

    SET key value  设定指定key的值
    MGET/MSET key1 [value1] key2 [value2] 获取设置多个给定的key值
    GET key 获取指定key的值
    GETRANGE key start end 返回字符串的子串
    SETRANGE key offset value 用value覆盖字符串的值,从offset开始
    GETSET key value将给定key的值设为value ,并返回key的旧值(old value)。
    SETEX 将值 value 关联到 key,并设置 key 的过期时间为 seconds (以秒为单位)。
    INCR/INCRBY key [num] 对value值加1/加num,value必须是数字

2. 哈希(Hash)

  • 一个string 类型的field(字段)和value(值)的映射表。

  • 存储对象属性

  • Key-value模式不变,但value是一个键值对,相当于map<key, map<key1, value>>

  • 常用命令:

    HSET key field value 将哈希表 key 中的字段 field 的值设为 value
    HMSET key field1 value1 [field2 value2 …]同时将多个 field-value (域-值)对设置到哈希表 key 中
    HGET key field / HMGET key field1 [field2] 获取给定字段的值
    HGETALL key 获取在哈希表中指定 key 的所有字段和值
    HKEYS key 获取所有哈希表中的字段
    HVALS key 获取哈希表中所有值

3. 列表(List)

  • 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
  • 实现消息队列

  • 常用命令:

    LPUSH key value1 value2 …  将一个或多个值插入到列表头部,头插法
    RPUSH key value1 value2 … 将一个或多个值插入到列表尾部,尾插法
    LPOP key 队头出队
    RPOP key 队尾出队
    LRANGE key start stop 获取列表指定范围内的元素
    支持下标,下标从0开始
    LSET key index value 通过下标设置列表元素的值
    LINDEX key index 通过下标获取列表中的元素
    LREM key count value 从队头开始移除count个值为value的列表元素

4. 集合(Set)

  • String类型的无序集合

  • 通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

  • 存储唯一元素

  • 常用命令:

    SADD key member1 member2 … 向集合添加一个或多个成员
    SCARD key 获取集合的成员数
    SMEMBERS key 返回集合中的所有成员
    SISMEMBER key member 判断 member 元素是否是集合 key 的成员
    SMOVE source destination member 将member元素从source集合移动到destination集合
    SREM key value 删除集合中值为value的元素
    SRANDMEMBER key num 在集合中随机选出num个数
    SPOP key [num] 移除并返回集合中的一个/num个随机元素

5. 有序集合(ZSet)

  • 带权重的元素集合,每个元素都会关联一个double****类型的分数(权重)。redis正是通过分数来为集合中的成员进行从小到大的排序。

  • string类型元素的集合,且不允许重复的成员,有序集合的成员是唯一的,但分数(权重,score)却可以重复。。

  • 常用命令:

    向有序集合添加一个或多个成员,或者更新已存在成员的分数
    ZADD key score1 member1 [score2 member2]
    ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员
    ZRANGEBYLEX key min max [LIMIT offset count] 通过字典区间返回有序集合的成员(分数要一致)
    ZCARD key 获取有序集合的成员数
    ZCOUNT key min max 计算在有序集合中指定区间分数的成员数

持久化机制

Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。

RDB(Redis Database)

当前数据保存到硬盘(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),在指定的时间间隔能对你的数据进行快照存储.指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。(/var/lib/redis/6379)

触发RDB快照的方式

  1. 在指定的时间间隔内,执行指定次数的写操作

  2. 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令

  3. 执行flushall 命令,清空数据库所有数据,意义不大

  4. 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义不大。

Conf# redis.conf配置
save 900 1 # 15分钟至少1个key变化,就会自动触发快照。
save 300 10 # 5分钟至少10个key变化,就会自动触发快照。
dbfilename dump.rdb

优缺点

优点

  • 适合大规模数据的恢复,恢复速率是比较快的
  • 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。
  • 使用的是二进制进行存储

缺点

  • 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
  • 备份时占用内存,因为Redis 在备份时会独立创建一个fork子进程,将数据写入到一个临时文件
    (此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。

AOF(Append Only File)

记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以redis协议追加. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.保存每次写的操作到文件末尾

默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。

appendonly yes
appendfsync everysec # 每秒同步

重写

AOF 的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF 文件的体积也会变得越来越大。

可以在不打断服务客户端的情况下, 对 AOF 文件进行重建(rebuild)。执行 BGREWRITEAOF命令, Redis 将生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令

set k1 100
set k2 200
set k3 hello
set k1 400
incy k1
//...100
//104条代码
set k1 500
set k2 200
set k3 hello

Redis 2.4 则可以自动触发 AOF 重写, 具体信息请查看 配置文件。自动触发命令:

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大
于64M时触发
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

优缺点

优点

  • 对数据的完整性与一致性的比较高
  • 数据的丢失比较小(相对RDB而言)

缺点

  • 不适合大规模数据的恢复,因为恢复的速率比较慢,需要将所有的写命令重新执行一次
  • 因为使用的是文件追加的模式,所以文件的体积会比较大

总论

  • 两种持久化的方式都可以同时存在,如果对数据的恢复速率比较快,可以选择rdb持久化;如果对
    数据的完整性与一致性要求比较高(更加精确),可以选择aof持久化
  • 单独使用aof持久化的方式,也是可以启动服务器的
  • 如果aof持久化是唯一的方式,那么如果该文件损坏了,就不能启动服务器
  • 如果aof文件损坏了,可以使用修复命令进行修复文件,redis-check-aof

事务与锁机制

概念

Redis事务可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序的串行化执行,而不会被其他命令插入,不许加塞。Redis事务是通过命令队列机制实现的批量操作执行方式,与传统关系型数据库的ACID事务有本质区别。

Redis事务有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

其核心特点是:

  1. 非阻塞式执行:命令进入队列时不立即执行
  2. 无隔离级别:事务执行期间不保证数据隔离
  3. 弱原子性:仅保证命令连续执行,不保证整体成功

命令

命令 作用描述
MULTI 开启事务,进入命令排队模式
EXEC 执行事务队列中的所有命令
DISCARD 取消事务,清空命令队列
WATCH 监视指定key,实现乐观锁(若被监视key在EXEC前被修改,则事务失败)
UNWATCH 取消所有WATCH监视

事务执行流程

1、正常执行 MULTI+EXEC 分三步:开始,加入队列,执行

2、放弃事务 DISCARD 类似mysql中的rollback

3、事务执行中有错误的命令:部分执行成功or全部不执行?

		如果输入命令后显示的是QUEUED,表示入队成功,部分执行。

		如果提示ERROR,事务被放弃,全部不执行。

总结:编译时出错和运行时出错的区别。

4、WATCH监控 如果watch监控的keys发生了变化,EXEC执行的事务将被放弃

5、UNWATCH取消监控

6、一旦执行EXEC,WATCH监控会被取消。

> WATCH balance    # 监控账户余额
> MULTI
> DECRBY balance 100
> INCRBY stock 1
> EXEC # 执行时若balance被修改则返回(nil)

redis的事务是没有原子性的,也就是在事务中每一条命令执行成功与否,对其他命令没有影响

队列原子性:EXEC执行时队列命令连续执行,不会被其他客户端命令打断

# 情况2:命令执行时出错(如类型错误)
> MULTI
> SET key3 "abc"
> INCR key3 # 语法正确但执行错误
> EXEC # 执行错误命令返回错误,其他命令正常执行

# 情况1:命令入队时出错(如语法错误)
> MULTI
> SET key1 value1
> SET key2 # 语法错误
> EXEC # 直接拒绝执行整个事务,返回错误

特性

特性 Redis事务 关系型数据库事务
原子性 部分原子(单命令原子) 完整原子性
隔离性 无隔离 支持多种隔离级别
持久性 依赖持久化配置 通常保证持久性
回滚机制 不支持 支持完整回滚
执行方式 批量执行 交互式执行
锁机制 乐观锁(WATCH) 悲观锁(行锁/表锁

乐观锁和悲观锁

悲观锁先加锁再操作,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,当其他线程想要访问数据时,都需要阻塞挂起。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁,读锁,写锁等,都是在操作之前先上锁。

乐观锁(Optimistic Lock),无锁并发控制机制,核心思想是: “先操作,后校验” —— 默认认为并发操作冲突的概率较低,允许用户直接修改数据,但在提交时会验证数据是否被其他操作修改过。若发生冲突,则通过回滚重试策略解决。

在 Redis 中实现乐观锁主要依赖 WATCH 命令 + 事务机制,其本质是通过数据版本校验实现的 CAS(Compare and Swap)操作

version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

CAS操作方式:即 Compare And Swap,CAS是乐观锁技术,涉及到三个操作数,数据所在的内存值V,预期值A,新值B。当需要更新时,判断当前内存值V与之前取到的值A是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

维度 悲观锁 乐观锁
设计哲学 “先加锁再操作”(宁错杀不放过) “先操作后校验”(信任但验证)
冲突预期 认为数据冲突概率高 认为数据冲突概率低
锁机制类型 独占锁(排他锁) 无锁机制(CAS机制)
典型实现 数据库行锁、synchronized、ReentrantLock 版本号机制、CAS原子操作、Redis WATCH
资源消耗 高(锁维护成本) 低(无锁竞争)
适用场景 写操作密集型场景 读操作密集型场景
失败处理 线程阻塞等待 事务回滚、重试

高可用架构

主从复制

是Redis实现数据冗余和高可用的核心机制,通过建立主节点(Master)与从节点(Replica)的数据同步关系,实现:

  • 一主多从架构:1个主节点可对应多个从节点
  • 单向数据同步:数据从主节点流向从节点
  • 读写分离:主节点处理写操作,从节点提供读服务
image-20250309173453244

核心必要性

1. 数据冗余与灾备
  • 数据热备份:从节点实时复制主节点数据
  • 故障恢复:主节点宕机后可从从节点快速恢复
  • 防止数据丢失:即使主节点硬盘损坏,从节点仍保留数据副本
2. 高可用基石
  • 故障转移基础:为哨兵模式提供节点切换能力
  • 服务不间断:主节点故障时可手动/自动切换从节点
  • 零停机维护:主节点维护期间从节点可继续服务
3. 读性能扩展
  • 负载均衡:通过多个从节点分散读请求压力,由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载
  • 地理级扩展:在不同机房部署从节点加速区域访问
  • 大数据分析:在从节点执行耗时统计操作,避免影响主节点

使用方式

  1. 由于需要启动多个Redis实例,我们先把多个实例使用的配置文件做好修改。

  2. 需要修改的配置信息有:

    1. 先拷贝多份配置文件,用不同的名字命名
    2. 守护进程启动 daemonize yes
    3. Pid 文件名字
    4. 指定一个端口
    5. Log日志文件名
    6. Dump.rdb文件名
    7. Appendfilename “”
    8. 等等
  3. 通过刚改好的多个配置文件启动多个redis服务程序

    Redis-server /path/to/***.conf

  4. 主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情

  5. 从节点开启主从复制,有3种方式:

    ​ (1)配置文件

         在从服务器的配置文件中加入:slaveof <masterip> <masterport>
    

    ​ (2)启动命令

    ​ redis-server启动命令后面加入 –slaveof

    ​ (3)客户端命令

    ​ Redis服务器启动后,直接通过客户端执行命令:slaveof ,则该Redis实例成为从节点。

  6. 通过info replication可以查看主从信息。

复制流程

  1. 全量同步:主节点生成RDB发送给从节点
  2. 增量同步:通过复制缓冲区持续同步
  3. 作为主机的程序结束,从机的角色不会变化(角色还是slave)。
  4. 主机重新启动,从机可以继续与主机保持连接(从机原地待命)。
  5. 从机的程序重启后,角色变成了master,从机每次与master断开连接后,都需要重新连接,除非把slaveof配置到配置文件里
  6. 只要重新连接master,一次完全同步(全量复制)将被自动执行。

主从复制存在的一个问题是故障恢复无法自动化

哨兵模式

哨兵模式(Sentinel) 是Redis官方提供的高可用解决方案,基于Redis主从复制,通过独立进程监控主从架构,解决主节点故障恢复的自动化问题,实现自动故障发现主节点切换,进一步提高系统的高可用性。哨兵系统由多个哨兵节点组成,共同完成以下核心任务:

  1. 持续监控:检测主从节点健康状态
  2. 自动故障转移:主节点故障时选举新主
  3. 配置中心:提供最新的主节点地址
  4. 通知告警:通过API通知运维系统

Redis Sentinel是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress),这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。

虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel 。启动 Sentinel 实例必须指定相应的配置文件, 系统会使用配置文件来保存 Sentinel 的当前状态, 并在 Sentinel 重启时通过载入配置文件来进行状态还原。如果启动 Sentinel 时没有指定相应的配置文件, 或者指定的配置文件不可写(not writable), 那么 Sentinel 会拒绝启动。

#在/etc/redis下面建一个文件sentinel.conf,然后在其中添加如下命令
sentinel monitor mymaster 127.0.0.1 6379 2

哨兵节点的启动有两种方式,二者作用是完全相同的:

redis-sentinel /path/to/sentinel.conf

redis-server /path/to/sentinel.conf –sentinel

经典面试题

缓存雪崩

概念:大量热点数据同一时间失效,导致本来可以在缓存中查找到的数据,必须要到底层数据库下面
进行查找,会对底层数据库的压力比较大。

image-20250309174517327

解决方案:1、分散数据的失效时间(随机过期时间) 2、可以延长热点数据的失效时间或者让数据不失效

缓存击穿

问题:某热点数据失效了,导致本来可以在缓存中访问到的,现在只能到底层数据库中进行查找,会
增加底层数据库的压力
解决方法:1、延长热点数据的失效时间或者让数据永远不失效 2、让每个客户端互斥访问数据库(加
锁),一旦查到数据就缓存至Redis内,避免其他大量请求同时穿过Redis访问底层数据库

image-20250309174736565

缓存穿透

问题:要访问的数据本身是不存在,在缓存中找不到,就会到底层数据库中进行查找,也会增加对底层
数据库的访问压力。
解决方案:1、可以设置键值对,存放在缓存中,value为空,<key1,NULL>

					2、布隆过滤器

布隆过滤器是一个位数组(Bit Array) + 多个哈希函数组成的数据结构,通过以下机制工作:

  1. 添加元素:使用k个哈希函数计算元素值,将对应位数组位置置1

  2. 查询元素

    :检查所有哈希位置是否均为1

    • 存在可能:所有位为1,高效查找的同时存在哈希冲突的可能性,比如不同的两数据可能都落在所有的相同位置,而事实上,可能数据库中并不存在其中一个数据,存在误判的情况。
    • 绝对不存在:任一位置为0(无漏判)
方案 适用场景 不适用场景
布隆过滤器 海量数据存在性判断 需要精确判断的场景
空值缓存 数据量小的低频请求 数据动态变化的场景

Redis为什么快?

内存存储:数据操作的物理极限

  1. 零磁盘访问 所有数据存储在内存中,读写操作无需磁盘I/O,内存访问速度比磁盘快10万倍以上(内存访问约100ns,机械磁盘寻道约10ms)。
  2. 避免传统数据库瓶颈 传统数据库(如MySQL)需要维护磁盘数据一致性,涉及Buffer Pool、Redo Log、锁竞争等开销,而Redis直接操作内存,规避了这些瓶颈。

单线程模型:无锁竞争的极致

  1. 避免上下文切换 单线程处理所有命令,无需线程切换、锁竞争,减少CPU资源浪费。
  2. 原子性操作 所有命令按顺序执行,天然保证原子性,无需加锁。
  3. 非阻塞设计
    • 通过IO多路复用(epoll/kqueue)处理网络请求。
    • 异步处理客户端连接、命令解析,主线程仅处理核心逻辑。

网络IO优化:最大化吞吐量

  1. IO多路复用 Redis使用epoll(Linux)或kqueue(BSD)实现非阻塞IO,单线程可管理数万连接。
  2. 管道化(Pipeline) 客户端可将多个命令打包发送,减少网络往返次数,提升吞吐量。
  3. 协议简单 Redis协议为二进制安全的文本协议,解析效率高。

持久化策略:平衡速度与可靠性

  1. RDB快照
    • 通过fork子进程生成内存快照,主进程无阻塞
    • 数据恢复速度快(直接加载二进制文件)。
  2. AOF日志
    • 支持appendfsync everysec策略,每秒刷盘一次,平衡性能与数据安全。
    • AOF重写时使用子进程,避免影响主线程

其他优化细节

  1. 内存分配器(jemalloc) 默认使用高效内存分配器,减少内存碎片。
  2. LRU近似算法 淘汰策略通过随机采样近似LRU,避免全局排序开销。
  3. Lua脚本缓存 重复执行的Lua脚本会被缓存,避免重复解析。

最佳实践建议

  1. 生产环境务必启用持久化
  2. 主从节点建议分散在不同物理机
  3. 内存占用不超过物理内存的70%
  4. 使用Pipeline提升批量操作性能