中國推新能源車結合再生能源應用,拚減碳逾 20 萬噸

新華社報導,中國新能源汽車和再生能源綜合應用商業化推廣專案正式啟動,據中國汽車工程學會常務副理事長兼秘書長張進華表示,整個專案營運期間將直接減排二氧化碳超過 20 萬噸。

張進華指出,新能源汽車和再生能源綜合應用商業化推廣專案的實施,是落實中國汽車產業中長期發展規劃的重要工作之一。據瞭解,為促進中國新能源汽車與再生能源產業綜合應用,充分發揮新能源汽車全生命週期的節能減排效益,減少溫室氣體排放,中國工信部和聯合國工業發展組織共同向全球環境基金申報此項目並獲得批准,中國汽車工程學會將全面承擔專案實施與組織工作,實施週期為 3 年。

據瞭解,該專案具體內容包括組織制定新能源汽車和再生能源綜合應用的政策和標準法規,展開智慧充電系統、再生能源微電網、車網互動、移動充電系統、退役電池梯次利用等技術示範和驗證工作。目前,已確定將中國上海市、江蘇省鹽城市和如皋市作為示範城市,展開新能源汽車和再生能源綜合應用的技術示範和驗證。

(本文內容由 授權使用。首圖來源:)

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

中國電動車電池不到位?通用汽車被迫延後投產

中國推出「雙積分制」,規定各大車廠必須出售一定比例的電動車。通用汽車(GM)亟欲在中國開賣電動車,不料卻傳出當地生產的電動車電池達不到 GM 要求,暴露出中國電池業的隱憂。

《華爾街日報》報導,GM 原定下個月內生產油電混合車「Buick Velite 6」,明年再推純電動車。然而,內情人士透露上市計畫已經延後,原因是中國萬向集團旗下 A123 Systems 生產的電池,內部測試時效能和安全未達 GM 標準。A123 Systems 在杭州設有工廠,供應中國市場所需。電動車電池是相當複雜的零件,無法輕易更換,Velite 6 生產時程恐怕會大延誤。

GM 原本打算使用韓廠 LG Chem 的電池,可是 2016 年中國規定車廠必須使用當局核可的電池供應商,名單上全部都是中國廠,沒有一家外國業者入列。車廠抱怨此種排外政策是中國保護主義的實例,當局剔除外國業者,獨厚本土廠商。電池諮詢機構 LIB-X Consulting 總裁 Thomas Barrera 表示,中國業者急就章發展電池技術,或許有品質和安全風險。中國電池價格低廉,相當有吸引力,但是這些電池上市前,也許沒經過必須的品質檢測。

中國的電動車政策讓外國車廠陷入困境,車商必須使用中國製電池打造電動車,卻又不能在品質上做出妥協,使用品質欠佳的電池,車輛有著火風險。儘管多數外國車廠在公開場合信心滿滿表示,有能力達到中國政府目標,2019 年電動車產出將占總產量的 3~4%,但是少有業者詳細說明要如何辦到。

(本文內容由 授權使用。 CC BY 2.0)

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

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

台達電開發 400kW 高功率電動車充電機,獲美專案經費

電源管理及散熱解決方案廠商台達電 30 日宣布獲美國能源部(Department of Energy,DOE)研發專案經費輔助,合作開發充電輸出功率可達 400kW 的高速電動車充電機(XFC),預期不到 10 分鐘的快速充電即可為未來電動車款提供 180 英里的行駛里程(約 288 公里)。此充電機將應用固態變壓器(SST)技術,其電網輸入至電動車輸出的能源轉換率將高達 96.5%,體積為現今直流充電機的一半,重量更僅有四分之一,同時設計高壓直流(HVDC)端口,以利與儲能及再生能源系統整合,建構智能微電網以減少充電站對市電電網的負擔。

台達電指出,此研究專案將由深具汽車行業知識及經驗的台達底特律團隊,及位於北卡羅來納州  Raleigh 的台達電力電子研究室(DPEL)主導研究開發,通用汽車、DTE Energy、維吉尼亞理工大學 CPES 研究中心、NextEnergy、密西根州能源局之能源辦公室以及底特律市永續發展辦公室也將共同參與。此共同研發計畫為期三年,經費總計 700 萬美元,美國能源部將輔助一半費用。

台達電美洲區總經理黃銘孝表示,公司非常榮幸能主導此項重要研究計畫,並與頂尖的研究人員及合作夥伴共同開發嶄新技術。透過利用固態變壓器技術,將能創造前所未有的充電速度和便利性,樹立電動車快速充電的產業標竿,同時協助美國能源部達成提升電動車普及率的策略目標。

台達電表示,400kW 高速充電機採用碳化矽(SiC)MOSFET 元件並導入創新的固態變壓器拓撲設計(Topology),替代利用低壓交流電的傳統工頻變壓器技術,將可直接轉換 4.8kV 或 13.2kV 的中壓交流電為電動車以高達 3C 充電率快速充電。未來高續航里程的電動車款,10 分鐘的充電時間即可提供一半的最高續航里程,以續航里程為 360 英里的電動車為例,10 分鐘的充電,就可行駛 180 英里。此外,與現今業界的直流快速充電機相比,其系統效率預期將提升 3.5% 達到 96.5%、同時減少一半的設備體積,重量更只有四分之一。 而內建高壓直流端口,可讓此充電機在微電網內運行,降低電動車快速充電對電網的影響。此研究專案的原型機將於 2020 年測試運行。

台達電也表示,除了電動車充電技術的提升,此研究專案的數據和成果將能幫助汽車製造商、相關技術提供者、城市政府、與電力公司更加了解電動車高速充電如何影響電力需量反應規劃,以及充電站如何整合可再生能源,以避免大量的高速充電對電網基礎設施造成壓力。

(本文內容由 授權使用。首圖來源:科技新報)

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

電動車前景看俏,全球電動車累計銷售量已突破 400 萬

彭博能源財經(BNEF)最新報告指出,全球電動自小客車累計銷售已突破 400 萬大關,雖然乍看之下只佔總汽車銷售量的一小部分,但與 2015 年的 100 萬輛相比,其成長速度可說是一日千里。

BNEF 指出,若把電動巴士計算在內,電動車銷售量早在 7 月初就抵達 400 萬,其中電動自小客車全球銷售量在 2018 年 6 月底來到 350 萬輛,電動巴士則為是 421,000 輛,總銷售量為 397 萬。

報告顯示,電動車銷售量從 100 萬到 200 萬輛僅花費 17 個月,更在短短 6 個月就從 300 萬增加到 400 萬輛,而隨著電動車技術進步與價格下滑,未來電動車發展將踩油門加速,全球電動車銷售量僅需 6 個月、在 2019 年 3 月就能突破 500 萬。

電動車銷售量與日俱增,電動車銷售佔比在中國、歐洲和北美等主要市場也不斷提升,以 2018 年第二季來說,電動車就分別占當地總銷售的 4%、2.3% 與 1.6%。中國市場則是全球電動車發展迅速的一大功臣,中國市場早在 2011 年就佔全球電動自小客車總銷售的 37%,更占電動巴士的 99%。

BNEF 指出,未來中國將佔全球電動車總銷售的 42%,歐洲與北美分別占 26% 與 25%,若特斯拉平價電動車款 Model 3 在北美的銷售行情一路上漲,北美電動車銷售量則會迅速超越歐洲,而這兩個市場的銷售量也將同時達到 130 萬輛。

報告也表示,2018 年底之前還會有幾款電動車上市,這將能進一步提升全球電動車銷售市場。BNEF 指出,Model 3 將於 2019 年中旬進入歐洲市場、中國的「雙積分制」也將在 2019 年生效,新型車款與政策都能推動歐洲與中國電動車買氣。

中國雙積分制規定各大車廠必須出售一定比例的環保車,其中分為「油耗積分」與「新能源積分」,若車廠生產越多汽油車,油耗積分就會隨之減少;生產越多高性能電動車,新能源積分就越多。車廠每年正負積分必須抵銷歸零,如果積分沒辦法歸零就不能販售新車。

在政策挹注之下,中國電動車發展將逐步加速。中國媒體也指出,中國政府預估新能源車產量可在 2020 年達到 200 萬輛,銷售佔比更會在 2025 年達到總汽車市場的 20%。

BNEF 先前預估,2025 年全球電動車累計銷售量將增加 10 倍、達到 1,100 萬輛,2020-2030 年電動車價格則可與傳統汽車相當,2030 年全球電動車銷售量有望突破 3,000 萬輛。

隨著氣候變遷與空氣污染加劇,各國開始意識到電動車開發的重要性、紛紛開始制定禁售汽柴油車時間表與路上零排放目標,未來電動車的發展還會再加快,說不定可提早突破 1,000 萬大關。

(首圖來源:。文/DaisyChuang)

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

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Panasonic 歐洲 EV 零件傳大增產,擴產至 10 倍

日經新聞 1 日報導,因訂單以歐洲豪華車廠為中心呈現增加,故 Panasonic 計畫投下 100 億日圓資金,於 2023 年將歐洲電動車(EV)用零件產能擴增至現行的 10 倍以上水準。報導指出,Panasonic 所計畫增產的對象為 EV、插電式油電混合車(PHV)等電動化車款充電時所需要的車用充電器等產品,Panasonic 將擴增捷克工廠、斯洛伐克工廠的車用充電器等產品產能,搶攻需求看俏的電動化車款需求。

據報導,Panasonic 的車用充電器具備小型、高輸出等特性,有助於讓車用電池在更短的時間內完成充電,各家車廠預計於 2019 年以後開賣的新型 EV 已決定採用。

