分類
發燒車訊

Elastic Stack 開源的大數據解決方案

目的

本文主要介紹的內容有以下三點:
一. Elastic Stack是什麼以及組成部分
二. Elastic Stack前景以及業務應用
三. Elasticsearch原理(索引方向)
四. Elasticsearch相對薄弱的地方

一、Elastic Stack是什麼以及組成部分

介紹Elastic Stack是什麼,其實只要一句話就可以,就是: 一套完整的大數據處理堆棧,從攝入、轉換到存儲分析、可視化

它是不同產品的集合,各司其職,形成完整的數據處理鏈,因此Elastic Stack也可以簡稱為BLEK。

Beats 輕量型數據採集器

Logstash 輸入、過濾器和輸出

Elasticsearch 查詢和分析

Kibana 可視化,可自由選擇如何呈現數據

1. Beats – 全品類採集器,搞定所有數據類型

Filebeat(日誌文件):對成百上千、甚至上萬的服務器生成的日誌匯總,可搜索。

Metricbeat(指標): 收集系統和服務指標,CPU 使用率、內存、文件系統、磁盤 IO 和網絡 IO 統計數據。

Packetbeat(網絡數據):網絡數據包分析器,了解應用程序動態。

Heartbeat(運行時間監控):通過主動探測來監測服務的可用性
……
Beats支持許許多多的beat,這裏列的都是比較的常用的beat,了解更多可以點擊鏈接:

2. Logstash – 服務器端數據處理管道

介紹Logstash之前,我們先來看下Linux下常用的幾個命令

cat alldata.txt | awk ‘{print $1}’ | sort | uniq | tee filterdata.txt

只要接觸過Linux同學,應該都知道這幾個命名意思

cat alldata.txt #將alldata.txt的內容輸出到標準設備上
awk ‘{print $1}’ #對上面的內容做截取,只取每一行第一列數據
sort | uniq  #對截取后的內容,進行排序和唯一性操作
tee filterdata.txt #將上面的內容寫到filterdata.txt

上面的幾個簡單的命令就可以看出來,這是對數據進行了常規的處理,用名詞修飾的話就是:數據獲取/輸入數據清洗數據過濾數據寫入/輸出

而Logstash做的也是相同的事(看下圖)。

將系統的日誌文件、應用日誌文件、系統指標等數據,輸入到Input,再通過數據清洗以及過濾,輸入到存儲設備中,這裏當然是輸入到Elasticsearch

3. Elasticsearch – 分佈式文檔存儲、RESTful風格的搜索和數據分析引擎

Elasticsearch主要也是最原始的功能就是搜索和分析功能。這裏就簡單說一下,下面講原理的時候會着重講到Elasticsearch

搜索:全文搜索,完整的信息源轉化為計算機可以識別、處理的信息單元形成的數據集合 。

分析:相關度,搜索所有內容,找到所需的具體信息(詞頻或熱度等對結果排序)

4. Kibana- 可視化

可視化看下圖(來源官網)便知

可以對日誌分析、業務分析等做可視化

現在從總體上來了解下,在心中對Elastic Stack有個清楚的認知(下圖)。

二、Elastic Stack前景以及業務應用

1. DB-Engines 排名

Elasticsearch是Elastic Stack核心,由圖可以看出在搜索領域Elasticsearch暫時沒有對手。

2. ES社區

ES中文社區也是相當活躍的,會定期做一下分享,都是大公司的寶貴經驗,值得參考。

3. 2018年攜程的使用情況(讓我們看看能處理多大的數據)

集群數量是94個,最小的集群一般是3個節點。全部節點數量大概700+。

最大的一個集群是做日誌分析的,其中數據節點330個,最高峰一天產生1600億文檔,寫入值300w/s。

現在有2.5萬億文檔,大概是幾個PB的量

三、Elasticsearch(ES)原理

因為篇目有限,本篇只介紹ES的索引原理。

ES為什麼可以做全文搜索,主要就是用了倒排索引,先來看下面的一張圖

看圖可以簡單的理解倒排索引就是:關鍵字 + 頁碼

對倒排索引有個基本的認識后,下面來做個簡單的數據例子。

現在對Name做到排索引,記住:關鍵字 + ID(頁碼)。

對Age做到排索引。

對Intersets做到排索引。

現在搜索Age等於18的,通過倒排索引就可以快速得到1和3的id,再通過id就可以得到具體數據,看,這樣是不是快的狠。

如果是用Mysql等關係數據庫,現在有十多億數據(大數據嘛),就要一條一條的掃描下去找id,效率可想而知。而用倒排索引,找到所有的id就輕輕鬆鬆了。

在ES中,關鍵詞叫Term,頁碼叫Posting List。

但這樣就行了嗎? 如果Name有上億個Term,要找最後一個Term,效率豈不是還是很低?

再來看Name的倒排索引,你會發現,將Armani放在了第一個,Tyloo放在了第三個,可以看出來,對Term做了簡單的排序。雖然簡單,但很實用。這樣查找Term就可以用二分查找法來查找了,將複雜度由n變成了logn。

在ES中,這叫Term Dictionary。

到這裏,再來想想MySQL的b+tree, 你有沒有發現原理是差不多的,那為什麼說ES搜索比MySQL快很多,究竟快在哪裡? 接下來再看。

有一種數據結構叫Trie樹,又稱前綴樹或字典樹,是一種有序樹。這種數據結構的好處就是可以壓縮前綴和提高查詢數據。

現在有這麼一組Term: apps, apple, apply, appear, back, backup, base, bear,用Trie樹表示如下圖。

通過線路路徑字符連接就可以得到完成的Term,並且合用了前綴,比如apps, apple, apply, appear合用了app路徑,節省了大量空間。

這個時候再來找base單詞,立即就可以排除了a字符開頭的單詞,比Term Dictionary快了不知多少。

在ES中,這叫Term Index

現在我們再從整體看下ES的索引

先通過Trie樹快速定位block(相當於頁碼), 再到Term Dictionary 做二分查找,得到Posting List。

索引優化

ES是為了大數據而生的,這意味着ES要處理大量的數據,它的Term數據量也是不可想象的。比如一篇文章,要做全文索引,就會對全篇的內容做分詞,會產生大量的Term,而ES查詢的時候,這些Term肯定要放在內存裏面的。

雖然Trie樹對前綴做了壓縮,但在大量Term面前還是不夠,會佔用大量的內存使用,於是就有ES對Trie樹進一步演化。

FST(Finite State Transducer )確定無環有限狀態轉移器 (看下圖)

可以看appear、bear 對共同的後綴做了壓縮。

Posting List磁盤壓縮

假設有一億的用戶數據,現在對性別做搜索,而性別無非兩種,可能”男”就有五千萬之多,按int4個字節存儲,就要消耗50M左右的磁盤空間,而這僅僅是其中一個Term。

那麼面對成千上萬的Term,ES究竟是怎麼存儲的呢?接下來,就來看看ES的壓縮方法。

Frame Of Reference (FOR) 增量編碼壓縮,將大數變小數,按字節存儲

只要能把握“增量,大數變小數,按字節存儲”這幾個關鍵詞,這個算法就很好理解,現在來具體看看。

現在有一組Posting List:[60, 150, 300,310, 315, 340], 按正常的int型存儲,size = 6 * 4(24)個字節。

  1. 按增量存儲:60 + 90(150)+ 150(300) + 10(310) + 5(315)+ 25(340),也就是[60, 90, 150, 10, 5, 25],這樣就將大數變成了小數。

  2. 切分成不同的block:[60, 90, 150]、[10, 5, 25],為什麼要切分,下面講。

  3. 按字節存儲:對於[60, 90, 150]這組block,究竟怎麼按字節存儲,其實很簡單,就是找其中最大的一個值,看X個比特能表示這個最大的數,那麼剩下的數也用X個比特表示(切分,可以盡可能的壓縮空間)。

[60, 90, 150]最大數150 < 2^8 = 256,也就是這組每個數都用8個比特表示,也就是 3*8 = 24個比特,再除以8,也就是3個字節存在,再加上一個8的標識位(說明每個數是8個比特存儲),佔用一個字節,一共4個字節。

[10, 5, 25]最大數25 < 2^5 = 32,每個數用5個比特表示,3*5=15比特,除以8,大約2個字節,加上5的標識位,一共3個字節。

