kubernetes高級之動態准入控制

系列目錄

動態准入控制器文檔介紹了如何使用標準的,插件式的准入控制器.但是,但是由於以下原因,插件式的准入控制器在一些場景下並不靈活:

  • 它們需要編譯到kube-apiserver里

  • 它們僅在apiserver啟動的時候可以配置

准入鈎子(Admission Webhooks 從1.9版本開始)解決了這些問題,它允許准入控制器獨立於核心代碼編譯並且可以在運行時配置.

什麼是准入鈎子

准入鈎子是一種http回調,它接收准入請求然後做一些處理.你可以定義兩種類型的准入鈎子:驗證鈎子和變換鈎子.對於驗證鈎子,你可以拒絕請求以使自定義准入策略生效.對於變換鈎子,你可以改變請求來使自定義的默認配置生效.

體驗准入鈎子

准入控制鈎子是集群管制面板不可缺少的一部分.你在編寫部署它們時必須要警惕.如果你想要編寫/布置生產級別的准入控制器,請閱讀以下用戶指南.下面我們將介紹如何快速體驗准入鈎子.

準備工作:

  • 確保你的kubernetes集群版本至少是1.9版本.

  • 確保變換鈎子(MutatingAdmissionWebhook) 和驗證鈎子(ValidatingAdmissionWebhook)已經啟用.這裡是推薦開啟的一組准入控制器.

編寫一個准入鈎子服務器(admission webhook server)

請參閱已經被kubernetes e2e測試驗證通過的准入服務器鈎子( admission webhook server)的實現.這個web鈎子處理apiserver發出的admissionReview請求,然後把結果封裝成一個admissionResponse返回給請求者.

admissionReview請求可能有多個版本( v1beta1 或者 未來的v1),web鈎子可以通過admissionReviewVersions字段來定義它們接受的版本.apiserver會嘗試使用列表中出現的,支持的第一個版本.如果列表中的版本沒有一個是被支持的,驗證將失敗.如果webhook配置已經持久化,對web鈎子的請求將會失敗並被失敗策略控制.

示例鈎子服務器(admission webhook server)把ClientAuth字段留空,默認為NoClientCert.這意味着鈎子服務器不驗證客戶端身份.如果你需要使用mutual TLS或者其它方法來驗證客戶端請求,請參考如何認證apiserver

部署准入控制服務

