参照有赞TMC框架原理简单实现多级缓存

简介: 项目场景:有位同事因为缓存被后台删除,导致一堆高并发请求直接怼到DB上,导致数据库cpu 100%

项目场景:

有位同事因为缓存被后台删除,导致一堆高并发请求直接怼到DB上,导致数据库cpu 100%

解决方案:
处理缓存击穿问题:像布隆过滤器,或者说提前设置热点key
就是热点key检测,这里谈到了有赞TMC框架多级缓存以及它的热点key的发现
个人简单实现相关原理
本地变量
像热点key储存,本地缓存以及相关参数设置设置。 在这里插入图片描述

获取本地缓存的数据
在这里插入图片描述 解释: 1.由于是分布式环境,所以先查询下这个key有没有被删除过 2.直接走本地缓存 3.如果是后台数据被修改,redis这个标识被修改到了,我们需要重新加载数据库的数据更新到本地缓存中,以及set到redis中

数据一致性问题
就是redis缓存跟本地缓存一致性问题,我的想法是惰性就行更新,如果有人去读取,先返回本地缓存的旧数据,后面再进行更新,也就是实现最终一致性问题。

存在问题

就是这里的flag在更新之后会变成0,我这里的的优化方案是:采用nacos的版本控制,redis有一份版本,本地也有一份版本,如果说redis上的版本跟本地缓存的版本有所不一样,那么就进行修改本地缓存,以及将最新的版本更新到本地缓存中。

这样的话就不会导致说一台机器把redis设置为0,另一台本地缓存就不会变了。

优化方案

使用nacos版本修改的原理来控制不同机器的本地缓存更新
更新的时候可以加个分布式锁,获得锁才能去查数据库,防止高并发查崩数据库。其次在把这个数据塞到redis还有本地缓存中。
设置缓存的值
加粗样式

删除缓存
在这里插入图片描述

统一获取缓存的方法
/**

 * 统一获取缓存数据
 *
 * @param key
 * @return
 */
public String getRedisByKey(String key) {
    //计数
    stringRedisTemplate.opsForValue().increment(key + ":incr", 1);
    //5秒过期
    stringRedisTemplate.expire(key + ":incr", 10, TimeUnit.SECONDS);
    String count = stringRedisTemplate.opsForValue().get(key + ":incr");
    if (count != null && Integer.valueOf(count) > 2) {
        if (map.get(key) != null) {
            System.out.println("命中热点key....");
            return getCacheValue(key);
        }
        //2写死,表示5秒内get超过2次,定义为热点key
        map.put(key, "true");
        if (stringRedisTemplate.getExpire(key, TimeUnit.SECONDS) < 10) {
            //自动延期
            System.out.println("自动延期");
            stringRedisTemplate.expire(key, 20, TimeUnit.SECONDS);
        }
    } else {
        map.remove(key);
        String result = stringRedisTemplate.opsForValue().get(key);
        if (result == null) {
            String value = a(key);
            setRedisByKey(key, value, 20L);
            return value;
        }
        System.out.println("直接走redis");
        return result;
    }
    return getCacheValue(key);
}

前面是进行简单的计数法来保存这个热点key,如果命中热点key直接读本地缓存,否则读redis,没有的话再去读DB。

重点
如果是热点key的话,那么就会去判断它过期时间,如果不够的话会自动给它进行续期。

优化
比如说热点key的统计方式,这里只是简单的redis+1,如果高级一点就是时间滑窗统计热点key
这里是封装redistemplate查询的方案,比较好的是有一个特有的分布式集群来收集这些redis查询,redis key过期、设置、删除操作等等,会更好。
在删除热点key map那里也是需要再优化的,就是如果说重新这个key在接下来的时间内不那么火热,那么剔除map对应的key。
所有代码
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Component
public class RedisManagement {

@Autowired
private StringRedisTemplate stringRedisTemplate;

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

private LoadingCache<String, String> graphs = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(1, TimeUnit.HOURS)
        .refreshAfterWrite(1, TimeUnit.HOURS)
        .build(
                new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) {
                        return a(key);
                    }
                });

private String getCacheValue(String key) {
    String result;
    String flag = stringRedisTemplate.opsForValue().get(key + ":flag");
    try {
        System.out.println("走本地缓存");
        result = graphs.get(key);
    } catch (ExecutionException e) {
        System.out.println("出现报错:" + e);
        return null;
    }
    //不为空还有已经删除状态
    if (flag != null && "1".equals(flag)) {
        //更新本地缓存的
        graphs.refresh(key);
        //设置删除标识为未删除
        stringRedisTemplate.opsForValue().set(key + ":flag", "0");
    }
    return result;
}

