首页 > 数据库 >Redis缓存在项目中的使用升级

Redis缓存在项目中的使用升级

时间:2024-08-09 21:25:49浏览次数:15  
标签:缓存 uuid dbValue Redis value 升级 import redisUtil

对于redis在项目中的使用与介绍这里就不展开描述,与进行基本的代码构造了。

这里针对redis的代码使用进行介绍

一,最简单阶段(一)--简单使用redis缓存

思想:拿数据先从缓存中拿去,如果缓存中没有再从数据库获取。

package com.luojie.test.catchs;

import com.luojie.dao.mapper2.Mapper2;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 简单使用redis缓存
 */
@Component
@Slf4j
public class RedisCatch1 {

    @Autowired
    Mapper2 mapper2;

    @Autowired
    RedisServiceUtil redisUtil;
    public String test1() {
        // 先从redis中找数据,没有才去查数据库
        String value = redisUtil.get("uuid");
        if (StringUtils.isNotEmpty(value)) {
            return value;
        } else {
            String dbValue = mapper2.getuuid("uuid");
            log.info("get data form db dbValue:{}", dbValue);
            if (StringUtils.isNotEmpty(dbValue)) {
                // 缓存数据
                redisUtil.setWithExpire("uuid", dbValue, 3, TimeUnit.DAYS);
            }
            return dbValue;
        }
    }
}

缺点:

无法避免缓存穿透,缓存击穿,缓存雪崩,并发写入的问题。

简单介绍问题名词:

  • 缓存穿透: 多个线程同时请求一个缓存中没有的数据时,可能会导致多个线程同时去查询数据库,这会给数据库带来较大的负载。

  • 缓存雪崩: 当缓存中的某个热数据在同一时间失效时,大量请求会同时击中数据库,导致数据库压力骤增。

  • 缓存击穿: 当缓存中某个热数据失效的瞬间,大量请求直接击中数据库,导致数据库瞬间负载过大。

  • 并发写入: 多个线程同时执行redisUtil.set("key", dbValue);,可能会导致一些不必要的重复操作。

二,优化上一步(二)--解决redis缓存常见问题

思想:

1,空结果缓存:解决缓存穿透

2,设置(随机过期时间):解决缓存雪崩

3,加锁:解决缓存击穿与并发写入问题

package com.luojie.test.catchs;

import com.luojie.dao.mapper2.Mapper2;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 简单使用redis缓存2
 */
@Component
@Slf4j
public class RedisCatch2 {

    @Autowired
    Mapper2 mapper2;

    @Autowired
    RedisServiceUtil redisUtil;

    public String test1() {
        // 先从redis中找数据,没有才去查数据库
        String value = redisUtil.get("uuid");
        if (StringUtils.isNotEmpty(value)) {
            return value;
        }
        synchronized (this) {
            // 双重检查,以防止在获取锁之后,缓存已经被其他线程更新
            value = redisUtil.get("uuid");
            if (StringUtils.isNotEmpty(value)) {
                return value;
            }
            // 缓存空结果的前提是该方法如果出错应抛出异常,而不是被catch后返回空
            String dbValue = mapper2.getuuid("uuid");
            log.info("get data form db dbValue:{}", dbValue);
            // 缓存数据
            redisUtil.setWithExpire("uuid", dbValue, 1 + (int) Math.ceil(Math.random() * 10), TimeUnit.DAYS);
            return dbValue;
        }

    }

}

缺点

锁的力度大,范围广。高并发环境下,可能重复获取锁

三,优化上一步(三)--使用最小粒度锁

思想:使用最小粒度锁。采用轮询的方式,避免锁被重复获取

package com.luojie.test.catchs;

import com.luojie.dao.mapper2.Mapper2;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 简单使用redis缓存3
 */
@Component
@Slf4j
public class RedisCatch3 {

    @Autowired
    Mapper2 mapper2;

    @Autowired
    RedisServiceUtil redisUtil;

    Lock lock = new ReentrantLock(false);

    public String test1() throws InterruptedException {
        // 先从redis中找数据,没有才去查数据库
        String value = redisUtil.get("uuid");
        if (StringUtils.isNotEmpty(value)) {
            return value;
        }
        try {
            if (lock.tryLock(300, TimeUnit.MILLISECONDS)) {
                // 双重检查,以防止在获取锁之后,缓存已经被其他线程更新
                value = redisUtil.get("uuid");
                if (StringUtils.isNotEmpty(value)) {
                    return value;
                }
                // 缓存空结果的前提是该方法如果出错应抛出异常,而不是被catch后返回空
                String dbValue = mapper2.getuuid("uuid");
                log.info("get data form db dbValue:{}", dbValue);
                // 缓存数据
                redisUtil.setWithExpire("uuid", dbValue, 1 + (int) Math.ceil(Math.random() * 10), TimeUnit.DAYS);
                return dbValue;
            } else {
                // 如果未获取到锁,则等待一段时间再从缓存中获取数据
                Thread.sleep(100);
                return test1();
            }
        } finally {
            lock.unlock();
        }

    }

}

