关于缓存 db redis local 取舍之道

文章目录

  • 前言
  • 一、影响因素
  • 二、db or redis or local
    • 1.db
    • 2.redis
    • 3. local
  • 三、redisson 和 CaffeineCache 封装
    • 3.1 redisson
      • 3.1.1 maven
      • 3.1.2 封装
      • 3.1.3 使用
    • 3.2 CaffeineCache
      • 3.1.1 maven
      • 3.1.2 封装
      • 3.1.3 使用
  • 总结


前言

让我们来聊一下数据缓存,它是如何为我们带来快速的数据响应的。你知道吗,为了提高数据的读取速度,我们通常会引入数据缓存。但是,你知道吗,不是所有的数据都适合缓存,有些数据更适合直接从数据库查询。现在,我们就来一起讨论一下,什么样的数据适合直接从数据库查询,什么样的数据适合从缓存中读取。这将有助于我们更好地利用缓存,提高系统的性能。让我们开始吧!

一、影响因素

当涉及到数据查询和缓存时,有几个因素可以考虑来确定什么样的数据适合直接从数据库查询,什么样的数据适合从缓存中读取。

  • 访问频率:如果某个数据被频繁访问,且对实时性要求不高,那么将其缓存在内存中会显著提高响应速度。这样的数据可以是经常被查询的热点数据,比如网站的热门文章、商品信息等。

  • 数据更新频率:如果某个数据经常发生更新,那么将其缓存可能导致缓存和数据库中的数据不一致。对于这种情况,最好直接从数据库中查询最新数据。比如用户个人信息、订单状态等经常变动的数据。

  • 数据大小:较大的数据对象,如图片、视频等,由于其体积较大,将其缓存到内存中可能会占用大量资源。这种情况下,可以将这些数据存储在分布式文件系统或云存储中,并通过缓存存储其访问路径或标识符。

  • 数据一致性:一些数据在不同地方的多个副本可能会导致一致性问题。对于需要保持强一致性的数据,建议直接从数据库查询。而对于可以容忍一定程度的数据不一致的场景,可以考虑将数据缓存。

  • 查询复杂度:某些复杂的查询操作可能会消耗大量的计算资源和时间,如果这些查询结果需要频繁访问,可以将其缓存,避免重复计算,提高响应速度。

需要注意的是,数据缓存并非适用于所有情况。缓存的使用需要谨慎,需要权衡数据的实时性、一致性和存储成本等方面的需求。此外,对于缓存数据的更新和失效策略也需要考虑,以确保缓存数据的准确性和及时性。

综上所述,数据适合直接从数据库查询还是缓存读取,取决于数据的访问频率、更新频率、大小、一致性要求和查询复杂度等因素。在实际应用中,需要根据具体情况进行综合考虑和合理选择。

二、db or redis or local

1.db

  • 查询复杂度低
  • 字段少
  • sql执行效率高
  • 实时性高

通常数据库适合查询字典类型数据,如类似 key value 键值对,数据更新频繁,实时性高的数据。
对于sql效率高的查询,redis查询不一定比db查询快。

2.redis

  • 查询复杂度高
  • 字段相对不多
  • 实时性低

Redis适合查询复杂度较高、实时性要求较低的数据。当SQL查询效率较低,或者需要进行字段code和value的转换存储时,Redis可以提供更高效的查询方式。不过,需要注意的是,Redis的主要瓶颈在于数据的序列化和反序列化过程。如果数据量较大,包含大量字段或者数据量巨大,那么Redis的查询速度可能不一定比数据库快,当然此时数据库本身执行效率也低。在这种情况下,我们需要综合考虑数据的复杂度、实时性要求以及数据量的大小,选择最适合的查询方式。有时候,可能需要在数据库和Redis之间进行权衡和折中,以找到最佳的性能和效率平衡点。因此,为了提高查询速度,我们需要根据具体的业务需求和数据特性,选择合适的存储和查询方案。

3. local

  • 查询复杂度高
  • 字段多
  • 实时性低

本地缓存通常是最快的。它可以在内存中直接读取数据,速度非常快。然而,由于受限于内存大小,本地缓存的数据量是有限的。对于那些数据库和Redis难以处理的大型数据,我们可以考虑使用本地缓存。通过将一部分频繁访问的数据存储在本地缓存中,可以大大提高系统的响应速度。这样,我们可以在不牺牲太多内存资源的情况下,快速获取到需要的数据。当然,需要注意的是,由于本地缓存的数据是存储在内存中的,所以在服务器重启或缓存过期时,需要重新从数据库或Redis中加载数据到本地缓存中。因此,在使用本地缓存时,需要权衡数据的大小、更新频率以及内存资源的限制,以获得最佳的性能和可用性。

