国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

這篇具有很好參考價(jià)值的文章主要介紹了第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

緩存預(yù)熱

緩存預(yù)熱就是系統(tǒng)啟動(dòng)前,提前將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。避免在用戶請(qǐng)求的時(shí)候,先查詢數(shù)據(jù)庫(kù),然后再將數(shù)據(jù)緩存的問(wèn)題!用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)。

可以通過(guò)@PostConstruct初始化白名單數(shù)據(jù)

緩存雪崩

發(fā)生?

  • redis主機(jī)掛了,Redis?全盤崩潰,偏硬件運(yùn)維
  • redis中有大量key同時(shí)過(guò)期大面積失效,偏軟件開發(fā)

預(yù)防+解決

  • redis中key設(shè)置為永不過(guò)期?or?過(guò)期時(shí)間錯(cuò)開
  • redis緩存集群實(shí)現(xiàn)高可用(主從+哨兵、Redis?Cluster、開啟Redis持久化機(jī)制aof/rdb,盡快恢復(fù)緩存集群)
  • 多緩存結(jié)合預(yù)防雪崩(ehcache本地緩存?+?redis緩存)
  • 服務(wù)降級(jí)(Hystrix或者阿里sentinel限流&降級(jí))

    第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透
    ?
  • 人民幣玩家” :阿里云-云數(shù)據(jù)庫(kù)Redis版

緩存穿透

是什么

請(qǐng)求去查詢一條記錄,先查redis無(wú),后查mysql無(wú),都查詢不到該條記錄,但是請(qǐng)求每次都會(huì)打到數(shù)據(jù)庫(kù)上面去,導(dǎo)致后臺(tái)數(shù)據(jù)庫(kù)壓力暴增,這種現(xiàn)象稱為緩存穿透,這個(gè)redis變成了一個(gè)擺設(shè)。。。。。。

簡(jiǎn)單說(shuō)就是本來(lái)無(wú)一物,兩庫(kù)都沒有。既不在Redis緩存庫(kù),也不在mysql,數(shù)據(jù)庫(kù)存在被多次暴擊風(fēng)險(xiǎn)。

解決

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

一圖

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

方案1:空對(duì)象緩存或者缺省值?

一般OK

第一種解決方案,回寫增強(qiáng)

如果發(fā)生了緩存穿透,我們可以針對(duì)要查詢的數(shù)據(jù),在Redis里存一個(gè)和業(yè)務(wù)部門商量后確定的缺省值(比如,零、負(fù)數(shù)、defaultNull等)。

比如,鍵uid:abcdxxx,值defaultNull作為案例的key和value

先去redis查鍵uid:abcdxxx沒有,再去mysql查沒有獲得 ,這就發(fā)生了一次穿透現(xiàn)象。

but,可以增強(qiáng)回寫機(jī)制

mysql也查不到的話也讓redis存入剛剛查不到的key并保護(hù)mysql。

第一次來(lái)查詢uid:abcdxxx,redis和mysql都沒有,返回null給調(diào)用者,但是增強(qiáng)回寫后第二次來(lái)查uid:abcdxxx,此時(shí)redis就有值了。

可以直接從Redis中讀取default缺省值返回給業(yè)務(wù)應(yīng)用程序,避免了把大量請(qǐng)求發(fā)送給mysql處理,打爆mysql。

但是,此方法架不住黑客的惡意攻擊,有缺陷......,只能解決key相同的情況

But

黑客或者惡意攻擊

黑客會(huì)對(duì)你的系統(tǒng)進(jìn)行攻擊,拿一個(gè)不存在的id去查詢數(shù)據(jù),會(huì)產(chǎn)生大量的請(qǐng)求到數(shù)據(jù)庫(kù)去查詢??赡軙?huì)導(dǎo)致你的數(shù)據(jù)庫(kù)由于壓力過(guò)大而宕掉。

key相同打你系統(tǒng)

第一次打到mysql,空對(duì)象緩存后第二次就返回defaultNull缺省值,避免mysql被攻擊,不用再到數(shù)據(jù)庫(kù)中去走一圈了。

key不同打你系統(tǒng)

由于存在空對(duì)象緩存和緩存回寫(看自己業(yè)務(wù)不限死),redis中的無(wú)關(guān)緊要的key也會(huì)越寫越多(記得設(shè)置redis過(guò)期時(shí)間)

方案2:Google布隆過(guò)濾器Guava解決緩存穿透

