当前位置:网站首页>Redis - 12、应用问题解决
Redis - 12、应用问题解决
2022-06-22 11:28:00 【Q.E.D.】
1、缓存穿透
1.1、问题描述
当系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回,缓存中没有就去db中查询,db中如果有就会将其丢到缓存中,但是有些key对应更多数据在db中并不存在,每次针对此次key的请求从缓存中取不到,请求都会压到db,从而可能压垮db。
比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用大量此类攻击可能压垮数据库。
1.2、解决方案
1.2.1、对空值缓存
如果一个查询返回的数据为空(不管数据库是否存在),我们仍然把这个结果(null)进行缓存,给其设置一个很短的过期时间,最长不超过五分钟
1.2.2、设置可访问的名单(白名单)
使用redis中的bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次范文和bitmap里面的id进行比较,如果访问的id不在bitmaps里面,则进行拦截,不允许访问
1.2.3、采用布隆过滤器
布隆过滤器(Bloom Filter)是1970年有布隆提出的,它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检测一个元素是否在一个集合中,它的优点是空间效率和查询的世界都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
1.2.4、进行实时监控
当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制对其提供服务(比如:IP黑名单)
2、缓存击穿
2.1、问题描述
redis中某个热点key(访问量很高的key)过期,此时大量请求同时过来,发现缓存中没有命中,这些请求都打到db上了,导致db压力瞬时大增,可能会打垮db,这种情况成为缓存击穿。

缓存击穿出现的现象
- 数据库访问压力瞬时增大
- redis里面没有出现大量的key过期
- redis正常运行
2.2、解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据,这个时候,要考虑一个问题:缓存被“击穿”的问题,常见的解决方案如下
2.2.1、预先设置热门数据,适时调整过期时间
在redis高峰之前,把一些热门数据提前存入到redis里面,对缓存中的这些热门数据进行监控,实时调整过期时间。
2.2.2、使用锁
缓存中拿不到数据的时候,此时不是立即去db中查询,而是去获取分布式锁(比如redis中的setnx),拿到锁再去db中load数据;没有拿到锁的线程休眠一段时间再重试整个获取数据的方法。
3、缓存雪崩
3.1、问题描述
key对应的数据存在,但是极短时间内有大量的key集中过期,此时若有大量的并发请求过来,发现缓存没有数据,大量的请求就会落到db上去加载数据,会将db击垮,导致服务奔溃。
缓存雪崩与缓存击穿的区别在于:前者是大量的key集中过期,而后者是某个热点key过期。
3.2、解决方案
缓存失效时的雪崩效益对底层系统的冲击非常可怕,常见的解决方案如下
3.2.1、构建多级缓存
nginx缓存+redis缓存+其他缓存(ehcache等)
3.2.2、使用锁或队列
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况。
3.2.3、监控缓存过期,提前更新
监控缓存,发下缓存快过期了,提前对缓存进行更新。
3.2.4、将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样缓存的过期时间重复率就会降低,就很难引发集体失效的事件。
4、分布式锁
4.1、问题描述
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
4.2、分布式锁主流的实现方案
- 基于数据库实现分布式锁
- 基于缓存(redis等)
- 基于zookeeper
每一种分布式锁解决方案都有各自的优缺点
- 性能:redis最高
- 可靠性:zookeeper最高
这里我们就基于redis实现分布式锁。
4.3、解决方案:使用redis实现分布式锁
需要使用下面这个命令来实现分布式锁
set key value NX PX 有效期(毫秒)
这条命令表示:当key不存在的时候,设置其值为value,且同时设置其有效期
示例
set sku:1:info "ok" NX PX 10000表示当 sku:1:info 不存在的时候,设置值为ok,且有效期为1万毫秒.
4.3.1、上锁的过程
过程如下图,执行 set key value NX PX 有效期(毫秒) 命令,返回ok表示执行成功,则获取锁成功,多个客户端并发执行此命令的时候,redis可确保只有一个可以执行成功。

