部落格

  • 大眾計畫為高端車型開發第二個電動汽車平臺

    據外媒報導稱,大眾正在考慮開發第二個電動汽車平臺,用於更大、更高端的車型。

    大眾的第一個電動汽車平臺名叫MEB,曾在CES上展示 過,BUDD-e概念車就是用該平臺開發的。該平臺專門針對小型汽車、輕型商務用車,充電一次行駛距離大約155英里至310英里(250-500公 裡)。目前大眾正在調查MEB平臺的彈性,看它是否可以用在高端車型上,比如Phaeton(輝騰),未來大眾可能會為高端車型專門開發一個新平臺。

    按照大眾的計畫,2019年之前MEB平臺準備就緒,因為該平臺主要針對的是電動汽車,擴展性更強。也就是說汽車製造商調整軸距、軌距、座位位置更加容易一些,它們可以將平臺用在更多的車型上。落地式電池尤其引人注目,因為工程師可以根據車型改變電池的大小。

    大眾集團電子研發主管沃克馬•坦尼伯格(Volkmar Tanneberger)稱,‘巧克力電池’(chocolate battery)製造更容易,可以進行大規模工業化生產。MEB能夠用在所有車型上,包括超小型Polo和中型汽車帕薩特(Passat),未來MEB有可能會成為大眾所有產品線的基石。如果MEB架構無法用在更大型的汽車上,比如輝騰,開發第二個電動汽車平臺就會變得有意義。保時捷Mission E和奧迪e-tron Quattro將會擁有自己的平臺,因為早在MEB架構公佈之前,它們已經設計成型。

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 特斯拉超級電池工廠Gigafactory 7月正式開業 目前仍在建

    據外媒體報導,特斯拉稱其位於內華達州里諾市郊的超級電池工廠Gigafactory將於7月29日晚上舉行開業典禮,目前該工廠仍處於建設之中。

    特斯拉在發給客戶的一封電子郵件中披露,為特斯拉成功推薦的客戶超過5位元的人將會受邀參加此次開業典禮。特斯拉已經通過電子郵件向這些符合資格的客戶通知了開業典禮時間。

    事實上,在未來2個月內Gigafactory工廠不可能完全竣工,特斯拉僅僅是決定在已建成區域舉行小型典禮。在今年3月份,特斯拉、SolarCity在該廠區接待了內華達州議員,探討了太陽能業務在該州的發展。

    據悉,Gigafactory工廠建設造價逾50億美元,到今年5月初僅建成14%。此外,松下向特斯拉的超級電池工廠供應電池生產設備,並提供部分資金。

    儘管電池廠還在建設之中,但是工廠已經投產。工廠目前生產的太陽能電池被用於公共設施、企業以及各個地產業主。

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

    【其他文章推薦】

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

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

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

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • Gigafactory 即將正式開幕,福斯隔海叫陣

    Gigafactory 即將正式開幕,福斯隔海叫陣

    特斯拉(Tesla)位於美國內華達州的超級電池工廠 Gigafactory 已經部分營運了好一陣子,不過一直一邊生產一邊還在施工,尚未正式開幕,如今開工大典的日子出爐,特斯拉將於 2016 年 7 月 29 日舉辦開工典禮。   特斯拉 Gigafactory 將是預購熱銷的 Model 3 是否能達成出貨的重要關鍵,Gigafactory 預定 2020 年達到最大產能,1 年生產 3,500 萬度儲存容量的電池,特斯拉表示,這超過 2013 年全球鋰電池生產總量,理論上並將降低 30% 生產成本,使得 Model 3 得以壓低售價還能獲利。   在 2016 年 5 月初,Gigafactory 大約有 14% 完工,目前已經開始生產能源儲存產品 Powerpack 與 Powerwall,不過特斯拉一直未在 Gigafactory 舉辦媒體活動,只有曾經獨家邀請汽車雜誌《汽車趨勢》(Motor Trend)前往其中參觀 Model 3 原型。如今特斯拉在寄給消費者的邀請函中透露開工典禮的日期。   特斯拉的活動往往是消費者優於媒體,Model 3 發表會時,車主先受邀,之後才邀請媒體,Gigafactory 開幕式看樣子也是如此,目前特斯拉先邀請透過介紹 5 位朋友買 Model 3 而贏得參加開幕式權利的消費者參加,媒體尚未受邀,目前尚不清楚到底最後會不會邀請媒體。   就在特斯拉緊鑼密鼓籌辦開工大典時,競爭對手也不甘示弱,德國報導指出德國汽車大廠福斯(VW)打算建造與 Gigafactory 一爭雄長的「數十億歐元」工廠,用以生產電動車並自行生產鋰電池,擺脫對 Panasonic、三星與 LG 化學等電池供應商的依賴。   福斯剛經歷柴油車作弊事件,讓福斯柴油車的形象跌到谷底,很容易理解福斯如今更積極發展電動車以開拓新局,福斯將於 2016 年 6 月 22 日的年會上發表電動車與電池工廠的進一步詳情。特斯拉 Gigafactory 的開幕,可能將是全球車廠與電池廠軍備大賽的開端。

    (首圖來源:)   (本文授權轉載自《》─〈〉)

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

    【其他文章推薦】

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

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

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

    小三通物流營運型態?

    ※快速運回,大陸空運推薦?

  • Model 3將不會向使用者提供免費充電服務

    據外媒報導,Model 3實惠的價格引發了消費者的熱烈預訂,不過特斯拉CEO艾隆•馬斯克表示,Model 3將不會向使用者提供免費充電服務,消費者需要購買額外的服務包。

    目前為止,特斯拉對於所有購買了電動車的使用者均提供免費充電服務。這也是得益於他們在美國、澳大利亞中國等地建設了大量的充電站和充電樁。電力消耗成本低廉,特斯拉已經在把充電服務作為電動車的一個增值服務。

    但是隨著特斯拉用戶的增加,充電站以及充電樁的數量卻不能無限的增長,這也給特斯拉帶來了極大的壓力。馬斯克在回答提問時表示,除非使用者購買額外充電服務,否則Model 3使用者將不會獲得免費長途充電服務。不過馬斯克並未詳細介紹充電服務包的內容和價格。

    馬斯克認為,免費充電服務也是有一定的成本的,因此把Model 3本身的成本和充電的成本分開是非常有必要的。Model 3的付費充電服務會非常便宜,遠遠低於加油的費用。

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

    【其他文章推薦】

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

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

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

    台灣寄大陸海運貨物規則及重量限制?

    大陸寄台灣海運費用試算一覽表

  • 北汽規劃打造四大海外基地 南非新工廠將投產

    據媒體報導,從北汽集團官方獲悉,北汽將斥資50億元在南非建設總裝 廠,該工廠擬於下月開始建設,計畫於2017年11月建成投產。按照規劃,北汽將打造包括南非、伊朗在內的四大海外運營基地並輻射周邊市場,形成四大屬地化產業運營集團。

    未來北汽將海外市場拓展劃分了三條線,歸納為“一帶一路一洲 哥倫布航線”,其中“一帶一路”符合大環境下的海外發展趨勢,“一洲”則是“一帶一路”中涉及到的非洲,在此基礎上,北汽增加了“哥倫布航線”沿途的中南美地區。繼續細化,北汽將以“南非、伊朗、東南亞、墨西哥”四大重點專案為引領,建設輻射周邊市場的四大海外運營基地,逐步實現從“旅行者”到“定居者”的角色轉變。

    與跨國車企在中國的發展要尋求本地車企進行合作類似,北汽在南非新建的工廠將由北汽集團與南非工業開發公司的合資企業負責運營。新工廠總投資金額達到50億元(7.73億美元),計畫於下月開始建設,有望於2017年11月建成投產,計畫年產能為10萬輛。

    據介紹,南非工廠將作為試點,為後續北汽加速海外涉及12個國家的19個KD(散件組裝廠)專案建設積累經驗。北汽集團已於2013年在南非開設了一家小型SKD廠,位於斯普林斯鎮,該廠生產小型麵包計程車,此次在南非斥鉅資打造新工廠並作為試點也就不難理解。

    北京汽車國際發展公司擁有五大核心業務,包括自主品牌整車和零部件產品的出口,技術、設備、整車的進口,此外還有產品改裝,境外投資以及國際合作,初期投放海外市場的產品也將以北汽自主品牌為主。這意味著即將建成的南非汽車生產廠將有望投產包括北京紳寶、北京牌和北汽威旺三大產品系列,初步形成對當地市場佈局的同時還將進行他國出口,擴大南非及周邊國家和地區市場份額。

    在北汽擴大海外市場佈局後,未來市場銷量將成為企業諸多努力的體現。“十三五”期間北汽制定了“2030”戰略,戰略中指出企業要在2020年實現20萬輛整車出口,完成3個建設,包括國際化運營隊伍建設、合作夥伴隊伍建設、體系能力建設,從而實現品牌高端市場的突破。

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

    【其他文章推薦】

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

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

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

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

  • Gogoro騎進中台灣!台中據點開幕,市府再祭補助措施

    Gogoro騎進中台灣!台中據點開幕,市府再祭補助措施

    台灣智慧雙輪電動機車Gogoro持續開疆拓土,正式啟用台中門市與維修中心。在6月4日的啟用典禮同時,台中市府也表示將繼續推出相關補助措施,積極鼓勵民眾改用綠能載具,打造智慧低碳城市。

    繼CITY POWER、EZ SWAP之後,Gogoro是第三家進駐大台中市的電池交換型電動機車系統,除開設門市與維修中心外,預計將在六月底前於台中設置近30座電池交換站,方便騎士使用。

    打造智慧城市,Gogoro積極拓點

    Gogoro致力於以「智慧雙輪」產品打造智慧城市,並從台北開始,透過成立服務據點、電池交換站等,陸續打開市場規模。去年11月、12月間,Gogoro先與超商業者取得合作,在部分台北超商門市設置電池交換據點GoStation,之後也在桃、竹設點,啟用電池交換站,把服務範圍擴大到整個雙北市、桃竹地區。

    目前,Gogoro在全台已有接近8,000位車主,服務據點15處,電池交換站超過200座。

    Gogoro執行長暨共同創辦人陸學森表示,截至6月4日為止已有超過2,000為台中市民參加Gogoro試乘活動,且已有200多位消費者搶先預購。未來,Gogoro將積極在台中佈建GoStation電池交換站,六月底前預計將建置30處交換據點。此外,7-Eleven、萊爾富與全家都將成為超商合作夥伴。

    台中市民享早鳥優惠、市府補貼

    Gogoro表示,六月底前下定的台中市車主將可獲得Gogoro提供的早鳥三好禮──後擋泥板、防滑踏墊、清潔組,價值新台幣2,300元。此外,全台各地的六月新車主只要選用599元的能源服務方案,前六個月可享有1,200公里的免費里程。

    對於Gogoro進軍台中,台中市府表示也將持續推動綠能交通,繼續在市府洽公處、大專院校等公共空間設置電動機車電池交換站,或者透過增加電動機車專屬停車格等方式來鼓勵電動機車的使用。

    此外,台中市府已通過〈臺中市公私場所管制生煤及禁用石油焦自治條例〉,四年內目標減少生煤使用量40%,以及相關減碳措施。針對電動機車,台中市府除上述增加電池交換點等措施外,也將繼續提供換車、購車補貼。市府經發局長呂曜志表示,凡台中市民汰換二行程改購重型電動機車者,市府環保局就補助新台幣一萬元,中央政府環保署也將補助新台幣7,000元。

    (照片來源:Gogoro官網)

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

    【其他文章推薦】

    ※專營大陸空運台灣貨物推薦

    台灣空運大陸一條龍服務

  • 別翻了,這篇文章就是要讓你入門java多線程!

    別翻了,這篇文章就是要讓你入門java多線程!

    目錄

    就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時內心是小鹿亂撞啊….於是這幾天茶不思飯不想,好幾天深夜皆是輾轉反側,兩目深凝,以至於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,一般兩步,走在大馬路中央上差點被打~我承認太誇張了,感覺又要被打~。最終還是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像可以看的出來,這位朋友絕bi歷經滄桑,對生活無盡的坦然浩對,看透俗世凡塵、世態炎涼、趨炎附勢,擁有着極高的安心恬盪情懷…啥?啥子?這個是系統默認頭像….嗯嗯嗯呃。。。那個那個宜春啥都沒說哈,別把什麼事都扯宜春身上,你們一天天的,我啥都沒說(理直氣壯)…

    @

    1. 理解線程與進程

    由於併發肯定涉及到多線程,因此在進入併發編程主題之前,我們先來了解一下進程和線程的由來,這對後面對併發編程的理解將會有很大的幫助。

    進程和線程的對比這一知識點由於過於基礎,正因為過於基礎,所以我們更應該透徹它!我們必須掌握什麼是線程和進程,掌握線程與進程的關係、區別及優缺點 !

    1.1、何為進程?

    首先我們來看一下進程的概念:

    進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

    看完之後,是不是感覺很抽象?很懵bi?懵bi就對了,說明你和我智商一樣高….~開個玩笑~

    不妨先憋棄上面的概念,放鬆一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出遊戲並且保持微笑,然後正襟危坐心平氣和的看宜春寫的博客….

    這個時候的你不僅僅是愉快的擼了一把遊戲,而且還親自體驗擼了一把進程…其實在你雙擊打開LOL的時候就已經創建了進程,此話怎講?眾所周知,我們的電腦安裝的軟件比如:LOL、微信、谷歌等等都是存儲在我們的硬盤上的,硬盤上的數據可以說是永久存儲(ORM),當我們雙擊LOL的時候,LOL程序執行就進入了內存中,所有的程序必須進入內存中才能執行,內存屬於臨時存儲(RAM),而進入內存的程序都可以叫做是進程,把LOL程序退出的時候,LOL程序就會退出內存,進程也就隨之銷毀了!因此說各位擼了一把進程也不為過吧。

    啥?字太多了,看的不夠明了,不如看圖得勁….額。。。

    上面主要是通過抽象的描述了進程,其實進程是可以很直觀的看的到的,我們可以再電腦底部任務欄,右鍵—–>打開任務管理器,可以查看當前任務的進程:

    其實,關於線程博主我完全可以一兩句話概括,但是這樣並不負責,畢竟這篇文章標題就是要讓你徹底入門java多線程。如果連進程都理解不好談何徹底理解多線程?

    1.2、何為線程?

    同樣的,我們先來看線程的概念

    線程是進程中的一個執行單位,負責當前進程中程序的執行。一個進程中至少有一個線程,也就是說一個進程可以有多個線程的,而多個線程的進程運用程序就叫做多線程程序

    線程的概念稍微好理解很多,但是想更深層次的去理解光靠上面一段文字的概述是完全不夠的!

    這不打LOL的過程中,屬實卡的一批,果然花高價998買的6手戴爾筆記本打LOL屬實像極了愛情。這個時候不得不雙擊打開電腦安全管家進行殺毒,果然2500天沒有進行過病毒查殺,我天。。。其實我相信很多人都用過電腦管家或者手機管家之類的安全軟件,我們都很清楚我們開啟病毒查殺之後一般要幾分鐘掃描查殺,這個時候我們是可以讓它後台進行的,我們不會等而是開啟另一個垃圾清理的功能,這個時候我們也不會等而是再去啟動電腦加速功能。等到 這些操作都完成之後果斷退出電腦管家,繼續LOL,果然高價998買的6手戴爾筆記本再怎麼殺毒打LOL還是照樣的卡….

    其實清楚線程必然涉及到CPU的相關概念了,將上面文字所描述的用圖片概括,大致為:

    1.3、何為多線程?

    從上一節中,我們也提到過多線程,所以理解起來應該不難。

    多線程就是多個線程同時運行交替運行

    單核CPU:交替運行。
    多核CPU:同時運行。

    其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。

    1.4、何為線程調度優先級?

    說起線程調度優先級這個概念,就讓我想到現在我們大部分人投簡歷一樣。如果你的學歷或者工作經驗越高,那麼你的優先級就越高,面試官很大幾率就會讓你去面試但也不是一定只是幾率特別大,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性)!在我們每個人的電腦中線程是可以設置線程的優先級的,但是生活中沒有優先級(學歷、工作經驗)的孩子就只能靠自己的能力了~媽耶,太真實了…~

    線程優先級具有繼承特性比如A線程啟動B線程,則B線程的優先級和A是一樣的。

    線程優先級具有隨機性也就是說線程優先級高的不一定每一次都先執行完,只是被執行的可能性更大。

    在今後的多線程學習旅遊中我們會使用到getPriority()方法獲取線程的優先級。

    1.5、為什麼提倡使用多線程而不是多進程?

    線程與進程相似,但線程是一個比進程更小的執行單位,是程序執行的最小單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。同時線程是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是因為線程間的切換和調度的成本遠遠小於進程。

    而使用多線程,多線程會將程序運行方式從串行運行變為併發運行,效率會有很大提高。

    2、理解并行和併發

    在博主認為併發和并行是兩個非常容易被混淆的概念。為了防止繞暈大家,所以我選擇長話短說!

    1. 併發:一個時間段內同時發生(並不是同時發生)。
    2. 并行:同一時刻發生(真正的同時發生)。

    它們都可以表示兩個或者多個任務一起執行,但是偏重點有些不同。

    於此同時,我們不妨回顧一下上面所提到過的CPU,並再次理解併發與并行的區別,從而溫故知新 ~我TM簡直是個天才!~

    單核CPU:交替運行【併發】
    多核CPU:同時運行【并行】

    併發給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的!

    3、特殊的一個單線程:主線程(Main線程)

    我們常說的主線程就是Main線程,它是一個特殊的單線程,話不多說,直接擼碼:

    定義一個用於測試的demo類Person

    package demo;
    
    public class Person {
       public String name;
    
       public Person(String name){
           this.name=name;
       }
    
       public void run(){
           int i=1;
           while (i<5){
               System.out.println(name+i);
               i++;
           }
       }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    編寫Main方法

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
    
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    運行結果就已經很顯而易見了,放心我不是靠你們運行結果而是單純的先分析主線程。

    運行結果:
        常威1
        常威2
        常威3
        常威4
        來福1
        來福2
        來福3
        來福4

    3.1、分析主線程原理

    3.2、 單線程的局限性

    單線程不僅效率低下,而且存在很大的局限性,惟一的優點就是安全。所以說女孩子長得安全其實也是一種優點,噗哈哈哈…

    如何體現出單線程效率低下以及它的局限性呢?其實只要一句代碼即可,還是以上面的單線程Main線程為例:

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
            int a=6/0;  //=====================特別注意這行代碼
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    試想一下運行結果…

    如果對上面的運行結果有問題,或者疑問。那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),很有可能異常機制沒學好,好吧我給你貼出來:

    言歸正傳,效率低下何以見得?這是數據少,如果是一億條數據呢,單線程就是一個一個打印。那局限性又何以見得呢?從上面運行結果來看也能看出,只因為一行代碼而導致下面代碼不再執行。已經很明顯了。

    4、 創建多線程的四種方式

    說是說創建多線程有四種方式,但考慮到是入門文章還是主要寫入門的兩種方式,剩下的兩個暫時忽略。忽略的兩種方法有:實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorServiceCallableFuture實現有返回結果的線程。現在可能對於入門的童鞋來說是接收不了的,以後再去了解也不晚!

    4.1、繼承Thread類

    Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來代表這段程序流。

    Java中通過繼承Thread類來創建啟動多線程的步驟如下:

    1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱為線程執行體。
    2. 創建Thread子類的實例,即創建了線程對象
    3. 調用線程對象的start()方法來啟動該線程

    代碼如下:

    測試類:

    public class Demo01 {
        public static void main(String[] args) {
            //創建自定義線程對象
            MyThread mt = new MyThread("新的線程!");
            //開啟新線程
            mt.start();
            //在主方法中執行for循環
            for (int i = 0; i < 10; i++) {
                System.out.println("main線程!"+i);
            }
        }
    }

    自定義線程類:

    public class MyThread extends Thread {
        //定義指定線程名稱的構造方法
        public MyThread(String name) {
            //調用父類的String參數的構造方法,指定線程的名稱
            super(name);
        }
        /**
         * 重寫run方法,完成該線程執行的邏輯
         */
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName()+":正在執行!"+i);
            }
        }
    }

    Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新線程並執行自己定義的run()方法。

    4.2、實現Runnable接口

    如果自己的類已經繼承另一個類,就無法直接繼承Thread,此時,可以實現一個Runnable接口來創建線程,顯然實現Runnable接口方式創建線程的優勢就很明顯了。

    直接擼碼:

    自定義一個類實現Runnable接口,並重寫接口中的run()方法,併為run方法添加要執行的代碼方法。

    public class RunableDemo implements Runnable{
    
        @Override
        public void run() {
            int a = 1;
            while (a<20){
                System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()為獲取當前線程的名字
                a++;
            }
        }
    }

    編寫Main方法

    為了啟動自定義類RunableDemo ,需要首先實例化一個Thread,並傳入RunableDemo 實例

    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            //實例化一個Thread並傳入自己的RunableDemo 實例
            Thread thread=new Thread(runn);
            thread.start();
    
            int a = 1;
            while (a<20){
                //Thread.currentThread().getName()為獲取當前線程的名字
                System.out.println(Thread.currentThread().getName()+ a);
                a++;
            }
        }
    }

    運行結果:

    main1
    main2
    main3
    Thread-01
    Thread-02
    Thread-03
    Thread-04
    Thread-05
    Thread-06
    ....

    其實多運行幾遍,你會方法每次運行的結果順序都不一樣,這主要是由於多線程會去搶佔CPU的資源,誰搶到了誰就執行,而Main和Thread兩個線程一直在爭搶。

    實際上,當傳入一個Runnable target(目標)參數給Thread后,Threadrun()方法就會調用target.run(),參考JDK源代碼:

    public void run() {  
      if (target != null) {  
       target.run();  
      }  
    }  

    4.3、兩種入門級創建線程的區別

    採用繼承Thread類方式:

    (1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
    (2)缺點:因為線程類已經繼承了Thread類,所以不能再繼承其他的父類。

    採用實現Runnable接口方式:

    (1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相
    同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
    (2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。

    小結:
    如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

    實現Runnable接口比繼承Thread類的優勢:

    1.適合多個相同代碼的線程去處理同一個資源。

    2.可以避免java中單繼承的限制。

    3.增加代碼的健壯性,實現解耦。代碼可以被多個線程共享,代碼和數據獨立。

    4.線程池中只能放入實現Runnable或Callable類線程,不能放入繼承Thread的類【線程池概念之後會慢慢涉及】

    所以,如果選擇哪種方式,盡量選擇實現Runnable接口

    其實學到後面的線程池,你會發現上面兩種創建線程的方法實際上很少使用,一般都是用線程池的方式比較多一點。使用線程池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到“線程資源必須通過線程池提供,不允許在應用中自行显示創建線程”。不過處於入門階段的童鞋博主還是強烈建議一步一個腳印比較好!

    5、使用匿名內部類方式創建線程

    談起匿名內部類,可能很多小白是比較陌生的,畢竟開發中使用的還是比較少,但是同樣是非常重要的一個知識!於此同時我就貼出關於匿名內部類的文章如果小白童鞋能看懂下面這個代碼,真的你不需要看那篇文章了,你T喵的簡直是個天才!

    package AnonymousInner;
    
    public class NiMingInnerClassThread {
        public static void main(String[] args) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i<5;i++){
                        System.out.println("熊孩子:"+i);
                    }
                }
            };
            new Thread(r).start();
            for (int i = 0; i < 5 ; i++){
                System.out.println("傻狍子:"+i);
            }
        }
    }

    小白童鞋還愣着幹啥呀趕緊去補補…

    6、線程安全問題

    線程安全問題主要是共享資源競爭的問題,也就是在多個線程情況下,一個或多個線程同時搶佔同一資源導致出現的一些不必要的問題,最典型的例子就是火車四個窗口售票問題了,這裏就不再舉售票例子了,已經爛大街了,這裏就簡單實現一個線程安全問題代碼….

    實現Runnable接口方式為例,主要實現過程是:實例化三個Thread,並傳入同一個RunableDemo 實例作為參數,最後開啟三條相同參數的線程,代碼如下:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
        
        @Override
        public void run() {
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }
    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            Thread thread1=new Thread(runn);
            Thread thread2=new Thread(runn);
            Thread thread3=new Thread(runn);
            thread1.start();
            thread2.start();
            thread3.start();
            }
     }

    運行結果:

    Thread-0==100
    Thread-0==99
    Thread-1==100
    Thread-1==97
    Thread-1==96
    Thread-1==95
    Thread-2==98
    ...

    根據結果可以看出,確實是三條線程(Thread-0、1、2)在執行,安全問題就出在線程會出現相同的結果比如上面的100就出現了兩次,如果循環條件更改一下可能也會出現負數的情況。這種情況該怎麼解決呢?這個時候就需要線程同步了!

    7、解決線程安全問題:線程同步

    實際上,線程安全問題的解決方法有三種:

    1、同步代碼塊
    2、同步方法
    3、鎖機制

    7.1、 synchronized同步代碼塊

    第一種方法:同步代碼塊

    格式:

    synchronized(鎖對象) {
    可能會出現線程安全問題的代碼(訪問共享數據的代碼)
    }

    使用同步代碼塊特別注意:
    1、通過代碼塊的鎖對象,可以是任意對象
    2、必須保證多個線程使用的鎖對象必須是同一個
    3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行

    還是以上麵線程安全問題為例子,使用同步代碼塊舉例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        Object object=new Object(); //事先準備好一個鎖對象
    
        @Override
        public void run() {
            synchronized (object){  //使用同步代碼塊
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            }
        }
    }

    Main方法沒有任何改動,運行一下結果是絕對沒問題的,數據都是正確的沒有出現重複情況這一出,各位可以自己嘗試一下!

    同步代碼塊的原理:

    使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之後當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
    同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
    缺點:頻繁的獲取釋放鎖對象,降低程序效率

    7.2、同步方法

    使用步驟:

    1、把訪問了共享數據的代碼抽取出來,放到一個方法中
    2、在該方法上添加 synchronized 修飾符

    格式:

    修飾符 synchronized 返回值類型 方法名稱(參數列表) {
      方法體...
    }

    代碼示例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        @Override
        public void run() {
            while (true){
                sell(); //調用下面的sell方法
            }
        }
        
        //訪問了共享數據的代碼抽取出來,放到一個方法sell中 
        public synchronized void sell(){
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰。

    說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據     =====此時共享數據也要加上static
    
        @Override
        public void run() {
            while (true){
                sell();
            }
        }
    
        public static synchronized void sell(){  //注意添加了static關鍵字
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    使用靜態同步方法時,此時共享數據也要加上static,因為static成員才能訪問static成員,如果對static關鍵字不是他別理解的可以補補了,放心,博主有信心讓你有所收穫,會讓你重新認識到static的魅力:

    當然靜態同步方法了解即可!

    7.3、Lock鎖

    Lock接口位於java.util.concurrent.locks.Lock它是JDK1.5之後出現的,Lock接口中的方法:

    void lock(): 獲取鎖

     

    void unlock(): 釋放鎖

    Lock接口的一個實現類java.util.concurrent.locks.ReentrantLock implements Lock接口

    使用方法:
    1、在Runable實現類的成員變量創建一個ReentrantLock對象
    2、在可能產生線程安全問題的代碼該對象調用lock方法獲取鎖
    3、在可能產生線程安全問題的代碼該對象調用unlock方法釋放鎖

    代碼示例:

    import java.util.concurrent.locks.ReentrantLock;
    
    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據
    
        //1、在Runable實現類的成員變量創建一個ReentrantLock對象============
        ReentrantLock reentrantLock = new ReentrantLock();
    
        @Override
        public void run() {
            // 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖=======
            reentrantLock.lock();
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            // 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖======
            reentrantLock.unlock();
        }
    
    }

    當然更安全的寫法是,在線程安全問題代碼中try...catchy,最後在finally語句中添加reentrantLock.unlock();,這樣方為上上策!

    7.4、三種方法小結

    第一種
    synchronized 同步代碼塊:可以是任意的對象必須保證多個線程使用的鎖對象是同一個

     

    第二種
    synchronized 同步方法: 鎖對象是this,誰調用鎖對象就是誰

     

    synchronized 靜態同步方法: 鎖對象是其class對象,該對象可以用this.getClass()方法獲取,也可以使用當前類名.class 表示。【了解即可】

     

    第三種
    Look鎖方法:該方法提供的方法遠遠多於synchronized方式,主要在Runable實現類的成員變量創建一個ReentrantLock對象,並使用該對象調用lock方法獲取鎖以及unlock方法釋放鎖!

    8、線程常用方法

    8.1、Thread類

      Thread():用於構造一個新的Thread。

      Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。

      Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該

      線程使用了指定target的run方法。

      currentThread():獲得當前運行線程的對象引用。

      interrupt():將當前線程置為中斷狀態。

      sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少為指定毫秒數。

      join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束后才繼續本線程。

      yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其他就緒線程執行。

    8.2、Object類

      wait():讓當前線程進入等待阻塞狀態,直到其他線程調用了此對象的notify()或notifyAll()方法后,當前線程才被喚醒進入就緒狀態。

      notify():喚醒在此對象監控器(鎖對象)上等待的單個線程。

      notifyAll():喚醒在此對象監控器(鎖對象)上等待的所有線程。

    注意:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每個對象只有一個,所以這些方法定義在Object類中,而不是Thread類中。

    8.3、yield()、sleep()、wait()比較

       wait():讓線程從運行狀態進入等待阻塞狀態,並且會釋放它所持有的同步鎖。

       yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。

       sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。

    9、線程的狀態

    以上只是簡單的一個線程狀態圖,其實線程狀態博大精深,要講清楚還是要一大篇文筆,作為入門文章先了解一下吧,之後的併發編程文章將再講述吧!

    如果想要去深入了解一下的話也是可以的:

    10、線程池

    在java中只要說到池,基本都是一個套路,啥數據庫連接池、jdbc連接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建資源而消耗過多資源。

    10.1、線程池概述

    線程池其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

    合理利用線程池能夠帶來三個好處:

    1. 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
    2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
    3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

    10.2、 線程池的使用

    Java裏面線程池的最頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService

    要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

    Executors類中有個創建線程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

    獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法如下:

    • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

    Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

    使用線程池中線程對象的步驟:

    1. 創建線程池對象。
    2. 創建Runnable接口子類對象。(task)
    3. 提交Runnable接口子類對象。(take task)
    4. 關閉線程池(一般不操作這一步)。

    Runnable實現類代碼:

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我要一個游泳教練");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("教練來了: " + Thread.currentThread().getName());
            System.out.println("教我游泳,教會後,教練又回到了游泳池");
        }
    }

    線程池測試類:

    public class ThreadPoolDemo {
        public static void main(String[] args) {
            // 創建線程池對象
            ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
            // 創建Runnable實例對象
            MyRunnable r = new MyRunnable();
    
            //自己創建線程對象的方式
            // Thread t = new Thread(r);
            // t.start(); ---> 調用MyRunnable中的run()
    
            // 從線程池中獲取線程對象,然後調用MyRunnable中的run()
            service.submit(r);
            // 再獲取個線程對象,調用MyRunnable中的run()
            service.submit(r);
            service.submit(r);
            // 注意:submit方法調用結束后,程序並不終止,是因為線程池控制了線程的關閉。
            // 將使用完的線程又歸還到了線程池中
            // 關閉線程池
            //service.shutdown();
        }
    }

    以上只是簡單的使用線程池,僅僅是入門階段!道阻且長,路還很長….

    到這裏,本文章入門暫時告一段落,以後有時間盡量抽空更新….

    如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝你~

    歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 三、netcore跨平台之 Linux配置nginx負載均衡

    三、netcore跨平台之 Linux配置nginx負載均衡

    前面兩章講了netcore在linux上部署以及配置nginx,並讓nginx代理webapi。

    這一章主要講如何配置負載均衡,有些步驟在前兩章講的很詳細了,所以這一章我就不會一個個截圖了。

    因為本人只有一個服務器。所以我會在同一台服務器上部署兩套差不多的實例。

    同樣的代碼,我們在Program.cs進行了修改,如圖所示:

    這裏我把原來的端口6666改成了8888

     

     然後你可以改一改你的接口部分的代碼,便於讓你更好的看到效果。

    這裏把value1和value2改成value3和value4,這裡是為了看到測試效果,在實際的開發中這裏不用改。

     

     然後發布和上傳到服務器,如何發布和上傳,我在第一章有講到:https://www.cnblogs.com/dengbo/p/11878766.html

    注意的是你同樣的地方新建一個新的目錄保存你新上傳的程序,netcore是我第一章建立的,netcore1是新建的,

    你把你新的發布包放在netcore即可。如圖:

    上傳結束后,在這個目錄中運行你的程序,輸入下面的命令

    dotnet WebApiTest.dll   --server.urls "http://*:8888"

    如圖所示

     

     然後去看看你的接口是否正常

     

     

    好了,這裏的準備工作完成了,下面我們進入到nginx的配置的目錄中

    輸入下面的命令:

    cd /usr/local/nginx/conf

    然後對文件進行編輯

    vim nginx.conf

     

     我們需要在這裏修改一下配置。

    在如圖的server的平級添加如下的代碼

    upstream NgWebApi {
                    server localhost:6666;
                    server localhost:8888;
        }

    上面的 NgWebApi是隨意寫的名稱,不要糾結這裏。

    然後在修改 proxy_pass後面的內容:

    proxy_pass http://NgWebApi;

    最終的結果如下:

     

     這樣你就修改完成,輸入:wq退出並保存即可。

    最後檢查並重啟nginx

    /usr/local/nginx/sbin/nginx -t
    /usr/local/nginx/sbin/nginx -s reload

    最後不要忘記把你的8888端口的webapi啟動一下。

    這裏我務必要提醒你,請進入到你的程序的目錄中執行這段代碼,

    cd /root/netcore1
    dotnet WebApiTest.dll   --server.urls "http://*:8888"

    啟動如下:

     

     

     好了,配置結束了,下面我們來測試下

     

    還是昨天的那個網站進行測試   https://www.sojson.com/httpRequest/

     

     

     

    多次發送請求會出現下面的響應

     

     

    看到上面兩個請求,就說明你配置成功了,是不是很簡單。

    上面這種配置,系統會採用默認的輪詢訪問不同的端口,nginx作為強大的反向代理,強大的遠遠不止這裏

    下面簡單講講分發策略。

    1)、輪詢 ——輪流處理請求(這是系統默認的)

          每個請求按時間順序逐一分配到不同的應用服務器,如果應用服務器down掉,自動剔除它,剩下的繼續輪詢,如果您的服務器都差不多,建議這個。 

    2)、權重 ——誰的設置的大,誰就承擔大部分的請求

          通過配置權重,指定輪詢幾率,權重和訪問比率成正比,用於應用服務器性能不均的情況,有時候你買的服務器可能參差不齊,有的性能強大

        有的一般,你可以通過設置權重,把服務器性能強大權重設置大一點,這樣可以合理分配壓力。 

    3)ip_哈希算法

          每一次的請求按訪問iphash結果分配,這樣每個訪客固定訪問一個應用服務器,可以解決session共享的問題。

     

     

    關於權重的策略,如下圖示的 你只要加一個  weight=6 即可這裏不一定是6,是整數都行。

     

     

     然後保存即可

    這裏不要忘記重啟nginx,以及運行8888端口的程序了,如果你不會,可以看前面的部分

    最後我們看看效果

    結果和上面的測試結果差不多,唯一不同的是出現下面這個結果的次數要大於另外一個的。

     

     

    到這裏就結束了,感謝觀看。

     

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

    【其他文章推薦】

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

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

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

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

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • 利用SSH隧道技術穿越內網訪問遠程設備

    利用SSH隧道技術穿越內網訪問遠程設備

    本文為作者原創,轉載請註明出處:

    通常,我們用於調試的計算機無法遠程訪問位於局域網中的待調試設備。通過 ssh 的端口轉發(又稱 ssh 隧道)技術,可以實現這種遠程調試功能。

    下文中,sshc 指 ssh 客戶端,sshd 指 ssh 服務器。

    1. ssh 端口轉發模式簡介

    ssh 客戶端運行於本地機器,它的作用是:登錄到目標機器並在目標機器上執行命令。它可以建立一個安全通道,為不安全網絡上兩個不受信任的主機提供安全的加密通信。X11 連接、任意 TCP 端口和 UNIX 域套接字也可以通過 ssh 安全通道進行轉發。

    ssh 連接並登錄到指定的主機名(用戶名可選)。如果指定了命令,命令將在遠程主機上執行,而不是在本機 shell 里執行。

    1.1 ssh 常用選項簡介

    ssh 端口轉發相關的常用選項如下:

    -C

    請求壓縮所有數據(包括 stdin、stdout、stderr 和用於轉發的 X11、TCP 和 UNIX 域連接的數據)。壓縮算法與 gzip 使用的算法相同,壓縮級別由 ssh 協議版本 1 的 CompressionLevel 選項控制。在調製解調器線路和其他慢速連接上採用壓縮是可取的,但它會減慢快速網絡上的速度。

    -f

    請求 ssh 在執行命令之前轉到後台。如果用戶希望 ssh 在後台運行,但 ssh 需要用戶提供密碼或口令,使用 -f 選項就很有用,在用戶輸入密碼之後,ssh 就會轉入後台運行。這個選項隱含了 -n 選項的功能(-n 選項將 stdin 重定向到 /dev/null,從而避免後台進程讀 stdin)。在遠程站點上啟動 X11 程序的推薦方法是使用 “ssh -f host xterm” 。

    如果 ExitOnForwardFailure 配置選項設置的是 “yes”,則使用 -f 選項啟動的 ssh 客戶端會等所有的遠程端口轉發建立成功后才將自己轉到後台運行。

    -n

    將 stdin 重定向到 /dev/null (實際上是為了防止後台進程從stdin讀取數據)。當 ssh 在後台運行時必須使用此選項。

    一個常見的技巧是使用它在目標機器上運行 X11 程序。例如,ssh -n shadow.cs.hut.fi emacs & 將在 shadows.cs.hut.fi 上啟動 emacs 程序。X11 的連接將通過加密通道自動轉發。ssh 程序將在後台運行。(如果 ssh 需要請求密碼或口令,則此操作無效;參見-f選項。)

    -N

    不執行遠程命令。此選項用於只需要端口轉發功能時。

    -g

    允許遠程主機連接到本地轉發端口。如果用於多路復用連接,則必須在主進程上指定此選項。

    -t

    強制分配一個偽終端。在目標機上執行任意的基於屏幕的程序時(例如,實現菜單服務),分配偽終端很有用。使用多個 -t 選項則會強制分配終端,即使 ssh 沒有本地終端。

    -T

    禁止分配偽終端。

    -L [bind_address:]port:host:hostport
    -L [bind_address:]port:remote_socket
    -L local_socket:host:hostport
    -L local_socket:remote_socket

    數據從本機轉發到遠程。本機上指定 TCP 端口或 UNIX 套接字的連接將被轉發到目標機上指定端口或套接字。

    上述參數中,bind_address 指本地地址;port 指本地端口;local_socket 指本地 UNIX 套接字;host 指遠程主機地址;hostport 指遠程端口;remote_socket 指遠程 UNIX 套接字。

    本地(ssh 客戶端)與遠程(ssh 服務端)建立一條連接,此連接的形式有四種:

    本地 [bind_address:]port    <====>   遠程 host:hostport  
    本地 [bind_address:]port    <====>   遠程 remote_socket  
    本地 local_socket           <====>   遠程 host:hostport  
    本地 local_socket           <====>   遠程 remote_socket  

    位於本機的 ssh 客戶端會分配一個套接字來監聽本地 TCP 端口(port),此套接字可綁定本機地址(bind_address, 可選,本機不同網卡具有不同的 IP 地址)或本地 UNIX 套接字(local_socket)。每當一個連接建立於本地端口或本地套接字時,此連接就會通過安全通道進行轉發。

    也可在配置文件中設置端口轉發功能。只有超級用戶可以轉發特權端口。

    默認情況下,本地端口是根據 GatewayPorts 設置選項綁定的。但是,使用顯式的bind_address 可將連接綁定到指定地址。bind_address 值是 “localhost”時,表示僅監聽本機內部數據[TODO: 待驗證],值為空或“*”時,表示監聽本機所有網卡的監聽端口。

    注意:localhost 是個域名,不是地址,它可以被配置為任意的 IP 地址,不過通常情況下都指向 127.0.0.1(ipv4)和 。127.0.0.1 這個地址通常分配給 loopback 接口。loopback 是一個特殊的網絡接口(可理解成虛擬網卡),用於本機中各個應用之間的網絡交互。

    GatewayPorts 說明 (查閱 man sshd_config):指定是否允許遠程主機(ssh客戶端)連接到本機(ssh服務端)轉發端口。默認情況下,sshd(8)將遠程端口轉發綁定到環回地址,這將阻止其他遠程主機連接到本機轉發端口。GatewayPorts 也可設置為將將遠程端口轉發綁定到非環回地址,從而允許其他遠程主機連接到本機。GatewayPorts 值“no”,表示強制遠程端口轉發僅對本機可用;值“yes”,表示強制遠程端口轉發綁定到通配符地址;值“clientspecified”,表示允許客戶端選擇轉發綁定到的地址。默認是“no”。

    -R [bind_address:]port:host:hostport
    -R [bind_address:]port:local_socket
    -R remote_socket:host:hostport
    -R remote_socket:local_socket

    此選項在本地機上執行,目標機上指定 TCP 端口或 UNIX 套接字的連接將被轉發到本機上指定端口或套接字。

    上述參數中,bind_address 指遠程地址;port 指遠程端口;remote_socket 指遠程 UNIX 套接字;host 指本地地址;hostport 指本地端口;local_socket 指本地 UNIX 套接字。

    工作原理:位於遠程的 ssh 服務端會分配一個套接字來監聽 TCP 端口或 UNIX 套接字。當目標機(服務端)上有新的連接建立時,此連接會通過安全通道進行轉發,本地機執行當前命令的進程收到此轉發的連接后,會在本機內部新建一條 ssh 連接,連接到當前選項中指定的端口或套接字。參 2.3 節分析。

    也可在配置文件中設置端口轉發功能。只有超級用戶可以轉發特權端口。

    默認情況下,目標機(服務端)上的 TCP 監聽套接字只綁定迴環接口。也可將目標機上的監聽套接字綁定指定的 bind_address 地址。bind_address 值為空或 “*” 時,表示目標機上的監聽套接字會監聽目標機上的所有網絡接口。僅當目標機上 GatewayPorts 設置選項使能時,通過此選項為目標機指定 bind_address 才能綁定成功(參考 sshd_config(5))。

    如果 port 參數是 ‘0’,目標機(服務端)可在運行時動態分配監聽端口並通知本地機(客戶端),如果同時指定了 “-O forward” 選項,則動態分配的監聽端口會被打印在標準輸出上。

    -D [bind_address:]port

    指定本地“動態”應用程序級端口轉發。它的工作方式是分配一個套接字來監聽本地端口(可選綁定指定的 bind_address)。每當連接到此端口時,連接都通過安全通道進行轉發,然後使用應用程序協議確定將遠程計算機連接到何處。目前支持 SOCKS4 和 SOCKS5 協議,ssh 將充當 SOCKS 服務器。只有 root 用戶可以轉發特權端口。還可以在配置文件中指定動態端口轉發。

    IPv6 地址可以通過將地址括在方括號中來指定。只有超級用戶可以轉發特權端口。默認情況下,本地端口是根據 GatewayPorts 設置選項進行綁定的。但是,可以使用顯式的 bind_address 將連接綁定到特定的地址。bind_address 值為 “localhost” 時表示監聽端口僅綁定為本地使用,而空地址或 “*” 表示監聽所有網絡接口的此端口。

    1.2 ssh 端口轉發模式

    ssh 的端口轉發有三種模式:

    • 本地:ssh -C -f -N -g -L local_listen_port:remote_host:remote_port agent_user@agent_host

      將本地機監聽端口 local_listen_port 上的數據轉發到遠程端口 remote_host:remote_port

    • 遠程:ssh -C -f -N -g -R agent_listen_port:local_host:local_port agent_user@agent_host

      將代理機監聽端口 agent_listen_port 上的數據轉發到本地端口 local_host:local_port

    • 動態:ssh -C -f -N -g -D listen_port agent_user@agent_host

    2. 利用 ssh 隧道建立遠程調試環境

    組網環境下設備角色如下:

    代理機:把一個具有公網 IP 的中間服務器用作 ssh 代理,將這台代理機稱作代理 A(Agent)。

    目標機:把待調試的目標機器稱作目標機 T(Target)。目標機通常是待調試的設備,處於局域網內,外網無法直接訪問內網中的設備。

    本地機:把調試用的本地計算機稱作本地機 L(Local)。本地機通常也位於局域網內。

    L 和 T 無法互相訪問,但 L 和 T 都能訪問 A。我們將 T 通過 ssh 連接到A,將 L 也通過 ssh 連接到A,A 用於轉發數據,這樣就能使用本地計算機 L 來訪問遠端設備 R。

    2.1 目標機 T (sshc)

    2.1.1 shell 中 T 連接 A

    目標機 T 上的 sshc 連接代理機 A 上的 sshd:

    ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    這條命令的作用:
    1. 建立一條 ssh 連接,T 上的 ssh 客戶端連接到 A 上的 ssh 服務器,A 的 IP 是 120.198.45.126,端口號是 10022,賬號是10022;
    2. 如果有其他 ssh 客戶端連接到了 A 的 10022 端口上,則 A 會將這條連接轉發到 T,T 在內部建立新的連接,連接到本機 22 端口。

    這條命令在 T 上執行。在 T 連接 A 這條命令里,T 是本地主機(local),A 是遠程主機(remote)。

    解釋一下此命令各選項:

    • -T 不分配偽終端;
    • -f 使 ssh 進程在用戶輸入密碼之後轉入後台運行;
    • -N 不執行遠程指令,即遠程主機(代理機A)不需執行指令,只作端口轉發;
    • -g 允許遠程主機(代理機A)連接到本地轉發端口;
    • -R 將遠程主機(代理機A)指定端口上的連接轉發到本機端口;
    • frank@120.198.45.126
      表示使用遠程主機 120.198.45.126 上的用戶 frank 來連接遠程主機;
    • :10022:127.0.0.1:22
      表示本機迴環接口(127.0.0.1,也可使用本機其他網絡接口的地址,比如以太網 IP 或 WiFi IP)的 22 端口連接到遠程主機的 10022 接口,因遠程主機 10022 綁定的地址為空,所以遠程主機會監聽其所有網絡接口的 10022 端口。

    在目標機 shell 中查看連接是否建立:

    root@localhost:~# ps | grep ssh
    22850 root      2492 S    ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    22894 root      3500 S    grep ssh

    在目標機 shell 中關閉 ssh 連接:

    kill -9 $(pidof ssh)

    此時在目標機 T 和代理機 A 中查看 ssh 連接信息,兩端都可以看到 ssh 連接不存在了。

    2.1.2 C 代碼中 T 連接 A 的處理

    C 代碼中主要還是調用 2.1.1 節中的命令。但是由 C 代碼編譯生成的進程無法在命令行和用戶進行交互,因此要避免交互問題。

    1. 避免首次連接時的 y/n(或yes/no) 詢問

    如果是首次登錄代理機 A,本機(目標機 T)沒有 A 的信息,需用用戶手動輸入 y 之後才能繼續。打印如下:

    root@localhost:~# ssh -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    Host '120.198.45.126' is not in the trusted hosts file.
    (ssh-rsa fingerprint md5 86:09:0c:1b:fd:0b:02:8c:29:62:7f:ff:70:1b:64:f5)
    Do you want to continue connecting? (y/n) 

    如果 T 上有 A 的信息,可通過執行刪除操作:rm ~/.ssh/known_hosts 再進行上述測試。

    如果是在 C 代碼中執行登錄命令,進程在後台自動運行,是無法和用戶進行交互的。為了避免交互動作,應該禁止 ssh 發出 y/n 的詢問。

    如果 ssh 客戶端是 dropbear ssh,則添加 -y 參數,如下:

    ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    如果 ssh 客戶端是 openssh,則添加 -o StrictHostKeyChecking=no 選項,如下:

    ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    2. 避免輸入登錄密碼

    避免由用戶手動輸入登錄密碼有如下方法:

    1) 用 ssh-copy-id 把本地主機的公鑰複製到遠程主機的authorized_keys文件上,登錄不需要輸入密碼。
    2) 用 expect 調用 shell 腳本,向 shell 腳本發送密碼。這種方式是模擬鍵盤輸入。
    3) 如果是 openssh,則用 sshpass 向 ssh 命令行傳遞密碼。如果是 dropbear,則通過 DROPBEAR_PASSWORD 環境變量向 ssh 命令行傳遞密碼。

    我們採用第 3 種方法。

    假如代理機 A 上用戶 frank 密碼是 123456,則最終 C 代碼里應執行的指令如下:

    # openssh
    sshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    # dropbear
    DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    dropbear 無法接收 DROPBEAR_PASSWORD 變量傳遞密碼的處理方法:

    dropbear 包含 ssh 客戶端和 ssh 服務器,體積小巧,常用於嵌入式設備。dropbear ssh 無法接收 sshpass 傳入的密碼信息。但 dropbear ssh 可以通過環境變量 DROPBEAR_PASSWORD 傳入密碼信息。openwrt 從某一版開始,通過打補丁的方式禁用了 DROPBEAR_PASSWORD 選項,我們可以找到對應的補丁,開啟 DROPBEAR_PASSWORD 選項,再重新編譯生成 dropbear。如下:

    修改 dropbear patch 文件(如下路徑位於 openwrt 源碼根目錄):

    vim package/network/services/dropbear/patches/120-openwrt_options.patch

    將如下幾行刪除:

    @@ -226,7 +226,7 @@ much traffic. */
      * note that it will be provided for all "hidden" client-interactive
      * style prompts - if you want something more sophisticated, use 
      * SSH_ASKPASS instead. Comment out this var to remove this functionality.*/
    -#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"
    +/*#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD"*/
     
     /* Define this (as well as ENABLE_CLI_PASSWORD_AUTH) to allow the use of
      * a helper program for the ssh client. The helper program should be

    重新編譯生成 dropbear,並替換設備里已安裝的 dropbear。

    #define DEFAULT_SSH_AGENT_HOST      "120.198.45.126"
    #define DEFAULT_SSH_AGENT_PORT      "10022"
    #define DEFAULT_SSH_AGENT_USER      "ssha_debug"
    #define DEFAULT_SSH_AGENT_PASSWD    "220011ssha"
    int login_to_ssh_agent(const char *host, const char *port, const char *user, const char *passwd)
    {
        // openssh client:
        // sshpass -p '123456' ssh -o "StrictHostKeyChecking no" -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
        // dropbear ssh clent:
        // DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
        char cmd[256];
        snprintf(cmd, sizeof(cmd), "DROPBEAR_PASSWORD='%s' ssh -y -T -f -N -g -R :%s:127.0.0.1:22 %s@%s", 
                 (passwd != NULL) ? passwd : DEFAULT_SSH_AGENT_PASSWD, 
                 (port != NULL) ? port : DEFAULT_SSH_AGENT_PORT,
                 (user != NULL) ? user : DEFAULT_SSH_AGENT_USER,
                 (host != NULL) ? host : DEFAULT_SSH_AGENT_HOST);
        printf("login to ssh agent: \n%s\n", cmd);
        system(cmd);
    
        return 0;
    }

    2.2 代理機 A (sshd)

    在 /etc/ssh/sshd_config 中添加如下幾行后重啟 ssh 服務:

    GatewayPorts yes
    UseDNS no
    GSSAPIAuthentication no

    目標機 T 發起連接后,在代理機 A 上查詢目標機 T 是否連接成功:

    sudo netstat -anp | grep 10022

    打印形如:

    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      8264/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      8264/sshd: frank

    上述打印中,8264 就是和目標機 T 保持連接的 sshd 進程號,如需關閉當前連接重新建立一個新的連接,則先在代理機 A 上執行:

    kill -9 8264

    然後再執行 2.1 節的指令,就會建立一次新的代理連接。

    為了安全,我們可以專門新建一個用戶,僅用於 ssh 端口轉發功能,不能在 shell 中使用此用戶登錄。如下創建一個 ssha_debug 的用戶,無 shell 登錄權限。然後為此用戶創建密碼。注意系統中 nologin 文件的位置,不同系統可能路徑不同。

    sudo useradd ssha_debug -M -s /usr/sbin/nologin
    sudo passwd ssha_debug

    2.3 本地機 L (sshc)

    2.3.1 本地機 L 登錄目標機 T

    有三種方式:

    1. 在本地機 L 上通過 ssh 登錄代理機 A,在 A 的 shell 中再登錄目標機 T

    代理服務器的公網 ip 是 120.198.45.126,內網 ip 是 192.168.1.102。

    1) 先使用 ssh(SecureCRT 或 OpenSSH 命令行) 登錄上代理服務器的 shell。如果調試機在內網,既可登錄代理機的外網 ip,也可登錄其內網 ip。

    2) 在代理機的 shell 中執行如下命令登錄遠程設備:

    ssh -p 10022 root@127.0.0.1 -vvv

    注意,此命令中用戶 root 及其密碼是遠程設備上的賬戶。

    如果提示 Host key 認證失敗之類的信息,請按提示執行如下命令:

    ssh-keygen -f "/home/frank/.ssh/known_hosts" -R [127.0.0.1]:10022

    也可直接刪除當前用戶目錄下的 .ssh/known_hosts 文件。
    然後重新執行登錄設備操作。

    建議優先使用此方法。

    2. 在本地機 L 上使用 ssh 命令登錄目標機 T

    Win 10 系統默認安裝有 OpenSSH 客戶端。可以在調試機 Windows 命令行中執行:

    ssh -p 10022 root@120.198.45.126 -vvv

    對於本地計算機來說,待調試的設備 ip 地址不可見。本機登錄到代理機 120.198.45.126 的轉發端口 10022,通過代理機轉發功能,本地機能成功登錄到遠程設備上。注意,此命令中用戶 root 及其密碼是設備上的賬戶,不是 SSH 代理服務器上的賬戶。

    如果出現認證失敗之類的信息。可刪除 C:/Users/當前用戶/.ssh/known_hosts 文件,然後再試。

    3. 在本地機 L 上使用 SecureCRT 工具登錄目標機 T

    也可以直接使用 SecureCRT 軟件,設置好代理機的 ip(120.198.45.126) 和端口號(10022),填上設備的登錄用戶和登錄密碼。

    不建議使用此方法。因為連接過程太長或連接失敗的話,無法看到錯誤提示信息。

    2.3.2 查看代理機 A 打印信息

    在 L 執行登錄 T 之前查看打印信息:

    frank@SERVER:~$ sudo netstat -anp  | grep 10022
    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      106438/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      106438/sshd: frank

    在 L 執行登錄 T 之後查看打印信息:

    frank@SERVER:~$ sudo netstat -anp  | grep 10022
    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      106438/sshd: frank
    tcp        0      0 192.168.1.102:10022     120.229.163.51:27027    ESTABLISHED 106438/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      106438/sshd: frank

    可以看到,上述第二行是 L 執行登錄命令后新出現的打印信息。表示新建立了一條 L 到 A 的 ssh 連接。

    L 端的外網地址 120.229.163.51:27027 連接到 A 上的 192.168.1.102:10022,L 通常位於局域網內、具有一個內網地址,120.229.163.51 可能是 L 連接的路由器的公網 IP。

    這條連接建立后,A 將這條連接轉發到 R。

    2.3.3 查看目標機 T 打印信息

    在 L 執行登錄 T 之前查看打印信息:

    root@localhost:~# netstat -anp | grep 22
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      917/sshd
    tcp        0      0 192.168.202.140:47989   120.198.45.126:22       ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:22      192.168.202.100:64737   ESTABLISHED 2041/sshd
    tcp        0      0 :::22                   :::*                    LISTEN      917/sshd

    在 L 執行登錄 T 之後查看打印信息:

    root@localhost:~# netstat -anp | grep 22
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      917/sshd
    tcp        0      0 192.168.202.140:47989   120.198.45.126:22       ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:51732   192.168.202.140:22      ESTABLISHED 9452/ssh
    tcp        0      0 192.168.202.140:22      192.168.202.140:51732   ESTABLISHED 9579/sshd
    tcp        0      0 :::22                   :::*                    LISTEN      917/sshd

    可以看到,上述第 3 行和第 4 行是登錄之後新增加的打印信息。

    第 2 行,表示 T 上的 ssh 客戶端連接到了 A 上的 ssh 服務端,進程號是 9452。第 3 行,表示進程 9452 收到了 A 轉發來的 ssh 連接后,在本機內部建立新的 ssh 連接,使用 51732 端口號作為 ssh 客戶端,連接到本機 22 端口,22 端口是 sshd 端口。第 4 行,表示本機新啟動一個 sshd 進程,來接收 sshc 的連接。

    這樣,L 到 T 的 ssh 通路徹底打通了。A 將來自 L 的連接轉發到 R,R 在內部啟動了 sshd 來處理來自 L 的請求,通過 A 的代理作用,實現了 L 上的 sshc 和 T 上的 sshd 的交互。

    3. 典型使用場景步驟總結

    上文已涵蓋詳細使用方法,但篇幅太長。此處簡單總結使用步驟如下:

    3.1 在代理機 A 上執行

    使用 SecureCRT 登錄代理機 A。代理機外網 ip 120.198.45.126,內網 ip 192.168.1.102,端口 22。如果本地機與代理機在同一個局域網裡,使用代理機的內網 ip 登錄即可。

    在代理機 shell 中查看是否有未關閉的 ssh 隧道:

    sudo netstat -anp | grep 10022

    若打印形如:

    tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN      8264/sshd: frank
    tcp6       0      0 :::10022                :::*                    LISTEN      8264/sshd: frank

    則表示有未關閉的 ssh 隧道連接。執行如下命令可關閉連接。

    kill -9 8264

    3.2 在目標機 T 上執行

    使用遠程應用程序接口或者在遠程設備 T 上做一些特殊操作,觸發 T 執行如下兩條指令之一:

    # openssh
    sshpass -p '123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126
    
    # dropbear
    DROPBEAR_PASSWORD='123456' ssh -y -T -f -N -g -R :10022:127.0.0.1:22 frank@120.198.45.126

    3.3 在本地機 L 上執行

    在本地機 L 上執行如下指令,登錄遠程目標機 T:

    ssh -vvv -p 10022 root@120.198.45.126

    另外一種變通的方式是,在本地機先通過 ssh 登錄上代理機 A 的 shell。然後在 A 的 shell 中執行如下指令:

    ssh -vvv -p 10022 root@127.0.0.1

    4. 注意事項

    1. 確保代理機 A 所在的網絡防火牆不屏蔽 10022 端口
    2. 確保代理機 A 上 /etc/ssh/sshd_config 配置文件設置正確
    3. 關閉 ssh 隧道既可在代理機 A 上進行(關閉相應的 sshd 進程),也可在目標機 T 上進行(關閉相應的 ssh 進程)
    4. 每次只能訪問一台目標機。如果想同時訪問多台,可以代理機上設置多個轉發端口,每條連接使用一個端口進行轉發
    5. 為保證安全,打開 ssh 隧道時盡量使用無登錄權限的用戶,並且此用戶的密碼建議經常更新

    5. 參考資料

    [1] 阮一峰,
    [2]
    [3]

    6. 修改記錄

    2019-11-20 V1.0 初稿

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • Zabbix-(五)監控Docker容器與自定義jvm監控項

    Zabbix-(五)監控Docker容器與自定義jvm監控項

    Zabbix-(五)監控Docker容器與自定義jvm監控項

    一.前言

    前文中講述了Zabbix對服務器硬件方面的監控功能,本文將講述利用Zabbix監控Docker容器中的Java Web服務,並通過自定義監控項,監控JVM老年代使用情況以及GC信息。Zabbix其實提供了,自帶了JMX模板能夠直接監控JVM信息,本文主要側重於自定義參數與自定義監控項,關於JMX會在之後的文章中介紹。

    準備

    • Zabbix Server (Zabbix 4.4) (ip:192.168.152.140)
    • 運行Java應用的主機 以下簡稱Server-A (已被Zabbix監控) (ip:192.168.152.142)

    二.開啟agent用戶自定義參數配置

    1. 修改配置

      使用自定義參數時,首先需要修改Server-A的agent配置

      # vim /etc/zabbix/zabbix_agentd.conf

      修改配置 UnsafeUserParameters=1

      UnsafeUserParameters=1
    2. 重啟zabbix-agent

      # systemctl restart zabbix-agent

    三.運行tomcat容器

    在Server-A運行tomcat容器

    # docker run --name tomcat -p 8080:8080 -dit tomcat:jdk8-adoptopenjdk-hotspot

    將zabbix賬號添加到docker組。參考

    # sudo gpasswd  -a zabbix docker

    外部訪問測試一下

    四.創建自定義Docker模板

    我們可以定義一個比較通用的Docker模板,有服務需要被監控時,直接鏈接該模板即可。

    1. 創建群組

      點擊【配置】-【主機群組】-【創建主機群組】

      定義一個組名 Docker Group

      配置項
      * 組名 Docker Group
    2. 創建模板

      創建一個自定義模板,模板名稱Docker Template,選擇上步驟創建的Docker Group群組

      配置項
      * 模板名稱 Docker Template
      * 群組 Docker Group

    五.編寫腳本與自定義監控參數

    我們需要編寫一個腳本,用於發現當前正在運行的docker容器(這裏使用容器名稱)。

    1. 在Server-A編寫發現運行容器的python腳本

      創建腳本

      # cd /data/zabbix
      # touch find_container.py
      # chmod a+x find_container.py
      # vim find_container.py

      腳本內容:

      #!/usr/bin/env python
      import os
      import json
      
      # 查看當前運行的docker容器
      t=os.popen(""" docker ps  |grep -v 'CONTAINER ID'|awk {'print $NF'} """)
      container_name = []
      for container in  t.readlines():
              r = os.path.basename(container.strip())
              container_name += [{'{#CONTAINERNAME}':r}]
      # 轉換成json數據
      print json.dumps({'data':container_name},sort_keys=True,indent=4,separators=(',',':'))

      運行腳本,查看一下json數據格式:

      {
          "data":[
              {
                  "{#CONTAINERNAME}":"tomcat"
              }
          ]
      }
    2. 在Server-A自定義容器發現參數

      我們需要自定義一個鍵值對的配置類型,以便Zabbix可以通過鍵讀取到值。

      增加自定義參數

      # cd /etc/zabbix/zabbix_agentd.d
      # vim userparameter_find_container.conf
      docker.container /data/zabbix/find_container.py (腳本的運行結果)
      UserParameter=docker.container,/data/zabbix/find_container.py
    3. 在Server-A創建查看容器JVM GC情況的腳本

      我們可以使用jstat -gcutil 命令查看GC情況

      創建python腳本

      # cd /data/zabbix
      # touch monitor_gc.py
      # chmod a+x monitor_gc.py
      # vim monitor_gc.py

      腳本內容

      #!/usr/bin/python
      import sys
      import os
      
      def monitor_gc(container_name, keyword):
              cmd = ''' docker exec %s bash -c "jstat -gcutil 1" | grep -v S0 | awk '{print $%s}' ''' %(container_name, keyword)
              value = os.popen(cmd).read().replace("\n","")
              print value
      
      if __name__ == '__main__':
              # 參數1:容器的名稱
              # 參數2:查看第幾列(例如 Eden區在第3列傳入3,Full GC次數在第9列傳入9)
              container_name, keyword = sys.argv[1], sys.argv[2]
              monitor_gc(container_name, keyword)

      測試腳本,查看當前tomcat容器Full GC次數

      # /data/zabbix/monitor_gc.py 'tomcat' '9'

    4. 在Server-A自定義Zabbix JVM GC參數

      同樣,增加一個conf文件,表示自定義參數

      # cd /etc/zabbix/zabbix_agentd.d
      # touch userparameter_gc_status.conf
      # vim userparameter_gc_status.conf
      jvm.gc.status[*] /data/zabbix/monitor_gc.py $1 $2
      UserParameter=jvm.gc.status[*], /data/zabbix/monitor_gc.py $1 $2

      jvm.gc.status[*] 表示可以使用參數。其中$1表示參數1,即容器名稱;$2表示參數2,需要查看哪項GC信息,$1 $2都是通過Zabbix配置時傳遞的。

    5. 在Zabbix server上測試自定義參數

      為zabbix sever安裝zabbix-get

      # yum install -y zabbix-get

      測試自定義參數,如果有權限問題,可以參考

      # zabbix_get -s 192.168.152.142 -p 10050 -k docker.container
      # zabbix_get -s 192.168.152.142 -p 10050 -k "jvm.gc.status['tomcat', 9]"

    六.Zabbix模板增加自動發現規則

    上述配置中,已經可以通過腳本獲取到已運行的容器信息,此步驟將通過Zabbix配置界面,在模板中添加自動發現規則,以發現被監控主機中正在運行的docker容器,並利用這些獲取的數據進一步監控容器中jvm數據。

    1. 創建自動發現規則

      點擊【配置】-【模板】-【Docker Template】

      點擊【自動發現規則】-【創建發現規則】

      先配置【自動發現規則】

      配置項
      * 名稱 發現正在運行的Docker容器規則
      類型 Zabbix 客戶端
      * 鍵值 docker.container (這是我們上述步驟中自定義的鍵值)
      其他配置 根據需要配置

      鍵值配置項是之前

      再配置【過濾器】

      則配置自定義腳本返回json數據中的

      配置項
      {#CONTAINERNAME}
    2. 添加監控項原型

      點擊新建的自動發現規則的【監控項原型】-【創建監控項原型】

      輸入參數

      配置項
      * 名稱 Tomcat Full GC次數監控項
      類型 Zabbix 客戶端
      * 鍵值 jvm.gc.status[{#CONTAINERNAME} , 9]
      其他配置項 根據需要填寫

      鍵值是定義的參數,{#CONTAINERNAME} 是jvm.gc.status的參數1,使用了自動發現規則,發現到的docker容器名稱(本文中即是 tomcat);參數2 9 則是表示需要查看FullGC次數,FGC列(第9列)

      除此之外,還可以添加Old老年代(對應第4列),Full GC時間(對應第10列)等監控項,這裏就不一一添加了,和上述過程基本一致,只需修改參數2即可(也可以利用剛新建的監控項原型進行【克隆】)。

    七.鏈接模板

    將上述鏈接到Server-A主機

    八.DashBoard添加可視化圖形

    回到Zabbix首頁可以為新增的自定義監控項,增加圖形(添加圖形步驟可以參考)

    九.其他

    部署問題

    • zabbix在執行腳本時,是使用的zabbix賬戶,因此可能要注意要給zabbix賬號賦予權限。

      例如,zabbix賬戶無法使用docker命令,將zabbix添加到docker組

      # sudo gpasswd -a zabbix docker
    • zabbix server無法執行agent自定義參數中的腳本

      為agent主機設置

      # setenforce 0

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

    【其他文章推薦】

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

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

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

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?