Redis缓存相关的几个问题

打印 上一主题 下一主题

主题 653|帖子 653|积分 1959

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
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4.     <modelVersion>4.0.0</modelVersion>
  5.     <parent>
  6.         <groupId>org.springframework.boot</groupId>
  7.         <artifactId>spring-boot-starter-parent</artifactId>
  8.         <version>2.6.7</version>
  9.         <relativePath/>
  10.     </parent>
  11.     <groupId>com.example</groupId>
  12.     <artifactId>demo426</artifactId>
  13.     <version>0.0.1-SNAPSHOT</version>
  14.     <name>demo426</name>
  15.     <properties>
  16.         <java.version>1.8</java.version>
  17.     </properties>
  18.     <dependencies>
  19.         <dependency>
  20.             <groupId>org.springframework.boot</groupId>
  21.             <artifactId>spring-boot-starter-web</artifactId>
  22.         </dependency>
  23.         <dependency>
  24.             <groupId>org.springframework.boot</groupId>
  25.             <artifactId>spring-boot-starter-data-redis</artifactId>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>org.redisson</groupId>
  29.             <artifactId>redisson</artifactId>
  30.             <version>3.17.1</version>
  31.         </dependency>
  32.         <dependency>
  33.             <groupId>com.github.ben-manes.caffeine</groupId>
  34.             <artifactId>caffeine</artifactId>
  35.             <version>2.9.2</version>
  36.         </dependency>
  37.         <dependency>
  38.             <groupId>com.alibaba</groupId>
  39.             <artifactId>fastjson</artifactId>
  40.             <version>2.0.1</version>
  41.         </dependency>
  42.         <dependency>
  43.             <groupId>org.apache.commons</groupId>
  44.             <artifactId>commons-lang3</artifactId>
  45.             <version>3.12.0</version>
  46.         </dependency>
  47.         <dependency>
  48.             <groupId>org.projectlombok</groupId>
  49.             <artifactId>lombok</artifactId>
  50.             <optional>true</optional>
  51.         </dependency>
  52.         <dependency>
  53.             <groupId>org.springframework.boot</groupId>
  54.             <artifactId>spring-boot-starter-test</artifactId>
  55.             <scope>test</scope>
  56.         </dependency>
  57.     </dependencies>
  58.     <build>
  59.         <plugins>
  60.             <plugin>
  61.                 <groupId>org.springframework.boot</groupId>
  62.                 <artifactId>spring-boot-maven-plugin</artifactId>
  63.                 <configuration>
  64.                     <excludes>
  65.                         <exclude>
  66.                             <groupId>org.projectlombok</groupId>
  67.                             <artifactId>lombok</artifactId>
  68.                         </exclude>
  69.                     </excludes>
  70.                 </configuration>
  71.             </plugin>
  72.         </plugins>
  73.     </build>
  74. </project>
复制代码
Product.java
  1. package com.example.demo426.domain;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. import java.time.LocalDateTime;
  5. /**
  6. * @Author ChengJianSheng
  7. * @Date 2022/4/26
  8. */
  9. @Data
  10. public class Product implements Serializable {
  11.     private Long productId;
  12.     private String productName;
  13.     private Integer stock;
  14.     private LocalDateTime createTime;
  15.     private LocalDateTime updateTime;
  16.     private Integer isDeleted;
  17.     private Integer version;
  18. }
