Redis缓存相关的几个问题

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

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();
    }
}

Original: https://www.cnblogs.com/cjsblog/p/16196302.html
Author: 废物大师兄
Title: Redis缓存相关的几个问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612200/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Nginx 反向代理、Rewrite

    Rewrite功能配置 Rewrite是Nginx服务器提供的一个重要基本功能,是Web服务器产品中几乎必备的功能。主要的作用是用来实现URL的重写。www.jd.com注意:Ng…

    数据库 2023年6月6日
    092
  • day05-离线留言和离线文件

    多用户即时通讯系统05 4.编码实现04(拓展) 拓展功能: 实现离线留言,如果某个用户不在线 ,当登陆后,可以接收离线的消息 实现离线发文件,如果某个功能没有在线,当登录后,可以…

    数据库 2023年6月11日
    081
  • md5解密异常

    javax.crypto.BadPaddingException: Given final block not properly paddedat com.sun.crypto.p…

    数据库 2023年6月11日
    0101
  • 正则表达式=Regex=regular expression

    正则表达式=Regex=regular expression 反向引用*2 \index索引引用 \b(\w+)\b\s+\1\b \k \b(? 数量符/限定符62 贪婪Gree…

    数据库 2023年6月15日
    071
  • MySQL45讲之InnoDB刷脏策略

    本文介绍 InnoDB 的刷脏控制策略,它是如何控制刷脏速率的,以及一些相关参数。 了解 MySQL 的刷脏策略有什么意义? 当一条正确的 SQL 执行时偶尔延迟较高,无法复现场景…

    数据库 2023年5月24日
    088
  • Mysql数据库 ALTER 基本操作

    背景: ALTER作为DDL语言之一,工作中经常遇到,这里我们简单介绍一下常见的几种使用场景 新建两个测试表offices 和 employess CREATE TABLE off…

    数据库 2023年6月14日
    085
  • 2022的七夕,奉上7个精美的表白代码,同时教大家快速改源码自用

    🤵‍♂️ 个人主页:奇想派👨‍💻 作者简介:奇想派,十年全栈开发经验,团队负责人。喜欢钻研技术,争取成为编程达人 🎖️!🗺️学海无涯苦作舟,🛤️编程之路无悔路!📝 如果文章对你有帮…

    数据库 2023年6月16日
    0116
  • SQL与数据库编程学习笔记——day3

    SQL与数据库编程学习笔记-day3 增加语句; 利用insert into语句进行增加数据库数据; 格式: insert into 表名 (字段名) values (数值);ps…

    数据库 2023年6月9日
    0135
  • Python实现XMind测试用例快速转Excel用例

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/c2d10f21.html 你好,我是测试蔡坨坨。 今天分享一个Python编写的小工具,实现XMind…

    数据库 2023年6月11日
    095
  • MySQL实战45讲 1,2

    01 | 基础架构:一条SQL查询语句是如何执行的? Server 层 所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 存储引擎层负责数据的存储和提取。其架构模…

    数据库 2023年6月16日
    074
  • java使用EasyExcel导入导出excel

    使用alibab的EasyExce完成导入导出excel 一、准备工作 1、导包 org.apache.poi poi 3.17 org.apache.poi poi-ooxml-…

    数据库 2023年6月6日
    094
  • 2022-8-30 servlet

    HttpServletRequest — request(请求) 所有的 和&a…

    数据库 2023年6月14日
    088
  • java通过内存流去掉多行文本中的空行

    对于多行文本,你直接通过replace,replaceAll是不能将空行删除的,你需要遍历这些行,对每行文本进行操作,最后把返回新的文本才行。 public static Stri…

    数据库 2023年6月6日
    097
  • MySQL 中 bigint、int、mediumint、smallint、tinyint 有符号和无符号的取值范围

    想要了解取值范围首先需要知道的是 bit 和 Byte 的概念 bit :位 二进制数系统中,位通常简写为 “b”,也称为比特,每个二进制数字 0 或 1 …

    数据库 2023年5月24日
    091
  • 12 用最有效率的方法计算 2 乘以 8

    2 << 3 左移是位运算符,直接操作内存中整数对应的二进制位,效率高; 左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方。 Original: https:…

    数据库 2023年6月6日
    077
  • 人的思维定势

    人的思维定势 前段时间,因为咳嗽时间太长,去了医院看”呼吸内科”,检查了一番,最终发现是”变异性咳嗽”,也叫”变异性哮喘…

    数据库 2023年6月9日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球