僅次於北京,安徽新能源汽車創新能力全國第二

據國家知識產權局專利局專利文獻部與中國專利技術開發公司組織編寫的《全球專利創新活動研究報告(2014)》顯示,安徽新能源汽車產業創新能力在中國各省區市中綜合排名第二,僅次於北京。   2010—2013年,安徽新能源汽車產業共獲發明專利授權131件,每申請人平均發明專利授權量14.56件。奇瑞汽車股份有限公司在全球新能源汽車產業主要競爭者綜合排名中,僅次於豐田自動車株式會社和通用汽車環球科技運作公司居第三位。   最新的統計數據顯示,2013-2015年9月底,合肥市範圍內累計推廣應用新能源汽車7334輛,任務完成率128%,位居全國前列,合肥在全國範圍內累計推廣應用新能源汽車1.8萬輛,約占全國8%。在蕪湖,過去5年來奇瑞相繼推出QQ3EV、M1EV、eQ等純電動產品車型,累計銷售純電動汽車約3萬輛。其中2014年實現銷量9007輛,位列全國第三。   未來5年,合肥、蕪湖新能源汽車產業集聚發展基地將規劃建設108個重大專案,力爭產值突破千億元,朝著有國際競爭力的新能源汽車產業集聚發展基地的目標大步邁進。   資料來源:安徽日報

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

分佈式應用框架 Dapr

微服務架構已成為構建雲原生應用程序的標準,微服務架構提供了令人信服的好處,包括可伸縮性,鬆散的服務耦合和獨立部署,但是這種方法的成本很高,需要了解和熟練掌握分佈式系統。為了使用所有開發人員能夠使用任何語言和任何框架輕鬆地構建便攜式微服務應用程序,無論是開發新項目還是遷移現有代碼

Dapr 介紹

Github: 

Dapr是一種可移植的,事件驅動的,無服務器運行時,用於構建跨雲和邊緣的分佈式應用程序。

Distributed Application Runtime. An event-driven, portable runtime for building microservices on cloud and edge.

其中提到了多語言和多開發者框架,我認為這是他選擇的通過通信共享信息,即 HTTPGRPC 支持多語言等特性。微軟想通過這個設定一個構建微服務應用的規則。從根本上確立你開發的每一個應用的獨立性。賦能每個開發者,為了使Dapr對於不同的語言更加方便,它還包括針對Go,Java,JavaScript,.NET和Python的語言特定的SDK。這些SDK通過類型化的語言API(而不是調用http / gRPC API)公開了Dapr構建塊中的功能,例如保存狀態,發布事件或創建actor。這使開發人員可以使用他們選擇的語言編寫無狀態和有狀態功能以及參与者的組合。並且由於這些SDK共享Dapr運行時,您甚至可以獲得跨語言的actor和功能支持!

Dapr還可以與任何開發人員框架集成。例如,在Dapr .NET SDK中, Core集成,該集成帶來了可響應其他服務的發布/訂閱事件的狀態路由控制器, Core成為構建微服務Web應用程序的更好框架。

 

不過需要注意的是Dapr目前正處於Alpha階段, 今天剛發布了0.2版本。在v1.0穩定版本發布之前,建議不要用於生產環境。

 

下面進行一個 QuickStart

環境

  1. Install (微服務已經離不開容器化了)
  2. Install
  3. Install

在Windows 上通過Powershell 安裝:

powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
然後把 c:\dapr 添加到環境變量 PATH
運行dapr命令,檢查輸出是否正常

C:\workshop\Github\dotnet-sdk>dapr –help

         __
     ____/ /___ _____  _____
    / __  / __ ‘/ __ \/ ___/
   / /_/ / /_/ / /_/ / /   
   \__,_/\__,_/ .___/_/
               /_/

======================================================
A serverless runtime for hyperscale, distributed systems

Usage:
   dapr [command]

Available Commands:
   help        Help about any command
   init        Setup dapr in Kubernetes or Standalone modes
   list        List all dapr instances
   publish     publish an event to multiple consumers
   run         Launches dapr and your app side by side
   send        invoke a dapr app with an optional payload
   stop        Stops a running dapr instance and its associated app
   uninstall   removes a dapr installation

Flags:
   -h, –help      help for dapr
       –version   version for dapr

Use “dapr [command] –help” for more information about a command.

 

執行初始化(會啟動 docker 容器)
dapr init 
Making the jump to hyperspace... 
Downloading binaries and setting up components 
Success! Dapr is up and running
下載.NET SDk代碼
 ,裏面有.NET Core的多個示例代碼:
示例 描述
Demonstrates creating virtual actors that encapsulate code and state. Also see docs in this repo for a tutorial.
Demonstrates ASP.NET Core integration with Dapr by create Controllers and Routes.

The gRPC client sample shows how to make Dapr calls to publish events, save state, get state and delete state using a gRPC client.


