SpringBoot——redis-starter

緩存使用指南

緩存是現(xiàn)在系統(tǒng)中必不可少的模塊,并且已經(jīng)成為了高并發(fā)高性能架構(gòu)的一個關(guān)鍵組件。這篇博客我們來分析一下使用緩存的正確姿勢。

緩存能解決的問題

提升性能

  • 絕大多數(shù)情況下,select是出現(xiàn)性能問題最大的地方。一方面,select 會有很多像 join、group、order、like等這樣豐富的語義,而這些語義是非常耗性能的;另一方面,大多數(shù)應(yīng)用都是讀多寫少,所以加劇了慢查詢的問題。
  • 分布式系統(tǒng)中遠(yuǎn)程調(diào)用也會耗很多性能,因為有網(wǎng)絡(luò)開銷,會導(dǎo)致整體的響應(yīng)時間下降。為了挽救這樣的性能開銷,在業(yè)務(wù)允許的情況(不需要太實時的數(shù)據(jù))下,使用緩存是非常必要的事情。

緩解數(shù)據(jù)庫壓力

  • 當(dāng)用戶請求增多時,數(shù)據(jù)庫的壓力將大大增加,通過緩存能夠大大降低數(shù)據(jù)庫的壓力。

緩存的適用場景

對于數(shù)據(jù)實時性要求不高

  • 對于一些經(jīng)常訪問但是很少改變的數(shù)據(jù),讀明顯多于寫,適用緩存就很有必要。比如一些網(wǎng)站配置項。

對于性能要求高

  • 比如一些秒殺活動場景。

緩存三種模式

一般來說,緩存有以下三種模式:

  • Cache Aside 更新模式
  • Read/Write Through 更新模式
  • Write Behind Caching 更新模式

通俗一點來講就是,同時更新緩存和數(shù)據(jù)庫(Cache Aside 更新模式);先更新緩存,緩存負(fù)責(zé)同步更新數(shù)據(jù)庫(Read/Write Through 更新模式);先更新緩存,緩存定時異步更新數(shù)據(jù)庫(Write Behind Caching 更新模式)。這三種模式各有優(yōu)劣,可以根據(jù)業(yè)務(wù)場景選擇使用。

Cache Aside 更新模式

這是最常用的緩存模式了,具體的流程是:

  • 失效:應(yīng)用程序先從 cache 取數(shù)據(jù),沒有得到,則從數(shù)據(jù)庫中取數(shù)據(jù),成功后,放到緩存中。
  • 命中:應(yīng)用程序從 cache 中取數(shù)據(jù),取到后返回。
  • 更新:先把數(shù)據(jù)存到數(shù)據(jù)庫中,成功后,再讓緩存失效。

注意我們上面所提到的,緩存更新時先更新數(shù)據(jù)庫,然后在讓緩存失效。那么為什么不是直接更新緩存呢?這里有一些緩存更新的坑,我們需要避免入坑。

避坑指南一

先更新數(shù)據(jù)庫,再更新緩存。這種做法最大的問題就是兩個并發(fā)的寫操作導(dǎo)致臟數(shù)據(jù)。如下圖(以Redis和Mysql為例),兩個并發(fā)更新操作,數(shù)據(jù)庫先更新的反而后更新緩存,數(shù)據(jù)庫后更新的反而先更新緩存。這樣就會造成數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致,應(yīng)用程序中讀取的都是臟數(shù)據(jù)。

避坑指南二

先刪除緩存,再更新數(shù)據(jù)庫。這個邏輯是錯誤的,因為兩個并發(fā)的讀和寫操作導(dǎo)致臟數(shù)據(jù)。如下圖(以Redis和Mysql為例)。假設(shè)更新操作先刪除了緩存,此時正好有一個并發(fā)的讀操作,沒有命中緩存后從數(shù)據(jù)庫中取出老數(shù)據(jù)并且更新回緩存,這個時候更新操作也完成了數(shù)據(jù)庫更新。此時,數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致,應(yīng)用程序中讀取的都是原來的數(shù)據(jù)(臟數(shù)據(jù))。

避坑指南三