/**
 * 统一设置缓存
 *
 * @param key
 * @param value
 * @return
 */
public void setRedisByKey(String key, String value, long time) {
    //设置删除标识为未删除
    stringRedisTemplate.opsForValue().set(key + ":flag", "0");
    stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}

/**
 * 统一删除缓存
 *
 * @param key
 * @return
 */
public Boolean delRedisByKey(String key) {
    //设置删除标识为删除
    stringRedisTemplate.opsForValue().set(key + ":flag", "1");
    return stringRedisTemplate.delete(key);
}

/**
 * 统一获取缓存数据
 *
 * @param key
 * @return
 */
public String getRedisByKey(String key) {
    //计数
    stringRedisTemplate.opsForValue().increment(key + ":incr", 1);
    //5秒过期
    stringRedisTemplate.expire(key + ":incr", 10, TimeUnit.SECONDS);
    String count = stringRedisTemplate.opsForValue().get(key + ":incr");
    if (count != null && Integer.valueOf(count) > 2) {
        if (map.get(key) != null) {
            System.out.println("命中热点key....");
            return getCacheValue(key);
        }
        //2写死,表示5秒内get超过2次,定义为热点key
        map.put(key, "true");
        if (stringRedisTemplate.getExpire(key, TimeUnit.SECONDS) < 10) {
            //自动延期
            System.out.println("自动延期");
            stringRedisTemplate.expire(key, 20, TimeUnit.SECONDS);
        }
    } else {
        map.remove(key);
        String result = stringRedisTemplate.opsForValue().get(key);
        if (result == null) {
            String value = a(key);
            setRedisByKey(key, value, 20L);
            return value;
        }
        System.out.println("直接走redis");
        return result;
    }
    return getCacheValue(key);
}

/**
 * 初始化本地缓存数据
 *
 * @param key
 * @return
 */
private String a(String key) {
    System.out.println("查db");
    //执行不同逻辑
    if (key.startsWith("activity")) {
        //查数据库
        return "activity";
    } else if (key.startsWith("content")) {
        //查数据库
        return "content";
    } else {
        return "haha";
    }
}

}

相关文章
|
5月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
477 0
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
817 0
|
9月前
|
缓存 NoSQL Java
Redis应用—8.相关的缓存框架
本文介绍了Ehcache和Guava Cache两个缓存框架及其使用方法,以及如何自定义缓存。主要内容包括:Ehcache缓存框架、Guava Cache缓存框架、自定义缓存。总结:Ehcache适合用作本地缓存或与Redis结合使用,Guava Cache则提供了更灵活的缓存管理和更高的并发性能。自定义缓存可以根据具体需求选择不同的数据结构和引用类型来实现特定的缓存策略。
610 16
Redis应用—8.相关的缓存框架
|
12月前
|
存储 缓存 自然语言处理
SCOPE:面向大语言模型长序列生成的双阶段KV缓存优化框架
KV缓存是大语言模型(LLM)处理长文本的关键性能瓶颈,现有研究多聚焦于预填充阶段优化,忽视了解码阶段的重要性。本文提出SCOPE框架,通过分离预填充与解码阶段的KV缓存策略,实现高效管理。SCOPE保留预填充阶段的关键信息,并在解码阶段引入滑动窗口等策略,确保重要特征的有效选取。实验表明,SCOPE仅用35%原始内存即可达到接近完整缓存的性能水平,显著提升了长文本生成任务的效率和准确性。
636 3
SCOPE:面向大语言模型长序列生成的双阶段KV缓存优化框架
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
1465 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
缓存 NoSQL 应用服务中间件
SpringCloud基础8——多级缓存
JVM进程缓存、Lua语法、OpenResty、Nginx本地缓存、缓存同步、Canal
SpringCloud基础8——多级缓存
|
存储 缓存 监控
多级缓存有哪些级别?
【10月更文挑战第24天】多级缓存有哪些级别?
287 1
|
存储 缓存 监控
多级缓存
【10月更文挑战第24天】多级缓存
177 1
|
缓存 监控 算法
小米面试题:多级缓存一致性问题怎么解决
【10月更文挑战第23天】在现代分布式系统中,多级缓存架构因其能够显著提高系统性能和响应速度而被广泛应用。
913 3