我們一起來看下ASP.NET Core的Demo;

例子中主 我們使用 Dapr 的交互。Dapr通過 Runtime

  • 提供 Dapr API 給多語言調用。
  • 提供 狀態管理 By state stores

 

/// <summary>
  /// Sample showing Dapr integration with controller.
  /// </summary>
  [ApiController]
  public class SampleController : ControllerBase
  {
      /// <summary>
      /// Gets the account information as specified by the id.
      /// </summary>
      /// <param name=”account”>Account information for the id from Dapr state store.</param>
      /// <returns>Account information.</returns>
      [HttpGet(“{account}”)]
      public ActionResult<Account> Get(StateEntry<Account> account)
      {
          if (account.Value is null)
          {
              return this.NotFound();
          }

         return account.Value;
      }

 

     /// <summary>
      /// Method for depositing to account as psecified in transaction.
      /// </summary>
      /// <param name=”transaction”>Transaction info.</param>
      /// <param name=”stateClient”>State client to interact with dapr runtime.</param>
      /// <returns>A <see cref=”Task{TResult}”/> representing the result of the asynchronous operation.</returns>
      [Topic(“deposit”)]
      [HttpPost(“deposit”)]
      public async Task<ActionResult<Account>> Deposit(Transaction transaction, [FromServices] StateClient stateClient)
      {
          var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);
          state.Value ??= new Account() { Id = transaction.Id, };
          state.Value.Balance += transaction.Amount;
          await state.SaveAsync();
          return state.Value;
      }

 

     /// <summary>
      /// Method for withdrawing from account as specified in transaction.
      /// </summary>
      /// <param name=”transaction”>Transaction info.</param>
      /// <param name=”stateClient”>State client to interact with dapr runtime.</param>
      /// <returns>A <see cref=”Task{TResult}”/> representing the result of the asynchronous operation.</returns>
      [Topic(“withdraw”)]
      [HttpPost(“withdraw”)]
      public async Task<ActionResult<Account>> Withdraw(Transaction transaction, [FromServices] StateClient stateClient)
      {
          var state = await stateClient.GetStateEntryAsync<Account>(transaction.Id);

         if (state.Value == null)
          {
              return this.NotFound();
          }

         state.Value.Balance -= transaction.Amount;
          await state.SaveAsync();
          return state.Value;
      }
  }

 

這裏重點是狀態存儲,即將 state 通過 StateClient 存儲在 Dapr 中,我們通過狀態轉移在 Dapr 里實現了 stateless。
 
Dapr 運行.NET 應用程序

演示Dapr的服務調用,在終端中切換到項目目錄,然後使用dapr啟動應用

 

C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample>dapr run –app-id routing –app-port 5000 dotnet run   
Starting Dapr with id routing. HTTP Port: 61102. gRPC Port: 61103
You’re up and running! Both Dapr and your app logs will appear here.

 

注意: 以上dapr run命令,通過app-id指定了應用的ID,通過app-port指定了應用的端口(webapi默認使用5000作為http端口),後跟dotnet run命名啟動當前項目。可參考Dapr文檔

 

後台運行的 CLI 命令,這裡是前台打印的日誌, 注意到 .NET App 在指定的 5000 端口運行,同時還有狀態存儲的 redis6379 端口運行

 

== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”starting Dapr Runtime — version 0.2.0 — commit c75b11
1-dirty”
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”log level set to: info”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”standalone mode configured”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”dapr id: routing”
== DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”loaded component messagebus (pubsub.redis)”       
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”loaded component statestore (state.redis)”
     == DAPR == time=”2019-11-16T18:33:22+08:00″ level=info msg=”application protocol: http. waiting on port 5000″
     == APP == info: Microsoft.Hosting.Lifetime[0]
     == APP ==       Now listening on:
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Application started. Press Ctrl+C to shut down.
     == APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Hosting environment: Development
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Content root path: C:\workshop\Github\dotnet-sdk\samples\AspNetCore\ControllerSample
     == DAPR == time=”2019-11-16T18:33:31+08:00″ level=info msg=”application discovered on port 5000″
     == DAPR == 2019-11-16 18:33:32.029764 I | redis: connecting to localhost:6379
     == DAPR == 2019-11-16 18:33:32.036316 I | redis: connected to localhost:6379 (localAddr: [::1]:61164, remAddr:
[::1]:6379)
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actor runtime started. actor idle timeout: 1h0m0s.
actor scan interval: 30s”
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: starting connection attempt to placement se
rvice at localhost:6050″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”http server is running on port 61102″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”gRPC server is running on port 61103″
     == DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”local service entry announced”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”dapr initialized. Status: Running. Init Elapsed 917