那麼總體size = 4 + 3(7)個字節,相當於24個字節,大大壓縮了空間。

再看下圖表示

Posting List內存壓縮

同學們應該都知道越複雜的算法消耗的CPU性能就越大,比如常見的https,第一次交互會用非對稱密碼來驗證,驗證通過後就轉變成了對稱密碼驗證,FOR同樣如此,那麼ES是用什麼算法壓縮內存中的Posting List呢?

Roaring Bitmaps 壓縮位圖索引

Roaring Bitmaps 涉及到兩種數據結構 short[] 、bitmap。

short好理解就是2個字節的整型。

bitmap就是用比特表示數據,看下面的例子。

Posting List:[1, 2, 4, 7, 10] -> [1, 1, 0, 1, 0, 0, 1,0, 0, 1],取最大的值10,那麼就用10個比特表示這組Posting List,第1, 2, 4, 7, 10位存在,就將相對應的“位”置為1,其他的為0。

但這種bitmap數據結構有缺陷,看這組Posting List: [1, 3, 100000000] -> [1, 0, 1, 0, 0, 0, …, 0, 0, 1 ],最大數是1億,就要1億的比特表示,這麼算下來,反而消耗了更多的內存。

那如何解決這個問題,其實也很簡單,跟上面一樣,將大數變成小數

看下圖:

第一步:將每個數除以65536,得到(商,餘數)。

第二步:按照商,分成不同的block,也就是相同的商,放在同一個block裏面,餘數就是這個值在這個block裏面的位置(永遠不會超過65536,餘數嘛)。

第三步:判斷底層block用什麼數據結構存儲數據,如果block裏面的餘數的個數超過4096個,就用short存儲,反之bitmap。

上面那個圖是官網的圖,看下我畫的圖,可能更好理解些。

到這裏,ES的索引原理就講完了,希望大家都能理解。

四、Elasticsearch(ES)相對薄弱的地方

1. 多表關聯

其實ES有一個很重要的特性這裏沒有介紹到,也就是分佈式,每一個節點的數據和,才是整體數據。

這也導致了多表關聯問題,雖然ES裏面也提供了Nested& Join 方法解決這個問題,但這裏還是不建議用。

那這個問題在實際應用中應該如何解決? 其實也很簡單,裝換思路,ES無法解決,就到其他層解決,比如:應用層,用面向對象的架構,拆分查詢。

2. 深度分頁

分佈式架構下,取數據便不是那麼簡單,比如取前1000條數據,如果是10個節點,那麼每個節點都要取1000條,10個節點就是10000條,排序后,返回前1000條,如果是深度分頁就會變的相當的慢。

ES提供的是Scroll + Scroll_after,但這個採取的是緩存的方式,取出10000條后,緩存在內存里,再來翻頁的時候,直接從緩存中取,這就代表着存在實時性問題。

來看看百度是怎麼解決這個問題的。

一樣在應用層解決,翻頁到一定的深度后,禁止翻頁。

3. 更新應用

頻繁更新的應用,用ES會有瓶頸,比如一些遊戲應用,要不斷的更新數據,用ES不是太適合,這個看大家自己的應用情況。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

線性模型之邏輯回歸(LR)(原理、公式推導、模型對比、常見面試點),3種類型的梯度下降算法總結,李宏毅機器學習筆記2:Gradient Descent(附帶詳細的原理推導過程),L0、L1、L2范數正則化

參考資料(要是對於本文的理解不夠透徹,必須將以下博客認知閱讀,方可全面了解LR):

(1).

(2).

(3).

(4).

(5).

(6).

 

一、邏輯回歸介紹

  邏輯回歸(Logistic Regression)是一種廣義線性回歸。線性回歸解決的是回歸問題,預測值是實數範圍,邏輯回歸則相反,解決的是分類問題,預測值是[0,1]範圍。所以邏輯回歸名為回歸,實為分類。接下來讓我們用一句話來概括邏輯回歸(LR):

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

這句話包含了五點,接下來一一介紹:

  • 邏輯回歸的假設
  • 邏輯回歸的損失函數
  • 邏輯回歸的求解方法
  • 邏輯回歸的目的
  • 邏輯回歸如何分類

 二、邏輯回歸的假設

任何的模型都是有自己的假設,在這個假設下模型才是適用的。邏輯回歸的第一個基本假設是假設數據服從伯努利分佈。

伯努利分佈:是一個離散型概率分佈,若成功,則隨機變量取值1;若失敗,隨機變量取值為0。成功概率記為p,失敗為q = 1-p。

在邏輯回歸中,既然假設了數據分佈服從伯努利分佈,那就存在一個成功和失敗,對應二分類問題就是正類和負類,那麼就應該有一個樣本為正類的概率p,和樣本為負類的概率q = 1- p。具體我們寫成這樣的形式:

邏輯回歸的第二個假設是正類的概率由sigmoid的函數計算,即

即:

寫在一起:

個人理解,解釋一下這個公式,並不是用了樣本的標籤y,而是說你想要得到哪個的概率,y = 1時意思就是你想得到正類的概率,y = 0時就意思是你想要得到負類的概率。另外在求參數時,這個y是有用的,這點在下面會說到。

另外關於這個值,  是個概率,還沒有到它真正能成為預測標籤的地步,更具體的過程應該是分別求出正類的概率,即y = 1時,和負類的概率,y = 0時,比較哪個大,因為兩個加起來是1,所以我們通常默認的是只用求正類概率,只要大於0.5即可歸為正類,但這個0.5是人為規定的,如果願意的話,可以規定為大於0.6才是正類,這樣的話就算求出來正類概率是0.55,那也不能預測為正類,應該預測為負類。

 理解了二元分類回歸的模型,接着我們就要看模型的損失函數了,我們的目標是極小化損失函數來得到對應的模型係數θ

 三、邏輯回歸的損失函數

 回顧下線性回歸的損失函數,由於線性回歸是連續的,所以可以使用模型誤差的的平方和來定義損失函數。但是邏輯回歸不是連續的,自然線性回歸損失函數定義的經驗就用不上了。不過我們可以用最大似然法(MLE)來推導出我們的損失函數。有點人把LR的loss function成為log損失函數,也有人把它稱為交叉熵損失函數(Cross Entropy)。

極大似然估計:利用已知的樣本結果信息,反推最具有可能(最大概率)導致這些樣本結果出現的模型參數值(模型已定,參數未知)

如何理解這句話呢?

再聯繫到邏輯回歸里,一步步來分解上面這句話,首先確定一下模型是否已定,模型就是用來預測的那個公式:

參數就是裏面的  ,那什麼是樣本結果信息,就是我們的x,y,是我們的樣本,分別為特徵和標籤,我們的已知信息就是在特徵取這些值的情況下,它應該屬於y類(正或負)。

反推最具有可能(最大概率)導致這些樣本結果出現的參數,舉個例子,我們已經知道了一個樣本點,是正類,那麼我們把它丟入這個模型后,它預測的結果一定得是正類啊,正類才是正確的,才是我們所期望的,我們要盡可能的讓它最大,這樣才符合我們的真實標籤。反過來一樣的,如果你丟的是負類,那這個式子計算的就是負類的概率,同樣我們要讓它最大,所以此時不用區分正負類。

這樣串下來,一切都說通了,概括一下:

一個樣本,不分正負類,丟入模型,多的不說,就是一個字,讓它大

一直只提了一個樣本,但對於整個訓練集,我們當然是期望所有樣本的概率都達到最大,也就是我們的目標函數,本身是個聯合概率,但是假設每個樣本獨立,那所有樣本的概率就可以由以下公式推到:

設:

似然函數:

為了更方便求解,我們對等式兩邊同取對數,寫成對數似然函數:

在機器學習中我們有損失函數的概念,其衡量的是模型預測錯誤的程度。如果取整個數據集上的平均對數似然損失,我們可以得到:

即在邏輯回歸模型中,我們最大化似然函數和最小化損失函數實際上是等價的。所以說LR的loss function可以由MLE推導出來。

 四、邏輯回歸損失函數的求解

解邏輯回歸的方法有非常多,主要有梯度下降(一階方法)和牛頓法(二階方法)。優化的主要目標是找到一個方向,參數朝這個方向移動之後使得損失函數的值能夠減小,這個方嚮往往由一階偏導或者二階偏導各種組合求得。邏輯回歸的損失函數是:

隨機梯度下降:梯度下降是通過 J(w) 對 w 的一階導數來找下降方向,初始化參數w之後,並且以迭代的方式來更新參數,更新方式為 :

其中 k 為迭代次數。每次更新參數后,可以通過比較  小於閾值或者到達最大迭代次數來停止迭代。

梯度下降又有隨機梯度下降,批梯度下降,small batch 梯度下降三種方式:

  • 簡單來說 批梯度下降會獲得全局最優解,缺點是在更新每個參數的時候需要遍歷所有的數據,計算量會很大,並且會有很多的冗餘計算,導致的結果是當數據量大的時候,每個參數的更新都會很慢。
  • 隨機梯度下降是以高方差頻繁更新,優點是使得sgd會跳到新的和潛在更好的局部最優解,缺點是使得收斂到局部最優解的過程更加的複雜。
  • 小批量梯度下降結合了sgd和batch gd的優點,每次更新的時候使用n個樣本。減少了參數更新的次數,可以達到更加穩定收斂結果,一般在深度學習當中我們採用這種方法。

加分項,看你了不了解諸如Adam,動量法等優化方法(在這就不展開了,以後有時間的話專門寫一篇關於優化方法的)。因為上述方法其實還有兩個致命的問題:

  • 第一個是如何對模型選擇合適的學習率。自始至終保持同樣的學習率其實不太合適。因為一開始參數剛剛開始學習的時候,此時的參數和最優解隔的比較遠,需要保持一個較大的學習率儘快逼近最優解。但是學習到後面的時候,參數和最優解已經隔的比較近了,你還保持最初的學習率,容易越過最優點,在最優點附近來回振蕩,通俗一點說,就很容易學過頭了,跑偏了。
  • 第二個是如何對參數選擇合適的學習率。在實踐中,對每個參數都保持的同樣的學習率也是很不合理的。有些參數更新頻繁,那麼學習率可以適當小一點。有些參數更新緩慢,那麼學習率就應該大一點。

有關梯度下降原理以及優化算法詳情見我的博客:

 

任何模型都會面臨過擬合問題,所以我們也要對邏輯回歸模型進行正則化考慮。常見的有L1正則化和L2正則化。

L1 正則化

LASSO 回歸,相當於為模型添加了這樣一個先驗知識:w 服從零均值拉普拉斯分佈。 首先看看拉普拉斯分佈長什麼樣子:

由於引入了先驗知識,所以似然函數這樣寫:

取 log 再取負,得到目標函數:

等價於原始損失函數的後面加上了 L1 正則,因此 L1 正則的本質其實是為模型增加了“模型參數服從零均值拉普拉斯分佈”這一先驗知識。

L2 正則化

Ridge 回歸,相當於為模型添加了這樣一個先驗知識:w 服從零均值正態分佈。

首先看看正態分佈長什麼樣子:

由於引入了先驗知識,所以似然函數這樣寫:

取 ln 再取負,得到目標函數:

等價於原始的損失函數後面加上了 L2 正則,因此 L2 正則的本質其實是為模型增加了“模型參數服從零均值正態分佈”這一先驗知識。

其餘有關正則化的內容詳見:

五、邏輯回歸的目的

 該函數的目的便是將數據二分類,提高準確率。

六、邏輯回歸如何分類

這個在上面的時候提到了,要設定一個閾值,判斷正類概率是否大於該閾值,一般閾值是0.5,所以只用判斷正類概率是否大於0.5即可。

七、為什麼LR不使用平方誤差(MSE)當作損失函數?

1.  平方誤差損失函數加上sigmoid的函數將會是一個非凸的函數,不易求解,會得到局部解,用對數似然函數得到高階連續可導凸函數,可以得到最優解。

2.  其次,是因為對數損失函數更新起來很快,因為只和x,y有關,和sigmoid本身的梯度無關。如果你使用平方損失函數,你會發現梯度更新的速度和sigmod函數本身的梯度是很相關的。sigmod函數在它在定義域內的梯度都不大於0.25。這樣訓練會非常的慢。

八、邏輯回歸的優缺點

優點:

  • 形式簡單,模型的可解釋性非常好。從特徵的權重可以看到不同的特徵對最後結果的影響,某個特徵的權重值比較高,那麼這個特徵最後對結果的影響會比較大。
  • 模型效果不錯。在工程上是可以接受的(作為baseline),如果特徵工程做的好,效果不會太差,並且特徵工程可以大家并行開發,大大加快開發的速度。
  • 訓練速度較快。分類的時候,計算量僅僅只和特徵的數目相關。並且邏輯回歸的分佈式優化sgd發展比較成熟,訓練的速度可以通過堆機器進一步提高,這樣我們可以在短時間內迭代好幾個版本的模型。
  • 資源佔用小,尤其是內存。因為只需要存儲各個維度的特徵值。
  • 方便輸出結果調整。邏輯回歸可以很方便的得到最後的分類結果,因為輸出的是每個樣本的概率分數,我們可以很容易的對這些概率分數進行cut off,也就是劃分閾值(大於某個閾值的是一類,小於某個閾值的是一類)。

缺點:

  • 準確率並不是很高。因為形式非常的簡單(非常類似線性模型),很難去擬合數據的真實分佈。
  • 很難處理數據不平衡的問題。舉個例子:如果我們對於一個正負樣本非常不平衡的問題比如正負樣本比 10000:1.我們把所有樣本都預測為正也能使損失函數的值比較小。但是作為一個分類器,它對正負樣本的區分能力不會很好。
  • 處理非線性數據較麻煩。邏輯回歸在不引入其他方法的情況下,只能處理線性可分的數據,或者進一步說,處理二分類的問題 。
  • 邏輯回歸本身無法篩選特徵。有時候,我們會用gbdt來篩選特徵,然後再上邏輯回歸。

 

 

西瓜書中提到了如何解決LR缺點中的藍色字體所示的缺點:

 

九、 邏輯斯特回歸為什麼要對特徵進行離散化。

  • 非線性!非線性!非線性!邏輯回歸屬於廣義線性模型,表達能力受限;單變量離散化為N個后,每個變量有單獨的權重,相當於為模型引入了非線性,能夠提升模型表達能力,加大擬合; 離散特徵的增加和減少都很容易,易於模型的快速迭代;
  • 速度快!速度快!速度快!稀疏向量內積乘法運算速度快,計算結果方便存儲,容易擴展;
  • 魯棒性!魯棒性!魯棒性!離散化后的特徵對異常數據有很強的魯棒性:比如一個特徵是年齡>30是1,否則0。如果特徵沒有離散化,一個異常數據“年齡300歲”會給模型造成很大的干擾;
  • 方便交叉與特徵組合:離散化后可以進行特徵交叉,由M+N個變量變為M*N個變量,進一步引入非線性,提升表達能力;
  • 穩定性:特徵離散化后,模型會更穩定,比如如果對用戶年齡離散化,20-30作為一個區間,不會因為一個用戶年齡長了一歲就變成一個完全不同的人。當然處於區間相鄰處的樣本會剛好相反,所以怎麼劃分區間是門學問;
  • 簡化模型:特徵離散化以後,起到了簡化了邏輯回歸模型的作用,降低了模型過擬合的風險。

十、與其他模型的對比

與SVM

相同點

1. 都是線性分類器。本質上都是求一個最佳分類超平面。都是監督學習算法。
2. 都是判別模型。通過決策函數,判別輸入特徵之間的差別來進行分類。

  • 常見的判別模型有:KNN、SVM、LR。
  • 常見的生成模型有:樸素貝恭弘=叶 恭弘斯,隱馬爾可夫模型。

 

不同點

(1). 本質上是損失函數不同
LR的損失函數是交叉熵:

SVM的目標函數:

  • 邏輯回歸基於概率理論,假設樣本為正樣本的概率可以用sigmoid函數(S型函數)來表示,然後通過極大似然估計的方法估計出參數的值。
  • 支持向量機基於幾何間隔最大化原理,認為存在最大幾何間隔的分類面為最優分類面。