e2e測試的鈎子服務器通過部署api(https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#deployment-v1beta1-apps)被部署到kubernetes集群中.測試項目也為鈎子服務器創建了一個前端服務,代碼

你也可以把你的鈎子服務部署到集群外,你需要相應地更新web鈎子客戶端配置

運行時配置准入web鈎子

你可以通過ValidatingWebhookConfiguration和MutatingWebhookConfiguration動態地配置哪些資源被哪些web鈎子控制.

以下是一個validatingWebhookConfiguration配置的示例,變換鈎子的配置也類似

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: <name of this configuration object>
webhooks:
- name: <webhook name, e.g., pod-policy.example.io>
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: "Namespaced"
  clientConfig:
    service:
      namespace: <namespace of the front-end service>
      name: <name of the front-end service>
    caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
  admissionReviewVersions:
  - v1beta1
  timeoutSeconds: 1

scope字段指定了集群級別的資源(“Cluster”)或者名稱空間級別的資源(“Namespaced”)需要匹配這些規則.”*”表示沒有任何範圍限制.

注意,如果使用clientConfig.service,服務端證書必須對<svc_name>.<svc_namespace>.svc有效.

web鈎子請求默認超時時間為30秒,但是從1.14版本開始,你可以自由設置超時時間但是建議設置較小的時間.如果web鈎子請求超時,請求將被web鈎子的失敗策略處理.

當apiserver接收到一個匹配規則的請求,apiserver將會發送一個admissionReview請求到clientConfig配置的web鈎子里.

創建web鈎子配置以後,系統將會經過一段時間使新配置生效.

認證apiserver

如果你的准入web鈎子需要認證,你可以配置apiserver使用基本認證(basic auth), bearer token 或者證書認證.需要三個步驟來完成認證配置.

  • 當啟動apiserver時,通過--admission-control-config-file選項來指定準入控制配置文件的位置.

  • 在准入控制配置文件里,指定變換控制器(MutatingAdmissionWebhook)和驗證控制器(ValidatingAdmissionWebhook)從哪裡讀取證書.證書存儲在kubeConfig文件里(和kubectl使用的相同),字段名為kubeConfigFile.下面是准入控制配置文件示例

apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
  configuration:
    apiVersion: apiserver.config.k8s.io/v1alpha1
    kind: WebhookAdmission
    kubeConfigFile: <path-to-kubeconfig-file>
- name: MutatingAdmissionWebhook
  configuration:
    apiVersion: apiserver.config.k8s.io/v1alpha1
    kind: WebhookAdmission
    kubeConfigFile: <path-to-kubeconfig-file>

這裡是admissionConfiguration的schema定義

  • 在kubeConfig文件里,提供證書
apiVersion: v1
kind: Config
users:
# DNS name of webhook service, i.e., <service name>.<namespace>.svc, or the URL
# of the webhook server.
- name: 'webhook1.ns1.svc'
  user:
    client-certificate-data: <pem encoded certificate>
    client-key-data: <pem encoded key>
# The `name` supports using * to wildmatch prefixing segments.
- name: '*.webhook-company.org'
  user:
    password: <password>
    username: <name>
# '*' is the default match.
- name: '*'
  user:
    token: <token>

當然,你需要設置web鈎子服務器來處理這些認證.

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

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

高速輸出-我們戲說緩存

前言

緩存要解決的問題是速度的問題,使用緩存的目的是為了減少對物理資源的訪問,緩存大量的應用在軟硬件的方方面面,從 CPU 到硬盤,就應用了 一級緩存、二級緩存,少部分高速緩存和大量低速緩存相結合,以提高 CPU 的計算能力,本文講的主要是系統集成項目中的軟件級別的緩存。

緩存因果圖

緩存在現代系統中的位置可以說是舉足輕重,不是可有可無的問題了,而是怎麼樣用好的問題。怎麼樣講好緩存這個話題,我思考了很久;如果是生搬硬套,我估計我肯定是涼涼,但要是上來就一頓代碼操作,顯得字太多,各位看官老爺也是要噴死我的(你們噴我算我贏)。各位看官就當故事隨便看看就好了。

客戶端緩存

小明同學是一個大學生,每個月都會從爸爸那裡領取生活費,由於爸爸給的生活費比較充裕,他就將一部分存了起來,周而復始,固定頻率,這就是瀏覽器網頁緩存;有一天,他接到爸爸的一個電話,說每個月給現金,我總是從銀行拿也不方便,這樣,咱給存銀行卡裡頭,但是說好了,每個月1號的時候轉1000塊進去,3號之前可以領取,過時不侯。這就是 Cookie!小明說爸,這時間太緊迫了,我萬一太過於專心學習忘記取錢那你兒子可是要餓死的吖,到時候沒人給你養老送終事兒就大了。他爸爸經過深思熟慮后,決定放開取現時間的限制:行,你想什麼時候取都成,就是別把卡給弄掉了就行。這就是LocalStorage/IndexDB,瀏覽器本地存儲。如果銀行卡掉了(瀏覽器重裝,刪庫跑路),那還得找爸爸。

服務器緩存

我們本次討論的重點就是服務器緩存,對於小明的爸爸來說,每次給小明生活費之前都要到銀行(數據庫)去取錢出來,實在是太麻煩,不如提前把錢取出來準備好,每次取半年的錢出來放家裡保險箱里(緩存),小明申請生活費的時候,直接給他就好了,節約時間效率又高,唯一的問題就是可能沒那麼安全,有可能被小偷把錢給偷了(緩存更新),然後就是半年時間過後再重新取一筆出來(緩存過期策略)。

靜態對象緩存

靜態對象也是一種特殊的緩存,靜態對象作用於程序的整個生命周期中。需要注意的是,靜態對象不會被 GC 回收 ,但是,如果靜態對象被多次引用覆蓋,那麼之前的引用就有可能被 GC 回收。這就好像,小明在向爸爸領取生活費的時候,發現這次領到手的錢實在是太破舊了,都不好意思花出去,爸爸只好重新給了另一份。

會話級緩存

在 Web 站點中,Session 是私有制的,各個 Session 之間是不會共享內存對象的,我們可以利用這個特性(在Asp.NET 時代常用)來暫時保存一些數據,例如用戶購物車。還是以小明的需求為例子,小明下面還有一個妹妹,妹妹每個月都可以從爸爸那裡多領取200元生活費,看到妹妹的資源這麼好,小明憤憤不平,就像妹妹提議共享生活費,一家人一起用,多好吖!結果小明被爸爸揍了一頓,還收回了部分生活費。

進程級別緩存

基本上每個應用程序都具有本地緩存的能力,在 Asp.NET 中就有 MemoryCache ,也叫做進程級緩存(本地緩存),MemoryCache 和 分佈式緩存的作用基本一致,所不同的是,本地緩存在應用程序停止后就會被釋放掉,無法進行持久存儲。就好像,小明在上大學期間,每個月都是生活費的,但是到暑假的時候就沒有了(只能靠苦逼的暑期工掙點生活費啊)。

分佈式緩存

分佈式緩存是個好東西,目前市場上出現了非常多的 nosql 數據庫,都可用作緩存數據庫,有時候,這些緩存數據庫也提供持久化的能力。

小明家的生活條件,在經過爸爸的不懈努力之後,生活水平漸漸提高了;有一天,爸爸對小明說:明仔吖,咱們家現在生活水平提高了,但是爸爸也越來越忙了,這樣,如果我忙的時候,你問媽媽或者爺爺奶奶,也是可以拿到生活費的,咱們家這幾位長輩手裡都有錢了,這就是分佈式緩存。

但是分佈式緩存又分為主從模式和集群模式,上面說的是集群模式,爸爸媽媽爺爺奶奶都可以拿錢,但是主從模式就不同了,主從模式就是錢都在爸爸手裡,爸爸會把錢分給每個長輩一份,如果當時正好爸爸太忙了,沒來得及分給媽媽,而小明又恰好問媽媽要生活費的話;媽媽只能對他說:小命呀,不好意思,媽媽這裏也沒有,你看看再問問其它人(客戶端自己輪詢),在問了媽媽、爺爺后(引用指向),終於知道,錢在爸爸那裡,還得問爸爸要生活費。而且爸爸給其它人分錢的時候,還要佔用他工作的時間。

緩存雪崩

緩存雪崩就是在某一個時刻,大量的緩存同時失效,造成數據庫訪問壓力倍增。小明的爸爸最近壓力比較大,因為收入減少了,他爸爸做的一個工程因為種種原因,貨款只能分期付清,為了防止小明和妹妹同時申請生活費,造成資金周轉困難;爸爸規定妹妹 1 號領取生活費,而小明在 5 號才能領取,小明心裏的苦啊!

緩存穿透

緩存穿透就是客戶端總是嘗試訪問某個不存在的緩存,造成了每次都要取請求數據庫讀取數據。就好像小明吧,本來生活挺平靜的,這剛上大二,就交了個女朋友,每個月的 1000 元生活費有點捉襟見肘,然後他又不能讓爸爸知道,就在申請生活費的時候,每次都多要一點;這樣就搞得爸爸很被動,本來計劃得好好的,每個月都是 1000 塊,這沒次都不夠,老是要跑銀行取現金,終於在3個月後,爸爸發現了這個問題,想著兒子也大了,為了早日抱上孫子,就提高了小明的生活費,解決了每次都要跑銀行的問題。

緩存擊穿

緩存擊穿和緩存雪崩有點類似,其中不同的是;緩存雪崩是大量緩存 key 同時過期,而緩存擊穿是大量的請求指向同一個緩存key,在這個 key 過期的時候,大量的請求湧入數據庫中,造成了瞬間巨大的壓力。舉個栗子,因為小明交了女朋友的原因,他的生活費總是很快用完了;這種情況下,他也必須在 1 號的時候拿到生活費,不然就要吃土了,但是媽媽不允許他們一起取打擾爸爸,媽媽就指定了妹妹去問,在妹妹沒有回來之前,小明只能等着,這就是為了解決緩存擊穿而採用的策略:互斥鎖(mutex key)

運維級別緩存

除了在應用程序中可以接入緩存以外,部分運維工具也集成了緩存服務,比如 Nginx、IIS。

代理緩存

Nginx 就是反向代理緩存,通過配置 Nginx 的緩存功能,在客戶端請求到來到時候去加載緩存內容,用以提高響應能力,IIS 緩存又分為用戶緩存和內核緩存。IIS 的輸出緩存設置中,內核模式緩存不會對驗證等用戶信息進行檢查,就好像小明等爸爸因為太趕時間,把錢放門衛大媽那裡了,結果隨便來了個學生就把小明等生活費給領走了,但是加上用戶模式緩存后,就可以添加對身份的檢查(請求標頭),這樣大媽就會知道誰是小明而不會隨便把生活費交給別人了。

網頁靜態化

這種技術,在 Web1.0 時代非常的流行,我還記得那些個年頭的網站開發項目中的要求,大部分項目的需求分析文檔裏面就明晃晃的寫着:網頁靜態化!,而靜態化常見於各種企業型、論壇帖子,在發表這些信息后就將其生成靜態網頁,客戶端訪問的時候,直接重定向到該靜態網頁,基本無需訪問數據庫。

CDN 緩存

CDN 緩存類似於上面提到的分佈式緩存,但是實際上 CDN 緩存服務目前來說,主要說針對靜態資源的,比如圖片、視頻、文件等等;大家經常可以看到,很多站點都號稱提供了 CDN 加速服務,這些站點就像一個個代辦信用卡的銷售中介,實際上拿的都是銀行的資源。

更形象一些的說法,就是大家的錢都是中國人民銀行發行的,我們可以通過各個不同的銀行(CDN節點)查詢我們的銀行卡餘額(我怎麼可能有餘額),在以前沒有手機銀行的時候,大家就可以到附近的銀行去查詢,然後取款(CDN緩存),如果附近的銀行的櫃員機沒有現金,那麼可能就需要到總行去取了(回源),如果發生了太多回源的事件,就會造成 CDN 的擁堵,所以 CDN 服務商也不敢打包票自己的基礎服務沒有問題,反正我是沒見到哪家 CDN 服務商敢註明服務穩定性 100%,基本上都是 99.99%

結束語

緩存的理論知識,其實是非常宏大的,我這裏只是拋磚引玉,希望能給入門的朋友帶來一點幫助,如果你喜歡這篇文章,請給我點贊,讓更多同學可以看到。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

一文帶你了解爬蟲

六月分享主題:爬蟲
HTTP詳解
網頁結構簡介

前段時間我媽突然問我:兒子,爬蟲是什麼?我當時既驚訝又尷尬,驚訝的是為什麼我媽會對爬蟲好奇?尷尬的是我該怎麼給她解釋呢?

一、爬蟲介紹

1.爬蟲是什麼

網絡爬蟲(web crawler 簡稱爬蟲)就是按照一定規則從互聯網上抓取信息的程序,既然是程序那和正常用戶訪問頁面有何區別?爬蟲與用戶正常訪問信息的區別就在於:用戶是緩慢、少量的獲取信息,而爬蟲是大量的獲取信息。

這裏還需要注意的是:爬蟲並不是Python語言的專利,Java、Js、C、PHP、Shell、Ruby等等語言都可以實現,那為什麼Python爬蟲會這麼火?我覺得相比其他語言做爬蟲Python可能就是各種庫完善點、上手簡單大家都在用,社區自然活躍,而社區活躍促成Python爬蟲慢慢變成熟,成熟又促使更多用戶來使用,如此良性循環,所以Python爬蟲相比其他語言的爬蟲才更火。

下面是一段hello world級別的Python爬蟲,它等效於你在百度搜索關鍵字:python

2.爬蟲案例

既然爬蟲是大量抓取網頁,那是不是爬蟲都是不好的呢?答案當然不是,可以說我們的日常上網已經離不開爬蟲了,為什麼這麼說?下面我就為大家盤點幾個爬蟲日常應用:

  1. 搜索引擎:如Google、百度、雅虎、搜狗、必應等等很多搜索引擎其本質就是一個(可能多個)巨大爬蟲,這些搜索引擎工作原理是:頁面收錄->頁面分析->頁面排序->響應關鍵字查詢,也就是說它會先把互聯網上很多頁面保存到服務器,然後分析網頁內容建立關鍵字索引,最後用戶輸入關鍵字的時候去查詢內容,然後根據相關性排序(百度害人的競價排名毫無相關性可言),第一步的頁面收錄就是爬蟲,百度查看一個網站有多少網頁被收錄方法,百度輸入:site:你想查詢的網站,如:site:blog.csdn.net。
  2. 搶票軟件:很多人在吐槽12306卡,可你不知道12306幾乎每天都相當於淘寶雙11的流量,這誰受得了。為什麼每天都是如此高的流量?答案自然是爬蟲了,為什麼搶票軟件可以搶票?因為它在不斷的刷新和監控是否有餘票,大大小小這麼多搶票app,訪問量之高可想而知。之前很多公司都出過搶票插件,如:百度、360、金山、搜狗等等,後來都被鐵道部約談下線了,而現在又流行搶票app,為什麼搶票app可以,插件就不可以?可能是因為管理和可控性的原因。
  3. 惠惠購物助手:這是一款能進行多個網站比價並能知道最低價的一個網站,其工作原理也是通過大量爬蟲爬取商品價格然後存儲,這樣就可以製作一個價格走勢圖,幫助你了解商品最低價。

二、爬蟲的價值

從上面舉的幾個例子來看,爬蟲對整個互聯網的價值真的無法估量,那對於小我而言,爬蟲能給我們帶來哪些價值?

1.隱形的翅膀

如果你問我學完Python基礎之後該學習什麼技能?我會毫不猶疑的說爬蟲,為什麼是爬蟲?

  1. 爬蟲相對其他技能簡單易學,且效果立即可見,會有一定的成就感
  2. 爬蟲可以說是其他技能的基石,因為他是數據的來源,現在這個時代誰有數據誰才能稱王,所以會爬蟲絕對會讓你如虎添翼
  3. 在國內,很多企業巴不得你啥都會,所以在應聘時,爬蟲會是一個不錯的加分項

2.看不見的商戰

職場對話:

老闆:小明給你個重要任務。
小明:就算996我也在所不辭(第一次接到老闆的直接需求)!
老闆:你能不能搞到競爭對手的商品價格?
小明:沒問題(牛逼先吹出去),小事!
老闆:這可不是小事,只要你能持續獲取競品價格,我們就可以標價低於他們一點點,持續如此大家就知道我們的價格肯定是比他們低,這樣大家就直接來我們這裏購買商品了,到時候慶功宴上你就是最大功臣(先畫個餅)。
小明:老闆牛逼,老闆英明!

3.會爬蟲就可以創業

工作之後很多同學會利用上班空餘時間,來做自己的東西或者項目,別看開始是一個小打小鬧的東西,慢慢豐富將來也許會成為一款成熟的產品。

而爬蟲可以讓你很輕鬆的實現自己的產品,做的好的話就可以創業。這裏豬哥給大家列幾個簡單的創業項目,當然作為一個思維引導。

如果你想做一款好的產品,你需要從用戶需求出發去思考,做解決目前存在的問題的產品或者目前還沒有的服務,也許你的產品就是下一個頭條。

三、盜亦有道

爬蟲既然如此強大而優秀,那是不是會了爬蟲就可以為所欲為?

延伸話題:其實我內心一直有一個疑問:為什麼互聯網公司都偏愛用動植物來命名或作為logo?如:螞蟻金服、天貓、菜鳥、金東狗、騰訊企鵝,百度的熊掌、搜狗、途牛、美團的袋鼠。。。真的是太多了,難道僅僅是因為好記?我認為好記是一個原因,其根本原因是受到編程行業的影響,你想想編程行業有多少動植物:Java(咖啡)、Python(蟒蛇)、Go(囊地鼠)、PHP(大象)、Linux(企鵝)、Perl(駱駝)、Mysql(海豚)等等,具體為什麼編程行業喜歡用動植物這豬哥就不清楚,還請明白的同學留言告知大家!

講上面的延伸話題想表達的是,大自然世間萬物,相生相剋,衡而不禍!而爬蟲也是如此,下面就為大家介紹一下約束爬蟲的幾個點。

1.robots協議

做過網站的同學也許知道,我們在建站的時候需要在網站根目錄下放一個文件:robots.txt,這個文件是干什麼用的呢?

Robots協議,也稱為爬蟲協議、機器人協議等,其全稱為“網絡爬蟲排除標準(Robots Exclusion Protocol)”。網站通過Robots協議告訴搜索引擎哪些頁面可以抓取,哪些頁面不能抓取。

每個網站的根目錄下都必須放置robots.txt文件,否則搜索引擎將不收錄網站任何網頁。

下面我們以百度為例子,來看看百度的robots.txt文件:

我們在百度robots協議的最下面,有這樣一條:

User-agent: *
Disallow: /

這表示除上面那些定義過的爬蟲以外其他爬蟲都不允許爬取百度任何東西!

2.法律

我們都知道在發起請求時User-agent是可以自定義的,也就是說我們可以繞過robots協議和以User-agent來定義的反爬蟲技術,所以robots協議可能更像一個君子協議,那咱們國家有沒有法律明文規定呢?我們來了解一下非法侵入計算機信息系統罪

第二百八十五條 非法侵入計算機信息系統罪:違反國家規定,侵入前款規定以外的計算機信息系統或者採用其他技術手段,獲取該計算機信息系統中存儲、處理或者傳輸的數據,或者對該計算機信息系統實施非法控制,情節嚴重的,處三年以下有期徒刑或者拘役,並處或者單處罰金;情節特別嚴重的,處三年以上七年以下有期徒刑,並處罰金。

我們可以看到重點信息:入侵計算機獲取數據是違法的,也就是說爬蟲技術本身是無罪的,因為它是獲取的公開信息,並未非法入侵計算機。但是如果你用爬取到的數據去從事商業化操作,那也許就構成了違法犯罪行為!

給大家介紹下因爬蟲而被判刑的案件:

  1. 頭條爬蟲官司:https://dwz.cn/qBBHc2fq
  2. 酷米客公司官司:https://dwz.cn/9IpAOHGB
  3. 360和百度爬蟲官司:https://dwz.cn/RGRa3HJc

我發現這幾起案件的通性有兩點:1、公司性質。2、競爭對手。3、讓人家找到線索。

最後提醒大家:技術人,要守住自己的底線,違反國家法律法規的事情咱們堅決不能做!

3.反爬蟲工程師

本來想採訪一位攜程反爬蟲工程師,但是他說由於工作保密原因不便接受採訪,所以只好尊重他的選擇!

四、爬蟲現狀

之前豬哥說過互聯網中有超過50%的流量來自爬蟲,那我們就來簡單聊聊目前爬蟲的現狀吧!

1.技術

反爬蟲幾乎和爬蟲同時誕生,它們是相愛相殺的連個技術,如果沒有爬蟲就沒有反扒,而反扒技術又反過來又可以促進爬蟲技術的發展。

  1. 交互問題:各種變態的驗證碼充斥,尤其是12306,分分鐘都想爆粗口,以後會越來越變態。。。
  2. js加密:最近很流行的一種反扒技術,會爬蟲還得先學js,然後反爬蟲工程師在js裏面各種投毒,殺人誅心。。。
  3. IP限制:限制單個ip某段時間內訪問次數

豬哥只介紹了一個反扒技術,當然都有現成的技術解決,但是作為爬蟲員最重的並不是會使用工具或框架去應對反扒,而是能通過自己的思考和摸索破解反扒,因為反扒技術更新快且多樣化。

2.就業

就業的話主要從招聘、薪資、就業形勢三個方面分析,我從網上查閱了一些資料,然後整理以圖片形式提供給大家參考。

數據來源:職友集:https://dwz.cn/6PeU46QY

3.前景

現在很多人並不看好爬蟲的前景,只搞爬蟲的話技術只停留在當前水平,不再學習新知識,不再進步的話,那麼是沒有前途的,總有一天會被時代淘汰,其實其他職位也只如此。

每個職業都是有一個橫向和縱向的發展,也就是所謂的廣度和深度的意思。第一、如果專研得夠深,你的爬蟲功能很強大,性能很高,擴展性很好等等,那麼還是很有前途的。第二、爬蟲作為數據的來源,後面還有很多方向可以發展,比如可以往大數據分析、數據展示、機器學習等方面發展,前途不可限量,現在作為大數據時代,你佔據在數據的的入口,還怕找不到發展方向?所以爬蟲也許只是一個起點一個跳板,是你走向人生巔峰的一個基石,總有一天你會迎娶白富美!

五、總結

本期為大家講解什麼是爬蟲、爬蟲的價值、爬蟲的合法性以及爬蟲的現狀,下期豬哥將採訪一位牛逼的爬蟲工程師,讓他為大家講講實際工作中的一些知識以及作為一名爬蟲工程師該掌握和學習哪些技能,讓大家能更近距離接觸爬蟲,如果你有想對他提問的問題,可以在下方留言哦,我會抽取一些問題讓他為大家解答,問題不限

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

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

Spring Boot從入門到實戰:整合通用Mapper簡化單表操作

數據庫訪問是web應用必不可少的部分。現今最常用的數據庫ORM框架有Hibernate與Mybatis,Hibernate貌似在傳統IT企業用的較多,而Mybatis則在互聯網企業應用較多。通用Mapper(https://github.com/abel533/Mapper) 是一個基於Mybatis,將單表的增刪改查通過通用方法實現,來減少SQL編寫的開源框架,且也有對應開源的mapper-spring-boot-starter提供。我們在此基礎上加了一些定製化的內容,以便達到更大程度的復用。

框架源碼地址:https://github.com/ronwxy/base-spring-boot (持續更新完善中,歡迎follow,star)
Demo源碼地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-tkmapper

在開源mapper-spring-boot-starter的基礎上,增加了如下內容:

  1. 針對MySQL數據庫與PostgreSQL數據庫添加了一些Java類型與數據庫類型的轉換處理類,如將List、Map類型與MySQL數據庫的json類型進行轉換處理
  2. 對Domain、Mapper、Service、Controller各層進行了封裝,將基本的增刪改查功能在各層通用化
  3. 提供了基於druid連接池的自動配置
  4. 其它一些調整,如默認映射複雜類型屬性(主要是List、Map類型,其它自定義類型需要自定義轉換處理類),將枚舉作為簡單類型處理
  5. 提供了一個parent項目,將一些常用的框架進行集成,實際項目可繼承parent簡化依賴配置(持續更新完善)

該框架可用於實際基於springboot的項目,只需簡單配置數據源,即可引入druid連接池及通用mapper的功能,以及各層基本的增刪改查方法。

如何使用?
下文給出使用步驟,可參考示例:https://github.com/ronwxy/springboot-demos/tree/master/springboot-tkmapper

1. 框架Maven部署安裝

下載框架源碼后,在項目根路徑下執行mvn clean install可安裝到本地maven庫。如果需要共享,且搭了Nexus私服,則在根路徑pom.xml文件中添加distributionManagement配置,指定Nexus倉庫分發地址,使用mvn clean deploy安裝到遠程maven倉庫,如

<distributionManagement>
     <repository>
         <id>nexus-releases</id>
         <url>
             http://ip:port/repository/maven-releases/
         </url>
     </repository>
     <snapshotRepository>
         <id>nexus-snapshots</id>
         <url>
             http://ip:port/repository/maven-snapshots/
         </url>
     </snapshotRepository>
 </distributionManagement>

 

上述指定的repository需要在maven的全部配置文件settings.xml中有對應賬號配置(id需要一一對應),如 

 <servers>
   <server>
     <id>nexus-snapshots</id>
     <username>admin</username>
     <password>xxx</password>
   </server>
<server>
     <id>nexus-releases</id>
     <username>admin</username>
     <password>xxx</password>
   </server>
 </servers>

 

2. pom.xml配置

項目中引入該數據庫框架有三種方式:

  1. 直接引入 cn.jboost.springboot:tkmapper-spring-boot-starter(沒有連接池)
  2. 直接引入 cn.jboost.springboot:druid-spring-boot-starter(druid連接池支持)
  3. 項目繼承 cn.jboost.springboot:spring-boot-parent(使用的是druid連接池)

三種方式的pom.xml配置如下

#第一種方式
<dependency>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>tkmapper-spring-boot-starter</artifactId>
   <version>1.2-SNAPSHOT</version>
</dependency>

#第二種方式
<dependency>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.2-SNAPSHOT</version>
</dependency>

#第三種方式
<parent>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>spring-boot-parent</artifactId>
   <version>1.2-SNAPSHOT</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

 

根據情況引入mysql或postgresql的驅動依賴(其它數據庫暫未做類型轉換支持,未作測試)

 

3. 配置數據源

如果使用druid連接池,則在application.yml配置文件中,加入如下數據源配置(推薦)

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
      username: root
      password:
      # 自定義配置
      initialSize: 2  # 初始化大小
      minIdle: 1   # 最小連接
      maxActive: 5 # 最大連接
      druidServletSettings:
        allow: 127.0.0.1
        deny:
        loginUsername: admin
        loginPassword: Passw0rd
        resetEnable: true
      druidFilterSettings:
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      maxWait: 60000   # 配置獲取連接等待超時的時間
      timeBetweenEvictionRunsMillis: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
      minEvictableIdleTimeMillis: 300000 # 配置一個連接在池中最小生存的時間,單位是毫秒
      validationQuery: SELECT 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true # 打開PSCache,並且指定每個連接上PSCache的大小
      maxPoolPreparedStatementPerConnectionSize: 20
      filters: stat #,wall(添加wall代碼里不能直接拼接sql,druid有sql注入校驗) # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
      useGlobalDataSourceStat: true # 合併多個DruidDataSource的監控數據

 

如果不使用連接池,則配置相對簡單,如下 

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
    username: root
    password:
    driver-class-name: com.mysql.jdbc.Driver

 

4. 定義相應domain,mapper,service,controller各層對象 

以demo為例(demo數據庫腳本見resources/schema.sql),domain定義一個User類,

@Table(name = "user")
@Getter
@Setter
@ToString
public class User extends AutoIncrementKeyBaseDomain<Integer> {
    private String name;
    @ColumnType(jdbcType = JdbcType.CHAR)
    private Gender gender;
    private List<String> favor;
    private Map<String, String> address;

    public enum Gender{
        M,
        F
    }
}

 

需要添加@Table註解指定數據庫表名,可通過繼承AutoIncrementKeyBaseDomain來實現自增主鍵,或UUIDKeyBaseDomain來實現UUID主鍵,如果自定義其它類型主鍵,則繼承BaseDomain。 

該框架Service層通用方法實現BaseService只支持單列主鍵,不支持組合主鍵(也不建議使用組合主鍵)

框架默認對List、Map等複雜類型屬性會映射到mysql的json類型或postgresql的jsonb類型,如果某個屬性不需要映射,可添加@Transient註解;枚舉類型需添加@ColumnType指定jdbcType。

dao層定義UserMapper

@Repository
public interface UserMapper extends BaseMapper<User> {
}

 

BaseMapper默認實現了單表的增刪改查及批量插入等功能,如需定義複雜查詢,可在該接口中定義,然後通過mapper xml文件編寫實現。 

service層定義 UserService,繼承了BaseService的通用功能(具體可查看源碼),同樣可在該類中自定義方法

@Service
public class UserService extends BaseService<Integer, User> {

    @Transactional
    public void createWithTransaction(User user){
        create(user);
        //用於測試事務
        throw new RuntimeException("拋出異常,讓前面的數據庫操作回滾");
    }
}

 

controller層定義 UserController,繼承了BaseController的通用接口(具體可查看源碼) 

@RestController
@RequestMapping("/user")
public class UserController extends BaseController<Integer, User> {
}

 

如上,只需要定義各層對應的接口或類,繼承基礎接口或類,便完成了用戶基本的增刪改查功能,不需要寫一行具體的實現代碼。 

5. 測試、運行

  1. 示例中提供了兩個新建用戶的單元測試,參考SpringbootTkmapperApplicationTests

  2. 運行,在主類上直接運行,然後瀏覽器里打開 http://localhost:8080/user 則可列出單元測試中創建的用戶(其它接口參考BaseController實現)

6. 總結

本文介紹框架基於tk.mybatis:mapper-spring-boot-starter做了一些自定義擴展,以更大程度地實現復用。可用於實際項目開發,使用過程中如果遇到問題,可關注公眾號留言反饋。

我的個人博客地址:http://blog.jboost.cn
我的頭條空間: https://www.toutiao.com/c/user/5833678517/#mid=1636101215791112
我的github地址:https://github.com/ronwxy
我的微信公眾號:jboost-ksxy

————————————————————————————————————————

歡迎關注我的微信公眾號,及時獲取最新分享

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

Trie|如何用字典樹實現搜索引擎的關鍵詞提示功能

Trie字典樹

Trie字典樹又稱前綴樹,顧名思義,是查詢前綴匹配的一種樹形數據結構

可以分為插入(創建) 和 查詢兩部分。參考地址極客時間

下圖為插入字符串的過程:

創建完成后,每個字符串最後一個字母標記為終結點(圖中显示為紅色)

下圖為查詢字符串:“her”的過程:綠色箭頭表示查詢路徑
我們將要查找的字符串分割成單個的字符 h,e,r,一個一個查詢

下圖為查詢字符串:“he”的過程:綠色箭頭表示查詢路徑
因為‘e’不是終結點,所以不能完全匹配上。

Trie字典樹的實現

1.首先是字典樹 數據結構定義的代碼實現

樹形結構,類比於二叉樹的存儲嘛,每個結點兩條分支(二叉樹);
而字典樹,每個節點可以最多有 26個分支(存儲英文字母)。

1-1二維數組存儲字母

int trie[MAX_NODE][26];//MAX_NODE表示結點數量,每個結點有26個字母結點
int k;

MAX_NODE表示結點數量,每個結點有26個字母結點
Trie[i][j]的值是0,表示trie樹中i號節點,並沒有一條連出去的邊滿足邊上的字符標識是字符集中第j個字符(從0開始);
trie[i][j]的值是正整數x表示trie樹中i號節點,有一條連出去的邊滿足邊上的字符標識是字符集中第j個字符,並且
這條邊的終點是x號節點。

1-2鏈表
我這裏用C++中的vector實現,

vector< pair<char, int> > trie[MAX_NODE];
int k;

也可以寫一個真正的鏈表,包含二元組字段<char,int>型的對應關係

1-3hash,

map<char, int> trie[MAX_NODE];

每次我們想找i號節點有沒有標識
是某個字符ch的邊時,只要看trie[i][ch]的值即可
但是實際上map時空複雜度的常數都比較大

2.插入 和 查詢 兩個函數的代碼實現

插入 查詢 實際上是類似的,就是從樹的根開始往下遍歷,

2-1插入:從樹的根開始往下遍歷,到達一個結點,沒有這個字母就插入到這個結點下,作為這個結點的子節點

基於二維數組結構的插入功能實現

代碼的第6~8行,一開始trie[][]被初始化為0,保證每個節點被創建出來時,都沒有子節點。K初
始化為1表示一開始只有1個節點,也就是0號節點根節點。Color是用來標記一個節點是不是終結
點。Color[i]=1標識i號節點是終結點。
第9~21行是插入函數insert(w),w是字符指針,實際上可以看作是一個字符串。
第11行是p從0號節點開始。
第12~19行是依次插入w的每一個字符。
第13行是計算w[i]是字符集第幾個字符,這裏我們假設字符集只包含26個小寫字母。
第14~17行是如果p沒有連出標識是w[i]的邊,那麼就創建一個。這裏新創建的節點一定就是k號節
點。所謂創建新節點實際上也沒什麼可創建的,新節點就是個編號。所以我們直接令trie[i][c]=k
即可,然後將k累加1,整個創建過程就完成了。
第18行是沿着標記着w[i]的邊移動到下一個節點。
最後第20行,是將最後到達的節點p標記為終結點。

2-2查詢:從樹的根開始往下遍歷,查看是否匹配上當前正在查的單詞
基於二維數組結構的查詢功能實現

第24行是從p=0也就是根節點開始。
第25~29行是枚舉s的每一個字符。
第26行是計算當前字符s[i]在字符集的序號。
第27行是判斷p節點有沒有連出標識s[i]字符的邊,如果沒有,說明現在無路可走,直接返回0;如
果有的話,
第28行就是移動到下一個節點。如果整個循環結束還沒有return 0,那就說明成功沿着s的每一個
字符到達了p節點。這時只要判斷p節點是不是終結點即可,也就是第30行的代

3.完整代碼C++版

public class Trie {
  private TrieNode root = new TrieNode('/'); // 存儲無意義字符

  // 往 Trie 樹中插入一個字符串
  public void insert(char[] text) {
    TrieNode p = root;
    for (int i = 0; i < text.length; ++i) {
      int index = text[i] - 'a';
      if (p.children[index] == null) {
        TrieNode newNode = new TrieNode(text[i]);
        p.children[index] = newNode;
      }
      p = p.children[index];
    }
    p.isEndingChar = true;
  }

  // 在 Trie 樹中查找一個字符串
  public boolean find(char[] pattern) {
    TrieNode p = root;
    for (int i = 0; i < pattern.length; ++i) {
      int index = pattern[i] - 'a';
      if (p.children[index] == null) {
        return false; // 不存在 pattern
      }
      p = p.children[index];
    }
    if (p.isEndingChar == false) return false; // 不能完全匹配,只是前綴
    else return true; // 找到 pattern
  }

  public class TrieNode {
    public char data;
    public TrieNode[] children = new TrieNode[26];
    public boolean isEndingChar = false;
    public TrieNode(char data) {
      this.data = data;
    }
  }
}

Trie字典樹的時間複雜度 與 缺點

插入的時間複雜度:O(N),N為所有待插入字符串的長度之和
查詢的時間複雜度:O(K),K為待查詢字符串的長度

占內存:如果用二維數組實現,每個節點就會額外需要 26*8=208 個字節
優化思路:將每個節點中的數組換成其他數據結構,比如有序數組(可以二分查找)、跳錶、散列表、紅黑樹等。

Trie變體,縮點優化:對只有一個子節點的節點,而且此節點不是一個串的結束節點,可以將此節點與子節點合併

Trie字典樹的實際應用

1.搜索引擎輸入框關鍵詞提示

因為字典樹是查找 “與前綴匹配的字符串”,又稱為前綴樹。
關鍵詞提示就是 查尋找前綴匹配的前綴合適關鍵詞,當然還有更複雜的關鍵詞排名問題,這裏不再展開。

2.自動補全功能,如:IDE編譯器自動補全,輸入法自動補全等

原理與搜索引擎類似。

3.敏感詞過濾系統

4.其它

Trie在面試與算法競賽中的例題

1.hihoCoder1014

hihoCoder1014

解題思路:Trie字典樹

首先我們把集合中的N個字符串都插入到trie中。
對於每一個查詢s我們在trie中查找s,如果查找過程中無路可走,那麼一定沒有以s為前綴的字符串。
如果最後停在一個節點p,那我們就要看看以p為根的子樹里一共有多少終結點。
終結點的數目就是答案。

但是如果我們每次都遍歷以P為根的子樹,那時間複雜度就太高了。解決的辦法是用空間換時間,我們增加一個數組intcnt[MAX_NODE]
cnt[i]記錄的是以i號節點為根的子樹中,有幾個終結點。
然後我們每次insert一個字符串的時候,順便就把沿途的節點的cnt值都+1。
這樣就不用每次遍歷以P為根的子樹,而是直接輸出cnt[P]即可。

代碼:

2.hihoCoder1107微軟面試題

hihoCoder1014

其實就是找一個節點p,滿足以p為根的子樹中的終結點不多於5個,同時以p的父節點為根的子樹中的終結點大於5個。
和上題一樣用cnt數組標記,之後dfs查找終結點的數目

3.Trie應用在整數xor異或值最大的題目

給定一個包含N個整數的集合S={A1, A2, A3, … AN}。然
後有M個詢問,每次詢問給定一個整數X,讓你找一個Ai使得Ai xor X的值最大。

首先我們知道一個整數可以用二進製表示成一個01串。比如3=(011)2, 5=(101)2, 4=(100)2……。
我們假設輸入的整數都在0~2^32-1之間,於是我們可以用一個長度是32位的01串表示一個整數。
然後對於給定的N個整數A1, A2, A3, … AN,我們把它們對應的01串都插入到一個trie中。注意這裏字符集只有0和1,所以整個trie是一棵二叉樹。

下面我們舉一個例子,為了描述方便,我們假設整數都在0~7之間,也就是可以用3位01串表示。
現在假設S={1, 2, 7},也就是說我們要在Trie中插入{001, 010, 111}:

這時假設我們要查詢x=4,也就是哪個數和4異或結果最大?4=(100)2,
我們的做法是在trie樹中,盡量與4的二進制位反着走。
比如4的第一位(最高位)是1,我們從0出發第一步就盡量沿着0走。因為我們要異或和最大,01相反才能異或值是1。
並且這一步是可以貪心的,也就是說如果有相反的邊,那麼我們一定沿着這條邊走。因為最高位異或得1的話,即便後面都是0, 10000…000也要比最高位是0,後面都是1的011111…111大。
所以我們第一步沿着標識是0的邊,移動到了1號節點;4第二位是0,所以我們沿着標識是1的邊移動到4號節點;
4的第三位是0,但是4號節點沒有標識是1的邊,所以我們也只好沿着標識是0的邊移動到5號節點。
已經到了終結點,所以5號節點對應的A2=(010)2=2就是我們要求的答案,A2 xor 4 = 6是最大的。

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

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

ApplicationContextRunner如何簡化自動配置測試

 

1. 概覽

眾所周知,自動配置是Spring Boot的關鍵功能之一, 但測試自動配置可能會很棘手。

在以下部分中,我們將展示ApplicationContextRunner如何簡化自動配置測試。

2. 測試自動化配置方案

ApplicationContextRunner是一個實用程序類,它運行ApplicationContext並提供AssertJ樣式斷言。 最好用作測試類中的字段以便共享配置,然後我們在每個測試中進行自定義:

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();

讓我們通過測試一些案例來展示它的魔力。

2.1. 測試Class Condition

在本節中,我們將測試一些使用@ConditionalOnClass和@ConditionalOnMissingClass 註解的自動配置類:

@Configuration
@ConditionalOnClass(ConditionalOnClassIntegrationTest.class)
protected static class ConditionalOnClassConfiguration {
    @Bean
    public String created() { return "This is created when ConditionalOnClassIntegrationTest is present on the classpath"; } } @Configuration @ConditionalOnMissingClass("com.baeldung.autoconfiguration.ConditionalOnClassIntegrationTest") protected static class ConditionalOnMissingClassConfiguration { @Bean public String missed() { return "This is missed when ConditionalOnClassIntegrationTest is present on the classpath"; } } 

我們想測試自動配置是否正確實例化或跳過createdmissing beans給定的預期條件。

  • ApplicationContextRunner為我們提供了withUserConfiguration方法,我們可以根據需要提供自動配置,以便為每個測試自定義ApplicationContext

  • run 方法將 ContextConsumer 作為將斷言應用於上下文的參數。 測試退出時,ApplicationContext將自動關閉:

@Test
public void whenDependentClassIsPresent_thenBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)       .run(context -> {         assertThat(context).hasBean("created");         assertThat(context.getBean("created"))           .isEqualTo("This is created when ConditionalOnClassIntegrationTest is present on the classpath");       }); }   @Test public void whenDependentClassIsPresent_thenBeanMissing() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)         .run(context -> {             assertThat(context).doesNotHaveBean("missed");         }); } 

