Redis简介

Redis是一款高性能、开源、内存键值存储数据库,由Salvatore Sanfilippo(网名:antirez)使用C语言开发,并遵循BSD开源协议。

  • 内存数据库

Redis将所有数据存储在内存中,这使得它的数据访问速度非常快,适用于构建高性能的应用程序,尤其是在需要快速响应的缓存场景下。

  • 持久化

Redis支持两种持久化方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。RDB通过周期性的将数据转储到硬盘上生成快照文件,而AOF则是记录每次写操作的指令序列,确保在服务器重启时能够恢复数据。

  • 复制与集群

Redis支持主从复制(master-slave replication),可用于数据备份和故障切换。通过Redis Sentinel可以实现高可用性,而Redis Cluster则提供了自动分片功能,允许创建大规模的分布式环境。

Redis的数据结构

Redis有5种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。

3 种特殊数据类型:HyperLogLog(基数统计)、Bitmap (位图)、Geospatial (地理位置)。

除了上面提到的之外,还有一些其他的比如 Bloom filter(布隆过滤器)open in new window、Bitfield(位域)。

这里仅介绍5种基础类型。

  • String(字符串)

最基本的数据结构,可以存储任何类型的数据,数据最大512M。

key

value

name

张三

  • List(列表)

字符串列表,按照插入顺序排序,元素可重复。可以添加一个元素到头部或尾部,底层是个链表结构。

key

value

name

张三 李四 王五

  • Set(集合)

是String类型的无序无重复集合。

key

value

name

张三

李四

王五

  • Hash(哈希)

Hash是一种键值对集合的数据结构,把key-value的value映射到field-value这样的表中。

其中field和value都是字符串类型的数据。

key

value

user1

field

value

name

张三

age

33

  • Zset(有序集合)

Zset在Set的基础上增加了排序功能,每个元素都会关联一个分数,通过分数来进行排序

key

value score

name

张三 1

李四 2

王五 3

Redis 为什么这么快?

Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:

Redis 基于内存,内存的访问速度是磁盘的上千倍;

Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);

Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。

为什么要用 Redis为什么要用缓存?

  • 高性能

项目中,用户访问从数据库中访问数据,这个过程是从硬盘中读取的。

如果使用Redis缓存数据,那么用户访问数据是从内存中读取。

读取内存数据的速度远远大于读取硬盘数据。

所以可以将一些被高频率读取,并不会被经常修改的数据缓存到Redis中,提高项目性能。

  • 高并发

一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g)。

但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况,Redis 集群的话会更高)。

QPS(Query Per Second):服务器每秒可以执行的查询次数;

由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。

Redis 除了做缓存,还能做什么?

  • 分布式锁

通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。(在Spring 项目集成 Redis 中有实现)

  • 复杂业务场景

通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。

什么是缓存穿透?

当出现大量不合理请求,这些请求的 key 根本不存在于缓存中,也不存在于数据库中 。

这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。

举个例子:某个黑客故意制造一些非法的 key 发起大量请求,导致大量请求落到数据库,结果数据库上也没有查到对应的数据。也就是说这些请求最终都落到了数据库上,对数据库造成了巨大的压力。

  • 有哪些解决办法?

参数校验

最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

缓存无效 key

参数校验只能过滤一些明显不符合规则的数据。

如果参数校验无法解决问题,可以将这些缓存与数据库都查询不到的key缓存到Redis中,兵器设置一个过期时间。

这种方式可以解决 key 变换不频繁的情况。

如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

布隆过滤器

(待了解)

什么是缓存击穿?

缓存击穿中,请求的 key 对应的是热点数据 ,该数据存在于数据库中,但不存在于缓存中,通常是因为缓存中的那份数据已经过期 。

这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。

  • 有哪些解决办法?

设置过期时间

设置热点数据永不过期或者过期时间比较长。

数据预热

针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。

互斥锁

请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。

什么是缓存雪崩?

缓存在同一时间大量失效(过期),导致大量的请求直接打到了数据库上,对数据库造成了巨大的压力,甚至直接宕机。

  • 有哪些解决办法?

针对 Redis 服务不可用的情况

采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。

限流,避免同时处理大量的请求。

针对热点缓存失效的情况

设置不同的失效时间比如随机设置缓存的失效时间。

缓存永不失效(不太推荐,实用性太差)。

设置二级缓存。

缓存穿透和缓存击穿有什么区别?

缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。

缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)

Redis和MySQL如何保证数据一致性

问题出现在数据库数据被修时,Redis需要同步更新。

在这种情况下,可以选择的方法有两种:

  1. 先更新数据库,再更新缓存数据

  2. 先更新缓存数据,再更新数据库

因为这两个操作不是原子性的,所以会存在并发问题和数据有一个操作更新失败的问题导致数据不一致。

解决前先思考需要达到那种一致性

  • 数据最终一致性

数据最终一致,中途可能会出现短时间的脏读,更新失败等问题,但在一段时候后最终保持一致。

一种实现最终一致性的方法为,先更新数据库,再更新缓存,这样只会存在更新缓存失败导致的数据最终不一致。

那么缓存更新失败后,可以使用MQ消息队列对其进行重试,这样来确保缓存更新成功。

  • 实时强一致性

我们需要对两个操作加锁,这样第一个人操作时,其他想操作的需要等待,操作过程中一方失败,整个回滚,这样来保证强一致性,同时,消耗的性能也更多。

Redis缓存热点分离

数据更新时,会在更新方法中更新或者删除Redis中的缓存。

然后在查询方法中,去Redis缓存找数据,没有找到,会查询DB,然后从新缓存Redis。

如果项目是高并发,需要修改代码,节省Redis的性能。

具体实现就是做热点分离,热点数据是一直会被访问的,而冷门数据可能访问次数有限。

那么在缓存Redis的时候设置过期时间,比如24小时,这样冷门数据就会慢慢过期,不会占用Redis空间。

热门数据会一直被访问,为了防止它过期,可以在查询方法中,在Redis缓存找到数据的这个判断中,通过redis 的expire方法设置延期,让这些热门数据一直不过期。

减少热门数据重新缓存消耗与大量热门数据同时出现过期出现缓存雪崩的问题。