自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

20 圖|緩存實(shí)戰(zhàn)之一

存儲(chǔ) 存儲(chǔ)軟件
20 年前常見的系統(tǒng)就是單機(jī)的,比如 ERP 系統(tǒng),對(duì)性能要求不高,使用緩存的并不常見,但現(xiàn)如今,已經(jīng)步入到互聯(lián)網(wǎng)時(shí)代,高并發(fā)、高可用、高性能總是被提起,而緩存在這“三高”中立下汗馬功勞。

[[394864]]

前言

先說(shuō)個(gè)小事情,今天試了下做動(dòng)圖,就一張動(dòng)圖都花了我 1 個(gè)小時(shí),還做得很難看。。在線求個(gè)做動(dòng)圖的好軟件

本文主要內(nèi)容如下:

上一篇講到如何做性能調(diào)優(yōu)的方式:《48 張圖 | 手摸手教你微服務(wù)的性能監(jiān)控、壓測(cè)和調(diào)優(yōu)》,比如給表加索引、動(dòng)靜分離、減少不必要的日志打印。但有一個(gè)很強(qiáng)大的優(yōu)化方式?jīng)]有提到,那就是加緩存,比如查詢小程序的廣告位配置,因?yàn)闆]什么人會(huì)去頻繁的改,將廣告位配置丟到緩存里面再適合不過(guò)了。那我們就給開源 Spring Cloud 實(shí)戰(zhàn)項(xiàng)目 PassJava 加下緩存來(lái)提升下性能。

我把后端、前端、小程序都上傳到同一個(gè)倉(cāng)庫(kù)里面了,大家可以通過(guò) Github 或 碼云訪問(wèn)。地址如下:

Github: https://github.com/Jackson0714/PassJava-Platform

碼云:https://gitee.com/jayh2018/PassJava-Platform

配套教程:www.passjava.cn

在實(shí)戰(zhàn)之前,我們先來(lái)看下使用緩存的原理和問(wèn)題。

一、緩存

1.1 為什么要用緩存

20 年前常見的系統(tǒng)就是單機(jī)的,比如 ERP 系統(tǒng),對(duì)性能要求不高,使用緩存的并不常見,但現(xiàn)如今,已經(jīng)步入到互聯(lián)網(wǎng)時(shí)代,高并發(fā)、高可用、高性能總是被提起,而緩存在這“三高”中立下汗馬功勞。

我們通過(guò)會(huì)將部分?jǐn)?shù)據(jù)放入緩存中,來(lái)提高訪問(wèn)速度,然后數(shù)據(jù)庫(kù)承擔(dān)存儲(chǔ)的工作。

那么哪些數(shù)據(jù)適合放入緩存中呢?

  • 即時(shí)性。例如查詢最新的物流狀態(tài)信息。
  • 數(shù)據(jù)一致性要求不高。例如門店信息,修改后,數(shù)據(jù)庫(kù)中已經(jīng)改了,5 分鐘后緩存中才是最新的,但不影響功能使用。
  • 訪問(wèn)量大且更新頻率不高。比如首頁(yè)的廣告信息,訪問(wèn)量,但是不會(huì)經(jīng)常變化。

當(dāng)我們想要查詢數(shù)據(jù)時(shí),使用緩存的流程如下:

讀模式緩存使用流程

1.2 本地緩存

比如現(xiàn)在有一個(gè)需求:前端小程序需要查詢題目的類型,而題目類型放在小程序的首頁(yè)在,訪問(wèn)量是非常高的,但是又不是經(jīng)常變化的數(shù)據(jù),所以可以將題目類型數(shù)據(jù)放到緩存中。

最簡(jiǎn)單的使用緩存的方式是使用本地緩存,也就是在內(nèi)存中緩存數(shù)據(jù),可以用 HashMap、數(shù)組等數(shù)據(jù)結(jié)構(gòu)來(lái)緩存數(shù)據(jù)。

1.2.1 不使用緩存

我們先來(lái)看下不使用緩存的情況:前端的請(qǐng)求先經(jīng)過(guò)網(wǎng)關(guān),然后請(qǐng)求到題目微服務(wù),然后查詢數(shù)據(jù)庫(kù),返回查詢結(jié)果。

再來(lái)看下核心代碼是怎么樣的。

