当前位置: 代码网 > it编程>数据库>Redis > 解读缓存db redis local的取舍之道

解读缓存db redis local的取舍之道

2024年05月26日 Redis 我要评论
前言让我们来聊一下数据缓存,它是如何为我们带来快速的数据响应的。你知道吗,为了提高数据的读取速度,我们通常会引入数据缓存。但是,你知道吗,不是所有的数据都适合缓存,有些数据更适合直接从数据库查询。现在

前言

让我们来聊一下数据缓存,它是如何为我们带来快速的数据响应的。

你知道吗,为了提高数据的读取速度,我们通常会引入数据缓存。

但是,你知道吗,不是所有的数据都适合缓存,有些数据更适合直接从数据库查询。

现在,我们就来一起讨论一下,什么样的数据适合直接从数据库查询,什么样的数据适合从缓存中读取。

这将有助于我们更好地利用缓存,提高系统的性能。让我们开始吧!

一、影响因素

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

  • 访问频率:如果某个数据被频繁访问,且对实时性要求不高,那么将其缓存在内存中会显著提高响应速度。这样的数据可以是经常被查询的热点数据,比如网站的热门文章、商品信息等。
  • 数据更新频率:如果某个数据经常发生更新,那么将其缓存可能导致缓存和数据库中的数据不一致。对于这种情况,最好直接从数据库中查询最新数据。比如用户个人信息、订单状态等经常变动的数据。
  • 数据大小:较大的数据对象,如图片、视频等,由于其体积较大,将其缓存到内存中可能会占用大量资源。这种情况下,可以将这些数据存储在分布式文件系统或云存储中,并通过缓存存储其访问路径或标识符。
  • 数据一致性:一些数据在不同地方的多个副本可能会导致一致性问题。对于需要保持强一致性的数据,建议直接从数据库查询。而对于可以容忍一定程度的数据不一致的场景,可以考虑将数据缓存。
  • 查询复杂度:某些复杂的查询操作可能会消耗大量的计算资源和时间,如果这些查询结果需要频繁访问,可以将其缓存,避免重复计算,提高响应速度。

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

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

二、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

2.去除转换时间,直接响应

db > 208ms
redis > 428ms
local > 96ms

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com