Redis缓存相关的几个问题

database

1  缓存穿透

问题描述

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

解决方案

缓存空值,即对于不存在的数据,在缓存中放置一个空对象(注意,设置过期时间)

2  缓存击穿

问题描述

缓存击穿是指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到数据库。

解决方案

加互斥锁,在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。

3  缓存雪崩

问题描述

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

解决方案

可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

4  缓存服务器宕机

问题描述

并发太高,缓存服务器连接被打满,最后挂了

解决方案

  • 限流:nginx、spring cloud gateway、sentinel等都支持限流
  • 增加本地缓存(JVM内存缓存),减轻redis一部分压力

5  Redis实现分布式锁

问题描述

如果用redis做分布式锁的话,有可能会存在这样一个问题:key丢失。比如,master节点写成功了还没来得及将它复制给slave就挂了,于是slave成为新的master,于是key丢失了,后果就是没锁住,多个线程持有同一把互斥锁。

解决方案

必须等redis把这个key复制给所有的slave并且都持久化完成后,才能返回加锁成功。但是这样的话,对其加锁的性能就会有影响。

zookeeper同样也可以实现分布式锁。在分布式锁的的实现上,zookeeper的重点是CP,redis的重点是AP。因此,要求强一致性就用zookeeper,对性能要求比较高的话就用redis

5  示例代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.6.7</version>

<relativePath/><!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>demo426</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>demo426</name>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<dependency>

<groupId>org.redisson</groupId>

<artifactId>redisson</artifactId>

<version>3.17.1</version>

</dependency>

<dependency>

<groupId>com.github.ben-manes.caffeine</groupId>

<artifactId>caffeine</artifactId>

<version>2.9.2</version>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>2.0.1</version>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-lang3</artifactId>

<version>3.12.0</version>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<excludes>

<exclude>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

</exclude>

</excludes>

</configuration>

</plugin>

</plugins>

</build>

</project>

Product.java

package com.example.demo426.domain;

import lombok.Data;

import java.io.Serializable;

import java.time.LocalDateTime;

/**

* @Author ChengJianSheng

* @Date 2022/4/26

*/

@Data

public class Product implements Serializable {

private Long productId;

private String productName;

private Integer stock;

private LocalDateTime createTime;

private LocalDateTime updateTime;

private Integer isDeleted;

private Integer version;

}

ProductController.java

package com.example.demo426.controller;

import com.alibaba.fastjson.JSON;

import com.example.demo426.domain.Product;

import com.example.demo426.service.ProductService;

import com.github.benmanes.caffeine.cache.Cache;

import com.github.benmanes.caffeine.cache.Caffeine;

import org.apache.commons.lang3.StringUtils;

import org.redisson.api.RLock;

import org.redisson.api.RReadWriteLock;

import org.redisson.api.RedissonClient;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

import java.time.Duration;

import java.util.Random;

import java.util.concurrent.TimeUnit;

/**

* @Author ChengJianSheng

* @Date 2022/4/26

*/

@RestController

@RequestMapping("/product")

public class ProductController {

@Autowired

private RedissonClient redissonClient;

@Resource

private StringRedisTemplate stringRedisTemplate;

@Autowired

private ProductService productService;

private final Cache PRODUCT_LOCAL_CACHE = Caffeine.newBuilder()

.maximumSize(100)

.expireAfterWrite(Duration.ofMinutes(60))

.build();

private final String PRODUCT_CACHE_PREFIX = "cache:product:";

private final String PRODUCT_LOCK_PREFIX = "lock:product:";

private final String PRODUCT_RW_LOCK_PREFIX = "lock:rw:product:";

/**

* 更新

* 写缓存的方式有这么几种:

* 1. 更新完数据库后,直接删除缓存

* 2. 更新完数据库后,主动更新缓存

* 3. 更新完数据库后,发MQ消息,由消费者去刷新缓存

* 4. 利用canal等工具,监听MySQL数据库binlog,然后去刷新缓存

*/

@PostMapping("/update")

public void update(@RequestBody Product productDTO) {

RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productDTO.getProductId());

RLock wLock = readWriteLock.writeLock();

wLock.lock();

try {

// 写数据库

// update product set name=xxx,...,version=version+1 where id=xx and version=xxx

Product product = productService.update(productDTO);

// 放入缓存

PRODUCT_LOCAL_CACHE.put(product.getProductId(), product);

stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + product.getProductId(), JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);

} finally {

wLock.unlock();

}

}

/**

* 查询

*/

@GetMapping("/query")

public Product query(@RequestParam("productId") Long productId) {

// 1. 尝试从缓存读取

Product product = getProductFromCache(productId);

if (null != product) {

return product;

}

// 2. 准备从数据库中加载

// 互斥锁

RLock lock = redissonClient.getLock(PRODUCT_LOCK_PREFIX + productId);

lock.lock();

try {

// 再次先查缓存

product = getProductFromCache(productId);

if (null != product) {

return product;

}

// 为了避免缓存与数据库双写不一致

// 读写锁

RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productId);

RLock rLock = readWriteLock.readLock();

rLock.lock();

try {

// 查数据库

product = productService.getById(productId);

if (null == product) {

// 如果数据库中没有,则放置一个空对象,这样做是为了避免”缓存穿透“问题

product = new Product();

} else {

PRODUCT_LOCAL_CACHE.put(productId, product);

}

// 放入缓存

stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + productId, JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);

} finally {

rLock.unlock();

}

} finally {

lock.unlock();

}

return null;

}

/**

* 查缓存

*/

private Product getProductFromCache(Long productId) {

// 1. 尝试从本地缓存读取

Product product = PRODUCT_LOCAL_CACHE.getIfPresent(productId);

if (null != product) {

return product;

}

// 2. 尝试从Redis中读取

String key = PRODUCT_CACHE_PREFIX + productId;

String value = stringRedisTemplate.opsForValue().get(key);

if (StringUtils.isNotBlank(value)) {

product = JSON.parseObject(value, Product.class);

return product;

}

return null;

}

/**

* 为了避免缓存集体失效,故而加了随机时间

*/

private int getProductTimeout(int initVal) {

Random random = new Random(10);

return initVal + random.nextInt();

}

}

以上是 Redis缓存相关的几个问题 的全部内容, 来源链接: utcz.com/z/536450.html

回到顶部