先更新數(shù)據(jù)庫,再刪除緩存。這種做法其實不能算是坑,在實際的系統(tǒng)中也推薦使用這種方式。但是這種方式理論上還是可能存在問題。如下圖(以Redis和Mysql為例),查詢操作沒有命中緩存,然后查詢出數(shù)據(jù)庫的老數(shù)據(jù)。此時有一個并發(fā)的更新操作,更新操作在讀操作之后更新了數(shù)據(jù)庫中的數(shù)據(jù)并且刪除了緩存中的數(shù)據(jù)。然而讀操作將從數(shù)據(jù)庫中讀取出的老數(shù)據(jù)更新回了緩存。這樣就會造成數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致,應(yīng)用程序中讀取的都是原來的數(shù)據(jù)(臟數(shù)據(jù))。


但是,仔細(xì)想一想,這種并發(fā)的概率極低。因為這個條件需要發(fā)生在讀緩存時緩存失效,而且有一個并發(fā)的寫操作。實際上數(shù)據(jù)庫的寫操作會比讀操作慢得多,而且還要加鎖,而讀操作必需在寫操作前進(jìn)入數(shù)據(jù)庫操作,又要晚于寫操作更新緩存,所有這些條件都具備的概率并不大。但是為了避免這種極端情況造成臟數(shù)據(jù)所產(chǎn)生的影響,我們還是要為緩存設(shè)置過期時間。

Read/Write Through 更新模式

在上面的 Cache Aside 更新模式中,應(yīng)用代碼需要維護(hù)兩個數(shù)據(jù)存儲,一個是緩存(Cache),一個是數(shù)據(jù)庫(Repository)。而在Read/Write Through 更新模式中,應(yīng)用程序只需要維護(hù)緩存,數(shù)據(jù)庫的維護(hù)工作由緩存代理了。


Read Through

Read Through 模式就是在查詢操作中更新緩存,也就是說,當(dāng)緩存失效的時候,Cache Aside 模式是由調(diào)用方負(fù)責(zé)把數(shù)據(jù)加載入緩存,而 Read Through 則用緩存服務(wù)自己來加載。

Write Through

Write Through 模式和 Read Through 相仿,不過是在更新數(shù)據(jù)時發(fā)生。當(dāng)有數(shù)據(jù)更新的時候,如果沒有命中緩存,直接更新數(shù)據(jù)庫,然后返回。如果命中了緩存,則更新緩存,然后由緩存自己更新數(shù)據(jù)庫(這是一個同步操作)。

Write Behind Caching 更新模式

Write Behind Caching 更新模式就是在更新數(shù)據(jù)的時候,只更新緩存,不更新數(shù)據(jù)庫,而我們的緩存會異步地批量更新數(shù)據(jù)庫。這個設(shè)計的好處就是直接操作內(nèi)存速度快。因為異步,Write Behind Caching 更新模式還可以合并對同一個數(shù)據(jù)的多次操作到數(shù)據(jù)庫,所以性能的提高是相當(dāng)可觀的。

但其帶來的問題是,數(shù)據(jù)不是強(qiáng)一致性的,而且可能會丟失。另外,Write Behind Caching 更新模式實現(xiàn)邏輯比較復(fù)雜,因為它需要確認(rèn)有哪些數(shù)據(jù)是被更新了的,哪些數(shù)據(jù)需要刷到持久層上。只有在緩存需要失效的時候,才會把它真正持久起來。


總結(jié)三種緩存模式的優(yōu)缺點:

  • Cache Aside 更新模式實現(xiàn)起來比較簡單,但是需要維護(hù)兩個數(shù)據(jù)存儲,一個是緩存(Cache),一個是數(shù)據(jù)庫(Repository)。
  • Read/Write Through 更新模式只需要維護(hù)一個數(shù)據(jù)存儲(緩存),但是實現(xiàn)起來要復(fù)雜一些。
  • Write Behind Caching 更新模式和Read/Write Through 更新模式類似,區(qū)別是Write Behind Caching 更新模式的數(shù)據(jù)持久化操作是異步的,但是Read/Write Through 更新模式的數(shù)據(jù)持久化操作是同步的。優(yōu)點是直接操作內(nèi)存速度快,多次操作可以合并持久化到數(shù)據(jù)庫。缺點是數(shù)據(jù)可能會丟失,例如系統(tǒng)斷電等。