6.5164ms”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: established connection to placement service
  at localhost:6050″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: lock”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: update”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement tables updated”
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”actors: placement order received: unlock”
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 2228.2998000000002ms – OK    
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]      
== APP ==       End processing HTTP request after 2257.3405000000002ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
     == APP ==       Received HTTP response after 67.46000000000001ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 68.0343ms – Created
     == APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
     == APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 5.8247ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 6.268400000000001ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request POST
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 4.5181000000000004ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 4.6208ms – Created
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[100]
== APP ==       Start processing HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[100]
== APP ==       Sending HTTP request GET
== APP == info: System.Net.Http.HttpClient.state.ClientHandler[101]
== APP ==       Received HTTP response after 20.2967ms – OK
== APP == info: System.Net.Http.HttpClient.state.LogicalHandler[101]
== APP ==       End processing HTTP request after 20.691100000000002ms – OK

 

 

為了同時實現可移植性和與現有代碼的輕鬆集成,Dapr通過http或gRPC提供了標準API。Dapr端口可從Dapr啟動日誌中獲取,如以下日誌表示Dapr公開的HTTP端口為61102(通過Dapr也可使用gRPC方式進行服務調用)

 

== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”http server is running on port 61102″
== DAPR == time=”2019-11-16T18:33:32+08:00″ level=info msg=”gRPC server is running on port 61103″

 

我們可通過以下地址來調用示例方法,根據Dapr服務調用API規範,其代理調用規則為:

POST/GET/PUT/DELETE http://localhost:<Dapr端口>/v1.0/invoke/<id>/method/<method-name>

直接調用:GET http://localhost:5000/17

通過Dapr服務調用: GET 
 
注意: Dapr的服務調用是有dapr sidecar來實現的,在被調用的服務中無需注入任何與dapr相關的代碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

農業和環保引發對立 德國兩派人馬街頭拉鋸

摘錄自2020年01月19日中央通訊社德國報導

德國農民與環保人士對壘加深,農民不滿新環保措施加重成本負擔,環保人士則抗議農業推升氣候變遷危機,兩邊的對立讓政治人物左右為難,也使得德國年度最大農業展蒙上陰影。

柏林一年一度「綠色週」貿易博覽會昨天(18日)開幕,德國農民選在當天展開一場名為「土地創造聯繫」(Land schafft Verbindung, LSV)活動,將數千輛牽引機開上全國各地城市街頭,藉此抗議政府施行更嚴格的農業法規。

德國最近推行的種種農業改革,包括在食品引進「動物福利標籤」、限制農藥使用以保護昆蟲等,都讓農民大喊吃不消,說政府新環保措施引發的成本都是農民在扛。他們也擔心新法規將導致收入大減,走上街頭據理力爭,高舉標語吶喊「別忘了是農民餵飽你們」、「沒有農場、沒有食物、沒有未來」。

農民昨天集體抗議後,今天輪到環保人士走上街頭。主辦單位說有約2萬7000名環保人士齊聚柏林地標布蘭登堡大門(Brandenburg Gate)附近,抗議他們口中的農業損害效應,並要求改變德國現行的務農方法。這場集會由環保團體「我們受夠了」(We are fed up)發起。氣候環保人士指稱近期的農業改革做得還不夠,現場另一邊站著抗議的農民,國會議員則被夾在中間。

農民和環保倡議者之間的鴻溝近幾年來有加深趨勢,這與學生帶領的「週五為未來而戰」(Fridays for Future)罷課抗暖化行動有關。環保人士今天的抗議行動自2011年以來年年登場,這個由綠色團體發起的活動要求全面性改革,以支持小農和對環境友善的農業。他們在網路上說:「農業加深暖化危機和社會衝突,我們必須加以阻止。」

農民組成的LSV抗議團體則自稱是相對於「我們受夠了」團體的反制力量,有憤怒的養蜂人將蜂蜜傾倒在政治人物大門前,也有生氣的農民走上街頭阻撓交通。農業部長克勒克納(Julia Kloeckner)16日呼籲對立的兩方要有妥協的胸襟。

綠黨共同領袖哈柏克(Robert Habeck)警告環保人士勿把氣候暖化歸咎在農民頭上,但也堅持說,拒絕改革並非社會前進之道,「減少對氣候和動物的保障,絕對不會是正確解答」。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

亞銀:借鏡碳信用建構「藍色信用」 以市場機制籌措海洋基金

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

每天3分鐘操作系統修鍊秘籍(14):IO操作和DMA、RDMA

I/O操作和DMA、RDMA

用戶進程想要執行IO操作時(例如想要讀磁盤數據、向磁盤寫數據、讀鍵盤的輸入等等),由於用戶進程工作在用戶模式下,它沒有執行這些操作的權限,只能通過發起對應的系統調用請求操作系統幫忙完成這些操作。這裏因為系統調用產生中斷將陷入到內核,進行一次上下文切換操作。

