1 缓存穿透 问题描述 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。 解决方案 缓存空值,即对于不存在的数
1 缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
缓存空值,即对于不存在的数据,在缓存中放置一个空对象(注意,设置过期时间)
2 缓存击穿
缓存击穿是指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到数据库。
加互斥锁,在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
3 缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
4 缓存服务器宕机
并发太高,缓存服务器连接被打满,最后挂了
5 Redis实现分布式锁
如果用redis做分布式锁的话,有可能会存在这样一个问题:key丢失。比如,master节点写成功了还没来得及将它复制给slave就挂了,于是slave成为新的master,于是key丢失了,后果就是没锁住,多个线程持有同一把互斥锁。
必须等redis把这个key复制给所有的slave并且都持久化完成后,才能返回加锁成功。但是这样的话,对其加锁的性能就会有影响。
ZooKeeper同样也可以实现分布式锁。在分布式锁的的实现上,zookeeper的重点是CP,redis的重点是AP。因此,要求强一致性就用zookeeper,对性能要求比较高的话就用redis
5 示例代码
pom.xml
xmlns="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">
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.6.7
com.example
demo426
0.0.1-SNAPSHOT
demo426
1.8
org.springframework.boot
spring-boot-starter-WEB
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson
3.17.1
com.GitHub.ben-manes.caffeine
caffeine
2.9.2
com.alibaba
fastJSON
2.0.1
org.apache.commons
commons-lang3
3.12.0
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
Product.java
package com.example.demo426.domain;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@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;
@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:";
@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();
}
}
原文地址:https://www.cnblogs.com/cjsblog/arcHive/2022/04/26/16196302.html
--结束END--
本文标题: Redis缓存相关的几个问题
本文链接: https://lsjlt.com/news/9297.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-10-23
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0