Redis学习

一 Redis缓存穿透、击穿、雪崩

1.1 缓存穿透

 缓存穿透:故意去请求缓存中不存在的数据,从而去数据库中查,数据库中也没有,所以无法加到缓存,下次还是直接怼到数据库,所以高并发的时候就导致数据库崩了。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

解决方案:

1)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。

2)数据库没查到数据,也往缓存中写入一个空值,但是设置失效时间短一点,防止恶意攻击。

1.2 缓存击穿

缓存击穿:是指一个key非常热点(类似于爆款),在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案:

1)可以将爆款的缓存失效时间设置为永久。

2)加互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。

1.3 缓存雪崩

缓存雪崩:即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

1)给缓存失效时间,加上一个随机值,避免大量缓存集体失效。

2)双缓存:缓存A和B,比如A的失效时间是20分钟,B不失效。比如从A中没读到,就去B中读,然后异步起一个线程同步到A。

3)也可以使用互斥锁,但是会影响性能,因为这个是业务场景,不是恶意攻击。

二 Redis 锁

2.1 加锁机制

Redis分布式锁,一般用Redisson框架,非常的简便易用。例如:

Rlock lock = redisson.getLock(“myLock”);
lock.lock();
lock.unlock();

此外,Redisson还支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构,都可以完美实现。

执行lock.lock()代码时,如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。紧接着,就会发送一段lua脚本到redis上:

 

脚本中有三个参数:KEYS[1]代表的是加锁的那个key,即上面代码中的“myLock”。

ARGV[1]代表的就是锁key的生存时间,默认30秒。

ARGV[2]代表的是加锁的客户端的ID,比如这样:8743c9c0-0795-4907-87fd-6c719a6b4586:1

脚本的逻辑如下:第一个if:

先判断有没有“myLock”这个锁(即使用exists myLock命令),如果没有就进行加锁。加锁是使用hset myLock命令,即hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1。这行命令执行后,会出现一个类似下面的数据结构:

myLock:
{
	“8743c9c0-0795-4907-87fd-6c719a6b4586:1”:1
}

上述就代表“8743c9c0-0795-4907-87fd-6c719a6b4586:1”这个客户端对“myLock”这个锁key完成了加锁。接着会执行“pexpire myLock 30000”命令,设置myLock这个锁key的生存时间是30秒。好了,到此为止,加锁完成了。

总结一下加锁过程:

首先选择一台机器,然后发送一段lua脚本,带有三个参数:一个是锁的名字(在代码里指定的)、一个是锁的时常(默认30秒)、一个是加锁的客户端id(每个客户端对应一个id)。然后脚本会判断是否有该名字的锁,如果没有就往数据结构中加入该锁的客户端id。

2.2 锁互斥机制

很简单,上面第一个if判断会执行“exists myLock”,如果发现myLock这个锁key已经存在了,就会进行第二个if判断,判断一下myLock锁key的hash数据结构中,是否包含客户端2的ID,但是明显不是的,因为那里包含的是客户端1的ID。

所以,客户端2会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的剩余生存时间。比如还剩15000毫秒的生存时间。

此时客户端2会进入一个while循环,不停的尝试加锁。直到客户端1释放myLock这个锁。

2.3 锁时间自动延迟机制

客户端1加锁的默认时长是30秒,如果超过了30秒,客户端1还想持有这把锁该怎么办呢?机制如下:

只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

2.4 可重入加锁机制

如果客户端1已经持有这把锁了,还想加锁,该怎么办呢?比如代码逻辑如下:

Rlock lock = redisson.getLock(“myLock”);
lock.lock();
// 一坨代码
lock.lock();
// 一坨代码
lock.unlock();
lock.unlock();

这时我们来分析一下上面那段lua脚本。第一个if判断肯定不成立,“exists myLock”会显示锁key已经存在了。第二个if判断会成立,因为myLock的hash数据结构中包含的那个ID,就是客户端1的那个ID,也就是“8743c9c0-0795-4907-87fd-6c719a6b4586:1”

此时就会执行可重入加锁的逻辑,他会用:incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1 通过这个命令,对客户端1的加锁次数,累加1。

此时,myLock的数据结构变成了这样:

myLock:
{
	“8743c9c0-0795-4907-87fd-6c719a6b4586:1”:2
}

可以看出,后面那个数字表示该id的锁的加锁次数。

2.5 锁释放机制

如果执行lock.unlock(),就可以释放分布式锁,此时的业务逻辑也是非常简单的。其实说白了,就是每次都对myLock数据结构中的那个加锁次数减1。如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从redis里删除这个key。然后呢,另外的客户端2就可以尝试完成加锁了。

这就是Redis分布式锁基于开源Redisson框架的实现机制。

2.6 Redis分布式锁的缺点

这种分布式锁最大的缺点在于:如果对某个redis-master实例写入了myLock这个key的锁的value,此时会异步复制数据到redis-slave实例上。

但是在这个过程中发生了redis-master宕机了,主备切换,redis-slave变成了redis-master。

紧接着,客户端2就来尝试加锁,在新的redis-master上加了锁,而此时客户端1也认为自己加了锁,这就导致多个客户端对同一个分布式锁完成了加锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值