Guava?中布隆過(guò)濾器的實(shí)現(xiàn)算是比較權(quán)威的,所以實(shí)際項(xiàng)目中我們可以直接使用Guava布隆過(guò)濾器。

Guava’s BloomFilter源碼出處

案例:白名單過(guò)濾器

白名單架構(gòu)說(shuō)明

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

誤判問(wèn)題,但是概率小可以接受,不能從布隆過(guò)濾器刪除

全部合法的key都需要放入Guava版布隆過(guò)濾器+redis里面,不然數(shù)據(jù)就是返回null

Coding實(shí)戰(zhàn)?

建Module

redis7_study

改POM

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test.redis7</groupId>
    <artifactId>redis7_study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencies>
        <!--guava Google 開源的 Guava 中自帶的布隆過(guò)濾器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

        <!--SpringBoot通用依賴模塊-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.3.1</version>
        </dependency>

        <!--lettuce-->
        <!--<dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.2.1.RELEASE</version>
        </dependency>-->

        <!--SpringBoot與Redis整合依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--Mysql數(shù)據(jù)庫(kù)驅(qū)動(dòng)-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--SpringBoot集成druid連接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <!--mybatis和springboot整合-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.3</version>
        </dependency>

        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>

        <!--通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.1.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <!--通用基礎(chǔ)配置junit/devtools/test/log4j/lombok/-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

寫YML

server.port=7777

spring.application.name=redis7_study

# ========================logging=====================
logging.level.root=info
logging.level.com.test.redis7=info
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n 

logging.file.name=D:/mylogs2023/redis7_study.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n

# ========================swagger=====================
spring.swagger2.enabled=true
#在springboot2.6.X結(jié)合swagger2.9.X會(huì)提示documentationPluginsBootstrapper空指針異常,
#原因是在springboot2.6.X中將SpringMVC默認(rèn)路徑匹配策略從AntPathMatcher更改為PathPatternParser,
# 導(dǎo)致出錯(cuò),解決辦法是matching-strategy切換回之前ant_path_matcher
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

# ========================redis單機(jī)=====================
spring.redis.database=0
# 修改為自己真實(shí)IP
spring.redis.host=192.168.111.185
spring.redis.port=6379
spring.redis.password=111111
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

# ========================alibaba.druid=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bigdata?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false

# ========================mybatis===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.test.redis7.entities

# ========================redis集群=====================
#spring.redis.password=111111
## 獲取失敗 最大重定向次數(shù)
#spring.redis.cluster.max-redirects=3
#spring.redis.lettuce.pool.max-active=8
#spring.redis.lettuce.pool.max-wait=-1ms
#spring.redis.lettuce.pool.max-idle=8
#spring.redis.lettuce.pool.min-idle=0
##支持集群拓?fù)鋭?dòng)態(tài)感應(yīng)刷新,自適應(yīng)拓?fù)渌⑿率欠袷褂盟锌捎玫母拢J(rèn)false關(guān)閉
#spring.redis.lettuce.cluster.refresh.adaptive=true
##定時(shí)刷新
#spring.redis.lettuce.cluster.refresh.period=2000
#spring.redis.cluster.nodes=192.168.111.185:6381,192.168.111.185:6382,192.168.111.172:6383,192.168.111.172:6384,192.168.111.184:6385,192.168.111.184:6386

 

主啟動(dòng)

package com.test.redis7;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

/**
 * @auther admin
 */
@SpringBootApplication
@MapperScan("com.test.redis7.mapper") 
//import tk.mybatis.spring.annotation.MapperScan;
public class Redis7Study7777 {

    public static void main(String[] args) {
        SpringApplication.run(Redis7Study7777.class, args);
    }

}

 

業(yè)務(wù)類(取樣本100W數(shù)據(jù),查查不在100W范圍內(nèi),其它10W數(shù)據(jù)是否存在)

Case01

新建測(cè)試案例,hello入門

@Test
public void testGuavaWithBloomFilter() {
    // 創(chuàng)建布隆過(guò)濾器對(duì)象
    BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), 100);

    // 判斷指定元素是否存在
    System.out.println(filter.mightContain(1));
    System.out.println(filter.mightContain(2));

    // 將元素添加進(jìn)布隆過(guò)濾器
    filter.put(1);
    filter.put(2);

    System.out.println(filter.mightContain(1));
    System.out.println(filter.mightContain(2));
}

 

Case02

GuavaBloomFilterController

package com.test.redis7.controller;

import com.test.redis7.service.GuavaBloomFilterService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @auther admin
 */
@Slf4j
@RestController
@Api(tags = "google工具Guava處理布隆過(guò)濾器")
public class GuavaBloomFilterController {

