什么是Redis?

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。与传统数据库不同的是 Redis 数据存在内存中,所以读写速度非常快

Redis优缺点有哪些?

优点

  • 读写性能优异:读110000次/s,写81000次/s

  • 支持数据持久化:支持AOFRDB两种持久化方式

  • 支持事务:Redis的所有操作都是原子性的,同时还支持对几个操作合并后的原子性执行

  • 支持多种数据结构:除了支持String类型外还支持HashSetZsetList等数据结构

  • 支持主从复制:主机会自动将数据同步到从机,可以进行读写分离

缺点

  • 缓存和数据库双写一致性问题
  • 缓存雪崩问题
  • 缓存击穿问题
  • 缓存的并发竞争问题

为什么要用Redis?

  • 高性能

假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可

  • 高并发

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库

Redis为什么快?

  • 完全基于内存:绝大部分请求是纯粹的内存操作非常快速。数据存在内存中类似于 HashMap。查找和操作的时间复杂度都是O(1)

  • 采用单线程:避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗

  • 使用多路 I/O 复用模型,非阻塞 IO

  • 传统并发模型:每个 I/O 流(快递)都有一个新的线程管理
    多路复用:只有单个线程,通过跟踪每个 I/O 流的状态来管理多个 I/O

Redis有哪些数据类型?

5种基本数据类型

  • String
  • List
  • Set
  • Zset
  • Hash

3种特殊数据类型

  • Geospatial 地理位置
  • Hyperloglog 基数统计
  • Bitmap位图场景

Redis的应用场景有哪些?

计数器

可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量

缓存

将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率

会话缓存

可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性

消息队列(发布/订阅功能)

List 是一个双向链表,可以通过 lpushrpop 写入和读取消息实现消息队列(不过最好使用 Kafka、RabbitMQ 等消息中间件)

分布式锁实现

在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现

其它

Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能

什么是Redis持久化?

持久化就是把内存的数据写到磁盘中去,防止服务宕机内存数据丢失

Redis 的持久化机制有哪些?

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制

RDB:是Redis DataBase缩写快照,Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照周期

优点

  • 只有一个文件 dump.rdb,方便持久化

  • 容灾性好,一个文件可以保存到安全的磁盘

  • 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能

  • 数据集较大时时,比 AOF 的启动效率更高

缺点

  • 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢失
  • AOF(Append-only file): 将所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储,保存为 aof 文件

AOF:Append Only File,是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据

优点

  • 数据安全,AOF 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次

  • 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题

  • AOF 机制的 rewrite模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))

缺点

  • aof文件比 rdb文件大,且恢复速度慢
  • 数据集大的时候,比rdb启动效率低

对比

  • aof文件比rdb更新频率高,优先使用aof还原数据
  • aof比rdb更安全也更大
  • rdb性能比aof好
  • 如果两个都配了优先加载aof

如何选择合适的持久化方式?

一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整

如果非常关心数据, 但仍然可以承受数分钟以内的数据丢失,那么可以只使用RDB持久化

如果只希望数据在服务器运行的时候存在,也可以不使用任何持久化方式

过期键的删除策略?

Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理

过期策略通常有以下三种

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存

  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。Redis中同时使用了惰性过期定期过期两种过期策略

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),还可以根据具体的业务需求进行自定义的缓存淘汰策略

Redis key的过期时间和永久有效怎么设置?

EXPIREPERSIST命令

Redis的内存淘汰策略有哪些?

Redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略;Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据

全局的键空间选择性移除

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key

设置过期时间的键空间选择性移除

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

Redis的内存淘汰策略的选取并不会影响过期的key的处理。

  • 内存淘汰策略用于处理内存不足时的需要申请额外空间的数据
  • 过期策略用于处理过期的缓存数据

Redis的内存用完了会发生什么?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)或者可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容

Redis如何做内存优化?

可以利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以应该尽可能的将数据模型抽象到一个散列表里面。比如web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而应该把这个用户的所有信息存储到一张散列表里面