Panasonic 歐洲事業負責人 Laurent Abadie(Panasonic 常務執行幹部)接受日經新聞採訪時表示,「計畫將歐洲車用事業營收擴增至 2 倍以上水準」。Laurent Abadie 並透露,考慮祭出購併措施。

Panasonic 車用事業目前以車用電池為主,日美歐車廠所生產的約 70 款車種採用了 Panasonic 的電池產品,而 Panasonic 計畫將車用事業產品群多樣化,除了車用電池之外、也將對 EV 相關零件進行積極投資,目標在 2021 年度將車用事業營收擴增至 2.5 兆日圓、將較 2017 年度增加約 5 成。

Panasonic 於 7 月 31 日公布財報資料指出,因車用事業業績佳,提振上季(2018 年 4-6 月)合併營收較去年同期成長 7.7% 至 2 兆 87 億日圓、合併營益大增 19.1% 至 999.56 億日圓。Panasonic 預估 2018 年度(2018 年 4 月-2019 年 3 月)合併營收將成長 4.0% 至 8.3 兆日圓、合併營益將勁揚 11.7% 至 4,250 億日圓。

(本文內容由 授權使用。首圖來源: CC BY 2.0)

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

用高科技提升訓練效率 竹市消防首創「MR實境」救災場景

新竹市消防教育訓練基地獲經濟部前瞻計畫補助,導入全國首創MR(混合實境)、VR(虛擬實境)等,模擬出幾乎真實場景,還有高溫濃煙、火焰閃燃等,都是過去消防員如何寫文案唯有透過實際救災才能接觸火災危險,全面提升消防員救災訓練效率。竹市長林智銷售文案堅今搶先體驗,他與消防員組隊,在MR(混合實境)撲滅失火民宅,並救出生還者。

林智堅市長9日到訓練基地視察,首度體驗MR(混合實境)救災訓練設施,透過實境眼鏡,能讓受訓者眼中的訓練基地變成失火民宅實況,起火、受困民眾皆栩栩如生。林智堅市長擔任水線瞄子手(瞄子即為消防水帶前網頁設計端噴射頭),在消防訓練教官指導與隊員協助下,嘗試滅火更救出生還者。他也體驗消防員訓練日常,先換裝網頁設計公司消防衣,負重爬兩層樓梯,還接受搜救訓練,進入倒塌大樓內,協助搜尋生還者。

林智堅市長說,MR(混合實境)系統,最佳優點消防訓練教官可透過系統看見學員視野,提iphone維修醒學員忽略的救災盲點,即時糾正並指導救災,救災訓練更加全面。另外,該系統也可讓學員重複練習,不須像過去顧慮模擬真實場景的高成本。讓消防員能透過高科技身歷其境火場實況,事半功倍做好救災應變準備。

MR能高度擬真模擬救災現場狀貨運況,帶上實境眼鏡能看見火場,MR的「感應握把」更可裝在連接水帶的噴頭(即瞄子)上,學員戴上眼鏡、手持握把,FB行銷不只身歷其境,連器材的觸感及重量都十分逼真。透過VR(虛擬實境)打造的消包裝行銷防救災模擬演練系統,還網頁設計特別針對新竹街景特色,模擬出大型購物中心(仿大遠百)、高層建租車築物(仿國賓飯店)、狹小巷弄地區(仿關帝廟)等火災樣貌,以便消防指揮官訓練調度指揮。

消防局長李世恭表示,新竹市消防教育訓練基地成立迄今,已有16年,代訓人數已超過3萬4000人。利用中央補助經費建立「多功能應用台北網頁設計救助訓練設施」仿造山坡、密閉空間場景,更可全面訓練學員救災能力。未來,新竹市消防教育訓練基地不僅訓練消防員,也將轉台北網頁設計化訓練課程,協助國內大型企業及高科技公司等單位,訓練公安人員,提升自我防護能力,共創防救災產、官、學界三贏。

Java 從入門到進階之路(二十三)

在之前的文章我們介紹了一下 Java 中的  集合框架中的Collection 的迭代器 Iterator,本章我們來看一下 Java 集合框架中的Collection 的泛型。

在講泛型之前我們先來看下面一段代碼:

 1 public class Main {
 2     public static void main(String[] args) {
 3         Point point = new Point(1, 2);
 4 
 5         point.setX(2);
 6         int ix = point.getX();
 7         System.out.println(ix); // (2, 2)
 8 
 9         /**
10          * 如果想要 x 值變為 double 類型則可以強轉為 double 類型
11          * */
12         point.setX(2);
13         double dx = (double) point.getX();
14         System.out.println(dx); // 2.0
15     }
16 }
17 
18 class Point {
19     private int x;
20     private int y;
21 
22     public Point(int x, int y) {
23         this.x = x;
24         this.y = y;
25     }
26 
27     public int getX() {
28         return x;
29     }
30 
31     public void setX(int x) {
32         this.x = x;
33     }
34 
35     public int getY() {
36         return y;
37     }
38 
39     public void setY(int y) {
40         this.y = y;
41     }
42 
43     @Override
44     public String toString() {
45         return "(" + x + ", " + y + ")";
46     }
47 }

上面的代碼我們之前的文章講過,我們可以通過傳入 x 和 y 值來定義 Point 點,如果我們想要 double 類型的點時需要造型為 double 類型,那我要定義漢字類型的呢?那就造型成 String 類型,這就很麻煩,每次都需要自己來造型,有種鞋不合腳的感覺,那能不能定義我想要什麼類型就是什麼類型呢,如下:

 1 public class Main {
 2     public static void main(String[] args) {
 3         Point<Integer> point1 = new Point<Integer>(1, 2); // 必須是包裝類
 4         point1.setX(1);
 5         System.out.println(point1.getX()); // 1
 6 
 7         Point<Double> point2 = new Point<Double>(1.1, 2.1); // 必須是包裝類
 8         point2.setX(1.2);
 9         System.out.println(point2.getX()); // 1.2
10 
11         Point<String> point3 = new Point<String>("一", "二"); // 必須是包裝類
12         point3.setX("三");
13         System.out.println(point3.getX()); //
14     }
15 }
16 
17 /**
18  * 泛型
19  * 又稱參數化類型,是將當前類的屬性的類型,方法參數的類型及方法
20  * 返回值的類型的定義權移交給使用者,
21  * 使用者在創建當前類的同時將泛型的試劑類型傳入
22  * 数字和字母組合,数字不能開頭
23  */
24 class Point<T> { // 定義為泛型 T 類型
25     private T x;
26     private T y;
27 
28     public Point(T x, T y) {
29         this.x = x;
30         this.y = y;
31     }
32 
33     public T getX() {
34         return x;
35     }
36 
37     public void setX(T x) {
38         this.x = x;
39     }
40 
41     public T getY() {
42         return y;
43     }
44 
45     public void setY(T y) {
46         this.y = y;
47     }
48 
49     @Override
50     public String toString() {
51         return "(" + x + ", " + y + ")";
52     }
53 }

從上面的代碼中,我們定義了一個 T 的類型 Point,當我們要實例化該類時,根據自己的需求傳入想要的包裝類類型即可,這樣就滿足了不同的需求,各取所需。 

泛型從底層來說其實就是 Object,定義了泛型只是編譯器在做一些驗證工作,當我們對泛型類型設置值時,會檢查是否滿足類型要求,當我們獲取一個泛型類型的值時,會自動進行類型轉換。

在平時我們是很少自己來定義泛型的,泛型是用來約束集合中元素的類型,如下:

 1 import java.util.ArrayList;
 2 import java.util.Collection;
 3 import java.util.Iterator;
 4 
 5 public class Main {
 6     public static void main(String[] args) {
 7         Collection<String> collection = new ArrayList<String>(); // 只能添加 String 類型的元素
 8         collection.add("one");
 9         collection.add("two");
10         collection.add("thee");
11         collection.add("four");
12         // collection.add(1); // 編譯錯誤
13         for (String string : collection) {
14             System.out.println(string); // one two three four
15         }
16         Iterator<String> iterator = collection.iterator();
17         while (iterator.hasNext()) {
18             // String string = (String) iterator.next(); 不需要再造型
19             String string = iterator.next();
20             System.out.println(string); // one two three four
21         }
22     }
23 }

  

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

Spring Boot 把 Maven 幹掉了,擁抱 Gradle!

在國外某社交網站上有一個關於遷移 Spring Boot 遷移 Maven 至 Gradle 的帖子:

該貼子上也有很多人質疑:Maven 用的好好的,為什麼要遷移至 Gradle?

雖然該貼子只是說 Gradle 牛逼,但並沒有說遷移至 Gradle 所帶來的影響和價值。

所以,Spring Boot 官方對此也發了博文作了解釋:

https://spring.io/blog/2020/06/08/migrating-spring-boot-s-build-to-gradle

棧長簡單概括一下。

沒錯,Spring Boot 做了一個重大調整:

在 Spring Boot 2.3.0.M1 中,將首次使用 Gradle 代替 Maven 來構建 Spring Boot 項目。

為什麼要遷移?

Spring Boot 團隊給出的主要原因是,遷移至 Gradle 可以減少構建項目所花費的時間

因為使用 Maven 構建,回歸測試時間太長了,等待項目構建大大增加了修復 bug 和實現新特性的時間。

而 Gradle 的宗旨是減少構建工作量,它可以根據需要構建任何有變化的地方或者并行構建。

當然,Spring Boot 團隊也花了很多時間來嘗試用 Maven 進行 并行構建,但因為構建 Spring Boot 項目的複雜性,最終失敗了。