    @Resource
    private GuavaBloomFilterService guavaBloomFilterService;

    @ApiOperation("guava布隆過(guò)濾器插入100萬(wàn)樣本數(shù)據(jù)并額外10W測(cè)試是否存在")
    @RequestMapping(value = "/guavafilter", method = RequestMethod.GET)
    public void guavaBloomFilter() {
        guavaBloomFilterService.guavaBloomFilter();
    }

}

GuavaBloomFilterService

package com.test.redis7.service;

/**
 * @auther admin
 */
public interface GuavaBloomFilterService{

   void guavaBloomFilter();

}

GuavaBloomFilterServiceImpl

package com.test.redis7.service.impl;

import com.test.redis7.service.GuavaBloomFilterService;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @auther admin
 */
@Slf4j
@Service
public class GuavaBloomFilterServiceImpl implements GuavaBloomFilterService {

    public static final int _1W = 10000;

    /**
     * 布隆過(guò)濾器里預(yù)計(jì)要插入多少數(shù)據(jù)
     */
    public static int size = 100 * _1W;

    /**
     * 誤判率,它越小誤判的個(gè)數(shù)也就越少(思考,是不是可以設(shè)置的無(wú)限小,沒有誤判豈不更好)
     * 當(dāng)不設(shè)置誤判率時(shí),默認(rèn)的誤判率為0.03,當(dāng)誤判率設(shè)置的越小,分配的bit數(shù)組與使用的hash
     * 函數(shù)個(gè)數(shù)將會(huì)越多,耗費(fèi)的資源也就越多
     * fpp the desired false positive probability
     */
    public static double fpp = 0.03;

    // 構(gòu)建布隆過(guò)濾器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

    public void guavaBloomFilter(){
        //1 先往布隆過(guò)濾器里面插入100萬(wàn)的樣本數(shù)據(jù)
        for (int i = 1; i <=size; i++) {
            bloomFilter.put(i);
        }

        //故意取10萬(wàn)個(gè)不在過(guò)濾器里的值,看看有多少個(gè)會(huì)被認(rèn)為在過(guò)濾器里
        List<Integer> list = new ArrayList<>(10 * _1W);

        for (int i = size + 1; i <= size + (10 *_1W); i++) {
            if (bloomFilter.mightContain(i)) {
                log.info("被誤判了:{}",i);
                list.add(i);
            }
        }

        log.info("誤判的總數(shù)量::{}",list.size());
    }

}
 

上一步結(jié)論

現(xiàn)在總共有10萬(wàn)數(shù)據(jù)是不存在的,誤判了3033次,

原始樣本:100W

不存在數(shù)據(jù):1000001W---1100000W

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

?可以debug源碼分析下,看看hash函數(shù)

布隆過(guò)濾器說(shuō)明

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

黑名單使用

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

緩存擊穿?

是什么?

大量的請(qǐng)求同時(shí)查詢一個(gè)?key?時(shí),此時(shí)這個(gè)key正好失效了,就會(huì)導(dǎo)致大量的請(qǐng)求都打到數(shù)據(jù)庫(kù)上面去。(備注:穿透和擊穿,截然不同)

簡(jiǎn)單說(shuō)就是熱點(diǎn)key突然失效了,暴打mysql。

危害

  • 會(huì)造成某一時(shí)刻數(shù)據(jù)庫(kù)請(qǐng)求量過(guò)大,壓力劇增。
  • 一般技術(shù)部門需要知道熱點(diǎn)key是那些個(gè)?做到心里有數(shù)防止擊穿。

解決

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

熱點(diǎn)key失效

  • 時(shí)間到了自然清除但還被訪問(wèn)到
  • delete掉的key,剛巧又被訪問(wèn)?

方案1:差異失效時(shí)間,對(duì)于訪問(wèn)頻繁的熱點(diǎn)key,干脆就不設(shè)置過(guò)期時(shí)間

方案2:互斥跟新,采用雙檢加鎖策略

多個(gè)線程同時(shí)去查詢數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請(qǐng)求上使用一個(gè) 互斥鎖來(lái)鎖住它。

其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(shù)據(jù),然后做緩存。后面的線程進(jìn)來(lái)發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存。

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

案例

天貓聚劃算功能實(shí)現(xiàn)+防止緩存擊穿

模擬高并發(fā)的天貓聚劃算案例code

是什么

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

生產(chǎn)案例網(wǎng)址

問(wèn)題,熱點(diǎn)key突然失效導(dǎo)致了緩存擊穿。

