应用问题解决

1 缓存穿透

1.1 访问结构

正常情况下,服务器接收到浏览器发来的web服务请求,会先去访问redis缓存,如果缓存中存在数据则直接返回,否则会去查询数据库里面的数据,然后保存在redis中再进行返回。

1.2 缓存击穿的现象
  • 出现大量web请求,应用服务器压力变大

  • redis命中率降低:redis中频繁查不到数据

  • 应用服务器一直在查询数据库

1.3 缓存击穿出现的原因

网站被恶意攻击,主要表现为:

  • redis查询不到数据库

  • 出现大量非正常url访问

查询不到数据并不是真的想要获取到数据,而是希望借此增大redis内存压力进而致使服务器瘫痪。

1.4 缓存击穿解决方案

一个不存在于缓存并且注定也不存在于数据库的数据,由于缓存是不命中的时候被动写的,并且处于容错的考虑,当存储层查不到数据就不会写到缓存里面,这将导致每次在缓存中查不到数据就去存储层查询,失去了缓存的意义。

  • 对空值也进行缓存:如果查询到一个数据的结果为空,那么我们也对这个空值进行缓存,不管这个数据是不是存在,设置空结果的过期时间都会很短,最多不超过五分钟

  • 设置可以访问的名单(白名单):只允许指定的id进行访问,使用redis 的 bitmaps,将id作为偏移量,然后每次访问都需要和bitmaps中的id进行比较,如果访问的id不在bitmaps中,则不允许访问。

  • 布隆过滤器:它底层实现实际上类似于bitmaps,用一个很长的二进制量(位图)和一系列随机哈希函数。布隆过滤器可以检测一个元素是否存在于一个集合中,优点是空间效率和查询时间,缺点是由一定的误识别率和删除困难。

    将所有可能存在的数据哈希到一个足够大的bitmaps里面,一个一定不会存在的数据会被这个bitmaps拦截掉,从而避免了底层系统的查询压力。

  • 进行实时监控:当发现Redis的命中率开始降低,排查访问对象和查询的数据,和运维人员配合,进行设置黑名单拦截。

2 缓存击穿

2.1 缓存穿透的现象
  • 数据库访问压力瞬间增大

  • redis并没有出现大量key过期(大量key过期为缓存雪崩)

  • redis正常运行

2.2 缓存击穿造成的原因

redis中的某个key过期的时候,突然出现了大量对于该key的web服务请求,导致只能去访问数据库,而造成的数据库压力瞬间增大。

2.3 缓存击穿解决方案

可以在某个时间点被超高并发访问的问题,被称作热点数据问题,解决方案主要有:

  • 预先设置热门数据:在redis访问高峰之前,就提前把一些热门数据放入redis中并增大key的时长

  • 实时调整:现场监控热门数据,实时调整key的时长

  • 使用锁:即在查询失败的时候设置一个排它锁,并开启一个线程查询数据库并同步缓存,查询过程中不允许其他线程查询数据库,查询成功后则删除排它锁。

3 缓存雪崩

3.1 缓存雪崩的现象
  • key对应的数据在数据库中,但是在redis中短时间大量key过期

  • 数据库崩溃

3.2 缓存雪崩造成的原因

key对应的数据在数据库中,但是在redis中短时间大量key过期,导致大量请求请求key的时候就会去直接从后端DB加载数据并回设到缓存,这时候大并发的请求可能会瞬间把后端DB压垮。

缓存击穿和缓存雪崩的区别在于是否出现大量key过期

3.3 解决方案
  • 构建多级缓存:nginx 缓存 + redis 缓存 + 其他缓存 (ehcahe等)

  • 使用锁或者队列:使用锁或者队列能够避免有大量的线程对数据库一次性读写,从而避免失效时大量的并发请求落在底层存储系统上,不适用于高并发地情况。

  • 设置过期标志更新缓存:记录缓存是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际的key缓存。

  • 将缓存过期时间分散开

4 分布式锁

4.1 简介

随着业务发展的需要,原来单体单机部署的系统被演化成为了分布式集群系统,由于分布式系统多线程、多进程,并且分布在不同的系统上,这使得原来单机部署情况下的并发锁策略失效,单纯的JavaAPI并不能提供分布式锁的能力,为了解决这个问题就需要一个跨JVM的互斥机制来控制共享资源的访问。

4.2 分布式锁的主流解决方案
  • 基于数据库实现分布式锁

  • 基于redis

  • 基于zookeeper

4.3 设置锁和过期时间
setnx 对key值添加锁

添加锁之后其他进程不能够进行修改

del 释放锁
expire 设置锁的过期时间

上面存在的问题是锁一直没有释放则导致数据一直无法访问,解决方案是对锁设置过期时间,时间到了会自动释放

