Kubernetes日誌的6個最佳實踐

本文轉自Rancher Labs

Kubernetes可以幫助管理部署在Pod中的上百個容器的生命周期。它是高度分佈式的並且各個部分是動態的。一個已經實現的Kubernetes環境通常涉及帶有集群和節點的幾個系統,這些系統託管着幾百個容器,而這些容器不斷地基於工作負載啟動、毀滅。

當在Kubernetes中處理大量的容器化應用和工作負載時,主動進行監控和調試錯誤十分重要。在容器、節點或集群級別,這些錯誤都能在容器中看到。Kubernetes的日誌機制是一個十分重要的組件,可以用來管理和監控服務以及基礎設施。在Kubernetes中,日誌可以讓你跟蹤錯誤甚至可以調整託管應用程序的容器的性能。

配置stdout(標準輸出)和stderr(標準錯誤)數據流

圖片來源:kubernetes.io

第一步是理解日誌是如何生成的。通過Kubernetes,日誌會被發送到兩個數據流——stdout和stderr。這些數據流將寫入JSON文件,並且此過程由Kubernetes內部處理。你可以配置將哪個日誌發送到哪個數據流中。而一個最佳實踐的建議是將所有應用程序日誌都發送到stdout並且所有錯誤日誌都發送到stderr。

決定是否使用Sidecar模型

Kubernetes建議使用sidecar容器來收集日誌。在這一方法中,每個應用程序容器將有一個鄰近的“streaming容器”,該容器將會將所有日誌流傳輸到stdout和stderr。Sidecar模型可以幫助避免在節點級別公開日誌,並且它可以讓你控制容器級別的日誌。

然而,這一模型的問題是它能夠適用於小容量的日誌記錄,如果面對大規模的日誌記錄,可能會造成大量資源被佔用。因此,你需要為每個正在運行的應用程序容器單獨運行一個日誌容器。在Kubernetes文檔中,將sidecar模型形容為“幾乎沒有很大的開銷”。需要由你決定是否嘗試這一模型並在選擇它之前查看它所消耗的資源類型。

替代方法是使用日誌代理,該代理在節點級別收集日誌。這樣可以減少開銷,並確保安全地處理日誌。Fluentd已成為大規模聚合Kubernetes日誌的最佳選擇。它充當Kubernetes與你要使用Kubernetes日誌的任意數量的端點之間的橋樑。你也可以選擇像Rancher這樣的Kubernetes管理平台,在應用商店已經集成了Fluentd,無需從頭開始安裝配置。

確定Fluentd可以更好地匯總和路由日誌數據后,下一步就是確定如何存儲和分析日誌數據。

選擇日誌分析工具:EFK或專用日誌記錄

傳統上,對於以本地服務器為中心的系統,應用程序日誌存儲在系統中的日誌文件中。這些文件可以在定義的位置看到,也可以移動到中央服務器。但是對於Kubernetes,所有日誌都發送到磁盤上/var/log的JSON文件中。這種類型的日誌聚合併不安全,因為節點中的Pod可以是臨時的也可以是短暫的。刪除Pod時,日誌文件將丟失。如果你需要嘗試對部分日誌數據丟失進行故障排除時,這可能很難。

Kubernetes官方推薦使用兩個選項:將所有日誌發送到Elasticsearch,或使用你選擇的第三方日誌記錄工具。同樣,這裏存在一個潛在的選擇。採用Elasticsearch路線意味着你需要購買一個完整的堆棧,即EFK堆棧,包括Elasticsearch、Fluentd和Kibana。每個工具都有其自己的作用。如上所述,Fluentd可以聚合和路由日誌。Elasticsearch是分析原始日誌數據並提供可讀輸出的強大平台。Kibana是一種開源數據可視化工具,可以從你的日誌數據創建漂亮的定製dashboard。這是一個完全開源的堆棧,是使用Kubernetes進行日誌記錄的強大解決方案。

儘管如此,有些事情仍然需要牢記。Elasticsearch除了由名為Elastic的組織構建和維護,還有龐大的開源社區開發人員為其做貢獻。儘管經過大量的實踐檢驗,它可以快速、強大地處理大規模數據查詢,但在大規模操作時可能會出現一些問題。如果採用的是自我管理(Self-managed)的Elasticsearch,那麼需要有人了解如何構建大規模平台。

替代方案是使用基於雲的日誌分析工具來存儲和分析Kubernetes日誌。諸如Sumo Logic和Splunk等工具都是很好的例子。其中一些工具利用Fluentd來將日誌路由到他們平台,而另一些可能有它們自己的自定義日誌代理,該代理位於Kubernetes中的節點級別。這些工具的設置十分簡單,並且使用這些工具可以花費最少的時間從零搭建一個可以查看日誌的dashboard。

使用RBAC控制對日誌的訪問

在Kubernetes中身份驗證機制使用的是基於角色訪問控制(RBAC)以驗證一個用戶的訪問和系統權限。根據用戶是否具有特權(authorization.k8s.io/decision )並向用戶授予原因(authorization.k8s.io/reason ),對在操作期間生成的審核日誌進行註釋。默認情況下,審核日誌未激活。建議激活它以跟蹤身份驗證問題,並可以使用kubectl進行設置。

保持日誌格式一致

Kubernetes日誌由Kubernetes架構中不同的部分生成。這些聚合的日誌應該格式一致,以便諸如Fluentd或FluentBit的日誌聚合工具更易於處理它們。例如,當配置stdout和stderr或使用Fluentd分配標籤和元數據時,需要牢記這一點。這種結構化日誌提供給Elasticsearch之後,可以減少日誌分析期間的延遲。

在日誌收集守護進程上設置資源限制

由於生成了大量日誌,因此很難在集群級別上管理日誌。DaemonSet在Kubernetes中的使用方式與Linux類似。它在後台運行以執行特定任務。Fluentd和filebeat是Kubernetes支持的用於日誌收集的兩個守護程序。我們必須為每個守護程序設置資源限制,以便根據可用的系統資源來優化日誌文件的收集。

結 論

Kubernetes包含多個層和組件,因此對其進行良好地監控和跟蹤能夠讓我們在面對故障時從容不迫。Kubernetes鼓勵使用無縫集成的外部“Kubernetes原生”工具進行日誌記錄,從而使管理員更輕鬆地獲取日誌。文章中提到的實踐對於擁有一個健壯的日誌記錄體繫結構很重要,該體繫結構在任何情況下都可以正常工作。它們以優化的方式消耗計算資源,並保持Kubernetes環境的安全性和高性能。

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

※回頭車貨運收費標準

NetAnalyzer筆記 之 十四 NetAnalyzer 6.0 的使用方法 — 3.協議分析與統計

數據分析

完成了數據的抓取,那麼接下來就是NetAnalyzer的第二個重點部分了,協議分析作為整個軟件的核心之一,在最新的NetAnalyzer中已經得到了巨大的提升。NetAnalyzer中協議分析分為單數據包分析,和聯合分析兩種分析方式,對於聯合分析會根據不同的協議特性進行形成不同的分析方案,目前支持傳輸協議(TCP/UDP)協議分析, HTTP協議分析。在數據統計部分部分還增加了針對ARP協議的圖形化分析。對於協議分析,需要了解相關的網絡知識或是有相關專業背景支持。

單數據包分析,在獲取到數據包后,軟件工作界面數據包列表框中會显示所獲取的所用數據包,並且對這次數據做了一些簡單的分析,我們可以憑藉這些數據簡單判斷所對應的的數據包類型。

 

數據包列表

當我們選中一行,即選中一個數據包,我們可以看到對該數據包詳細的數據分析信息,並一樹狀結構樹呈現出來,並在右側显示該數據包原始信息。當我們選中協議樹中一個字段時,右側的數據就會定位到當前字端所分析數據的位置。

 

數據分析

然後通過對應的協議格式進行匹配與分析,如這部分的IP協議。

 

IPv4協議格式

需要注意的是,NetAnalyzer目前對於選中的字段只能精確到字節層次,對於一些協議,其中一個字節可能包含了多個字段,或是跨字節的字段,則會選擇全部的字節數據,比如IPv6協議。

 

IPv6協議格式

其中的版本字段只佔用了4bit(1字節為8bit),通信類型佔了8bit 也就是1字節,但是因為其中前面部分使用了版本字段所在字節後面的4bit,所以改字段為一個典型的跨字節字段,同樣流標籤字段使用了20bit,佔用第二個字節的4bit加上後面自身的2個字節(16bit)。

 

解析后的IPv6數據

對於類型的字段因為NetAnalyzer使用十六進制显示數據,並不能清晰表達bit層次的信息所以當選定字段后默認選中改字段所在的字節,如點擊版本選中方式如下,

 

IPv6版本信息

選中通信類型和流標籤則呈現方式如下。

 

通信類型和流標籤共用數據

數據分析標籤

雖然NetAnalyzer盡可能多分析每個數據包所包含的信息,但是依舊存在很多數據需要我們手動去解析。所以軟件增加了數據標籤。

 

數據分析

數據標籤頁點擊 显示 按鈕 就可以打開數據轉換窗口,當然也可以在常規轉換中點擊任意功能可以打開轉換窗口

 

轉換窗口管理

 

關閉按鈕為關閉轉換窗口,清空則是清空當前窗口內的數據。

點擊清空按鈕,則清空轉換信息。

 

常規轉換工具

NetAnalyzer中提供了一部分簡單的轉換功能,這些功能只有在載荷數據被選中的情況的才可以啟用,

如點擊二進制按鈕,則對所選的數據轉換為對應的二進制字符串。如下圖所示。

 

常規數據轉換窗口

除了一些簡單的轉換功能,還集成了MangoScript擴展方式和插件擴展方式(無可用插件的

時候不显示)的轉換。

 

擴展MangoScript的解析

如下面通過MangoScript針對某即時通信軟件的數據分析。

針對於MangoScript和插件兩種方式的轉換,將會在在《NetAnalyzer使用說明書 二 擴展與開發》中詳細說明,此處不再贅述。

 

 

定位轉換功能需要配合常規轉換進行使用,有時候我們確定某個字節會在一個確定的位置出現,比如IP地址字段,我們選中該位置,位置字段就會出現一串代碼 (10,1) [26]-4