技術(shù)方案實(shí)現(xiàn)

分析過(guò)程

步驟
說(shuō)明
1
?100%高并發(fā),絕對(duì)不可以用mysql實(shí)現(xiàn)
2?
先把mysql里面參加活動(dòng)的數(shù)據(jù)抽取進(jìn)redis,一般采用定時(shí)器掃描來(lái)決定上線活動(dòng)還是下線取消。
3
?支持分頁(yè)功能,一頁(yè)20條記錄
請(qǐng)大家思考,redis里面什么樣子的數(shù)據(jù)類型支持上述功能?

高并發(fā)+定時(shí)任務(wù)+分頁(yè)顯示。。。。

redis數(shù)據(jù)類型選型

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

springboot+redis實(shí)現(xiàn)高并發(fā)的聚劃算業(yè)務(wù)V2?

建Module

修改上面redis7_study工程

業(yè)務(wù)類

entity

package com.test.redis7.entities;

import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @auther admin
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "聚劃算活動(dòng)producet信息")
public class Product {

    /**
     * 產(chǎn)品ID
     */
    private Long id;

    /**
     * 產(chǎn)品名稱
     */
    private String name;

    /**
     * 產(chǎn)品價(jià)格
     */
    private Integer price;

    /**
     * 產(chǎn)品詳情
     */
    private String detail;

}

JHSProductController

package com.test.redis7.controller;

import com.test.redis7.entities.Product;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @auther admin
 */
@Slf4j
@RestController
@Api(tags = "聚劃算商品列表接口")
public class JHSProductController {

    public  static final String JHS_KEY = "jhs";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分頁(yè)查詢:在高并發(fā)的情況下,只能走redis查詢,走db的話必定會(huì)把db打垮
     * @param page 當(dāng)前頁(yè)
     * @param size 頁(yè)面容量
     * @return 商品列表
     */
    @ApiOperation("按照分頁(yè)和每頁(yè)顯示容量,點(diǎn)擊查看")
    @RequestMapping(value = "/pruduct/find", method = RequestMethod.GET)
    public List<Product> find(int page, int size) {
        List<Product> list = null;

        long start = (page - 1) * size;
        long end = start + size - 1;

        try {
            //采用redis list數(shù)據(jù)結(jié)構(gòu)的lrange命令實(shí)現(xiàn)分頁(yè)查詢
            list = this.redisTemplate.opsForList().range(JHS_KEY, start, end);

            if (CollectionUtils.isEmpty(list)) {
                //TODO 走DB查詢
            }

            log.info("查詢結(jié)果:{}", list);
        } catch (Exception ex) {
            //這里的異常,一般是redis癱瘓 ,或 redis網(wǎng)絡(luò)timeout
            log.error("exception:", ex);
            //TODO 走DB查詢
        }

        return list;
    }
}

JHSTaskService

package com.test.redis7.service;

import cn.hutool.core.date.DateUtil;
import com.test.redis7.entities.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @auther admin
 */
@Slf4j
@Service
public class JHSTaskService {

    public  static final String JHS_KEY = "jhs";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 偷個(gè)懶不加mybatis了,模擬從數(shù)據(jù)庫(kù)讀取100件特價(jià)商品,用于加載到聚劃算的頁(yè)面中
     * @return 商品列表
     */
    private List<Product> getProductsFromMysql() {
        List<Product> list = new ArrayList<>();

        for (int i = 1; i <= 20; i++) {
            Random rand = new Random();
            int id = rand.nextInt(10000);
            Product obj = new Product((long) id, "product" + i, i, "detail");
            list.add(obj);
        }

        return list;
    }

    @PostConstruct
    public void initJHS(){
        log.info("啟動(dòng)定時(shí)器淘寶聚劃算功能模擬.........." + DateUtil.now());

        new Thread(() -> {
            //模擬定時(shí)器一個(gè)后臺(tái)任務(wù),定時(shí)把數(shù)據(jù)庫(kù)的特價(jià)商品,刷新到redis中
            while (true){
                //模擬從數(shù)據(jù)庫(kù)讀取100件特價(jià)商品,用于加載到聚劃算的頁(yè)面中
                List<Product> list = this.getProductsFromMysql();
                //采用redis list數(shù)據(jù)結(jié)構(gòu)的lpush來(lái)實(shí)現(xiàn)存儲(chǔ)
                this.redisTemplate.delete(JHS_KEY);
                //lpush命令
                this.redisTemplate.opsForList().leftPushAll(JHS_KEY, list);

                //間隔一分鐘 執(zhí)行一遍,模擬聚劃算每3天刷新一批次參加活動(dòng)
                try { 
                    TimeUnit.MINUTES.sleep(1); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                }

                log.info("runJhs定時(shí)刷新..............");
            }
        }, "t1").start();
    }
}

