开发者

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

开发者 https://www.devze.com 2024-08-10 10:09 出处:网络 作者: Mr-Wanter
目录前言一、影响因素二、db or Redis or local1.db2.redis3. local三、redisson 和 CaffeineCache 封装3.1 redisson3.2 CaffeineCache总结前言
目录
  • 前言
  • 一、影响因素
  • 二、db or Redis or local
    • 1.db
    • 2.redis
    • 3. local
  • 三、redisson 和 CaffeineCache 封装
    • 3.1 redisson
    • 3.2 CaffeineCache
  • 总结

    前言

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

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

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

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

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

    一、影响因素

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

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

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

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

    二、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");
            }
        }
    
        /**
         * 删除缓存
       js  *
         * @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) -&pythongt; {
    	  // 缓存数据查询
        });
        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
    

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号