(x,y)[offset] – length

 x: 十六進制編輯器水平方向的偏移量

 y :   十六進制編輯器垂直方向的偏移量

offset : 字節偏移量,offset = y * 16 + x

length :  當前選擇的數據長度

 

數據轉換

所以代碼 (10,1) [26]-4 確定了當前IP地址的位置,此時點擊 常規轉換 -> IPv4地址 則會在模式中記錄當前的轉換模式,然後點擊定位轉換,就會在當前數據包列表中針對每個數據包這個位置執行定位操作,這對於尋找所需要的數據非常重要。

 

選擇了IPv4轉換

 

執行定位轉換

對於MangoScript和插件擴展依然支持定位轉換。

 

區塊複製,主要是對一些已經選中的字節進行複製轉為代碼,字節數組,以及保存的功能,以及數據做手動分析,腳本分析以及自定義轉換等,後續將會說明,此處不再詳細介紹。

 

數據塊操作

 

字節定位,與定位轉換類似,但是字節定位主要是用來在數據包列表中查找相同位置出現相同字節序列的數據包。算作一個查找功能。

 

字節定位

分析標籤

分析標籤下個功能依託於數據包列表,分別有載荷數據提取,數據包標記,編碼轉換,數據查找,統計等相關功能,是聯合分析的主要功能,下面將會着重對一下功能進行說明。

 

數據分析標籤

TCP/UDP協議分析   前面介紹的都是基於單包的數據分析,而在協議分析中,我們大部分分析的數據都是依託於TCP/UDP的長連接數據,這部分數據的特點就是有多個數據包通過tcp或udp相關協議完成數據重組后才可以使用(基於udp的連接數據可能不是很嚴格)。

NetAnalyzer 除了提供基於單包的數據分析,更提供了基於連接數據的分析,而分析出來的數據不僅僅是在窗口上呈現一堆亂碼,更可以通過DocBar將獲取的數據提取出來進行使用。

開始 標籤最後一部分就是基於長連接的分析。點擊TCP/UDP 按鈕

 

基於TCP/UDP載荷數據查看

此時NetAnalyzer便會切換到載荷數據模式(該過程可以通過配置,使用獨立窗口打開)。在該模式下會打開專有的載荷數據菜單,數據區域也會變為對於載荷數據的分析,這裏先介紹一個NetAnalyzer中的DocBar工具,如下圖

 

DocBar

在文本模式下,分析載荷數據會显示該工具條,該工具條會提供針對當前數據塊的各種操作,當然在不動情況下,显示的工具和數量,都有所不同,下面是對當前各個功能的說明。

l   對當前數據塊進行摺疊

l   選中當前的分析數據

l   保存當前原始數據

l   查看原始數據(bytes數據)

l   MangScript解析數據

l   手動測試數據

 

對於其他情況下的工具在這裏不會一一介紹,但是碰到的時候會有說明,並且隨着後續功能點的增加,DocBar可能會有更多的功能添加進來。

 

tcp/udp 的分析分為 文本模式原始模式 ,文本模式主要是用於分析載荷數據為文本的數據,我們可以通過下面兩種方式更改文本編碼方式,分析數據。

文本模式下,呈現方式如下:

 

查看載荷數據

原始模式分析如下,可用通過TCP/UDP的下拉菜單命令 字節數據 切換為原始數據

 

 

字節查詢方式

 

字節方式呈現

對於在該功能下針對TCP的所有數據都已經進行過TCP重組,所以最終分析完成的數據並不是按照數據包方式做簡單呈現就可以的,都會做數據的篩查與整理。如果需要單包分析的使用者需要注意一下。

 

HTTP數據分析 http作為最有網絡代表意義的協議,NetAnalyzer提供了更加完善的分析,http基於tcp協議,所以數據還原等都建立在tcp數據還原的基礎之上。通過http分析,我們可以還原很多有意義的數據,如獲取到Http所傳輸的的html、js、css數據文件,還可以獲取到基於http協議分析得到的圖片,文件等信息,如下圖分別為還原后的圖片和zip壓縮包。

 

http方式分析出的圖片

 

http方式分析出的文件

對於常規的字符串或圖片可以直接在NetAnalyzer呈現,但是對於其他類型的文件,如視頻、音樂、以及上面提到的zip壓縮包文件,在在NetAnalyzer會簡單显示為二進制數據,該數據如果過長,則會截斷显示,但是在後面會加入【全部數據】下鑽選項,當點擊該數據后則會打開原始數據對話框,並且會完整显示當前的數據,如下圖所示。

 

查看原始數據

原始數據對話框中,提供了簡單的數據另存為和數據識別相關的功能。

 

原始數據保存

保存 保存當前窗口中的數據為一個文件。

保存選擇數據 是當選擇對話框中其中的一段數據保存為文件,有時候數據可能存在偏差,或者我們需要提取選定的數據保存為文件,可以通過下拉保存選定的數據進行保存。

數據識別功能。

轉為… 則是將當前的數據轉到編碼轉換工具中進行進一步分析。

自動識別 為了更加快速的實現數據提取,NetAnalyzer增加了數據識別模塊,通過整理不同文件的頭部或尾部字節形成數據識別特徵,當進行自動識別的時候,可以快速定位字節。

 

文件識別

添加特徵 將選定的指定字節添加為文件識別頭,並且添加相關信息,形成一個特徵。

 

添加文件識別

識別管理 管理特徵庫,在後續將詳細介紹該功能點。

 

載荷數據分析出的文件

除了使用常規的識別方式,在載荷數據提取中也加入了數據識別功能。在使用的時候點擊數據識別就可以在下方显示被識別到的數據類型,有時候可能會存在多個類型和誤識別的情況,使用的時候請務必注意。

有時候通過HTTP協議還原部分二進制數據,如下面還原ZIP文件,文檔會以二進制數據呈現,而我們可以通過0x50 0x4B(PK)推斷出該文件很有可能是zip文件 ,所以我們點擊全部數據 ,打開原始數據窗口,這部分數據正好是zip的全部數據。

   

保存的zip文件內容

此時點擊將當前數據保存為zip文件。減壓就可以看到對應的文件內容。

 

在載荷數據模式下,菜單會自動切換為,載荷模式菜單

 

載荷數據標籤

該菜單下提供了很多常用的字符串轉換工具

 

格式轉換工具

如下面通過通過Cookie格式化,格式化了http頭中的cookie字段

 

Cookie格式化

需要注意的是使用這些字段首先需要選中被轉換的文本,然後點擊需對應的功能項。其中如果點擊轉換為…,則啟動NetAnalyzer附帶的編碼轉換工具,進行集中處理。

 

編碼轉換工具

針對html字符串數據,還提供了過濾標籤和HTML預覽功能,因為該部分功能都很類型,且使用簡單,用戶自行嘗試使用即可。

 

 

時序圖 在數據分析中,除了對於數據本身的分析之外,有時候我們還要去評測一些數據質量等方面的內容。並且可以通過圖像化的方式表現出來。

 

TCP時序圖分析

時序圖模擬TCP/UDP在數據網絡中的數據傳輸過程,還原網絡通信場景,如該圖可以完整的反映TCP三次握手以及斷開連接四次揮手的情景。可以作為對當前分析數據從另外一個方面的反饋,更具有參考意義。

點擊

 

時序圖選項

就可以看到針對於當前tcp/udp 數據交互的情況。

 

 

數據標記

在分析標籤下面,有標記功能,實現對當前採集會話數據連接的進行快速識別。

 

數據標記

NetAnalyzer提供了四中顏色對數據包鏈接進行區分。

如TCP數據包,就會通過源IP地址+源端口地址+目標IP地址+目標端口 作為一個特徵來進行識別,此處的源和目標具有相對性。

注*  ctrl+鼠標左鍵 可以實現對數據會話的快速標記 顏色為紅色

 

標記完成的數據

通過點擊清理標記,可還原數據。

 

 

數據包查找 

 

數據包查找

在數據包列表模式下使用Ctrl+F即可以打開數據包查找功能。

該功能主要是實現快速查找數據包的功能,可以通過編號,協議,地址(mac/ip),端口,關鍵字等五種方式查找數據包。還可以通過數據列表導航按鈕進行數據包列表瀏覽。

 

 

編碼方式

在通過TCP/UDP 或HTTP 功能還原數據的時候,有時候會出現亂碼,尤其是對非英文字符。在HTTP協議中通常都會在頭部信息中攜帶編碼方法,通過提取就可以獲取到編碼方式,但是仍然後部分服務並不提供編碼字段,這時候就需要我們通過手動切換,來嘗試還原相關信息。

通過菜單欄或者是狀態欄都可以對編碼方案進行切換

 

字符編碼

 

狀態欄字符編碼

這裏需要注意的是如果http頭部包含了編碼方式,則使用頭部提供的編碼方式。

 

 

數據統計

目前NetAnalyzer显示了大量的統計方式,涵蓋了數據報表、流量分析、主機通信矩,傳輸報告、ARP報告等多種統計方式。

 

數據報表

 

報表信息

對當前捕獲的數據表中的數據進行統計與歸類。呈現方式如有圖所示。

 

報表內容

包含一些基本信息,數據量與時間直線圖,數據量佔比,關係圖等信息

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

悅翔V7的小弟,據說比大哥操控還棒!

動力總成悅翔V3搭載了1。4L自然吸氣發動機+5擋手動變速箱,最大輸出101匹馬力和135牛米。實際表現來看,悅翔V3不同於一般小排量的車會把油門調得較為靈敏,它反而會調得偏肉,起步並不是很利索,但是3擋以下提速還是很給力的,只是滿載時會較為吃力,上個緩坡也不是很帶勁。

一說起長安,大家首先想到的肯定是CS75。但是除了SUV車型以外,長安還有一些不錯的小車,例如悅翔V3。可能有少部分讀者聽過這台車,那其產品力到底如何呢?

大哥悅翔V7已經獲得不少人的認可,但是小弟悅翔V3基本處於默默無聞的狀態。其實,悅翔V3的整個設計還是有點小V7的感覺。