通過前面的示例,我們發現測試classpath上存在某個類的場景的簡單性。但是,當類不在classpath上時,我們如何測試相反的情況呢

這就是FilteredClassLoader發揮作用的地方。它用於在運行時過濾classpath上指定的類:

@Test
public void whenDependentClassIsNotPresent_thenBeanMissing() {     this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)         .withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))         .run((context) -> {             assertThat(context).doesNotHaveBean("created");             assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);         }); }   @Test public void whenDependentClassIsNotPresent_thenBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)       .withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))       .run((context) -> {         assertThat(context).hasBean("missed");         assertThat(context).getBean("missed")           .isEqualTo("This is missed when ConditionalOnClassIntegrationTest is present on the classpath");         assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);       }); } 

2.2. 測試 Bean Condition

我們剛剛測試了 @ConditionalOnClass 和 @ConditionalOnMissingClass 註解, 現在 讓我們看看使用@ConditionalOnBean和@ConditionalOnMissingBean註釋時的情況。

首先, 我們同樣需要 一些自動配置的類:

@Configuration
protected static class BasicConfiguration {
    @Bean
    public String created() {         return "This is always created";     } } @Configuration @ConditionalOnBean(name = "created") protected static class ConditionalOnBeanConfiguration {     @Bean     public String createOnBean() {         return "This is created when bean (name=created) is present";     } } @Configuration @ConditionalOnMissingBean(name = "created") protected static class ConditionalOnMissingBeanConfiguration {     @Bean     public String createOnMissingBean() {         return "This is created when bean (name=created) is missing";     } } 