內核進程幫忙執行IO操作時,由於IO操作相比於CPU來說是極慢的操作,CPU不應該等待在這個過程中,而是切換到其它進程上去執行其它任務。這裏再次涉及到一次上下文切換:從內核態回到用戶態的其它進程。

DMA要求硬件的支持,需要在硬件中集成一個小型的“CPU”,比如現在的机械硬盤、固態硬盤、網卡等硬件都帶有DMA功能,這樣操作系統要執行IO操作時,直接將相關指令發送給這些DMA硬件,DMA處理器負責IO操作,而操作系統這時可以放棄CPU,讓CPU去執行其它進程。例如對於讀磁盤文件時,操作系統將相關指令以及數據應寫在哪個內存地址發送給DMA硬件后,由DMA硬件去讀寫數據到指定內存地址,當IO操作完成后,DMA硬件通過總線發送一個硬件中斷給CPU,於是陷入到內核態(這裏涉及了一次上下文切換),內核就知道了IO已經完成,於是將Kernel Buffer數據拷貝到用戶進程的IO Buffer,並準備調度用戶進程(再次上下文切換)。

假如不使用DMA硬件的話,那麼IO操作過程中,操作系統將多次參与,負責將硬件數據讀入或讀出內存,操作系統參与意味着要陷入到內核態,並且獲取CPU控制權,這也意味着要進行大量的上下文切換以及佔用大量CPU資源。

而使用DMA后,只有4次必要的上下文切換,且IO操作的過程中完全不需要消耗CPU資源。

除了DMA,還有更高級的RDMA(Remote Direct Memory Access)機制,它需要操作系統和硬件的支持,還需要編寫RDMA方式的代碼。

前面介紹緩衝空間時提到過,一般情況下,每個用戶進程要讀、寫數據,都會經過兩個必要的緩衝層:內核空間的Kernel Buffer、用戶空間的IO Buffer。例如讀文件數據時,先將數據拷貝到內核的緩衝空間(page cache),然後陷入內核,內核將該緩衝空間數據拷貝到用戶空間的緩衝空間(IO Buffer),當調度到用戶進程時,用戶進程從自己的緩衝空間讀取數據。

DMA機制並沒有繞過這兩個緩衝層,但使用RDMA機制,程序可以直接繞過Kernel Buffer,內核發現是RDMA操作后,直接告訴RDMA硬件將讀取的數據(寫操作也一樣)寫入到用戶空間的IO Buffer,而不需要先拷貝到Kernel Buffer,再拷貝到IO Buffer。雖然RDMA機制相比DMA不會減少上下文切換次數,但是它減少了內存數據拷貝的過程,相當於是使用了O_DIRECT標記的直接IO技術。

DMA和RDMA兩種技術對比如圖:RDMA一般實現在網卡上,但出於方便理解,下圖直接使用磁盤來描述

像這種繞過內核功能的技術,通常稱為內核旁路(Kernel Bypass),RDMA技術內核旁路的是一種,還有像TOE也是內核旁路的一種。

雖然RDMA比較優秀,但是它需要硬件、操作系統和代碼的同時支持,對編程而言是一個比較大的衝擊,所以目前使用的非常少。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

森林大火逼近 坎培拉進入緊急狀態

摘錄自2020年1月31日Yahoo新聞報導

澳洲森林大火逼近首都坎培拉,坎培拉今天(31日)宣布進入緊急狀態,這是近20年來的首次。

有40萬人口的坎培拉宣布進入緊急狀態,主要是考量天氣預報近日將有熱浪來襲,預測森林大火可能延燒到坎培拉南部郊區。澳洲首都特區首席部長巴爾(Andrew Barr)警告:「情勢可能演變到無法控制的地步…進入緊急狀態,是我們能對首都特區民眾發布的最強烈警示,民眾必須為自己和家人做好準備。」

南澳州(South Australia) 昨天氣溫達攝氏40度以上,氣象單位對州內數處容易發生大火的地區發布危險大火警示,並預測今天稍晚熱浪將襲擊墨爾本與坎培拉,而雪梨部分地區週末氣溫將達攝氏45度高溫。

當局表示,灼熱高溫伴隨乾燥熱風,將為新南威爾斯州(New South Wales)與維多利亞州(Victoria)部分地區創造嚴重森林大火的良好條件,而這兩州目前還有80多處有大火在延燒。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Spring源碼解析之@Configuration

@Configuration簡介

用於標識一個類為配置類,與xml配置效果類似

用法簡介

public class TestApplication {

    public static void main(String args[]) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    }
}

@Configuration
public class AppConfig {

    @Bean
    public A a(){
        return new A();
    }
    @Bean
    public B b(){
        return new B();
    }
} 




public class A {

    public A(){
        System.out.println("Call A constructor");
    }
}



public class B {

    public B(){
        System.out.println("Call B constructor");
    }
}