長安汽車-悅翔V3

指導價:4.69-5.39萬

外觀設計

悅翔V3的車身尺寸為4200*1650*1465mm,軸距為2410mm。前臉來看,跟悅翔V7差別不大。發動機艙蓋上的兩條弧線剛強有力,兩顆大燈也顯得炯炯有神,中網上的鍍鉻裝飾條,點綴得剛好,不顯俗氣。

側面的話,車身還是顯得比較緊湊,只是尾部看起來會有點臃腫。同時輪胎的尺寸也偏小了一點。

相比起車頭,尾部會顯得有點平庸,僅有車牌上方的鍍鉻裝飾條略微點綴一下。

內飾設計

車廂的用料在這個價位來說算是可以的,中控台的設計中規中矩。烤黑鋼琴漆的面板算是為數不多的亮點所在。三副式的方向盤上面沒有任何按鈕,看起來很乾凈,樣式也不錯,給人感覺很好。

動力總成

悅翔V3搭載了1.4L自然吸氣發動機+5擋手動變速箱,最大輸出101匹馬力和135牛米。實際表現來看,悅翔V3不同於一般小排量的車會把油門調得較為靈敏,它反而會調得偏肉,起步並不是很利索,但是3擋以下提速還是很給力的,只是滿載時會較為吃力,上個緩坡也不是很帶勁。

底盤表現

悅翔V3的前懸架為麥弗遜式獨立懸架,而後懸架則為多連桿獨立懸架。這在同級別車型中是比較罕見的。實際表現來看,底盤的調校較為偏向於操控,過彎時的側傾不大。但是面對顛簸時會有種硬碰硬的感覺。

空間表現

受制於2410mm的軸距,後排空間並不充裕。身高為182cm的體驗者坐於後排,腿部僅有4指的空間,而頭部則僅為三指。雖然後排中央的拱起不高而且平整,但是受制於偏短的腿部空間,坐於後排中部的乘客也只能稍微將就一下。

油耗表現

多位車主反映的悅翔V3百公里綜合油耗為6.2L,這個数字是相當省的,也是許多車主購買它的原因。

配置分析

悅翔V3隻有三個車型可以選擇,但是每個車型又分國四與國五版本,中間價差1000元。這三個車型中,我會推薦中配的手動溫馨型,它比低配的手動美滿型貴了3000元,但是卻多出了主副駕駛座的安全氣囊和后駐車雷達。這兩項配置都很重要,儘管車子本身便宜,但是這種關乎生命安全的配置還是不能省。

編者總結:

悅翔V3可以說一直活在大哥悅翔V7的陰影下,同時受制於自身定位的問題,在市場上的表現也較為一般,但同級罕有的後輪獨立懸架賦予了它同級出色的操控性。如果是一台個人用車的話,悅翔V3可以滿足你對操控的幻想。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

亞馬遜6月毀林創紀錄 巴西太空署總督導下台

摘錄自2020年7月14日中央社報導

巴西政府免去國家太空署(INPE)地球觀測站總督導溫海斯(Lubia Vinhas)的職位,免職書由科技部長龐特斯(Marcos Pontes)簽署,刊登在今(13日)的國家公報。

溫海斯接受環球電視(TV Globo)訪問時說,她通過高考進入國家太空署服務23年了,所以就算被免去管理職位,也將繼續待在機關。溫海斯也表示不曉得為何被免職,說自己是通過國家公報才知道被調職。

上週巴西國家太空署公布報告,今年6月份的毀林警報數量創下2015年以來當月最高紀錄。今年上半年累計的警報顯示,亞馬遜遭破壞面積達3069.57平方公里,與2019年上半年相比增加25%。僅6月的毀林警報範圍就達1034.4平方公里。

溫海斯遭免職後,國際環保團體綠色和平組織(Greenpeace)發表聲明表示,基於巴西總統波索納洛(Jair Bolsonaro)政府先前的決定,溫海斯被免職並不意外,只是再次顯示「巴西政府是真理的敵人」。

巴西經濟部長葛德斯(Paulo Guedes)今天在經濟合作暨發展組織(OECD)線上會議中表示,巴西願意配合和幫助保護環境;如果巴西的環境政策出現錯誤或過度行為,也將予以糾正。

生物多樣性
國際新聞
巴西
亞馬遜雨林
森林

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

可愛的負擔…美國爆發「花栗鼠之亂」 到處打洞居民快瘋了

摘錄自2020年7月19日自由時報報導

據《福斯新聞》報導,緬因州內陸漁業與野生動植物小型哺乳動物專家韋伯(Shevenell Webb)表示,去(2019)年秋天產出大量的橡實,讓花栗鼠在春季繁衍後代時在地面上到處都可以找到食物,就這樣造成如今的花栗鼠嬰兒潮。韋伯說,花栗鼠真的很可愛,但同時也是破壞狂,不僅會挖洞破壞草坪和花園,有時還會溜進屋內造反。

佛蒙特州魚類和野生動物部門野生動植物多樣性主任帕倫(Steven Parren)則說,他監控的地區有太多橡實,以至於囓齒動物無法在冬天把它們全都藏起來,到了今(2020)年春天地面上還留有很多橡實,除了花栗鼠之外也造成松鼠、兔子等族群增加。

不過,人們不用太擔心這次的花栗鼠狂潮,因為小型哺乳類動物族群本來就很容易出現物種激增的事件,隨後就會迎來一陣消寂,更何況花栗鼠很容易成為貓頭鷹、老鷹、蛇類、狐狸和浣熊的獵物,野生花栗鼠平均只有3年壽命,比最高壽命少了許多。

※ 本文與 行政院農業委員會 林務局   合作刊登

生物多樣性
國際新聞
美國
食物鏈
生態平衡
處變不驚──與野生動物相遇
人與動物衝突事件簿

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

RocketMQ系列(四)順序消費

折騰了好長時間才寫這篇文章,順序消費,看上去挺好理解的,就是消費的時候按照隊列中的順序一個一個消費;而併發消費,則是消費者同時從隊列中取消息,同時消費,沒有先後順序。RocketMQ也有這兩種方式的實現,但是在實踐的過程中,就是不能順序消費,好不容易能夠實現順序消費了,發現採用併發消費的方式,消費的結果也是順序的,頓時就蒙圈了,到底怎麼回事?哪裡出了問題?百思不得其解。

經過多次調試,查看資料,debug跟蹤程序,最後終於搞清楚了,但是又不知道怎麼去寫這篇文章,是按部就班的講原理,講如何配置到最後實現,還是按照我的調試過程去寫呢?我覺得還是按照我的調試過程去寫這篇文章吧,因為我的調成過程應該和大多數人的理解思路是一致的,大家也更容易重視。

環境回顧

我們先來回顧一下前面搭建的RocketMQ的環境,這對於我們理解RocketMQ的順序消費是至關重要的。我們的RocketMQ環境是一個兩主兩從的異步集群,其中有兩個broker,broker-a和broker-b,另外,我們創建了兩個Topic,“cluster-topic”,這個Topic我們在創建的時候指定的是集群,也就是說我們發送消息的時候,如果Topic指定為“cluster-topic”,那麼這個消息應該在broker-a和broker-b之間負載;另外創建的一個Topic是“broker-a-topic”,這個Topic我們在創建的時候指定的是broker-a,當我們發送這個Topic的消息時,這個消息只會在broker-a當中,不會出現在broker-b中。

和大家羅嗦了這麼多,大家只要記住,我們的環境中有兩個broker,“broker-a”和“broker-b”,有兩個Topic,“cluster-topic”和“broker-a-topic”就可以了。

cluster-topic可以順序消費嗎

我們發送的消息,如果指定Topic為“cluster-topic”,那麼這種消息將在broker-a和broker-b直接負載,這種情況能夠做到順序消費嗎?我們試驗一下,

消費端的代碼如下:

@Bean(name = "pushConsumerOrderly", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer pushConsumerOrderly() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("pushConsumerOrderly");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("cluster-topic","*");
    consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
        Random random = new Random();
        try {
            Thread.sleep(random.nextInt(5) * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (MessageExt msg : msgs) {
            System.out.println(new String(msg.getBody()));
        }
        return ConsumeOrderlyStatus.SUCCESS;
    });
    return consumer;
}
  • 消費者組的名稱,連接的NameServer,訂閱的Topic,這裏就不多說了;
  • 再來看一下註冊的消息監聽器,它是MessageListenerOrderly,順序消費,具體實現里我們打印出了消息體的內容,最後返回消費成功ConsumeOrderlyStatus.SUCCESS。
  • 重點看一下打印語句之前的隨機休眠,這是非常重要的一步,它可以驗證消息是否是順序消費的,如果消費者是消費完一個消息以後,再去取下一個消息,那麼順序是沒有問題,但是如果消費者是併發地取消息,但是每個消費者的休眠時間又不一樣,那麼打印出來的就是亂序

生產端我們採用同步發送的方式,代碼如下:

@Test
public void producerTest() throws Exception {

    for (int i = 0;i<5;i++) {
        Message message = new Message();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is simpleMQ,my NO is "+i+"---"+new Date()).getBytes());

        SendResult sendResult = defaultMQProducer.send(message);
        System.out.println("i=" + i);
        System.out.println("BrokerName:" + sendResult.getMessageQueue().getBrokerName());
    }
}

和前面一樣,我們發送5個消息,並且打印出i的值和broker的名稱,發送消息的順序是0,1,2,3,4,發送完成后,我們觀察一下消費端的日誌,如果順序也是0,1,2,3,4,那麼就是順序消費。我們運行一下,看看結果吧。

生產者的發送日誌如下:

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-a
i=3
BrokerName:broker-a
i=4
BrokerName:broker-b

發送5個消息,其中4個在broker-a,1個在broker-b。再來看看消費端的日誌:

this is simpleMQ,my NO is 3---Wed Jun 10 13:48:57 CST 2020
this is simpleMQ,my NO is 2---Wed Jun 10 13:48:57 CST 2020
this is simpleMQ,my NO is 4---Wed Jun 10 13:48:57 CST 2020
this is simpleMQ,my NO is 1---Wed Jun 10 13:48:57 CST 2020
this is simpleMQ,my NO is 0---Wed Jun 10 13:48:56 CST 2020