set nx ex 同时进行上锁和过期时间设置

用于实现上面两条命令的原子操作

4.4 UUID防止误删
分布式锁的代码实现
@Autowired
StringRedisTemplate redisTemplate; @GetMapping("/test")
public String testHandle() {
// 上锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111", 3, TimeUnit.SECONDS);
if(lock) {
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty(value)) {
return "success";
}
int num = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num", String.valueOf(++num));
// 释放锁
redisTemplate.delete("lock"); } else {
// 获取锁失败,3s后再次尝试
try {
Thread.sleep(3000);
testHandle();
} catch (InterruptedException e){
e.printStackTrace();
}
}
return "success";
}
ab压力测试
[root@hadoop100 ~]# ab -n 1000 -c 100 http://192.168.1.108:8080/test
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.1.108 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests Server Software:
Server Hostname: 192.168.1.108
Server Port: 8080 Document Path: /test
Document Length: 7 bytes Concurrency Level: 100
Time taken for tests: 199.311 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 139000 bytes
HTML transferred: 7000 bytes
Requests per second: 5.02 [#/sec] (mean)
Time per request: 19931.077 [ms] (mean)
Time per request: 199.311 [ms] (mean, across all concurrent requests)
Transfer rate: 0.68 [Kbytes/sec] received Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 3 6.4 1 36
Processing: 1 8360 30440.5 2 198574
Waiting: 1 8360 30440.5 2 198574
Total: 2 8363 30445.3 3 198580 Percentage of the requests served within a certain time (ms)
50% 3
66% 5
75% 9
80% 12
90% 3078
95% 72293
98% 141421
99% 171505
100% 198580 (longest request)
存在的问题:可能导致锁的误删

比如上面我们设置了锁的自动过期时间为3s,A上锁执行操作的过程中出现了服务器卡顿导致操作暂停超过了三秒,锁被自动释放了,这时候B抢到了锁,然后上锁进行一系列操作,而此时服务器正常执行了,A又执行了没有执行的释放锁操作,导致实际上是删除了B的锁,解决的方法是每次上锁的时候都给锁设置UUID,然后释放锁的时候检查是不是之前自己上的锁,防止误删。

@Autowired
StringRedisTemplate redisTemplate; @GetMapping("/test")
public String testHandle() {
String uuid = UUID.randomUUID().toString();
// 上锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
if(lock) {
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty(value)) {
return "success";
}
int num = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num", String.valueOf(++num));
// 防止误删
if(uuid.equals(redisTemplate.opsForValue().get("lock"))) {
// 释放锁
redisTemplate.delete("lock");
}
} else {
// 获取锁失败,3s后再次尝试
try {
Thread.sleep(3000);
testHandle();
} catch (InterruptedException e){
e.printStackTrace();
}
}
return "success";
}
4.5 LUA保证原子性

上面使用uuid之后,仍然会存在误删的风险:线程A在判断uuid相同后,准备删除lock,这时候锁到期自动释放了,B抢到了锁然后设置了uuid,最后A删掉了B的锁。 这其实就是因为uuid判断与删除锁的操作不是原子性造成的,解决方案是使用lua脚本,保证在没有删除完成之前,别人是不能进行操作的。

4.6 分布式锁可用需要满足的条件
  • 互斥性

  • 不会发生死锁:即使有客户端在持有锁期间没有主动解锁,其他客户端也能正常获取锁

  • 不能误删其他客户端的锁

  • 加锁和解锁必须具有原子性

