剑指offer之java缓存总结,从单机缓存到分布式缓存架构

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 剑指offer之java缓存总结,从单机缓存到分布式缓存架构

1、缓存定义



高速数据存储层,提高程序性能


2、为什么要用缓存(读多写少,高并发)



1、提高读取吞吐量

2、提升应用程序性能

3、降低数据库成本

4、减少后端负载

5、消除数据库热点

6、可预测的性能


3、缓存分类



3.1、单机缓存(localCache)


实现方案


1、基于JSR107规范自研(了解即可):


1、Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

2、CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。

3、CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

4、Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。

5、Entry是一个存储在Cache中的key-value对。

每一个存储在Cache中的条目有一个定义的有效期,即Expiry Duration。

一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。


2、基于ConcurrentHashMap实现数据缓存


3.2、分布式缓存(redis、Memcached)


4、单机缓存



1、自己实现一个单机缓存


创建缓存类


/**
 * @author yinfeng
 * @description 本地缓存实现:用map实现一个简单的缓存功能
 * @since 2022/2/8 13:54
 */
public class MapCacheDemo {
    /**
     * 在构造函数中,创建了一个守护程序线程,每5秒扫描一次并清理过期的对象
     */
    private static final int CLEAN_UP_PERIOD_IN_SEC = 5;
    /**
     * ConcurrentHashMap保证线程安全的要求
     * SoftReference <Object>  作为映射值,因为软引用可以保证在抛出OutOfMemory之前,如果缺少内存,将删除引用的对象。
     */
    private final ConcurrentHashMap<String, SoftReference<CacheObject>> cache = new ConcurrentHashMap<>();
    public MapCacheDemo() {
        //创建了一个守护程序线程,每5秒扫描一次并清理过期的对象
        Thread cleanerThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(CLEAN_UP_PERIOD_IN_SEC * 1000);
                    cache.entrySet().removeIf(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(CacheObject::isExpired).orElse(false));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        cleanerThread.setDaemon(true);
        cleanerThread.start();
    }
    public void add(String key, Object value, long periodInMillis) {
        if (key == null) {
            return;
        }
        if (value == null) {
            cache.remove(key);
        } else {
            long expiryTime = System.currentTimeMillis() + periodInMillis;
            cache.put(key, new SoftReference<>(new CacheObject(value, expiryTime)));
        }
    }
    public void remove(String key) {
        cache.remove(key);
    }
    public Object get(String key) {
        return Optional.ofNullable(cache.get(key)).map(SoftReference::get).filter(cacheObject -> !cacheObject.isExpired()).map(CacheObject::getValue).orElse(null);
    }
    public void clear() {
        cache.clear();
    }
    public long size() {
        return cache.entrySet().stream().filter(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(cacheObject -> !cacheObject.isExpired()).orElse(false)).count();
    }
    /**
     * 缓存对象value
     */
    private static class CacheObject {
        private Object value;
        private final long expiryTime;
        private CacheObject(Object value, long expiryTime) {
            this.value = value;
            this.expiryTime = expiryTime;
        }
        boolean isExpired() {
            return System.currentTimeMillis() > expiryTime;
        }
        public Object getValue() {
            return value;
        }
        public void setValue(Object value) {
            this.value = value;
        }
    }
}


写个main方法测试一下

public static void main(String[] args) throws InterruptedException {
        MapCacheDemo mapCacheDemo = new MapCacheDemo();
        mapCacheDemo.add("uid_10001", "{1}", 5 * 1000);
        mapCacheDemo.add("uid_10002", "{2}", 5 * 1000);
        System.out.println("从缓存中取出值:" + mapCacheDemo.get("uid_10001"));
        Thread.sleep(5000L);
        System.out.println("5秒钟过后");
        // 5秒后数据自动清除了
        System.out.println("从缓存中取出值:" + mapCacheDemo.get("uid_10001"));
    }

2、谷歌guava cache缓存框架


2.1、简介


Guava Cache是一个内存缓存模块,用于将数据缓存到jvm内存中,是单个应用运行时的本地缓存,他不将数据放到文件或外部服务器。


2.2 简单使用


/**
 * @author yinfeng
 * @description guava测试,https://github.com/google/guava
 * @since 2022/2/8 14:13
 */
