当前位置: 首页 > news >正文

【Java实战㊱】Spring Boot邂逅Redis:缓存加速的奇妙之旅

目录

  • 一、Redis 概述与环境准备
    • 1.1 Redis 核心特性
    • 1.2 Redis 环境搭建
    • 1.3 Redis 客户端工具
  • 二、Spring Boot 整合 Redis
    • 2.1 整合依赖导入
    • 2.2 Redis 配置
    • 2.3 RedisTemplate 使用
    • 2.4 StringRedisTemplate 与 RedisTemplate 对比
  • 三、Spring Boot 缓存机制
    • 3.1 缓存注解使用
    • 3.2 缓存管理器配置
    • 3.3 缓存实战案例
    • 3.4 缓存问题解决


一、Redis 概述与环境准备

1.1 Redis 核心特性

Redis 作为一款高性能的键值存储数据库,在当今的软件开发领域中占据着举足轻重的地位。它基于内存存储数据,这使得它在读写速度上远远超越了传统的磁盘数据库。根据官方基准测试,Redis 的读写性能可达 10 万 +/ 秒 ,如此高效的数据处理能力,为高并发场景下的应用提供了坚实的支持。

在数据结构方面,Redis 的表现十分出色。它支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)等。以电商应用为例,商品信息可以用哈希结构存储,其中商品 ID 作为键,商品的名称、价格、库存等属性作为哈希的字段和值;而用户的购物车列表则可以使用列表结构来实现,用户 ID 作为键,商品 ID 依次存入列表中。这样的设计,使得 Redis 能够灵活地满足各种复杂业务场景的需求。

持久化机制是 Redis 的另一大亮点。它提供了 RDB(Redis Database)和 AOF(Append Only File)两种持久化方式。RDB 通过快照的方式,将内存中的数据保存到磁盘上,生成的快照文件是一个紧凑的二进制文件,适合用于数据备份和恢复;AOF 则是通过记录每次写操作的命令来实现持久化,这种方式可以保证数据的完整性和一致性,即使在系统崩溃的情况下,也能通过重放 AOF 文件中的命令来恢复数据。

1.2 Redis 环境搭建