先自定義一個(gè) Rest API 用來(lái)查詢題目類型列表,數(shù)據(jù)是從數(shù)據(jù)庫(kù)查詢出來(lái)后直接返回給前端。

  1. @RequestMapping("/list"
  2. public R list(){ 
  3.     // 從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù) 
  4.     typeEntityList = ITypeService.list();  
  5.     return R.ok().put("typeEntityList", typeEntityList); 

1.2.2 使用緩存

來(lái)看下使用緩存的情況:前端先經(jīng)過(guò)網(wǎng)關(guān),然后到題目微服務(wù),先判斷緩存中有沒有數(shù)據(jù),如果沒有,則查詢數(shù)據(jù)庫(kù)再更新緩存,最后返回查詢到的結(jié)果。

那我們現(xiàn)在創(chuàng)建一個(gè) HashMap 來(lái)緩存題目的類型列表:

  1. private Map<String, Object> cache = new HashMap<>(); 

先獲取緩存的類型列表

  1. List<TypeEntity> typeEntityListCache = (List<TypeEntity>) cache.get("typeEntityList"); 

如果緩存中沒有,則先從數(shù)據(jù)庫(kù)中獲取。當(dāng)然,第一次查詢緩存時(shí),肯定是沒有這個(gè)數(shù)據(jù)的。

  1. // 如果緩存中沒有數(shù)據(jù) 
  2. if (typeEntityListCache == null) { 
  3.   System.out.println("The cache is empty"); 
  4.   // 從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù) 
  5.   List<TypeEntity> typeEntityList = ITypeService.list(); 
  6.   // 將數(shù)據(jù)放入緩存中 
  7.   typeEntityListCache = typeEntityList; 
  8.   cache.put("typeEntityList", typeEntityList); 
  9. return R.ok().put("typeEntityList", typeEntityListCache); 

我們用 Postman 工具來(lái)看下查詢結(jié)果:

  1. 請(qǐng)求URL:https://github.com/Jackson0714/PassJava-Platform 

返回了題目類型列表,共 14 條數(shù)據(jù)。

以后再次查詢時(shí),因?yàn)榫彺嬷幸呀?jīng)有該數(shù)據(jù)了,所以直接走緩存,不會(huì)再?gòu)臄?shù)據(jù)庫(kù)中查詢數(shù)據(jù)了。

從上面的例子中我們可以知道本地緩存有哪些優(yōu)點(diǎn)呢?

  • 減少和數(shù)據(jù)庫(kù)的交互,降低因磁盤 I/O 引起的性能問(wèn)題。
  • 避免數(shù)據(jù)庫(kù)的死鎖問(wèn)題。
  • 加速相應(yīng)速度。

當(dāng)然,本地緩存也存在一些問(wèn)題:

  • 占用本地內(nèi)存資源。
  • 機(jī)器宕機(jī)重啟后,緩存丟失。
  • 可能會(huì)存在數(shù)據(jù)庫(kù)數(shù)據(jù)和緩存數(shù)據(jù)不一致的問(wèn)題。
  • 同一臺(tái)機(jī)器中的多個(gè)微服務(wù)緩存的數(shù)據(jù)不一致。

  • 集群環(huán)境下存在緩存的數(shù)據(jù)不一致的問(wèn)題。

基于本地緩存的問(wèn)題,我們引入了分布式緩存 Redis 來(lái)解決。

二、緩存 Redis

2.1 Docker 安裝 Redis

首先需要安裝 Redis,我是通過(guò) Docker 來(lái)安裝 Redis。另外我在 ubuntu 和 Mac M1 上都裝過(guò) docker 版的 Redis,大家可以參照這兩篇來(lái)安裝。

《Ubuntu 上到 Docker 安裝redis》

《M1 和 Docker 談了個(gè)戀愛...》

2.2 引入 Redis 組件

我用的是 passjava-question 微服務(wù),所以是在 passjava-question 模塊下的配置文件 pom.xml 中引入 redis 組件。

文件路徑:/passjava-question/pom.xml

  1. <dependency> 
  2.     <groupId>org.springframework.boot</groupId> 
  3.     <artifactId>spring-boot-starter-data-redis</artifactId> 
  4. </dependency> 

 