然後,我們將像上一節一樣調用withUserConfiguration方法,然後發送我們的自定義配置類來測試自動配置是否在不同的條件下恰當地實例化bean或跳過createOnBeancreateOnMissingBean :

@Test
public void whenDependentBeanIsPresent_thenConditionalBeanCreated() {     this.contextRunner.withUserConfiguration(BasicConfiguration.class,       ConditionalOnBeanConfiguration.class)     // ommitted for brevity } @Test public void whenDependentBeanIsNotPresent_thenConditionalMissingBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingBeanConfiguration.class)     // ommitted for brevity } 

2.3. 測試 Property Condition

在本節中,我們測試使用 @ConditionalOnPropertyannotations的自動配置類。

首先,我們需要這個測試的屬性:

com.baeldung.service=custom

然後,我們編寫嵌套的自動配置類,根據前面的屬性創建bean:

@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties") protected static class SimpleServiceConfiguration {     @Bean     @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "default")     @ConditionalOnMissingBean     public DefaultService defaultService() {         return new DefaultService();     }     @Bean @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "custom") @ConditionalOnMissingBean public CustomService customService() { return new CustomService(); } } 

現在,我們調用withPropertyValues方法來覆蓋每個測試中的屬性值:

@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() { this.contextRunner.withPropertyValues("com.baeldung.service=custom") .withUserConfiguration(SimpleServiceConfiguration.class) .run(context -> { assertThat(context).hasBean("customService"); SimpleService simpleService = context.getBean(CustomService.class); assertThat(simpleService.serve()).isEqualTo("Custom Service"); assertThat(context).doesNotHaveBean("defaultService"); }); } @Test public void whenGivenDefaultPropertyValue_thenDefaultServiceCreated() { this.contextRunner.withPropertyValues("com.baeldung.service=default") .withUserConfiguration(SimpleServiceConfiguration.class) .run(context -> { assertThat(context).hasBean("defaultService"); SimpleService simpleService = context.getBean(DefaultService.class); assertThat(simpleService.serve()).isEqualTo("Default Service"); assertThat(context).doesNotHaveBean("customService"); }); } 

3. 結論

總結一下, 這篇教程主要展示 如何使用ApplicationContextRunner運行帶有自定義的ApplicationContext並應用斷言.

我們在這裏介紹了最常用的場景,而不是列出如何自定義ApplicationContext 。

在此期間,請記住ApplicationConetxtRunner適用於非Web應用程序,因此請考慮WebApplicationContextRunner用於基於servlet的Web應用程序,ReactiveWebApplicationContextRunner用於響應式Web應用程序。

本文源代碼,請訪問GitHub。

原文:www.baeldung.com/spring-boot…

作者:baeldung

譯者:Leesen

 

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

為什麼阿里巴巴要求謹慎使用ArrayList中的subList方法

GitHub 3.7k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

集合是Java開發日常開發中經常會使用到的。在之前的一些文章中,我們介紹過一些關於使用集合類應該注意的事項,如《為什麼阿里巴巴禁止在 foreach 循環里進行元素的 remove/add 操作》、《為什麼阿里巴巴建議集合初始化時,指定集合容量大小》等。

關於集合類,《阿里巴巴Java開發手冊》中其實還有另外一個規定:

本文就來分析一下為什麼會有如此建議?其背後的原理是什麼?

subList

subList是List接口中定義的一個方法,該方法主要用於返回一個集合中的一段、可以理解為截取一個集合中的部分元素,他的返回值也是一個List。

如以下代碼:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    List subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼輸出結果為:

[Hollis]

如果我們改動下代碼,將subList的返回值強轉成ArrayList試一下:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    ArrayList subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼將拋出異常:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

不只是強轉成ArrayList會報錯,強轉成LinkedList、Vector等List的實現類同樣也都會報錯。

那麼,為什麼會發生這樣的報錯呢?我們接下來深入分析一下。

底層原理

首先,我們看下subList方法給我們返回的List到底是個什麼東西,這一點在JDK源碼中註釋是這樣說的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是說subList 返回是一個視圖,那麼什麼叫做視圖呢?

我們看下subList的源碼:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

這個方法返回了一個SubList,這個類是ArrayList中的一個內部類。

SubList這個類中單獨定義了set、get、size、add、remove等方法。

當我們調用subList方法的時候,會通過調用SubList的構造函數創建一個SubList,那麼看下這個構造函數做了哪些事情:

SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

可以看到,這個構造函數中把原來的List以及該List中的部分屬性直接賦值給自己的一些屬性了。

也就是說,SubList並沒有重新創建一個List,而是直接引用了原有的List(返回了父類的視圖),只是指定了一下他要使用的元素的範圍而已(從fromIndex(包含),到toIndex(不包含))。

所以,為什麼不能講subList方法得到的集合直接轉換成ArrayList呢?因為SubList只是ArrayList的內部類,他們之間並沒有集成關係,故無法直接進行強制類型轉換。

視圖有什麼問題

前面通過查看源碼,我們知道,subList()方法並沒有重新創建一個ArrayList,而是返回了一個ArrayList的內部類——SubList。

這個SubList是ArrayList的一個視圖。

那麼,這個視圖又會帶來什麼問題呢?我們需要簡單寫幾段代碼看一下。

1、非結構性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.set(1, "666");

    System.out.println("subList.set(3,666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

得到結果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

當我們嘗試通過set方法,改變subList中某個元素的值得時候,我們發現,原來的那個List中對應元素的值也發生了改變。

同理,如果我們使用同樣的方法,對sourceList中的某個元素進行修改,那麼subList中對應的值也會發生改變。讀者可以自行嘗試一下。

1、結構性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.add("666");

    System.out.println("subList.add(666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

得到結果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我們嘗試對subList的結構進行改變,即向其追加元素,那麼得到的結果是sourceList的結構也同樣發生了改變。

1、結構性改變原List

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    sourceList.add("666");

    System.out.println("sourceList.add(666) 得到List :");
    System.out.println("sourceList : " + sourceList);
    System.out.println("subList : " + subList);

}

得到結果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
    at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
    at java.util.AbstractList.listIterator(AbstractList.java:299)
    at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
    at java.util.AbstractCollection.toString(AbstractCollection.java:454)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.hollis.SubListTest.main(SubListTest.java:28)

我們嘗試對sourceList的結構進行改變,即向其追加元素,結果發現拋出了ConcurrentModificationException。關於這個異常,我們在《一不小心就踩坑的fail-fast是個什麼鬼?》中分析過,這裏原理相同,就不再贅述了。

小結

我們簡單總結一下,List的subList方法並沒有創建一個新的List,而是使用了原List的視圖,這個視圖使用內部類SubList表示。

所以,我們不能把subList方法返回的List強制轉換成ArrayList等類,因為他們之間沒有繼承關係。

另外,視圖和原List的修改還需要注意幾點,尤其是他們之間的相互影響:

1、對父(sourceList)子(subList)List做的非結構性修改(non-structural changes),都會影響到彼此。

2、對子List做結構性修改,操作同樣會反映到父List上。

3、對父List做結構性修改,會拋出異常ConcurrentModificationException。

所以,阿里巴巴Java開發手冊中有另外一條規定:

如何創建新的List

如果需要對subList作出修改,又不想動原list。那麼可以創建subList的一個拷貝:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

PS:最近,《阿里巴巴Java開發手冊》已經正式更名為《Java開發手冊》,併發布了新版本,增加了21條新規約,修改描述112處。

關注公眾號後台回復:手冊,即可獲取最新版Java開發手冊。

參考資料: https://www.jianshu.com/p/5854851240df https://www.cnblogs.com/ljdblog/p/6251387.html

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

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

【機器學習】算法原理詳細推導與實現(二):邏輯回歸

【機器學習】算法原理詳細推導與實現(二):邏輯回歸

在上一篇算法中,線性回歸實際上是 連續型 的結果,即 \(y\in R\) ,而邏輯回歸的 \(y\) 是離散型,只能取兩個值 \(y\in \{0,1\}\),這可以用來處理一些分類的問題。

logistic函數

我們可能會遇到一些分類問題,例如想要劃分 鳶尾花 的種類,嘗試基於一些特徵來判斷鳶尾花的品種,或者判斷上一篇文章中的房子,在6個月之後能否被賣掉,答案是 或者 ,或者一封郵件是否是垃圾郵件。所以這裡是 \(x\) ,這裡是 \(y\) 在一個分類問題中,\(y\) 只能取兩個值0和1,這就是一個二元分類的問題,如下所示:

可以使用線性回歸對以上數值進行劃分,可以擬合出如下那麼一條線,用 \(y=0.5\) 作為臨界點,如果 \(x\) 在這個臨界點的右側,那麼 \(y\) 的值就是1,如果在臨界點的左側,那麼 \(y\) 的值就是0,所以確實會有一些人會這麼做,用線性回歸解決分類問題:

線性回歸解決分類問題,有時候它的效果很好,但是通常用線性回歸解決像這樣的分類問題會是一個很糟糕的主意,加入存在一個額外的訓練樣本 \(x=12\),如果現在對這個訓練集合做線性擬合,那麼可能擬合出來那麼一條直線:

這時候\(y\)的臨界點估計已經不太合適了,可以知道線性回歸對於分類問題來說,不是一個很好的方法。

假設 \(h_\theta(x) \in [0,1]\),當如果已知 \(y\in \{0,1\}\),那麼至少應該讓假設 \(h_\theta(x)\) 預測出來的值不會比1大太多,也不會比0小太多,所以一般不會選擇線性函數作為假設,而是會選擇一些稍微不同的函數圖像:

\[ g(z)=\frac{1}{1+e^{-z}} \]

\[ h_\theta(x)=g(\theta^Tx)=\frac{1}{1+e^{-\theta^Tx}} \]

\(g(z)\) 被稱為 sigmoid函數 ,也通常被稱為 logistic函數,它的函數圖像是:

\(z\) 變得非常小的時候,\(g(x)\) 會趨向於0,當\(z\)變得非常大的時候,\(g(x)\) 會趨向於1,它和縱軸相較於0.5。

邏輯回歸

那麼我們的假設\(h_\theta(x)\) 要嘗試估計 \(y\in \{0,1\}\) 的概率,即:

\[ P(y=1|x;\theta)=h_\theta(x) \]

\[ P(y=0|x;\theta)=1-h_\theta(x) \]

以上可以把兩個公式合併簡寫為(如果\(y=1\)那麼公式為\(h_\theta(x)\);如果\(y=0\)那麼公式為\(1-h_\theta(x)\)):

\[ P(y|x;\theta)=(h_\theta(x))^y(1-h_\theta(x))^{1-y} \]

如果對《概率論和數理統計》學得好的人不難看出,以上函數其實就是 伯努利分佈 的函數。

對於每一個假設值\(h_\theta(x)\),為了使每一次假設值更準確,即當 \(y=1\) 時估計函數 \(P(y=1|x;\theta)=h_\theta(x)\) 趨向於1,當\(y=0\) 時估計函數 \(P(y=0|x;\theta)=1-h_\theta(x)\) 趨向於0。則對於每一個\((x_i,y_i)\),參數 \(\theta\) 的似然估計 \(L(\theta)\)為:

\[ \begin{split} L(\theta)&=P(\vec{y}|X;\theta) \\ &=\prod_{i=1}^mP(y^{(i)}|x^{(i)};\theta) \\ &=\prod_{i=1}^m(h_\theta(x^{(i)}))^{y^{(i)}}(1-h_\theta(x^{(i)}))^{1-{y^{(i)}}} \end{split} \]

如果每一個\((x_i,y_i)\)都準確,即 \(P(y|x;\theta)\) 趨向於1,則應該使似然估計 \(L(\theta)\) 最大化,也就是轉化成熟悉的問題:求解 \(L(\theta)\) 的極大似然估計

為了調整參數 \(\theta\) 使似然估計 \(L(\theta)\) 最大化,推導如下(取 \(log\) 是為了去掉疊乘方便計算):

\[ \begin{split} l(\theta)&=logL(\theta) \\ &=\sum_{i=1}^m{y^{(i)}logh(x^{(i)})+(1-y^{(i)})log(1-h(x^{(i)}))} \end{split} \]

為了使這個函數最大,同樣可以使用前面學習過的梯度下降算法使對數似然估計最大化。之前學習的是要使誤差和 最小化,所以梯度下降的公式為:

\[ \theta:=\theta-\alpha\frac{\partial J(\theta)}{\partial\theta}=>\theta:=\theta-\alpha\nabla_\theta J(\theta) \]

而本次為了求解似然估計最大化,使用的是梯度上升:

\[ \theta:=\theta+\alpha\nabla_\theta l(\theta)=>\theta:=\theta+\alpha\frac{\partial l(\theta)}{\partial\theta} \]

對數似然性是和 \(\theta\) 有關,同樣的為了計算 梯度上升 最快的方向,要對上述公式求偏導得到極值,即是上升最快的方向:

\[ \begin{split} \frac{\partial l(\theta)}{\partial\theta_j}&=(y\frac{1}{g(\theta^Tx)}-(1-y)\frac{1}{1-g(\theta^Tx)})\frac{\partial}{\partial\theta_j}g(\theta^Tx) \\ &=(y\frac{1}{g(\theta^Tx)}-(1-y)\frac{1}{1-g(\theta^Tx)})g(\theta^Tx)(1-g(\theta^Tx))\frac{\partial}{\partial\theta_j}\theta^Tx \\ &=(y(1-g(\theta^Tx))-(1-y)g(\theta^Tx))x_j \\ &=(y-g(\theta^Tx))x_j \\ &=(y-h_{\theta}(x))x_j \end{split} \]

則對於 m 個樣本,則有:

\[ \frac{\partial l(\theta)}{\partial\theta_j}=\sum_{i=1}^m{(y-h_{\theta}(x))x_j} \]

\[ \theta_j:=\theta_j+\sum_{i=1}^m{(y^{(i)}-h_{\theta}(x^{(i)}))x^{(i)}_j} \]

所以總結來說:

邏輯回歸假設數據服從伯努利分佈,通過極大化似然函數的方法,運用梯度下降來求解參數,來達到將數據二分類的目的。

鳶尾花分類

為了劃分 鳶尾花 的種類,嘗試基於一些特徵來判斷鳶尾花的品種,選取100條鳶尾花數據集如下所示:

花萼長度(單位cm) 花萼寬度(單位cm) 種類
5.1 3.5 0
4.9 3.0 0
4.7 3.2 0
7.0 3.2 1
6.4 3.2 1

其中:

種類 含義
0 山鳶尾(setosa)
1 變色鳶尾(versicolor)
2 維吉尼亞鳶尾(virginica)

數據集的圖像分佈為:

計算損失函數:

# 損失函數
def computeCost(theta, X, y):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid(X * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid(X * theta.T)))
    return np.sum(first - second) / (len(X))

梯度下降函數為:

# 梯度下降
def gradient(theta, X, y):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)

    parameters = int(theta.ravel().shape[1])
    grad = np.zeros(parameters)

    error = sigmoid(X * theta.T) - y

    for i in range(parameters):
        term = np.multiply(error, X[:, i])
        grad[i] = np.sum(term) / len(X)

    return grad