上面的例子應該是@Configuration最普遍一種使用場景了,在@Configuration class下面配置@Bean method,用於想Spring Ioc容器注入bean.但其實我們把AppConfig的@Configuration註解去掉,對應的Bean也是可以被注入到容器中去的。

那麼問題來了@Configuration到底有什麼作用?

我們給AppConfig改寫一下,如下:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

   @Bean
   public A a(){
      b();
      return new A();
   }
   @Bean
   public B b(){
      return new B();
   }

再去執行TestApplication#main,那麼執行結果會是什麼樣呢?

Call A constructor
Call B constructor

嗯哼?按照Java的語法,B的構造函數應該是被調用了兩次啊?為什麼只有輸出一句Call B constructor?

這其實就是@Configuration再發揮作用啦,不信你去掉@Configuration,再去運行一下,就會發現B的構造函數被執行了兩次。

官方給出了這樣一段解釋對於被@Configuration註解的類

In common scenarios,@Beanmethods are to be declared within@Configurationclasses, ensuring that “full” mode is always used and that cross-method references therefore get redirected to the container’s lifecycle management.

在一般情況下,@Bean method 是被聲明在@Configuration類中的,以確保 full mode總是被使用,並且跨方法的引用會被重定向到容器生命周期管理。

怎麼理解呢?

原來Spring將被@Configuration註解的配置類定義為full configuration, 而將沒有被@Configuration註解的配置類定義為lite configuration。full configuration能重定向從跨方法的引用,從而保證上述代碼中的b bean是一個單例.

源碼中是如何實現@Configuration語意

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
   /**
    * 調用無參構造函數,實例化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
    * 同時會調用父類GenericApplicationContext無參構造函數實例化一個關鍵的工廠DefaultListableBeanFactory
    * 同時還會註冊一些開天闢地的後置處理器到beanDefinitionMap,這些後置處理器有bean工廠後置處理器;有bean後置處理器
    */
   this();
   //將componentClasses註冊到beanDefinitionMap集合中去
   register(componentClasses);
  
   refresh();
}

跟蹤refresh()

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         //供上下文(Context)子類繼承,允許在這裏後置處理bean factory
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         //按順序調用BeanFactoryPostProcessor,這裏的按順序僅實現了PriorityOrdered和Ordered的語意,未實現@Order註解的語意
         //通過調用ConfigurationConfigPostProcessor#postProcessBeanDefinitionRegistry
         //解析@Configuration配置類,將自定義的BeanFactoryPostProcessor、BeanPostProcessor註冊到beanDefinitionMap
         //接着實例化所有(包括開天闢地)的BeanFactoryPostProcessor,然後再調用BeanFactoryPostProcessor#postProcessBeanFactory
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         //按順序將BeanPostProcessor實例化成bean並註冊到beanFactory的beanPostProcessors,
         //這裏的按順序僅實現了PriorityOrdered和Ordered的語意,未實現@Order註解的語意
         //因為BeanPostProcessor要在普通bean初始化()前後被調用,所以需要提前完成實例化並註冊到beanFactory的beanPostProcessors
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         //註冊國際化相關的Bean
         initMessageSource();

         // Initialize event multicaster for this context.
         //為上下文註冊應用事件廣播器(用於ApplicationEvent的廣播),如果有自定義則使用自定義的,如果沒有則內部實例化一個
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         //註冊所有(靜態、動態)的listener,並廣播earlyApplicationEvents
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         //實例化用戶自定義的普通單例Bean(非開天闢地的、非後置處理器)
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

跟蹤invokeBeanFactoryPostProcessors(beanFactory)

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

   // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
   // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
   if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   }
}

跟蹤invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