緩存是通過犧牲強(qiáng)一致性來提高性能的。所以使用緩存提升性能,就是會有數(shù)據(jù)更新的延遲。這需要我們在設(shè)計時結(jié)合業(yè)務(wù)仔細(xì)思考是否適合用緩存。然后緩存一定要設(shè)置過期時間,這個時間太短太長都不好,太短的話請求可能會比較多的落到數(shù)據(jù)庫上,這也意味著失去了緩存的優(yōu)勢。太長的話緩存中的臟數(shù)據(jù)會使系統(tǒng)長時間處于一個延遲的狀態(tài),而且系統(tǒng)中長時間沒有人訪問的數(shù)據(jù)一直存在內(nèi)存中不過期,浪費內(nèi)存。

Redis簡介

Redis是當(dāng)前比較熱門的NOSQL系統(tǒng)之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(tǒng)(區(qū)別于MySQL的二維表格的形式存儲。)。和Memcache類似,但很大程度補(bǔ)償了Memcache的不足。和Memcache一樣,Redis數(shù)據(jù)都是緩存在計算機(jī)內(nèi)存中,不同的是,Memcache只能將數(shù)據(jù)緩存到內(nèi)存中,無法自動定期寫入硬盤,這就表示,一斷電或重啟,內(nèi)存清空,數(shù)據(jù)丟失。所以Memcache的應(yīng)用場景適用于緩存無需持久化的數(shù)據(jù)。而Redis不同的是它會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,實現(xiàn)數(shù)據(jù)的持久化。

它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如字符串(Strings),散列(Hash),列表(List),集合(Set),有序集合(Sorted Set或者是ZSet)與范圍查詢,Bitmaps,Hyperloglogs 和地理空間(Geospatial)索引半徑查詢。其中常見的數(shù)據(jù)結(jié)構(gòu)類型有:String、List、Set、Hash、ZSet這5種。

Redis 內(nèi)置了復(fù)制(Replication),LUA腳本(Lua scripting), LRU驅(qū)動事件(LRU eviction),事務(wù)(Transactions) 和不同級別的磁盤持久化(Persistence),并通過 Redis哨兵(Sentinel)和自動分區(qū)(Cluster)提供高可用性(High Availability)。

Redis也提供了持久化的選項,這些選項可以讓用戶將自己的數(shù)據(jù)保存到磁盤上面進(jìn)行存儲。根據(jù)實際情況,可以每隔一定時間將數(shù)據(jù)集導(dǎo)出到磁盤(快照),或者追加到命令日志中(AOF只追加文件),他會在執(zhí)行寫命令時,將被執(zhí)行的寫命令復(fù)制到硬盤里面。您也可以關(guān)閉持久化功能,將Redis作為一個高效的網(wǎng)絡(luò)的緩存數(shù)據(jù)功能使用。

Redis不使用表,他的數(shù)據(jù)庫不會預(yù)定義或者強(qiáng)制去要求用戶對Redis存儲的不同數(shù)據(jù)進(jìn)行關(guān)聯(lián)。

數(shù)據(jù)庫的工作模式按存儲方式可分為:硬盤數(shù)據(jù)庫和內(nèi)存數(shù)據(jù)庫。Redis 將數(shù)據(jù)儲存在內(nèi)存里面,讀寫數(shù)據(jù)的時候都不會受到硬盤 I/O 速度的限制,所以速度極快。

**硬盤數(shù)據(jù)庫的工作模式: **


**內(nèi)存數(shù)據(jù)庫的工作模式: **


Redis到底有多快