public class GuavaCacheDemo {
    public static void main(String[] args) throws ExecutionException {
        //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
        LoadingCache<String, User> userCache
                //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                = CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后8秒钟过期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                //设置写缓存后1秒钟刷新
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(100)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(notification -> System.out.println(notification.getKey() + " 被移除了,原因: " + notification.getCause()))
                //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(
                        new CacheLoader<String, User>() {
                            @Override
                            public User load(String key) {
                                System.out.println("缓存没有时,从数据库加载" + key);
                                // TODO jdbc的代码~~忽略掉
                                return new User("yinfeng" + key, key);
                            }
                        }
                );
        // 第一次读取
        for (int i = 0; i < 20; i++) {
            User user = userCache.get("uid" + i);
            System.out.println(user);
        }
        // 第二次读取
        for (int i = 0; i < 20; i++) {
            User user = userCache.get("uid" + i);
            System.out.println(user);
        }
        System.out.println("cache stats:");
        //最后打印缓存的命中率等 情况
        System.out.println(userCache.stats().toString());
    }
    @Data
    @AllArgsConstructor
    public static class User implements Serializable {
        private String userName;
        private String userId;
        @Override
        public String toString() {
            return userId + " --- " + userName;
        }
    }
}


5、分布式缓存



5.1 redis


5.1.1 介绍


Redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API。

本质是客户端-服务端应用软件程序。


特点是使用简单,性能强悍,功能应用场景丰富。



5.1.2通用命令


image.png


5.1.3 数据结构


1. String


定义


String数据结构是简单的key-value类型,value其实不仅是String,也可以是数字。

使用场景:微博数,粉丝数(常规计数)


常用命令


image.png


2. List


定义


List就是链表,依赖于链表结构

使用场景:微博的关注列表,粉丝列表


常用命令


image.png


3. Set


定义


Set就是一个集合,集合的概念就是一堆不重复值的组合。利用Reds提供的Set数据结构,可以存储一些集合性的数据。

使用场景:实现如共同关注,共同喜好,二度好友


常用命令


image.png


4. Sorted set


定义


Sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。


使用场景:排行榜、按照用户投票和时间排序


常用命令


image.png


5. Hash


定义


Hash是一个sting类型的field和value的映射表

使用场景:存储部分变更数据,如用户信息


常用命令


image.png


6. GEO


定义


GEO3.2版本开始对GEO(地理位置)的支持

使用场景:LBS应用开发


常用命令


image.png


7. Stream


定义


Stream5.0版本开始的新结构“流”

使用场景:消费者生产者场景(类似MO)


常用命令


image.png


5.1.4 持久化机制


1. 介绍


redis的数据都存放在内存中,如果没有配置持久化,重启后数据就全丢失,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据


2. 持久化方式


RDB持久化:RDB持久化方式能够在指定的时间间隔对你的数据进行快照存储

AOF持久化: AOF持久化方式记录每次对服务器写的操作,当服务器重后的时候会重新执行这些命令来恢复原始的数据


3.RDB方式


客户端直接通过命令BGSAVE或者SAVE来创建一个内存快照:

  1.BGSAVE调用fork来创建一个子进程,子进程负责将快照写入磁盘,而父进程仍然继续处理命令。

  2.SAVE执行SAVE命令过程中,不再响应其他命令。


在redis.conf中调整save配置选项,当在规定的时间内,redis发生了写操作的个数满足条件会触发BGSAVE命令


#900秒之内至少一次写操作
save 900 1
#300秒之内至少发生10次写操作
save 300 10


优缺点


image.png


4. AOF持久化方式


开启AOF持久化


appendonty yes


AOF策略调整


#每次有数据修改发生时都会写入AOF文件
appendfsync always
#每秒钟同步一次,该策略为AOF的默认策略
appendfsync everysec
#从不同步。高效但是数据不会被持久化
appendfsync no


优点


image.png


5.1.5 内存管理


1、内存分配


不同数据类型的大小限制:


Strings类型:一个Strings类型的Value最大可以存储512M。

Lists类型:list的元素个数最多为2^32-1个

Sets类型:元素个数最多为2^32-1个

