首页 > 数据库 >Redis(七)缓存穿透、缓存击穿、缓存雪崩以及分布式锁

Redis(七)缓存穿透、缓存击穿、缓存雪崩以及分布式锁

时间:2022-12-09 15:56:10浏览次数:64  
标签:缓存 过期 Redis redis key requests redisTemplate 分布式

应用问题解决

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,redis,key,requests,redisTemplate,分布式
From: https://www.cnblogs.com/tod4/p/16969141.html

相关文章

  • Redis(一)五种基本数据类型
    1NoSQl数据库1.1技术的发展技术的分类:①解决功能性问题:javase②解决扩展性问题:框架③解决性能问题:redis1.2NoSQL数据库概述NoSQL(NotOnlySQL),不仅仅是SQL,泛指......
  • Redis的数据被删除,占用内存咋还那么大?
    通过CONFIGSETmaxmemory100mb或者在redis.conf配置文件设置maxmemory100mbRedis内存占用限制。当达到内存最大值值,会触发内存淘汰策略删除数据。除此之外,当ke......
  • redis之配置
    一.基本参数1、daemonizedaeonize参数决定了Redis是否会称为一个守护进程,如果该参数值为yes,则表示Redis将会成为一个守护进程,如果该参数为no,则表示Redis不会成为一个守护......
  • 【分布式技术专题】「架构设计方案」盘点和总结秒杀服务的功能设计及注意事项技术体系
    秒杀应该考虑哪些问题超卖问题分析秒杀的业务场景,最重要的有一点就是超卖问题,假如备货只有100个,但是最终超卖了200,一般来讲秒杀系统的价格都比较低,如果超卖将严重影响公司的......
  • 论述微服务和分布式
    集中式框架、分布式框架和微服务概要:在系统架构与设计的实践中,从宏观上可以总结为三个阶段:集中式架构:就是把所有的功能、模块都集中到一个项目中,部署在一台服务器上,从而对......
  • redis特性/版本/删除/安装
    Redis是一个开源的高性能键值对数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,并且借助许多高层级的接口使其可以胜任,如缓存、队列系统的不同角色。​​Red......
  • 自己动手基于 Redis 实现一个 .NET 的分布式锁类库
    分布式锁的核心其实就是采用一个集中式的服务,然后多个应用节点进行抢占式锁定来进行实现,今天介绍如何采用Redis作为基础服务,实现一个分布式锁的类库,本方案不考虑Redis集......
  • GFS分布式文件系统
    一、文件系统简介1.1文件系统的组成接口:文件系统接口功能模块(管理、存储的工具):对对象管理里的软件集合对象及属性:(使用此文件系统的消费者1.2文件系统的作用从系......
  • 分布式系统初学:一条服务请求的响应过程
    友情提示:后续内容都是按照下面图片展开的,建议保存后再打开。其中用到的各种组件不是唯一的,只是我比较熟悉的,关于组件介绍,请看分布式系统相关组件介绍。 后续再写......
  • redis 状态 跟踪 参数值
    1查看客户端连接信息通过执行clientlist命令来查看客户端连接信息,每行都代表一个客户端127.0.0.1:6379>clientlistid=3addr=127.0.0.1:58752fd=7name=age=19951id......