复制代码
ProductController.java
  1. package com.example.demo426.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.demo426.domain.Product;
  4. import com.example.demo426.service.ProductService;
  5. import com.github.benmanes.caffeine.cache.Cache;
  6. import com.github.benmanes.caffeine.cache.Caffeine;
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.redisson.api.RLock;
  9. import org.redisson.api.RReadWriteLock;
  10. import org.redisson.api.RedissonClient;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.data.redis.core.StringRedisTemplate;
  13. import org.springframework.web.bind.annotation.*;
  14. import javax.annotation.Resource;
  15. import java.time.Duration;
  16. import java.util.Random;
  17. import java.util.concurrent.TimeUnit;
  18. /**
  19. * @Author ChengJianSheng
  20. * @Date 2022/4/26
  21. */
  22. @RestController
  23. @RequestMapping("/product")
  24. public class ProductController {
  25.     @Autowired
  26.     private RedissonClient redissonClient;
  27.     @Resource
  28.     private StringRedisTemplate stringRedisTemplate;
  29.     @Autowired
  30.     private ProductService productService;
  31.     private final Cache<long, product=""> PRODUCT_LOCAL_CACHE = Caffeine.newBuilder()
  32.             .maximumSize(100)
  33.             .expireAfterWrite(Duration.ofMinutes(60))
  34.             .build();
  35.     private final String PRODUCT_CACHE_PREFIX = "cache:product:";
  36.     private final String PRODUCT_LOCK_PREFIX = "lock:product:";
  37.     private final String PRODUCT_RW_LOCK_PREFIX = "lock:rw:product:";
  38.     /**
  39.      * 更新
  40.      * 写缓存的方式有这么几种:
  41.      * 1. 更新完数据库后,直接删除缓存
  42.      * 2. 更新完数据库后,主动更新缓存
  43.      * 3. 更新完数据库后,发MQ消息,由消费者去刷新缓存
  44.      * 4. 利用canal等工具,监听MySQL数据库binlog,然后去刷新缓存
  45.      */
  46.     @PostMapping("/update")
  47.     public void update(@RequestBody Product productDTO) {
  48.         RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productDTO.getProductId());
  49.         RLock wLock = readWriteLock.writeLock();
  50.         wLock.lock();
  51.         try {
  52.             //  写数据库
  53.             //  update product set name=xxx,...,version=version+1 where id=xx and version=xxx
  54.             Product product = productService.update(productDTO);
  55.             //  放入缓存
  56.             PRODUCT_LOCAL_CACHE.put(product.getProductId(), product);
  57.             stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + product.getProductId(), JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
  58.         } finally {
  59.             wLock.unlock();
  60.         }
  61.     }
  62.     /**
  63.      * 查询
  64.      */
  65.     @GetMapping("/query")
  66.     public Product query(@RequestParam("productId") Long productId) {
  67.         //  1. 尝试从缓存读取
  68.         Product product = getProductFromCache(productId);
  69.         if (null != product) {
  70.             return product;
  71.         }
  72.         //  2. 准备从数据库中加载
  73.         //  互斥锁
  74.         RLock lock = redissonClient.getLock(PRODUCT_LOCK_PREFIX + productId);
  75.         lock.lock();
  76.         try {
  77.             //  再次先查缓存
  78.             product = getProductFromCache(productId);
  79.             if (null != product) {
  80.                 return product;
  81.             }
  82.             //  为了避免缓存与数据库双写不一致
  83.             //  读写锁
  84.             RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productId);
  85.             RLock rLock = readWriteLock.readLock();
  86.             rLock.lock();
  87.             try {
  88.                 //  查数据库
  89.                 product = productService.getById(productId);
  90.                 if (null == product) {
  91.                     //  如果数据库中没有,则放置一个空对象,这样做是为了避免”缓存穿透“问题
  92.                     product = new Product();
  93.                 } else {
  94.                     PRODUCT_LOCAL_CACHE.put(productId, product);
  95.                 }
  96.                 //  放入缓存
  97.                 stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + productId, JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
  98.             } finally {
  99.                 rLock.unlock();
  100.             }
  101.         } finally {
  102.             lock.unlock();
  103.         }
  104.         return null;
  105.     }
  106.     /**
  107.      * 查缓存
  108.      */
  109.     private Product getProductFromCache(Long productId) {
  110.         //  1. 尝试从本地缓存读取
  111.         Product product = PRODUCT_LOCAL_CACHE.getIfPresent(productId);
  112.         if (null != product) {
  113.             return product;
  114.         }
  115.         //  2. 尝试从Redis中读取
  116.         String key = PRODUCT_CACHE_PREFIX + productId;
  117.         String value = stringRedisTemplate.opsForValue().get(key);
  118.         if (StringUtils.isNotBlank(value)) {
  119.             product = JSON.parseObject(value, Product.class);
  120.             return product;
  121.         }
  122.         return null;
  123.     }
  124.     /**
  125.      * 为了避免缓存集体失效,故而加了随机时间
  126.      */
  127.     private int getProductTimeout(int initVal) {
  128.         Random random = new Random(10);
  129.         return initVal + random.nextInt();
  130.     }
  131. }</long,>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81428

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表