最終預測準確率為:

accuracy = 99%

結果分類的圖像為:

數據和代碼下載請關注公眾號【 TTyb 】,後台回復【 機器學習 】即可獲取:

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

Amzaon EC2虛擬化技術演進:從 Xen 到 Nitro

  今年2月,由光環新網運營的 AWS 中國(北京)區域和由西雲數據運營的 AWS 中國 (寧夏)區域發布新的實例類型,新的實例類型包括 C5、C5d、R5、R5d。除了這四種之外,在AWS國外部分區域還上線了最新的C5n。       這些新實例類型個個都具有鮮明的特徵,我簡單整理歸納如下:

  • C5實例:性價比顯著提升(與 C4 實例相比,C5 實例提供了更高的內存與 vCPU 比率,並且性價比提高了 25%,某些應用程序提高了 50% 以上),更大的實例大小(C5 實例新的更大的實例 c5.18xlarge提供了 72 個 vCPU 和 144 GiB 內存並提供了 25 Gbps 的網絡帶寬)。
  • C5d實例:基於本地 NVMe 的 SSD 磁盤將被物理連接到主機服務器,提供與C5實例的生命周期相耦合的塊級存儲。c5d.18xlarge 規格的實例支持2塊900GB的NVMe SSD作為本地存儲。
  • C5n實例:這是C5 系列的最新成員,其c5n.18xlarge規格可提供高達 100Gbps 的網絡吞吐量。
  • R5實例:其最大實例規格支持96 vCPU、768 GiB內存和25 Gbps 網絡帶寬。
  • R5d實例:R5d 實例與 R5 實例規格相同,它還包括高達 3.6 TB 的本地 NVMe 存儲。