(2). 兩個模型對數據和參數的敏感程度不同

  • SVM考慮分類邊界線附近的樣本(決定分類超平面的樣本)。在支持向量外添加或減少任何樣本點對分類決策面沒有任何影響;
  • LR受所有數據點的影響。直接依賴數據分佈,每個樣本點都會影響決策面的結果。如果訓練數據不同類別嚴重不平衡,則一般需要先對數據做平衡處理,讓不同類別的樣本盡量平衡。
  • LR 是參數模型,SVM 是非參數模型,參數模型的前提是假設數據服從某一分佈,該分佈由一些參數確定(比如正太分佈由均值和方差確定),在此基礎上構建的模型稱為參數模型;非參數模型對於總體的分佈不做任何假設,只是知道總體是一個隨機變量,其分佈是存在的(分佈中也可能存在參數),但是無法知道其分佈的形式,更不知道分佈的相關參數,只有在給定一些樣本的條件下,能夠依據非參數統計的方法進行推斷。

(3). SVM 基於距離分類,LR 基於概率分類。

  • SVM依賴數據表達的距離測度,所以需要對數據先做 normalization;LR不受其影響。

(4). 在解決非線性問題時,支持向量機採用核函數的機制,而LR通常不採用核函數的方法。

  • SVM算法里,只有少數幾個代表支持向量的樣本參与分類決策計算,也就是只有少數幾個樣本需要參与核函數的計算。
  • LR算法里,每個樣本點都必須參与分類決策的計算過程,也就是說,假設我們在LR里也運用核函數的原理,那麼每個樣本點都必須參与核計算,這帶來的計算複雜度是相當高的。尤其是數據量很大時,我們無法承受。所以,在具體應用時,LR很少運用核函數機制。

(5). 在小規模數據集上,Linear SVM要略好於LR,但差別也不是特別大,而且Linear SVM的計算複雜度受數據量限制,對海量數據LR使用更加廣泛。

(6). SVM的損失函數就自帶正則,而 LR 必須另外在損失函數之外添加正則項。

那怎麼根據特徵數量和樣本量來選擇SVM和LR模型呢?Andrew NG的課程中給出了以下建議:

 

  • 如果Feature的數量很大,跟樣本數量差不多,這時候選用LR或者是Linear Kernel的SVM
  • 如果Feature的數量比較小,樣本數量一般,不算大也不算小,選用SVM+Gaussian Kernel
  • 如果Feature的數量比較小,而樣本數量很多,需要手工添加一些feature變成第一種情況。(LR和不帶核函數的SVM比較類似。)

 

插入一個知識點:判別模型與生成模型的區別?

公式上看

  • 生成模型: 學習時先得到P(x,y),繼而得到 P(y|x)。預測時應用最大后驗概率法(MAP)得到預測類別 y。
  • 判別模型: 直接學習得到P(y|x),利用MAP得到 y。或者直接學得一個映射函數 y=f(x) 。

直觀上看

  • 生成模型: 關注數據是如何生成的
  • 判別模型: 關注類別之間的差別

數據要求:生成模型需要的數據量比較大,能夠較好地估計概率密度;而判別模型對數據樣本量的要求沒有那麼多。

更多區別見

 

與樸素貝恭弘=叶 恭弘斯

相同點

樸素貝恭弘=叶 恭弘斯和邏輯回歸都屬於分類模型,當樸素貝恭弘=叶 恭弘斯的條件概率  服從高斯分佈時,它計算出來的 P(Y=1|X) 形式跟邏輯回歸是一樣的。

不同點

  • 邏輯回歸是判別式模型 p(y|x),樸素貝恭弘=叶 恭弘斯是生成式模型 p(x,y):判別式模型估計的是條件概率分佈,給定觀測變量 x 和目標變量 y 的條件模型,由數據直接學習決策函數 y=f(x) 或者條件概率分佈 P(y|x) 作為預測的模型。判別方法關心的是對於給定的輸入 x,應該預測什麼樣的輸出 y;而生成式模型估計的是聯合概率分佈,基本思想是首先建立樣本的聯合概率概率密度模型 P(x,y),然後再得到后驗概率 P(y|x),再利用它進行分類,生成式更關心的是對於給定輸入 x 和輸出 y 的生成關係;
  • 樸素貝恭弘=叶 恭弘斯的前提是條件獨立,每個特徵權重獨立,所以如果數據不符合這個情況,樸素貝恭弘=叶 恭弘斯的分類表現就沒邏輯會好了。

十一、多分類問題(LR解決多分類問題)

參考西瓜書!!!

現實中我們經常遇到不只兩個類別的分類問題,即多分類問題,在這種情形下,我們常常運用“拆分”的策略,通過多個二分類學習器來解決多分類問題,即將多分類問題拆解為多個二分類問題,訓練出多個二分類學習器,最後將多個分類結果進行集成得出結論。最為經典的拆分策略有三種:“一對一”(OvO)、“一對其餘”(OvR)和“多對多”(MvM),核心思想與示意圖如下所示。

  • OvO:給定數據集D,假定其中有N個真實類別,將這N個類別進行兩兩配對(一個正類/一個反類),從而產生N(N-1)/2個二分類學習器,在測試階段,將新樣本放入所有的二分類學習器中測試,得出N(N-1)個結果,最終通過投票產生最終的分類結果。

          優點:它在一定程度上規避了數據集 unbalance 的情況,性能相對穩定,並且需要訓練的模型數雖然增多,但是每次訓練時訓練集的數量都降低很多,其訓練效率會提高。

     缺點:訓練出更多的 Classifier,會影響預測時間。

  • OvM:給定數據集D,假定其中有N個真實類別,每次取出一個類作為正類,剩餘的所有類別作為一個新的反類,從而產生N個二分類學習器,在測試階段,得出N個結果,若僅有一個學習器預測為正類,則對應的類標作為最終分類結果。

    優點:普適性還比較廣,可以應用於能輸出值或者概率的分類器,同時效率相對較好,有多少個類別就訓練多少個分類器。

    缺點:很容易造成訓練集樣本數量的不平衡(Unbalance),尤其在類別較多的情況下,經常容易出現正類樣本的數量遠遠不及負類樣本的數量,這樣就會造成分類器的偏向性。

  • MvM:給定數據集D,假定其中有N個真實類別,每次取若干個類作為正類,若干個類作為反類(通過ECOC碼給出,編碼),若進行了M次劃分,則生成了M個二分類學習器,在測試階段(解碼),得出M個結果組成一個新的碼,最終通過計算海明/歐式距離選擇距離最小的類別作為最終分類結果。

 

十一、邏輯回歸實例(數據來源於Kaggle)

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

【其他文章推薦】

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

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

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

築起和平之牆 修復河壩 達佛「氣候變遷戰爭」平息有望

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

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

瑞士朝非核家園邁進 首度關閉核電廠

摘錄自2019年12月21日公視報導

核電占發電量36.4%的瑞士,已經在台灣時間20號晚上,首度永久關閉核能電廠。瑞士電力公司表示,這座米勒貝格核能電廠,要達到完全廢爐,還得經過15年的時間,但已經是重要的起步。

控制室裡的工程師按下按鈕,正式切斷米勒貝格核電廠與瑞士全國電力網的連結。在典禮會場觀禮的官員、業者和民間團體代表,響起如雷的掌聲。負責營運的伯恩能源集團指出,20號切斷與全國電力網的連結,只是米勒貝格核電廠除役的第一步而已。

伯恩能源集團執行長蘇珊托瑪說:「整個過程將耗時15年,分成三個步驟:第一階段後在發電廠仍有高輻射劑量,這個狀態將持續五年,之後的七年會處於低輻射狀態。直到最後廢爐前的五年期間,原地將會重建新的建築物。」

在2011年日本的福島核能災變後,許多使用核能發電的國家紛紛檢討能源政策。瑞士政府決定逐步廢核,米勒貝格電廠停機在2013年就定案。而另一個使用更久的貝茲諾核電廠,瑞士國會決議在2029與2031年先後停機。2016年一項公投結果,否決了提前廢核的政策。不過在去年,瑞士政府仍舊決定,無限期停發新的核電廠執照,也就是維持逐步廢核的路線。

儘管廢核政策是在福島核災後成形定案,不過瑞士走向非核家園並非基於政治因素。電力業者以米勒貝格電廠為例指出,維持營運要付出龐大的成本,不符合經濟效益。同時土地狹窄多山的瑞士,要尋找合適的核廢料最終儲存地點,也是相當困難的任務。

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

【其他文章推薦】

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

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

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

騰訊、和諧汽車、富士康“互聯網+智慧電動汽車”專案公佈企業負責人