三、redisson 和 CaffeineCache 封装

提供缓存查询封装,查询不到时直接查数据库后存入缓存。

3.1 redisson

3.1.1 maven

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
        </dependency>

3.1.2 封装

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.cuzue.common.core.exception.BusinessException;
import com.cuzue.dao.cache.redis.RedisClient;
import org.redisson.api.RBucket;
import org.redisson.api.RKeys;
import org.redisson.api.RedissonClient;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class RedisCacheProvider {

    private static RedissonClient redissonClient;

    public RedisCacheProvider(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 从redissonClient缓存中取数据,如果没有,查数据后存入
     *
     * @param key         redis key
     * @param dataFetcher 获取数据
     * @param ttl         缓存时间
     * @param timeUnit    缓存时间单位
     * @param <T>
     * @return 数据
     */
    public <T> List<T> getCachedList(String key, Supplier<List<T>> dataFetcher, long ttl, TimeUnit timeUnit) {
        if (ObjectUtil.isNotNull(redissonClient)) {
            // 尝试从缓存中获取数据
            List<T> cachedData = redissonClient.getList(key);
            if (cachedData.size() > 0) {
                // 缓存中有数据,直接返回
                return cachedData;
            } else {
                // 缓存中没有数据,调用数据提供者接口从数据库中获取
                List<T> data = dataFetcher.get();
                cachedData.clear();
                cachedData.addAll(data);
                // 将数据存入缓存,并设置存活时间
                // 获取 bucket 对象,为了设置过期时间
                RBucket<List<T>> bucket = redissonClient.getBucket(key);
                // 为整个列表设置过期时间
                bucket.expire(ttl, timeUnit);
                // 返回新获取的数据
                return data;
            }
        } else {
            throw new BusinessException("redissonClient has not initialized");
        }
    }

    /**
     * 删除缓存
     *
     * @param key redis key
     */
    public void deleteCachedList(String systemName, String key) {
        if (ObjectUtil.isNotNull(redissonClient)) {
            RKeys keys = redissonClient.getKeys();
            keys.deleteByPattern(key);
        } else {
            throw new BusinessException("redis client has not initialized");
        }
    }
}

3.1.3 使用

启动类添加:@Import({RedissonConfig.class})
直接引用:


@Resource
private RedissonClient redissonClient;

//缓存数据获取
public List<MatMaterialsResp> listCache(ListQO qo) {
    RedisCacheProvider cache = new RedisCacheProvider(redissonClient);
    List<MatMaterialsResp> resps = cache.getCachedList("testList", () -> {
        // 缓存数据查询
    }, 20, TimeUnit.SECONDS);
    return resps;
}

3.2 CaffeineCache

也可以使用hashMap

3.1.1 maven

       <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>3.0.5</version>
        </dependency>

3.1.2 封装

CaffeineCache<K, V>

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Weigher;

import java.util.concurrent.TimeUnit;
import java.util.function.Function;

public class CaffeineCache<K, V> {
    private final Cache<K, V> cache;

    /**
     * 不过期缓存
     *
     * @param maxSize 缓存条目数量 注意对象大小不要超过jvm内存
     */
    public CaffeineCache(long maxSize) {
        this.cache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .build();
    }

    /**
     * 初始化Caffeine
     *
     * @param maxSize
     * @param expireAfterWriteDuration
     * @param unit
     */
    public CaffeineCache(long maxSize, long expireAfterWriteDuration, TimeUnit unit) {
        this.cache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(expireAfterWriteDuration, unit)
                .build();
    }

    /**
     * 初始化Caffeine 带权重
     *
     * @param maxSize
     * @param weigher                  权重
     * @param expireAfterWriteDuration
     * @param unit
     */
    public CaffeineCache(long maxSize, Weigher weigher, long expireAfterWriteDuration, TimeUnit unit) {
        this.cache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .weigher(weigher)
                .expireAfterWrite(expireAfterWriteDuration, unit)
                .build();
    }

    public V get(K key) {
        return cache.getIfPresent(key);
    }

    public void put(K key, V value) {
        cache.put(key, value);
    }

    public void remove(K key) {
        cache.invalidate(key);
    }

    public void clear() {
        cache.invalidateAll();
    }

    // 如果你需要一个加载功能(当缓存miss时自动加载值),你可以使用这个方法
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
        return cache.get(key, mappingFunction);
    }

    // 添加获取缓存统计信息的方法
    public String stats() {
        return cache.stats().toString();
    }
}


LocalCacheProvider

import cn.hutool.core.util.ObjectUtil;
import com.cuzue.dao.cache.localcache.CaffeineCache;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 本地缓存
 */
public class LocalCacheProvider {

    private static CaffeineCache cache;

