Redis是什么?
Redis是一款开源的内存数据存储系统,它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等。Redis将数据全部保留在内存中,因此读写速度快,是一款高性能的缓存系统。Redis还支持数据持久化,即将数据存储到磁盘中,以避免数据丢失。
Redis作为缓存系统的优势
高性能
Redis将数据全部加载到内存中,因此读写速度快,可以实现毫秒级的响应速度。
数据持久化
Redis支持数据持久化,将数据保存到磁盘中,即使发生宕机等问题,数据也不会丢失。
多种数据结构支持
Redis支持多种数据结构,可以存储字符串、哈希表、列表、集合、有序集合等数据类型,可以满足不同的业务需求。
分布式集群
Redis支持分布式集群,可以实现数据分片,提高数据存储和读写的性能。
Redis作为缓存系统的应用场景
读多写少的场景
Redis适合处理读多写少的场景,因为Redis的读写速度非常快,可以实现毫秒级的响应速度。而写操作较少,即使数据出现丢失或者宕机等问题,也不会对业务产生过大的影响。
对数据实时性要求高的场景
Redis支持数据缓存,可以将热点数据存储在缓存中,提供实时的读取服务。对于实时性要求比较高的业务场景,Redis可以作为缓存系统使用。
大数据查询场景
Redis支持多种数据结构,可以存储集合、有序集合等结构类型的数据,可以快速查询和处理大量数据。
Redis缓存问题及解决方法
缓存穿透问题
缓存穿透是指请求的数据在缓存中不存在,但是却不断请求数据库,从而造成数据库的过度压力。缓存穿透可能是恶意攻击或者是业务数据异常等原因造成的。解决方法有两种:
使用布隆过滤器
布隆过滤器是一种空间效率比较高的随机数据结构,可以用于检测一个元素是否为集合中的成员。在缓存层面上,将所有可能查询的结果存储起来,并将每个结果对应一个布隆过滤器。当一个请求查询数据库时,先使用布隆过滤器判断对应结果是否存在,如果不存在,则直接返回空结果,避免不必要的数据库查询。
数据预热
数据预热是指在系统启动时将部分相关数据加载到缓存中,以提高缓存命中率。可以使用定时任务或者在系统启动时部分数据进行预热。
缓存雪崩问题
缓存雪崩是指在某一时刻大量缓存数据失效,导致请求直接落到数据库上,瞬间造成数据库压力过大,无法承受。解决方法有:
缓存数据过期时间随机化
将缓存数据的过期时间随机化,可以避免大量缓存数据同时失效的情况。
搭建分布式缓存集群
搭建分布式缓存集群,在多个节点上分布缓存数据,可以避免因为单个节点崩溃导致整个缓存系统瘫痪的情况。
缓存击穿问题
缓存击穿是指一个非常热门的key在缓存过期的一瞬间同时有大量的请求访问,这些请求需要重新查询数据库,可能会造成数据库过载等问题。解决方法有两种:
加锁
在缓存失效的时候,加锁防止大量的请求同时进行数据库查询。在加锁期间,只有一个线程去查询数据库,其他线程等待锁释放后再去缓存中查询结果。
数据预加载
将热门数据提前缓存到Redis中,提高缓存的命中率,减少因为缓存失效而查询数据库的次数。
Redis缓存示例代码
缓存穿透解决方案示例代码
使用布隆过滤器解决缓存穿透问题的示例代码:
@Slf4j
@Component
public class BloomFilterHelper {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
private BloomFilter bloomFilter;
/**
* 初始化布隆过滤器
*/
@PostConstruct
public void init() {
Jedis jedis = null;
try {
jedis = new Jedis(redisHost, redisPort);
boolean bloomExists = jedis.exists("bloom");
if (!bloomExists) {
int numHashFunctions = 32;
int expectedInsertions = 10000000;
double fpp = 0.01;
bloomFilter = BloomFilter.create(Funnels.byteArrayFunnel(), expectedInsertions, fpp);
jedis.set("bloom", SerializerUtil.serialize(bloomFilter));
jedis.expire("bloom", 3600);
} else {
byte[] filterBytes = jedis.get("bloom".getBytes());
bloomFilter = (BloomFilter) SerializerUtil.deserialize(filterBytes);
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 判断元素是否存在于集合中
*
* @param key 缓存key
* @param element 元素名称
* @return 返回是否存在于集合中
*/
public boolean isExist(String key, String element) {
if (bloomFilter == null) {
init();
}
if (!bloomFilter.mightContain(element.getBytes())) {
return false;
}
Jedis jedis = null;
try {
jedis = new Jedis(redisHost, redisPort);
return jedis.sismember(key, element);
} catch (Exception e) {
log.error(e.getMessage());
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* 将元素添加到集合中
*
* @param key 缓存key
* @param element 元素名称
*/
public void put(String key, String element) {
if (bloomFilter == null) {
init();
}
bloomFilter.put(element.getBytes());
Jedis jedis = null;
try {
jedis = new Jedis(redisHost, redisPort);
jedis.sadd(key, element);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
缓存雪崩解决方案示例代码
将缓存数据的过期时间随机化解决缓存雪崩问题的示例代码:
@Component
public class RedisHelper {
@Autowired
private RedisTemplate redisTemplate;
private Random random = new Random();
/**
* 获取缓存数据,如果不存在,则先查询数据库填充缓存
*
* @param key 缓存key
* @param clazz 缓存内容的类型
* @param supplier 查询数据库的过程
* @param <T> 缓存内容的类型
* @return 返回缓存内容
*/
public <T> T getCache(String key, Class<T> clazz, Supplier<T> supplier) {
ValueOperations<String, T> valueOps = redisTemplate.opsForValue();
T cacheData = valueOps.get(key);
if (cacheData == null) {
// 随机设置过期时间,避免缓存同时失效
int expireTime = random.nextInt(60) + 60;
cacheData = supplier.get();
valueOps.set(key, cacheData, expireTime, TimeUnit.SECONDS);
}
return cacheData;
}
}
缓存击穿解决方案示例代码
加锁防止缓存击穿的示例代码:
@Slf4j
@Component
public class RedisLockHelper {
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
*
* @param lockKey 锁定的key
* @param expire 锁的有效期
* @return 返回是否加锁成功
*/
public boolean lock(String lockKey, long expire) {
String key = "lock:" + lockKey;
ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
boolean lock = valueOps.setIfAbsent(key, "1");
if (lock) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return lock;
}
/**
* 解锁
*
* @param lockKey 锁定的key
*/
public void unlock(String lockKey) {
String key = "lock:" + lockKey;
redisTemplate.delete(key);
}
}
@Slf4j
@Service
public class DemoService {
@Autowired
private RedisLockHelper redisLockHelper;
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取数据,避免缓存击穿
*
* @param id 数据id
* @return 返回数据
*/
public String getData(String id) {
String cacheKey = "cache:data:" + id;
String lockKey = "lock:data:" + id;
ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
String cacheData = valueOps.get(cacheKey);
if (cacheData == null) {
// 尝试加锁
boolean locked = redisLockHelper.lock(lockKey, 10);
if (!locked) {
try {
// 等待100ms重试
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
cacheData = getDataFromDB(id);
} else {
try {
cacheData = getDataFromDB(id);
valueOps.set(cacheKey, cacheData, 10, TimeUnit.MINUTES);
} finally {
redisLockHelper.unlock(lockKey);
}
}
}
return cacheData;
}
private String getDataFromDB(String id) {
// 查询数据库
return "";
}
}
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Redis缓存问题 - Python技术站