Redis采用的是基于內(nèi)存的采用的是單進(jìn)程單線程模型的 KV 數(shù)據(jù)庫,由C語言編寫,官方提供的數(shù)據(jù)是可以達(dá)到100000+的QPS(每秒內(nèi)查詢次數(shù))。這個數(shù)據(jù)不比采用單進(jìn)程多線程的同樣基于內(nèi)存的 KV 數(shù)據(jù)庫 Memcached 差!有興趣的可以參考官方的基準(zhǔn)程序測試《How fast is Redis?》(https://redis.io/topics/benchmarks


橫軸是連接數(shù),縱軸是QPS。此時,這張圖反映了一個數(shù)量級,希望大家在面試的時候可以正確的描述出來,不要問你的時候,你回答的數(shù)量級相差甚遠(yuǎn)!

Redis為什么這么快

  • 1、完全基于內(nèi)存,絕大部分請求是純粹的內(nèi)存操作,非??焖佟?shù)據(jù)存在內(nèi)存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復(fù)雜度都是O(1);
  • 2、數(shù)據(jù)結(jié)構(gòu)簡單,對數(shù)據(jù)操作也簡單,Redis中的數(shù)據(jù)結(jié)構(gòu)是專門進(jìn)行設(shè)計的;
  • 3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現(xiàn)死鎖而導(dǎo)致的性能消耗;
  • 4、使用多路I/O復(fù)用模型,非阻塞IO;
  • 5、使用底層模型不同,它們之間底層實現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣,Redis直接自己構(gòu)建了VM 機(jī)制 ,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求;

以上幾點都比較好理解,下邊我們針對多路 I/O 復(fù)用模型進(jìn)行簡單的探討:

多路 I/O 復(fù)用模型

多路I/O復(fù)用模型是利用 select、poll、epoll 可以同時監(jiān)察多個流的 I/O 事件的能力,在空閑的時候,會把當(dāng)前線程阻塞掉,當(dāng)有一個或多個流有 I/O 事件時,就從阻塞態(tài)中喚醒,于是程序就會輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作。

這里“多路”指的是多個網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個線程。采用多路 I/O 復(fù)用技術(shù)可以讓單個線程高效的處理多個連接請求(盡量減少網(wǎng)絡(luò) IO 的時間消耗),且 Redis 在內(nèi)存中操作數(shù)據(jù)的速度非常快,也就是說內(nèi)存內(nèi)的操作不會成為影響Redis性能的瓶頸,主要由以上幾點造就了 Redis 具有很高的吞吐量。

那么為什么Redis是單線程的

