Redis为什么快,以及雪崩、穿透的解决方案

本文深入解析Redis的高速运作原理,包括纯内存操作、单线程机制及非阻塞I/O多路复用,探讨其数据类型、事务机制,并提供缓存雪崩、击穿与穿透的解决方案。

一、什么是Redis 

Redis 是开源免费的,遵守BSD协议,是一个高性能的key-value非关系型数据库.

二、Redis为什么快

1、纯内存操作

2、单线程操作:避免了上下文的切换,也就不存在线程之间的资源竞争。虽然大多数理论认为CPU不是瓶颈,网络与带宽才是。但是实际的测试中结果并不是如此。

3、采用了非阻塞I/O多路复用机制:多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了事件,不在I/O上浪费过多的时间。

关于多路复用机制的案例解释:假如有一家快递店,负责同城快送服务。因为资金限制,雇佣了一批快递员,发现资金不够了,只够买一辆车送快递。

经营方式一

客户每送来一份快递,小曲就让一个快递员盯着,然后快递员开车去送快递。慢慢的小曲就发现了这种经营方式存在下述问题:

  • 时间都花在了抢车上了,大部分快递员都处在闲置状态,抢到车才能去送快递。

  • 随着快递的增多,快递员也越来越多,小曲发现快递店里越来越挤,没办法雇佣新的快递员了。

  • 快递员之间的协调很花时间。

    经营方式二

    小曲只雇佣一个快递员。当客户送来快递,小曲按送达地点标注好,依次放在一个地方。最后,让快递员依次去取快递,一次拿一个,再开着车去送快递,送好了就回来拿下一个快递。上述两种经营方式对比,很明显第二种效率更高。

     

    在上述比喻中:

  • 每个快递员→每个线程

  • 每个快递→每个 Socket(I/O 流)

  • 快递的送达地点→Socket 的不同状态

  • 客户送快递请求→来自客户端的请求

  • 小曲的经营方式→服务端运行的代码

  • 一辆车→CPU 的核数

于是有了如下结论:

  • 经营方式一就是传统的并发模型,每个 I/O 流(快递)都有一个新的线程(快递员)管理。

  • 经营方式二就是 I/O 多路复用。只有单个线程(一个快递员),通过跟踪每个 I/O 流的状态(每个快递的送达地点),来管理多个 I/O 流。

三、Redis可以存储的数据类型

string,List,Hash,Set,Sorted Set

四、Redis事务

redis中的事务与传统关系型数据库(如mysql)的事务是不同的

redis中的事务是一组命令的集合,事务与命令都是最小执行单位,原理是先将属于一个事务的命令发送给Redis,然后Redis一次执行这些命令。

redis的事务可以保证一个事务内的命令一次执行而不被其他命令插入影响。

如果事务块中某一条命令出错,关系型数据库的事务会执行回滚,而redis不会执行回滚,而是会继续执行后续的命令。因为redis的事务没有关系型数据库的回滚(rollback)功能。因此需要开发者在事务执行出错时自己处理。

redis事务(Transaction)命令
1.watch
用于监视一个或多个key,如果在事务执行之前这个或(这些)key被其他命令所改动,事务将被中断。

2.unwatch
用于取消watch命令对所有key的监视。

3.multi
用于标记一个事务块的开始,之后的所有命令都存放在队列,等遇到exec命令再执行。

4.exec
用于执行事务块内所有的命令,如果命令被中断,返回false

五、Redis缓存雪崩,击穿与穿透以及解决方案

        1、穿透

   缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。解决办法有两个:

      1)查询数据库不存在,则在redis中存一份Null值,避免访问Mysql

       2)采用布隆过滤器,将List数据装载入布隆过滤器中,访问经过布隆过滤器,存在才可以往db中查询。于是在内存中就可以拦截恶意请求。

2、雪崩

雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升,从而崩溃。

出现原因: 1 key同时失效

                 2 redis本身崩溃了

方案:

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(跟击穿的第一个方案类似,但是这样是避免不了其它key去查数据库,只能减少查询的次数)
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
不同的key,设置不同的过期时间,具体值可以根据业务决定,让缓存失效的时间点尽量均匀
做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。(这种方式复杂点)

击穿: 指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。

   如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃

注意: 这里指的是单个key发生高并发!!!

解决方案:  1) 通过synchronized+双重检查机制:某个key只让一个线程查询,阻塞其它线程
缺点: 会阻塞其它线程