順序是亂的?怎麼回事?說明消費者在並不是一個消費完再去消費另一個,而是拉取了一個消息以後,並沒有消費完就去拉取下一個消息了,那這不是併發消費嗎?可是我們程序中設置的是順序消費啊。這裏我們就開始懷疑是broker的問題,難道是因為兩個broker引起的?順序消費只能在一個broker里才能實現嗎?那我們使用broker-a-topic這個試一下吧。

broker-a-topic可以順序消費嗎?

我們把上面的程序稍作修改,只把訂閱的Topic和發送消息時消息的Topic改為broker-a-topic即可。代碼在這裏就不給大家重複寫了,重啟一下程序,發送消息看看日誌吧。

生產者端的日誌如下:

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-a
i=3
BrokerName:broker-a
i=4
BrokerName:broker-a

我們看到5個消息都發送到了broker-a中,再來看看消費端的日誌,

this is simpleMQ,my NO is 0---Wed Jun 10 14:00:28 CST 2020
this is simpleMQ,my NO is 2---Wed Jun 10 14:00:29 CST 2020
this is simpleMQ,my NO is 3---Wed Jun 10 14:00:29 CST 2020
this is simpleMQ,my NO is 4---Wed Jun 10 14:00:29 CST 2020
this is simpleMQ,my NO is 1---Wed Jun 10 14:00:29 CST 2020

消費的順序還是亂的,這是怎麼回事?消息都在broker-a中了,為什麼消費時順序還是亂的?程序有問題嗎?review了好幾遍沒有發現問題。

問題排查

問題卡在這個地方,卡了好長時間,最後在官網的示例中發現,它在發送消息時,使用了一個MessageQueueSelector,我們也實現一下試試吧,改造一下發送端的程序,如下:

SendResult sendResult = defaultMQProducer.send(message, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        return mqs.get(0);
    }
},i);

在發送的方法中,我們實現了MessageQueueSelector接口中的select方法,這個方法有3個參數,mq的集合,發送的消息msg,和我們傳入的參數,這個參數就是最後的那個變量i,大家不要漏了。這個select方法需要返回的是MessageQueue,也就是mqs變量中的一個,那麼mqs中有多少個MessageQueue呢?我們猜測是2個,因為我們只有broker-a和broker-b,到底是不是呢?我們打斷點看一下,

MessageQueue有8個,並且brokerName都是broker-a,原來Broker和MessageQueue不是相同的概念,之前我們都理解錯了。我們可以用下面的方式理解,

集群 ——–》 Broker ————》 MessageQueue

一個RocketMQ集群里可以有多個Broker,一個Broker里可以有多個MessageQueue,默認是8個。

那現在對於順序消費,就有了正確的理解了,順序消費是只在一個MessageQueue內,順序消費,我們驗證一下吧,先看看發送端的日誌,

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-a
i=3
BrokerName:broker-a
i=4
BrokerName:broker-a

5個消息都發送到了broker-a中,通過前面的改造程序,這5個消息應該都是在MessageQueue-0當中,再來看看消費端的日誌,

this is simpleMQ,my NO is 0---Wed Jun 10 14:21:40 CST 2020
this is simpleMQ,my NO is 1---Wed Jun 10 14:21:41 CST 2020
this is simpleMQ,my NO is 2---Wed Jun 10 14:21:41 CST 2020
this is simpleMQ,my NO is 3---Wed Jun 10 14:21:41 CST 2020
this is simpleMQ,my NO is 4---Wed Jun 10 14:21:41 CST 2020

這回是順序消費了,每一個消費者都是等前面的消息消費完以後,才去消費下一個消息,這就完全解釋的通了,我們再把消費端改成併發消費看看,如下:

