offer来了(原理篇)学习笔记-第8章分布式缓存的原理及应用

缓存指将需要频繁访问的数据存放在内存中以加快用户访问速度的一种技术。缓存分进程级缓存分布式缓存,进程级缓存指将数据缓存在服务内部,通过Map、List等结构实现存储;分布式缓存指将缓存数据单独存放在分布式系统中,以便于缓存的统一管理和存取。常用的分布式缓存系统有Ehcache、Redis和Memcached。

分布式缓存介绍

当我们需要频繁访问一些基本数据,比如用户信息、系统字典信息等热数据时,为了加快系统的访问速度,往往会选择把数据缓存在内存中,这样用户再次访问数据时直接从内存中获取数据即可,不用频繁查询数据库,这不但缩短了系统的访问时间,还有效降低了数据库的负载,具体流程如图所示。在用户有写请求数据时先将数据写入数据库,然后写入缓存,用户再次访问该数据时会尝试直接从缓存中获取,如果在缓存中没有找到数据,则从数据库中查询并将结果返回给用户,同时将查询结果缓存起来以方便下次查询。

分布式缓存是相对于传统的进程内缓存而言的,对于传统的单点Web系统一般使用进程内缓存即可,而在微服务架构下往往需要一个分布式缓存来实现跨服务的缓存系统,如图所示。用户访问的数据库是被部署在多个服务器节点的集群数据库,缓存是被部署在多个服务器节点的分布式缓存,同时缓存之间有数据备份,在一个节点出问题后,分布式缓存会将用户的请求转发到其他备份节点以保障业务的正常运行。

Ehcache的原理及应用

Ehcache是基于Java实现的一套简单、高效、线程安全的缓存管理类库。Ehcache提供了内存、磁盘文件及分布式存储方式等多种灵活的Cache管理方案,特点是快速、轻量、可伸缩、操作灵活、支持持久化等。

Ehcache的原理

Ehcache是基于Java实现的高效缓存框架,其内部采用多线程实现,采用LinkedHashMap存储元素,同时支持将数据持久化到物理磁盘上。

Ehcache的特点

  • 快速:Ehcache内部采用多线程机制实现,数据存取性能高。
  • 轻量:Ehcache的安装包大小只有1.6MB,可以被快速、方便地继承到系统中。
  • 可伸缩:Ehcache缓存在内存和硬盘的存储可以伸缩到数几十GB,可轻松应对大数据场景。
  • 操作灵活:Ehcache提供了丰富的API接口,可实现基于主键、条件进行数据读取等。同时,Ehcache支持在运行时修改缓存配置(存活时间、空闲时间、内存的最大数据、磁盘的最大数量),提高了系统维护的灵活性。
  • 支持多种淘汰算法:Ehcache支持最近最少被使用、最少被使用和先进先出缓存策略。
  • 支持持久化:Ehcache支持将缓存数据持久化到磁盘上,在机器重启后从磁盘上重新加载缓存数据。

Ehcache的架构

**Ehcache在架构上由Cache Replication、In-Process API和Core组成。**其中,Cache Replication存储缓存副本;In-Process API封装操作缓存数据的API,包括Hibernate API、JMX API、ServletCacheing Filter API等;Core是Ehcache的核心部分,包括用于管理缓存的CacheManger、用于存储缓存的Store和用于操作缓存的Cache API等;NetWork APIs提供RESTful API、SOAP API等WebAPI接口。

Ehcache的扩展模块

Ehcache是开放的缓存系统,除自身的实现外还有其他扩展模型,这些扩展模型是相互独立的库,每个都为Ehcache添加新的功能:

Ehcache的应用

在Spring Boot中使用Ehcache组件比较简单,分为引入jar包、配置ehcache.xml和使用Ehcache缓存,具体实现如下:

  1. 引入jar包。按照如下代码在Spring Boot项目中引入ehcache-3.7.0的jar包依赖:
<dependency>
	<groupId>org.ehcache</groupId>
	<artifactId>ehcache</artifactId>
	<version>3.7.0</version>
</dependency>
  1. 设置ehcache.xml。在项目resource的目录下新建ehcache.xml配置文件,并加入以下配置:
<? xml  version="1.0"  encoding="UTF-8"? >
<ehcache>
    <cache  name="user"  eternal="true"
                        overflowToDisk="true"  maxElementsInMemory="1000"/>
</ehcache>

以上代码在ehcache.xml配置文件中声明了一个名称为user的缓存,其中eternal=true表示缓存对象永不过期,maxElementsInMemory表示内存中该Cache可存储最大的数据量,overflowToDisk=true表示在内存缓存的对象数量达到了maxElementsInMemory界限后,会把溢出的对象写到磁盘缓存中。注意:如果需要将缓存的对象写入磁盘中,则该对象必须实现了Serializable接口。

  1. 使用Ehcache缓存:
@Service
public class UserService {
  private static final Logger logger  =
                        LoggerFactory.getLogger(UserService.class);
  @Autowired
  UserRepository userRepository;
  @CachePut(value = "user", key = "#user.id"public User save(User user) {
      User userAdd = userRepository.save(user);
      logger.info("user  info  add  db  and  ehcache, key:"  +  userAdd.getId());
      return userAdd;
  }
  @Cacheable(value = "user", key = "#user.id"public User findOne(String id) {
      User userSearch = userRepository.getOne(id);
      return userSearch;
  }
}

以上代码定义了名为UserService的类,同时定义了保存用户数据的方法save()和查找用户数据的方法findOne(),并分别在方法上通过@Cacheable(value = “user”, key="#user.id")开启Ehcache缓存。

在用户调用save()保存数据时会在Ehcache内存中也保存一份User对象,其key为User对象的id属性。在用户调用findOne()查询该数据时,首先会去Ehcache缓存中查找数据,如果在缓存中存在该数据,则将该数据返回,如果在缓存中不存在该数据,则会去数据库中查询并返回结果。

Redis的原理及应用

Redis是一个开源(BSD许可)的内存中的数据结构存储系统,可以用作数据库、缓存和消息中间件,支持多种类型的数据结构,例如String(字符串)、Hash(散列)、List(列表)、Set(集合)、ZSet(有序集合)、Bitmap(位图)、HyperLogLog(超级日志)和Geospatial(地理空间)。Redis内置了复制、Lua脚本、LRU驱动事件、事务和不同级别的磁盘持久化,并通过Redis哨兵(Sentinel)模式和集群模式(Cluster)提供高可用性(High Availability)。

Redis的原理

Redis不但支持丰富的数据类型,还支持分布式事务数据分片数据持久化等功能,是分布式系统中不可或缺的内存数据库服务

Redis的数据类型

Redis支持String、Hash、List、Set、ZSet、Bitmap、HyperLogLog和Geospatial这8种数据类型。

  1. String:String是Redis基本的数据类型,一个key对应一个value。String类型的值最大能存储512MB数据。Redis的String数据类型支持丰富的操作命令,常用的String操作命令如表所示。
  1. Hash:Redis Hash是一个键值(key->value)对集合。Redis的Hash列表支持的操作如表所示。
  1. List:Redis List是简单的字符串列表,按照插入顺序排序。我们可以添加一个元素到列表的头部(左边)或者尾部(右边)。列表最多可存储231-1(4294967295≈4亿多)个元素。List列表常用的操作如表所示。
  1. Set:Set是String类型的无序集合。集合是通过散列表实现的,所以添加、删除、查找的复杂度都是O(1)。Set支持的操作如表所示。
  1. ZSet:Redis ZSet和Set一样也是String类型元素的集合,且不允许有重复的成员,不同的是,每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序的。Redis ZSet支持的操作如表所示。
  1. Bitmap:通过操作二进制位记录数据。Redis Bitmap支持的操作如表所示。
  1. HyperLogLog:被用于估计一个Set中元素数量的概率性的数据结构。Redis HyperLogLog支持的操作如表所示。
  1. Geospatial:用于地理空间关系计算,支持的操作如表所示。

Redis管道

Redis是基于请求/响应协议的TCP服务。在客户端向服务器发送一个查询请求后,需要监听Socket的返回,该监听过程一直阻塞,直到服务器有结果返回。由于Redis集群是部署在多个服务器上的,所以Redis的请求/响应模型在每次请求时都要跨网络在不同的服务器之间传输数据,这样每次查询都存在一定的网络延迟(服务器之间的网络延迟一般在20ms左右)。由于服务器一般采用多线程处理业务,并且内存操作效率很高,所以如果一次请求延迟20ms,则多次请求的网络延迟会不断累加。也就是说,在分布式环境下,Redis的性能瓶颈主要体现在网络延迟上。Redis请求/响应模型的数据请求、响应流程如图所示。

Redis的管道技术指在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术能减少客户端和服务器交互的次数,将客户端的请求批量发送给服务器,服务器针对批量数据分别查询并统一回复,能显著提高Redis的性能。Redis管道模型的数据请求流程如图所示。

Redis管道技术基于Spring Boot的使用如下:

	//4:Redis Pipeline执行批量操作,将操作结果返回在list中
    List<Object>  list  =  redisTemplate.executePipelined(
    new  RedisCallback<Object>()  {
      @Nullable
      @Override
      public  Object  doInRedis(RedisConnection  connection)
                                throws  DataAccessException  {
          connection.openPipeline(); //1:打开Pipeline
          for (int i = 0; i < 10000; i++) {//2:执行批量操作
              String  key  =  "key_"  +  i;
              String  value  =  "value_"  +  i;
              connection.set(key.getBytes(), value.getBytes());
          }
          return null; //3:结果返回:这里返回null,
                      //4:redisTemplate会将最终结果汇总在外层的list中
      }
    });
    //5:查看管道批量操作返回的结果
    for  (Object  item:  list)  {
      System.out.println(item);
    }

以上代码使用redisTemplate.executePipelined()在Spring Boot中实现了基于Redis的管道操作。具体的步骤为:新建RedisCallback对象并覆写doInRedis();在doInRedis()中通过connection.openPipeline()开启Pipeline操作;在for循环中批量进行Redis数据写操作;最终将批量操作结果返回。

Redis的事务

Redis支持分布式环境下的事务操作,其事务可以一次执行多个命令,事务中的所有命令都会序列化地顺序执行。事务在执行过程中,不会被其他客户端发送来的命令请求打断。服务器在执行完事务中的所有命令之后,才会继续处理其他客户端的其他命令。Redis的事务操作分为开启事务、命令入队列、执行事务三个阶段。Redis的事务执行流程如下,如图所示。

  1. 事务开启:客户端执行Multi命令开启事务。
  2. 提交请求:客户端提交命令到事务。
  3. 任务入队列:Redis将客户端请求放入事务队列中等待执行。
  4. 入队状态反馈:服务器返回QURUD,表示命令已被放入事务队列。
  5. 执行命令:客户端通过Exec执行事务。
  6. 事务执行错误:在Redis事务中如果某条命令执行错误,则其他命令会继续执行,不会回滚。可以通过Watch监控事务执行的状态并处理命令执行错误的异常情况。
  7. 执行结果反馈:服务器向客户端返回事务执行的结果。

Redis事务的相关命令有Multi、Exec、Discard、Watch和Unwatch。

Redis事务基于Spring Boot的使用如下:

public  void  transactionSet(Map<String, Object>  commandList){
      //1:开启事务权限
      redisTemplate.setEnableTransactionSupport(true);
      try  {
          //2:开启事务
          redisTemplate.multi();
          //3:执行事务命令
          for(Map.Entry<String,  Object>  entry  :  commandList.entrySet()){
              String  mapKey  =  entry.getKey();
              Object  mapValue  =  entry.getValue();
              redisTemplate.opsForValue().set(mapKey,  mapValue);
          }
          //4:成功就提交
          redisTemplate.exec();
      }  catch  (Exception  e)  {
          //5:失败就回滚
          redisTemplate.discard();
      }
}

以上代码定义了名为transactionSet()的Redis事务操作方法,该方法接收事务命令commandList并以事务命令列表在一个事务中执行。具体步骤为:开启事务权限、开启事务、执行事务命令、提供事务和回滚事务

Redis发布、订阅

Redis发布、订阅是一种消息通信模式:发送者(Pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息。Redis客户端可以订阅任意数量的频道,发送者也可以向任意频道发送数据。图展示了1个发送者(pub1)、1个频道(channe0)和3个订阅者(sub1、sub2、sub3)的关系。由于3个订阅者sub1、sub2、sub3都订阅了频道channel0,在发送者pub1向频道channel0发送一条消息后,这条消息就会被发送给订阅它的三个客户端。

Redis常用的消息订阅与发布命令如表所示。

Redis集群数据复制的原理

Redis提供了复制功能,可以实现在主数据库(Master)中的数据更新后,自动将更新的数据同步到从数据库(Slave)。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

Redis的主从数据复制原理如下,如图所示。

  1. 一个从数据库在启动后,会向主数据库发送SYNC命令。
  2. 主数据库在接收到SYNC命令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。在该持久化过程中会生成一个.rdb快照文件。
  3. 在主数据库快照执行完成后,Redis会将快照文件和所有缓存的命令以.rdb快照文件的形式发送给从数据库。
  4. 从数据库收到主数据库的.rdb快照文件后,载入该快照文件到本地。
  5. 从数据库执行载入后的.rdb快照文件,将数据写入内存中。以上过程被称为复制初始化。
  6. 在复制初始化结束后,主数据库在每次收到写命令时都会将命令同步给从数据库,从而保证主从数据库的数据一致。

在Redis中开启复制功能时需要在从数据库配置文件中加入如下配置,对主数据库无须进行任何配置:

#slaveof master_address master_port
slaveof 127.0.0.1 9000
#如果master有密码,则需要设置masterauth
mastauth=123

在上述配置中,slaveof后面的配置分别为主数据库的IP地址和端口,在主数据库开启了密码认证后需要将masterauth设置为主数据库的密码,在配置完成后重启Redis,主数据库上的数据就会同步到从数据库上。

Redis的持久化

Redis支持RDBAOF两种持久化方式。

  • RDB(Redis DataBase):RDB在指定的时间间隔内对数据进行快照存储。RDB的特点在于:文件格式紧凑,方便进行数据传输和数据恢复;在保存.rdb快照文件时父进程会fork出一个子进程,由子进程完成具体的持久化工作,所以可以最大化Redis的性能;同时,与AOF相比,在恢复大的数据集时会更快一些。
  • AOF(Append Of Flie):AOF记录对服务器的每次写操作,在Redis重启时会重放这些命令来恢复原数据。AOF命令以Redis协议追加和保存每次写操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。AOF的特点有:可以使用不同的fsync策略(无fsync、每秒fsync、每次写的时候fsync),只有某些操作追加命令到文件中,操作效率高;同时,AOF文件是日志的格式,更容易被操作。

Redis的集群模式及工作原理

Redis有三种集群模式:主从模式、哨兵模式和集群模式。

  • 主从模式:所有的写请求都被发送到主数据库上,再由主数据库将数据同步到从数据库上。主数据库主要用于执行写操作和数据同步,从数据库主要用于执行读操作缓解系统的读压力。(Redis的一个主库可以拥有多个从库,从库还可以作为其他数据库的主库。如图所示,Master的从库有Slave-0和Slave-1,同时Slave-1作为Slave-1-0和Slave-1-1的主库。)
  • 哨兵模式:在主从模式上添加了一个哨兵的角色来监控集群的运行状态。哨兵通过发送命令让Redis服务器返回其运行状态。哨兵是一个独立运行的进程,在监测到Master宕机时会自动将Slave切换成Master,然后通过发布与订阅模式通知其他从服务器修改配置文件,完成主备热切。
  • 集群模式:Redis集群实现了在多个Redis节点之间进行数据分片和数据复制。基于Redis集群的数据自动分片能力,我们能够方便地对Redis集群进行横向扩展,以提高Redis集群的吞吐量。基于Redis集群的数据复制能力,在集群中的一部分节点失效或者无法进行通信时,Redis仍然可以基于副本数据对外提供服务,这提高了集群的可用性。

Redis集群遵循如下原则:

  • 所有Redis节点彼此都通过PING-PONG机制互联,内部使用二进制协议优化传输速度和带宽。
  • 在集群中超过半数的节点检测到某个节点Fail后将该节点设置为Fail状态。
  • 客户端与Redis节点直连,客户端连接集群中任何一个可用节点即可对集群进行操作。
  • Redis-Cluster把所有的物理节点都映射到0~16383的slot(槽)上,Cluster负责维护每个节点上数据槽的分配。Redis的具体数据分配策略为:在Redis集群中内置了16384个散列槽;在需要在Redis集群中放置一个Key-Value时,Redis会先对Key使用CRC16算法算出一个结果,然后把结果对16384求余数,这样每个Key都会对应一个编号为0~16383的散列槽;Redis会根据节点的数量大致均等地将散列槽映射到不同的节点。

Redis的应用

安装Redis

Redis的安装分Redis单机版安装、Redis主从模式安装、Redis哨兵模式安装和Redis集群模式安装。下面介绍Redis集群模式的安装,该模式也是最复杂的一种模式。

  1. 下载Redis软件。执行以下命令从官网下载稳定版本的Redis并解压
wget http://download.redis.io/releases/redis-stable.tar.gz
tar -zxvf redis-stable.tar.gz
  1. 编译和安装。执行以下命令进入Redis的安装目录编译和安装Redis
cd redis-stable
make && make install
  1. 创建Redis节点。执行以下命令在Redis根目录下创建节点目录(以上命令分别建立了7000、7001、7002、7003、7004、7005共6个目录,用于存放6个节点的配置文件信息。将redis.conf文件分别复制到7000、7001、7002、7003、7004、7005目录下,并修改端口号和cluster-config-file。)
mkdir cluster-cluster
cd cluster-cluster
mkdir 7000 7001 7002 7003 7004 7005
  1. 配置Redis集群。分别修改7000、7001、7002、7003、7004、7005目录下的redis.conf配置文件(在以上配置中,port用于指定Redis服务端口,cluster-enabled用于设置Redis为集群模式,cluster-config-file用于设置集群配置文件)
#Redis端口:7000 7001 7002 7003 7004 7005
port 7000
#开启Redis集群模式
cluster-enabled yes
#集群的配置文件地址
cluster-config-file nodes_7000.conf
cluster-node-timeout 5000
appendonly yes
  1. 启动节点。执行以下命令分别启动6个节点
redis-server redis_cluster/7000/redis.conf
redis-server redis_cluster/7001/redis.conf
redis-server redis_cluster/7002/redis.conf
redis-server redis_cluster/7003/redis.conf
redis-server redis_cluster/7004/redis.conf
redis-server redis_cluster/7005/redis.conf
  1. 创建集群。执行以下命令创建集群(在以上命令中,cluster表示该命令是集群相关的命令;create表示创建一个集群,create后面的参数为参与集群创建的节点;cluster-replicas表示集群中的副本数。在创建集群的过程中命令行会列出集群的配置让我们确认,确认时输入yes即可。)
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1

这样便在一个服务器上安装了一个Reds集群,在该集群中共有6个节点,节点的IP地址为127.0.0.1,节点的端口号分别为7000、7001、7002、7003、7004和7005。

应用Redis SpringBoot

在Spring Boot中使用Redis的步骤为:引入jar包、配置application.properties,以及配置和使用RedisTemplate。

  1. 引入jar包。按照如下代码在Spring Boot项目中加入Redis的jar包依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置application.properties:在resource目录的application.properties加入以下Redis配置
#启动Redis命令:redis-server
# Redis的数据库索引(默认为0)
spring.redis.database=0
# Redis的服务器地址
#spring.redis.host=127.0.0.1
# Redis的服务器连接端口
#spring.redis.port=7000
# Redis的服务器连接密码(默认为空)
spring.redis.password=
# 连接池的最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=2000
# 连接池的最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池的最大空闲连接
spring.redis.jedis.pool.max-idle=100
# 连接池的最小空闲连接
spring.redis.jedis.pool.min-idle=50
# 连接超时时间(毫秒)
spring.redis.timeout=1000
#哨兵模式配置
#spring.redis.sentinel.master=mymaster
#spring.redis.sentinel.nodes=127.0.0.1:9000
#集群模式配置spring.redis.cluster.nodes=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127. 0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005

在以上配置中,spring.redis.cluster.nodes为Redis集群节点的服务地址,在多个服务地址之间使用逗号隔开;spring.redis.password为Redis服务密码,如果没有密码,则将其设置为空即可。

需要注意的是,以上是集群模式下的Redis配置,如果Redis是主从模式,则将spring.redis.cluster.nodes地址修改为主从节点的服务地址即可;如果是哨兵模式,则注释掉spring.redis.cluster.nodes配置,在spring.redis.sentinel.master和spring.redis.sentinel.nodes中分别配置哨兵的名称和哨兵的节点即可;如果是单机模式,则注释掉spring.redis.sentinel.nodes的配置,通过spring.redis.host配置Redis服务的地址,并通过spring.redis.port配置Redis服务的端口即可。

  1. 配置RedisTemplate。Spring Boot默认配置了RedisTemplate,在应用时注入、使用即可,也可以创建自定义的RedisTemplate。
@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)
public  class  RedisConfig  {
    @Bean
    public  RedisTemplate<String,  Serializable>
      redisCacheTemplate(LettuceConnectionFactory  redisConnectionFactory)  {
        RedisTemplate<String,  Serializable>  template  =  new  RedisTemplate<>();
        template.setKeySerializer(new  StringRedisSerializer());
        template.setValueSerializer(new   GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return  template;
    }
}

以上代码定义了RedisConfig类,并通过@Configuration开启配置文件注解,通过@AutoConfigureAfter配置自动注解类。在RedisConfig类中定义了RedisTemplate用于对Redis数据库进行操作。

  1. 使用RedisTemplate。新建测试类,并在测试类中加入以下测试代码
@Autowired
 private  RedisTemplate  redisTemplate;
 @Test
 public  void  contextLoads()  {
  //1:Redis key-value插入
    redisTemplate.opsForValue().set("key", "value");
    //2:Redis根据key查询
    Object  result  =  redisTemplate.opsForValue().get("key");
    //3:Redis根据key删除
    redisTemplate.delete("key");
}

RedisTemplate基于Jedis对Redis数据库的操作进行了二次封装,使得操作Redis数据库更加方便。以上代码在测试类中依赖注入了RedisTemplate,并通过redisTemplate.opsForValue()实现了对Redis数据的插入、查询和删除操作。

分布式缓存设计的核心问题

分布式缓存设计的核心问题是以哪种方式进行缓存预热和缓存更新,以及如何优雅解决缓存雪崩、缓存穿透、缓存降级等问题。这些问题在不同的应用场景下有不同的解决方案,下面介绍常用的解决方案。

缓存预热

缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动加载、定时加载等方式。

缓存更新

缓存更新指在数据发生变化后及时将变化后的数据更新到缓存中。常见的缓存更新策略有以下4种。

  • 定时更新:定时将底层数据库内的数据更新到缓存中,该方法比较简单,适合需要缓存的数据量不是很大的应用场景。
  • 过期更新:定时将缓存中过期的数据更新为最新数据并更新缓存的过期时间。
  • 写请求更新:在用户有写请求时先写数据库同时更新缓存,这适用于用户对缓存数据和数据库的数据有实时强一致性要求的情况。
  • 读请求更新:在用户有读请求时,先判断该请求数据的缓存是否存在或过期,如果不存在或已过期,则进行底层数据库查询并将查询结果更新到缓存中,同时将查询结果返回给用户。

缓存淘汰策略

在缓存数据过多时需要使用某种淘汰算法决定淘汰哪些数据。常用的淘汰算法有以下几种。

  • FIFO(First In First Out,先进先出):判断被存储的时间,离目前最远的数据优先被淘汰。
  • LRU(Least Recently Used,最近最少使用):判断缓存最近被使用的时间,距离当前时间最远的数据优先被淘汰。
  • LFU(Least Frequently Used,最不经常使用):在一段时间内,被使用次数最少的缓存优先被淘汰。

缓存雪崩

缓存雪崩指在同一时刻由于大量缓存失效,导致大量原本应该访问缓存的请求都去查询数据库,而对数据库的CPU和内存造成巨大压力,严重的话会导致数据库宕机,从而形成一系列连锁反应,使整个系统崩溃。一般有以下3种处理方法。

  • 请求加锁:对于并发量不是很多的应用,使用请求加锁排队的方案防止过多请求数据库。
  • 失效更新:为每一个缓存数据都增加过期标记来记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存。
  • 设置不同的失效时间:为不同的数据设置不同的缓存失效时间,防止在同一时刻有大量的数据失效。

缓存穿透

缓存穿透指由于缓存系统故障或者用户频繁查询系统中不存在(在系统中不存在,在自然数据库和缓存中都不存在)的数据,而这时请求穿过缓存不断被发送到数据库,导致数据库过载,进而引发一连串并发问题。

比如用户发起一个userName为zhangsan的请求,而在系统中并没有名为zhangsan的用户,这样就导致每次查询时在缓存中都找不到该数据,然后去数据库中再查询一遍。由于zhangsan用户本身在系统中不存在,自然返回空,导致请求穿过缓存频繁查询数据库,在用户频繁发送该请求时将导致数据库系统负载增大,从而可能引发其他问题。常用的解决缓存穿透问题的方法有布隆过滤器cachenull策略

  • 布隆过滤器:指将所有可能存在的数据都映射到一个足够大的Bitmap中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被这个布隆过滤器拦截,从而避免对底层存储系统带来查询上的压力。
  • cache null策略:指如果一个查询返回的结果为null(可能是数据不存在,也可能是系统故障),我们仍然缓存这个null结果,但它的过期时间会很短,通常不超过5分钟;在用户再次请求该数据时直接返回null,而不会继续访问数据库,从而有效保障数据库的安全。其实cache null策略的核心原理是:在缓存中记录一个短暂的(数据过期时间内)数据在系统中是否存在的状态,如果不存在,则直接返回null,不再查询数据库,从而避免缓存穿透到数据库上。

缓存降级

缓存降级指由于访问量剧增导致服务出现问题(如响应时间慢或不响应)时,优先保障核心业务的运行,减少或关闭非核心业务对资源的使用。常见的服务降级策略如下。

  • 写降级:在写请求增大时,可以只进行Cache的更新,然后将数据异步更新到数据库中,保证最终一致性即可,即将写请求从数据库降级为Cache。
  • 读降级:在数据库服务负载过高或数据库系统故障时,可以只对Cache进行读取并将结果返回给用户,在数据库服务正常后再去查询数据库,即将读请求从数据库降级为Cache。这种方式适用于对数据实时性要求不高的场景,保障了在系统发生故障的情况下用户依然能够访问到数据,只是访问到的数据相对有延迟。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值