public static void invokeBeanFactoryPostProcessors(
            ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

        // Invoke BeanDefinitionRegistryPostProcessors first, if any.
        Set<String> processedBeans = new HashSet<>();

        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
            List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else {
                    regularPostProcessors.add(postProcessor);
                }
            }

            // Do not initialize FactoryBeans here: We need to leave all regular beans
            // uninitialized to let the bean factory post-processors apply to them!
            // Separate between BeanDefinitionRegistryPostProcessors that implement
            // PriorityOrdered, Ordered, and the rest.
            List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

            // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
            String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            //此處調用ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry,
            //解析配置類,為配置中的bean定義生成對應beanDefinition,並注入到registry的beanDefinitionMap
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();

            // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();

            // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
            boolean reiterate = true;
            while (reiterate) {
                reiterate = false;
                postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
                for (String ppName : postProcessorNames) {
                    if (!processedBeans.contains(ppName)) {
                        currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                        processedBeans.add(ppName);
                        reiterate = true;
                    }
                }
                sortPostProcessors(currentRegistryProcessors, beanFactory);
                registryProcessors.addAll(currentRegistryProcessors);
                invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
                currentRegistryProcessors.clear();
            }

            // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
            //調用ConfigurationClassPostProcessor#postProcessBeanFactory增強配置類(通過cglib生成增強類,load到jvm內存,
            //設置beanDefinition的beanClass為增強類)
            //為什麼要增強配置類?主要是為了讓@Bean生成的bean是單例,
            invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
        }

        else {
            // Invoke factory processors registered with the context instance.
            invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
        }

        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let the bean factory post-processors apply to them!
        String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

        // Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
        // Ordered, and the rest.
        List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
        List<String> orderedPostProcessorNames = new ArrayList<>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        for (String ppName : postProcessorNames) {
            if (processedBeans.contains(ppName)) {
                // skip - already processed in first phase above
            }
            else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

        // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
        sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

        // Next, invoke the BeanFactoryPostProcessors that implement Ordered.
        List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
        for (String postProcessorName : orderedPostProcessorNames) {
            orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
        }
        sortPostProcessors(orderedPostProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

        // Finally, invoke all other BeanFactoryPostProcessors.
        List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
        for (String postProcessorName : nonOrderedPostProcessorNames) {
            nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
        }
        invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

        // Clear cached merged bean definitions since the post-processors might have
        // modified the original metadata, e.g. replacing placeholders in values...
        beanFactory.clearMetadataCache();
    }

跟蹤invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

private static void invokeBeanFactoryPostProcessors(
      Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

   for (BeanFactoryPostProcessor postProcessor : postProcessors) {
      postProcessor.postProcessBeanFactory(beanFactory);
   }
}

跟蹤ConfigurationClassPostProcessor#postProcessBeanFactory

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   int factoryId = System.identityHashCode(beanFactory);
   if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + beanFactory);
   }
   this.factoriesPostProcessed.add(factoryId);
   if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
   //為@Configuration註解的類生成增強類(如果有必要),並替換bd中的beanClass屬性,
   enhanceConfigurationClasses(beanFactory);
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

到了這一步謎底幾乎已經揭曉了,@Configuration class是通過增強來實現它的語義的。通過增強把跨方法的引用調用重定向到Spring生命周期管理.我們近一步探索下這個enhanceConfigurationClasses方法

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      MethodMetadata methodMetadata = null;
      if (beanDef instanceof AnnotatedBeanDefinition) {
         methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
      }
      if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
         // Configuration class (full or lite) or a configuration-derived @Bean method
         // -> resolve bean class at this point...
         AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
         if (!abd.hasBeanClass()) {
            try {
               abd.resolveBeanClass(this.beanClassLoader);
            }
            catch (Throwable ex) {
               throw new IllegalStateException(
                     "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
         }
      }
      //在ConfigurationClassUtils.checkConfigurationClassCandidate方法中會標記Configuration is full or lite
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         if (!(beanDef instanceof AbstractBeanDefinition)) {
            throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                  beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
         }
         else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
            logger.info("Cannot enhance @Configuration bean definition '" + beanName +
                  "' since its singleton instance has been created too early. The typical cause " +
                  "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                  "return type: Consider declaring such methods as 'static'.");
         }
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }
   if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
   }

   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      // Set enhanced subclass of the user-specified bean class
      Class<?> configClass = beanDef.getBeanClass();
      //為@Configuration註解的類生成增強類
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (logger.isTraceEnabled()) {
            logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

看到有那麼一句話Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

很明顯了,使用cglib技術為config class生成一個enhancedClass,再通過beanDef.setBeanClass(enhancedClass);修改beanDefinition的BeanClass屬性,在bean實例化階段,會利用反射技術將beanClass屬性對應的類實例化出來,所以最終實例化出來的@Configuration bean是一個代理類的實例。這裏稍微提一下為什麼要使用cglib,而不是jdk動態代理,主要是因為jdk動態代理是基於接口的,而這裏AppConfig並沒有實現任何接口,所以必須用cglib技術。

總結

被@Configuration 註解的類,是 full configuration class,該類會被增強(通過cglib),從而實現跨方法引用調用被重定向到Spring 生命周期管理,最終保證@Bean method生成的bean是一個單例。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

海洋熱浪讓更多鯨魚受困漁網 專家將研發預測工具

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

孟買減少噪音試驗計畫 司機響按喇叭交通燈自動延遲轉綠燈

摘錄自2020年2月6日星島日報報導

印度孟買警方提出一項具有創意的計畫,藉以減少該市的噪音污染。如果駕駛人士響按造成大量噪音,交通燈會自動作出調整,延遲由紅燈轉成綠燈,令司機等得更久才可開車。

孟買警方於去年11月和12月推行這項試驗計畫,在交通燈燈柱上安裝量度聲音分貝的儀器。如果儀器錄得汽車響按製造出來的噪音達85分貝或以上,交通燈會延遲由紅燈轉成綠燈。

警方發言人說,在市內幾個繁忙的道路交匯處安裝這套裝置,每天試驗15分鐘。當局會於下月起擴大這項計畫,在市內10處地點進行試驗。如果試驗成功,會在整個交通管理系統內實行。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Redis持久化的幾種方式——深入解析RDB

Redis 的讀寫都是在內存中,所以它的性能較高,但在內存中的數據會隨着服務器的重啟而丟失,為了保證數據不丟失,我們需要將內存中的數據存儲到磁盤,以便 Redis 重啟時能夠從磁盤中恢復原有的數據,而整個過程就叫做 Redis 持久化。

Redis 持久化也是 Redis 和 Memcached 的主要區別之一,因為 Memcached 是不具備持久化功能的。

1.持久化的幾種方式

Redis 持久化擁有以下三種方式:

  • 快照方式(RDB, Redis DataBase)將某一個時刻的內存數據,以二進制的方式寫入磁盤;
  • 文件追加方式(AOF, Append Only File),記錄所有的操作命令,並以文本的形式追加到文件中;
  • 混合持久化方式,Redis 4.0 之後新增的方式,混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的數據以 RDB 的形式寫入文件的開頭,再將後續的操作命令以 AOF 的格式存入文件,這樣既能保證 Redis 重啟時的速度,又能簡單數據丟失的風險。

因為每種持久化方案,都有特定的使用場景,讓我們先從 RDB 持久化說起吧。

2.RDB簡介

RDB(Redis DataBase)是將某一個時刻的內存快照(Snapshot),以二進制的方式寫入磁盤的過程。

3.持久化觸發

RDB 的持久化觸發方式有兩類:一類是手動觸發,另一類是自動觸發。

1)手動觸發