2.3 測(cè)試 Redis

我們可以寫一個(gè)測(cè)試方法來(lái)測(cè)試引入的 redis 是否能存數(shù)據(jù),以及能否查出存的數(shù)據(jù)。

我們都是使用 StringRedisTemplate 庫(kù)來(lái)操作 Redis,所以可以自動(dòng)裝載下 StringRedisTemplate。

  1. @Autowired 
  2. StringRedisTemplate stringRedisTemplate; 

然后在測(cè)試方法中,測(cè)試存儲(chǔ)方法:ops.set(),以及 查詢方法:ops.get()

  1. @Test 
  2. public void TestStringRedisTemplate() { 
  3.     // 初始化 redis 組件 
  4.     ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); 
  5.     // 存儲(chǔ)數(shù)據(jù) 
  6.     ops.set("悟空""悟空聊架構(gòu)_" + UUID.randomUUID().toString()); 
  7.     // 查詢數(shù)據(jù) 
  8.     String wukong = ops.get("悟空"); 
  9.     System.out.println(wukong); 

set 方法的第一個(gè)參數(shù)是 key,比如示例中的 “悟空”。

get 方法的參數(shù)也是 key。

最后打印出了 redis 中 key = “悟空” 的緩存的值:

另外也可以通過(guò)客戶端工具來(lái)查看,如下圖所示:

我下載的是這個(gè)軟件:Redis Desktop Manager windows,Mac M1 上正常使用。下載地址:

  1. http://www.pc6.com/softview/SoftView_450180.html 

2.4 用 Redis 改造業(yè)務(wù)邏輯

用 redis 替換 hashmap 也不難,把用到 hashmap 的地方都用 redis 改下。另外需要注意的是:

從數(shù)據(jù)庫(kù)中查詢到的數(shù)據(jù)先要序列化成 JSON 字符串后再存入到 Redis 中,從 Redis 中查詢數(shù)據(jù)時(shí),也需要將 JSON 字符串反序列化為對(duì)象實(shí)例。

  1. public List<TypeEntity> getTypeEntityList() { 
  2.   // 1.初始化 redis 組件 
  3.   ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); 
  4.   // 2.從緩存中查詢數(shù)據(jù) 
  5.   String typeEntityListCache = ops.get("typeEntityList"); 
  6.   // 3.如果緩存中沒有數(shù)據(jù) 
  7.   if (StringUtils.isEmpty(typeEntityListCache)) { 
  8.     System.out.println("The cache is empty"); 
  9.     // 4.從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù) 
  10.     List<TypeEntity> typeEntityListFromDb = this.list(); 
  11.     // 5.將從數(shù)據(jù)庫(kù)中查詢出的數(shù)據(jù)序列化 JSON 字符串 
  12.     typeEntityListCache = JSON.toJSONString(typeEntityListFromDb); 
  13.     // 6.將序列化后的數(shù)據(jù)存入緩存中 
  14.     ops.set("typeEntityList", typeEntityListCache); 
  15.     return typeEntityListFromDb; 
  16.   } 
  17.   // 7.如果緩存中有數(shù)據(jù),則從緩存中拿出來(lái),并反序列化為實(shí)例對(duì)象 
  18.   List<TypeEntity> typeEntityList = JSON.parseObject(typeEntityListCache, new TypeReference<List<TypeEntity>>(){}); 
  19.   return typeEntityList; 

整個(gè)流程如下:

  • 1.初始化 redis 組件。
  • 2.從緩存中查詢數(shù)據(jù)。
  • 3.如果緩存中沒有數(shù)據(jù),執(zhí)行步驟 4、5、6。
  • 4.從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)。
  • 5.將從數(shù)據(jù)庫(kù)中查詢出的數(shù)據(jù)轉(zhuǎn)化為 JSON 字符串。
  • 6.將序列化后的數(shù)據(jù)存入緩存中,并返回?cái)?shù)據(jù)庫(kù)中查詢到的數(shù)據(jù)。
  • 7.如果緩存中有數(shù)據(jù),則從緩存中拿出來(lái),并反序列化為實(shí)例對(duì)象。

2.5 測(cè)試業(yè)務(wù)邏輯

我們還是用 postman 工具進(jìn)行測(cè)試:

通過(guò)多次測(cè)試,第一次請(qǐng)求會(huì)稍微慢點(diǎn),后面幾次速度非常快。說(shuō)明使用緩存后性能有提升。

另外我們用 Redis 客戶端看下結(jié)果:

Redis key = typeEntityList,Redis value 是一個(gè) JSON 字符串,里面的內(nèi)容是題目分類列表。

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

高并發(fā)下使用緩存會(huì)帶來(lái)的幾個(gè)問(wèn)題:緩存穿透、雪崩、擊穿。

3.1 緩存穿透

3.1.1 緩存穿透的概念

緩存穿透指一個(gè)一定不存在的數(shù)據(jù),由于緩存未命中這條數(shù)據(jù),就會(huì)去查詢數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)也沒有這條數(shù)據(jù),所以返回結(jié)果是 null。如果每次查詢都走數(shù)據(jù)庫(kù),則緩存就失去了意義,就像穿透了緩存一樣。

3.1.2 帶來(lái)的風(fēng)險(xiǎn)

利用不存在的數(shù)據(jù)進(jìn)行攻擊,數(shù)據(jù)庫(kù)壓力增大,最終導(dǎo)致系統(tǒng)崩潰。

3.1.3 解決方案

對(duì)結(jié)果 null 進(jìn)行緩存,并加入短暫的過(guò)期時(shí)間。

3.2 緩存雪崩

3.2.1 緩存雪崩的概念

緩存雪崩是指我們緩存多條數(shù)據(jù)時(shí),采用了相同的過(guò)期時(shí)間,比如 00:00:00 過(guò)期,如果這個(gè)時(shí)刻緩存同時(shí)失效,而有大量請(qǐng)求進(jìn)來(lái)了,因未緩存數(shù)據(jù),所以都去查詢數(shù)據(jù)庫(kù)了,數(shù)據(jù)庫(kù)壓力增大,最終就會(huì)導(dǎo)致雪崩。

3.2.2 帶來(lái)的風(fēng)險(xiǎn)

嘗試找到大量 key 同時(shí)過(guò)期的時(shí)間,在某時(shí)刻進(jìn)行大量攻擊,數(shù)據(jù)庫(kù)壓力增大,最終導(dǎo)致系統(tǒng)崩潰。

3.2.3 解決方案

在原有的實(shí)效時(shí)間基礎(chǔ)上增加一個(gè)碎擠汁,比如 1-5 分鐘隨機(jī),降低緩存的過(guò)期時(shí)間的重復(fù)率,避免發(fā)生緩存集體實(shí)效。

3.3 緩存擊穿

3.3.1 緩存擊穿的概念

某個(gè) key 設(shè)置了過(guò)期時(shí)間,但在正好失效的時(shí)候,有大量請(qǐng)求進(jìn)來(lái)了,導(dǎo)致請(qǐng)求都到數(shù)據(jù)庫(kù)查詢了。

3.3.2 解決方案

大量并發(fā)時(shí),只讓一個(gè)請(qǐng)求可以獲取到查詢數(shù)據(jù)庫(kù)的鎖,其他請(qǐng)求需要等待,查到以后釋放鎖,其他請(qǐng)求獲取到鎖后,先查緩存,緩存中有數(shù)據(jù),就不用查數(shù)據(jù)庫(kù)。

四、加鎖解決緩存擊穿

怎么處理緩存穿透、雪崩、擊穿的問(wèn)題呢?

  • 對(duì)空結(jié)果進(jìn)行緩存,用來(lái)解決緩存穿透問(wèn)題。
  • 設(shè)置過(guò)期時(shí)間,且加上隨機(jī)值進(jìn)行過(guò)期偏移,用來(lái)解決緩存雪崩問(wèn)題。
  • 加鎖,解決緩存擊穿問(wèn)題。另外需要注意,加鎖對(duì)性能會(huì)帶來(lái)影響。

這里我們來(lái)看下用代碼演示如何解決緩存擊穿問(wèn)題。

