Redis緩存相關的幾個問題

来源:https://www.cnblogs.com/cjsblog/archive/2022/04/26/16196302.html
-Advertisement-
Play Games

1 緩存穿透 問題描述 緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時需要從資料庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到資料庫去查詢,進而給資料庫帶來壓力。 解決方案 緩存空值,即對於不存在的數據,在緩存中放置一個空對象(註意,設置過期時間) 2 緩存擊穿 問 ...


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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 許多社區類平臺都具備點贊功能,應運而生的就是自動點贊器,今天用Python寫一款點贊機器人,最簡單易理解的核心邏輯。全 文涉及的偽代碼,使用 Python 編寫,由於是偽代碼的原因,不懂Python,你也能看懂。 一、簡介 1.適用場景 本次點贊機器人,主要面向電腦上的 Web 站點,不涉及 ...
  • 作者:血夜之末 來源:www.cnblogs.com/Tiancheng-Duan/p/16002433.html 一、背景 一轉眼,又到了金三銀四的跳槽 & 求職季。 在 IT 行業,跳槽就離不開一個詞,那就是外包。 可以說,每一位 IT 人都接觸過外包,甚至參與其中。而多數IT職場萌新,都面臨著 ...
  • 在開發過程中經常需要在程式中嵌入瀏覽器控制項用於訪問網頁,Visual Studio中自帶了一個webbrowser控制項,但無奈這個控制項是調用用戶電腦上的IE瀏覽器來實現的,如果用戶裝的是低版本IE(比如Win7的IE8)很多新的網頁標準就無法得到支持,這時使用CefSharp來替代webbrowse... ...
  • Linux 下的 Docker 安裝與使用 一、安裝與配置 1.安裝依賴包 1 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 2.設置阿裡雲鏡像源 1 sudo yum-config-manager --add-re ...
  • 本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 按下按鈕控制LED燈的亮滅。 硬體設計 在第一節的基礎上,在Proteus中添加電路如下圖所示。 打開CubeM ...
  • win10系統不知道哪天開始,打不開組策略,區域網共用印表機也搜索不到,運行輸入gpedit.msc,就提示下圖: 百度了一大推解決方法,不是改防火牆就是win10安裝包重置系統,都沒有解決問題,最後終於找到瞭解決辦法: 因為mmc.exe不能運行,運行視窗直接輸入gpedit.msc是打不開組策略 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、版本說明 JetPack 4.6——2021.8 yolov5-v6.0版本 使用的為yolov5的yolov5n.pt,並利用tensorrtx進行加速推理,在調用攝像頭實時檢測可以達到FPS=25。 二、配置CUDA sudo gedit ...
  • 本文例子參考《STM32單片機開發實例——基於Proteus虛擬模擬與HAL/LL庫》 源代碼:https://github.com/LanLinnet/STM33F103R6 項目要求 STM32單片機控制單個LED燈亮滅,在PC0引腳控制LED燈以1s為周期閃爍。 硬體設計 在上一節的基礎上,在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...