@Bean(name = "pushConsumerOrderly", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer pushConsumerOrderly() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("pushConsumerOrderly");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("broker-a-topic","*");
    consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
        Random random = new Random();
        try {
            Thread.sleep(random.nextInt(5) * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (MessageExt msg : msgs) {
            System.out.println(new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });
    return consumer;
}

這回使用的是併發消費,我們再看看結果,

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-a
i=3
BrokerName:broker-a
i=4
BrokerName:broker-a

5個消息都在broker-a中,並且知道它們都在同一個MessageQueue中,再看看消費端,

this is simpleMQ,my NO is 1---Wed Jun 10 14:28:00 CST 2020
this is simpleMQ,my NO is 0---Wed Jun 10 14:28:00 CST 2020
this is simpleMQ,my NO is 3---Wed Jun 10 14:28:00 CST 2020
this is simpleMQ,my NO is 2---Wed Jun 10 14:28:00 CST 2020
this is simpleMQ,my NO is 4---Wed Jun 10 14:28:00 CST 2020

是亂序的,說明消費者是併發的消費這些消息的,即使它們在同一個MessageQueue中。

總結

好了,到這裏終於把順序消費搞明白了,其中的關鍵就是Broker中還有多個MessageQueue,同一個MessageQueue中的消息才能順序消費。

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

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

研究:全球礁鯊數量銳減 部分海域已絕跡

摘錄自2020年7月22日中央社報導

22日發布在科學期刊「自然」(Nature)的一份調查,首度全面了解全球哪裡的礁鯊實際上已滅絕。這份調查為期四年,在近60個國家超過370個暗礁海域進行研究。

加拿大達爾豪希大學(Dalhousie University)副教授麥克尼爾(Aaron MacNeil)表示:「我們原本預期……地球上每個暗礁海域應該都有鯊魚出沒,結果發現我們調查的海域中,有20%沒有任何鯊魚,這令人非常擔心。」

調查顯示,在卡達、印度、越南及肯亞等八個國家的暗礁海域,完全找不到鯊魚。這不代表這些國家的海域沒有鯊魚,但證明當地暗礁海域的鯊魚數量少得可憐。這表示礁鯊在當地生態系統已失去任何角色,也就是牠們已經功能性滅絕。

研究指出,破壞性的捕魚活動最可能是礁鯊數量大減罪魁禍首。「使用流刺網和延繩捕魚,對原本數量相對豐沛的礁鯊帶來最大負面影響。」

生物多樣性
物種保育
土地利用
農林漁牧業
國際新聞
全球
鯊魚
暗礁
滅絕
捕魚
生態系統
流刺網
延繩釣

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

新基建下,智慧交通發展新規劃:智慧隧道監控可視化系統

前言 隨着當代經濟的發展,交通環境日益緊張,加上山區地區的交通運輸的需求,隧道的交通建設開發方興未艾。
隧道交通的規劃越來越完備,而對於隧道內監控管理維護卻顯得有些不足。而
工業4.0的崛起,逐步進入了智能化的新時代,伴隨着
工業互聯網的新興力量,工控可視化系統應運而生,不僅能起到日常的監控管理維護,在發現事故或險情時能第一時間採取
應急預案;還能通過實時數據的採集反饋,遠程操控設備運行以及預測設備的優良性能,從而達到更立體更全面的工控系統的運行。
HT for Web
 不止自主研發了強大的基於 HTML5 的 2D、3D 渲染引擎,為可視化提供了豐富的展示效果。介於 2D 組態和 3D 組態上,Hightopo(以下簡稱 HT )的 HT for Web 產品上的有着豐富的組態化可供選擇,本文將介紹如何運用 HT 豐富的 2/3D 組態搭建出一個隧道監控可視化系統的解決方案 監控隧道內的車道堵塞情況、隧道內的車禍現場,在隧道中显示當前車禍位置並在隧道口給予提示等功能都是非常有必要的。這個隧道監控可視化系統的主要內容包括:照明、風機、車道指示燈、交通信號燈、情報板、消防、火災報警、車行橫洞、風向儀、微波車檢、隧道緊急逃生出口的控制以及事故模擬等等。  
界面簡介及效果預覽  

預覽鏈接:http://www.hightopo.com/demo/tunnel2/index.html

上圖中的各種設備都可以雙擊,此時 camera 的位置會從當前位置移動到雙擊的設備的正前方;隧道入口的展示牌會自動輪播,出現事故時會展示牌中的內容會由“限速80,請開車燈”變為“超車道兩車追尾,請減速慢行”;兩隧道中間的逃生通道上方的指示牌是可以點擊的,點擊切換為藍綠色激活狀態,兩旁的逃生通道門也會打開,再單擊指示牌變為灰色,門關閉;還有一個事故現場模擬,雙擊兩旁變壓器中其中一個,在隧道內會出現一個“事故現場圖標”,單擊此圖標,出現彈出框显示事故等等等等。

 
代碼實現
一、場景搭建 整個隧道都是基於 3D 場景上繪製的,先來看看怎麼搭建 3D 場景:

// 數據容器 dm = new ht.DataModel(); // 3d 場景 g3d = new ht.graph3d.Graph3dView(dm); // 將場景添加到 body 中 g3d.addToDOM();

上面代碼中的 addToDOM 函數,是一個將組件添加到 body 體中的函數的封裝,定義如下:

addToDOM = function(){ var self = this, // 獲取組件的底層 div view = self.getView(), style = view.style; // 將組件底層div添加進body中  document.body.appendChild(view); // ht 默認將所有的組件的position都設置為absolute絕對定位 style.left = '0'; style.right = '0'; style.top = '0'; style.bottom = '0'; // 窗口大小改變事件,調用刷新函數 window.addEventListener('resize', function () { self.iv(); }, false); }

 
二、JSON反序列化 整個場景是由名為 隧道1.json 的文件導出而成的,我只需要用代碼將 json 文件中的內容轉換為我需要的部分即可:

// xhrLoad 函數是一個異步加載文件的函數 ht.Default.xhrLoad('./scenes/隧道1.json', function(text) { // 將 json 文件中的文本轉為我們需要的 json 格式的內容 var json = ht.Default.parse(text); // 反序列化數據容器,解析用於生成對應的Data對象並添加到數據容器 這裏相當於把 json 文件中生成的 ht.Node 節點反序列化到數據容器中,這樣數據容器中就有這個節點了  dm.deserialize(json); });

由於 xhrLoad 函數是一個異步加載函數,所以如果 dm 數據容器反序列化未完成就直接調用了其中的節點,那麼會造成數據獲取不到的結果,所以一般來說我是將一些邏輯代碼寫在這個函數內部,或者給邏輯代碼設置 timeout 錯開時間差。

首先,由於數據都是存儲在 dm 數據容器中的(通過 dm.add(node) 添加的),所以我們要獲取數據除了可以通過 id、tag 等獨立的方式,還可以通過遍曆數據容器來獲取多個元素。由於這個場景比較複雜,模型的面也比較多,鑒於設備配置,我將能 Batch 批量的元素都進行了批量。

批量是 HT 實現下的一種特有的機制,批量能提高性能的原理在於,當圖元一個個獨立繪製模型時性能較差,而當一批圖元聚合成一個大模型進行一次性的繪製時, 則會極大提高 WebGL 刷新性能,執行代碼如下

dm.each(function(data) { // 對“電話”進行批量 if (data.s('front.image') === 'assets/sos電話.png'){ data.s('batch', 'sosBatch'); } // 逃生通道批量(透明度也會影響性能) else if (data.s('all.color') === 'rgba(222,222,222,0.18)') { data.s('batch', 'emergencyBatch'); } else if (data.s('shape3d') === 'models/隧道/攝像頭.json' || data.s('shape3d') === 'models/隧道/橫洞.json' || data.s('shape3d') === 'models/隧道/捲簾門.json') { // 個別攝像頭染色了 不做批量 if(!data.s('shape3d.blend')) // 基礎批量什麼也不做 data.s('batch', 'basicBatch'); } else if (data.s('shape3d') === 'models/大型變壓器/變壓器.json') { data.s('batch', 'tileBatch'); data.setToolTip('單擊漫遊,雙擊車禍地點出現圖標'); } else if (data.getDisplayName() === '地面') { // 設置隧道“地面”不可選中 data.s('3d.selectable', false); } else if (data.s('shape3d') === 'models/隧道/排風.json') { // 排風扇的模型比較複雜,所以做批量 data.s('batch', 'fanBatch'); } else if (data.getDisplayName() === 'arrow') { // 隧道兩旁的箭頭路標 if (data.getTag() === 'arrowLeft') data.s('shape3d.image', 'displays/abc.png'); else data.s('shape3d.image', 'displays/abc2.png'); data.s({ 'shape3d': 'billboard', // 緩存,設置了 cache 的代價是需要設置 invalidateShape3dCachedImage 'shape3d.image.cache': true, // 設置這個值,圖片上的鋸齒就不會太明顯了(若圖片類型為 json,則設置 shape3d.dynamic.transparent) 'shape3d.transparent': true }); g3d.invalidateShape3dCachedImage(data); } // 隧道入口處的情報板 else if (data.getTag() === 'board' || data.getTag() === 'board1') { // 業務屬性,用來控制文本的位置[x,y,width,height] data.a('textRect', [0, 2, 244, 46]); // 業務屬性,設置文本內容 data.a('limitText', '限速80,請開車燈'); var min = -245; var name = 'board' + data.getId(); window[name] = setInterval(function() { // 設置情報板中的文字向左滾動,並且當文字全部显示時重複閃爍三次  circleFunc(data, window[name], min); }, 100); } //給逃生通道上方的指示板 動態設置顏色 var infos = ['人行橫洞1', '人行橫洞2', '人行橫洞3', '人行橫洞4', '車行橫洞1', '車行橫洞2', '車行橫洞3']; infos.forEach(function(info) { if(data.getDisplayName() === info) { data.a('emergencyColor', 'rgb(138, 138, 138)'); } }); infos = ['車道指示器', '車道指示器1', '車道指示器2', '車道指示器3']; infos.forEach(function(info) { if (data.getDisplayName() === info) { // 考慮到性能問題 將六面體變換為 billboard 類型元素 createBillboard(data, 'assets/車道信號-過.png', 'assets/車道信號-過.png', info); } }); });

上面有一處設置了 tooltip 文字提示信息,在 3d 中,要显示這個文字提示信息,就需要設置 g3d.enableToolTip() 函數,默認 3d 組件是關閉這個功能的。  
三、邏輯代碼
情報板滾動條 我就直接按照上面代碼中提到的方法進行解釋,首先是 circleFunc 情報板文字循環移動的函數,在這個函數中我們用到了業務屬性 limitText 設置情報板中的文字屬性以及 textRect 設置情報板中文字的移動位置屬性:

// 設置情報板中的文字向左滾動,並且當文字全部显示時重複閃爍三次 function circleFunc(data, timer, min) { // 獲取當前業務屬性 limitText 的內容 var text = data.a('limitText'); // 設置業務屬性 textRect 文本框的坐標和大小 data.a('textRect', [data.a('textRect')[0]-5, 2, 244, 46]); if (parseInt(data.a('textRect')) <= parseInt(min)) { data.a('textRect', [255, 2, 244, 46]); } else if (data.a('textRect')[0] === 0) { clearInterval(timer); var index = 0; // 設置多個 timer 是因為能夠進入這個函數中的不止一個 data,如果在同一時間多個 data 設置同一個 timer,那肯定只會對最後一個節點進行動畫。後面還有很多這種陷阱,要注意 var testName = 'testTimer' + data.getId(); window[testName] = setInterval(function() { index++; // 如果情報板中文本內容為空 if(data.a('limitText') === '') { setTimeout(function() { // 設置為傳入的 text 值 data.a('limitText', text); }, 100); } else { setTimeout(function() { // 若情報板中的文本內容不為空,則設置為空 data.a('limitText', ''); }, 100); } // 重複三次 if(index === 11) { clearInterval(window[testName]); data.a('limitText', text); } }, 100); setTimeout(function() { timer = setInterval(function() { // 回調函數  circleFunc(data, timer, min); }, 100); }, 1500); } } 

由於 WebGL 對瀏覽器的要求不低,為了能盡量多的適應各大瀏覽器,我們將所有的“道路指示器” ht.Node 類型的六面體全部換成 billboard 類型的節點,性能能提升不少。

http://www.hightopo.com 設置 billboard 的方法很簡單,獲取當前的六面體節點,然後給這些節點設置:

node.s({
    'shape3d': 'billboard', 'shape3d.image': imageUrl, 'shape3d.image.cache': true }); // 還記得用 shape3d.image.cache 的代價么? g3d.invalidateShape3dCachedImage(node); 

當然,因為 billboard 不能雙面显示不同的圖片,只是一個“面”,所以我們還得在這個節點的位置創建另一個節點,在這個節點的“背面”显示圖片,並且跟這個節點的配置一模一樣,不過位置要稍稍偏移一點。  
Camera 緩慢偏移 其他動畫部分比較簡單,我就不在這裏多說了,這裡有一個雙擊節點能將視線從當前 camera 位置移動到雙擊節點正前方的位置的動畫我提一下。我封裝了兩個函數 setEye 和 setCenter,分別用來設置 camera 的位置和目標位置的:

// 設置“目標”位置 function setCenter(center, finish) { // 獲取當前“目標”位置,為一個數組,而 getCenter 數組會在視線移動的過程中不斷變化,所以我們先拷貝一份 var c = g3d.getCenter().slice(0), // 當前x軸位置和目標位置的差值 dx = center[0] - c[0], dy = center[1] - c[1], dz = center[2] - c[2]; // 啟動 500 毫秒的動畫過度  ht.Default.startAnim({ duration: 500, action: function(v, t) { // 將“目標”位置緩慢從當前位置移動到設置的位置處  g3d.setCenter([ c[0] + dx * v, c[1] + dy * v, c[2] + dz * v ]); } }); }; // 設置“眼睛”位置 function setEye(eye, finish) { // 獲取當前“眼睛”位置,為一個數組,而 getEye 數組會在視線移動的過程中不斷變化,所以我們先拷貝一份 var e = g3d.getEye().slice(0), dx = eye[0] - e[0], dy = eye[1] - e[1], dz = eye[2] - e[2]; // 啟動 500 毫秒的動畫過度  ht.Default.startAnim({ duration: 500, // 將 Camera 位置緩慢地從當前位置移動到設置的位置 action: function(v, t) { g3d.setEye([ e[0] + dx * v, e[1] + dy * v, e[2] + dz * v ]); } }); };

後期我們要設置的時候就直接調用這兩個函數,並設置參數為我們目標的位置即可。比如我這個場景中的各個模型,由於不同視角對應的各個模型的旋轉角度也不同,我只能找幾個比較有代表性的 0°,90°,180°以及360° 這四種比較典型的角度了。所以繪製 3D 場景的時候,我也盡量設置節點的旋轉角度為這四个中的一種(而且對於我們這個場景來說,基本上只在 y 軸上旋轉了):

// 獲取事件對象的三維坐標 var p3 = e.data.p3(), // 獲取事件對象的三維尺寸 s3 = e.data.s3(), // 獲取事件對象的三維旋轉值 r3 = e.data.r3(); // 設置“目標”位置為當前事件對象的三維坐標值 setCenter(p3); // 如果節點的 y 軸旋轉值 不為 0 if (r3[1] !== 0) { // 浮點負數得做轉換才能進行比值 if (parseFloat(r3[1].toFixed(5)) === parseFloat(-3.14159)) { // 設置camera 的目標位置 setEye([p3[0], p3[1]+s3[1], p3[2] * Math.abs(r3[1]*2.3/6)]);  } else if (parseFloat(r3[1].toFixed(4)) === parseFloat(-1.5708)) { setEye([p3[0] * Math.abs(r3[1]/1.8), p3[1]+s3[1], p3[2]]);  } else { setEye([p3[0] *r3[1], p3[1]+s3[1], p3[2]]); } } else { setEye([p3[0], p3[1]+s3[1]*2, p3[2]+1000]); }

 
事故模擬現場 最後來說說模擬的事故現場吧,這段還是比較接近實際項目的。操作流程如下:雙擊“變壓器”–>隧道中間某個部分會出現一個“事故現場”圖標–>單擊圖標,彈出對話框,显示當前事故信息–>點擊確定,則事故現場之前的燈都显示為紅色×,並且隧道入口的情報板上的文字显示為“超車道兩車追尾,請減速慢行”–>再雙擊一次“變壓器”,場景恢復事故之前的狀態。 在 HT 中,可通過 Graph3dView#addInteractorListener(簡寫為 mi)來監聽交互過程:

g3d.addInteractorListener(function(e) { if(e.kind === 'doubleClickData') { // 有“事故”圖標節點存在 if (e.data.getTag() === 'jam') return; // 如果雙擊對象是變壓器 if (e.data.s('shape3d') === 'models/大型變壓器/變壓器.json') { index++; // 通過唯一標識 tag 標籤獲取“事故”圖標節點對象 var jam = dm.getDataByTag('jam'); if(index === 1){ var jam = dm.getDataByTag('jam'); jam.s({ // 設置節點在 3d 上可見 '3d.visible': true, // 設置節點為 billboard 類型 'shape3d': 'billboard', // 設置 billboard 的显示圖片 'shape3d.image': 'assets/車禍.png', // 設置 billboard 圖片是否緩存 'shape3d.image.cache': true, // 是否始終面向鏡頭 'shape3d.autorotate': true, // 默認保持圖片原本大小,設置為數組模式則可以設置圖片显示在界面上的大小 'shape3d.fixSizeOnScreen': [30, 30], }); // cache 的代價是節點需要設置這個函數  g3d.invalidateShape3dCachedImage(jam); } else { jam.s({ // 第二次雙擊變壓器就將所有一切恢復“事故”之前的狀態 '3d.visible': false }); dm.each(function(data) { var p3 = data.p3(); if ((p3[2] < jam.p3()[2]) && data.getDisplayName() === '車道指示器1') { data.s('shape3d.image', 'assets/車道信號-過.png'); } if(data.getTag() === 'board1') { data.a('limitText', '限速80,請開車燈'); } }); index = 0; } } } });

既然“事故”節點圖標出現了,接着點擊圖標出現“事故信息彈出框”,監聽事件同樣是在 mi(addInteractorListener)中,但是這次監聽的是單擊事件,我們知道,監聽雙擊事件時會觸發一次單擊事件,為了避免這種情況,我在單擊事件裏面做了演示:

// 點擊圖元 else if (e.kind === 'clickData'){ timer = setTimeout(function() { clearTimeout(timer); // 如果是“事故”圖標節點 if (e.data.getTag() === 'jam') { // 創建一個對話框  createDialog(e.data); } }, 200); }

在上面的雙擊事件中我沒有 clearTimeout,怕順序問題給大家造成困擾,要記得加一下。 彈出框如下: 這個彈出框是由兩個 ht.widget.FormPane 表單構成的,左邊的表單隻有一行,行高為 140,右邊的表單是由 5 行構成的,點擊確定,則“事故”圖標節點之前的道路指示燈都換成紅色×的圖標:

// 彈出框右邊的表單 function createForm4(node, dialog) { // 表單組件 var form = new ht.widget.FormPane(); // 設置表單組件的寬 form.setWidth(200); // 設置表單組件的高 form.setHeight(200); // 獲取表單組件的底層 div var view = form.getView(); // 將表單組件添加到 body 中  document.body.appendChild(view); var infos = [ '編輯框內容為:2輛', '編輯框內容為:客車-客車', '編輯框內容為:無起火', '編輯框內容為:超車道' ]; infos.forEach(function(info) { // 向表單中添加行  form.addRow([ info // 第二個參數為行寬度,小於1的值為相對值 ], [0.1]); }); form.addRow([ { // 添加一行的“確認”按鈕  button: { label: '確認', // 按鈕點擊事件觸發 onClicked: function() { // 隱藏對話框  dialog.hide(); dm.each(function(data) { var p3 = data.p3(); // 改變“車道指示器”的显示圖片為紅色×,這裏我是根據“事故”圖標節點的坐標來判斷“車道显示器”是在前還是在後的 if ((p3[2] < node.p3()[2]) && data.getDisplayName() === '車道指示器1') { data.s('shape3d.image', 'assets/車道信號-禁止.png'); } // 將隧道口的情報板上的文字替換 if(data.getTag() === 'board1') { data.a('limitText', '超車道兩車追尾,請減速慢行'); } }); } } } ], [0.1]); return form; }

 
總結 伴隨着新基建的建設興起,是以新發展理念為引領,以技術創新為驅動,以信息網絡為基礎,面向高質量發展需要,提供数字轉型、智能升級、融合創新等服務的基礎設施體系的完備,國家正邁入新時代的建設,也迎來了新時代的挑戰與機遇。隧道交通的監控可以歸納為工控管理與智慧交通建設的產物,同樣具有極為重要的意義。在眾多行業上所積累的經驗,HT 已經實現了許多不同領域建設的案例,例如 路口監管可視化系統,有興趣的話也可以了解一下!   2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這裏你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網: https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA 同時,你也可以查看更多案例及效果: https://www.hightopo.com/demos/index.html 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

前瞻布局 穩健經營 東風日產第800萬輛整車下線

建立經銷商能力診斷體系並擴大經銷商經營範圍,進行二手車、汽車保險、汽車金融、汽車租貸等業務的擴充發展,在2016年1-10月達到12。21%的置換率,為東風日產歷史新高,進一步提升了經銷商收益力、服務能力和渠道效率,從而實現更加便捷高效的服務。

2016年12月26日,東風日產第800萬整車下線儀式在花都二工廠舉行。100餘名媒體記者以及車主代表共同參与和見證這一盛事。

東風日產副總經理周先鵬在下線儀式上表示:“東風日產的經營理念始終伴隨着中國經濟發展以及汽車產業的升級而轉型,堅持用前瞻性的眼光探索行業發展的態勢,對未來的發展方向提前布局。13年來,東風日產穩健經營、用心發展,從容迎來第八百萬輛整車下線。”

伴隨着第800萬輛整車下線,東風日產提前完成2016年度銷售目標,截至12月25日,全年銷量達到110萬輛,比去年同期增長13%,再次穩健跨越百萬。

客戶至上 體系能力全面升級

品牌順應時代,不斷成長,是企業穩健經營的前提。在購車者年齡越來越年輕的新汽車消費時代,東風日產2016年繼續深化YOUNG NISSAN 戰略,通過一系列“創新、走心、用心”的營銷活動,全面彰顯品牌年輕化心態,極大提升了品牌知名度與好感度。無論是攜手NBA、歐冠等頂級賽事,還是邀約頂級明星易建聯代言新生代TIIDA,都讓消費者近距離感受體育運動的激情與活力;產品營銷方面,新樓蘭、新奇駿、全新軒逸、藍鳥等產品圍繞文化、越野、音樂等不同主題,通過創新的活動形式,不僅讓消費者體驗到各具特色的產品魅力,更展現出不同產品和目標消費者的情懷與個性。據悉,2016年東風日產品牌好感度相較於2015年提升3.8%,首次超越豐田,躋身合資品牌前三。

客戶服務是企業穩健經營的基礎。2016年,東風日產圍繞“客戶年”的主題,開展“擁抱客戶,用心服務”主題實踐活動,強化全員客戶意識;通過成立地區支持辦公室,以更扁平化的運作架構貼近客戶;同時,在全國77家店開展了一系列的呼叫制培訓方式,使受訓店服務投訴率降幅達到38%。此外,易誠認證車首推兩年四萬公里保修升級政策,此舉為行業首創,深度保障消費者利益。

渠道健康是企業穩健經營的保障。2016年,東風日產落實p20大城市戰略,優化專營店的數量及效率,經銷商整體收益得到提升;建立經銷商能力診斷體系並擴大經銷商經營範圍,進行二手車、汽車保險、汽車金融、汽車租貸等業務的擴充發展,在2016年1-10月達到12.21%的置換率,為東風日產歷史新高,進一步提升了經銷商收益力、服務能力和渠道效率,從而實現更加便捷高效的服務。

不僅如此,東風日產更在提升企業體系力方面,未雨綢繆,坐言起行。2016年,秉承“穩健經營”的理念,東風日產腳踏實地、強調客戶服務、渠道和品牌健康成長。價值鏈前端建設也初見成果,先進工程技術中心、啟辰造型中心及東風日產大學,全面投入使用,從產品、研發設計、製造、人才培養等多個緯度鍛造企業內功,提升綜合實力,為東風日產未來新中期事業提供有力支撐。

智能驅動未來 I³計劃全面展開

隨着社會及技術層面信息化、智能化的發展,以及國家“智能製造”戰略藍圖的提出,汽車企業面臨着新的的機遇及挑戰。汽車行業已進入了智能時代,順應消費者需求智能化發展的趨勢,東風日產聚焦智能時代,進入以智能技術為驅動的YOUNG NISSAN 3.0時代,發布了“I³計劃”。以全價值鏈智能升級為核心,從智能出行(Intelligent Mobility Technology)、智造品質(Intelligent Manufacture Quality)、智享體驗(Intelligent Customer Experience)三大維度布局未來。

在智能出行方面,以“零碰撞、零排放、零距離”作為終極目標,開啟汽車技術的智能化升級,東風日產將成為率先導入中國的量產電動車的首個合資品牌;在智造品質方面,構建國內首創“整建制”先進工程技術中心,以数字化開發平台、智能化精工製造和信息化品質管理,實現製造技術的智能化升級;在智享體驗等方面,依託國內首個合資汽車公司自建電商平台車巴巴、率先將VR技術應用於新車體驗的沉浸式產品数字體驗平台、車載智能信息服務的應用,進行顧客全觸點的智能化升級。

2017年是東風日產再次跨越百萬之後的重要一年,800萬輛整車下線,對東風日產來說是一個歷史性的里程碑,更是一個新的起點。東風日產將以“I³計劃”為基礎,助推品牌年輕化戰略再升級,進入以智能技術為驅動的YOUNG NISSAN 3.0時代。同時,東風日產還將以“客戶年2.0”作為2017年發展的整體指導方向,從消費者需求出發,持續提升品牌力和客戶滿意度,保證主力車型的銷量及新車上市,同時整合網絡安全,強化經銷商基礎,為客戶帶來更加精彩的智能化汽車生活。

周先鵬表示,“前瞻性的戰略思考,以及穩健高效的執行力,為東風日產更快速響應市場,決勝未來奠定了堅實基礎。在800萬份信賴之上,東風日產砥礪前行,以智能化的未來驅動力,助力東風日產引領行業趨勢,穩健前行。”本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

小菜成長之路,警惕淪為 API 調用俠

小菜(化名)在某互聯網公司擔任運維工程師,負責公司後台業務的運維保障工作。由於自己編程經驗不多,平時有不少工作需要開發協助。

聽說 Python 很火,能快速開發一些運維腳本,小菜也加入 Python 大軍學起來。 Python 語言確實簡單,小菜很快就上手了,覺得自己應對運維開發工作已經綽綽有餘,便不再深入研究。

背景

這天老闆給小菜派了一個數據採集任務,要實時統計服務器 TCP 連接數。需求背景是這樣的:開發同事需要知道服務的連接數以及不同狀態連接的比例,以便判斷服務狀態。

因此,小菜需要開發一個腳本,定期採集並報告 TCP 連接數,提交數據格式定為 json :

{
  "LISTEN": 4,
  "ESTABLISHED": 100,
  "TIME_WAIT": 10
}

作為運維工程師,小菜當然知道怎麼查看系統 TCP 連接。
Linux 系統中有兩個命令可以辦到, netstat 和 ss :

$ netstat -nat
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:8388          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 192.168.56.3:22         192.168.56.1:54983      ESTABLISHED
tcp6       0      0 :::22                   :::*                    LISTEN
$ ss -nat
State                    Recv-Q                    Send-Q                                         Local Address:Port                                         Peer Address:Port
LISTEN                   0                         128                                                127.0.0.1:8388                                              0.0.0.0:*
LISTEN                   0                         128                                            127.0.0.53%lo:53                                                0.0.0.0:*
LISTEN                   0                         128                                                  0.0.0.0:22                                                0.0.0.0:*
ESTAB                    0                         0                                               192.168.56.3:22                                           192.168.56.1:54983
LISTEN                   0                         128                                                     [::]:22                                                   [::]:*

小菜還知道 ss 命令比 netstat 命令要快,但至於為什麼,小菜就不知道了。

小菜很快找到老闆,提出了自己的解決方案:寫一個 Python 程序,調用 ss 命令採集 TCP 連接信息,然後再逐條統計。

老闆告訴小菜,線上服務器很多都是最小化安裝,並不能保證每台機器上都有 ss 或者 netstat 命令。

老闆還告訴小菜,程序開發要學會 站在巨人的肩膀上 。動手寫代碼前,先調研一番,看是否有現成的解決方案。 切忌重複造輪子 ,浪費時間不說,可能代碼質量還差,效果也不好。

最後老闆給小菜指了條明路,讓他回去再看看 psutil 。 psutil 是一個 Python 第三方包,用於採集系統性能數據,包括: CPU 、內存、磁盤、網卡以及進程等等。臨走前,老闆還叮囑小菜,完成工作后花點時間研究下這個庫。

psutil 方案

小菜搜索 psutil 發現,原來有這麼順手的第三方庫,喜出望外!他立馬裝好 psutil ,準備開干:

$ pip install psutil

導入 psutil 后,一個函數調用就可以拿到系統所有連接,連接信息非常豐富:

>>> import psutil
>>> for conn in psutil.net_connections('tcp'):
...     print(conn)
...
sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None)
sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None)
sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None)
sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None)
sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None)

小菜很滿意,感覺不用花多少時間就可搞定數據採集需求了,準時下班有望!噼里啪啦,很快小菜就寫下這段代碼:

import psutil
from collections import defaultdict

# 遍歷每個連接,按連接狀態累加
stats = defaultdict(int)
for conn in psutil.net_connections('tcp'):
    stats[conn.status] += 1

# 遍歷每種狀態,輸出連接數
for status, count in stats.items():
    print(status, count)

小菜接着在服務器上測試這段代碼,功能完全正常:

ESTABLISHED 1
LISTEN 4

小菜將數據採集腳本提交,並按既定節奏逐步發布到生產服務器上。開發同事很快就看到小菜採集的數據,都誇小菜能力不錯,需求完成得很及時。小菜也很高興,感覺 Python 沒白學。如果用其他語言開發,說不定現在還在加班加點呢!Life is short, use Python! 果然沒錯!

小菜愈發自信,早就把老闆的話拋到腦後了。 psutil 這個庫這麼好上手,有啥好深入研究的?

內存悲劇

突然有一天,其他同事緊急告訴小菜,他開發的採集腳本佔用很多內存, CPU 也跑到了 100% ,已經開始影響線上服務了。小菜還沉浸在成功的喜悅中,收到這個反饋如同晴天霹靂,有點举手無措。

業務同事告訴小菜,受影響的機器系統連接數非常大,質疑小菜是不是腳本存在性能問題。小菜覺得很背,腳本只是調用 psutil 並統計數據,怎麼就攤上性能故障?腳本影響線上服務,小菜壓力很大,但不知道如何是好,只能跑去找老闆尋求幫助。

老闆要小菜第一時間停止數據採集,降低影響。復盤故障時,老闆很敏銳地問小菜,是不是用容器保存所有連接了?小菜自己並沒有,但是 psutil 這麼做了:

>>> psutil.net_connections()
[sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='10.0.2.15', port=68), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None)]

psutil 將採集到的所有 TCP 連接放在一個列表裡返回。如果服務器上有十萬個 TCP 連接,那麼列表裡將有十萬個連接對象。難怪採集腳本吃了那麼多內存!

老闆告訴小菜,可以用生成器加以解決。與列表不同,生成器逐個返回數據,因此不會佔用太多內存。Python2 中 range 和 xrange 函數的區別也是一樣的道理。

小菜從 pstuil  fork 了一個分支,並將 net_connections 函數改造成 生成器 :

def net_connections():
    while True:
        if done:
            break

        # 解析一個TCP連接
        conn = xxx

        yield conn

代碼上線后,採集腳本內存佔用量果然下降了! 生成器 將統計算法的空間複雜度由原來的 O(n) 優化為 O(1) 。經過這次教訓,小菜不敢再盲目自信了,他決定抽時間好好看看 psutil 的源碼。

源碼體會

深入學習源碼后,小菜發現原來 psutil 採集 TCP 連接數的秘笈是:從 /proc/net/tcp 以及 /proc/net/tcp6 讀取連接信息。

由此,他還進一步了解到 procfs ,這是一個偽文件系統,將內核空間信息以文件方式暴露到用戶空間。 /proc/net/tcp 文件則是提供內核 TCP 連接信息:

$ cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534        0 18183 1 0000000000000000 100 0 0 10 0
   1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 16624 1 0000000000000000 100 0 0 10 0
   2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 18967 1 0000000000000000 100 0 0 10 0
   3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:00023B11 00000000     0        0 22284 4 0000000000000000 20 13 23 10 20

小菜還注意到,連接信息看起來像個自定義類對象,但其實是一個 nametuple :

# psutil.net_connections()
sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
                             'status', 'pid'])