另外,Spring Boot 團隊也看到了在其他 Spring 項目中使用 Gradle 以及并行構建所帶來的提升,並且還可以使用 Gradle 在一些第三方項目上的構建緩存,這些優勢都促使 Gradle 帶到構建 Spring Boot 項目中來。

遷移有什麼好處?

棧長使用 Maven,哪怕只改一個代碼也是構建全部,構建項目確實要花不少時間。

Spring Boot 官方也給出了數據,一次完整的 Maven 項目構建一般需要一個小時或者以上,而在過去的 4 周時間內,使用 Gradle 構建的平均時間只用了 9 分 22 秒!!!

如下面截圖所示:

光從構建時間來看,效率真是倍數級的。

https://github.com/spring-projects/spring-boot/tree/v2.3.0.RELEASE

棧長特意去看了下,在 Spring Boot 2.2.8 中使用的是 Maven:

而最新發布的 Spring Boot 2.3.1 已經是切換到 Gradle 了:

會帶來什麼影響?

也許會有小夥伴質疑,Spring Boot 遷移到了 Gradle,會不會對公司現有的 Maven 項目或者後續的版本升級造成影響?

如果你只是使用 Spring Boot 框架來搭建系統,那還是可以繼續使用 Maven 來管理依賴的,Spring Boot 會繼續在 Maven 中央倉庫提交。

如下面所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot</artifactId>
    <version>2.3.1.RELEASE</version>
</dependency>

因為當版本確定之後,這個 Maven 構建只是一次性的,不會影響 Spring Boot 團隊的日常迭代效率。

但是,如果我們需要在本地構建 Spring Boot 源碼,或者你正在學習最新 Spring Boot 源碼,就需要掌握 Gradle 構建了。

題外話,Gradle 肯定是未來的趨勢,但也不一定非得遷移至 Gradle,只有適合自己的才是最好的,畢竟現在 Maven 和 Gradle 都是主流,但是 Maven 更佔有市場,很多主流開源項目都是以 Maven 依賴來作為示例演示的。

棧長也會陸續關注 Spring Boot 動態,後續也會給大家帶來各方面的教程,獲取歷史教程可以在Java技術棧公眾號後台回復:boot,掌握 Spring Boot 問題不大。

學習、從不止步。

推薦去我的博客閱讀更多:

1.Java JVM、集合、多線程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※教你寫出一流的銷售文案?

※超省錢租車方案

從0到1學會logstash的玩法(ELK)

本篇文章採用的採用的是logstash7.7.0版本,主要從如下幾個方面介紹

1、logstash是什麼,可以用來幹啥

2、logstash的基本原理是什麼

3、怎麼去玩這個elk的組件logstash

一、logstash是什麼,有哪些作用

1.1、概念

官方概念:Logstash是免費且開放的服務器端數據處理管道,能夠從多個來源採集數據,轉換數據,然後將數據發送到您最喜歡的“存儲庫”中。

1.2、功能

Logstash能夠動態地採集、轉換和傳輸數據,不受格式或複雜度的影響。利用Grok從非結構化數據中派生出結構,從IP地址解碼出地理坐標,匿名化或排除敏感字段,並簡化整體處理過程。

採集數據:

  能夠採集各種樣式、大小和來源的數據往往以各種各樣的形式,比如log日誌,收集redis、kafka等熱門分佈式技術的數據,並且還可以收集實現了java的JMS規範的消息中心的數據,或分散或集中地存在於很多系統中。Logstash支持各種輸入選擇,可以同時從眾多常用來源捕捉事件。能夠以連續的流式傳輸方式,輕鬆地從日誌、指標、Web應用、數據存儲以及各種AWS服務採集數據

 

 

 

解析數據和轉換:

  數據從源傳輸到存儲庫的過程中,Logstash過濾器能夠解析各個事件,識別已命名的字段以構建結構,並將它們轉換成通用格式,以便進行更強大的分析和實現商業價值。
Logstash能夠動態地轉換和解析數據,不受格式或複雜度的影響:

  • 利用Grok從非結構化數據中解析出結構數據
  • 從IP地址破譯出地理坐標
  • 將PII數據匿名化,完全排除敏感字段
  • 簡化整體處理,不受數據源、格式或架構的影響

  數據輸入端從各種數據源收集到的數據可能會有很多不是我們想要的,這時我們可以給Logstash定義過濾器,過濾器可以定義多個,它們依次執行,最終把我們想要的數據過濾出來,然後把這些數據解析成目標數據庫,如elasticsearch等能支持的數據格式存儲數據。

 

數據轉存:

  選擇好存儲庫,導出數據到存儲庫進行存儲,儘管Elasticsearch是我們的首選輸出方向,能夠為我們的搜索和分析帶來無限可能,但它並非唯一選擇。Logstash提供眾多輸出選擇,可以將數據發送到您要指定的地方,比如redis、kafka等

 

 

 

 

二、logstash的基本原理

  logstash分為三個步驟:inputs(必須的)→ filters(可選的)→ outputs(必須的),inputs生成時間,filters對其事件進行過濾和處理,outputs輸出到輸出端或者決定其存儲在哪些組件里。inputs和outputs支持編碼和解碼。

 

執行模型:

Logstash是協調inputs、filters和outputs執行事件處理的管道。

  Logstash管道中的每個input階段都在自己的線程中運行。將寫事件輸入到內存(默認)或磁盤上的中心隊列。每個管道工作線程從該隊列中取出一批事件,通過配置的filter處理該批事件,然後通過output輸出到指定的組件存儲。管道處理數據量的大小和管道工作線程的數量是可配置的

  默認情況下,Logstash使用管道階段(input→filter和filter→output)之間的內存限制隊列來緩衝事件。如果Logstash不安全地終止,存儲在內存中的所有事件都將丟失。為了幫助防止數據丟失,可以啟用Logstash將飛行中的事件持久化到磁盤。有關詳細信息,請參閱持久隊列https://www.elastic.co/guide/en/logstash/current/persistent-queues.html

如下是目前logstash7.7.0支持的inputs、outputs、filters

inputs:

azure_event_hubs,beats,cloudwatch,couchdb_changes,dead_letter_queue,elasticsearch,exec,file,ganglia,gelf,generator,github,google_cloud_storage,google_pubsub,graphite,heartbeat,http,http_poller,imap,irc,java_generator,java_stdin,jdbc,jms,jmx,kafka,kinesis,log4j,lumberjack,meetup,pipe,puppet_facter,rabbitmq,redis,relp,rss,s3,s3-sns-sqs,salesforce,snmp,snmptrap,sqlite,sqs,stdin,stomp,syslog,tcp,twitter,udp,unix,varnishlog,websocket,wmi,xmpp

outputs:

boundary, circonus, cloudwatch, csv, datadog, datadog_metrics, elastic_app_search, elasticsearch, email, exec, file, ganglia, gelf, google_bigquery, google_cloud_storage, google_pubsub, graphite, graphtastic, http, influxdb, irc, sink, java_stdout, juggernaut, kafka, librato, loggly, lumberjack, metriccatcher, mongodb, nagios, nagios_nsca, opentsdb, pagerduty, pipe, rabbitmq, redis, redmine, riak, riemann, s3, sns, solr_http, sqs, statsd, stdout, stomp, syslog, tcp, timber, udp, webhdfs, websocket, xmpp, zabbix

filters:

aggregate, alter, bytes, cidr, cipher, clone, csv, date, de_dot, dissect, dns, drop, elapsed, elasticsearch, environment, extractnumbers, fingerprint, geoip, grok, http, i18n, java_uuid, jdbc_static, jdbc_streaming, json, json_encode, kv, memcached, metricize, metrics, mutate, prune, range, ruby, sleep, split, syslog_pri, threats_classifier, throttle, tld, translate, truncate, urldecode, useragent, uuid, xml

三、玩一玩logstash

3.1、壓縮包方式安裝

下載地址1:https://www.elastic.co/cn/downloads/logstash   

下載地址2:https://elasticsearch.cn/download/

這裏需要安裝jdk,我使用的是elasticsearch7.7.0自帶的jdk:

解壓即安裝:

tar -zxvf logstash-7.7.0.tar.gz

來個logstash版本的HelloWorld:

./bin/logstash -e 'input { stdin { } } output { stdout {} }'

 

 

 

3.2、logstash配置文件

 logstash.yml:包含Logstash配置標誌。您可以在此文件中設置標誌,而不是在命令行中傳遞標誌。在命令行上設置的任何標誌都會覆蓋logstash中的相應設置

 pipelines.yml:包含在單個Logstash實例中運行多個管道的框架和指令。

 jvm.options:包含JVM配置標誌。使用此文件設置總堆空間的初始值和最大值。您還可以使用此文件為Logsta設置語言環境

 log4j2.properties:包含log4j 2庫的默認設置

 start.options (Linux):用於配置啟動服務腳本

logstash.yml文件詳解:

node.name  #默認主機名,該節點的描述名字
path.data  #LOGSTASH_HOME/data ,Logstash及其插件用於任何持久需求的目錄
pipeline.id #默認main,pipeline的id
pipeline.java_execution #默認true,使用java執行引擎
pipeline.workers #默認為主機cpu的個數,表示并行執行管道的過濾和輸出階段的worker的數量
pipeline.batch.size #默認125 表示單個工作線程在嘗試執行過濾器和輸出之前從輸入中收集的最大事件數
pipeline.batch.delay #默認50 在創建管道事件時,在將一個小批分派給管道工作者之前,每個事件需要等待多長時間(毫秒)
pipeline.unsafe_shutdown  #默認false,當設置為true時,即使內存中仍有運行的事件,強制Logstash在關閉期間將會退出。默認情況下,Logstash將拒絕退出,直到所有接收到的事件都被推入輸出。啟用此選項可能導致關機期間數據丟失
pipeline.ordered #默認auto,設置管道事件順序。true將強制對管道進行排序,如果有多個worker,則阻止logstash啟動。如果為false,將禁用維持秩序所需的處理。訂單順序不會得到保證,但可以節省維護訂單的處理成本
path.config #默認LOGSTASH_HOME/config  管道的Logstash配置的路徑
config.test_and_exit #默認false,設置為true時,檢查配置是否有效,然後退出。請注意,使用此設置不會檢查grok模式的正確性
config.reload.automatic #默認false,當設置為true時,定期檢查配置是否已更改,並在更改時重新加載配置。這也可以通過SIGHUP信號手動觸發
config.reload.interval  #默認3s ,檢查配置文件頻率
config.debug #默認false 當設置為true時,將完全編譯的配置显示為調試日誌消息
queue.type #默認memory ,用於事件緩衝的內部排隊模型。為基於內存中的遺留隊列指定內存,或為基於磁盤的脫機隊列(持久隊列)指定持久內存
path.queue #默認path.data/queue  ,在啟用持久隊列時存儲數據文件的目錄路徑
queue.page_capacity #默認64mb ,啟用持久隊列時(隊列),使用的頁面數據文件的大小。隊列數據由分隔為頁面的僅追加數據文件組成
queue.max_events #默認0,表示無限。啟用持久隊列時,隊列中未讀事件的最大數量
queue.max_bytes  #默認1024mb,隊列的總容量,以字節為單位。確保磁盤驅動器的容量大於這裏指定的值
queue.checkpoint.acks #默認1024,當啟用持久隊列(隊列)時,在強制執行檢查點之前被隔離的事件的最大數量
queue.checkpoint.writes #默認1024,當啟用持久隊列(隊列)時,強制執行檢查點之前的最大寫入事件數
queue.checkpoint.retry #默認false,啟用后,對於任何失敗的檢查點寫,Logstash將對每個嘗試的檢查點寫重試一次。任何後續錯誤都不會重試。並且不推薦使用,除非是在那些特定的環境中
queue.drain #默認false,啟用后,Logstash將等待,直到持久隊列耗盡,然後關閉
path.dead_letter_queue#默認path.data/dead_letter_queue,存儲dead-letter隊列的目錄
http.host #默認"127.0.0.1" 表示endpoint REST端點的綁定地址。
http.port #默認9600 表示endpoint REST端點的綁定端口。
log.level #默認info,日誌級別fatal,error,warn,info,debug,trace,
log.format #默認plain 日誌格式
path.logs  #默認LOGSTASH_HOME/logs 日誌目錄

3.3、keystore

keystore可以保護一些敏感的信息,使用變量的方式替代,比如使用ES_PWD代替elasticsearch的密碼,可以通過${ES_PWD}來獲取elasticsearch的密碼,這樣就是的密碼不再是明文密碼。

./bin/logstash-keystore create   #創建一個keyword
 ./bin/logstash-keystore add ES_PWD #創建一個elastic的passwd,然後通過${ES_PWD}使用該密碼
 ./bin/logstash-keystore list  #查看已經設置好的鍵值對
 ./bin/logstash-keystore remove ES_PWD #刪除在keyword中的key

例如:

 

 

 3.4、logstash命令

注意:參數和logstash.yml配置文件對應(這裏不詳解,請查看3.2節)

-n, --node.name NAME          
-f, --path.config CONFIG_PATH 
-e, --config.string CONFIG_STRING
--field-reference-parser MODE 
--modules MODULES             
-M, --modules.variable MODULES_VARIABLE
--setup                      
--cloud.id CLOUD_ID                                            
--cloud.auth CLOUD_AUTH       
--pipeline.id ID              
-w, --pipeline.workers COUNT 
--pipeline.ordered ORDERED   
--java-execution                                               
--plugin-classloaders         
-b, --pipeline.batch.size SIZE 
-u, --pipeline.batch.delay DELAY_IN_MS 
--pipeline.unsafe_shutdown    
--path.data PATH              
-p, --path.plugins PATH       
-l, --path.logs PATH         
--log.level LEVEL             
--config.debug                
-i, --interactive SHELL      
-V, --version                 
-t, --config.test_and_exit    
-r, --config.reload.automatic 
--config.reload.interval 
--http.host HTTP_HOST         
--http.port HTTP_PORT         
--log.format FORMAT           
--path.settings SETTINGS_DIR

3.5、logstash配置文件的格式和value type

1、logstash配置文件的格式如下:

輸入,解析過濾,輸出,其中filter不是必須的,其他兩個是必須的。

input {
  ...
}

filter {
  ...
}

output {
  ...
}

2、value types(logstash支持的數據類型)

array:數組可以是單個或者多個字符串值。

users => [ {id => 1, name => bob}, {id => 2, name => jane} ]

Lists:集合

path => [ "/var/log/messages", "/var/log/*.log" ]
uris => [ "http://elastic.co", "http://example.net" ]

Boolean:true 或者false

ssl_enable => true

Bytes:字節類型

my_bytes => "1113"   # 1113 bytes
my_bytes => "10MiB"  # 10485760 bytes
my_bytes => "100kib" # 102400 bytes
my_bytes => "180 mb" # 180000000 bytes

Codec:編碼類型

codec => "json"

Hash:哈希(散列)

match => {
  "field1" => "value1"
  "field2" => "value2"
  ...
}
# or as a single line. No commas between entries:
match => { "field1" => "value1" "field2" => "value2" }

Number:数字類型

port => 33

Password:密碼類型

my_password => "password"

URI:uri類型

my_uri => "http://foo:bar@example.net"

Path: 路徑類型

my_path => "/tmp/logstash"

String:字符串類型,字符串必須是單個字符序列。注意,字符串值被括在雙引號或單引號中

3.6、logstash的權限配置

配置賬號,一個種是role,一種是user,配置方式有兩種,一種是通過elasticsearch的API配置,一種是通過kibana配置:

第一種方式:通過elasticsearch的API配置:

#添加一個logstash_writer的角色
POST _xpack/security/role/logstash_writer
{
  "cluster": ["manage_index_templates", "monitor", "manage_ilm"], 
  "indices": [
    {
      "names": [ "logstash-*" ],  #索引的模式匹配
      "privileges": ["write","create","delete","create_index","manage","manage_ilm"]   #權限內容
    }
  ]
}

#添加一個有logstash_writer角色權限的用戶:logstash_internal
POST _xpack/security/user/logstash_internal
{
  "password" : "x-pack-test-password",
  "roles" : [ "logstash_writer"],  #分配角色
  "full_name" : "Internal Logstash User"
}

#添加一個logstash_reader角色,只有read權限
POST _xpack/security/role/logstash_reader
{
  "indices": [
    {
      "names": [ "logstash-*" ], 
      "privileges": ["read","view_index_metadata"]
    }
  ]
}

#添加一個有logstash_reader角色權限的用戶:logstash_user
POST _xpack/security/user/logstash_user
{
  "password" : "x-pack-test-password",
  "roles" : [ "logstash_reader", "logstash_admin"], 
  "full_name" : "Kibana User for Logstash"
}

第二種:通過kibana的界面配置

Management > Roles
Management > Users

 

 

 權限選擇見elasticsearch官網:

https://www.elastic.co/guide/en/elasticsearch/reference/current/authorization.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html

 3.7、多管道配置

  如果需要在同一個進程中運行多個管道,Logstash提供了一種通過名為pipelines.yml的配置文件來實現此目的的方法。

例如:

- pipeline.id: my-pipeline_1
  path.config: "/etc/path/to/p1.config"
  pipeline.workers: 3
- pipeline.id: my-other-pipeline
  path.config: "/etc/different/path/p2.cfg"
  queue.type: persisted

  該文件在YAML文件格式中,並包含一個字典列表,其中每個字典描述一個管道,每個鍵/值對指定該管道的設置。該示例展示了通過id和配置路徑描述的兩個不同管道。對於第一個管道,為pipeline.workers的值設置為3,而在另一个中,持久隊列特性被啟用。未在pipelines.yml顯式設置的值。yml文件將使用到logstash中指定的默認值。
  當啟動Logstash不帶參數時,它將讀取管道pipelines.yml。yml文件並實例化文件中指定的所有管道。另一方面,當使用-e或-f時,Logstash會忽略管道。

注意:

  • 如果當前配置的事件流不共享相同的輸入/過濾器和輸出,並且使用標記和條件將它們彼此分離,這顯得多個管道尤其重要
  • 在單個實例中擁有多個管道還允許這些事件流具有不同的性能和持久性參數(例如,pipeline.workers和persistent queues的不同設置)。這種分離意味着一條管道中的阻塞輸出不會對另一條管道產生反壓力。
  • 考慮管道之間的資源競爭是很重要的,因為默認值是針對單個管道調優的。因此,例如,考慮減少每個管道使用pipeline.worker的數量,因為每個管道在默認情況下每個CPU核使用一個worker。
  • 每個管道都隔離持久隊列和死信隊列,它們的位置命名空間為pipeline.id的值

各管道之間的通信原理:https://www.elastic.co/guide/en/logstash/current/pipeline-to-pipeline.html,有興趣的可以了解下。

3.8、配置的重新加載

在我們運行logstash的過程,不想停掉logstash進程,但是又想修改配置,就可以使用到配置的重新加載了,有兩種方式。

第一種是在啟動的時候指定參數:

bin/logstash -f apache.config --config.reload.automatic

Logstash每3秒檢查一次配置更改。要更改此間隔,請使用–config.reload.interval <interval>選項,其中interval指定Logstash檢查配置文件更改的頻率(以秒為單位),請注意,必須使用單位限定符(s)

第二種就是強制加載配置文件

kill -SIGHUP pid  #pid為logstash的pid

自動配置重新加載配置注意點:

  • 當Logstash檢測到配置文件中的更改時,它將通過停止所有輸入來停止當前管道,並嘗試創建使用更新后的配置的新管道。驗證新配置的語法后,Logstash驗證所有輸入和輸出都可以初始化(例如,所有必需的端口都已打開)。如果檢查成功,則Logstash會將現有管道與新管道交換。如果檢查失敗,舊管道將繼續運行,並且錯誤將傳播到控制台。
  • 在自動重新加載配置期間,不會重新啟動JVM。管道的創建和交換都在同一過程中進行。
  • 對grok模式文件的更改也將重新加載,但僅在配置文件中的更改觸發重新加載(或重新啟動管道)時。

3.9、常用filter介紹

1、grok

注:grok插件是一個十分耗費資源的插件

官網:https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html

Grok是將非結構化日誌數據解析為結構化和可查詢內容的好方法,非常適合syslog日誌,apache和其他Web服務器日誌,mysql日誌等等

首先官方提供了120中的匹配模式(但是我一直都沒打開這個網址):https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns

還有一個用來驗證自定義的解析是否正確的一個網址:http://grokdebug.herokuapp.com/

既然我沒能打開官方提供的120個的模式匹配,從一片博客中找到了一部分的匹配模式(如下):https://blog.csdn.net/cui929434/article/details/94390617

USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
INT (?:[+-]?(?:[0-9]+))
BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))
BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b

POSINT \b(?:[1-9][0-9]*)\b
NONNEGINT \b(?:[0-9]+)\b
WORD \b\w+\b
NOTSPACE \S+
SPACE \s*
DATA .*?
GREEDYDATA .*
QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

# Networking
MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])
IP (?:%{IPV6}|%{IPV4})
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
HOST %{HOSTNAME}
IPORHOST (?:%{HOSTNAME}|%{IP})
HOSTPORT %{IPORHOST}:%{POSINT}

# paths
PATH (?:%{UNIXPATH}|%{WINPATH})
UNIXPATH (?>/(?>[\w_%!$@:.,-]+|\\.)*)+
TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
URIHOST %{IPORHOST}(?::%{POSINT:port})?
# uripath comes loosely from RFC1738, but mostly from what Firefox
# doesn't turn into %XX
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?

# Months: January, Feb, 3, 03, 12, December
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
MONTHNUM (?:0?[1-9]|1[0-2])
MONTHNUM2 (?:0[1-9]|1[0-2])
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])

# Days: Monday, Tue, Thu, etc...
DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)

# Years?
YEAR (?>\d\d){1,2}
HOUR (?:2[0123]|[01]?[0-9])
MINUTE (?:[0-5][0-9])
# '60' is a leap second in most time standards and thus is valid.
SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
ISO8601_SECOND (?:%{SECOND}|60)
TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
DATE %{DATE_US}|%{DATE_EU}
DATESTAMP %{DATE}[- ]%{TIME}
TZ (?:[PMCE][SD]T|UTC)
DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}
DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}

# Syslog Dates: Month Day HH:MM:SS
SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
PROG (?:[\w._/%-]+)
SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
SYSLOGHOST %{IPORHOST}
SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}

# Shortcuts
QS %{QUOTEDSTRING}

# Log formats
SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}

# Log Levels
LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

grok內置的一些匹配模式

基礎語法,一種是自帶的模式,一種是自定義的模式:

自帶的模式語法:  %{SYNTAX:SEMANTIC}

SYNTAX是將匹配文本模式的名稱,grok自帶的那些匹配模式名
SEMANTIC是你給一段文字的標識相匹配該匹配模式匹配到的內容,相當於一個字段名

例如:

%{NUMBER:duration} %{IP:client}

比如要解析如下的日誌:

55.3.244.1 GET /index.html 15824 0.043

就可以使用該匹配模式去匹配:

%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}

得出的結果如下:

client: 55.3.244.1
method: GET
request: /index.html
bytes: 15824
duration: 0.043

 

自定義的模式語法:(?<field_name>the pattern here)

例如:

(?<queue_id>[0-9A-F]{10,11})  #表示10-11個字符的16進制

我們可以創建一個目錄pattters,把我自定義的模式添加進去,在使用的時候就可以使用grok自帶的匹配模式的語法,例如:

我們在./patterns/postfix文件中添加如下內容
POSTFIX_QUEUEID [0-9A-F]{10,11}

如上我們就定義了一個匹配模式了,可以使用如下的方式使用。假如我們有如下的日誌格式:

Jan  1 06:25:43 mailserver14 postfix/cleanup[21403]: BEF25A72965: message-id=<20130101142543.5828399CCAF@mailserver14.example.com>

然後我們對其就行解析:

filter {
      grok {
        patterns_dir => ["./patterns"]  #指定自定義的匹配模式路徑
        match => { "message" => "%{SYSLOGBASE} %{POSTFIX_QUEUEID:queue_id}: %{GREEDYDATA:syslog_message}" }
      }
    }

解析出的結果如下:

timestamp: Jan  1 06:25:43
logsource: mailserver14
program: postfix/cleanup
pid: 21403
queue_id: BEF25A72965
syslog_message: message-id=<20130101142543.5828399CCAF@mailserver14.example.com>

 grok的配置選項

break_on_match
值類型是布爾值
默認是true
描述:match可以一次設定多組,預設會依照順序設定處理,如果日誌滿足設定條件,則會終止向下處理。但有的時候我們會希望讓Logstash跑完所有的設定,這時可以將break_on_match設為false。

keep_empty_captures
值類型是布爾值
默認值是 false
描述:如果為true,捕獲失敗的字段將設置為空值

match
值類型是數組
默認值是 {}
描述:字段值的模式匹配
例如:
filter {
  grok { match => { "message" => "Duration: %{NUMBER:duration}" } }
}
#如果你需要針對單個字段匹配多個模式,則該值可以是一組,例如:
filter {
  grok { match => { "message" => [ "Duration: %{NUMBER:duration}", "Speed: %{NUMBER:speed}" ] } }
}

named_captures_only
值類型是布爾值
默認值是 true
描述:如果設置為true,則僅存儲來自grok的命名捕獲

overwrite
值類型是 array
默認是[]
描述:覆蓋已經存在的字段內容
例如:

filter {
  grok {
    match => { "message" => "%{SYSLOGBASE} %{DATA:message}" }
    overwrite => [ "message" ]
  }
}
如果日誌是May 29 16:37:11 sadness logger: hello world經過match屬性match => { “message” => “%{SYSLOGBASE} %{DATA:message}” }處理后,message的值變成了hello world。這時如果使用了overwrite => [ “message” ]屬性,那麼原來的message的值將被覆蓋成新值。

pattern_definitions
值類型是 數組
默認值是 {}
描述:模式名稱和模式正則表達式,也是用於定義當前過濾器要使用的自定義模式。匹配現有名稱的模式將覆蓋預先存在的定義。可以將此視為僅適用於grok定義的內聯模式,patterns_dir是將模式寫在外部。
例如:
filter {
    grok {
        patterns_dir => "/usr/local/elk/logstash/patterns"
        pattern_definitions => {"MYSELFTIMESTAMP" => "20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND})"}
        match => {"message" => ["%{MYSELFTIMESTAMP:timestamp} %{JAVACLASS:message}","%{MYSELF:content}"]}
    }
}

patterns_dir
值類型是數組
默認值是 []
描述:一些複雜的正則表達式,不適合直接寫到filter中,可以指定一個文件夾,用來專門保存正則表達式的文件,需要注意的是該文件夾中的所有文件中的正則表達式都會被依次加載,包括備份文件。

patterns_dir => ["/opt/logstash/patterns", "/opt/logstash/extra_patterns"]
正則文件以文本格式描述:

patterns_file_glob
屬性值的類型:string
默認值:“*”
描述:針對patterns_dir屬性中指定的文件夾里哪些正則文件,可以在這個filter中生效,需要本屬性來指定。默認值“*”是指所有正則文件都生效。

tag_on_failure
值類型是數組
默認值是 [“_grokparsefailure”]
描述:沒有成功匹配時,將值附加到字段到tags

tag_on_timeout
值類型是字符串
默認值是 “_groktimeout”
描述:如果Grok正則表達式超時,則應用標記。

timeout_millis
值類型是数字
默認值是 30000
描述: 嘗試在這段時間后終止正則表達式。如果應用了多個模式,則這適用於每個模式。這將永遠不會提前超時,但超時可能需要一些時間。實際的超時時間是基於250ms量化的近似值。設置為0以禁用超時。

各組件的公共配置選項

break_on_match
值類型是布爾值
默認是true
描述:match可以一次設定多組,預設會依照順序設定處理,如果日誌滿足設定條件,則會終止向下處理。但有的時候我們會希望讓Logstash跑完所有的設定,這時可以將break_on_match設為false。

keep_empty_captures
值類型是布爾值
默認值是 false
描述:如果為true,捕獲失敗的字段將設置為空值

match
值類型是數組
默認值是 {}
描述:字段值的模式匹配
例如:

filter {
  grok { match => { "message" => "Duration: %{NUMBER:duration}" } }
}

