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

據媒體報導,從北汽集團官方獲悉,北汽將斥資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持續開疆拓土,正式啟用台中門市與維修中心。在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多線程!

目錄

就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時內心是小鹿亂撞啊….於是這幾天茶不思飯不想,好幾天深夜皆是輾轉反側,兩目深凝,以至於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,一般兩步,走在大馬路中央上差點被打~我承認太誇張了,感覺又要被打~。最終還是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像可以看的出來,這位朋友絕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,並讓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 隧道)技術,可以實現這種遠程調試功能。

下文中,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對服務器硬件方面的監控功能,本文將講述利用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

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

【其他文章推薦】

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

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

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

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

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

數組與鏈表

前言

數組和鏈表是兩種數據結構,數組非常簡單易用但是它有兩個非常大的缺點,一個是數組一旦創建無法擴展,另一個則是數組的查找和刪除的速度很慢.

鏈表改善了一些數組的缺點,但是同樣的鏈表自身也存在一些自己的缺點.

本篇博客將為大家介紹一下這數組和鏈表特點及各自的優缺點.

閱讀前的準備工作

,一種粗略的評價計算機算法效率的方法.後面的內容會用到表示效率的方法.

1. 數組

我們按數組中的數組是否排序對數組進行劃分,將數組分為無序數組和有序數組.無序數組中的數組是無序的,而有序數組中的數據則是升序或者降序排序的.

1.1 無序數組

因為無序數組中的數據是無序的,往數組中添加數據時不用進行比較和移動數據,所以往無序數組裡面添加數據很快.無論是添加第一個數據還是第一萬個數據所需的時間是相同的,效率為O(1).

至於查找和刪除速度就沒有那麼快了,以數組中有一萬個數據項為例,最少需要比較1次,最多則需要比較一萬次,平均下來需要比較5000次,即N/2次比較,N代表數據量,大O表示法中常數可以忽略,所以效率為O(N).

結論:

  1. 插入很快,因為總是將數據插入到數組的空餘位置.
  2. 查找和刪除很慢,假設數組的長度為N,那麼平均的查找/刪除的比較次數為N/2,並且還需要移動數據.

1.2 有序數組

無序數組中存放的數據是無序的,有序數組裡面存放的數據則是有序的(有可能是升序有可能是降序).

因為有序數組中的數據是按升序/降序排列的,所以插入的時候需要進行排序並且移動數據項,所有有序數組的插入速度比無序數組慢. 效率為O(N).

刪除速度和無序數組一樣慢 效率為O(N).

有序數組的查找速度要比無序數組快,這是因為使用了一個叫做二分查找的算法.

二分查找: 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列.

有一個關於二分查找的形象類比 -> 猜數遊戲

假設要在0-100之間猜一個數,那麼你第一個要猜的数字就是100的一半50的時候,你的朋友會告訴你這個数字比要猜的数字是大還是小,如果比数字大,你接下來要猜的数字就是50的一半25,你的朋友說比這個数字要大,那麼你下面要猜的数字就是25-50中間的那個數37,以此類推…

使用二分查找可極大的提高查找的效率,假設一個有序數組有十億個數據,那麼查找到所需的数字,最多只需比較30次.

有序數組使用二分查找的效率為O(logN).有序數組也可以通過二分查找來新增和刪除數據以提高效率,但是依然需要在新增/刪除后移動數據項,所以效率依然會有影響.

總結:

  1. 有序數組的查找速度比無序數組高,效率為O(logN)
  2. 有序數組的刪除和新增速度很慢,效率為O(N)

1.3 數組總結

數組雖然簡單易用,但是數組有兩個致命的缺點:

  1. 數組存儲的數量有限,創建的過大浪費資源,創建的過小溢出
  2. 數組的效率比其他數據結構低
  • 無序數組插入效率為O(1)時間,但是查找花費O(N)時間
  • 有序數組查找花費O(logN)時間,插入花費O(N)時間
  • 刪除需要移動平均半數的數據項,所以刪除都是O(N)的時間

2. 鏈表

數組一經創建大小就固定住了,無法修改,鏈表在這方面做出了改善,只要內存夠用就可以無限制的擴大.

鏈表是繼數組之後應用最廣泛的數據結構.

2.1 鏈表的特點

鏈表為什麼叫鏈表呢? 因為它保存數據的方式就像一條鎖鏈

鏈表保存數據的方式很像上面的這一條鎖鏈,每一塊鎖鏈就是一個鏈節點,鏈節點保存着自己的數據同時通過自己的next()方法指向下一個鏈節點. 鏈表通過鏈節點不斷地調用next()方法就可以遍歷鏈表中的所有數據.