Hashes类型:键值对个数最多为2^32-1个


最大内存控制:


maxmemory 最大内存阈值

maxmemory-policy 到达阈值的执行策略


2、内存压缩

#配置字段最多512个
hash-max-zipmap-entries 512
#配置value最大为64字节
hash-max-zipmap-value 64
#配置元素个数最多512个
lst-max-zipmap-entries 512
#配置value最大为64字节
list-max-zipmap-value 64
#配置元素个数最多512个
set-max-zipmap-entries 512

大小超出压缩范围,溢出后redis将自动将其转换为正常大小


3、过期数据的处理策略


主动处理(redis主动触发检测key足否过期)每秒抗行10次。过程如下:


  1.从具有相关过期的key集合中测试20个随机key

  2.删除找到的所有已过期key

  3.如果超过25%的key已过期,请从步骤1重新开始


被动处理:


  1.每次访问key的时候,发现超时后被动过期,清理掉


数据恢复阶段过期数据的处理策略:


RDB方式:过期的Key不会被持久化到文件中。载入时过期的key,会通过redis的主动和被动方式清理掉。

AOF方式:每次遇到过期的key,redis会追加一条DEL命令到AOF文件,也就是说只要我们顺序载入执行AOF命令文件就会删除过期的key

注意:过期数据的计算和计算机本身的时间是有直接联系的!


Redis内存回收策略:


配置文件中设置:maxmemory-poIicy noeviction

命令动态调整:config set maxmemory-policy noeviction


image.png


4、LRU算法


LRU(最近最少使用):根据数据的历史访问记录来进行沟汰数据


核心思想:如果数据最近被访问过,那么将来被访问的几率也更高。

注意:Redis的LRU算法并非完整的实现,完整的LRU实现需要太多的内存。

方法:通过对少量keys进行取样(50%),然后回收其中一个最好的key。

配置方式:maxmemory-samples 5


5、LFU算法


LFU:根据数据的历史访问频率来沟汰数据


核心思想:如果数据过去被访问多次,那么将来被访问的频率也更高。

启用LFU算法后,可以使用热点数据分析功能。


5.1.6 主从复制


1、介绍


为什么要主从复制


redis-server单点故障

单节点QPS有限


应用场景分析


读写分离场景,规避redis单机瓶颈

故障切换,master出问题后还有slave节点可以使用


2、搭建主从复制


主Redis Server以普通模式启动,主要是启动从服务器的方式


1.命令行


#连接需要实现从节点的rediS,执行下面的命令
slaveof [ip] [port]


2.redis.conf配置文件


#配置文件中增加
slaveof [ip] [port]
#从服务器是否只读(默认yes)
slave-read-only yes


3.退出主从集群的方式


slaveof no one


3、检查主从复制


#redis客户端执行
info replication


4、主从复制流程


从服务器通过psync命令发送服务器已有的同步进度(同步源ID,同步进度offset)

master收到请求,同步源为当前master,则根据偏移量增量同步

同步源非当前master,则进入全量同步:maser生成rdb,传输到slave,加载到slave内存


5、主从复制核心知识


Redis默认使用异步复制,slave和master之间异步地确认处理的数据量

一个master可以拥有多个slave

Slave可以接受其他slave的连接。slave可以有下级sub slave

主从同步过程在master侧是非阻塞的

slave初次同步需要删除旧数据,加载新数据,会阻塞到来的连接请求


6、应用场景


主从复制可以用来支持读写分离

slave服务器设定为只读,可以用在数据安全的场景下。

可以使用主从复制来避免master持久化造成的开销。master关闭持久化,slave设置为不定期保存或开启AOF

注意:重新启动的master程序将从一个空数据集开始,如果一个slave试图与它同步,那么这个slave也会被清空。


7、注意事项


 1.读写分离场景:


数据复制延时导致读到过期数据或者读不到数据(网络原因,slave阻塞)

从节点故障(多个client如何迁移)


 2.全量复制情况下:


第一次建立主从关系或者runid不匹配会导致全量复制

故障转移的时候也会出现全量复制


 3.复制风暴:


master故障重启,如果slave节点过多,所有slave都要复制,对服务器的性能,网络的压力都有很大影响。