#如果你需要針對單個字段匹配多個模式,則該值可以是一組,例如:
filter {
  grok { match => { "message" => [ "Duration: %{NUMBER:duration}", "Speed: %{NUMBER:speed}" ] } }
}

named_captures_only
值類型是布爾值
默認值是 true
描述:如果設置為true,則僅存儲來自grok的命名捕獲

overwrite
值類型是 array
默認是[]
描述:覆蓋已經存在的字段內容
例如:

filter {
  grok {
    match => { "message" => "%{SYSLOGBASE} %{DATA:message}" }
    overwrite => [ "message" ]
  }
}

如果日誌是May 29 16:37:11 sadness logger: hello world經過match屬性match => { “message” => “%{SYSLOGBASE} %{DATA:message}” }處理后,message的值變成了hello world。這時如果使用了overwrite => [ “message” ]屬性,那麼原來的message的值將被覆蓋成新值。

pattern_definitions
值類型是 數組
默認值是 {}
描述:模式名稱和模式正則表達式,也是用於定義當前過濾器要使用的自定義模式。匹配現有名稱的模式將覆蓋預先存在的定義。可以將此視為僅適用於grok定義的內聯模式,patterns_dir是將模式寫在外部。
例如:

filter {
    grok {
        patterns_dir => "/usr/local/elk/logstash/patterns"
        pattern_definitions => {"MYSELFTIMESTAMP" => "20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND})"}
        match => {"message" => ["%{MYSELFTIMESTAMP:timestamp} %{JAVACLASS:message}","%{MYSELF:content}"]}
    }
}

patterns_dir
值類型是數組
默認值是 []
描述:一些複雜的正則表達式,不適合直接寫到filter中,可以指定一個文件夾,用來專門保存正則表達式的文件,需要注意的是該文件夾中的所有文件中的正則表達式都會被依次加載,包括備份文件。

patterns_dir => ["/opt/logstash/patterns", "/opt/logstash/extra_patterns"]
正則文件以文本格式描述:


patterns_file_glob
屬性值的類型:string
默認值:“*”
描述:針對patterns_dir屬性中指定的文件夾里哪些正則文件,可以在這個filter中生效,需要本屬性來指定。默認值“*”是指所有正則文件都生效。

tag_on_failure
值類型是數組
默認值是 [“_grokparsefailure”]
描述:沒有成功匹配時,將值附加到字段到tags

tag_on_timeout
值類型是字符串
默認值是 “_groktimeout”
描述:如果Grok正則表達式超時,則應用標記。

timeout_millis
值類型是数字
默認值是 30000
描述: 嘗試在這段時間后終止正則表達式。如果應用了多個模式,則這適用於每個模式。這將永遠不會提前超時,但超時可能需要一些時間。實際的超時時間是基於250ms量化的近似值。設置為0以禁用超時。

常用選項
所有過濾器插件都支持以下配置選項:


dd_field
值類型是散列
默認值是 {}
描述:如果匹配成功,向此事件添加任意字段。字段名可以是動態的,並使用%{Field}包含事件的一部分
filter {
      grok {
        add_field => { "foo_%{somefield}" => "Hello world, from %{host}" }
      }
    }

# 你也可以一次添加多個字段
filter {
  grok {
    add_field => {
      "foo_%{somefield}" => "Hello world, from %{host}"
      "new_field" => "new_static_value"
    }
  }
}

add_tag
值類型是數組
默認值是 []
描述:如果此過濾器成功,請向該事件添加任意標籤。標籤可以是動態的,並使用%{field} 語法包含事件的一部分。
例如:

filter {
  grok {
    add_tag => [ "foo_%{somefield}" ]
  }
}

# 你也可以一次添加多個標籤
filter {
  grok {
    add_tag => [ "foo_%{somefield}", "taggedy_tag"]
  }
}

enable_metric
值類型是布爾值
默認值是 true
描述:禁用或啟用度量標準

id
值類型是字符串
此值沒有默認值。
描述:向插件實例添加唯一ID,此ID用於跟蹤插件特定配置的信息。
例如:

filter {
  grok {
    id => "ABC"
  }
}

periodic_flush
值類型是布爾值
默認值是 false
描述:如果設置為ture,會定時的調用filter的更新函數(flush method)

remove_field
值的類型:array
默認值:[]
描述:刪除當前文檔中的指定filted

filter {
  grok {
    remove_field => [ "foo_%{somefield}" ]
  }
}
# 你也可以一次移除多個字段:
filter {
  grok {
    remove_field => [ "foo_%{somefield}", "my_extraneous_field" ]
  }
}

remove_tag
值類型是數組
默認值是 []
描述:如果此過濾器成功,請從該事件中移除任意標籤。標籤可以是動態的,並使用%{field} 語法包括事件的一部分。
例如:

filter {
  grok {
    remove_tag => [ "foo_%{somefield}" ]
  }
}
# 你也可以一次刪除多個標籤
filter {
  grok {
    remove_tag => [ "foo_%{somefield}", "sad_unwanted_tag"]
  }
}

2、mutate

官網網址:https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html

mutate允許對字段執行常規改變。可以重命名、刪除、替換和修改事件中的字段。

二話不說,來個例子先:

filter {
    mutate {
        split => ["hostname", "."]  #切分
        add_field => { "shortHostname" => "%{hostname[0]}" } #獲取切分后的第一個字段作為添加字段
    }
    mutate {
        rename => ["shortHostname", "hostname" ] #重命名
    }
}

mutate的配置選項:

常用的一些操作:
convert:轉換數據類型,數據類型hash
可以裝換的數據類型有:integer,string,integer_eu(和integer相同,显示格式為1.000),float,float_eu,boolean
實例:
filter {
      mutate {
        convert => {
          "fieldname" => "integer"
          "booleanfield" => "boolean"
        }
      }
    }

copy:類型hash,將現有字段複製到另一個字段。現有的目標域將被覆蓋
實例:
 filter {
      mutate {
         copy => { "source_field" => "dest_field" }
      }
    }
    
gsub:類型array,根據字段值匹配正則表達式,並用替換字符串替換所有匹配項。只支持字符串或字符串數組的字段。對於其他類型的字段,將不採取任何操作。
實例:
filter {
      mutate {
        gsub => [
          # replace all forward slashes with underscore
          "fieldname", "/", "_",
          # replace backslashes, question marks, hashes, and minuses
          # with a dot "."
          "fieldname2", "[\\?#-]", "."
        ]
      }
    }
    
join:類型hash,用分隔符連接數組。對非數組字段不執行任何操作
實例:
filter {
     mutate {
       join => { "fieldname" => "," }
     }
   }
  
lowercase:類型array,轉為小寫
實例:
filter {
      mutate {
        lowercase => [ "fieldname" ]
      }
    }
    
merge:類型hash,合併數組或散列的兩個字段。字符串字段將自動轉換為數組
實例:
filter {
     mutate {
        merge => { "dest_field" => "added_field" }
     }
   }
   
coerce:類型hash,設置存在但為空的字段的默認值
實例:
filter {
      mutate {
        # Sets the default value of the 'field1' field to 'default_value'
        coerce => { "field1" => "default_value" }
      }
    }
    
rename:類型hash,重命名
實例:
filter {
      mutate {
        # Renames the 'HOSTORIP' field to 'client_ip'
        rename => { "HOSTORIP" => "client_ip" }
      }
    }
    
replace:類型hash,用新值替換字段的值
實例:
filter {
      mutate {
        replace => { "message" => "%{source_host}: My new message" }
      }
    }
    
split:類型hash,使用分隔符將字段分割為數組。只對字符串字段有效
實例:
filter {
      mutate {
         split => { "fieldname" => "," }
      }
    }
    
strip:類型array,字段中刪除空白。注意:這隻對前導和后導空格有效。
實例:
filter {
      mutate {
         strip => ["field1", "field2"]
      }
    }
    
update:類型hash,使用新值更新現有字段。如果該字段不存在,則不採取任何操作。
實例:
filter {
      mutate {
        update => { "sample" => "My new message" }
      }
    }
    
uppercase:類型array,轉為大寫
實例:
filter {
      mutate {
        uppercase => [ "fieldname" ]
      }
    }
    
capitalize:類型array,將字符串轉換為等效的大寫字母。
實例:
filter {
      mutate {
        capitalize => [ "fieldname" ]
      }
    }

tag_on_failure:類型string,如果在應用此變異篩選器期間發生故障,則終止其餘操作,默認值:_mutate_error

公共配置見:grok公共配置

3、date

date filter用於從字段解析日期,然後使用該日期或時間戳作為事件的logstash時間戳

date常用的配置選項:

match:類型array,字段名在前,格式模式在後的數組[ field,formats... ],表示該字段能夠匹配到的時間模式,時間模式可以有多種
實例:
filter {
      date {
        match => [ "logdate", "MMM dd yyyy HH:mm:ss" ]
      }
    }
    
tag_on_failure:類型array,默認值["_dateparsefailure"],當沒有成功匹配時,將值追加到tags字段

target:類型String,默認值"@timestamp",將匹配的時間戳存儲到給定的目標字段中。如果沒有提供,默認更新事件到@timestamp字段。

timezone:類型String,表示時區,可以在該網址查看:http://joda-time.sourceforge.net/timezones.html

公共配置見:grok公共配置

這裏只對如上三種filter說明,具體其他的filter請見官網:https://www.elastic.co/guide/en/logstash/current/filter-plugins.html

3.10、案例一、apache日誌解析

這個例子屬官網的一個例子:https://www.elastic.co/guide/en/logstash/current/advanced-pipeline.html