Redis(七)缓存穿透、缓存击穿、缓存雪崩以及分布式锁的更多相关文章

  1. 【干货!!】三句话搞懂 Redis 缓存穿透、击穿、雪崩

    前言 如何有效的理解并且区分 Reids 穿透.击穿和雪崩之间的区别,一直以来都挺困扰我的.特别是穿透和击穿,过一段时间就稀里糊涂的分不清了. 为了有效的帮助笔者自己,以及拥有同样烦恼的朋友们区分这三 ...

  2. redis的缓存穿透、击穿、雪崩以及实用解决方案

    今天来聊聊redis的缓存穿透.击穿.雪崩以及解决方案,其中解决方案包括类似于布隆过滤器这种网上一搜一大片但是实际生产部署有一定复杂度的,也有基于spring注解通过一行代码就能解决的,其中各有优劣, ...

  3. Redis高级应用解析:缓存穿透、击穿、雪崩

    1 背景 像我们去面试一些大公司的时候,就会遇到一些关于缓存的问题.可能很多同学都是接触过,多多少少了解一些,但是如果没有好好记录这些内容,不熟练精通的话,在真正面试的时候,就很难答出来了. 在我们的 ...

  4. redis 缓存穿透、击穿、雪崩

    缓存穿透: 大量查询 redis 中不存在的key(用随救数进行查询),导致每次都会去查询数据库,造成数据库压力过大(甚至宕机). 解决办法: 1.对我们的 api 接口 进行限流处理.用户授权.黑名 ...

  5. Redis缓存穿透、击穿、雪崩,数据库与缓存一致性

    Redis作为高性能非关系型(NoSQL)的键值对数据库,受到了广大用户的喜爱和使用,大家在项目中都用到了Redis来做数据缓存,但有些问题我们在使用中不得不考虑,其中典型的问题就是:缓存穿透.缓存雪 ...

  6. 穿透、击穿、雪崩…Redis这么多问题,如何解决?

    摘要:什么是缓存穿透?什么是缓存击穿,又什么是缓存雪崩呢?它们是如何造成的?又该如何解决呢?今天,我们就一起来探讨这些问题. 本文分享自华为云社区<[高并发]什么是缓存穿透?击穿?雪崩?如何解决 ...

  7. Redsi缓存问题(穿透,击穿,雪崩)以及解决办法(分布式锁)【高并发问题】

    Redsi常见问题 缓存在高平发和安全压力下的一些问题 缓存击穿 是某一个热点key在高并发访问的情况下,突然失效,导致大量的并发大金mysql数据库的情况 缓存穿透 是利用redis和mysql的机 ...

  8. 【Redis场景3】缓存穿透、击穿问题

    场景问题及原因 缓存穿透: 原因:客户端请求的数据在缓存和数据库中不存在,这样缓存永远不会生效,请求全部打入数据库,造成数据库连接异常. 解决思路: 缓存空对象 对于不存在的数据也在Redis建立缓存 ...

  9. 【Redis的那些事 · 上篇】Redis的介绍、五种数据结构演示和分布式锁

    Redis是什么 Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务. redis属于nosql非关系型数据库.Nosql常见的数据关系,基本上是以key ...

  10. NoSQL & Redis 介绍、缓存穿透 & 击穿 & 雪崩

    1. NoSql 简介 2. Redis 简介 2.1 Redis 的起源 2.2 缓存过期 & 缓存淘汰 3. 缓存异常 1)缓存穿透 2)缓存击穿 3)缓存雪崩 4)总结 1. NoSQL ...

随机推荐

  1. Idea项目构建时解决方法

    java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: GC overhead limit exceeded 整 ...

  2. 解决Pycharm不能识别selenium的部分提示代码

    这是解决前,pycharm没有提示相关的webelement的代码,例如:send_keys, click 之类的 把鼠标指针放在 selenium 这个单词上就能看到这一串路径 然后找到这个路径的文 ...

  3. Vue+SSM+Element-Ui实现前后端分离(2)

    前言:后台使用ssm搭建,对以前学习知识的一个回顾,与此同时来发现自己不足.这里主要采用配置文件方式进行,有部分注解. 目标:搭建ssm框架,并测试成功:(其中也有aop切面的编写) 一.开发工具 I ...

  4. RabbitMq安装配置启动

    RabbitMq安装配置启动 一:安装材料 请前往官方地址下载 Erlang: https://www.erlang.org/downloads rabbitmq: https://www.rabbi ...

  5. Error java 错误 不支持发行版本5 ( 完美解决版)

    问题 在Intellij idea中新建了一个Maven项目,运行时报错如下:Error : java 不支持发行版本5 解决方案 1. 原因 是因为ideal中默认配置中有几个地方的jdk版本与实际 ...

  6. c/s winForm框架 tabpage标签切换窗体

    /// <summary> /// 根据窗体Name打开窗体 /// </summary> /// <param name="name">< ...

  7. js 小数和百分数的转换

    百分数转化为小数 function toPoint(percent){ var str=percent.replace("%",""); str= str/10 ...

  8. JS中报错处理 try catch finally的使用

    JS中标准报错处理通过 try catch finally ,使用格式 try { } catch (err) { } finally { } 代码1: try { console.log('顺序 1 ...

  9. 惰性载入(函数)-跟JS性能有关的思想

    一.惰性载入概念: 惰性.懒惰.其实跟懒惰没有关系,就是图省事,把没意义的事少做.不做. 惰性载入函数:函数执行时会根据不同的判断分支最终选择合适的方案执行,但这样的分支判断仅会发生一次,后面的其他同 ...

  10. 初学-javaFX

    使用javaFX做一个简单的音乐播放器 主要功能 1:加载歌曲列表 2:加载歌曲对应歌词 3:歌曲播放进度显示 4:歌词滚动 5:播放  暂停  上一首 下一首 界面如下 组件说明: 1:页面布局 容 ...