手動觸發持久化的操作有兩個: save 和 bgsave ,它們主要區別體現在:是否阻塞 Redis 主線程的執行。

① save 命令

在客戶端中執行 save 命令,就會觸發 Redis 的持久化,但同時也是使 Redis 處於阻塞狀態,直到 RDB 持久化完成,才會響應其他客戶端發來的命令,所以在生產環境一定要慎用

save 命令使用如下:

從圖片可以看出,當執行完 save 命令之後,持久化文件 dump.rdb 的修改時間就變了,這就表示 save 成功的觸發了 RDB 持久化。
save 命令執行流程,如下圖所示:

② bgsave 命令

bgsave(background save)既後台保存的意思, 它和 save 命令最大的區別就是 bgsave 會 fork() 一個子進程來執行持久化,整個過程中只有在 fork() 子進程時有短暫的阻塞,當子進程被創建之後,Redis 的主進程就可以響應其他客戶端的請求了,相對於整個流程都阻塞的 save 命令來說,顯然 bgsave 命令更適合我們使用。
bgsave 命令使用,如下圖所示:

bgsave 執行流程,如下圖所示:

2)自動觸發

說完了 RDB 的手動觸發方式,下面來看如何自動觸發 RDB 持久化?
RDB 自動持久化主要來源於以下幾種情況。

① save m n

save m n 是指在 m 秒內,如果有 n 個鍵發生改變,則自動觸發持久化。
參數 m 和 n 可以在 Redis 的配置文件中找到,例如,save 60 1 則表明在 60 秒內,至少有一個鍵發生改變,就會觸發 RDB 持久化。
自動觸發持久化,本質是 Redis 通過判斷,如果滿足設置的觸發條件,自動執行一次 bgsave 命令。
注意:當設置多個 save m n 命令時,滿足任意一個條件都會觸發持久化。
例如,我們設置了以下兩個 save m n 命令:

  • save 60 10
  • save 600 1

當 60s 內如果有 10 次 Redis 鍵值發生改變,就會觸發持久化;如果 60s 內 Redis 的鍵值改變次數少於 10 次,那麼 Redis 就會判斷 600s 內,Redis 的鍵值是否至少被修改了一次,如果滿足則會觸發持久化。

② flushall

flushall 命令用於清空 Redis 數據庫,在生產環境下一定慎用,當 Redis 執行了 flushall 命令之後,則會觸發自動持久化,把 RDB 文件清空。
執行結果如下圖所示:

③ 主從同步觸發

在 Redis 主從複製中,當從節點執行全量複製操作時,主節點會執行 bgsave 命令,並將 RDB 文件發送給從節點,該過程會自動觸發 Redis 持久化。

4.配置說明

合理的設置 RDB 的配置,可以保障 Redis 高效且穩定的運行,下面一起來看 RDB 的配置項都有哪些?

RDB 配置參數可以在  Redis 的配置文件中找見,具體內容如下:

# RDB 保存的條件
save 900 1
save 300 10
save 60 10000

# bgsave 失敗之後,是否停止持久化數據到磁盤,yes 表示停止持久化,no 表示忽略錯誤繼續寫文件。
stop-writes-on-bgsave-error yes