Redis线程模型?

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型

参考https://www.cnblogs.com/barrywxx/p/8570821.html

什么是事务?

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行

Redis事务的概念?

Redis 事务的本质是通过MULTIEXECWATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中

总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

Redis事务的三个阶段?

  1. 事务开始 MULTI

  2. 命令入队

  3. 事务执行EXEC

事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求将会把请求放入队列中排队

Redis事务相关命令?

  • Redis事务功能是通过MULTIEXECDISCARDWATCH 四个原语实现的

  • Redis会将一个事务中的所有命令序列化,然后按顺序执行

  • Redis不支持回滚,Redis 在事务失败时不进行回滚,而是继续执行余下的命令, 所以 Redis 的内部可以保持简单且快速

  • 如果在一个事务中的命令出现错误,那么所有的命令都不会执行

  • 如果在一个事务中出现运行错误,那么正确的命令会被执行

  • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set(CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令

  • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行

  • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil

  • 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出

  • UNWATCH命令可以取消Watch对所有key的监控

事务管理(ACID)概述

  • 原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生

  • 一致性(Consistency)

    事务前后数据的完整性必须保持一致

  • 隔离性(Isolation)

    多个事务并发执行时,一个事务的执行不应影响其他事务的执行

  • 持久性(Durability)

    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有持久性

Redis事务支持隔离性吗?

Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此Redis 的事务是总是带有隔离性的

Redis事务保证原子性吗,支持回滚吗?

Redis中单条命令是原子性执行的,但事务不保证原子性且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行

Redis事务其他实现?

  • 基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行。其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完
  • 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁

当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写

返回值:设置成功返回 1 | 设置失败返回 0

如何解决Redis的并发竞争 Key 问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和期望的顺序不同,也就导致了结果的不同

方案一:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)

方案二:并发量过大的情况下,可以通过消息中间件进行处理,把Redis.Set操作放在队列中使其串行化必须依次执行

什么是 RedLock

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

  • 安全特性:互斥访问,即永远只有一个 client 能拿到锁
  • 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
  • 容错性:只要大部分 Redis 节点存活就可以正常提供服务

缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉

使缓存集中失效的原因:

1、Redis服务器宕机

2、对缓存数据设置了相同的过期时间,导致某时间段内缓存集中失效

解决方案

1、针对原因1,可以实现Redis的高可用,Redis Cluster 或者 Redis Sentinel(哨兵) 等

2、针对原因2,设置缓存过期时间时加上一个随机值,避免缓存在同一时间过期

3、使用双缓存策略,设置两个缓存,原始缓存和备用缓存,原始缓存失效时,访问备用缓存,备用缓存失效时间设置长点

缓存穿透

缓存穿透表示查询一个一定不存在的数据,由于没有获取到缓存,所以没写入缓存,导致这个不存在的数据每次都需要去数据库查询,失去了缓存的意义。

请求的数据大量的没有获取到缓存,导致走数据库,有可能搞垮数据库,使整个服务瘫痪

解决方案

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿

缓存击穿表示某个key的缓存非常热门,有很高的并发一直在访问,如果该缓存失效,请求会同时打到数据库,压垮数据库

缓存击穿与缓存雪崩的区别是:

  • 缓存击穿针对的是某一热门key缓存
  • 缓存雪崩针对的是大量缓存的集中失效

解决方案

  • 设置热点数据永远不过期

  • 加互斥锁

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据

解决方案

  • 直接写个缓存刷新页面,上线时手工操作一下

  • 数据量不大,可以在项目启动的时候自动进行加载

  • 定时刷新缓存

如何保证缓存与数据库双写时的数据一致性?

TODO

Redis回收进程如何工作的?

  1. 一个客户端运行了新的命令,添加了新的数据。
  2. Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  3. 一个新的命令被执行,等等。
  4. 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

Redis回收使用的是什么算法?

LRU算法