這些實例類型之所以如此實力超群,我認為主要歸功於兩點:

  • 處理器升級

C5 實例配備 Intel Xeon Platinum 8000 系列 (Skylake-SP) 處理器,它發佈於2017/Q3,具有高達 3.4GHz 的穩定全核 Turbo CPU 時鐘速度,並使用 Intel Turbo Boost Technology 來允許單個核心睿頻高達3.5GHz。C5 實例為新的 Intel 高級矢量擴展 512 (AVX-512) 指令集提供了支持,與上一代 C4 實例相比,矢量和浮點計算性能提高最高可達2倍。
而發佈於2015年的C4 實例類型,配備Intel Xeon E5-2666 v3 (Haswell) 處理器。其時鐘頻率為2.9 GHz,配合Intel® Turbo Boost后最高可達3.5 GHz。

  • 採用了AWS Nitro 虛擬化平台

AWS Nitro 將是這篇文章的主角。本文會從它的發展歷程、架構、所創造的價值等方面進行分析和介紹,試圖總結出AWS上虛擬化基礎平台發展的脈絡。

AWS EC2虛擬化發展歷程

下錶總結了AWS曾經採用的虛擬化技術,以及這些技術之間的性能對比:

 

  • #1是全模擬技術。這種虛擬化方式能支持未修改的客戶機操作系統,但速度會嚴重下降。典型產品是VMware 在1986年發布的虛擬化產品。AWS 並沒有採用這種虛擬化技術,放在表格中只是為了做對比用。
  • #2 是基於Xen的半虛擬化技術(Paravirtualization,PV)。PV 要求修改客戶機內核和驅動。EC2第一個採用半虛擬化的實例類型是 m1.small。
  • #3 到 #6 是基於Xen和CPU硬件的全虛擬化技術(Hardware-assisted virtualization,HVM)。採用Xen HVM 技術的虛擬機運行在具有CPU和內存(VT-x)硬件虛擬化能力的處理器上,並使用半虛擬化驅動程序用於網絡和存儲設備。HVM 3.0 中尚未實現中斷和定時器半虛擬化,但在4.0中已有改善。
  • #7 和 #8 則是AWS Nitro技術,這是AWS 研發的一種新虛擬化平台。後面會有詳細介紹。

過去幾年中,Xen是AWS上虛擬化技術的主體,業已成為業界標準之一,已經非常成熟。那麼,為什麼AWS要從Xen 向 Nitro 發展呢?這得從Xen 的架構說起。

從上圖可以看出,Xen 實現了虛擬機的CPU 和內存虛擬化,但是虛擬機的I/O 訪問,包括網絡和存儲等,都是通過虛擬機中的前端模塊和 dom0 中的後端模塊通信,然後由dom0 中的後端模塊通過設備驅動實現的。這I/O路徑太長,這降低了I/O性能,而且dom0還會和業務虛擬機搶佔宿主機資源,很難實現管理虛機和業務虛機之間的平衡,以及避免抖動。

2013年,AWS 採用 Xen PV虛擬化技術的cr1.8xlarge 實例的架構如下圖所示:

這是嚴格意義上未採用Nitro技術的最後一個EC2型號。簡要說明:

  1. 圖中的硬件(Hardware),是運行虛擬機的物理服務器,採用了當時很強大的標準的10Gbps網卡,以及管理一些本地磁盤的存儲HBA卡。Hardware上既運行用戶的業務虛擬機,還運行Xen的dom0虛擬機。
  2. VMM採用Xen項目的PV模式。
  3. 圖中 Amzon Linux 代表Xen dom0,它負責訪問硬件,向虛擬機提供I/O 能力。

圖中 cr1.8xlarge 代表一個這種規格的虛擬機,它的本地存儲、EBS卷和VPC網絡訪問都是通過Xen管理的dom0 虛擬機實現的。

Nitro起源和發展

針對傳統虛擬化架構存在的問題,從2012年開始,AWS EC2虛擬化團隊就開始思考以下問題:

  1. 能做出比純軟件架構更好的hypervisor嗎?
  2. 設備模型本身很複雜,而且它會和業務虛擬機競爭CPU和系統資源,同時技術上它很難避免抖動發生
  3. hypervisor太重了,能將hypervisor 和它周邊的組件解耦嗎?

  從成立之日起,AWS就善於聽取客戶的呼聲和建議,並不斷進行迭代式改進,而不是大刀闊斧地從頭設計一個新架構。根據該原則,AWS團隊首先從最難的網絡部分着手,其位置就是上圖中的金黃色虛線框所示位置。從2013年開始,一些EC2實例類型開始支持網絡接口的硬件虛擬化:單根I/O虛擬化(SR-IOV),而第一個是2013年1月發布的C3,它首次採用了AWS增強型網絡(enhanced networking)。這最初是通過ixgbe驅動程序實現的,速度高達10 Gbps。   c3.8xlarge的架構如下圖所示:

c3.8xlarge的架構與cr1.8xlarge相比,在宿主機上增加了一塊新網卡,這塊網卡和原有的標準網絡通過一個迴環線(loopback cable)連接起來。虛機VPC網絡功能不再通過Xen 的dom0 實現,而是直接訪問宿主機上的這塊硬件網卡。C3 是AWS EC2 歷史上增長最快的幾個實例類型之一,它尤其以控制性能抖動和持續的網絡性能著稱。這可以看做Nitro思想的發源,那就是將軟件功能卸載到專有硬件上。

  下一個改進方向是EBS存儲訪問性能提升。   2015年,AWS推出了C4實例類型,它針對EBS卷使用了硬件虛擬化技術。c4.8xlarge的架構如下圖所示。仔細對比能發現,這個新架構與C3中的網絡架構改進有些不同。在虛擬機中,還保留了“前端-後端”這種Xen傳統架構,這是當時為了兼容性和穩妥新考慮,因為NVMe在當時來說還是一種非常新的技術。在宿主機上,採用了新收購的Annapurna Labs公司開發一種卡(下圖中黃色虛線框內),它能將遠端存儲以NMVe形式呈現給虛擬機。

這個改進的結果是,宿主機上的CPU被Xen佔用得少了,能更多地被虛機使用了。

  2016年5月發布的X1 是第一個支持ENA的實例類型。ENA是增強型網絡的最新實現,速度高達25 Gbps。ENA,全稱是Elastic Network Adapter,它正是Nitro項目的一部分,它是由Annapurna Labs公司開發的。

現在的ENA,能用於虛擬機和物理機,它以開源項目形式發布在github上。ENA 是AWS網絡虛擬化一關鍵技術,它使得虛擬機能夠繞過內核和用戶空間網絡處理程序,直接操作網卡硬件,這顯著提升了網絡效率。

從用戶使用角度,也許只是用了一個新網卡驅動。但是其底層採用了Annapurna Labs公司開發的定製網絡ASIC硬件卡。這是Nitro第一款真正的專用硬件卡。它不僅卸載了VPC網絡功能,還卸載了EBS 存儲網絡功能。因此這是一種完全的網絡負載卸載硬件。

 