小菜一開始並不知道作者為啥要這麼做。後來,小菜開始研究 Python 源碼,學習了 Python 類機制后他恍然大悟。

Python 自定義類的每個實例對象均需要一個 dict 來保存對象屬性,這也就是對象的 屬性空間 。

如果用自定義類來實現,每個連接都需要創建一個字典,而字典又是 散列表 實現的。如果系統存在成千上萬的連接,開銷可想而知。

小菜將學到的知識總結起來:對於 數量大 而 屬性固定 的實體,沒有必要用自定義類來實現,用 nametuple 更合適,開銷更小。由此,小菜不經由衷佩服 psutil 的作者。

CPU悲劇

後來小菜又收到業務反饋,採集腳本在高併發的服務器上, CPU 使用率很高,需要再優化一下。

小菜回憶 psutil 源碼,很快就找到了性能瓶頸處: psutil 將連接信息所有字段都解析了,而採集腳本只需要其中的 狀態 字段而已。

跟老闆商量后,小菜決定自行讀取 procfs 來實現採集腳本,只解析狀態字段,避免不必要的計算開銷。

procfs 方案

直接讀取 /proc/net/tcp ,可以得到完整的 TCP 連接信息:

>>> with open('/proc/net/tcp') as f:
...     for line in f:
...         print(line.rstrip())
...
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534        0 18183 1 0000000000000000 100 0 0 10 0
   1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 16624 1 0000000000000000 100 0 0 10 0
   2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 18967 1 0000000000000000 100 0 0 10 0
   3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:0007169E 00000000     0        0 22284 3 0000000000000000 20 20 33 10 20

