文章目录
-
- 项目中是如何使用缓存的
- redis效率为什么这么高
- [Redis 6.x之后有哪些新特性](#Redis 6.x之后有哪些新特性)
- 缓存穿透
- 缓存雪崩
- 缓存击穿
项目中是如何使用缓存的
redis效率为什么这么高
Redis 内部使用文件事件处理器 file event handler
,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。
多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
除了利用IO多路复用模型外,redis还有以下几个方面可以提高效率:
- 纯内存操作:Redis 为了达到最快的读写速度,将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 Redis 具有快速和数据持久化的特征。
- 单线程 ,避免了多线程的频繁上下文切换问题
Redis 利用队列技术,将并发访问变为串行访问,消除了传统数据库串行控制的开销。
实际上,Redis 4.0 开始,也开始有了一些异步线程,用于处理一些耗时操作。例如说,异步线程,实现惰性删除(解决大 KEY 删除,阻塞主线程)和异步 AOF (解决磁盘 IO 紧张时,fsync 执行一次很慢)等等。 - 丰富的数据结构
例如哈希表和跳表,这是它实现高性能的一个重要原因
Redis 6.x之后有哪些新特性
多线程
Redis 一直被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF 重写),但是,从网络 IO 处理到实际的读写命令处理,都是由单个线程完成的。
随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,也就是说,
单个主线程处理网络请求的速度跟不上底层网络硬件的速度。
为了解决这个问题,Redis采用多个 IO 线程来处理网络请求,提高网络请求处理的并行度。
但Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线
程来处理。这是因为,Redis 处理请求时,网络处理经常是瓶颈,通过多个 IO 线程并行处
理网络操作,可以提升实例的整体处理性能。而继续使用单线程执行命令操作,就不用为
了保证 Lua 脚本、事务的原子性,额外开发多线程互斥机制了。这样一来,Redis 线程模
型实现就简单了。
在 Redis 6.0 中,主线程和 IO 线程具体是怎么协作完成请求处理的?
阶段一:服务端和客户端建立 Socket 连接,并分配处理线程
首先,主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程
会创建和客户端的连接,并把 Socket 放入全局等待队列中。紧接着,主线程通过轮询方法
把 Socket 连接分配给 IO 线程。
阶段二:IO 线程读取并解析请求
主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求
读取和解析。因为有多个 IO 线程在并行处理,所以,这个过程很快就可以完成。
阶段三:主线程执行请求操作
等到 IO 线程解析完请求,主线程还是会以单线程的方式执行这些命令操作。下面这张图显
示了刚才介绍的这三个阶段
阶段四:IO 线程回写 Socket 和主线程清空全局队列
当主线程执行完请求操作后,会把需要返回的结果写入缓冲区,然后,主线程会阻塞等待
IO 线程把这些结果回写到 Socket 中,并返回给客户端。
和 IO 线程读取和解析请求一样,IO 线程回写 Socket 时,也是有多个线程在并发执行,
所以回写 Socket 的速度也很快。等到 IO 线程回写 Socket 完毕,主线程会清空全局队
列,等待客户端的后续请求。
其他新增特性总结如下:
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,造成数据库的压力倍增的情况
例:发起为id值为 -1 的数据或 id 为特别大不存在的数据
解决方案:
- 接口层增加校验,比如用户鉴权校验,参数做校验 比如:id 做基础校验,id <=0的直接拦截
- 对于像ID为负数的非法请求直接过滤掉,采用布隆过滤器(Bloom Filter)
- 针对在数据库中找不到记录的,我们仍然将该空数据存入缓存中,当然一般会设置一个较短的过期时间
缓存雪崩
缓存服务器宕机或者大量缓存集中某个时间段失效,导致请求全部去到数据库,造成数据库压力倍增的情况,这个是针对多个key而言
造成缓存雪崩的原因主要有两个
- 缓存中有大量数据同时过期,导致大量请求无法得到处理。
针对这个原因,可以有两个解决方案:
- 避免给大量的数据设置相同的过期时间,给这些数据的过期时间增加一个较小的随机数(例如,随机增加 1~3 分钟)
- 通过服务降级来解决:当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;
当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存
缺失,也可以继续通过数据库读取。这样一来,就只有部分请求到数据库中,数据库压力就没那么大了。
- 缓存雪崩的另外一个原因是,Redis缓存实例发生故障宕机了,无法处理请求,这就会导致大量请求一下子积压到数据库层,从而发生缓存雪崩。
此时有下面解决方法:
- 业务系统中实现服务熔断或请求限流机制:所谓的服务熔断,是指在发生缓存雪崩时,为了防止引发连锁的数据库雪崩,甚至是整个系统的崩溃,我们暂停业务应用对缓存系统的接口访问。再具体点说,就是业务应用调用缓存接口时,缓存客户端并不把请求发给 Redis 缓存实例,而是直接返回,等到 Redis 缓存实例重新恢复服务后,再允许应用请求发送到缓存系统
- 服务熔断虽然可以保证数据库的正常运行,但是暂停了整个缓存系统的访问,对业务应用的影响范围大。为了尽可能减少这种影响,我们也可以进行请求限流。这里说的请求限流,就是指,我们在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。
- 过主从节点的方式构建 Redis 缓存高可靠集群。如果 Redis 缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。
缓存击穿
redis过期后的一瞬间,有大量用户请求同一个缓存数据,导致这些请求都去请求数据库,造成数据库压力倍增的情,针对一个key而言
缓存击穿与缓存雪崩的区别是这里针对的是某一热门key缓存,而雪崩针对的是大量缓存集中失效
解决方案
- 设置热点数据永远不过期。
- 用互斥锁(mutex key)