下一步的優化方向在實例存儲上。2017年,AWS發布了存儲優化實例類型i3,它使用了SR-IOV和NVMe存儲驅動。這是AWS首次採用Annapurna Labs研發的Nitro存儲卡40202所管理的SSD磁盤,這些磁盤被直接映射給虛擬機,虛擬機通過NVME驅動來使用宿主機上的SSD磁盤。這能實現磁盤300萬以上的IOPS性能。Nitro 芯片負責包括磁盤監控、加密、QoS等職責。  

 

  顯然,到這時候為止,仍然剩下的問題只能是Xen 自身,以及它的管理功能部分了。Xen過於笨重,因為作為傳統 Hypervisor,它必須做很多事情 – 它必須保護物理硬件和 BIOS,它必須虛擬化 CPU,虛擬化存儲,虛擬化網絡,並提供豐富的管理功能。其管理性dom0虛擬機會搶佔業務虛機的系統資源。那到底能不能把Xen徹底替換掉呢?答案是肯定的,因為AWS在技術上從來沒讓人失望過。   2017年11月,AWS發布了C5 實例類型。它使用基於KVM的Nitro hypervisor 替換了Xen,hypervisor 軟件大大被簡化,Xen 所用的 dom0 也不需要了。其架構示意圖如下:

 

AWS Nitro 則重新構建了EC2虛擬化基礎架構。Nitro 系統將存儲、網絡和安全功能卸載(offload)到專用的硬件(Nitro卡)上,帶來的好處是虛擬化實例幾乎可以為客戶機操作系統提供主機的所有 CPU 和內存,同時Hypervisor 的功能也因此大大減弱。   Nitro 還被用到2017年發布的AWS 首個物理機實例類型 i3.metal中。下圖是i3.metal架構示意圖:

在i3.metal 中,Nitro 發揮了基礎性作用。它的安全芯片通過提供硬件保護和固件驗證功能為I3實例提供安全保障;它的各種卡,使得I3實例具備基於非易失性存儲器標準 (NVMe) SSD 的實例存儲,通過ENA支持高達 25Gbps 的聚合網絡帶寬。 

Nitro 架構

AWS Nitro 系統是模塊化組件的集合,可以使用廣泛的計算、存儲、內存和網絡選項來設計 EC2 實例,為新一代EC2實例提供動力。它包括三大部分:

 

Nitro 卡

 

這些Nitro 卡是硬件,插入到宿主機的PCIe卡槽中,採用SR-IOV 直通(passthrough)技術將這些卡呈現給實例。包括:

  • VPC Data Plane(用於VPC訪問的Nitro卡):本質上是一塊通過PCIe附加到宿主機上的一塊定製網卡,支持網絡封包和解包、安全組、限速器和路由等功能。實例通過ENA驅動和它通信。同時,該卡還帶有一些網絡加速功能。以限速器為例,每個Nitro支持的實例,不管它在哪個區域哪個數據中心哪個宿主機上,都會有一致的性能,這對分佈式應用非常重要。
  • EBS Data Plane(用於EBS卷訪問的Nitro卡):本質上是一塊通過PCIe附加到宿主機上的一塊定製卡。通過該卡,遠端存儲被以NVMe設備形式展現給實例,實例通過標準NVMe驅動程序訪問該卡。它首次被用在C4中。支持卷加密、存儲加速;支持I3裸機實例。
  • Instance Storage Data Plane(用於實例存儲訪問的Nitro卡):通過該卡,本地磁盤被以NVMe設備形式展現給實例,實例通過標準NVMe驅動程序訪問這些磁盤。支持加密、限速器和本地磁盤監控。

除了卡之外,Nitro 還提供卡控制器(Card Controller)。它提供API端點,負責協調所有Nitro卡、Nitro Hypervisor和Nitro安全芯片。它還利用Nitro安全芯片實現了Hardware Root Of Trust(硬件信任根),支持實例監控、計量和認證。它還為Nitro EBS卡實現了NVMe控制器。

Nitro 安全芯片

Nitro安全芯片整合到宿主機主板中,控制對所有非易失性存儲的訪問,持續監控和保護硬件資源,並在每次系統啟動時獨立驗證固件。

Nitro hypervisor

Nitro hypervisor位於極簡化的定製的Linux 內核中,基於KVM,帶有定製的VMM和小用戶空間應用。它只負責管理內存和CPU分配,將Nitro卡虛擬功能分配給實例,監控和計量硬件等,不再需要提供任何網絡功能。因此它只需執行虛擬機所需指令,快速而且簡單,在大多數工作負載中能提供接近裸機的性能。 Nitro 各組件之間的關係如下圖所示:

 

Nitro 帶來的豐富價值

更高網絡訪問性能

利用Nitro提供的新一代 Elastic Network Adapter (ENA) 和 NVM Express (NVMe) 技術,C5 實例提供了高達 25 Gbps 的網絡帶寬和更低延遲及抖動。2018年發布的更強大變體 C5n 實例,支持網絡帶寬高達 100 Gbps,用戶的仿真、內存緩存、數據湖以及其他通訊密集型應用運行得將比以往更好。   採用Nitro增強網絡功能后的網絡延遲對比:

(Series 1:cc2.8xlarge,2:c3.8xlarge,3:c4.8xlarge,4:c5.18xlarge,5:c5.18xlarge(採用ENAv2))

網絡和存儲帶寬對比:   (1:c3.8xlarge,2:c4.8xlarge,3:c5.18xlarge,4:c5n.18xlarge. Series1:網絡,Series2:存儲)

更高EBS和本地存儲訪問性能

Nitro 使得實例可通過物理方式連接到主機服務器的基於 NVMe 的本地 SSD 塊級存儲,以及將遠端存儲以NVMe設備的形式呈現給實例。 2019年3月,由Nitro支撐的新計算密集型 C5 和 C5d 實例已經在AWS 北京和寧夏區域推出。C5實例支持高達9Gbps 的專用 Amazon EBS 帶寬。而 C5d 最大實例規格則可使用兩塊900G的NVMe SSD。這些實例非常適合需要訪問高速、低延遲的本地存儲的應用程序。

更大實例大小和CPU內存比率

由Nitro支撐的C5實例,其實例的CPU和內存比率,由C4的1:1.875上升到1:2;實例的最大規格,從C4的36vCPU/60Gib內存,上升到72vCPU/144Gib內存。

更低虛擬化花銷

Nitro Hypervisor 是一款輕薄的靜態的虛擬機管理程序,可管理虛擬機的內存和CPU分配,並提供與大多數工作負載無法區分的性能。據Netflix公司Brendan Gregg 觀察,Nitro Hypervisor的性能損耗非常小,通常不到1%,他的結論是 Nitro提供的虛擬化性能接近裸設備。

更低Hypervisor抖動

有了Nitro后,就不再需要為存儲和網絡I/O再預留CPU和內存資源了。這不僅使得可以向EC2實例分配更多資源,為更大的實例規格提供了可能,還為實現一個簡單的輕量的hypervisor提供了可能,而這就為實現更低hypervisor抖動創造了條件。   下圖是一AWS 客戶在三種EC2實例上採用對延遲要求極低的一實時應用做的對比測試。藍色是C5,紅色是i3.metal,黃色是C4。SLA 是用於測試的實時應用所能忍受的最高延遲。

 

從上圖中的測試結果看,C5 相對裸機只有一點極小的附加開銷,而且性能非常平穩,幾乎沒有波動,能完全滿足應用的SLA需求。而C4則有相對較大的波動,只能大概滿足70的SLA。

更多實例類型

AWS發布了基於Nitro的實例存儲實例類型 C5d,M5d 和 R5d,提供低延遲高吞吐的基於NVMe的實例存儲。 AWS在2017 re:Invent上宣布了基於Nitro的AWS EC2 Bare Metal實例 I3.metal。它沒有性能開銷,能夠運行你喜歡的任何東西,比如Xen,KVM,容器,ESXi,FireCracker微虛機等;支持非虛擬化環境,支持容器環境,同時還能繼續使用比如EBS、ELB和VPC等基礎服務;支持比如SAP HANA和其它內存型應用。 AWS還基於Nitro發布了採用AMD EPYC處理器的系列實例R5,M5和T3,最高可降低10%成本。 AWS發布了基於Nitro的具有100Gbps網絡帶寬的實例類型C5n,這是運行HPC和分佈式機器學習負載的理想類型。 AWS發布了基於Nitro的採用AWS Graviton(基於ARM)處理器的實例類型A1,最高可降低45%成本。

更低價格、更高性價比

下錶显示了AWS 北京(BJS)和中衛(ZHY)區域的4代和5代EC2實例的價格比較,你可以看到實實在在的價格下降:  

 

目前,Nitro支撐的C5 實例提供了 EC2 產品系列中最佳的價格/計算性能比。與C4實例相比,其性價比提高了49% 。 與R4實例相比,由Nitro支撐的R5實例為每個vCPU提供額外5%的內存,且每 GiB 價格低50%。R5實例非常適用於高性能數據庫、分佈式內存緩存、內存數據庫和大數據分析等應用程序。

為更多性能優化提供了可能

對於需要深度定製化EC2 的用戶而言,Nitro 還帶了了另外的好處:對於EC2 更深入的監控和優化。在由Nitro支撐的C5實例中,你可以得到數百個PMC 計數器(Performance Monitoring Counters ,性能監控計數器)。作為對比,以前的實例類型中,你只能看到區區7個PMCs。更多的PMC計數器,為性能優化提供了更多可能。

小結

亞馬遜 AWS CTO 沃納·威格爾(Werner Vogels)曾經說過,“在亞馬遜 AWS,我們90%到95%的新項目都是基於客戶給我們的反饋,剩下5%也是從客戶角度出發所做得創新嘗試。”而Nitro 正是這種項目之一,它誕生於2013年,成年於2017年,現在還在不斷成長中。Nitro 正在作為AWS核心虛擬化架構平台,推動着AWS最核心的EC2產品家族不斷往更大(單實例的vCPU和內存更大)、更快(I/O速度更快)、更安全(採用Nitro安全芯片)、更穩定(Hypervisor抖動更低)、更多類型、更高性價比方向演進,支撐越來越多用戶越來越多的業務場景,創造着越來越大的業務價值。     主要參考文檔:

  1. AWS re:Invent 2018: Powering Next-Gen EC2 Instances: Deep Dive into the Nitro System (CMP303-R1)
  2. AWS re:Invent 2017: C5 Instances and the Evolution of Amazon EC2 Virtualization (CMP332)
  3. AWS re:Invent 2018: Deep Dive on Amazon EC2 Instances & Performance Optimization Best Practices (CMP307)
  4. AWS re:Invent 2018:Optimizing Network Performance for Amazon EC2 Instances (CMP308-R1) 

感謝您的閱讀,歡迎關注我的微信公眾號:

 

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

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

反射機制 小小談

反射機制(Reflection)

何為反射

反射是在兩種物質分界面上改變傳播方向返回原來物質中的現象
反射是生物體對外界刺激做出應激行為的過程,根據產生的原因分為條件反射非條件反射等,典型的實驗案例包括巴甫洛夫的狗……
反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制

元……數據??什麼高深莫測的武功??

啊,誠然,一旦涉及到“元XXX”事情通常就開始變得無比抽象,以至於我不禁念叨起那句訣

太極生兩儀,兩儀生四象,四象生八卦……

不過元數據這個概念在數據庫里還是比較常見的,比如,某個關係型數據庫里有張表:

水果

編號 名字 數量
1 蘋果 6
2 香蕉 3
3 5
4 橘子 3
5 菠蘿 2

數據,就是存在表裡的一條一條的記錄,(1,蘋果,6),(3,梨,5)都是數據,那麼,元數據就是凌駕於這些數據之上的用於描述數據數據,對於這張表而言,也就是這張表的表頭(關係數據理論里稱之為關係模式):(編號,名稱,數據)

划重點
元數據(Metadata):用於描述數據的數據

好像有些明朗了,但那關面向對象什麼事呢