在 Windows 系统下搭建 Redis 环境,首先需要从 Redis 的 GitHub 地址(https://github.com/tporadowski/redis/releases)下载安装包。下载完成后,将压缩包解压到指定的目录,此时 Redis 就初步安装完成了。接下来,启动 Redis 的 Server,直接双击 redis-server.exe,当出现 “Server initialized” 和 “Ready to accept connections” 提示时,说明 Redis 的服务已正常启动。为了验证 redis-server 是否正常启动,可以双击 redis-cli.exe,然后输入 “PING” 命令,如果收到 “PONG” 的响应,则表示 Redis 服务运行正常。为了更方便地使用命令启动 Redis,还可以配置 Redis 的环境变量。右键点击 “此电脑”,选择 “属性”,再点击 “高级系统设置”,在弹出的窗口中点击 “环境变量” 按钮。新建一个系统变量,变量名为 “REDIS_HOME”,变量值为 Redis 的安装目录,比如 “C:\software\Redis-x64-5.0.14.1” (需根据实际安装目录填写)。然后编辑系统变量 “Path”,新建一个环境变量 “% REDIS_HOME%” ,点击确定保存设置。

在 Linux 系统中安装 Redis,以 Ubuntu 系统为例,首先要更新本地包信息,执行命令 “sudo apt-get update”。然后使用 apt 包管理器安装 Redis,执行 “sudo apt-get install redis-server redis-tools” ,安装完成后,Redis 服务会自动启动。可以通过命令 “sudo systemctl status redis-server.service” 来查看 Redis 服务的状态。如果需要通过源码编译安装,首先要下载 Redis 源码,访问 Redis 官方网站找到下载地址,比如使用命令 “wget https://download.redis.io/releases/redis-5.0.7.tar.gz”。下载完成后,使用 “tar -zxvf redis-5.0.7.tar.gz” 命令解压源码,进入解压后的目录,执行 “make” 命令进行编译。编译完成后,使用 “make install PREFIX=/usr/local/redis” 命令将 Redis 安装到指定目录。

1.3 Redis 客户端工具

Redis Desktop Manager(RDM)是一款广受欢迎的 Redis 客户端工具,它为用户提供了直观、便捷的图形化界面,大大简化了对 Redis 数据库的管理和操作。

在使用 RDM 连接 Redis 服务器时,首先要确保 Redis 服务器已经启动并运行。打开 RDM 软件后,点击界面左上角的 “添加连接” 按钮,在弹出的连接窗口中,需要填写以下关键信息:连接名称可以自定义,方便用户识别不同的连接;主机地址通常为 Redis 服务器的 IP 地址,如果是本地连接,一般为 “127.0.0.1” ;端口号默认是 6379,这是 Redis 的默认端口;如果 Redis 设置了密码,还需要在 “密码” 字段中输入正确的密码。填写完这些信息后,点击 “测试连接” 按钮,若显示连接成功,再点击 “保存” 按钮即可完成连接设置。

连接成功后,就可以通过 RDM 对 Redis 进行各种操作。在左侧的导航栏中,可以看到 Redis 数据库的信息,点击某个数据库后,右侧会显示该数据库中的键(key)。通过右键点击键,可以进行查看、编辑、删除等操作。例如,要查看某个键的值,只需点击该键,右侧窗口就会显示出对应的值;若要删除某个键,右键点击该键并选择 “Delete” 选项即可。RDM 还支持执行 Redis 命令,点击界面下方的 “命令行” 选项卡,即可直接输入 Redis 命令,如输入 “KEYS *” 可以获取所有的键,输入 “SET mykey “Hello, Redis!”” 可以新建一个键值对。

二、Spring Boot 整合 Redis

2.1 整合依赖导入

在 Spring Boot 项目中,若想整合 Redis,首先要在项目的构建文件(如 pom.xml,针对 Maven 项目)中导入spring-boot-starter-data-redis依赖。这个依赖是 Spring Boot 对 Redis 操作的核心依赖,它提供了一系列的工具类和接口,使得我们可以在 Spring Boot 应用中便捷地操作 Redis 数据库,极大地简化了开发流程。

在pom.xml文件中,添加如下依赖配置:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

当添加了这个依赖后,Maven 会自动下载该依赖及其所有相关的子依赖。在下载过程中,Maven 会从配置的远程仓库(如中央仓库)中获取所需的 JAR 包,并将它们添加到项目的类路径中。这就为后续在项目中使用 Redis 相关的功能奠定了基础,使得我们能够在代码中引入 Redis 的操作类和接口。

2.2 Redis 配置

完成依赖导入后,需要在application.yml文件中配置 Redis 的连接信息和序列化方式。下面是一个具体的配置示例:

spring:redis:host: localhostport: 6379password: database: 0lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: -1mstimeout: 3000msserializer:key: org.springframework.data.redis.serializer.StringRedisSerializervalue: org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer

在这个配置中:

  • host指定了 Redis 服务器的主机地址,这里配置为localhost,表示本地连接。如果 Redis 服务器部署在远程,需要填写对应的 IP 地址。
  • port指定了 Redis 服务器的端口号,默认是 6379。
  • password用于设置 Redis 的访问密码,如果 Redis 没有设置密码,这里可以留空。
  • database指定了要使用的 Redis 数据库索引,Redis 默认有 16 个数据库,编号从 0 到 15,这里选择使用第 0 个数据库。
  • lettuce.pool配置了 Lettuce 连接池的相关参数。max-active表示最大活跃连接数,这里设置为 8,即连接池最多可以同时创建 8 个活跃连接;max-idle表示最大空闲连接数,为 8;min-idle表示最小空闲连接数,是 0;max-wait表示最大等待时间,-1ms表示无限制等待。
  • timeout设置了连接超时时间,这里是 3000ms,即 3 秒。如果在 3 秒内无法建立与 Redis 服务器的连接,将会抛出异常。
  • serializer.key和serializer.value分别指定了键和值的序列化方式。键使用StringRedisSerializer进行序列化,保证键以字符串的形式存储在 Redis 中;值使用GenericJackson2JsonRedisSerializer进行序列化,将对象转换为 JSON 格式的字符串存储,这样在读取时能够方便地反序列化为对象。

2.3 RedisTemplate 使用

RedisTemplate是 Spring Data Redis 提供的用于操作 Redis 的核心类,它提供了丰富的方法来操作 Redis 中的各种数据结构。下面通过代码示例展示如何使用RedisTemplate对 String、Hash、List 等数据结构进行操作。

假设在 Spring Boot 项目中已经配置好了RedisTemplate,并通过依赖注入的方式将其引入到一个服务类中:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;@Service
public class RedisService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// String类型数据操作public void setString(String key, String value) {redisTemplate.opsForValue().set(key, value);}public String getString(String key) {return (String) redisTemplate.opsForValue().get(key);}// Hash类型数据操作public void putHash(String key, String hashKey, Object value) {redisTemplate.opsForHash().put(key, hashKey, value);}public Object getHash(String key, String hashKey) {return redisTemplate.opsForHash().get(key, hashKey);}public Map<Object, Object> entriesHash(String key) {return redisTemplate.opsForHash().entries(key);}// List类型数据操作public void leftPushList(String key, Object value) {redisTemplate.opsForList().leftPush(key, value);}public Object rightPopList(String key) {return redisTemplate.opsForList().rightPop(key);}public List<Object> rangeList(String key, long start, long end) {return redisTemplate.opsForList().range(key, start, end);}
}