和諧富騰互聯網加智慧電動汽車公司(以下簡稱“和諧富騰”)日前宣佈旗下“互聯網+智慧電動汽車”企業負責人,畢福康博士(Dr. Carsten Breitfeld)將擔任該企業首席執行官,戴雷博士(Dr. Daniel Kirchert)將擔任首席運營官。兩位管理層到任後將即刻啟動企業的運營。畢福康博士和戴雷博士亦將是該企業的事業合夥人和公司董事會成員。

左:畢福康博士(Dr. Carsten Breitfeld);右:戴雷博士(Dr. Daniel Kirchert)

和諧富騰是由中國和諧新能源汽車控股有限公司、鴻海集團與騰訊集團透過各自附屬實體聯合創立的創新投資平臺,“互聯網+智慧電動汽車”是該平臺旗下核心戰略專案和獨立企業,旨在開發面向未來的個人出行解決方案,塑造源自中國、佈局世界的高端品牌,為消費者提供智慧、愉悅、生態友好的駕乘體驗。

畢福康博士擁有機械工程學博士學位,是全球電動汽車研發領域的一流專家。畢福康博士此前在寶馬集團總部工作20年,擔任過底盤開發、傳動系統開發及產品戰略等方面的多個高級管理崗位,2010年起擔任寶馬集團新一代電動超級跑車i8項目總監,成為這一世界汽車行業劃時代旗艦車型的研發主腦。通過引入革命性的開發流程,畢福康博士帶領團隊實現了i8車型于2014年成功面世,在產品性能、材料、技術革新及研發速度等各個方面顯著優於傳統汽車產品,創下了全球汽車行業新的標杆。在畢福康博士的推動下,i8車型還引進了創新的銷售和客戶體驗機制。

戴雷博士是中國豪華汽車領域擁有最豐富銷售、運營和品牌塑造經驗的高層主管之一,也是業內公認的“中國通”。戴雷博士此前曾擔任東風英菲尼迪汽車有限公司總經理和華晨寶馬汽車有限公司行銷高級副總裁,相關品牌在其任內均創下豪華車市場的銷售增長紀錄,在品牌塑造和市場行銷方面也創造了若干標杆性的案例。戴雷博士在產品戰略、銷售網路發展和合資企業組建運營方面也擁有深厚的經驗。

畢福康博士和戴雷博士將在就任後與傳媒見面,並就“互聯網+智慧電動汽車”的戰略規劃和企業具體運營資訊與傳媒和公眾溝通。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

「車和家」產品計畫 中國微信現身

4月16日,中國汽車之家創始人李想在微信朋友圈發文表示,目前個人精力110%投入在車和家上,並首次透露具體的產品計畫:車和家將打造兩款車型來滿足90%的城市需求,一款小而美的SEV,一款大而強的SUV。

3月底,李想的電動車創業項目「車和家」完成A輪融資,並且申報造車資質。李想預計在四年時間裡,打造小而美電動車大概需要2億美元的投入。車和家已經準備好了1億美元,另外的1億美元將通過融資獲得。

李想強調,車和家不是互聯網造車,也不玩互聯網概念,而是標準的新生代汽車品牌。一是自主研發,研發與製造工程師團隊平均汽車行業從業15年以上;二是自主生產,自建常州武進750畝的30萬產能的全鋁工廠,以及配套的BMS與電池廠;三是自己提供銷售與服務,打破4S店高價、低質、效率低的現狀。車和家的目標是像蘋果一樣去創新產品與商業模式,像華為一樣扎扎實實搞研發,並放眼全球市場。

以下為李想微信朋友圈全文(簡體中文,截圖):

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

【其他文章推薦】

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

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

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

9012年了,再不會Https就老了

前言  

       合格的web後端程序員,在開發之外,必須給IIS、Nginx、各種原生web服務器上配置Https,不然你就僅僅是個碼農,本博最近將專題記錄

  • 如何為IIS,Nginx配置Https

  • 如何申請適用於生產的免費SSL證書

本博客小試牛刀,先實操在Nginx for Docker上添加自簽名SSL證書

為啥先倒騰自簽名SSL證書,申請公網SSL證書需要公網可識別的域名或者公網IP;

如果有實際SSL證書, 按照本文替換即可。

手繪Https原理

長話短說:  目前常見的Http請求明文傳輸, 報文可被截取並篡改,請求可被偽造; 

因此基於常見HTTP(HTTP-TCP-IP)協議棧引入SSL/TSL(Transport Secure Layer) ,HTTPS在進行加密傳輸之前會進行一次握手,確定傳輸密鑰。

流程解讀:

① 傳輸密鑰是對稱密鑰,用於雙方對傳輸數據的加解密

② 怎麼在傳輸之前確立傳輸密鑰呢? 針對普遍的多客戶端訪問受信web服務器的場景, 提出非對稱密鑰(公鑰存於客戶端,私鑰存於web服務器),雙方能互相加解密,說明中間數據(傳輸密鑰)沒被篡改。

③ 再拋出疑問,怎麼認定下發的公鑰是這個web服務器匹配的密鑰?怎麼確定這個公鑰下發過程沒被截取篡改? 這就是追溯到握手階段的下發證書過程,瀏覽器內置的CA機構認定該證書是其有效下發,並通過簽名認定該證書沒被篡改,最終認定該 證書下發的公鑰是受信web服務器準確下發。

④ 如果面向面試記憶Https原理,恐怕有些難度,所以個人用一種 【雞生蛋還是蛋生雞】的方式向上追溯流程, 方便大家知其然更知其所以然。

前置準備

 >   CentOS機器上安裝Docker、 Docker-Compose

 >   常規操作構建 Nginx for Docker網站, 項目結構如下: 

ssl-docker-nginx
    ├── docker-compose.yml
    ├── nginx   
    │      └── nginx.conf
    └── site
           └── index.html

    該項目將會使用  nginx/nginx.conf、site/index.html替換Nginx鏡像默認配置文件和默認啟動頁,docker-compose.yml 如下:

version: '2'
services:
  server:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./site:/usr/share/nginx/html
    ports:
    - "8080:80"

    docker-compose  up -d 啟動Nginx容器,還是那樣熟悉的味道: 【chrome默認將http連接認定為不安全

添加SSL自簽名證書

很明顯: web服務器需要存儲證書(內置了公鑰)和私鑰

 ① 創建自簽名證書 (什麼叫自簽名,就是自己給自己頒發 SSL證書)

[nodotnet@gs-server-5809 ssl-docker-nginx]$ openssl req -newkey rsa:2048 -nodes -keyout nginx/my-site.com.key -x509 -days 365out nginx/my-site.com.crt

req是證書請求的子命令,-newkey rsa:2048 -keyout nginx/my-site.com.key表示生成私鑰(PKCS8格式),

          -nodes 表示私鑰不加密,

           -x509表示輸出證書,-days365 為有效期,此後根據提示輸入證書擁有者信息;

       之後會在nginx目錄下生產2個文件, 分別是私鑰、證書

 ② 將證書和密鑰掛載到Nginx Image, 修改docker-compose.yml

version: '2'
services:
  server:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./site:/usr/share/nginx/html
      - ./nginx/my-site.com.crt:/etc/nginx/my-site.com.crt    #新行 - ./nginx/my-site.com.key:/etc/nginx/my-site.com.key    #新行
    ports:
    - "8080:80"
    - "443:443"        // 容器開啟HTTPS默認的443端口

 ③  修改nginx/nginx.conf,接受Https請求

events {
  worker_connections  4096;  ## Default: 1024
}

http {
    server {
        listen 80;
        root         /usr/share/nginx/html/;
    }

    server {            # 新Server接受來自443端口的Https請求
        listen              443 ssl;
        ssl_certificate     /etc/nginx/my-site.com.crt;
        ssl_certificate_key /etc/nginx/my-site.com.key;
        root        /usr/share/nginx/html;
    }
}

執行docker-compose down && docker-compose up -d 發起https://10.201.80.126:443請求,當前自簽名證書頒發機構不在瀏覽器內置的CA機構,所以該證書目前被瀏覽器認為是無效。

理論上將 該自簽名證書導出,之後在 【chrome瀏覽器】-【高級設置】-【管理證書】中導入該證書,即可讓 chrome接受自簽名SSL證書。

That‘s All,  Https作為以後web的主流配置,碼農進階資深必須掌握;後續會記錄Https & HSTS, 申請免費SSL證書,盡請關注。

 

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

OTA升級詳解(三)

君子知夫不全不粹之不足以為美也, 