击穿: 指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。

   如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃

注意: 这里指的是单个key发生高并发

解决方案:1) 通过synchronized+双重检查机制:某个key只让一个线程查询,阻塞其它线程

缺点: 会阻塞其它线程

2) 设置value永不过期

这种方式可以说是最可靠的,最安全的但是占空间,内存消耗大,并且不能保持数据最新 这个需要根据具体的业务逻辑来做 

     个人觉得如果要保持数据最新不放这么试试,仅供参考:

      起个定时任务或者利用TimerTask 做定时,每个一段时间多这些值进行数据库查询更新一次缓存,当然前提时不会给数据库造成压力过大(这个很重要)
3) 使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
 

### Redis 缓存击穿、雪崩穿透的概念及解决方案 #### 1. 缓存穿透 缓存穿透是指查询一个既不存在于数据库中,也不存在于缓存中的数据。这种情况下,由于缓存没有命中,请求会直接到达数据库,导致数据库压力增大[^1]。 - **原因**:某些恶意攻击者故意查询不存在的数据,或者系统中存在大量无效的缓存键。 - **解决方案**: - 使用布隆过滤器(Bloom Filter)提前判断数据是否存在数据库中。如果布隆过滤器返回不存在,则直接返回空结果,而不查询数据库[^2]。 - 对于不存在的数据,在缓存中设置一个空值,并赋予较短的过期时间,防止后续重复查询。 ```python # 示例代码:使用布隆过滤器避免缓存穿透 from pybloom_live import BloomFilter bloom_filter = BloomFilter(capacity=100000, error_rate=0.001) def get_data(key): if key not in bloom_filter: return None # 数据不存在,直接返回 data = cache.get(key) if data is None: data = db.query(key) # 查询数据库 if data is None: cache.set(key, "NULL", ex=60) # 缓存空值 else: cache.set(key, data) return data ``` #### 2. 缓存击穿 缓存击穿是指某个热点数据在缓存中过期时,恰好有大量请求同时访问该数据,导致这些请求都直接查询数据库,造成数据库压力骤增[^2]。 - **原因**:热点数据的缓存过期时间设置不合理,或者多个请求在同一时间点访问过期的缓存。 - **解决方案**: - 设置随机的缓存过期时间,避免所有缓存同时过期。 - 使用互斥锁(Mutex)机制,确保只有一个线程去加载数据到缓存中,其他线程等待加载完成后再从缓存中获取数据。 ```python # 示例代码:使用互斥锁避免缓存击穿 import redis from threading import Lock lock = Lock() cache = redis.StrictRedis() def get_data_with_mutex(key): data = cache.get(key) if data is not None: return data with lock: # 加锁 data = cache.get(key) # 再次检查缓存 if data is None: data = db.query(key) # 查询数据库 if data is not None: cache.set(key, data) return data ``` #### 3. 缓存雪崩 缓存雪崩是指大量缓存在同一时间过期,导致大量请求直接查询数据库,造成数据库压力过大甚至崩溃[^3]。 - **原因**:缓存的有效期设置不合理,导致大量缓存在同一时间失效。 - **解决方案**: - 为不同的缓存设置不同的过期时间,避免集中过期。 - 预热缓存:在系统启动或高峰来临前,主动加载热点数据到缓存中。 - 使用 Redis 主从架构,当主节点故障时,从节点可以切换为主节点继续提供服务,避免因单点故障导致的缓存雪崩。 ```python # 示例代码:预热缓存 def preload_cache(): keys = db.get_hot_keys() # 获取热点数据的键 for key in keys: data = db.query(key) if data is not None: cache.set(key, data, ex=random.randint(90, 120)) # 设置随机过期时间 ``` ### 区别总结 | 问题 | 定义 | 原因 | 解决方案 | |------------|----------------------------------------------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------| | 缓存穿透 | 查询不存在的数据,导致缓存和数据库均未命中 | 恶意攻击或无效查询 | 使用布隆过滤器或缓存空值 | | 缓存击穿 | 热点数据缓存过期时,大量请求同时访问数据库 | 缓存过期时间设置不合理 | 设置随机过期时间或使用互斥锁 | | 缓存雪崩 | 大量缓存同时过期,导致请求直接查询数据库 | 缓存有效期集中设置 | 设置不同过期时间、预热缓存、使用 Redis 主从架构 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值