缺点:

  • 本地锁局限性: ReentrantLock 是 JVM 级别的锁,只能在单个应用实例内有效。如果应用部署在多个实例(比如微服务架构或分布式系统)中,本地锁无法保证全局唯一性。

  • 潜在的死锁风险: 在尝试获取锁和释放锁时,如果发生异常而未能正确释放锁,可能会导致死锁。

四,优化上一步(四)--使用分布式锁

思想:

在集群部署情况下保证同一时间只有一个请求能打到数据库获取数据。

package com.luojie.test.catchs;

import com.luojie.dao.mapper2.Mapper2;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 简单使用redis缓存3
 */
@Component
@Slf4j
public class RedisCatch4 {

    @Autowired
    Mapper2 mapper2;

    @Autowired
    RedisServiceUtil redisUtil;

    public String test1() throws InterruptedException {
        // 先从redis中找数据,没有才去查数据库
        String value = redisUtil.get("uuid");
        if (StringUtils.isNotEmpty(value)) {
            return value;
        }
        try {
            if (redisUtil.setIfAbsent("lock", "doneLock")) {
                // 双重检查,以防止在获取锁之后,缓存已经被其他线程更新
                value = redisUtil.get("uuid");
                if (StringUtils.isNotEmpty(value)) {
                    return value;
                }
                // 缓存空结果的前提是该方法如果出错应抛出异常,而不是被catch后返回空
                String dbValue = mapper2.getuuid("uuid");
                log.info("get data form db dbValue:{}", dbValue);
                // 缓存数据
                redisUtil.setWithExpire("uuid", dbValue, 1 + (int) Math.ceil(Math.random() * 10), TimeUnit.DAYS);
                return dbValue;
            } else {
                // 如果未获取到锁,则等待一段时间再从缓存中获取数据
                Thread.sleep(100);
                // 自旋获取
                return test1();
            }
        } finally {
            // 删除锁
            redisUtil.delete("lock");
        }

    }

}

缺点:

若加锁成功但系统故障则会导致锁永久存在,进入死锁。

方法在未获取到锁时递归调用自身。如果长时间未获取到锁,这可能导致StackOverflowError。

五,优化上一步(五)--优化分布式锁

思想:

redis加锁增加超时时间已经独特vule设定--防止释放掉别人的锁

使用递归时增加次数循环次数判定

package com.luojie.test.catchs;

import com.luojie.dao.mapper2.Mapper2;
import com.luojie.util.RedisServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 简单使用redis缓存3
 */
@Component
@Slf4j
public class RedisCatch5 {

    @Autowired
    Mapper2 mapper2;

    @Autowired
    RedisServiceUtil redisUtil;

    private static final int MAX_RETRIES = 5;
    private static final long RETRY_DELAY_MS = 100;

    public String test1() throws InterruptedException {
        // 先从redis中找数据,没有才去查数据库
        String value = redisUtil.get("uuid");
        if (StringUtils.isNotEmpty(value)) {
            return value;
        }
        String uuid = UUID.randomUUID().toString();
        int retryCount = 0;
        boolean lockAcquired = false;
        // 循环代替迭代
        while (retryCount < MAX_RETRIES && !lockAcquired) {
            lockAcquired = redisUtil.setIfAbsentWithExpire("lock", uuid, 300, TimeUnit.SECONDS);
            if (!lockAcquired) {
                Thread.sleep(RETRY_DELAY_MS);
                ++retryCount;
            }
        }

        try {
            if (lockAcquired) {
                // 双重检查,以防止在获取锁之后,缓存已经被其他线程更新
                value = redisUtil.get("uuid");
                if (StringUtils.isNotEmpty(value)) {
                    return value;
                }
                // 缓存空结果的前提是该方法如果出错应抛出异常,而不是被catch后返回空
                String dbValue = mapper2.getuuid("uuid");
                log.info("get data form db dbValue:{}", dbValue);
                // 缓存数据
                redisUtil.setWithExpire("uuid", dbValue, 1 + (int) Math.ceil(Math.random() * 10), TimeUnit.DAYS);
                return dbValue;
            } else {
                // 如果未能获取到锁,重试从缓存获取数据
                return redisUtil.get("uuid");
            }
        } finally {
            // 获取锁值
            String lock = redisUtil.get("lock");
            if (StringUtils.isNotEmpty(lock) && lock.equals(uuid)) {
                // 删除锁
                redisUtil.delete("lock");
            }
        }
    }

}