故誦數以貫之,

思索以通之,

為其人以處之,

除其害者以持養之;

          出自荀子《勸學篇》

終於OTA的升級過程的詳解來了,之前的兩篇文章與主要是鋪墊,

OTA升級的一些基礎知識,那這邊文章就開始揭開OTA-recovery模式升級過程的神秘面紗,需要說明的是

以下重點梳理了本人認為的關鍵、核心的流程,其他如ui部分、簽名校驗部分我並未花筆墨去描述,主要

還是講升級的核心,其他都是枝枝恭弘=叶 恭弘恭弘=叶 恭弘。Android 10 recovery源碼分析,代碼來源路徑:

https://www.androidos.net.cn/android/10.0.0_r6/xref

本文所講的流程代碼路徑為:bootable/recovery/

首先從文件層面說下升級功能的調用流程,說明如下:

recovery-main.cpp      升級的主入口

recovery.cpp                開始recovery升級的處理流程

install/install.cpp         執行升級的處理流程(調用updater)

updater/updater.cpp  完成升級的核心流程

 

 

 

1 主入口代碼為:recovery-main.cpp,main入口

1.1 日誌相關的工作準備

 

 1 // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout
 2 // (which is redirected to recovery.log) as we used to do.
 3 android::base::InitLogging(argv, &UiLogger);
 4 
 5 // Take last pmsg contents and rewrite it to the current pmsg session.
 6 static constexpr const char filter[] = "recovery/";
 7 // Do we need to rotate?
 8 bool do_rotate = false;
 9 
10 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate);
11 // Take action to refresh pmsg contents
12 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);
13 
14 time_t start = time(nullptr);
15 
16 // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger
17 // instances with different timestamps.
18 redirect_stdio(Paths::Get().temporary_log_file().c_str());

1.2 load_volume_table(); 加載系統分區信息,注意這裏並明白掛載分區

.mount_point = “/tmp”, .fs_type = “ramdisk”, .blk_device = “ramdisk”, .length = 0 

mount_point — 掛載點    fs_type — 分區類型 

blk_device     — 設備塊名 length  — 分區大小

1.3 掛載/cache分區,我們的升級命令都放在這個分區下

1 has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;

1.4 獲取升級的參數並寫BCB塊信息

std::vector<std::string> args = get_args(argc, argv);

if (!update_bootloader_message(options, &err)) {
    LOG(ERROR) << "Failed to set BCB message: " << err;
}

a、讀取misc分區分區,並將recovery模式升級的標記寫到misc分區中,這樣做的目的是斷電續升,

升級中掉電之後,如果下次開機重啟,在bootloader中會讀取此標記,並重新進入到recovery模式中

update_bootloader_message函數完成此功能。

b、從/cache/recovery/command 中讀取升級參數,這裏recovery啟動進程是未帶入參數時,command

文件的接口其實有很詳細的解釋

 * The arguments which may be supplied in the recovery.command file:
 *   --update_package=path - verify install an OTA package file
 *   --wipe_data - erase user data (and cache), then reboot
 *   --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
 *       data (and cache), then reboot
 *   --wipe_cache - wipe cache (but not user data), then reboot
 *   --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
 *   --just_exit - do nothing; exit and reboot

1.5 加載recovery_ui_ext.so,完成升級中與屏幕信息的显示,升級進度,升級結果等。這裏就不多說了。

static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
  // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
  // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
  // in on dlclose).
  void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);

  using MakeDeviceType = decltype(&make_device);
  MakeDeviceType make_device_func = nullptr;
  if (librecovery_ui_ext == nullptr) {
    printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
  } else {
    reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
    if (make_device_func == nullptr) {
      printf("Failed to dlsym make_device: %s\n", dlerror());
    }
  }

1.6 非fastboot模式升級就開始了recovery模式升級,start_recovery

ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);

2 進入 recovery.cpp 

2.1 參數解析,這些參數其實就是來源於/cache/recovery/command, 上面已經通過get_arg,

讀取到了args中

2.2 界面的各種ui信息显示,點事電量的檢查等待輔助動作。

2.3 函數名為安裝升級包,其實還未真正開始進行升級包的安裝

1 status = install_package(update_package, should_wipe_cache, true, retry_count, ui);

2.4 安裝結束之後由finish_recovery()完成收尾工作,保存日誌、清除BCB中的標記,設備重啟。

 1 static void finish_recovery() {
 2   std::string locale = ui->GetLocale();
 3   // Save the locale to cache, so if recovery is next started up without a '--locale' argument
 4   // (e.g., directly from the bootloader) it will use the last-known locale.
 5   if (!locale.empty() && has_cache) {
 6     LOG(INFO) << "Saving locale \"" << locale << "\"";
 7     if (ensure_path_mounted(LOCALE_FILE) != 0) {
 8       LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
 9     } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
10       PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
11     }
12   }
13 
14   copy_logs(save_current_log, has_cache, sehandle);
15 
16   // Reset to normal system boot so recovery won't cycle indefinitely.
17   std::string err;
18   if (!clear_bootloader_message(&err)) {
19     LOG(ERROR) << "Failed to clear BCB message: " << err;
20   }
21 
22   // Remove the command file, so recovery won't repeat indefinitely.
23   if (has_cache) {
24     if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
25       LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
26     }
27     ensure_path_unmounted(CACHE_ROOT);
28   }
29 
30   sync();  // For good measure.
31 }

3 install/install.cpp

3.1 install.cpp其實就進入了安裝升級包的準備動作,剛上的install_package,是假的,這裏才是

really_install_package

1 really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
2                                     retry_count, &max_temperature, ui);

