Redis的击穿、雪崩、穿透

Redis的击穿、雪崩、穿透

正常使用redis的流程是:收到请求后,会去redis中查找key,查不到再去数据库中找,在数据库中找到之后就返回并放到缓存当中,查不到就抛出异常。但是在这个过程中会出现一些意外情况,比如击穿、雪崩、穿透。

1、缓存击穿

1、击穿是什么?

高并发状态下,当一个非常热点的key,在某一时间突然失效(或者一个冷门的key突然被大量访问,比如双十一交易量暴增,微博上明星的热点事件等等),大量并发穿破缓存访问数据库,会对数据库造成巨大压力,并且大量线程将key放回redis中,也会造成性能浪费。

2、如何解决?

两种方法:设置过期时间和加锁。

1、设置过期时间

比如对日常访问量答的key设置永不过期,冷门key设置一个过期时间。

但是这个方法解决不了冷门key突然被大量访问的问题。

2、加锁

当再redis中查询不到key的时候,加一个同步锁,只有一个线程能访问到。这个线程将key放进redis之后,其他线程被释放。

代码:

import Redis
import MySQL
import threading
# Redis.redis 是一个定义好的python访问r饿dis数据库的类

lock = threading.Lock()

def func(key):
    res1 = Resdis.redis.read(key)
    if res1 == None:
        with lock():    # 加同步锁
            res2 = Resdis.redis.read(key)    # 再次取数据
            if res2 != None:
                return res2
            res3 = MySQL.read(key)   # 去数据库取数据
            if res3 == None:
                return None      # 取不到说明数据库中没有,抛出异常
            Redis.redis.write(key, res3)
            return res3				 # 取到返回数据并放进redis
    else:
        return res1
1、为什么加锁之后要再访问一次redis?

当第一个线程将了key放进redis之后,后面的线程锁随即释放,如果这里不访问redis而是直接访问数据库,依旧会对数据库造成巨大压力,这里再次访问redis可以立即拿到数据,不需要访问数据库。

2、为什么是同步锁?

如果部署了集群,假如是10个缓存,那么对于数据库来说,应对10个请求是没问题的。

分布式锁可以更好,但同步锁足够解决问题。

2、缓存雪崩

1、雪崩是什么?

与击穿不同,雪崩是指数据库中所有数据某一时刻全部过期或者缓存服务器宕机,导致大量请求访问数据库,给数据库造成巨大压力的情况。

2、如何解决?

1、针对所有key同一时间全部失效的情况,可以在解决击穿的基础上,对key设置随机过期时间。

伪代码:

expire = int(random.random() * 1000)    # 生成一个三位数的随机数
Redis.redis.write(key, res3, expire)

2、针对服务器突然宕机或断电的情况,需要提前做好部署,集群哨兵模式或者多机房。

3、缓存穿透

1、什么是穿透?

请求的数据缓存中没有,数据库也没有。比如有攻击者使用很大的id或者负数id发送大量请求的时候,会对数据库造成巨大压力。

2、如何解决?

三种方式,一种是参数校验,一种是缓存空对象,一种是布隆过滤器。

数据校验

收到请求后对参数做个校验,过滤掉无效参数,比如很大的id或者负数id。

但是这种方法的缺点是没办法判断很大的id是恶意攻击还是正常id恰好没有。

缓存空对象

就是在去数据库查询的时候,不管有没有查到数据,都存在redis中,没有查到就存一个空对象,可以一定程度缓解数据库短时间内被大量攻击的情况。

布隆过滤器

布隆过滤器是一种比列表、集合等更高效的数据结构。

思路:在redis之前设置一个过滤器。每次访问前先去过滤器查询,查询不到再去数据库查询,数据库中也没有就将数据放进过滤器,再次访问在过滤器中访问到,返回空值。这个方法被称为黑名单过滤器,即存储数据库中没有的key。

还有一种白名单过滤器,就是将数据库中存在的数据放进过滤器。请求过来,如果过滤器中存在,再进行后续操作,如果过滤器中不存在,直接返回空值。

伪代码:

bloomFiliter = []   

# 黑名单
def getResquest(key):
    if key in bloomFiliter:
        return None
    return func(key)

# 白名单
def getResquest(key):
    if key in bloomFiliter:
        return func(key)
    return None

# 布隆过滤器不是列表,是一种数据结构,这里是用列表表示布隆过滤器解决缓存穿透的思路。

4、一句话总结

把redis比喻为防弹衣,MySQL比喻为身体,穿透就是子弹穿透了防弹衣打在身体上,雪崩就是很多颗子弹透过防弹衣在身体上爆炸了,穿透就是子弹把防弹衣和身体都打穿了。

伪代码
import Redis
import MySQL
import threading
import random
# Redis MySQL 定义好的python访问数据库的类

lock = threading.Lock()   # 线程锁

def findKey(key):
    res1 = Redis.redis.read(key)		# 第一次从redis中查询数据
    if res1 == None:
        with lock():		# 没查询到,加线程锁,防止大量请求访问数据库
            res2 = Redis.read(key)		# 再次查询,防止阻塞的请求大量访问数据库,这两行可以防止缓存击穿问题
            if res2 == None:
                res3 = MySQL.select(key)
                expire = int(random.random() * 1000)
                Redis.write(key, res3, expire)
                #  不管从数据库查询的结果如何,都存入redis,并设置过期时间,可以防止缓存雪崩问题
                return res3
            else:
                return res2
    else:
    	return res1		# 查询到,直接将数据返回
    
            

bloomFilter = []		#此处以黑名单为例
def bloomFilterGetID(key):
    if key in bloomFilter:
        return None		# id 存在于黑名单,直接返回,可以防止缓存穿透问题
    return findKey(key)
    

你可能感兴趣的:(redis,数据库,缓存)