我們需要用 synchronized 來(lái)進(jìn)行加鎖。當(dāng)然這是本地鎖的方式,分布式鎖我們會(huì)在下篇講到。

  1. public List<TypeEntity> getTypeEntityListByLock() { 
  2.   synchronized (this) { 
  3.     // 1.從緩存中查詢數(shù)據(jù) 
  4.     String typeEntityListCache = stringRedisTemplate.opsForValue().get("typeEntityList"); 
  5.     if (!StringUtils.isEmpty(typeEntityListCache)) { 
  6.       // 2.如果緩存中有數(shù)據(jù),則從緩存中拿出來(lái),并反序列化為實(shí)例對(duì)象,并返回結(jié)果 
  7.       List<TypeEntity> typeEntityList = JSON.parseObject(typeEntityListCache, new TypeReference<List<TypeEntity>>(){}); 
  8.       return typeEntityList; 
  9.     } 
  10.     // 3.如果緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù) 
  11.     System.out.println("The cache is empty"); 
  12.     List<TypeEntity> typeEntityListFromDb = this.list(); 
  13.     // 4.將從數(shù)據(jù)庫(kù)中查詢出的數(shù)據(jù)序列化 JSON 字符串 
  14.     typeEntityListCache = JSON.toJSONString(typeEntityListFromDb); 
  15.     // 5.將序列化后的數(shù)據(jù)存入緩存中,并返回?cái)?shù)據(jù)庫(kù)查詢結(jié)果 
  16.     stringRedisTemplate.opsForValue().set("typeEntityList", typeEntityListCache, 1, TimeUnit.DAYS); 
  17.     return typeEntityListFromDb; 
  18.   } 

1.從緩存中查詢數(shù)據(jù)。

2.如果緩存中有數(shù)據(jù),則從緩存中拿出來(lái),并反序列化為實(shí)例對(duì)象,并返回結(jié)果。

3.如果緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)。

4.將從數(shù)據(jù)庫(kù)中查詢出的數(shù)據(jù)序列化 JSON 字符串。

5.將序列化后的數(shù)據(jù)存入緩存中,并返回?cái)?shù)據(jù)庫(kù)查詢結(jié)果。

五、本地鎖的問(wèn)題

本地鎖只能鎖定當(dāng)前服務(wù)的線程,如下圖所示,部署了多個(gè)題目微服務(wù),每個(gè)微服務(wù)用本地鎖進(jìn)行加鎖。

本地鎖在一般情況下沒什么問(wèn)題,但是在某些情況下就會(huì)出問(wèn)題:

比如在高并發(fā)情況下用來(lái)鎖庫(kù)存就有問(wèn)題了:

1.比如當(dāng)前總庫(kù)存為 100,被緩存在 Redis 中。

2.庫(kù)存微服務(wù) A 用本地鎖扣減庫(kù)存 1 之后,總庫(kù)存為 99。

3.庫(kù)存微服務(wù) B 用本地鎖扣減庫(kù)存 1 之后,總庫(kù)存為 99。

4.那庫(kù)存扣減了 2 次后,還是 99,就超賣了 1 個(gè)。

 

那如何解決本地加鎖的問(wèn)題呢?

 

責(zé)任編輯:武曉燕 來(lái)源: 悟空聊架構(gòu)
相關(guān)推薦

2018-08-07 10:44:50

緩存技術(shù)瀏覽器

2021-02-18 22:21:20

ASM服務(wù)組件化

2013-05-21 09:47:45

虛擬化存儲(chǔ)虛擬化

2010-01-07 09:44:58

NetIQ

2009-08-03 13:19:07

WindowsServ虛擬化Hyper-V

2012-02-15 10:26:40

JavaJava Socket

2012-05-03 11:21:58

ApacheCXFJava

2013-11-29 10:24:52

Cluster設(shè)計(jì)資源池

2021-06-29 19:26:29

緩存Spring CachSpring

2021-08-04 06:56:49

HTTP緩存前端

2021-04-12 09:29:05

緩存穿透Facebook

2018-02-27 10:43:59

2022-03-09 18:54:30

HTTP緩存協(xié)議cache

2009-05-18 18:08:58

VMware虛擬化服務(wù)器

2021-01-08 10:24:32

Python項(xiàng)目基礎(chǔ)

2017-01-20 11:10:58

電信

2015-10-30 15:18:24

2023-11-30 07:45:11

useEffectReact

2011-04-11 15:53:40

C++

2021-06-03 19:55:55

MySQ查詢優(yōu)化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)