3.2 really_install_package 關鍵地方已加註釋

 1 static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
 2                                   std::vector<std::string>* log_buffer, int retry_count,
 3                                   int* max_temperature, RecoveryUI* ui) {
 4   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
 5   ui->Print("Finding update package...\n");
 6   // Give verification half the progress bar...
 7   ui->SetProgressType(RecoveryUI::DETERMINATE);
 8   ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
 9   LOG(INFO) << "Update location: " << path;
10 
11   // Map the update package into memory.
12   ui->Print("Opening update package...\n");
13 
14   if (needs_mount) {
15     if (path[0] == '@') {
16       ensure_path_mounted(path.substr(1));
17     } else {
18       ensure_path_mounted(path);
19     }
20   }
21 
22   /* 將zip映射到內存中 */
23   auto package = Package::CreateMemoryPackage(
24       path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
25   if (!package) {
26     log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
27     return INSTALL_CORRUPT;
28   }
29 
30   // Verify package.進行zip包進行簽名校驗
31   if (!verify_package(package.get(), ui)) {
32     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
33     return INSTALL_CORRUPT;
34   }
35 
36   // Try to open the package.打開zip包
37   ZipArchiveHandle zip = package->GetZipArchiveHandle();
38   if (!zip) {
39     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
40     return INSTALL_CORRUPT;
41   }
42 
43   // Additionally verify the compatibility of the package if it's a fresh install.
44   if (retry_count == 0 && !verify_package_compatibility(zip)) {
45     log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
46     return INSTALL_CORRUPT;
47   }
48 
49   // Verify and install the contents of the package.
50   ui->Print("Installing update...\n");
51   if (retry_count > 0) {
52     ui->Print("Retry attempt: %d\n", retry_count);
53   }
54   ui->SetEnableReboot(false);
55   int result =
56       /* 執行升級updater進程進行升級 */
57       try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
58   ui->SetEnableReboot(true);
59   ui->Print("\n");
60 
61   return result;
62 }

3.3 try_update_binary

從升級包中讀取元數據信息

1 ReadMetadataFromPackage(zip, &metadata)

3.4 從升級包中讀取updater進程

 1 int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
 2                              int status_fd, std::vector<std::string>* cmd) {
 3   CHECK(cmd != nullptr);
 4 
 5   // In non-A/B updates we extract the update binary from the package.
 6   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
 7   ZipString binary_name(UPDATE_BINARY_NAME);
 8   ZipEntry binary_entry;
 9   if (FindEntry(zip, binary_name, &binary_entry) != 0) {
10     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
11     return INSTALL_CORRUPT;
12   }
13 
14   const std::string binary_path = Paths::Get().temporary_update_binary();
15   unlink(binary_path.c_str());
16   android::base::unique_fd fd(
17       open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
18   if (fd == -1) {
19     PLOG(ERROR) << "Failed to create " << binary_path;
20     return INSTALL_ERROR;
21   }
22 
23   int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
24   if (error != 0) {
25     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
26     return INSTALL_ERROR;
27   }
28 
29   // When executing the update binary contained in the package, the arguments passed are:
30   //   - the version number for this interface
31   //   - an FD to which the program can write in order to update the progress bar.
32   //   - the name of the package zip file.
33   //   - an optional argument "retry" if this update is a retry of a failed update attempt.
34   *cmd = {
35     binary_path,
36     std::to_string(kRecoveryApiVersion),
37     std::to_string(status_fd),
38     package,
39   };
40   if (retry_count > 0) {
41     cmd->push_back("retry");
42   }
43   return 0;
44 }

3.5 創建管道,這裏子進程關閉了讀端,父進程關閉了寫端,這樣就是保證從單向的信息通信,從

子進程傳入信息到父進程中。

1 android::base::Pipe(&pipe_read, &pipe_write, 0)

3.6 創建子進程,在子進程中運行update-binary進程

 1 if (pid == 0) {
 2     umask(022);
 3     pipe_read.reset();
 4 
 5     // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
 6     auto chr_args = StringVectorToNullTerminatedArray(args);
 7     /* chr_args[0] 其實就是升級包中的 META-INF/com/google/android/update-binary */
 8     execv(chr_args[0], chr_args.data());
 9     // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
10     // hang. This deadlock results from an improperly copied mutex in the ui functions.
11     // (Bug: 34769056)
12     fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
13     _exit(EXIT_FAILURE);
14   }

3.7 recovery獲取子進程的信息並显示,進度、ui_print 等等。

1 FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
2 while (fgets(buffer, sizeof(buffer), from_child) != nullptr)

4 execv執行升級進程之後,工作在updater/updater.cpp中完成。

4.1 這裏的主要核心就是構造腳本解析器對updater-script中的命令進行執行,至於這個腳本解析器

是如何構造的,如何執行的, 其實我也搞的不是很清楚。

4.2 安裝升級包的核心程序就是Configure edify’s functions. 中的那些註冊回調函數

  1 int main(int argc, char** argv) {
  2 // Various things log information to stdout or stderr more or less
  3 // at random (though we've tried to standardize on stdout).  The
  4 // log file makes more sense if buffering is turned off so things
  5 // appear in the right order.
  6   setbuf(stdout, nullptr);
  7   setbuf(stderr, nullptr);
  8 // We don't have logcat yet under recovery. Update logs will always be written to stdout
  9 // (which is redirected to recovery.log).
 10   android::base::InitLogging(argv, &UpdaterLogger);
 11 if (argc != 4 && argc != 5) {
 12     LOG(ERROR) << "unexpected number of arguments: " << argc;
 13 return 1;
 14   }
 15 /* 支持的版本檢查 */
 16 char* version = argv[1];
 17 if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
 18 // We support version 1, 2, or 3.
 19     LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
 20 return 2;
 21   }
 22 // Set up the pipe for sending commands back to the parent process.
 23 int fd = atoi(argv[2]);
 24   FILE* cmd_pipe = fdopen(fd, "wb");
 25   setlinebuf(cmd_pipe);
 26 // Extract the script from the package.
 27 /* 從包中提取腳本 */
 28 const char* package_filename = argv[3];
 29   MemMapping map;
 30 if (!map.MapFile(package_filename)) {
 31     LOG(ERROR) << "failed to map package " << argv[3];
 32 return 3;
 33   }
 34   ZipArchiveHandle za;
 35 int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
 36 if (open_err != 0) {
 37     LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
 38     CloseArchive(za);
 39 return 3;
 40   }
 41 ZipString script_name(SCRIPT_NAME);
 42   ZipEntry script_entry;
 43 int find_err = FindEntry(za, script_name, &script_entry);
 44 if (find_err != 0) {
 45     LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
 46                << ErrorCodeString(find_err);
 47     CloseArchive(za);
 48 return 4;
 49   }
 50 std::string script;
 51   script.resize(script_entry.uncompressed_length);
 52 int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
 53                                     script_entry.uncompressed_length);
 54 if (extract_err != 0) {
 55     LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
 56     CloseArchive(za);
 57 return 5;
 58   }
 59 // Configure edify's functions.
 60 /* 註冊updater-script中的回調函數 這裏主要是一些斷言函數 abort assert*/
 61   RegisterBuiltins();
 62 /* 這裏主要是一些安裝升級包的函數 主要是對有文件系統的分區來說*/
 63   RegisterInstallFunctions();
 64 /* 這裏主要註冊對裸分區進行升級的函數 */
 65   RegisterBlockImageFunctions();
 66   RegisterDynamicPartitionsFunctions();
 67   RegisterDeviceExtensions();
 68 // Parse the script.
 69 std::unique_ptr<Expr> root;
 70 int error_count = 0;
 71 int error = ParseString(script, &root, &error_count);
 72 if (error != 0 || error_count > 0) {
 73     LOG(ERROR) << error_count << " parse errors";
 74     CloseArchive(za);
 75 return 6;
 76   }
 77   sehandle = selinux_android_file_context_handle();
 78   selinux_android_set_sehandle(sehandle);
 79 if (!sehandle) {
 80 fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
 81   }
 82 // Evaluate the parsed script.
 83   UpdaterInfo updater_info;
 84   updater_info.cmd_pipe = cmd_pipe;
 85   updater_info.package_zip = za;
 86   updater_info.version = atoi(version);
 87   updater_info.package_zip_addr = map.addr;
 88   updater_info.package_zip_len = map.length;
 89 State state(script, &updater_info);
 90 if (argc == 5) {
 91 if (strcmp(argv[4], "retry") == 0) {
 92       state.is_retry = true;
 93     } else {
 94 printf("unexpected argument: %s", argv[4]);
 95     }
 96   }
 97 std::string result;
 98 bool status = Evaluate(&state, root, &result);
 99 if (!status) {
100 if (state.errmsg.empty()) {
101       LOG(ERROR) << "script aborted (no error message)";
102 fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
103     } else {
104       LOG(ERROR) << "script aborted: " << state.errmsg;
105 const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
106 for (const std::string& line : lines) {
107 // Parse the error code in abort message.
108 // Example: "E30: This package is for bullhead devices."
109 if (!line.empty() && line[0] == 'E') {
110 if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
111             LOG(ERROR) << "Failed to parse error code: [" << line << "]";
112           }
113         }
114 fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
115       }
116     }
117 // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
118 // a more specific code has been set in errmsg.
119 if (state.error_code == kNoError) {
120       state.error_code = kScriptExecutionFailure;
121     }
122 fprintf(cmd_pipe, "log error: %d\n", state.error_code);
123 // Cause code should provide additional information about the abort.
124 if (state.cause_code != kNoCause) {
125 fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
126 if (state.cause_code == kPatchApplicationFailure) {
127         LOG(INFO) << "Patch application failed, retry update.";
128 fprintf(cmd_pipe, "retry_update\n");
129       } else if (state.cause_code == kEioFailure) {
130         LOG(INFO) << "Update failed due to EIO, retry update.";
131 fprintf(cmd_pipe, "retry_update\n");
132       }
133     }
134 if (updater_info.package_zip) {
135       CloseArchive(updater_info.package_zip);
136     }
137 return 7;
138   } else {
139 fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
140   }
141 if (updater_info.package_zip) {
142     CloseArchive(updater_info.package_zip);
143   }
144 return 0;
145 }

以上就是基於Android的OTARecovery模式升級流程。我這裏主要是梳理整個升級流程的主要,

很多地方還是寫的不夠細,望讀者理解,我認為比較核心與關鍵的地方有以下幾點吧

  • 主系統與recovery升級系統,升級消息的傳遞通過cache;
  • BCB塊中寫信息來保證斷電續升;
  • 主系統中fork子進程進行升級進程的執行,並通過pipe管道進行信息交互;
  • updater中使用命令與執行的分離,命令在updater-script中,執行在update-binary中;
    • 升級程序通過升級包帶入的,那麼核心升級流程是每次都有機會變更或者優化的,
    • 這樣就比那些將升級流程預置在系統中的要靈活的很多;

 

                                     

 

 

        長按二維碼關注【嵌入式C部落】,獲取更多編程資料及精華文章

 

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

【其他文章推薦】

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

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

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