# RDB 文件壓縮
rdbcompression yes

# 寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。
rdbchecksum yes

# RDB 文件名
dbfilename dump.rdb

# RDB 文件目錄
dir ./

其中比較重要的參數如下列表:
① save 參數
它是用來配置觸發 RDB 持久化條件的參數,滿足保存條件時將會把數據持久化到硬盤。
默認配置說明如下:

  • save 900 1:表示 900 秒內如果至少有 1 個 key 值變化,則把數據持久化到硬盤;
  • save 300 10:表示 300 秒內如果至少有 10 個 key 值變化,則把數據持久化到硬盤;
  • save 60 10000:表示 60 秒內如果至少有 10000 個 key 值變化,則把數據持久化到硬盤。

② rdbcompression 參數
它的默認值是 yes 表示開啟 RDB 文件壓縮,Redis 會採用 LZF 算法進行壓縮。如果不想消耗 CPU 性能來進行文件壓縮的話,可以設置為關閉此功能,這樣的缺點是需要更多的磁盤空間來保存文件。
③ rdbchecksum 參數
它的默認值為 yes 表示寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。

5.配置查詢

Redis 中可以使用命令查詢當前配置參數。查詢命令的格式為:config get xxx ,例如,想要獲取 RDB 文件的存儲名稱設置,可以使用 config get dbfilename ,執行效果如下圖所示:

查詢 RDB 的文件目錄,可使用命令 config get dir ,執行效果如下圖所示:

6.配置設置

設置 RDB 的配置,可以通過以下兩種方式:

  • 手動修改 Redis 配置文件;
  • 使用命令行設置,例如,使用 config set dir "/usr/data" 就是用於修改 RDB 的存儲目錄。

注意:手動修改 Redis 配置文件的方式是全局生效的,即重啟 Redis 服務器設置參數也不會丟失,而使用命令修改的方式,在 Redis 重啟之後就會丟失。但手動修改 Redis 配置文件,想要立即生效需要重啟 Redis 服務器,而命令的方式則不需要重啟 Redis 服務器。

小貼士:Redis 的配置文件位於 Redis 安裝目錄的根路徑下,默認名稱為 redis.conf。

7.RDB 文件恢復

當 Redis 服務器啟動時,如果 Redis 根目錄存在 RDB 文件 dump.rdb,Redis 就會自動加載 RDB 文件恢復持久化數據。
如果根目錄沒有 dump.rdb 文件,請先將 dump.rdb 文件移動到 Redis 的根目錄。
驗證 RDB 文件是否被加載
Redis 在啟動時有日誌信息,會显示是否加載了 RDB 文件,我們執行 Redis 啟動命令:src/redis-server redis.conf ,如下圖所示:

從日誌上可以看出, Redis 服務在啟動時已經正常加載了 RDB 文件。

小貼士:Redis 服務器在載入 RDB 文件期間,會一直處於阻塞狀態,直到載入工作完成為止。

8.RDB 優缺點

1)RDB 優點

  • RDB 的內容為二進制的數據,佔用內存更小,更緊湊,更適合做為備份文件;
  • RDB 對災難恢復非常有用,它是一個緊湊的文件,可以更快的傳輸到遠程服務器進行 Redis 服務恢復;
  • RDB 可以更大程度的提高 Redis 的運行速度,因為每次持久化時 Redis 主進程都會 fork() 一個子進程,進行數據持久化到磁盤,Redis 主進程並不會執行磁盤 I/O 等操作;
  • 與 AOF 格式的文件相比,RDB 文件可以更快的重啟。

    2)RDB 缺點

  • 因為 RDB 只能保存某個時間間隔的數據,如果中途 Redis 服務被意外終止了,則會丟失一段時間內的 Redis 數據;
  • RDB 需要經常 fork() 才能使用子進程將其持久化在磁盤上。如果數據集很大,fork() 可能很耗時,並且如果數據集很大且 CPU 性能不佳,則可能導致 Redis 停止為客戶端服務幾毫秒甚至一秒鐘。

    9.禁用持久化

    禁用持久化可以提高 Redis 的執行效率,如果對數據丟失不敏感的情況下,可以在連接客戶端的情況下,執行 config set save "" 命令即可禁用 Redis 的持久化,如下圖所示:

    10.小結

    通過本文我們可以得知,RDB 持久化分為手動觸發和自動觸發兩種方式,它的優點是存儲文件小,Redis 啟動時恢複數據比較快,缺點是有丟失數據的風險。RDB 文件的恢復也很簡單,只需要把 RDB 文件放到 Redis 的根目錄,在 Redis 啟動時就會自動加載並恢複數據。

    11.思考題

    如果 Redis 服務器 CPU 佔用過高,可能是什麼原因導致的?歡迎各位在評論區,寫下你們的答案。

    12.參考&鳴謝

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!