眾所周知,類(Class)是面向對象的一個重要概念,儘管,針對於數據庫來說,對象模型和關係模型是不同的概念(上文提到的是關係模型的一個例子),但是,對象模型中的對象和關係模型中的關係,其級別是等同的。

關係……又對象……越來越聽不懂了

好吧,我們先把關係放在一邊,我們只把上邊的東西看做一張表。

難道你就沒有把它改寫成如下形式的衝動嗎??

public class Fruit
{
    public int no;
    public string name;
    public int count;
    
    public Fruit(int no, string name, int count)
    {
        // ...
    }
}

好了,上面的類定義的語義就是

有這樣一類東西,我們稱呼這類東西為水果,結構如下……

那麼,這樣一來,我們就可以定義一個no為9,name叫做“西瓜”,count為5的一個對象,這個對象具有具體的數據。

而上面的類定義代碼,包含的就是這個類的元數據

說的再直白點吧

以人為例,數據注重的是這人的臉長啥樣,而元數據注重的是這人有沒有臉(好像不太對……)

好吧差不多了解了,但元數據和反射有什麼關係呢

反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制

本文一開始就說了,罰站20年

不過在此之前先解釋一件事,元數據在哪

任何一個面向對象的程序設計語言,其類類型都具備一個元數據的存儲,至少程序會使用這個元數據能夠動態地構造此類的對象。但不同的語言機制不同,比如C++這種的,因為直接和系統進行愉♂快的互♂動,因此元數據就直接使用系統的內存地址了,這種數據使用是很不直觀的,同時也不使用任何託管機製做後援(巨硬魔改的C++/CLI不在討論範圍內),因此這種貼近底層的語言不支持反射機制,雖然可以通過強行向程序代碼中通過工廠類模式強行注入可讀的元信息(方法參見這位大佬的文章)。

但是,正如前面所說的,如果元數據在託管編譯或解釋的狀態下會保留一份可讀的版本,這是提供給解釋器或者託管平台用的,當然,這種情況下語言一般會提供一個較為完善的元數據訪問機制,這就是反射。這類語言典型的代表就是C#(.NET託管)、Java(JVM虛擬機)、Python(解釋器提供)等。

那……反射是如何運作的呢??

反射嘛。那還不容易,拿個鏡子就可以了呀!
或者用羊角錘偷襲的方式砸膝蓋什麼的也是很容易的呀!
不過這麼說來,拿羊角錘偷襲鏡子豈不是更棒!!

正如之前所說,反射機制是對類的元數據的獲取和操縱,因此,一個重要的前提就是:

這個程序設計語言的運作機制當中,類的元數據必須是可見的,如果可讀的話那更好

只有當類的元數據是可見的,反射機制才有訪問它們的可能,但是元數據的可讀性會決定反射機制訪問它們的難易程度。

這裏補充一句,有人會說,在使用IDE或者代碼編輯器的時候,我們寫object.property這種訪問方式的時候編譯器不就直接告訴我們了么??
關於這一點,這裏暫時只說一個前提:

反射機制的實際動作是聚焦於運行時(Runtime)的。

在程序代碼編譯之前我們恣意地書寫這MyObject.id.hashCode.getFlush().balabala的時候,這是預編譯的過程,預編譯的時候當然這些元數據都是以字面形式給出的(因為你的代碼里寫了這個類的定義),你可以非常愉悅地Ctrl+C Ctrl+V或者享受着IntelliSense帶給你的N倍快樂,這個時候再談反射就沒什麼意義了,因此,反射機制訪問元數據都是在編譯后運行時發生的。

明明都是面向對象,為什麼偏偏C++不支持這個東西呢

以C++為例,這些元數據是否可見?答案是肯定的,那為什麼不支持反射機制呢,因為這些元數據是以指針的方式給出的,指針在已編譯的C++程序中的存在形式就是地址,說的再粗暴點,就是4或8字節的二進制數……
也就是說,在已經編譯完成的C++程序的眼裡,類的元數據已經變成二進制的地址碼了,如果某人在沒有源代碼的情況下想給這個項目寫一個反射機制,那麼他將不得不面對一大堆的:

0xb08dfe231a1c002e
0xb08dfe231bc128f6
0xb08dfe2417a90f5d
......

看到這些,他長舒了一口氣,優雅地點燃了一根香煙,然後毫不猶豫地戳到電腦屏幕上:

鬼知道這是什麼玩意啊!!

如果原項目加個殼、模板元編一下再做個混淆加密的話那更沒法看了,因此如果一定要實現反射機制,一般都是把反射機制直接囊括到項目開發過程當中(就像上面那位大佬的文章中提到的那樣,原項目的作者也是反射機制的構造者)。
這樣的話就會存在一個上上上個世紀汽車行業出現的問題:

這輛車的件無法用到另外一輛車上!
這個反射機制無法用到別的項目上!

當然,這樣說可能有些絕對,但以C++的方式實現一個能夠廣泛用於所有項目的反射機制應該是極端困難的。
上面大佬的文章當中,這個C++的項目要使用反射機制,是藉助工廠模式實現的,關於這些的實現方法,詳見大佬文章(當然我自己也沒完全看懂)

那託管語言又如何呢

C#、Java,這兩種語言都是託管代碼的(C#使用.NET進行託管,Java則交給了JVM虛擬機)。

與C++不同的是,他們並不直接接觸系統底層,而是通過中間代碼訪問底層的。

中間代碼由誰處理呢,C#是通過.NET提供的CLR,產生的中間語言是程序集,而Java靠的是JVM,其中間產物是class文件。
如果有幸使用一些IDE打開這兩個文件往裡窺探一遭的話,我們應當不難從中找到這些元數據的信息。

這就好像,一群孩子進了幼兒園,一個託管老師全程進行看護。

把拔碼麻區上辦,我區悠貳園吶

當然,託管老師肯定是知道孩子叫什麼名的,訪問他們自然也是很容易的。同理,託管環境(或虛擬環境)也是一樣的,因為銜接上下兩層,因此把底層的元數據和上層的可讀文本構造反射的橋樑是很容易辦到的,因此,C#和Java都提供了一套非常完善的反射庫,他們可以被用於使用這兩種語言寫的任意一個類當中。

好了,道理我都懂,但為什麼要反射呢?

反射能幹什麼呢

舉個最簡單的例子

我……我有一個夢想,我想要這樣一個函數,能夠返回Person類是否有我所說的方法,但是我不知道Person類里有什麼,比如我想問他有沒有Eat()方法,它返回true,我問他有沒有Fly()方法,它能返回false

好了,換作是你,你會怎麼實現這樣一個函數呢??

而反射機制恰恰做到了!
你提供給反射機制一個字符串形式的函數名,反射機制不僅可以得知這個函數是否存在,甚至能幫助你去執行這個函數(Invoke)。

什麼,你不好問它有沒有某個函數??好啊,反射機制甚至可以告訴你這個類都有哪些屬性哪些函數,繼承自誰,可見性如何,是否抽象等等。

那反射在什麼時候比較好用呢

上面那個例子其實就是一個經典的用途。

或者,我們可以考慮另外一個場景。

你寫了某個函數接受了一個抽象為Object的對象,你希望,如果Object的對象存在方法Grow則調用之,否則什麼也不做。

這個時候首先可以通過反射機制確定方法是否存在,但即便方法已經存在,我們是無法直接調用的,因為對象已經抽象為Object,而Object並不存在方法Grow,所以直接調用就洗洗睡了。

我們不能具象回來么??

如果我們知道類在抽象之前是什麼類型的時候,那當然可以具象化回來。
但是抽象雖然發生於編譯時或運行時(動態創建的對象),但具象類型的獲知卻是在編譯之前的代碼源文件,而且還有些時候你根本無法知道原類型,那也沒辦法拆箱。


這裏面我為了方便,也是想不出啥更好的詞
這裏我稱派生類基類的多態轉化為抽象
反過來的過程稱為具象

那我還怎麼調用Grow

反射機制可以獲取到完整的可用方法的列表,我們在列表中找到了Grow,存在形式為Method/MethodInfo對象或乾脆就是個字符串。

但無論是哪種,obj.Grow();是不可能了,好在反射機制連這件事都考慮在內了——Invoke調用!!

反射機制不僅知道你想要什麼方法,還可以幫助你調用這個方法,這個調用就通過一個叫做Invoke的方法完成。

不同語言對Invoke的定義不盡相同但功能上大同小異,通過Invoke調用某方法的過程實質上是轉調回調(或者是間接調用)。
間接調用比直接調用更加的強大靈活,但繞了遠路。

還有什麼比較宏大一點的應用么??

宏大一點……好吧,其實每一個磅礴的工程都是從一點一滴做起的。

一個很經典的案例,就是上文那篇大佬文章里的一個常用功能——序列化(Serialization)
雖然C#和Java本身就有可以用於序列化的一些結構和功能庫(Serializable接口之類的),但是有些時候我們對序列化機制如果有更高的可定製性要求的時候,我們往往傾向於自己構建一套心儀的序列化功能庫。

於是乎就有一個最簡單的問題擺在面前:

現在有Class1類的對象,還有Class2類的對象,還有Class3類的一些對象想要轉化成可解析的內容,以供發送或保存(當然這也就意味着,這些對象的所有屬性和狀態都要保存),但是這老大老二老三一家一個樣,屬性也各不相同,我又不想挨個單獨寫,那該怎麼辦呢??

現在有了反射機制,問題就很容易解決了。三胞胎嫌分起來麻煩??反射機制可以把他們安排的明明白白!!你可以向反射類提供一個完整的類名,反射機制就能保證給你這個類對應的可用屬性的列表,以及一整套處理方案(Get和Set),之後還不是想來啥來啥,美滋滋~~

當然,以上都是反射機制用途中小的不能再小的冰山一角,比如我還可以通過反射機制根據我的輸入創建我想要的類型的對象等等。

哇,反射這麼強大??我要滿地反射!!

冷靜點!任何事物都有多面性,反射也不例外,我們看看反射機制有什麼特點,它到底是否適合所有情形。

極致靈動(Flexi Frenzy) 稀有屬性

反射機制可以讓你的代碼非常靈活,以不變應萬變。
這也正是反射機制帶來的最大的好處。

未卜先知(Fortune Tell) 普通屬性

反射機制是在運行時起作用,當然,運行期間發生什麼,編譯之前是無法獲知的,反射就是處理這件事的。

效率捉急(Emaciated Efficiency) 糟糕屬性

反射機制最大的問題!

反射機制的效率是十分低下的,首先在運行時獲取元數據再轉化成可讀形式就不是一個很快的過程,而反射的Invoke調用是個不折不扣的間接調用。

不當地使用大量反射會導致程序效率的急劇下降。

代碼膨脹(Code Expansion)

顯然,用反射進行調用的代碼往往比直接調用寫起來複雜,所以除非你寫代碼是按行數計工資,否則能直接調用就不要反射。

健壯風險(Robustness Risk)

反射機制一般允許用戶傳入字符串……

然後就是萬劫不復深淵之伊始

這時候用戶傳的字符串就可以非常的五花八門了,就好像一個動物園裡,反射機制是一個可愛的小動物,而遊客開始不分青紅皂白地對它投各種食,良莠不齊,可是你的反射機制很脆弱,它可禁不起這折騰,吃到不好的東西就會生病罷工(拋異常,然後中止),因此你這當奶媽奶爸就要多操心,幫它收拾(捕獲),告訴他如何分辨食物(預先判斷)……

不過呢,有些時候引入反射機制恰恰就是出於健壯性的考慮……

如果我養的不是個反射機制而是一隻熊貓的話我會上天的!!

總結

反射是個強大的武器,但使用應多加謹慎!

以上

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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