如果一个机器部署了多个master


 4.写能力有限


主从复制还是只有一台master,提供的写服务能力有限


 5.master故障情况下:


如果是mater无持久化,Slave开启持久化来保留数据的场展,建议不要配置redis自动重启。

启动redis自动重启,master启动后,无备份数据,可能导致集群数据丢失的情况


 6.带有效期的key:


Slave不会让key过期,而是等待master让key过期

在Lua脚本执行期间,不执行任何key过期操作


5.1.7 哨兵模式


1、哨兵(Sentinel)机制核心作用


image.png


2、核心运作流程


服务发现和健康检查流程


搭建redis主从集群 ==> 启动哨兵(客户端通过哨兵发现Redis实例信息) ==> 哨兵通过连接master发现主从集群内的所有实例信息 ==> 哨兵监控redis实例的健康状况


故障切换流程


哨兵一旦发现master不能正常提供服务,则通知给其他哨兵 ==> 当一定数量的哨兵都认为master挂了 ==> 选举一个哨兵作为故障转移的执行者 ==> 执行者在slave中选取一个作为新的master ==> 将其他slave重新设定为新master的从属


3、哨兵如何知道Redis主从信息


哨兵配置文件中,保存着主从集群中master的信息,可以通过info replication命令,进行主从信息自动发现。


4、什么是主观下线(sdown)


主观下线:单个哨兵自身认为redis实例已经不能提供服务

检测机制:哨兵向redis发送ping请求,+PONG,-LOADING,-MASTERDOWN三种情况视为正常,其他回复均视为无效

对应配置文件的配置项:sentinel down-after-milliseconds mymaster 1000


5、什么是客观下线(odown)


客观下线:一定数量值的哨兵认为master已经下线。

检测机制:当哨兵主观认为maser下线后,则会通过SENTINEL is-master-down-by-addr命令询问其他哨兵是否认为master已经下线,如果达成共识(达到quorum个数),就会认为master节点客观下线,开始故障转移流程

对应配置文件的配置项:sentinel monitor mymaster 1.0.0.1 6380 2


6、哨兵之间如何通信


 1.哨兵之间的自动发现:发布自己的信息,订阅其他哨兵消息(pub/sub)

 2.哨兵之间通过命令进行通信:直连发送命令

 3.哨兵之间通过订阅发布进行通信:相互订阅指定主题(pub/sub)


7、哨兵领导选举机制


基于Raft算法实现的选举机制,流程简述如下:


 1.拉票阶段:每个哨兵节点希望自己成为领导者;

 2.Sentinel节点收到拉票命令后,如果没有收到或同意过其他sentinel节点的请求,就同意该sentinel节点的请求(每个sentinel只持有一个同意票数)

 3.如果sentinel节点发现自己的票数已经超过一半的数值,那么它将成为领导者,去执行故障转移

 4.投票结束后,如果超过failover-timeout的时间内,没进行实际的故障转移操作,则重新拉票选举。


8、slave选举方案


slave节点状态 > 优先级 > 数据同步情况 > 最小的run id


9、最终主从切换的过程


针对即将成为master的slave节点,将其撒出主从集群,自动执行:slaveof NO ONE

针对其他slave节点,使它们成为新master的从属,自动执行:slaveof new_master_host new_master_port


10、哨兵服务部署方案


不推荐:一主一从,两个哨兵

推荐:一主两从,三个哨兵

redis集群非强一致:一主两从,网络分区下可能出现数据不一致或丢失。


5.1.8 redis集群分片存储


1、为什么要分片存储


redis的内存需求可能超过机器的最大内存。(一台机器不够用)


2、官方集群方案


redis cluster是redis的分布式集科解决方案,在3.0版本推出后有效地解决了redis分布式分面的需求,实现了数据在多个Redis节点之间自动分片,故障自动转移,扩容机制等功能。

主要基于CRC16(key) % 16384 计算出每个key对应的slot,然后根据redis集群中实例的预设槽slot(16384个)进行对应的操作,slot不存储数据,仅仅用来做片区划分。


3、搭建集群


 1.准备6个独立的redis服务

 2.通过redis-cli工具创建集群

 3.检验集群

 4.故障转移测试

 5.集群扩容

 6.集群节点删除