備注

至此步驟,上述聚劃算的功能算是完成,請(qǐng)思考在高并發(fā)下有什么經(jīng)典生產(chǎn)問(wèn)題?

Bug和隱患說(shuō)明

熱點(diǎn)key突然失效導(dǎo)致可怕的緩存擊穿

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

?delete命令執(zhí)行的一瞬間有空隙,其它請(qǐng)求線程繼續(xù)找Redis為null

打到了mysql,暴擊......

復(fù)習(xí)again

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

最終目的

2條命令原子性還是其次,主要是防止熱key突然失效暴擊mysql打爆系統(tǒng),^_^。

進(jìn)一步升級(jí)加固案例

復(fù)習(xí),互斥跟新,采用雙檢加鎖策略

多個(gè)線程同時(shí)去查詢數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請(qǐng)求上使用一個(gè) 互斥鎖來(lái)鎖住它。

其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(shù)據(jù),然后做緩存。后面的線程進(jìn)來(lái)發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存。

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

?差異失效時(shí)間

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

JHSProductController

package com.test.redis7.controller;

import com.test.redis7.entities.Product;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @auther admin
 */
@Slf4j
@RestController
@Api(tags = "聚劃算商品列表接口")
public class JHSProductController {

    public  static final String JHS_KEY = "jhs";

    public  static final String JHS_KEY_A = "jhs:a";

    public  static final String JHS_KEY_B = "jhs:b";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分頁(yè)查詢:在高并發(fā)的情況下,只能走redis查詢,走db的話必定會(huì)把db打垮
     * @param page 當(dāng)前頁(yè)
     * @param size 頁(yè)面容量
     * @return 商品列表
     */
    @ApiOperation("按照分頁(yè)和每頁(yè)顯示容量,點(diǎn)擊查看")
    @RequestMapping(value = "/pruduct/find", method = RequestMethod.GET)
    public List<Product> find(int page, int size) {
        List<Product> list=null;

        long start = (page - 1) * size;
        long end = start + size - 1;

        try {
            //采用redis list數(shù)據(jù)結(jié)構(gòu)的lrange命令實(shí)現(xiàn)分頁(yè)查詢
            list = this.redisTemplate.opsForList().range(JHS_KEY, start, end);

            if (CollectionUtils.isEmpty(list)) {
                //TODO 走DB查詢
            }

            log.info("查詢結(jié)果:{}", list);
        } catch (Exception ex) {
            //這里的異常,一般是redis癱瘓 ,或 redis網(wǎng)絡(luò)timeout
            log.error("exception:", ex);
            //TODO 走DB查詢
        }

        return list;
    }

    /**
     * 防止熱點(diǎn)key突然失效,AB雙緩存架構(gòu)
     * @param page 當(dāng)前頁(yè)
     * @param size 頁(yè)面容量
     * @return 商品列表
     */
    @ApiOperation("防止熱點(diǎn)key突然失效,AB雙緩存架構(gòu)")
    @RequestMapping(value = "/pruduct/findab", method = RequestMethod.GET)
    public List<Product> findAB(int page, int size) {
        List<Product> list = null;
        long start = (page - 1) * size;
        long end = start + size - 1;

        try {
            //采用redis list數(shù)據(jù)結(jié)構(gòu)的lrange命令實(shí)現(xiàn)分頁(yè)查詢
            list = this.redisTemplate.opsForList().range(JHS_KEY_A, start, end);

            if (CollectionUtils.isEmpty(list)) {
                log.info("=========A緩存已經(jīng)失效了,記得人工修補(bǔ),B緩存自動(dòng)延續(xù)5天");
                //用戶先查詢緩存A(上面的代碼),如果緩存A查詢不到(例如,更新緩存的時(shí)候刪除了),再查詢緩存B
                list = this.redisTemplate.opsForList().range(JHS_KEY_B, start, end);

                if (CollectionUtils.isEmpty(list)) {
                    //TODO 走DB查詢
                }
            }

            log.info("查詢結(jié)果:{}", list);
        } catch (Exception ex) {
            //這里的異常,一般是redis癱瘓 ,或 redis網(wǎng)絡(luò)timeout
            log.error("exception:", ex);
            //TODO 走DB查詢
        }

        return list;
    }
}