但是我這裏不弄這麼負責,我們不使用filebeat,直接使用logstash,apache日誌的數據集下載:https://download.elastic.co/demos/logstash/gettingstarted/logstash-tutorial.log.gz

我這裏不打算安裝apach,所以直接使用官方提供的數據集。

下載數據集,然後解壓文件,就可以得到我們的一個日誌文件:logstash-tutorial.log

首先我們看一下apache日誌的格式:

[elk@lgh ~]$ tail -3 logstash-tutorial.log 
86.1.76.62 - - [04/Jan/2015:05:30:37 +0000] "GET /projects/xdotool/ HTTP/1.1" 200 12292 "http://www.haskell.org/haskellwiki/Xmonad/Frequently_asked_questions" "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20140205 Firefox/24.0 Iceweasel/24.3.0"
86.1.76.62 - - [04/Jan/2015:05:30:37 +0000] "GET /reset.css HTTP/1.1" 200 1015 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20140205 Firefox/24.0 Iceweasel/24.3.0"
86.1.76.62 - - [04/Jan/2015:05:30:37 +0000] "GET /style2.css HTTP/1.1" 200 4877 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20140205 Firefox/24.0 Iceweasel/24.3.0"

然後開始配置:

cd logstash-7.7.0/ && mkdir conf.d
cd conf.d/ 
vim  apache.conf
#############apache.conf的內容如下###################
input {
        file {
            path => "/home/elk/logstash-tutorial.log"
            type => "log"
            start_position => "beginning"
            }
    }
filter {
    grok {
        match => { "message" => "%{COMBINEDAPACHELOG}"}
    }
}

output {
    stdout { codec => rubydebug }
}

然後啟動命令(可以選擇用nohup後台啟動):

 cd logstash-7.7.0/ && ./bin/logstash -f conf.d/apache.conf

執行結果如下(部分結果):

{
           "verb" => "GET",
          "bytes" => "8948",
           "type" => "log",
           "host" => "gxt_126_233",
    "httpversion" => "1.0",
        "message" => "67.214.178.190 - - [04/Jan/2015:05:20:59 +0000] \"GET /blog/geekery/installing-windows-8-consumer-preview.html HTTP/1.0\" 200 8948 \"http://www.semicomplete.com/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:21.0) Gecko/20100101 Firefox/21.0\"",
      "timestamp" => "04/Jan/2015:05:20:59 +0000",
       "referrer" => "\"http://www.semicomplete.com/\"",
     "@timestamp" => 2020-06-17T01:23:47.817Z,
           "path" => "/data/hd05/elk/logstash-tutorial.log",
          "ident" => "-",
       "response" => "200",
       "@version" => "1",
        "request" => "/blog/geekery/installing-windows-8-consumer-preview.html",
       "clientip" => "67.214.178.190",
          "agent" => "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:21.0) Gecko/20100101 Firefox/21.0\"",
           "auth" => "-"
}
{
           "verb" => "GET",
          "bytes" => "1015",
           "type" => "log",
           "host" => "gxt_126_233",
    "httpversion" => "1.1",
        "message" => "66.249.73.185 - - [04/Jan/2015:05:18:48 +0000] \"GET /reset.css HTTP/1.1\" 200 1015 \"-\" \"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"",
      "timestamp" => "04/Jan/2015:05:18:48 +0000",
       "referrer" => "\"-\"",
     "@timestamp" => 2020-06-17T01:23:47.815Z,
           "path" => "/data/hd05/elk/logstash-tutorial.log",
          "ident" => "-",
       "response" => "200",
       "@version" => "1",
        "request" => "/reset.css",
       "clientip" => "66.249.73.185",
          "agent" => "\"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)\"",
           "auth" => "-"
}
{
           "verb" => "GET",
          "bytes" => "28370",
           "type" => "log",
           "host" => "gxt_126_233",
    "httpversion" => "1.0",
        "message" => "207.241.237.220 - - [04/Jan/2015:05:21:16 +0000] \"GET /blog/tags/projects HTTP/1.0\" 200 28370 \"http://www.semicomplete.com/blog/tags/C\" \"Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)\"",
      "timestamp" => "04/Jan/2015:05:21:16 +0000",
       "referrer" => "\"http://www.semicomplete.com/blog/tags/C\"",
     "@timestamp" => 2020-06-17T01:23:47.817Z,
           "path" => "/data/hd05/elk/logstash-tutorial.log",
          "ident" => "-",
       "response" => "200",
       "@version" => "1",
        "request" => "/blog/tags/projects",
       "clientip" => "207.241.237.220",
          "agent" => "\"Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)\"",
           "auth" => "-"
}

執行結果

2.11、案例二、nginx日誌解析

首先安裝nginx:nginx功能介紹和基本安裝

這裏我們也不使用filebeat,因為這篇文章只是介紹logstash

配置:

cd conf.d/ 
vim  nginx.conf
############nginx.conf配置如下#####################
input {
        file {
            path => "/usr/local/nginx/logs/access.log"
            type => "log"
            start_position => "beginning"
            }
    }
   
filter {
grok {
        match => { "message" => ["(?<RemoteIP>(\d*.\d*.\d*.\d*)) - %{DATA:[nginx][access][user_name]} \[%{HTTPDATE:[nginx][access][time]}\] \"%{WORD:[nginx][access][method]} %{DATA:[nginx][access][url]} HTTP/%{NUMBER:[nginx][access][http_version]}\" %{NUMBER:[nginx][access][response_code]} %{NUMBER:[nginx][access][body_sent][bytes]} \"%{DATA:[nginx][access][referrer]}\" \"%{DATA:[nginx][access][agent]}\""] }
        add_field => {
                "Device" => "Charles Desktop"
        }
        remove_field => "message"
        remove_field => "beat.version"
        remove_field => "beat.name"
    }
}
    output {    
             elasticsearch {
                    hosts => ["192.168.110.130:9200"]
                    index => "nginx-log-%{+YYYY.MM.dd}"
                }
    }

如上的配置中輸出到elasticsearch中,這裏沒有設置密碼,所以不需要用戶和密碼,還有就是這裏使用的默認模板,如果想要修改的話可以使用,可以添加如下配置:

user => "elastic"  #用戶
password => "${ES_PWD}" #通過keystore存儲的密碼
manage_template => false #關閉默認的模板
template_name => "elastic-slowquery" #指定自定義的模板

執行命令啟動logstash

cd logstash-7.7.0/ && ./bin/logstash -f conf.d/nginx.conf

執行的結果(查看elasticsearch集群):

 

 

 從上面的兩個圖看,一個創建了我們在nginx.conf中指定的一個索引,然後索引的內容都是解析出來的一些字段內容。

3.12、案例三、elasticsearch慢日誌解析

這是實例我們採用filebeat+logstash+elasticsearch,還有權限驗證進行試驗:

這裏主要是對elasticsearch的慢日誌查詢做解析,雖然我在一篇文章搞懂filebeat(ELK)中篇文章中直接通過filebeat的elasticsearch(beat版本)的模塊對其做過解析,但是解析的還是不夠特別完善,這裏引入logstash對其解析,過濾。

首先配置filebeat文件(這裏只配置了一個輸入和一個輸出,沒有做多餘的處理,只是用來收集日誌):

#=========================== Filebeat inputs =============================
filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /var/logs/es_aaa_index_search_slowlog.log
    - /var/logs/es_bbb_index_search_slowlog.log
    #- c:\programdata\elasticsearch\logs\*

#================================ Outputs =====================================

# Configure what output to use when sending the data collected by the beat.

#----------------------------- Logstash output --------------------------------
output.logstash:
  # The Logstash hosts
  hosts: ["192.168.110.130:5044","192.168.110.131:5044","192.168.110.132:5044"]
  loadbalance: true   #這裏採用負載均衡機制,

  # Optional SSL. By default is off.
  # List of root certificates for HTTPS server verifications
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]

  # Certificate for SSL client authentication
  #ssl.certificate: "/etc/pki/client/cert.pem"

  # Client Certificate Key
  #ssl.key: "/etc/pki/client/cert.key"

然後啟動filebeat:

cd filebeat-7.7.0-linux-x86_64 && ./filebeat -e

然後配置logstash的配置文件:

cd conf.d/ 
vim  es.conf
############es.conf配置如下##############
input {
        beats{
                port => 5044
        }
    }
   
filter {
grok {
        match => {"message" => "\[%{TIMESTAMP_ISO8601:query_time},%{NUMBER:number1}\]\s*\[%{DATA:log_type}\]\s*\[%{DATA:index_query_type}\]\s*\[%{DATA:es_node}\]\s*\[%{DATA:index_name}\]\s*\[%{NUMBER:share_id}\]\s*took\[%{DATA:times_s}\],\s*took_millis\[%{NUMBER:query_times_ms}\],\s*types\[%{DATA:types}\],\s*stats\[%{DATA:status}\],\s*search_type\[%{DATA:search_type}\],\s*total_shards\[%{NUMBER:total_shards}\],\s*source\[%{DATA:json_query}\],\s*extra_source"}
        remove_field => ["message","@version","status","times_s","@timestamp","number1"]
    }
  mutate{
        convert => {
        "query_times_ms" => "integer"
}
}
}
    output {    
             elasticsearch {
                    hosts => ["192.168.110.130:9200","192.168.110.131:9200","192.168.110.132:9200"]
                    index => "elastic-slowquery-222"
                    user => "elastic"
                    password => "${ES_PWD}"
                    manage_template => false
                    template_name => "elastic-slowquery"
                }
    }

如上配置使用了用戶的權限驗證,以及elasticsearch的自定義模板

啟動logstash

cd logstash-7.7.0/ && ./bin/logstash -f conf.d/es.conf