    /**
     * 无过期时间
     * @param maxSize 缓存最大条数
     */
    public LocalCacheProvider(long maxSize) {
        cache = new CaffeineCache(maxSize);
    }

    /**
     * 带过期时间
     * @param maxSize 缓存最大条数
     * @param ttl 过期时间
     * @param timeUnit 时间单位
     */
    public LocalCacheProvider(long maxSize, long ttl, TimeUnit timeUnit) {
        cache = new CaffeineCache(maxSize, ttl, timeUnit);
    }

    public static <T> List<T> getCachedList(String key, Supplier<List<T>> dataFetcher) {
        if (ObjectUtil.isNotNull(cache.get(key))) {
            return (List<T>) cache.get(key);
        } else {
            List<T> data = dataFetcher.get();
            cache.put(key, data);
            return data;
        }
    }

    public static <T> List<T> getCachedList(String key, Function<String, List<T>> dataFetcher) {
        return (List<T>) cache.get(key, dataFetcher);
    }

    /**
     * 删除缓存
     *
     * @param key redis key
     */
    public void deleteCachedList(String key) {
        cache.remove(key);
    }
}

3.1.3 使用

//初始化caffeine对象
LocalCacheProvider cache = new LocalCacheProvider(5000, 20, TimeUnit.SECONDS);

//缓存数据获取
public List<MatMaterialsResp> listLocalCache(ListQO qo) {
    List<MatMaterialsResp> resps = cache.getCachedList("testList", (s) -> {
	  // 缓存数据查询
    });
    return resps;
}

注意:Caffeine 实现的缓存占用 JVM 内存,小心 OutOfMemoryError

解决场景:
1.本地缓存适用不限制缓存大小,导致OOM,适合缓存小对象
2.本地缓存长时间存在,未及时清除无效缓存,导致内存占用资源浪费
3.防止人员api滥用, 未统一管理随意使用,导致维护性差等等

总结

从前的无脑经验,db查询慢,redis缓存起来,redis真不一定快!
一个简单性能测试:(测试响应时间均为二次查询的大概时间)

  1. 前置条件: 一条数据转换需要200ms,共5条数据,5个字段项,数据量大小463 B
db > 1s
redis > 468ms
local > 131ms
  1. 去除转换时间,直接响应
db > 208ms
redis > 428ms
local > 96ms

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

React三大属性

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

x-cmd pkg | hurl - HTTP 请求处理工具

目录 简介首次用户功能特点竞品和相关作品进一步探索 简介 Hurl 是 HTTP 请求处理工具&#xff0c;支持使用简单的纯文本格式定义的 HTTP 请求。它的用途非常广泛&#xff0c;既可以用于获取数据&#xff0c;也可以用于测试HTTP会话。 它可以链式处理请求&#xff0c;捕获数值…

微服务Spring Cloud架构详解

"Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具&#xff08;例如配置管理&#xff0c;服务发现&#xff0c;断路器&#xff0c;智能路由&#xff0c;微代理&#xff0c;控制总线&#xff09;。分布式系统的协调导致了样板模式, 使用Spring Cloud开…

如何通过系统命令排查账号安全?

如何通过系统命令排查账号安全 query user 查看当前登录账号 logoff id 注销用户id net user 查看用户 net user username 查看用户登录情况 lusrmgr.msc 查看隐藏账号 winR打开regedit注册表 找到计算机\HEKY_LOCAL_MACHINE\SAM\SAM\右键给与用户读写权限 刷新打开 HKEY…

BACnet路由器 BA101专为实现BACnet IP和BACnet MS/TP协议之间的相互转化而研发的。

随着通讯技术和控制技术的发展&#xff0c;为了实现楼宇的高效、智能化管理&#xff0c;集中监控管理已成为楼宇智能管理发展的必然趋势。在此背景下&#xff0c;高性能的楼宇暖通数据传输解决方案——协议转换网关应运而生&#xff0c;广泛应用于楼宇自控和暖通空调系统应用中…

opencv#28 图像卷积

图像卷积 图像卷积是图像处理中最为基础的操作之一&#xff0c;其常用在图像的边缘检测&#xff0c;图像的去噪声以及图像压缩等领域。 图像卷积主要步骤: Step1:将卷积模板旋转180。 Step2:卷积模板移动到对应位置。 Step3:模板内求和&#xff0c;保存求和结果。 Step4:滑…

vue封装接口