我們首先要明白,上邊的種種分析,都是為了營造一個Redis很快的氛圍!官方FAQ表示,因為Redis是基于內(nèi)存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機(jī)器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。既然單線程容易實現(xiàn),而且CPU不會成為瓶頸,那就順理成章地采用單線程的方案了(畢竟采用多線程會有很多麻煩?。?。


注意:

這里我們一直在強(qiáng)調(diào)的單線程,只是在處理我們的網(wǎng)絡(luò)請求的時候只有一個線程來處理,一個正式的Redis Server運行的時候肯定是不止一個線程的,這里需要大家明確的注意一下!Redis 在持久化時會調(diào)用 glibc 的函數(shù)fork產(chǎn)生一個子進(jìn)程,快照持久化完全交給子進(jìn)程來處理,父進(jìn)程繼續(xù)處理客戶端請求。子進(jìn)程剛剛產(chǎn)生時,它和父進(jìn)程共享內(nèi)存里面的代碼段和數(shù)據(jù)段。這時你可以將父子進(jìn)程想像成一個連體嬰兒,共享身體。這是 Linux 操作系統(tǒng)的機(jī)制,為了節(jié)約內(nèi)存資源,所以盡可能讓它們共享起來。在進(jìn)程分離的一瞬間,內(nèi)存的增長幾乎沒有明顯變化。

Redis的特點

  • Redis讀取的速度是110000次/s,寫的速度是81000次/s
  • 原子 。Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。
  • 支持多種數(shù)據(jù)結(jié)構(gòu):string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
  • 持久化,主從復(fù)制(集群)
  • 支持過期時間,支持事務(wù),消息訂閱。
  • 官方不支持window,但是有第三方版本。

SpringBoot整合Redis

  • 1、引入maven
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.10.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 高版本redis的lettuce需要commons-pool2 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.7.0</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </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>
                <fork>true</fork>
            </configuration>
        </plugin>

        <!-- MyBatis 逆向工程 插件 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.6</version>
            <configuration>
                <!--允許移動生成的文件 -->
                <verbose>true</verbose>
                <!-- 是否覆蓋 -->
                <overwrite>true</overwrite>
                <!-- 自動生成的配置 -->
                <configurationFile>
                    ${basedir}/src/main/resources/generator/generatorConfig.xml
                </configurationFile>
            </configuration>
            <!--下面這兩個可以不配置-->
            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.18</version>
                </dependency>
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.6</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
  • 2、新建RedisConfig作為配置類
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
}
  • 3、新建RedisUtil作為Redis工具類
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    public boolean set(String key,Object value){
        try{
            redisTemplate.opsForValue().set(key,value);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    public boolean set(String key,Object value,long time){
        try{
            redisTemplate.opsForValue().set(key,value,time, TimeUnit.SECONDS);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    public Object get(String key){
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    ......
}
  • 4、application.properties加入Redis相關(guān)配置
# redis
# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
# 連接超時時間(毫秒)
spring.redis.timeout=10000

# 以下連接池已在SpringBoot2.0不推薦使用
#spring.redis.pool.max-active=8
#spring.redis.pool.max-wait=-1
#spring.redis.pool.max-idle=8
#spring.redis.pool.min-idle=0

# Jedis
#spring.redis.jredis.max-active=8
#spring.redis.jredis.max-wait=10000
#spring.redis.jredis.max-idle=8
#spring.redis.jredis.min-idle=0

# Lettuce
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
spring.redis.lettuce.pool.max-wait=10000
# 連接池中的最大空閑連接
spring.redis.lettuce.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.lettuce.pool.min-idle=0
# 關(guān)閉超時時間
spring.redis.lettuce.shutdown-timeout=100
  • 5、測試redis
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {

    @Autowired
    private RedisUtil redisUtil;

    @Test
    public void test() throws InterruptedException {
        redisUtil.set("yibo","你好",3);
        System.out.println(redisUtil.get("yibo"));
        Thread.sleep(3000);
        System.out.println(redisUtil.get("yibo"));
    }
}

企業(yè)級緩存使用

  • 定義倉儲層接口PersonRepository,采用數(shù)據(jù)庫和緩存實現(xiàn)
public interface PersonRepository {

    Person getPersonById(Integer id);
}

@Component
public class PersonRepositoryImpl implements PersonRepository {

    @Autowired
    private PersonMapper personMapper;

    public Person getPersonById(Integer id){
        return personMapper.selectByPrimaryKey(id);
    }
}

@Component
@Slf4j
public class PersonRepositoryCacheImpl implements PersonRepository {

    private static final String CACHE_PREFIX="cache_prefix_";

    @Autowired
    private RedisUtil redisUtil;

    @Resource(name="personRepositoryImpl")
    private PersonRepository personRepository;

    //這個用作緩存穿透使用
    private Person nullPerson = new Person(-1);

    //用隨機(jī)數(shù)防止緩存雪崩
    private static final Random random = new Random();

    @Override
    public Person getPersonById(Integer id) {
        Person person = getPersonFromCache(id);
        if(person == null){
            log.info("cache not hit");
            person = personRepository.getPersonById(id);
            cachePerson(id,person);
        }else if(-1 == person.getId()){
            log.warn("cache hit null");
            return null;
        }
        log.info("cache hit id");
        return person;
    }

    private void cachePerson(Integer id,Person person){
        if(person != null){
            redisUtil.set(generateCacheKey(id),JSON.toJSONString(person),random.nextInt(10)+5);
        }else {
            redisUtil.set(generateCacheKey(id),JSON.toJSONString(nullPerson),random.nextInt(10)+5);
        }
    }

    private Person getPersonFromCache(Integer id){
        String person = (String)redisUtil.get(generateCacheKey(id));
        if(StringUtils.isEmpty(person)){
            return null;
        }
        return JSON.parseObject(person,Person.class);
    }

    private String generateCacheKey(Integer id){
        return CACHE_PREFIX + id;
    }
}
  • 定義服務(wù)接口PersonService,實現(xiàn)服務(wù)接口api并調(diào)用倉儲層
public interface PersonService {

    Person getPersonById(Integer id);
}

@Service
public class PersonServiceImpl implements PersonService {

    @Resource(name="personRepositoryCacheImpl")
    private PersonRepository personRepository;

    @Override
    public Person getPersonById(Integer id) {
        return personRepository.getPersonById(id);
    }
}

緩存擊穿

  • 原因:一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進(jìn)行訪問,當(dāng)這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,導(dǎo)致DB瞬間壓力過大,壓垮DB,就像在一個屏障上鑿開了一個洞。
  • 方案:
    • 1、熱點數(shù)據(jù)緩存永不過期
    • 2、使用分布式鎖,保證同一時刻只能有一個查詢請求重新加載熱點數(shù)據(jù)到緩存中
    • 3、設(shè)置邏輯過期時間
      • 緩存中的熱點數(shù)據(jù)中冗余一個邏輯過期時間,但數(shù)據(jù)在Redis不設(shè)置過期時間
        當(dāng)一個請求拿到Redis中的數(shù)據(jù)時,判斷邏輯過期時間是否到期,如果沒有到期,直接返回,如果到期則開啟另一個線程獲得鎖后去查詢數(shù)據(jù)庫并將查詢的最新數(shù)據(jù)寫回Redis,而當(dāng)前請求返回已經(jīng)查詢的數(shù)據(jù)。

緩存穿透

  • 原因:惡意攻擊去查數(shù)據(jù)庫一定不存在的數(shù)據(jù),對數(shù)據(jù)庫造成壓力,甚至壓垮數(shù)據(jù)庫
  • 方案:
    • 1、使用特殊緩存空值標(biāo)識對象不存在
    • 2、使用布隆過濾器

緩存雪崩

  • 原因:在某一個時間段,緩存集中過期失效,對于數(shù)據(jù)庫而言,就會產(chǎn)生周期性的壓力波峰
  • 方案:
    • 1、針對大量緩存同時過期,設(shè)置緩存過期時間加上隨機(jī)因子,盡可能分散緩存過期時間
    • 2、而針對Redis宕機(jī)導(dǎo)致的緩存雪崩,可以提前搭建好Redis的主從服務(wù)器進(jìn)行數(shù)據(jù)同步,并配置哨兵機(jī)制,這樣在Redis服務(wù)器因為宕機(jī)而無法提供服務(wù)時,可以由哨兵將Redis從服務(wù)器設(shè)置為主服務(wù)器,繼續(xù)提供服務(wù)。

參考:
https://www.cnblogs.com/songwenjie/p/9027012.html

https://www.cnblogs.com/taiyonghai/p/9454764.html

https://www.cnblogs.com/jpfss/p/11016445.html

https://www.cnblogs.com/xxj-bigshow/p/10314414.html

https://blog.csdn.net/chenyao1994/article/details/79491337

https://blog.csdn.net/m0_71777195/article/details/127688572

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 緩存架構(gòu) 腦中的直觀反應(yīng) SQLAlchemy起到一定的本地緩存作用在同一請求中多次相同的查詢只查詢數(shù)據(jù)庫一次,S...
    大金葉子閱讀 3,038評論 0 2
  • 一、引言 本文談及的是后臺業(yè)務(wù)服務(wù)緩存問題,在構(gòu)建和優(yōu)化業(yè)務(wù)服務(wù)時,第一想到的應(yīng)該是優(yōu)化數(shù)據(jù)庫,比如數(shù)據(jù)庫模型設(shè)計...
    東_山_郎閱讀 1,218評論 0 8
  • 隨著移動互聯(lián)網(wǎng)的野蠻瘋長,各種互聯(lián)網(wǎng)技術(shù)層出不窮,但是不管各種技術(shù)框架如何滋長,永遠(yuǎn)無法繞過的一個坎,那就是數(shù)據(jù)庫...
    丑人林宗己閱讀 7,482評論 0 6
  • [TOC] 參考 Cache Aside Pattern 究竟先操作緩存,還是數(shù)據(jù)庫? 緩存更新的套路 使用緩存的...
    GOGOYAO閱讀 3,427評論 0 0
  • 微服務(wù)實踐目錄[http://www.itdecent.cn/p/f3d5a02757f1],可以參見連接。 緩...
    Wales_Kuo閱讀 1,380評論 1 8

友情鏈接更多精彩內(nèi)容