• 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

    <?xml version="1.0" encoding="UTF-8"?>
    <project 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">
        <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();
        }
    }
  • 相关阅读:
    初试django
    初试mysql
    ASP.NET MVC 做的网站项目
    Update 更新语句使用两个表关联
    SQL Server 输出所有表结构
    循环数据集字段赋默认值
    FireBird.conf配置文件常用参数
    Delphi 获取临时数据集 ClientDataSet
    DELPHI 读取csv 格式文本文件
    获取 临时数据集 的两种方法
  • 原文地址:https://www.cnblogs.com/cjsblog/p/16196302.html
Copyright © 2020-2023  润新知