4.3.2、为什么要设置过期时间?
客户端获取锁后,由于系统问题,如系统宕机了,会导致锁无法释放,其他客户端就无法或锁了,所以需要给锁指定一个使用期限。
4.3.3、如果设置的有效期太短怎么办?
比如有效期设置了10秒,但是10秒不够业务方使用,这种情况客户端需要实现续命的功能,可以解决这个问题。
4.3.4、解决锁误删的问题
锁存在误删的情况:所谓误删就是自己把别人持有的锁给删掉了。
比如线程A获取锁的时候,设置的有效期是10秒,但是执行业务的时候,A程序突然卡主了超过了10秒,此时这个锁就可能被其他线程拿到,比如被线程B拿到了,然后A从卡顿中恢复了,继续执行业务,业务执行完毕之后,去执行了释放锁的操作,此时A会执行del命令,此时就出现了锁的误删,导致的结果就是把B持有的锁给释放了,然后其他线程又会获取这个锁,挺严重的。
如何解决呢?
获取锁的之前,生成一个全局唯一id,将这个id也丢到key对应的value中,释放锁之前,从redis中将这个id拿出来和本地的比较一下,看看是不是自己的id,如果是的再执行del释放锁的操作。
4.3.5、还是存在误删的可能(原子操作问题)
刚才上面说了,del之前,会先从redis中读取id,然后和本地id对比一下,如果一致,则执行删除,伪代码如下
step1:判断 redis.get("key").id==本地id 是否相当,如果是则执行step2
step2:del key;
此时如果执行step2的时候系统卡主了,比如卡主了10秒,然后redis才收到,这个期间锁可能又被其他线程获取了,此时又发生了误删的操作。
这个问题的根本原因是:判断和删除这2个步骤对redis来说不是原子操作导致的,怎么解决呢?
需要使用Lua脚本来解决。
4.3.6、终极方案:Lua脚本来释放锁
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。
Lua脚本类似于redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务的操作。
但是注意redis的LUA脚本功能,只能在redis2.6以上版本才能使用。
代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @className LockTest
* @date 2022/6/21
**/
@RestController
public class LockTest {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@RequestMapping(value = "/lock", produces = MediaType.TEXT_PLAIN_VALUE)
public String lock() {
String lockKey = "k1";
String uuid = UUID.randomUUID().toString();
// 1.获取锁,有效期10秒
if (this.redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS)) {
// 2.执行业务
// todo 业务
//3.使用Lua脚本释放锁(可防止误删)
String script = "if redis.call('get',KEYS[1])==ARGV[1] then returnredis.call('del', KEYS[1]) else return 0 end ";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), uuid);
System.out.println(result);
return "获取锁成功!";
} else {
return "加锁失败!";
}
}
}4.3.7、分布式锁总结
为了确保分布式锁可用,我们至少需要确保分布式锁的实现同时满足以下四个条件
- 互斥性,在任意时刻只能有一个客户端能够持有锁
- 不糊发生死锁,即使有一个客户端在持有锁期间崩溃而没有释放锁,也能够保证后续其他客户端能够加锁
- 解锁还需寄铃人,加锁和解锁必须是同一个客户端,客户端不能把别人的锁给解了
- 加锁和解锁必须有原子性
边栏推荐
- Struggle, programmer -- Chapter 36 the falling flower man is independent and the tiny swift flies
- Reader case of IO
- 奋斗吧,程序员——第三十八章 旧时茅店社林边,路转溪头忽见
- Arc128 C convex hull optimized suffix and?
- 奋斗吧,程序员——第四十七章 所谓伊人,在水一方
- “中国巴菲特”段永平:投资有道
- “不敢去怀疑代码,又不得不怀疑代码”记一次网络请求超时分析
- 牛客挑战赛55D题解
- IO之ByteStream案例
- 微信小程序项目实例——图片处理小工具(自制低配版美图秀秀)
猜你喜欢

配置GPU版本的pytorch和torchvision,初学GPU版本torch踩坑

Install pyGame
![两两交换链表中的节点[单向链表不断链原则]](/img/67/8e9f3c396a8f529a616964b69cc47f.png)
两两交换链表中的节点[单向链表不断链原则]

1.11 haas506 2.0 development tutorial driver RTC (only versions above 2.2 are supported)

Electron ajoute une base de données SQLite

SAP Marketing Cloud 功能概述(二)

【软工】获取需求

二叉树的前序、中序、后序遍历的两种实现

Vector data of Zunyi city's benchmark land price in 2022 (WGS84)

TiFlash 函数下推必知必会丨十分钟成为 TiFlash Contributor
随机推荐
Three transition schemes from IPv4 to IPv6
R language uses user-defined functions to write step activation functions for deep learning and visualize step activation functions
GEE——Global Flood Database v1 (2000-2018)
牛客挑战赛54E题解
Puzzle (019) plane forward problem
《梦华录》成吸金王:广告主投500万排不上队,腾讯视频赢麻了?
IO之Reader案例
APM set pitch four rotor control mode
Struggle, programmer -- Chapter 36 the falling flower man is independent and the tiny swift flies
奋斗吧,程序员——第四十七章 所谓伊人,在水一方
Exchange the nodes in the linked list in pairs [the principle of one-way linked list without chain]
迪利克雷前缀和学习笔记
鉴权之cookie、session、JWT
CF751D Difficult Mountain
一般图最大匹配(带花树)模板
Development technology of NFT trading platform digital collection system
配置GPU版本的pytorch和torchvision,初学GPU版本torch踩坑
Successful cases | an Chaoyun helped the second hospital of Lanzhou University build a new IT infrastructure platform to improve the utilization of medical information resources
Niuke challenge 54F problem solution & Li Chaoshu's learning notes
Collection of IO operation cases