在鏈表中,每個數據項都被包含在”鏈節點”(link)中,一個鏈結點是某個類的對象,這個類可以叫做Link.因為一個鏈表中有許多類似的鏈結點,所以有必要用一個不同於鏈表的類來表達鏈結點.

每個Link對象中都包含一個對下一個鏈結點引用的字段(通常叫做next).

鏈表本身的對象中有一個字段指向對第一個鏈結點的引用.

數據與鏈表查找數據的區別: 在數組中查找數據就像在一個大倉庫裏面一樣,一號房間沒有,我們去二號房間,二號房間沒有我們去三號房間,以此類推.. 按照地址找完所有房間就可以了.

而在鏈表中查找數據就像單線彙報的地下工作者,你是孤狼你想要彙報點情報給你的頂級上司毒蜂,但是你必須先報告給你的接頭人豬剛鬣,豬剛鬣在報告給它的單線接頭人土行孫,最後由土行孫報告給毒蜂.只能一個找一個,這樣最終完成任務.

2.2 Java代碼

鏈節點類:


/**
 * @author liuboren
 * @Title: 鏈節點
 * @Description:
 * @date 2019/11/20 19:30
 */
public class Link {
    //  保存的數據
    public int data;

    // 指向的下一個鏈節點
    public Link nextLink;

    public Link(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Link getNextLink() {
        return nextLink;
    }

    public void setNextLink(Link nextLink) {
        this.nextLink = nextLink;
    }
}

鏈表類


/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;

    public LinkList() {
        first = null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link link = new Link(data);
        link.setNextLink(first);
        first = link;
    }
}

在新增節點的時候,新增的link的next方法指向原來的first節點,並將鏈表類的first指向新增的節點.

2.4 其他鏈表

剛剛介紹的鏈表是單向鏈表,只能從后往前遍歷,其他的鏈表還有雙端鏈表、雙向鏈表、有序鏈表.

再簡單介紹一下雙端鏈表吧.

雙端鏈表就是在單向鏈表的基礎上,新增一個成員變量指向鏈表的最後一個對象.

雙端鏈表代碼:

/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;
    private Link last;

    public LinkList() {
        first = null;
    }

    public boolean isEmpty() {
        return first == null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link newLink = new Link(data);
        newLink.setNextLink(first);
        if (isEmpty()) {
            last = newLink;
        }
        first = newLink;

    }
}

雙向鏈表則是可以從first和last兩個方向進行遍歷,有序鏈表的數據都是按照關鍵字的順序排列的,本文不再展開了.

2.5 鏈表的效率

鏈表的效率:

  • 表頭插入和刪除速度都很快,花費O(1)的時間.
  • 平均起來,查找&刪除&插入在制定鏈節點後面都需要搜索一半的鏈節點需要O(N)次比較,雖然數組也需要O(N)次比較,但是鏈表讓然要快一些,因為不需要移動數據(只需要改變他們的引用)

3. 總結

鏈表解決了數組大小不能擴展的問題,但是鏈表自身依然存在一些問題(在鏈表的鏈節點後面查找&刪除&插入的效率不高),那麼有沒有一種數據結構即擁有二者的優點又改善了二者的缺點呢,答案是肯定的,下篇博客將為您介紹這種優秀的數據結構,敬請期待.

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

大話字符串逆序

窗外的大廈,桌子上的水杯,手中的筆。

面試官:“先來一點基礎的吧,用Java寫一個方法,入參是一個字符串,返回逆序后的字符串。”

我暗想確實很基礎,於是便寫下:

public static String reverse(String str) {
    StringBuffer sb = new StringBuffer(str);
    return sb.reverse().toString();
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

面試官看了看,說:“寫的很好,用StringBuffer的reverse方法。如果你來實現其中算法,你會怎麼寫?”

我直接說:“從最後一個字符開始,一直向前添加字符就可以了。”重新寫了一個遍代碼:

public static String reverse(String str) {
    char[] chars = str.toCharArray();
    StringBuilder sb = new StringBuilder();
    for (int i = chars.length - 1; i >= 0; i--) {
        sb.append(chars[i]);
    }
    return sb.toString();
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

面試官看了看,說:“寫的很好,逆序的功能完成了。不過再想想,有什麼可以優化的地方?”

我想了想,說:“好像沒有什麼可以優化的?”

面試官提示了一句:“比如,採用首尾替換的方式呢?是不是可以減少時間複雜度?”

我恍然大悟,說:“的確是,我再改一下。”又重新寫了一個遍代碼:

public static String reverse(String str) {
    char[] chars = str.toCharArray();
    int n = chars.length - 1;
    for (int i = 0; i <= n / 2; i++) {
        int j = n - i;
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
    return new String(chars);
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

面試官又看了看,說:“寫的很好,就是這個思想。不過再想想,有什麼可以優化的地方?”

我左思右想一番,說:“應該沒有吧。”

面試官說:“確定沒有了嘛?”

我肯定地回答:“確定沒有了。”

面試官:“好吧,這個問題先到這。”

我有點不服氣,搶着問到:“您說說,還有什麼可以優化的地方?”

面試官微笑了一下,說:“我認為還有兩個地方可以優化。”

“第一,for循環的布爾表達式里不應該放除2的計算,否則每次循環都會計算一次。”

“第二,除2的計算可以用右移一位代替,這樣效率更高。”

面試官在我寫的代碼上改了幾筆,就變成了:

public static String reverse(String str) {
    char[] chars = str.toCharArray();
    int n = chars.length - 1;
    for (int i = (n - 1) >> 1; i >= 0; i--) {
        int j = n - i;
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
    return new String(chars);
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

我茅塞頓開,這次面試真的是學到了。

本故事純屬虛構,如有雷同實屬巧合。

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

【其他文章推薦】

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

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

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

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

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

Nebula 架構剖析系列(二)圖數據庫的查詢引擎設計

摘要

上文(存儲篇)說到數據庫重要的兩部分為存儲和計算,本篇內容為你解讀圖數據庫 Nebula 在查詢引擎 Query Engine 方面的設計實踐。

在 Nebula 中,Query Engine 是用來處理 Nebula 查詢語言語句(nGQL)。本篇文章將帶你了解 Nebula Query Engine 的架構。

上圖為查詢引擎的架構圖,如果你對 SQL 的執行引擎比較熟悉,那麼對上圖一定不會陌生。Nebula 的 Query Engine 架構圖和現代 SQL 的執行引擎類似,只是在查詢語言解析器和具體的執行計劃有所區別。

Session Manager

Nebula 權限管理採用基於角色的權限控制(Role Based Access Control)。客戶端第一次連接到 Query Engine 時需作認證,當認證成功之後 Query Engine 會創建一個新 session,並將該 session ID 返回給客戶端。所有的 session 統一由 Session Manger 管理。session 會記錄當前 graph space 信息及對該 space 的權限。此外,session 還會記錄一些會話相關的配置信息,並臨時保存同一 session 內的跨多個請求的一些信息。

客戶端連接結束之後 session 會關閉,或者如果長時間沒通信會切為空閑狀態。這個空閑時長是可以配置的。
客戶端的每個請求都必須帶上此 session ID,否則 Query Engine 會拒絕此請求。

Storage Engine 不管理 session,Query Engine 在訪問存儲引擎時,會帶上 session 信息。

Parser

Query Engine 解析來自客戶端的 nGQL 語句,分析器(parser)主要基於著名的 flex / bison 工具集。字典文件(lexicon)和語法規則(grammar)在 Nebula 源代碼的 src/parser  目錄下。設計上,nGQL 的語法非常接近 SQL,目的是降低學習成本。 圖數據庫目前沒有統一的查詢語言國際標準,一旦 ISO/IEC 的圖查詢語言(GQL)委員會發布 GQL 國際標準,nGQL 會儘快去實現兼容。
Parser 構建產出的抽象語法樹(Abstrac Syntax Tree,簡稱 AST)會交給下一模塊:Execution Planner。

Execution Planner

執行計劃器(Execution Planner)負責將抽象樹 AST 解析成一系列執行動作 action(可執行計劃)。action 為最小可執行單元。例如,典型的 action 可以是獲取某個節點的所有鄰節點,或者獲得某條邊的屬性,或基於特定過濾條件篩選節點或邊。當抽象樹 AST 被轉換成執行計劃時,所有 ID 信息會被抽取出來以便執行計劃的復用。這些 ID 信息會放置在當前請求 context 中,context 也會保存變量和中間結果。

Optimization

經由 Execution Planner 產生的執行計劃會交給執行優化框架 Optimization,優化框架中註冊有多個 Optimizer。Optimizer 會依次被調用對執行計劃進行優化,這樣每個 Optimizer都有機會修改(優化)執行計劃。最後,優化過的執行計劃可能和原始執行計劃完全不一樣,但是優化后的執行結果必須和原始執行計劃的結果一樣的。

Execution

Query Engine 最後一步是去執行優化后的執行計劃,這步是執行框架(Execution Framework)完成的。執行層的每個執行器一次只處理一個執行計劃,計劃中的 action 會挨個一一執行。執行器也會一些有針對性的局部優化,比如:決定是否併發執行。針對不同的 action所需數據和信息,執行器需要經由 meta service 與storage engine的客戶端與他們通信。

最後,如果你想嘗試編譯一下 Nebula 源代碼可參考如下方式:

有問題請在 GitHub(GitHub 地址:) 或者微信公眾號上留言,也可以添加 Nebula 小助手微信號:NebulaGraphbot 為好友反饋問題~

推薦閱讀

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

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

TCP time_wait close_wait問題(可能是全網最清楚的例子)

背景

公司群里,運維發現一個問題,task服務報錯(如下)

The stream or file \"/data/logs/adn_task/offer_service.log\" could not be opened:
failed to open stream: Too many open files

測試老大看到了,根據經驗就推測是應該是文件句柄使用完了,應該有TCP連接很多沒釋放,果真發現是很多CLOSE_WAIT的狀態

簡單認知

短鏈接,一次鏈接就會佔用一個端口,一個端口就是一個文件描述符;
文件描述符 又稱 句柄,linux系統最大的句柄數是65535,可以通過ulimit -a 查看

三次握手

TCP建立連接需要經過三次握手;
通俗版本:
A: 你好,你能聽見我說話嗎?
B: 能聽到,你能聽到我說話嗎?
A:我也能聽到,我們開始通信吧

專業版本:
建立TCP連接時,需要客戶端和服務器共發送3個包。

  • 第一次:客戶端發送初始序號x和syn=1請求標誌
  • 第二次:服務器發送請求標誌syn,發送確認標誌ACK,發送自己的序號seq=y,發送客戶端的確認序號ack=x+1
  • 第三次:客戶端發送ACK確認號,發送自己的序號seq=x+1,發送對方的確認號ack=y+1

四次揮手

TCP連接斷開需要經過四次揮手;
通俗版本:
前提A和B在通話
A:好的,我的話就說完了(FIN);
B:哦哦,我知道你說完啦(ACK),我還有說兩句哈;A: (沒說話,一直聽着)
B:哦了,我也說完了(FIN)
A:好的,我也知道你說玩了(ACK),掛電話吧

專業版本:

  • 第一次揮手:客戶端發出釋放FIN=1,自己序列號seq=u,進入FIN-WAIT-1狀態
  • 第二次揮手:服務器收到客戶端的后,發出ACK=1確認標誌和客戶端的確認號ack=u+1,自己的序列號seq=v,進入CLOSE-WAIT狀態
  • 第三次揮手:客戶端收到服務器確認結果后,進入FIN-WAIT-2狀態。此時服務器發送釋放FIN=1信號,確認標誌ACK=1,確認序號ack=u+1,自己序號seq=w,服務器進入LAST-ACK(最後確認態)
  • 第四次揮手:客戶端收到回復后,發送確認ACK=1,ack=w+1,自己的seq=u+1,客戶端進入TIME-WAIT(時間等待)。客戶端經過2個最長報文段壽命后,客戶端CLOSE;服務器收到確認后,立刻進入CLOSE狀態。

狀態流轉圖

實際例子

建立連接

linux上起了一個redis服務

本地起的6379端口

還是同一台機器上,通過python腳本連接該redis服務:

此時網絡連接如下:

關注這兩個網絡連接,第一個是redis-server的,第二是python腳本的,此時都是ESTABLISHED狀態,表示這兩個進程建立了連接

TIME_WAIT情況

現在斷掉python

之前的python的那個連接,是TIME_WAIT狀態
客戶端(主動方)主動斷開,進入TIME_WAIT狀態,服務端(被動方)進去CLOSE狀態,就是沒有显示了

等待2MSL(1分鐘)后,如下:

TIME_WAIT狀態的連接也消失了,TIME_WAIT回收機制,系統ing過一段時間會回收,資源重利用

CLOSE_WAIT情況

先建立連接,如下:

關掉redis服務,service redis stop

之前的redis-server的45370端口連接 進入了FIN_WAIT2狀態,而python端(被動關閉方)就進去了CLOSE_WAIT狀態

等待30s后,在看連接

只有python的那條CLOSE_WAIT

再次操作python端的腳本,再次get

關於6379端口(redis端口)的網絡連接都沒有了

TCP參數設置

如何快速回收TIME_WAIT和FIN_WAIT
/etc/sysctl.conf 包含以下配置項
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
root權限 執行/sbin/sysctl -p使之生效

經驗之談

個人經驗,不一定對,如有錯誤,請指正

  1. 當出現了CLOSE_WAIT大概率是業務代碼問題,代碼中沒有處理服務異常的情況,如上面的例子,python再次請求redis的時候,發現redis掛了,就會主動幹掉CLOSE_WAIT狀態
  2. 出現大量TIME_WAIT的情況,一般是服務端沒有及時回收端口,linux內核參數需要調整優化

參考資料

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

【其他文章推薦】

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

台灣空運大陸一條龍服務