JHSTaskService?

package com.test.redis7.service;

import cn.hutool.core.date.DateUtil;
import com.test.redis7.entities.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @auther admin
 */
@Slf4j
@Service
public class JHSTaskService {

    public  static final String JHS_KEY = "jhs";

    public  static final String JHS_KEY_A = "jhs:a";

    public  static final String JHS_KEY_B = "jhs:b";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 偷個(gè)懶不加mybatis了,模擬從數(shù)據(jù)庫(kù)讀取100件特價(jià)商品,用于加載到聚劃算的頁(yè)面中
     * @return 商品列表
     */
    private List<Product> getProductsFromMysql() {
        List<Product> list = new ArrayList<>();

        for (int i = 1; i <= 20; i++) {
            Random rand = new Random();
            int id= rand.nextInt(10000);
            Product obj=new Product((long) id, "product" + i, i, "detail");
            list.add(obj);
        }

        return list;
    }

    //@PostConstruct
    public void initJHS(){
        log.info("啟動(dòng)定時(shí)器淘寶聚劃算功能模擬.........." + DateUtil.now());

        new Thread(() -> {
            //模擬定時(shí)器,定時(shí)把數(shù)據(jù)庫(kù)的特價(jià)商品,刷新到redis中
            while (true){
                //模擬從數(shù)據(jù)庫(kù)讀取100件特價(jià)商品,用于加載到聚劃算的頁(yè)面中
                List<Product> list = this.getProductsFromMysql();
                //采用redis list數(shù)據(jù)結(jié)構(gòu)的lpush來(lái)實(shí)現(xiàn)存儲(chǔ)
                this.redisTemplate.delete(JHS_KEY);
                //lpush命令
                this.redisTemplate.opsForList().leftPushAll(JHS_KEY, list);

                //間隔一分鐘 執(zhí)行一遍
                try { 
                    TimeUnit.MINUTES.sleep(1); 
                } catch (InterruptedException e) { 
                    e.printStackTrace();
                }

                log.info("runJhs定時(shí)刷新..............");
            }
        }, "t1").start();
    }

    @PostConstruct
    public void initJHSAB(){
        log.info("啟動(dòng)AB定時(shí)器計(jì)劃任務(wù)淘寶聚劃算功能模擬.........." + DateUtil.now());

        new Thread(() -> {
            //模擬定時(shí)器,定時(shí)把數(shù)據(jù)庫(kù)的特價(jià)商品,刷新到redis中
            while (true){
                //模擬從數(shù)據(jù)庫(kù)讀取100件特價(jià)商品,用于加載到聚劃算的頁(yè)面中
                List<Product> list = this.getProductsFromMysql();
                //先更新B緩存
                this.redisTemplate.delete(JHS_KEY_B);
                this.redisTemplate.opsForList().leftPushAll(JHS_KEY_B, list);
                this.redisTemplate.expire(JHS_KEY_B, 20L, TimeUnit.DAYS);
                //再更新A緩存
                this.redisTemplate.delete(JHS_KEY_A);
                this.redisTemplate.opsForList().leftPushAll(JHS_KEY_A, list);
                this.redisTemplate.expire(JHS_KEY_A, 15L, TimeUnit.DAYS);

                //間隔一分鐘 執(zhí)行一遍
                try { 
                    TimeUnit.MINUTES.sleep(1);
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                }

                log.info("runJhs定時(shí)刷新雙緩存AB兩層..............");
            }
        }, "t1").start();
    }
}

總結(jié)

第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

緩存雪崩

解決方案:

  • ??? Redis緩存集群實(shí)現(xiàn)高可用(合理設(shè)置過(guò)期時(shí)間或者延時(shí),采用哨兵集群等)
  • ??? 采用多級(jí)緩存策略,例如 Nginx緩存+Redis緩存+ehcache緩存
  • ??? 采用Hystrix或者阿里sentinel進(jìn)行服務(wù)限流限流或者降級(jí)
  • ??? 監(jiān)控Redis各項(xiàng)指標(biāo),即使災(zāi)難預(yù)警
  • ??? 采用AOF/ RDB持久化,盡快恢復(fù)Redis集群

緩存擊穿

解決方案:

  • 對(duì)于熱點(diǎn)key加長(zhǎng)過(guò)期時(shí)間,或者干脆不設(shè)置過(guò)期時(shí)間
  • 二級(jí)緩存設(shè)置不同的失效時(shí)間,保證不會(huì)同時(shí)失效
  • 如果緩存中沒有該key則加鎖,保證只會(huì)有一個(gè)線程打到數(shù)據(jù)庫(kù)進(jìn)行查詢(效率較低)