在上述代码中:

  • setString方法使用opsForValue来设置 String 类型的数据,opsForValue专门用于操作 Redis 中的字符串类型数据,set方法将键值对存储到 Redis 中。
  • getString方法通过opsForValue的get方法获取指定键的字符串值。
  • putHash方法使用opsForHash来向 Hash 结构中存入数据,put方法将一个键值对(hashKey - value)存入到指定的 Hash 键(key)中。
  • getHash方法用于获取 Hash 结构中指定hashKey的值。
  • entriesHash方法可以获取整个 Hash 结构中的所有键值对,返回一个Map对象。
  • leftPushList方法使用opsForList将元素从列表的左侧插入,leftPush方法将一个元素添加到列表的头部。
  • rightPopList方法从列表的右侧弹出一个元素,rightPop方法移除并返回列表的最后一个元素。
  • rangeList方法用于获取列表中指定范围的元素,range方法返回从start索引到end索引(包括end)的所有元素。

2.4 StringRedisTemplate 与 RedisTemplate 对比

StringRedisTemplate和RedisTemplate都是 Spring Data Redis 提供的用于操作 Redis 的模板类,但它们在适用场景上存在一些差异。

StringRedisTemplate默认使用StringRedisSerializer进行序列化,这意味着它只能操作键值对都是字符串类型的数据。它在处理简单的字符串数据时非常方便,例如存储一些配置信息、简单的计数器等。由于它的序列化方式简单,直接将字符串存储到 Redis 中,所以在 Redis 客户端中查看数据时,数据是可读的明文。例如:

@Autowired
private StringRedisTemplate stringRedisTemplate;public void setStringByStringRedisTemplate(String key, String value) {stringRedisTemplate.opsForValue().set(key, value);
}public String getStringByStringRedisTemplate(String key) {return stringRedisTemplate.opsForValue().get(key);
}

RedisTemplate默认使用JdkSerializationRedisSerializer进行序列化,它可以操作各种类型的数据,包括复杂的 Java 对象。当需要存储 Java 对象时,RedisTemplate会将对象序列化为二进制字节数组存储到 Redis 中。在 Redis 客户端中查看时,数据是不可读的二进制形式。例如:

@Autowired
private RedisTemplate<String, Object> redisTemplate;public void setObjectByRedisTemplate(String key, Object value) {redisTemplate.opsForValue().set(key, value);
}public Object getObjectByRedisTemplate(String key) {return redisTemplate.opsForValue().get(key);
}

一般来说,如果项目中只涉及到简单的字符串数据的存储和读取,使用StringRedisTemplate可以提高开发效率,并且数据的可读性更好;而当需要存储复杂的 Java 对象,如自定义的实体类、集合等时,RedisTemplate则更合适,它能够满足对复杂数据结构的操作需求。但需要注意的是,使用RedisTemplate时,由于默认的序列化方式会导致数据在 Redis 中以二进制形式存储,可能会影响数据的可读性和跨语言操作的便利性,必要时可以自定义序列化方式,如使用 JSON 序列化器,将对象序列化为 JSON 格式的字符串存储 ,这样既能够存储复杂对象,又能保证一定的可读性。