其中, IP 、端口、狀態等字段都是以十六進制編碼的。例如, st 列表示狀態,狀態碼 0A 表示 LISTEN 。很快小菜就寫下這段代碼:

from collections import defaultdict

stat_names = {
    '0A': 'LISTEN',
    '01': 'ESTABLISHED',
    # ...
}

# 遍歷每個連接,按連接狀態累加
stats = defaultdict(int)

with open('/proc/net/tcp') as f:
    # 跳過表頭行
    f.readline()

    for line in f:
        st = line.strip().split()[3]
        stats[st] += 1

for st, count in stats.items():
    print(stat_names[st], count)

現在,小菜寫代碼比之前講究多了。在統計連接數時,他並不急於將狀態碼解析成名字,而是按原樣統計。等統計完成,他再一次性轉換,這樣狀態碼轉換開銷便降到最低: O(1)  而不是 O(n) 。

這次改進符合業務同事預期,但小菜決定好好做一遍性能測試,不打無準備之仗。他找業務同事要了一個連接數最大的 /proc/net/tcp 樣本,拉到本地測試。測試結果還算符合預期,採集腳本能夠扛住十萬連接採集壓力。

性能測試中,小菜發現了一個比較奇怪的問題。同樣的連接規模,把 /proc/net/tcp 拉到本地跑比直接在服務器上跑要快,而本地電腦性能肯定比不上服務器。

他百思不得其解,又去找老闆幫忙。老闆很快指出到其中的區別,將 /proc/net/tcp 拉到本地就成為普通 磁盤文件 ,而 procfs 是內核映射出來的 偽文件 ,並不是磁盤文件。