RocketMQ ACL使用指南

目錄

@(本節目錄)

1、什麼是ACL?

ACL是access control list的簡稱,俗稱訪問控制列表。訪問控制,基本上會涉及到用戶、資源、權限、角色等概念,那在RocketMQ中上述會對應哪些對象呢?

  • 用戶
    用戶是訪問控制的基礎要素,也不難理解,RocketMQ ACL必然也會引入用戶的概念,即支持用戶名、密碼。
  • 資源
    資源,需要保護的對象,在RocketMQ中,消息發送涉及的Topic、消息消費涉及的消費組,應該進行保護,故可以抽象成資源。
  • 權限
    針對資源,能進行的操作,
  • 角色
    RocketMQ中,只定義兩種角色:是否是管理員。

另外,RocketMQ還支持按照客戶端IP進行白名單設置。

2、ACL基本流程圖

在講解如何使用ACL之前,我們先簡單看一下RocketMQ ACL的請求流程:

對於上述具體的實現,將在後續文章中重點講解,本文的目的只是希望給讀者一個大概的了解。

3、如何配置ACL

3.1 acl配置文件

acl默認的配置文件名:plain_acl.yml,需要放在${ROCKETMQ_HOME}/store/config目錄下。下面對其配置項一一介紹。

3.1.1 globalWhiteRemoteAddresses

全局白名單,其類型為數組,即支持多個配置。其支持的配置格式如下:


  • 表示不設置白名單,該條規則默認返回false。
  • “*”
    表示全部匹配,該條規則直接返回true,將會阻斷其他規則的判斷,請慎重使用。
  • 192.168.0.{100,101}
    多地址配置模式,ip地址的最後一組,使用{},大括號中多個ip地址,用英文逗號(,)隔開。
  • 192.168.1.100,192.168.2.100
    直接使用,分隔,配置多個ip地址。
  • 192.168..或192.168.100-200.10-20
    每個IP段使用 “*” 或”-“表示範圍。

3.1.2 accounts

配置用戶信息,該類型為數組類型。擁有accessKey、secretKey、whiteRemoteAddress、admin、defaultTopicPerm、defaultGroupPerm、topicPerms、groupPerms子元素。

3.1.2.1 accessKey

登錄用戶名,長度必須大於6個字符。

3.1.2.2 secretKey

登錄密碼。長度必須大於6個字符。

3.1.2.3 whiteRemoteAddress

用戶級別的IP地址白名單。其類型為一個字符串,其配置規則與globalWhiteRemoteAddresses,但只能配置一條規則。

3.1.2.4 admin

boolean類型,設置是否是admin。如下權限只有admin=true時才有權限執行。

  • UPDATE_AND_CREATE_TOPIC
    更新或創建主題。
  • UPDATE_BROKER_CONFIG
    更新Broker配置。
  • DELETE_TOPIC_IN_BROKER
    刪除主題。
  • UPDATE_AND_CREATE_SUBSCRIPTIONGROUP
    更新或創建訂閱組信息。
  • DELETE_SUBSCRIPTIONGROUP
    刪除訂閱組信息。
3.1.2.5 defaultTopicPerm

默認topic權限。該值默認為DENY(拒絕)。

3.1.2.6 defaultGroupPerm

默認消費組權限,該值默認為DENY(拒絕),建議值為SUB。

3.1.2.7 topicPerms

設置topic的權限。其類型為數組,其可選擇值在下節介紹。

3.1.2.8 groupPerms

設置消費組的權限。其類型為數組,其可選擇值在下節介紹。可以為每一消費組配置不一樣的權限。

3.2 RocketMQ ACL權限可選值

  • DENY
    拒絕。
  • PUB
    擁有發送權限。
  • SUB
    擁有訂閱權限。

3.3、權限驗證流程

上面定義了全局白名單、用戶級別的白名單,用戶級別的權限,為了更好的配置ACL權限規則,下面給出權限匹配邏輯。

4、使用示例

4.1 Broker端安裝

首先,需要在broker.conf文件中,增加參數aclEnable=true。並拷貝distribution/conf/plain_acl.yml文件到${ROCKETMQ_HOME}/conf目錄。

broker.conf的配置文件如下:

brokerClusterName = DefaultCluster
brokerName = broker-b
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
listenPort=10915
storePathRootDir=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store
storePathCommitLog=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store/commitlog
namesrvAddr=127.0.0.1:9876
autoCreateTopicEnable=false
aclEnable=true

plain_acl.yml文件內容如下:

globalWhiteRemoteAddresses:

accounts:
- accessKey: RocketMQ
  secretKey: 12345678
  whiteRemoteAddress:
  admin: false
  defaultTopicPerm: DENY
  defaultGroupPerm: SUB
  topicPerms:
  - TopicTest=PUB
  groupPerms:
  # the group should convert to retry topic
  - oms_consumer_group=DENY

- accessKey: admin
  secretKey: 12345678
  whiteRemoteAddress:
  # if it is admin, it could access all resources
  admin: true

從上面的配置可知,用戶RocketMQ只能發送TopicTest的消息,其他topic無權限發送;拒絕oms_consumer_group消費組的消息消費,其他消費組默認可消費。

4.2 消息發送端示例

public class AclProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name", getAclRPCHook());
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        for (int i = 0; i < 1; i++) {
            try {
                Message msg = new Message("TopicTest3" ,"TagA" , ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

運行效果如圖所示:

4.3 消息消費端示例

public class AclConsumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4", getAclRPCHook(),new AllocateMessageQueueAveragely());
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

發現並不沒有消費消息,符合預期。

關於RocketMQ ACL的使用就介紹到這裏了,下一篇將介紹RocketMQ ACL實現原理。

推薦閱讀:
1、

2、

3、

4、

作者介紹:
丁威,《RocketMQ技術內幕》作者,RocketMQ 社區佈道師,公眾號: 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

canvas繪製工作流之繪製節點

   上一篇我們介紹了canvas繪製工作流的大概步驟,接下來會有系列文章細緻的介紹怎麼用canvas繪製工作流;這篇文章主要介紹用canvas繪製流程節點。

  繪製前我們需要先準備一張節點圖片,例如:;好了,正題開始:

  1. html中添加canvas標籤:
<canvas id="canvasId" width = "800" height="600" style="border:1px solid black;  margin-left: 1px;"></canvas>

這裏要注意設置canvas標籤的寬度跟高度,也就是要設置畫布的寬度跟高度。

  1. 獲取畫布對象並初始化畫布參數
var _canvas= document.getElementById(“canvasId”);

var _height = _canvas.height;//獲取畫布高度

var _width = _canvas.width;//獲取畫布寬度

Var ctx =_canvas.getContext('2d');

//畫個畫布大小的長方形,目的是為了有個好看的小邊框框
ctx.clearRect(0, 0, _width, _height);

/*繪製畫布的背景線*/
//設置線寬
ctx.lineWidth  = 0.1;
//繪製縱向背景線
for(var i = 1; i < _width / 15; i++) {
  ctx.beginPath();
  ctx.moveTo(i * 15, 0);
  ctx.lineTo(i * 15, _height);
  ctx.stroke();
}
//繪製橫向背景線
for(var i = 1; i < _ height / 15; i++) {
  ctx.beginPath();
  ctx.moveTo(0, i * 15);
  ctx.lineTo(_width, i * 15);
  ctx.stroke();
}

 

繪製完效果如圖:

  1. 獲取節點圖片對象
     //創建新的圖片對象
    
     var _img = new Image();
    
      //指定圖片的URL
    
     _img.src="node.png";

            我這裏為了舉個例子直接創建圖片對象,實際繪製過程中可以直接獲取圖片對象,因為動態創建圖片對象是有個圖片加載的時間。

  1. 繪製節點圖片
ctx.drawImage(_img,_x,_y,_imgWidth, _imgHeight);

    這裏_img是上面獲取到的圖片對象,_x是圖片要繪製在畫布中的x坐標,_y是圖片要繪製在畫布中的_y坐標,_imgWidth是要將圖片繪製的寬度,_imgHeight是要將 圖片繪製的寬度。

    實際應用過程中,一般都會當去鼠標的位置當做x坐標跟y坐標,具體的後面文章會介紹到。

       繪製的效果圖:

   節點下面的文字後面文章會詳細講到怎麼繪製。

  每天get一點點,每天成長一點點,好了,今天就到這裏。

        

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!