4、集群关心的问题


1.增加了slot槽的计算,是不是比单机性能差?


不是的,为了避免每次都需要服务器计算重定向,优秀的Java客户端都实现了本地计算,并且缓存服务器slots分配,有变动时再更新本地内容,从而避免了多次重定向带来的性能损耗。


2.redis集群大小,到底可以装多少数据?


理论是可以做到16384个槽,每个槽对应一个实例,但是redis宫方建议是最大1000个实例,因为存储已经足够大了。


3.集群节点间是怎么通信的?


每个Redis群集节点都有一个额外的TCP端口,每个节点使用TCP连接与每个其他节点连接。检测和故障转移这些步骤基本和哨兵模式类似。


4.ask和moved重定向的区别


重定向包括两种情况


若确定slot不属于当前节点,redis会返回moved。

若当前redis节点正在处理slot迁移,则代表此处请求对应的key暂时不在此节点,返回ask,告诉客户端本次请求重定向。


5.数据倾斜和访问倾斜的问题


倾斜导致集群中部分节点数据多,压力大。解决方案分为前期和后期:


前期是业务层面提前预测,哪些key是热点,在设计的过程中规避。

后期是slot迁移,尽量将压力分摊(slot调整有自动rebalance、reshard和手动)。


6.slot手动迁移怎么做?


 1.在迁移目的节点执行cluster setslot IMPORTING 命令,指明需要迁移的slot和迁移源节点。

 2.在迁移源节点执行cluster setslot MIGRATING 命令,指明需要迁移的slot和迁移目的节点。

 3.在迁移源节点执行cluster getkeysinslot获取该slot的key列表

 4.在迁移源节点执行对每个key执行migrate命令,该命令会同步把该key迁移到目的节点。

 5.在迁移源节点反复执行cluster getkeysinslo命令,直到该slot的列表为空。

 6.在迁移源节点和目的节点执行cluster setslot NODE ,完成迁移操作。


7.节点之间会交换信息,传递的消息包括槽的信息,带来带宽消耗。注意:避免使用大的一个集群,可以分多个集群。


8.Pub/Sub发布订阅机制:对集群内任意的一个节点执行pubish发布消息,这个消息会在集群中进行传播,其他节点都接收到发布的消息。


9.读写分离:


redis-cluster默认所有从节点上的读写,都会重定向到key对应槽的主节点上。

可以通过readonly设置当前连接可读,通过readwrite取消当前连接的可读状态。

注意:主从节点依然存在数据不一致的问题


5.1.9 redis监控


1、monitor命令


monitor是一个调试命令,返回服务器处理的每个命令。对于发现程序的错误非常有用。出于安全考虑,某些特殊管理命令CONFIG不会记录到MONITOR输出。


注意:运行一个MONITOR命令能够降低50%的吞吐量,运行多个MONITOR命令降低的吞吐量更多。


2、info命令


INFO命令以一种易于理解和阅读的格式,返回关于Redis服务器的各种信息和统计数值。


image.png


可以通过section返回部分信息,如果没有使用任何参数时,默认为detault。


3、图形化监控工具: Redis-Live


5.2 memcached入门


由于memcached慢慢淡出了人们的视野,使用的公司越来越少,所以这里只是做个入门介绍。


1、简介


是一个免费开源的、高性能的、具有分布式内存对象的缓存系统,它通过减轻数据库负载加速动态web应用。


本质上就是一个内存key-Value缓存

协议简单,使用的是基于文本行的协议

不支持数据的持久化,服务器关闭之后数据全部丢失

Memcached简洁而强大,便于快速开发,上手较为容易

没有安全机制


2、设计理念


   1.简单的键/值存储:服务器不关心你的数据是什么样的,只管数据存储


   2.服务端功能简单,很多逻辑依赖客户端实现


客户端专注如何选择读取或写入的服务器,以及无法联系服务器时要执行的操作。

服务器专注如何存储和管理何时清除或重用内存


   3.Memcached实例之间没有通信机制


   4.每个命令的复杂度为0(1):慢速机器上的查询应该在1ms以下运行。高端服务器的吞吐量可以达到每秒数百万


   5.缓存自动清除机制


   6.缓存失效机制