目录 封装接口前缀 配置逻辑 接口存放文件 配置代理 获取数据方法 封装接口前缀 config.js const serverConfig {baseURL: "https://xxx.xxxxxxxx.com/api", // 请求基础地址,可根据环境自定义useTokenAuthorization: false, // 是否开启 token 认证};export …

YOLOv8改进 | Conv篇 | 2024.1月最新成果可变形卷积DCNv4(适用检测、Seg、分类、Pose、OBB)

一、本文介绍 本文给大家带来的改进机制是2024-1月的最新成果DCNv4,其是DCNv3的升级版本,效果可以说是在目前的卷积中名列前茅了,同时该卷积具有轻量化的效果!一个DCNv4参数量下降越15Wparameters左右,。它主要通过两个方面对前一版本DCNv3进行改进:首先,它移除了空间聚…

Flutter底部导航栏插件persistent_bottom_nav_bar的使用

flutter 框架中的 persistent_bottom_nav_bar 插件可以让我们快速实现页面底部导航栏&#xff08;也就是 bottomNavigationBar &#xff09;的布局且能拥有多样的切换效果&#xff08;包括但不限于&#xff1a;动画切换效果、中间凸起按钮效果等&#xff09; 插件网址&#xf…

Go语言学习笔记:基础语法和类型

Go语言学习笔记&#xff1a;基础语法和类型 目录 Go语言学习笔记&#xff1a;基础语法和类型学习路线前言变量声明常量数据类型布尔型&#xff08;Boolean&#xff09;整型&#xff08;Integer&#xff09;浮点型&#xff08;Floating point&#xff09;复数型&#xff08;Comp…

2024PMP考试新考纲-【过程领域】近期典型真题和很详细解析(10)

华研荟继续为您分享【过程Process领域】的新考纲下的真题&#xff0c;帮助大家体会和理解新考纲下PMP的考试特点和如何应用所学的知识和常识&#xff08;经验&#xff09;来解题&#xff0c;并且举一反三&#xff0c;一次性3A通过2024年PMP考试。 如有的同学反馈和交流&#x…

苏州渭塘镇应用无人机“智慧执法”

苏州渭塘镇应用无人机“智慧执法” 在今年以来&#xff0c;渭塘镇综合行政执法局采用了“空中地面”的立体监督模式&#xff0c;以实现对“互联网执法”工作的深入推进。在这一模式下&#xff0c;无人机巡查作为技术手段得到广泛应用&#xff0c;而安全生产监管信息系统和综合…

Buttton样式设置background属性失效的问题

最近遇到一个之前没有遇见的问题&#xff0c;就是在添加Button控件的时候发现对其设置background时没有效果&#xff0c;原因是AndroidStudio升级后默认按钮就是主题色&#xff0c;一个比较简单的方法是将Button改为android.widget.Button&#xff0c;对比效果如下&#xff1a;…

【Emgu CV教程】5.7、几何变换之LogPolar()极坐标变换

上一篇讲完了LinearPolar()函数用法&#xff0c;Emgu CV里面还有一个LogPolar()函数&#xff0c;它是这样定义的: public static void LogPolar(IInputArray src, // 输入图像IOutputArray dst, // 输出图像PointF center, // 极坐标变换中心&#xff0c;一般就是图像的中心d…

[ACM学习]自上而下树形dp

问题引入 设置dp状态&#xff0c;相比于更容易出错的贪心更...不易出错。 状态设计 如果选择父结点&#xff0c;就会使孩子结点不能被选择&#xff0c;我们会多开一维的dp&#xff0c;用来标记该点是否被标记过。 以1点举例&#xff0c;f[1][0]为不选它的状态&#xff0c;那么…

shell脚本概述

将命令写到脚本里面&#xff0c;利用路径或者解释器去执行。简要来说脚本其实就是命令的集合。 例如&#xff1a;echo $&#xff1f; 自定义变量&#xff0c;查看上次命令执行是否正确 linux常用的shell 脚本的构成&#xff1a; 1.解释器 &#xff08;脚本是用什么语言写的…

【数据结构与算法】3.顺序表

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

FPGA经典书籍分享

推荐一系列FPGA开发方面的书&#xff0c;这些书看完的话对你的FPGA技能会有很大的帮助。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 内容简介 本书系统论述了新一代FPGA设计套件Vivado的性能、使用方法以及FPGA的开发方法。全书内容包括Vivado设计…

Pyside6在Pycharm下安装和使用

目录 一&#xff1a;安装 二&#xff1a;使用 一&#xff1a;安装 打开Pycharm编辑器&#xff0c;file-setting里Python解释器&#xff0c;点击小号&#xff0c;添加模块&#xff0c;搜索Pyside6,安装 安装报错&#xff0c;可能是默认的库安装超时&#xff0c;用其他的源 p…

Conda python管理环境environments 三 从入门到精通

Conda系列&#xff1a; 翻译: Anaconda 与 miniconda的区别Miniconda介绍以及安装Conda python运行的包和环境管理 入门Conda python管理环境environments 一 从入门到精通Conda python管理环境environments 二 从入门到精通 1. Activating an environment激活环境 激活环境…