博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
DeepSeek-行业融合之万象视界(附实战案例详解100+)
全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
感兴趣的可以先收藏起来,希望帮助更多的人
在当今互联网时代,高并发场景无处不在,如电商平台的秒杀活动、社交媒体的热点事件等。系统面临高并发请求时,若处理不当,可能会导致响应缓慢、系统崩溃等问题。为了应对这些挑战,分布式缓存技术应运而生。Redis 作为一款高性能的内存数据库,因其快速读写、丰富的数据结构等特性,成为了分布式缓存的首选。本文将详细介绍如何使用 Spring Boot 结合 Redis 构建分布式缓存系统,以解决高并发问题。
高并发场景通常具有以下特点:
Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)等。
首先,我们需要创建一个 Spring Boot 项目。可以使用 Spring Initializr(https://start.spring.io/)来快速创建项目,添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
dependencies>
在 application.properties
或 application.yml
中配置 Redis 连接信息:
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
创建一个 Redis 配置类,用于配置 RedisTemplate:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置 key 的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 设置 value 的序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
创建一个简单的测试类,验证 Redis 连接是否正常:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RedisTestController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/testRedis")
public String testRedis() {
redisTemplate.opsForValue().set("testKey", "testValue");
Object value = redisTemplate.opsForValue().get("testKey");
return value.toString();
}
}
启动 Spring Boot 应用,访问 http://localhost:8080/testRedis
,若返回 testValue
,则说明 Redis 连接正常。
缓存穿透是指大量请求的 key 既不在缓存中,也不在数据库中,导致请求直接穿透缓存访问数据库,从而给数据库带来巨大压力。例如,恶意用户可能会使用不存在的 key 进行大量请求,攻击系统。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
@Component
public class BloomFilterConfig {
private BloomFilter<String> bloomFilter;
@PostConstruct
public void init() {
// 预计插入数量
int expectedInsertions = 1000000;
// 误判率
double fpp = 0.001;
bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
// 初始化布隆过滤器数据
// 这里可以从数据库中读取所有有效的 key 并添加到布隆过滤器中
// bloomFilter.put("key1");
// bloomFilter.put("key2");
}
public boolean mightContain(String key) {
return bloomFilter.mightContain(key);
}
}
在 Controller 中使用布隆过滤器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BloomFilterController {
@Autowired
private BloomFilterConfig bloomFilterConfig;
@GetMapping("/bloomFilter/{key}")
public String bloomFilterTest(@PathVariable String key) {
if (!bloomFilterConfig.mightContain(key)) {
return "Key not found";
}
// 继续处理请求
return "Processing...";
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object getData(String key) {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) {
// 模拟从数据库中查询数据
Object dataFromDB = queryDataFromDB(key);
if (dataFromDB == null) {
// 缓存空对象,设置较短的过期时间
redisTemplate.opsForValue().set(key, "", 1, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(key, dataFromDB);
}
return dataFromDB;
}
return value;
}
private Object queryDataFromDB(String key) {
// 模拟从数据库中查询数据
return null;
}
}
缓存雪崩是指在某一时刻,大量的缓存 key 同时过期,导致大量请求直接访问数据库,从而使数据库压力过大,甚至崩溃。例如,系统在凌晨进行缓存更新,将大量的缓存 key 设置了相同的过期时间,当这些 key 同时过期时,就会引发缓存雪崩。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service
public class CacheServiceWithRandomExpire {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setDataWithRandomExpire(String key, Object value) {
Random random = new Random();
// 随机生成 1 到 60 分钟的过期时间
long expireTime = random.nextInt(60) + 1;
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.MINUTES);
}
}
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class MultiLevelCacheService {
private Cache<String, Object> localCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object getData(String key) {
// 先从本地缓存中获取数据
Object value = localCache.getIfPresent(key);
if (value == null) {
// 本地缓存中没有数据,从 Redis 中获取
value = redisTemplate.opsForValue().get(key);
if (value != null) {
// 将数据放入本地缓存
localCache.put(key, value);
}
}
return value;
}
}
缓存击穿是指某个热点 key 在缓存中过期时,恰好有大量的请求同时访问该 key,导致这些请求直接访问数据库,从而使数据库压力过大。例如,在电商平台的秒杀活动中,某个热门商品的缓存 key 过期时,可能会有大量用户同时请求该商品信息,引发缓存击穿问题。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Service
public class CacheServiceWithMutex {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_KEY = "cache_mutex:";
private static final String LOCK_VALUE = "lock";
private static final long LOCK_EXPIRE_TIME = 10;
public Object getData(String key) {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) {
// 获取锁
boolean locked = tryLock(LOCK_KEY + key);
if (locked) {
try {
// 再次检查缓存
value = redisTemplate.opsForValue().get(key);
if (value == null) {
// 从数据库中查询数据
value = queryDataFromDB(key);
if (value != null) {
// 将数据放入缓存
redisTemplate.opsForValue().set(key, value);
}
}
} finally {
// 释放锁
releaseLock(LOCK_KEY + key);
}
} else {
// 等待一段时间后重试
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getData(key);
}
}
return value;
}
private boolean tryLock(String key) {
RedisScript<Long> script = new DefaultRedisScript<>("if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end", Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key), LOCK_VALUE, LOCK_EXPIRE_TIME);
return result != null && result == 1;
}
private void releaseLock(String key) {
RedisScript<Long> script = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class);
redisTemplate.execute(script, Collections.singletonList(key), LOCK_VALUE);
}
private Object queryDataFromDB(String key) {
// 模拟从数据库中查询数据
return null;
}
}
通过本文的介绍,我们了解了高并发场景下可能出现的问题,以及如何使用 Spring Boot 结合 Redis 构建分布式缓存系统来解决这些问题。我们详细介绍了 Redis 的基本概念和优势,以及 Spring Boot 集成 Redis 的步骤。同时,针对缓存穿透、缓存雪崩和缓存击穿等问题,给出了相应的解决方案和代码示例。在实际开发中,我们可以根据具体的业务场景选择合适的解决方案,以提高系统的性能和可用性。