三、Spring Boot 缓存机制

3.1 缓存注解使用

在 Spring Boot 中,缓存注解为我们提供了一种便捷的方式来实现缓存功能,大大提高了应用程序的性能和响应速度。这些注解主要包括@EnableCaching、@Cacheable、@CachePut和@CacheEvict ,它们各自有着独特的作用和用法。

@EnableCaching是开启缓存功能的关键注解,它需要被添加到 Spring Boot 的主应用类或者配置类上。例如,在主应用类中添加如下注解:

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@EnableCaching
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}

当 Spring 容器启动时,会扫描到这个注解,并根据它来配置缓存相关的组件,为后续使用缓存注解奠定基础。

@Cacheable主要用于标记查询方法,它会在方法执行前检查缓存中是否已经存在该方法的返回值。如果存在,就直接从缓存中获取数据并返回,避免了方法的重复执行;如果不存在,才会执行方法,并将方法的返回值存入缓存中。它有几个常用的属性:

  • value或cacheNames:指定缓存的名称,可以是一个字符串或者字符串数组,用于标识缓存的区域。例如:@Cacheable(value = “userCache”) ,表示将方法的返回值缓存到名为userCache的缓存区域中。
  • key:用于指定缓存的键。默认情况下,Spring 会使用方法的参数作为键,但也可以通过 SpEL(Spring Expression Language)表达式来自定义键。比如:@Cacheable(value = “userCache”, key = “#id”) ,这里使用方法的id参数作为缓存的键。
  • condition:通过 SpEL 表达式指定缓存的条件。只有当条件满足时,才会进行缓存操作。例如:@Cacheable(value = “userCache”, condition = “#id > 0”) ,表示只有当id参数大于 0 时,才会缓存方法的返回值。

假设有一个用户服务类UserService,其中有一个根据用户 ID 查询用户信息的方法:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {@Cacheable(value = "userCache", key = "#id")public User getUserById(Long id) {// 模拟从数据库查询用户信息User user = new User(id, "张三", 20);System.out.println("从数据库查询用户信息:" + user);return user;}
}

当第一次调用getUserById方法时,会执行方法体从数据库查询用户信息,并将结果存入userCache缓存中,键为传入的id值。后续再次调用该方法,并且传入相同的id时,就会直接从缓存中获取用户信息,而不会再次执行数据库查询操作,从而提高了查询效率。

@CachePut主要用于更新缓存,它会在方法执行后,将方法的返回值存入缓存中。与@Cacheable不同的是,无论缓存中是否已经存在数据,@CachePut标注的方法都会执行。它的属性与@Cacheable类似,value和key的用法相同。例如,在UserService类中添加一个更新用户信息的方法:

import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;@Service
public class UserService {@CachePut(value = "userCache", key = "#user.id")public User updateUser(User user) {// 模拟更新数据库中的用户信息user.setName("李四");user.setAge(22);System.out.println("更新数据库中的用户信息:" + user);return user;}
}

当调用updateUser方法时,会先执行方法体更新用户信息,然后将更新后的用户对象存入userCache缓存中,键为用户对象的id。这样,下次查询该用户信息时,从缓存中获取的就是更新后的信息。

@CacheEvict用于清除缓存,可以指定清除某个缓存区域中的某个键对应的数据,或者清除整个缓存区域。它的常用属性有:

  • value或cacheNames:指定要清除的缓存区域。
  • key:指定要清除的缓存键。如果不指定key,则默认清除整个缓存区域。
  • allEntries:布尔类型,当设置为true时,表示清除整个缓存区域。例如:@CacheEvict(value = “userCache”, allEntries = true) ,会清除userCache缓存区域中的所有数据。
  • beforeInvocation:布尔类型,默认值为false。当设置为true时,表示在方法调用前就清除缓存;设置为false时,表示在方法调用后清除缓存。

假设在UserService类中添加一个删除用户的方法:

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class UserService {@CacheEvict(value = "userCache", key = "#id")public void deleteUser(Long id) {// 模拟从数据库中删除用户信息System.out.println("从数据库中删除用户信息,id:" + id);}
}

当调用deleteUser方法时,会先执行方法体从数据库中删除用户信息,然后清除userCache缓存区域中键为id的缓存数据,保证了缓存与数据库数据的一致性。

3.2 缓存管理器配置

在 Spring Boot 中,配置 Redis 作为缓存容器,需要创建并配置RedisCacheManager。RedisCacheManager是 Spring Data Redis 提供的用于管理 Redis 缓存的管理器,它负责创建、配置和管理 Redis 缓存实例。

首先,确保项目中已经引入了spring-boot-starter-data-redis依赖。然后,在配置类中创建RedisCacheManager的 Bean。以下是一个配置示例:

import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
import java.util.HashMap;
import java.util.Map;@Configuration
public class RedisConfig {@Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {// 创建默认的RedisCacheConfiguration,并设置全局缓存过期时间RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)) // 默认全局缓存过期时间为5分钟.disableCachingNullValues(); // 禁止缓存null值// 为特定缓存配置不同的过期策略Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();// 配置名为"shortLivedCache"的缓存,设置过期时间为1分钟cacheConfigurations.put("shortLivedCache",RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(1)) // 设置缓存的TTL为1分钟.disableCachingNullValues()); // 禁止缓存null值// 配置名为"longLivedCache"的缓存,设置过期时间为1小时cacheConfigurations.put("longLivedCache",RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)) // 设置缓存的TTL为1小时.disableCachingNullValues()); // 禁止缓存null值// 创建RedisCacheManager,加载自定义的缓存配置return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(defaultCacheConfig) // 设置默认的缓存配置.withInitialCacheConfigurations(cacheConfigurations) // 加载不同缓存区域的配置.build();}
}

在上述配置中:

  • RedisCacheConfiguration.defaultCacheConfig()创建了一个默认的缓存配置对象,它包含了一些默认的设置,如序列化方式等。
  • .entryTtl(Duration.ofMinutes(5))设置了默认的缓存过期时间为 5 分钟。可以根据实际需求调整这个时间,以平衡缓存的有效性和内存使用。
  • .disableCachingNullValues()禁止缓存null值,避免在缓存中存储无效数据,浪费内存空间。

对于cacheConfigurations,它是一个Map,用于存储不同缓存区域的自定义配置。通过put方法,可以为特定的缓存区域设置不同的过期时间、序列化方式等。例如,shortLivedCache设置了较短的过期时间,适用于一些时效性较强的数据;longLivedCache设置了较长的过期时间,适合存储相对稳定的数据。

最后,通过RedisCacheManager.builder(redisConnectionFactory)创建一个RedisCacheManager的构建器,传入RedisConnectionFactory,它是用于连接 Redis 服务器的工厂。然后,使用.cacheDefaults(defaultCacheConfig)设置默认的缓存配置,.withInitialCacheConfigurations(cacheConfigurations)加载自定义的缓存配置,最后调用.build()方法构建出RedisCacheManager实例。这个实例会被 Spring 容器管理,用于后续的缓存操作。

3.3 缓存实战案例

在实际业务中,Spring Boot 的缓存机制能够显著提升系统的性能和响应速度。以下以用户信息缓存和商品列表缓存为例,详细展示其应用及性能提升效果。

用户信息缓存

在一个用户管理系统中,经常需要根据用户 ID 查询用户信息。假设存在一个UserService类,其中包含一个根据用户 ID 查询用户信息的方法getUserById。在未使用缓存之前,每次调用该方法都需要从数据库中查询数据,当并发请求较多时,数据库的压力会很大,查询性能也会受到影响。

import org.springframework.stereotype.Service;@Service
public class UserService {public User getUserById(Long id) {// 模拟从数据库查询用户信息User user = new User(id, "张三", 20);System.out.println("从数据库查询用户信息:" + user);return user;}
}

使用 Spring Boot 的缓存机制后,在getUserById方法上添加@Cacheable注解:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {@Cacheable(value = "userCache", key = "#id")public User getUserById(Long id) {// 模拟从数据库查询用户信息User user = new User(id, "张三", 20);System.out.println("从数据库查询用户信息:" + user);return user;}
}

当第一次调用getUserById方法时,会执行方法体从数据库查询用户信息,并将结果存入名为userCache的缓存中,键为用户 ID。后续再次调用该方法,并且传入相同的用户 ID 时,会直接从缓存中获取用户信息,不再执行数据库查询操作。这样不仅减轻了数据库的压力,还大大提高了查询的响应速度。通过性能测试工具测试,在高并发情况下,使用缓存后的响应时间从原来的平均 200ms 降低到了 5ms 以内,性能提升效果显著。

商品列表缓存

在电商系统中,商品列表是用户经常访问的页面,展示了各种商品的信息。假设存在一个ProductService类,其中的getProductList方法用于获取商品列表数据。在未使用缓存时,每次请求商品列表都需要从数据库中查询所有商品信息,这对于数据库来说是一个较大的负担,尤其是在高并发场景下,可能会导致数据库性能下降,页面加载缓慢。

import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class ProductService {public List<Product> getProductList() {// 模拟从数据库查询商品列表List<Product> productList = new ArrayList<>();Product product1 = new Product(1L, "商品1", 100.0);Product product2 = new Product(2L, "商品2", 200.0);productList.add(product1);productList.add(product2);System.out.println("从数据库查询商品列表:" + productList);return productList;}
}

使用缓存后,在getProductList方法上添加@Cacheable注解:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class ProductService {@Cacheable(value = "productCache")public List<Product> getProductList() {// 模拟从数据库查询商品列表List<Product> productList = new ArrayList<>();Product product1 = new Product(1L, "商品1", 100.0);Product product2 = new Product(2L, "商品2", 200.0);productList.add(product1);productList.add(product2);System.out.println("从数据库查询商品列表:" + productList);return productList;}
}

第一次调用getProductList方法时,会执行数据库查询操作,并将商品列表数据存入productCache缓存中。后续再次请求商品列表时,直接从缓存中获取数据,无需查询数据库。经性能测试,在高并发情况下,页面加载时间从原来的平均 1 秒缩短到了 0.1 秒以内,大大提升了用户体验,同时也减轻了数据库的负载压力。

3.4 缓存问题解决

在使用缓存的过程中,可能会遇到缓存穿透、缓存击穿和缓存雪崩等问题,这些问题如果不及时解决,会对系统的性能和稳定性造成严重影响。下面将详细分析这些问题的产生原因及解决方案。

缓存穿透

缓存穿透是指查询一个在缓存和数据库中都不存在的数据,导致每次请求都绕过缓存直接访问数据库。如果有恶意用户利用这一点,大量发送不存在数据的查询请求,就会使数据库承受巨大的压力,甚至可能导致数据库崩溃。

产生原因主要有两种:一是恶意攻击,攻击者故意构造大量不存在的数据请求,以消耗数据库资源;二是业务逻辑中存在无法避免的查询不存在数据的情况,例如用户输入了错误的查询条件。

解决方案如下

  • 缓存空结果:当查询的数据在数据库中不存在时,也将空结果缓存起来,并设置一个较短的过期时间。这样,下次再查询相同的数据时,直接从缓存中获取空结果,避免了对数据库的无效查询。例如,在查询用户信息的方法中:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {@Cacheable(value = "userCache", key = "#id", unless = "#result == null")public User getUserById(Long id) {// 模拟从数据库查询用户信息User user = null;// 假设这里查询数据库后没有找到用户System.out.println("从数据库查询用户信息,未找到:" + id);return user;}
}

这里使用@Cacheable注解的unless属性,当方法返回结果为null时,不缓存结果。但在实际业务中,可以在方法内判断结果为null时,手动将null值存入缓存,设置较短的过期时间,如 1 分钟。

  • 使用布隆过滤器:布隆过滤器是一种概率型数据结构,它可以高效地判断一个元素是否存在于集合中。在缓存穿透场景中,可以在查询数据前,先通过布隆过滤器判断数据是否存在。如果布隆过滤器判断数据不存在,就直接返回,不再查询数据库和缓存;如果判断数据可能存在,再进行正常的缓存和数据库查询。布隆过滤器的优势在于它的空间效率和查询效率都很高,能有效防止大量无效请求穿透到数据库。可以使用 Google Guava 库中的BloomFilter来实现布隆过滤器。例如:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.List;@Service
public class ProductService {private BloomFilter<String> bloomFilter;@PostConstructpublic void initBloomFilter() {// 假设从数据库中获取所有商品IDList<String> productIds = getProductIdsFromDatabase();bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), productIds.size(), 0.01);for (String productId : productIds) {bloomFilter.put(productId);}}public Product getProductById(String productId) {if (!bloomFilter.mightContain(productId)) {System.out.println("布隆过滤器判断数据不存在,直接返回");return null;}// 进行正常的缓存和数据库查询Product product = getProductFromCacheOrDatabase(productId);return product;}

文章转载自:

http://um3FGpix.hnsdr.cn
http://GtsElzrc.hnsdr.cn
http://uPVDu0MY.hnsdr.cn
http://4HsokqS4.hnsdr.cn
http://dzk5AXMP.hnsdr.cn
http://DToMBn4E.hnsdr.cn
http://uQbCzO5g.hnsdr.cn
http://17zGVQbz.hnsdr.cn
http://WCozUhAy.hnsdr.cn
http://m8bEd1Ld.hnsdr.cn
http://O42EP9yU.hnsdr.cn
http://j48QbJtf.hnsdr.cn
http://0OTlw2ym.hnsdr.cn
http://S1uWWAkE.hnsdr.cn
http://cpWvjrdq.hnsdr.cn
http://qDZi6Mq0.hnsdr.cn
http://N0AcksXl.hnsdr.cn
http://9TY6rVUQ.hnsdr.cn
http://rCGMR5T7.hnsdr.cn
http://NiZWUrW1.hnsdr.cn
http://D4T1KylE.hnsdr.cn
http://jvGrpnCA.hnsdr.cn
http://c2xBUFMY.hnsdr.cn
http://YIe06nma.hnsdr.cn
http://EnbSoT9d.hnsdr.cn
http://jmITugqz.hnsdr.cn
http://slzxBXZF.hnsdr.cn
http://t7Uf6TAh.hnsdr.cn
http://c3npTwLM.hnsdr.cn
http://OZS77u5Y.hnsdr.cn
http://www.dtcms.com/a/374796.html

相关文章:

  • Spring Cache 多租户缓存隔离解决方案实践
  • Mybatis-12 第三方缓存-EhCache
  • 【C++】特别的程序错误处理方式——异常机制
  • 嵌入式设备上mqtt库的使用
  • 【Linux基础知识系列:第一百二十六篇】使用dd命令进行磁盘复制
  • 从零到一使用Linux+Nginx+MySQL+PHP搭建的Web网站服务器架构环境——LNMP(上)
  • 使用虚拟机Ubuntu搭建mosquito服务器 使esp32、上位机通信
  • 云计算技术栈
  • 国产时序数据库选型指南-从大数据视角看透的价值
  • 东京本社招聘 | 财务负责人 多个日本IT岗位(Java/C++/Python/AWS 等),IT营业同步招募
  • AWS ALB 详解:智能流量调度器
  • Django REST框架:ModelViewSet全面解析
  • 基于Centos7.9搭建svn服务端
  • PyTorch 和nn.Conv2d详解
  • pytorch基本运算-分离计算
  • 基于容器化云原生的 MySQL 及中间件高可用自动化集群项目
  • “图观”端渲染场景编辑器
  • 构建分布式京东商品数据采集系统:基于 API 的微服务实现方案
  • HTML5点击转圈圈 | 深入了解HTML5技术中的动态效果与用户交互设计
  • springboot rabbitmq 延时队列消息确认收货订单已完成
  • CString(MFC/ATL 框架)和 QString(Qt 框架)
  • Sklearn(机器学习)实战:鸢尾花数据集处理技巧
  • 工具框架:Scikit-learn、Pandas、NumPy预测鸢尾花的种类
  • AI GEO 优化能否快速提升网站在搜索引擎的排名?​
  • nvm和nrm的详细安装配置,从卸载nodejs到安装NVM管理nodejs版本,以及安装nrm管理npm版本
  • 对口型视频怎么制作?从脚本到成片的全流程解析
  • 从“能说话”到“会做事”:AI Agent如何重构日常工作流?
  • 洛谷 P1249 最大乘积-普及/提高-
  • 小红书获取笔记详情API接口会返回哪些数据?
  • JAVA Spring Boot maven导入使用本地SDK(jar包)