緩存穿透

解決方案:

  • 緩存空對(duì)象或者缺省值
    • 如果在數(shù)據(jù)庫(kù)也無(wú)法查詢,同樣回寫到緩存中null值,第二次請(qǐng)求就不會(huì)落到數(shù)據(jù)庫(kù)中
    • 如果黑客惡意攻擊每次采取不同的ID那么緩存將越寫越多,所以要設(shè)置過(guò)期時(shí)間,并且大量請(qǐng)求還是直接打中了數(shù)據(jù)庫(kù)所以該方案有缺陷
  • 使用布隆過(guò)濾器

大廠真實(shí)需求+面試題?

緩存預(yù)熱、雪崩、穿透、擊穿分別是什么?你遇到過(guò)那幾個(gè)情況?

緩存預(yù)熱你是怎么做的?

如何避免或者減少緩存雪崩?

穿透和擊穿有什么區(qū)別?他兩是一個(gè)意思還是截然不同?

穿透和擊穿你有什么解決方案?如何避免?

假如出現(xiàn)了緩存不一致,你有哪些修補(bǔ)方案?

。。。。。。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-468353.html

到了這里,關(guān)于第十八章_Redis緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Redis 之 緩存預(yù)熱 & 緩存雪崩 & 緩存擊穿 & 緩存穿透

    Redis 之 緩存預(yù)熱 & 緩存雪崩 & 緩存擊穿 & 緩存穿透

    目錄 一、緩存預(yù)熱 1.1 緩存預(yù)熱是什么? 1.2?解決方案: 二、緩存雪崩 2.1?緩存雪崩是什么?怎么發(fā)生的? 2.2 怎么解決 三、緩存穿透 3.1 是什么?怎么產(chǎn)生的呢? 3.2 解決方案 ?3.2.1、采用回寫增強(qiáng), ?3.2.2、加上一個(gè)布隆過(guò)濾器, 四、緩存擊穿 4.1 是什么? 4.2 怎么解決 五

    2024年02月13日
    瀏覽(33)
  • Redis7之緩存預(yù)熱 + 緩存雪崩 + 緩存擊穿 + 緩存穿透(八)

    Redis7之緩存預(yù)熱 + 緩存雪崩 + 緩存擊穿 + 緩存穿透(八)

    緩存預(yù)熱就是系統(tǒng)上線后,提前將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。避免在用戶請(qǐng)求的時(shí)候,先查詢數(shù)據(jù)庫(kù),然后再將數(shù)據(jù)緩存的問(wèn)題!用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)! 使用 @PostConstruct 初始化白名單數(shù)據(jù) 緩存雪崩就是瞬間過(guò)期數(shù)據(jù)量太大,導(dǎo)致對(duì)數(shù)據(jù)庫(kù)服務(wù)器造

    2023年04月08日
    瀏覽(27)
  • 探討Redis緩存問(wèn)題及解決方案:緩存穿透、緩存擊穿、緩存雪崩與緩存預(yù)熱(如何解決Redis緩存中的常見問(wèn)題并提高應(yīng)用性能)

    探討Redis緩存問(wèn)題及解決方案:緩存穿透、緩存擊穿、緩存雪崩與緩存預(yù)熱(如何解決Redis緩存中的常見問(wèn)題并提高應(yīng)用性能)

    Redis是一種非常流行的開源緩存系統(tǒng),用于緩存數(shù)據(jù)以提高應(yīng)用程序性能。但是,如果我們不注意一些緩存問(wèn)題,Redis也可能會(huì)導(dǎo)致一些性能問(wèn)題。在本文中,我們將探討Redis中的一些常見緩存問(wèn)題,并提供解決方案。 緩存穿透指的是當(dāng)一個(gè)請(qǐng)求嘗試訪問(wèn)一個(gè)不存在于緩存中的

    2024年02月03日
    瀏覽(169)
  • 14、緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

    14、緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透

    緩存預(yù)熱+緩存雪崩+緩存擊穿+緩存穿透 ● 緩存預(yù)熱、雪崩、穿透、擊穿分別是什么?你遇到過(guò)那幾個(gè)情況? ● 緩存預(yù)熱你是怎么做到的? ● 如何避免或者減少緩存雪崩? ● 穿透和擊穿有什么區(qū)別?它兩一個(gè)意思還是截然不同? ● 穿透和擊穿你有什么解決方案?如何避免

    2024年02月12日
    瀏覽(20)
  • 07_緩存預(yù)熱&緩存雪崩&緩存擊穿&緩存穿透

    07_緩存預(yù)熱&緩存雪崩&緩存擊穿&緩存穿透

    提前將數(shù)據(jù)從數(shù)據(jù)庫(kù)同步到redis。 在程序啟動(dòng)的時(shí)候,直接將數(shù)據(jù)刷新到redis 懶加載,用戶訪問(wèn)的時(shí)候,第一次查詢數(shù)據(jù)庫(kù),然后將數(shù)據(jù)寫入redis 發(fā)生情況 redis主機(jī)掛了,redis全盤崩潰,偏硬件運(yùn)維 redis中有大量key同時(shí)過(guò)期或大面積失效,偏軟件開發(fā) 預(yù)防解決方法 redis中key設(shè)

    2024年02月12日
    瀏覽(28)
  • Redis 緩存穿透、緩存雪崩、緩存擊穿

    Redis 緩存穿透、緩存雪崩、緩存擊穿

    緩存穿透是指客戶端請(qǐng)求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫(kù)中都不存在,這樣緩存永遠(yuǎn)不會(huì)生效,這些請(qǐng)求都會(huì)打到數(shù)據(jù)庫(kù)。 常見的解決方案有兩種: ????????緩存空對(duì)象 ???????? ????????優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,維護(hù)方便 ????????????????缺點(diǎn): 額外的內(nèi)存消耗 可能造

    2024年02月02日
    瀏覽(28)
  • redis 緩存雪崩 && 緩存擊穿 && 緩存穿透

    什么是緩存雪崩 當(dāng)我們提到緩存系統(tǒng)中的問(wèn)題,緩存雪崩是一個(gè)經(jīng)常被討論的話題。緩存雪崩是指在某一時(shí)刻發(fā)生大量的緩存失效,導(dǎo)致瞬間大量的請(qǐng)求直接打到了數(shù)據(jù)庫(kù),可能會(huì)導(dǎo)致數(shù)據(jù)庫(kù)瞬間壓力過(guò)大甚至宕機(jī)。尤其在高并發(fā)的系統(tǒng)中,這種情況會(huì)導(dǎo)致連鎖反應(yīng),整個(gè)系

    2024年02月07日
    瀏覽(30)
  • Redis緩存擊穿、雪崩、穿透

    我們通過(guò)對(duì)數(shù)據(jù)庫(kù)的數(shù)據(jù)進(jìn)行查詢后在redis中緩存,但是在高并發(fā)環(huán)境下,某個(gè)保存的key值在失效的瞬間被大量并發(fā)請(qǐng)求訪問(wèn),這些持續(xù)的大并發(fā)量就會(huì)穿破緩存,直接請(qǐng)求數(shù)據(jù)庫(kù),會(huì)對(duì)數(shù)據(jù)庫(kù)造成巨大壓力,這些請(qǐng)求還會(huì)使數(shù)據(jù)多次寫入緩存,多了許多不必要的操作。 ? ?

    2024年02月08日
    瀏覽(26)
  • REDIS緩存穿透 擊穿 雪崩

    REDIS緩存穿透 擊穿 雪崩

    一、前言 ? 在我們?nèi)粘5拈_發(fā)中,無(wú)不都是使用數(shù)據(jù)庫(kù)來(lái)進(jìn)行數(shù)據(jù)的存儲(chǔ),由于一般的系統(tǒng)任務(wù)中通常不會(huì)存在高并發(fā)的情況,所以這樣看起來(lái)并沒有什么問(wèn)題,可是一旦涉及大數(shù)據(jù)量的需求,比如一些商品搶購(gòu)的情景,或者是主頁(yè)訪問(wèn)量瞬間較大的時(shí)候,單一使用數(shù)據(jù)庫(kù)來(lái)

    2024年02月09日
    瀏覽(22)
  • Redis緩存穿透,雪崩,擊穿

    1、定義 緩存 就是數(shù)據(jù)交換的 緩沖區(qū) ,緩存就是 緩沖區(qū)內(nèi)的數(shù)據(jù) ,一般從數(shù)據(jù)庫(kù)中獲取,存儲(chǔ)于本地代碼。 由于其被 Static 修飾,所以隨著類的加載而被加載到 內(nèi)存之中 ,作為本地緩存,由于其又被 final 修飾,所以其引用和對(duì)象之間的關(guān)系是固定的,不能改變,因此不用擔(dān)心賦值(=

    2024年02月10日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包