Redis入门(六)之缓存以及删除策略和淘汰策略

本文详细讲解了Redis的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿的解决方案,如使用缓存空对象和布隆过滤器、设置二级缓存、使用分布式锁等。接着介绍了Redis的事务机制,包括multi、exec、discard命令及其错误处理策略。此外,还阐述了Redis的发布订阅功能,以及两种删除策略:定时删除、惰性删除和定期删除。最后讨论了内存满时的淘汰策略和相关配置。
摘要由CSDN通过智能技术生成

1.写在前面

前面的博客的简简单单的介绍完了Redis的基本的知识,持久化、API、集群、缓存、分布式锁这些东西,今天的博客的打算将缓存的剩下的几个问题给讲讲完,然后简单的介绍下Redis的事务、发布和订阅、Redis的删除策略和淘汰策略。

2.Redis缓存问题

2.1缓存穿透

缓存穿透:缓存中没有,数据库中也没有,那么不过不做处理,大量的请求就会直接打到数据库上,给数据库造成了很大的压力。

这个前面有一篇博客《Redis入门(二)之缓存穿透》,这篇主要介绍了Redis缓存中的缓存穿透问题,这儿就不做过多的赘述了,我这儿就讲一下具体的解决方案吧。

解决办法:缓存空对象,布隆过滤器

2.2缓存雪崩

缓存雪崩是指机器宕机或在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决办法如下:

  1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  2. 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
  3. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  4. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

2.3缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。

这儿我就演示下这个效果,还是之前《Redis入门(二)之缓存穿透》中的代码,不过我这儿写了一个测试类,具体的如下:

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.ys.entity.R;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

public class Test {

    private static CountDownLatch countDownLatch = new CountDownLatch(99);

    @org.junit.Test
    public void test() throws InterruptedException {
        TicketsRunBle ticketsRunBle = new TicketsRunBle();

        for (int i = 0; i < 99; i++) {
            Thread thread1 = new Thread(ticketsRunBle, "窗口" + i);
            thread1.start();
            countDownLatch.countDown();
        }
        Thread.currentThread().join();
    }

    public class TicketsRunBle implements Runnable {

        @Override
        public void run() {

            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            RestTemplate restTemplate = new RestTemplate();
            List<HttpMessageConverter<?>> fastJsonHttpMessageConverters = new ArrayList<>();
            fastJsonHttpMessageConverters.add(new FastJsonHttpMessageConverter());
            restTemplate.setMessageConverters(fastJsonHttpMessageConverters);
            R forObject = restTemplate.getForObject("http://localhost:8080/selectid?id=1", R.class);
            System.out.println(forObject);
        }
    }
}

上面的代码我们用99个线程去访问这个接口,这个时候的索引还没有建立好,就相当于索引失效,理论上这儿数据库只查询一次,然而情况却不是这样的,具体的情况如下:

在这里插入图片描述

然后你就发现我们的缓存的机制失效,这就是缓存的击穿,那么我们如何解决呢?加上分布式锁,于是我们修改原来的代码,具体的如下:

public R redisFindCache(String key, long expire, TimeUnit unit, CacheLoadble<T> cacheLoadble, boolean b) {
        if (!bloomFilter.isExist(key)) {
            return new R().setCode(600).setData(new NullValueResultDO()).setMsg("非法访问");
        }
        //查询缓存
        Object redisObj = valueOperations.get(String.valueOf(key));
        //命中缓存
        if (redisObj != null) {
            //正常返回数据
            return new R().setCode(200).setData(redisObj).setMsg("OK");
        }
        redisLock.lock(key);
        try{
            //查询缓存
            redisObj = valueOperations.get(String.valueOf(key));
            if (redisObj != null) {
                //正常返回数据
                return new R().setCode(200).setData(redisObj).setMsg("OK");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock(key);
        }
        T load = cacheLoadble.load();//查询数据库
        if (load != null) {
            valueOperations.set(key, load, expire, unit);  //加入缓存
            return new R().setCode(200).setData(load).setMsg("OK");
        }
        return new R().setCode(500).setData(new NullValueResultDO()).setMsg("查询无果");
    }

上面的代码主要是加上了一个Redis的分布式锁,在查询缓存为空的时候,然后加锁,再查一次缓存,然后如果查不到的话,就再走数据库,然后启动我们的项目,然后再来测试看看我们的加的Redis分布式的效果如何?具体的如下:

在这里插入图片描述

发现这儿就查询了一次数据库,这样我们的问题就解决了。

3.Redis的事务

3.1什么是Redis的事务

Redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体,就是一个队列,当执行的时候,一次性按照添加顺序依次执行,中间不会被打断或者干扰。

3.2用来干什么?

一个队列中,一次性,顺序性,排他性的执行一系列命令。

3.3Redis事务基本的操作

  • 开启事务:multi 设置事务开始位置,这个指令开启后,后面所有的指令都会加入事务中。
  • 执行事务:exec 设置事务的结束位置,同时执行事务,与multi成对出现,成对使用。
  • 取消事务:discard 终止当前事务,取消multi后,exec前的所有命令。
  • 注意:加入事务的命令并没有立马执行,而且加入队列中,exec命令后才执行。

3.4加入和执行事务有错误会怎么办?

加入事务语法错误,事务则取消。**(全体连坐)**具体的如下:

在这里插入图片描述

执行事务报错,则成功的返回成功,失败的返回失败,不会影响报错后面的指令。**(冤有头,债有主)**具体的如下:

在这里插入图片描述

注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己实现回滚。

3.5监控key

  • watch:对key进行监控,如果在exec执行前,监控的key发生了变化,终止事务执行。具体的如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdeiNBVZ-1626421764300)(/Users/king/博客/Redis/img5/5.png)]

  • unwatch:取消对所有的key进行监控

4.Redis发布订阅

  • publish:发布消息 语法:publish channel名称 “消息内容”

  • subscribe:订阅消息 语法:subscribe channel名称

    在这里插入图片描述

  • psubscribe:使用通配符订阅消息 语法:pubscribe channel*名称

  • punsubscribe:使用统配符退订消息 语法:punsubscribe channel*名称

  • unsubscribe:退订消息 语法:unsubscribe channel名称

    在这里插入图片描述

5.删除策略

5.1定时删除

先画个图吧,然后再讲具体的逻辑,具体的如下:

在这里插入图片描述

上面的就是先将键值对存入Redis中,然后Redis有一块内存存的是这些值的地址和存入Redis的时间戳,Redis中会开启一个定时器,定时扫描这些值有没有过期,如果过期的话,就直接删除。

以CPU换取Redis内存。

5.2惰性删除

就是所有的内容都不删除,而是等查询的时候,查看这个键是不是过期了,如果过期,就再删除。如果没过期,就直接返回对应的值。这样做就会有很多无效的数据。

以Redis内存换取CPU

5.3定期删除

  1. Redis在启动的时候读取配置文件hz的值,默认为10
  2. 每秒执行hz次serverCron()–>databasesCron()–>actveExpireCyle()
  3. actveExpireCyle()对每个expires[*]进行逐一检测,每次执行250ms/hz
  4. 对某个expires[*]检测时,随机挑选N个key检查
    • 如果key超时,删除key
    • 如果一轮中删除的key的数量大于N*25%,循环该过程
    • 如果一轮中删除的key的数量小于等于N*25%,检查下一个expires[*]
  5. current_db用于记录actveExpireCyle()进入哪个expires[*]执行,如果时间到了,那么下次根据current_db继续执行。

注意:Redis中默认的是惰性删除和定期删除

6.淘汰策略

就是Redis中内存满了,Redis该怎么处理的这些数据,以及怎么处理添加的新数据。

相关配置:

# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
# Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>

#当内存使用达到最大值时,redis使用的清除策略。有以下几种可以选择(明明有6种,官方配置文件里却说有5种可以选择?):
# volatile针对的是加了过期时间的数据, allkeys针对的是所有的数据
# 1)volatile-lru   利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used ) 最近最少使用
# 2)allkeys-lru   利用LRU算法移除任何key 
# 3)volatile-random 移除设置过过期时间的随机key 
# 4)allkeys-random  移除随机key 
# 5)volatile-ttl   移除即将过期的key(minor TTL) 
# 6)noeviction  不移除任何key,只是返回一个写错误 。默认选项
maxmemory-policy noeviction

# LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存),随意你可以选择样本大小进行检测。redis默认选择5个样本进行检测,你可以通过maxmemory-samples进行设置样本数。
maxmemory-samples 5

7.写在最后

本篇博客主要简简单单的介绍了下Redis的三大缓存问题、Redis事务、Redis发布订阅、Redis中的删除策略、Redis中内存满了的淘汰的策略。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值