缺点:

redis并没有跟服务进行脱钩,是强绑定的状态,当redis出现异常会导致服务也发生异常。

六,优化上一步(六) --最终 

思想:

对set,delete操作与主线程,主业务脱钩。使用多线程 + 监听器进行解耦操作

具体多线程 + 监听器的实现请参考:

SpringBoot使用Redis(事务异步add + 更新)_spring redis 异步处理-CSDN博客

标签:缓存,uuid,dbValue,Redis,value,升级,import,redisUtil
From: https://blog.csdn.net/weixin_54925172/article/details/140890468

相关文章

  • 璞公英与恩施三中试点签约,AI大模型批阅+精准教学赋能教育升级
    在国家大力推动教育数字化战略行动的浪潮中,通过科技创新赋能教育,促进教育公平与质量双提升的重要签约仪式,于恩施市第三高级中学隆重举行。一直以来,恩施市第三高级中学积极寻求创新与突破,引入璞公英的先进教育技术,旨在提升教学质量,为学生创造更优质的学习环境。此次签约仪式标志着......
  • redis的几种常见问题
    缓存穿透redis缓存和数据库中没有相关数据解决方案对不存在的数据缓存到redis中,设置key,value值为null(不管是数据未null还是系统bug问题),并设置一个短期过期时间段,避免过期时间过长影响正常用户使用。拉黑该IP地址对参数进行校验,不合法参数进行拦截布隆过滤器将所有可能存......
  • redis数据结构
    redis数据类型 stringlisthashsetzsetHyperLogLogGEOBloomFilter(布隆过滤器)HyperLogLog基本概念:Redis在2.8.9版本添加了HyperLogLog结构。RedisHyperLogLog是用来做基数统计的算法,所谓基数,也就是不重复的元素。优点在输入元素的数量或者体积非常......
  • 如何关闭redis的自动清理缓存,声明式事务(含有redis)如何解决,redis setnx锁的使用。
    20240809一、解决redis数据被删除的方案1、发现问题2、解决注意!!二、声明式事务(当有redis的时候)1.先看代码2.@Transactional(rollbackFor=Exception.class)3.如何解决redis在事务里面,如何保证原子性和一致性3.1我们可以用trycatchfinally来实现3.2我们可以让red......
  • Redis笔记
    Redis(Remotedictionaryserver)开源的基于内存的数据存储系统可用于数据库、缓存、队列等各种场景。支持key-value的储存形式,底层是用C语言编写的。基于key-value形式的数据字典,结构非常简单,没有数据表的概念,之间用键值对的形式完成数据的管理。SQL NoSQL数据结构结构......
  • docker 构建redis
    一、Docker搜索redis镜像dockersearchredis 二、Docker拉取镜像dockerpullredis 三、Docker挂载配置文件接下来就是要将redis的配置文件进行挂载,以配置文件方式启动redis容器。(挂载:即将宿主的文件和容器内部目录相关联,相互绑定,在宿主机内修改文件的话也随之修改容......
  • dockker部署redis报错(报:WARNING Memory overcommit must be enabled! Without it, a b
    转载博客(机翻):https://ourcodeworld.com/articles/read/2083/how-to-remove-redis-warning-on-docker-memory-overcommit-must-be-enabledHowtoremoveRediswarningonDocker:Memoryovercommitmustbeenabled如何删除Docker上的Redis警告:必须启用内存过量使用Carlos......
  • 一个升级的多租户权限管理系统,组件化,模块化,轻耦合,高扩展企业级的应用框架,功能强大(
    前言在现代软件开发中,多租户权限管理系统是企业级应用中的一个关键组件。然而,现有的一些框架,如RuoYi,虽然提供了一些基本的功能,但在面对更复杂的企业级需求时,如原生的MyBatis使用、复杂的分页处理,以及一些高级功能支持上,仍然存在一些不足和痛点。为了解决这些问题,并提供一个更......
  • Yum缓存包
    简单介绍 使用系统版本:[root@localhost~]#uname-aLinuxlocalhost5.14.0-427.13.1.el9_4.x86_64#1SMPPREEMPT_DYNAMICWedMay119:11:28UTC2024x86_64x86_64x86_64GNU/Linux[root@localhost~]#cat/etc/os-releaseNAME="RockyLinux"VERSION="9.......
  • 题解 洛谷P1478 陶陶摘苹果(升级版)
    题目传送门https://www.luogu.com.cn/problem/P1478截图来自洛谷:这道题就是这道题的升级版而已,我们可以定义一个结构体分别存抓当前苹果的力气与高度。之后进行从第1个苹果到第n个苹果的循环,判断当前苹果高度是否够,力气是否够。最重要的是要排序,因为要摘得苹果最多,所以要先......