Redis应用问题解决

news2024/9/22 7:02:01

16. Redis应用问题解决

16.1 缓存穿透

16.1.1 问题描述

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

 16.1.2 解决方案

一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决方案:

对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

设置可访问的名单(白名单):

使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)

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

进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

16.2 缓存击穿

16.2.1 问题描述

 16.2.2 解决方案

key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

解决问题:

(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长

(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长

(3)使用锁:

  1. 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
  2. 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
  3. 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
  4. 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

16.3 缓存雪崩 

16.3.1 问题描述

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key正常访问

 缓存失效瞬间

16.3.2 解决方案 

缓存失效时的雪崩效应对底层系统的冲击非常可怕!

解决方案:

构建多级缓存架构:

nginx缓存 + redis缓存 +其他缓存(ehcache等)

使用锁或队列

用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

设置过期标志更新缓存:

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

将缓存失效时间分散开:

比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

16.4 分布式锁

16.4.1 问题描述

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

分布式锁主流的实现方案:

1. 基于数据库实现分布式锁

2. 基于缓存(Redis等)

3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

1. 性能:redis最高

2. 可靠性:zookeeper最高

这里,我们就基于redis实现分布式锁。

16.4.2 解决方案:使用redis实现分布式锁

redis:命令

# set sku:1:info “OK” NX PX 10000

EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。

PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。

NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。

XX :只在键已经存在时,才对键进行设置操作。

 

1. 多个客户端同时获取锁(setnx)

2. 获取成功,执行业务逻辑{从db获取数据,放入缓存},执行完成释放锁(del)

3. 其他客户端等待重试

16.4.3 编写代码

Redis: set num 0

@GetMapping("testLock")
public void testLock(){
    //1获取锁,setne
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
    //2获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(StringUtils.isEmpty(value)){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        redisTemplate.delete("lock");

    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

重启,服务集群,通过网关压力测试:

ab -n 1000 -c 100 http://192.168.140.1:8080/test/testLock

查看redis中num的值:

基本实现。

问题:setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放

解决:设置过期时间,自动释放锁。

16.4.4 优化之设置锁的过期时间

设置过期时间有两种方式:

1. 首先想到通过expire设置过期时间(缺乏原子性:如果在setnx和expire之间出现异常,锁也无法释放)

2. 在set时指定过期时间(推荐)

 

 设置过期时间:

压力测试肯定也没有问题。自行测试

问题:可能会释放其他服务器的锁。

场景:如果业务逻辑的执行时间是7s。执行流程如下

  1. index1业务逻辑没执行完,3秒后锁被自动释放。
  1. index2获取到锁,执行业务逻辑,3秒后锁被自动释放。
  2. index3获取到锁,执行业务逻辑
  3. index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。

最终等于没锁的情况。

解决:setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁

 16.4.5 优化之UUID防误删

 

16.4.6 优化之LUA脚本保证删除的原子性

@GetMapping("testLockLua")
public void testLockLua() {
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
// 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 其他线程等待
        try {
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 Lua 脚本详解:

 项目中正确使用:

  1. 定义key,key应该是为每个sku定义的,也就是每个sku有一把锁。

String locKey ="lock:"+skuId; // 锁住的是每个商品的数据

Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,3,TimeUnit.SECONDS);

16.4.7 总结

1. 加锁

// 1. redis中获取锁,set k1 v1 px 20000 nx
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
      .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);

2.使用lua释放锁

// 2. 释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置lua脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);

3. 重试

Thread.sleep(500);
testLock();

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件

- 互斥性。在任意时刻,只有一个客户端能持有锁。

- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

- 加锁和解锁必须具有原子性。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/416678.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美元霸权的潜在风险——无锚货币,为什么都要刺激消费

第三章 美元体系的风险结构与定价 金句导读 结果平等 没有智慧&#xff0c;优胜劣汰 没有良心。竞争都不充分。 年轻人有出路&#xff0c;穷人有活路&#xff0c;先富起来的人才能有后路。 名词解释&#xff1a; 对冲风险&#xff1a;我是卖面包的的&#xff0c;我还囤很多面…

【持续更新篇】SLAM视觉特征点汇总+ORB特征点+VINS前端

Harris角点 opencv函数 cornerHarris提取输入图像的Harris角点 检测原理 检测思想&#xff1a;使用一个固定窗口在图像上进行任意方向的滑动&#xff0c;对比滑动前后的窗口中的像素灰度变化程度&#xff0c;如果存在任意方向上的滑动&#xff0c;都有较大灰度变化&#xf…

Jetpack Compose 实战 宝可梦图鉴

文章目录前言实现效果一、架构介绍二、一些的功能点的介绍加载图片并获取主色,再讲主色设置为背景一个进度缓慢增加的圆形进度条单Activity使用navigation跳转Compose可组合项返回时页面重组的问题hiltViewModel()主要参考项目总结前言 阅读本文需要一定compose基础&#xff0…

Python爬虫|全国补充耕地项目数量爬取与分析——多进程案例

一、使用的库 import requests from lxml import etree import time import random import re import openpyxl import openpyxl from pyecharts.charts import Bar, Pie from pyecharts import options as opts from multiprocessing.dummy import Pool 二、数据爬取思路 1…

手拉手Centos7安装配置Redis7

Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 Redis是一个NoSQL数据库&#xff0c;常用缓存(cache) Re…

Spark 安装及WordCount编写(Spark、Scala、java三种方法)

Spark 官网&#xff1a;Apache Spark™ - Unified Engine for large-scale data analytics Spark RDD介绍官网&#xff1a;https://spark.apache.org/docs/2.2.0/api/scala/index.html#org.apache.spark.rdd.RDD 下载好spark解压mv到软件目录 linux>mv spark-xxx-xxx /op…

统计套利策略

统计套利策略套利策略跨品种套利标的择时风控套利策略 套利是&#xff0c;某种商品在&#xff08;在同一市场或不同市场&#xff09;拥有两个价格的情况下&#xff0c;以较低的价格买进&#xff0c;较高的价格卖出&#xff0c;从而实现获利的交易方式。 比如咖啡店里有小杯、…

【jvm系列-04】精通运行时数据区共享区域---堆

JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…

chapter-6数据库设计原则

以下课程来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 数据库设计 数据库设计是基于应用系统需求分析中对数据的需求&#xff0c;解决数据的抽象、数据的表达和数据的存储等问题&#xff0c;其目标是设计出一个满足应用要求&#xff0c;简洁、高效、规范…

【c语言】二维数组

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…

「计算机控制系统」3. 计算机控制系统的数学描述

差分方程 Z变换 脉冲传递函数 计算机控制系统的响应 文章目录差分方程基础知识差分方程的解Z变换定义与性质求Z变换Z变换表求Z反变换用Z变换解差分方程脉冲传递函数脉冲传递函数与差分方程的相互转化开环脉冲传递函数闭环脉冲传递函数计算机控制系统的响应差分方程 基础知识 …

Photoshop CS6安装包下载及安装教程(Photoshop 2016)

下载链接&#xff1a; https://pan.quark.cn/s/f961759b36cc “Adobe Photoshop是一款集图像扫描、编辑修改、图像制作、广告创意、图像输入输出于一体的图形图像处理软件,简称ps,对于这款软件大家应该并不陌生,而今天小编带来的是Photoshop2023中文版,也是该系列的最新版本,不…

WAF攻防-菜刀冰蝎哥斯拉流量通讯特征绕过检测反制感知

文章目录菜刀-流量&绕过&特征&检测特征绕过检测冰蝎3-流量&绕过&特征&检测特征通讯过程检测绕过哥斯拉-流量&绕过&特征&检测特征Other使用Proxifier进行流量转发至Burp抓包分析(使用Wireshake也可以) 菜刀-流量&绕过&特征&检…

Java阶段一Day21

Java阶段一Day21 文章目录Java阶段一Day21多线程并发原理使用场景创建并启动线程创建线程的方法进程线程的生命周期获取线程信息的方法教师总结新单词多线程概念线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。多线程:多个单一顺序执行的流程…

最新!AI第一次有了国家标准,北大、华为、百度等单位共同编制

最近&#xff0c;国家标准全文公开系统网站正式发布了国家标准《神经网络表示与模型压缩 第一部分&#xff1a;卷积神经网络》&#xff08;GB/T 42382.1-2023&#xff09;&#xff0c;此标准由北京大学、鹏城实验室、华为、百度等16家单位共同编制。 &#xff08;来源&#xff…

考试前临时抱佛脚有用吗?这篇复习攻略会告诉你答案

夏天来了&#xff0c;大家的期末考试也将不远了。不知平时大家是如何准备考试的&#xff0c;是平时学习计划有序进行复习&#xff0c;还是考试前临时抱佛脚呢&#xff1f;今天就来跟大家讲一讲&#xff0c;学习中很重要的一个环节&#xff0c;如何复习。所以敲黑板&#xff0c;…

基于 AT89C51 单片机的数字时钟设计

目录 1.设计目的、作用 2.设计要求 3.设计的具体实现 3.1 设计原理 3.2 硬件系统设计 3.2.1 AT89C51 单片机原理 3.2.2 晶振电路设计 3.2.3 复位电路设计 3.2.4 LED 数码管显示 3.3 系统实现 3.3.1 系统仿真与调试 3.3.2 演示结果 4.总结 附录 附录 1 附录 2 1.…

【JVM】常量池

常量池&#xff08;Runtime Constant Poo&#xff09; 常量池Java中可以分为三种&#xff1a;字符串常量池&#xff08;堆&#xff09;、Class文件常量池、运行时常量池&#xff08;堆&#xff09;。 1.字符串常量池&#xff08;String Pool&#xff09; 为了提升性能和减少…

C++变量限定

C的变量限定指可以在变量类型的基础上加上特殊的限定条件&#xff0c;主要包括&#xff1a;是不是const&#xff0c;是不是volatile&#xff0c;是左值还是右值&#xff0c;是不是引用&#xff0c;是左值引用还是右值引用&#xff0c;等等。 1. 为什么要研究这个东西 主要是c…

练习之烦人的递归

文章目录1.删除公共字符2.读入一串以?结束的字符串&#xff0c;逆序输出。法一&#xff1a;常规递归法二&#xff1a;投机取巧3.递归将整数输出为字符串4.递归输出1--n的平方和5.递归计算222222...6.递归求最大公约数7.递归输出x的n次方8. 递归计算下列式子的值1.删除公共字符…