他讓小菜研究一下 Python 文件 IO 以及內核 IO 子系統在處理這兩種文件時有什麼區別,還讓小菜特別留意 IO 緩衝區大小。

IO緩衝

小菜打開一個普通的磁盤文件,發現 Python 選的默認緩衝區大小是 4K (讀緩存對象頭 152 字節):

>>> f = open('test.py')
>>> f.buffer.__sizeof__()
4248

但是如果打開的是 procfs 文件, Python 選的緩衝區卻只有 1K ,相差了 4 倍呢!

>>> f = open('/proc/net/tcp')
>>> f.buffer.__sizeof__()
1176

因此,理論上 Python 默認讀取 procfs 發生的上下文切換次數是普通磁盤文件的 4 倍,怪不得會慢。

雖然小菜還不知道這種現象背後的原因,但是他已經知道怎麼進行優化了。隨即他決定將緩衝區設置為 1M 以上,盡量避免 IO 上下文切換,以空間換時間:

with open('/proc/net/tcp', buffering=1*1024*1024) as f:
    # ...

經過這次優化,採集腳本在大部分服務器上運行良好,基本可以高枕無憂了。而小菜也意識到 編程語言 以及 操作系統 等底層基礎知識的重要性,他開始制定學習計劃補全計算機基礎知識。

netlink 方案

後來負載均衡團隊找到小菜,他們也想統計服務器上的連接信息。由於負載均衡服務器作為入口轉發流量,連接數規模特別大,達到幾十萬,將近百萬的規模。小菜決定好好進行性能測試,再視情況上線。

測試結果並不樂觀,採集腳本要跑幾十秒鐘才完成, CPU 跑到 100% 。小菜再次調高 IO 緩衝區,但效果不明顯。小菜又測試了 ss 命令,發現 ss 命令要快很多。由於之前嘗到了閱讀源碼的甜頭,小菜很想到 ss 源碼中尋找秘密。

由於項目時間較緊,老闆提醒小菜先用 strace 命令追蹤 ss 命令的系統調用,便可快速獲悉 ss 的實現方式。老闆演示了 strace 命令的用法,很快就找到了 ss 的秘密 —— Netlink :

$ strace ss -nat
...
socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_SOCK_DIAG) = 3
...

Netlink 套接字是 Linux 提供通訊機制,可用於內核與進程間、進程與進程間通訊。 Netlink 下的 sock_diag 子系統,提供了一種從內核獲取套接字信息的新方式。

procfs 不同,sock_diag 採用網絡通訊的方式,內核作為服務端接收客戶端進程查詢請求,並以二進制數據包響應查詢結果,效率更高。

這就是 ss 比 netstat 更快的原因, ss 採用 Netlink 機制,而 netstat 採用 procfs 機制。

很不幸 Python 並沒有提供 Netlink API ,一般人可能又要干著急了。好在小菜先前有意識地研究了部分 Python 源碼,對 Python 的運行機制有所了解。

他知道可以用 C 寫一個 Python 擴展模塊,在 C 語言中調用原生系統調用。

編寫 Python C 擴展模塊可不簡單,對編程功底要求很高,必須全面掌握 Python 運行機制,特別是對象內存管理。

一朝不慎可能導致程序異常退出、內存泄露等棘手問題。好在小菜已經不是當年的小菜了,他經受住了考驗。

小菜的擴展模塊上線后,效果非常好,頂住了百萬級連接的採集壓力。

一個看似簡單得不能再簡單的數據採集需求,背後涉及的知識可真不少,沒有一定的水平還真搞不定。好在小菜成長很快,他最終還是徹底地解決了性能問題,找回了久違的信心。

內核模塊方案

雖然性能問題已經徹底解決,小菜還是沒有將其淡忘。

他時常想:如果可以將統計邏輯放在內核空間做,就不用在內核和進程之間傳遞大量連接信息了,效率應該是最高的!受限於當時的知識水平,小菜還沒有能力實現這個設想。

後來小菜在研究 Linux 內核時,發現可以用內核模塊來擴展內核的功能,結合 procfs 的工作原理,他找到了技術方案!他順着 /proc/net/tcp 在內核中的實現源碼,依樣畫葫蘆寫了這個內核模塊:

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <net/tcp.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xiaocai");
MODULE_DESCRIPTION("TCP state statistics");
MODULE_VERSION("1.0");

// 狀態名列表
static char *state_names[] = {
    NULL,
    "ESTABLISHED",
    "SYN_SENT",
    "SYN_RECV",
    "FIN_WAIT1",
    "FIN_WAIT2",
    "TIME_WAIT",
    "CLOSE",
    "CLOSE_WAIT",
    "LAST_ACK",
    "LISTEN",
    "CLOSING",
    NULL
};


static void stat_sock_list(struct hlist_nulls_head *head, spinlock_t *lock,
    unsigned int state_counters[])
{
    // 套接字節點指針(用於遍歷)
    struct sock *sk;
    struct hlist_nulls_node *node;

    // 鏈表為空直接返回
    if (hlist_nulls_empty(head)) {
        return;
    }

    // 自旋鎖鎖定
    spin_lock_bh(lock);

    // 遍歷套接字鏈表
    sk = sk_nulls_head(head);
    sk_nulls_for_each_from(sk, node) {
        if (sk->sk_state < TCP_MAX_STATES) {
            // 自增狀態計數器
            state_counters[sk->sk_state]++;
        }
    }

    // 自旋鎖解鎖
    spin_unlock_bh(lock);
}


static int tcpstat_seq_show(struct seq_file *seq, void *v)
{
    // 狀態計數器
    unsigned int state_counters[TCP_MAX_STATES] = { 0 };
    unsigned int state;

    // TCP套接字哈希槽序號
    unsigned int bucket;

    // 先遍歷Listen狀態
    for (bucket = 0; bucket < INET_LHTABLE_SIZE; bucket++) {
        struct inet_listen_hashbucket *ilb;

        // 哈希槽
        ilb = &tcp_hashinfo.listening_hash[bucket];

        // 遍歷鏈表並統計
        stat_sock_list(&ilb->head, &ilb->lock, state_counters);
    }

    // 遍歷其他狀態
    for (bucket = 0; bucket < tcp_hashinfo.ehash_mask; bucket++) {
        struct inet_ehash_bucket *ilb;
        spinlock_t *lock;

        // 哈希槽鏈表
        ilb = &tcp_hashinfo.ehash[bucket];
        // 保護鎖
        lock = inet_ehash_lockp(&tcp_hashinfo, bucket);

        // 遍歷鏈表並統計
        stat_sock_list(&ilb->chain, lock, state_counters);
    }

    // 遍歷狀態輸出統計值
    for (state = TCP_ESTABLISHED; state < TCP_MAX_STATES; state++) {
        seq_printf(seq, "%-12s: %d\n", state_names[state], state_counters[state]);
    }

    return 0;
}


static int tcpstat_seq_open(struct inode *inode, struct file *file)
{
    return single_open(file, tcpstat_seq_show, NULL);
}


static const struct file_operations tcpstat_file_ops = {
    .owner   = THIS_MODULE,
    .open    = tcpstat_seq_open,
    .read    = seq_read,
    .llseek  = seq_lseek,
    .release = single_release
};


static __init int tcpstat_init(void)
{
    proc_create("tcpstat", 0, NULL, &tcpstat_file_ops);
    return 0;
}


static __exit void tcpstat_exit(void)
{
    remove_proc_entry("tcpstat", NULL);
}

module_init(tcpstat_init);
module_exit(tcpstat_exit);

內核模塊編譯好並加載到內核后, procfs 文件系統提供了一個新文件 /proc/tcpstat ,內容為統計結果:

$ cat /proc/tcpstat
ESTABLISHED : 5
SYN_SENT    : 0
SYN_RECV    : 0
FIN_WAIT1   : 0
FIN_WAIT2   : 0
TIME_WAIT   : 1
CLOSE       : 0
CLOSE_WAIT  : 0
LAST_ACK    : 0
LISTEN      : 14
CLOSING     : 0

當用戶程序讀取這個文件時,內核虛擬文件系統( VFS )調用小菜在內核模塊中寫的處理函數:遍歷內核 TCP 套接字完成統計並格式化統計結果。內核模塊、 VFS 以及套接字等知識超出專欄範圍,不再贅述。

小菜在服務器上試驗這個內核模塊,真的快得飛起!

經驗總結

小菜開始總結這次腳本開發工作中的經驗教訓,他列出了以下關鍵節點:

  1. 依靠 psutil 採集,沒有關注 psutil 實現導致性能問題;
  2. 用生成器代替列表返回連接信息,解決內存瓶頸;
  3. 直接讀取 procfs 文件系統,部分解決 CPU 性能瓶頸;
  4. 通過調節 IO 緩衝區大小,進一步降低 CPU 開銷;
  5. Netlink 代替 procfs ,徹底解決性能問題;
  6. 實驗內核模塊思路,終極解決方案快得飛起;

這些問題節點,一個比一個深入,沒有一定功底是搞不定的。小菜從剛開始跌跌撞撞,到後來獨當一面,快速成長的關鍵在於善於在問題中總結經驗教訓:

  • 程序開發完一定要做性能測試,看能夠扛住多大的壓力;
  • 使用任何工具,需要準確理解其背後的原理,避免誤用;
  • 對編程語言以及操作系統源碼要保持好奇心;
  • 計算機基礎知識很重要,需要及時補全才能達到新高度;
  • 學會問題發散,舉一反三;

更多章節

洞悉 Python 虛擬機運行機制,探索高效程序設計之道!

到底如何才能提升我的 Python 開發水平,向更高一級的崗位邁進? 如果你有這些問題或者疑惑,請訂閱我們的專欄,閱讀更多章節:

  • 內建對象
  • 虛擬機
  • 函數機制
  • 類機制
  • 生成器與協程
  • 內存管理機制

附錄

更多 Python 技術文章請訪問:小菜學Python,轉至 原文 可獲得最佳閱讀體驗。

訂閱更新,獲取更多學習資料,請關注 小菜學編程 :

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案