3、常用命令


image.png

4、客户端使用

客户端支持的特性:集群下多服务器选择,节点权重配置,失败/故障转移,数据压缩,连接管理


5、服务端配置

   1.命令行参数


查看memcached-h或man memcached获取最新文档


   2.init脚本


如果通过yum应用商店安装,可以使用/etc/sysconfig/memcached文件进行参数配置


   3.检查运行配置


stats settings查看运行中的memcached的配置


6、memcached性能


Memcached性能的关键是硬件,内部实现是hash表,读写操作都是0(1)。硬件好,几百万的OPS都是没问题的。


最大连接数限制:内部基于事件机制(类似JAVA NIO)所以这个限制和nio类似,只要内存,操作系统参数进行调整,轻松几十万。


集群节点数量限制:理论是没限制的,但是节点越多,客户端需要建立的连接就会越多。


注意:memcached服务端没有分布式的功能,所以不论是集群还是主从备份,都需要第三方产品支持。


7、服务器硬件需要

CPU要求:CPU占用率低,默认为4个工作线程


内存要求:


memcached内容存在内存里面,所有内存使用率高。

建议memcached实例独占服务器,而不是混用。

建议每个memcached实例内存大小都足一致的,如果不一致则需要进行权重调整


网络要求:


根据项目传输的内容来定,网络越大越好,虽然通常10M就够用了

建议:项目往memcached传输的内容保持尽可能的小


8、Memcached应用场景


   1.数据查询缓存:将数据库中的数据加载到memcached,提供程序的访问速度

   2.计数器的场景:通过incr/decr命令实现评论数量、点击数统计,操作次数等等场景。

   3.乐观锁实现:例如计划任务多实例部暑的场景下,通过CAS实现不重复执行

   4.防止重复处理:CAS命令


5.3 互联网高并发缓存架构


5.3.1 缓存架构分析图


image.png


5.3.2 缓存雪崩


定义:因为缓存服务挂掉或者热点缓存失效,从而导致所有请求都去查数据库,导致数据库连接不够用或者数据库处理不过来,从而导致整个系统不可用。


常用解决方案:


   1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

   2.缓存降级,直接返回错误码;

   3.加锁实现防止大量请求堆到数据库。

   4.设置热点数据永远不过期,防止了自动失效的情况,通过其他后台检查程序,防止缓存数据和数据库长期不同步


5.3.2 缓存击穿


定义:查询必然不存在的数据,请求透过Redis,直击数据库。


常用解决方案:


1.用户内容预生成。

2.访问频率限制。

3.缓存中无数据,也不查询数据库,直接返回错误码。

4.布隆过滤器


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
缓存 JavaScript 前端开发
Java 如何确保 JS 不被缓存
【10月更文挑战第19天】在 Java 中,可以通过设置 HTTP 响应头来确保 JavaScript 文件不被浏览器缓存。方法包括:1. 使用 Servlet 设置响应头,通过 `doGet` 方法设置 `Expires`、`Cache-Control` 和 `Pragma` 头;2. 在 Spring Boot 中配置拦截器,通过 `NoCacheInterceptor` 类和 `WebConfig` 配置类实现相同功能。这两种方法都能确保每次请求都能获取到最新的 JavaScript 内容。
|
21天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
60 10
|
2月前
|
缓存 NoSQL Java
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
缓存与分布式锁、Redisson分布式锁、缓存数据一致性【必须满足最终一致性】
120 14
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
|
1月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
62 4
|
1月前
|
缓存 JavaScript 前端开发
Java 如何确保 JS 不被缓存
大家好,我是 V 哥。本文探讨了 Java 后端确保 JavaScript 不被缓存的问题,分析了文件更新后无法生效、前后端不一致、影响调试与开发及安全问题等场景,并提供了使用版本号、设置 HTTP 响应头、配置静态资源缓存策略和使用 ETag 等解决方案。最后讨论了缓存的合理使用及其平衡方法。
消息中间件 缓存 监控
120 0
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
|
3月前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
|
3月前
|
缓存 前端开发 Java
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(-Xms512m -Xmx1204m)?
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