登錄es查看結果:

 

logstash就介紹到這裏了,如果有疑問多看官網比較好

 

參考:

logstash官網:https://www.elastic.co/guide/en/logstash/current/index.html

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

JDK動態代理

在《springAOP之代理模式》中說了代理模式,包含靜態代理和動態代理,在動態代理模式中又分為JDK動態代理和CGlib動態代理,今天重點來看JDK動態代理。

一、概述

說到JDK動態代理就必須想到JDK動態代理要求有一個統一的接口,那為什麼要有接口,下面會說到,下面看我的接口類,

package cn.com.jdk.proxy;

public interface Subject {

    void sayHello(String a);
}

接口類很簡單就是一個簡單的方法定義。下面看實際的接口的實現類SubjectImpl,

package cn.com.jdk.proxy;

public class SubjectImpl implements Subject {

    @Override
    public void sayHello(String a) {
        // TODO Auto-generated method stub

        System.out.println("hello:"+a);
    }

}

實現類簡單的事項了Subject接口,進行了打印操作。下面看代理類

package cn.com.jdk.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKProxy implements InvocationHandler {
    private SubjectImpl si;
    //此屬性不用管
    private String a;
/**
 * proxy  JDK動態生成的代理類的實例
 * method 目標方法的Method對象     Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
 * args   目標方法的參數                       new Object[] { paramString }
 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用si(被代理類)目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }
    public JDKProxy(SubjectImpl si,String a) {
        this.si=si;
        this.a=a;
    }

}

上面是代理類的實現,在代理類中含義被代理類的一個引用,且提供了響應的構造方法。下面具體的使用,

package cn.com.jdk.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        SubjectImpl si=new SubjectImpl();
        
        Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));
        subject.sayHello("tom");

    }

}

上面是使用的代碼,通過Proxy類的newProxyInstance方法獲得一個Subject的實例,調用sayHello方法,下面看執行結果

before
hello:tom
after

可以看到執行了sayHello方法,且打印了before和after,這不正是代理類中invoke方法的執行嗎,看下面

很神奇的一件事,我們不光調用了sayHello方法,實現了打印,而且在加入了自己的打印方法,這不正是AOP的增強功能嗎。這一切是怎麼發生的那,下面細細說來。

二、詳述

上面,我們又複習了JDK動態代理的內容,以及演示了如何使用JDK動態代理,下面我們要看這是怎麼實現的,先從測試的下面這段代碼說起,也是最重要的代碼,JDK動態代理的精華都在這句代碼里,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

這句代碼是調用了Proxy類的newProxyInstance方法,此方法的入參如下,

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

 一共三個參數,一個是ClassLoader,這裏傳入的是被代理對象的類加載器;一個是Class,這裏傳入的是被代理對象所實現的接口;一個是InvocationHandler,這裏傳入的是代理類,代理類實現了InvocationHandler接口。

 1、newProxyInstance方法

下面看newProxyInstance方法的定義,

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
          //1、使用代理類的類加載器和其所實現的接口,動態生成代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //2、返回JDK生成的代理類的構造方法,該構造方法的參數為
            //  InvocationHandler
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
//3、返回該構造方法的一個實例,也就是使用InvocationHandler為參數的構造方法利用反射的機制返回一個實例。
return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }

該方法中有三步比較重要,上面的註釋已經標出。

1.1、getProxyClass0(loader, intfs)方法

該方法便是上面的第一步,這一步的作用是JDK返回一個代理類的實例,方法上的註釋如下,

/*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

註釋直譯過來是查找或者生成指定的代理類,這裡有兩層意思,一個是查找,第二個是生成,由此可以想到這個方法中應該有緩存,下面看方法的具體定義,

/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

這個方法很簡單,判斷了接口的數量,大於65535便拋異常,接口的數量大於65535的可能性不大。最後調用了proxyClassCache的get方法,首先看proxyClassCache,從字面上理解是代理類的緩存,看其定義,

/**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

是一個WeakCache對象實例,看下該構造方法,

/**
     * Construct an instance of {@code WeakCache}
     *
     * @param subKeyFactory a function mapping a pair of
     *                      {@code (key, parameter) -> sub-key}
     * @param valueFactory  a function mapping a pair of
     *                      {@code (key, parameter) -> value}
     * @throws NullPointerException if {@code subKeyFactory} or
     *                              {@code valueFactory} is null.
     */
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

看了該類的構造方法后,回到proxyClassCache.get(loader, interfaces)方法的調用,我們已經知道proxyClassCache是WeakCache的一個實例,那麼get方法如下,

 /**
     * Look-up the value through the cache. This always evaluates the
     * {@code subKeyFactory} function and optionally evaluates
     * {@code valueFactory} function if there is no entry in the cache for given
     * pair of (key, subKey) or the entry has already been cleared.
     *
     * @param key       possibly null key
     * @param parameter parameter used together with key to create sub-key and
     *                  value (should not be null)
     * @return the cached value (never null)
     * @throws NullPointerException if {@code parameter} passed in or
     *                              {@code sub-key} calculated by
     *                              {@code subKeyFactory} or {@code value}
     *                              calculated by {@code valueFactory} is null.
     */
    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

 上面是WeakCache的get方法,這個方法暫時不作說明,後面會詳細介紹WeakCache類,請參見《JDK動態代理之WeakCache 》。這裏只需記住該get方法會返回一個代理類的實例即可。那麼此代理類是如何定義的那?

1.1.1、$Proxy0.class代理類

這個代理類是JDK動態生成的,其命名規則為以“$”開頭+Proxy+“從0開始的序列”。上面在測試的時候,我們加入了下面這行代碼,

//進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

註釋中寫到可以生成代理類的字節碼文件,下面是使用反編譯工具過來的java代碼,

package com.sun.proxy;

import cn.com.jdk.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
 //參數為InvocationHandler的構造方法
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
   //調用父類Proxy的構造方法,在父類的構造方法中會初始化h屬性
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }
//實現的Subject的sayHello方法
  public final void sayHello(String paramString)
    throws 
  {
    try
    {
      //調用h的invoke方法,這裏的h指的是實現了InvocationHandler的類
      //調用其中的invoke方法,在本例中是調用JDKProxy類中的invoke方
      //
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
}

上面是反編譯過來的JDK生成的代理類的代碼,包含了一個使用InvocationHandler作為參數的構造方法,以及實現了Subject接口的sayHello方法。上面註釋中寫到該構造方法調用了其父類Proxy的構造方法,下面看其父類Proxy的構造方法,

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

把InvocationHandler的值賦給了h,h的定義如下,

protected InvocationHandler h;

那麼在生成的代理類中自然會繼承該屬性,所以在代理類中的sayHello中使用下面的方法調用,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面的this.h便是其父類的h屬性。在上面的this.h.invoke中的m3是怎麼來的那,看下面,

 static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }

在該類的靜態代碼塊中給出了4個屬性。

1.2、getConstructor(constructorParams)方法

在上面的getProxyClass0方法中我們知道該方法會返回一個JDK生成代理類的Class對象,此類的定義便是上面的$Proxy0.class類。其定義在上面已經分析過。getConstructor方法要返回一個以constructorParams為參數的構造方法,

@CallerSensitive
    public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.PUBLIC);
    }

調用了getConstuctor0方法返回一個public的構造方法,

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }

上面的方法會返回一個public的構造方法。

回到最初的調用,我們看getConstructor方法的參數是constructorParams,此屬性定義如下,

/** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

是一個Class數組,其類型為InvocationHandler。這樣便可以知道是通過代理類的Class對象返回其構造方法cons。有了構造方法下面便是通過構造方法生成實例。

1.3、cons.newInstance(new Object[]{h})方法

此方法便是通過構造方法返回一個代理類的實例。

 

上面分析了Proxy的newProxyInstance方法,此方法最終會返回一個代理類的實例,會經過下面幾個步驟,

從上面的步驟,我們知道在獲得代理類的構造方法時,是獲得其參數為InvocationHandler的構造方法,所以肯定要實現InvocationHandler接口,在本例中便是JDKProxy類,這個類實現了這個接口。值開篇我們講到JDK動態代理必須要有統一的接口,從上面的步驟中我們知道在生成代理類的Class對象時使用了兩個參數,一個ClassLoader,另一個是接口,這裏就是為什麼要有統一的接口,因為在生成代理類的Class對象中需要接口,所以被代理類必須要有一個接口。

2、方法調用

這裏的方法調用,便是對應使用方法中的下面這行代碼,

subject.sayHello("tom");

在上面的分析中獲得了一個代理類的實例,即下面這行代碼,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

通過使用被代理類的類加載器、被代理類所實現的接口、實現了InvocationHandler接口的類的實例三個參數,返回了一個代理類的實例。上面已經詳細分析過。此代理類的實例繼承了Proxy,實現了Subject接口。其sayHello方法如下,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面已經分析過,this.h是InvocationHandler的實例,這裏便是new JDKProxy(si,”111″),m3是m3 = Class.forName(“cn.com.jdk.proxy.Subject”).getMethod(“sayHello”, new Class[] { Class.forName(“java.lang.String”) });下面看JDKProxy中的invoke方法,

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }

此方法的三個參數分別為代理類的實例、Method對象(sayHello),調用sayHello時的參數,所以要調用被代理類的sayHello方法,需要這樣寫:method.invoke(si,args),即調用被代理類(SubjectImpl)的sayHello方法,參數為args(tom)。下面是一個簡單的方法調用過程,

三、總結

本文分析了JDK動態代理的簡單使用方法及背後的原理,有不當之處歡迎指正,感謝!

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

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