標籤: 租車

  • Zookeeper分佈式過程協同技術 – 部署及設置

    Zookeeper分佈式過程協同技術 – 部署及設置

    Zookeeper分佈式過程協同技術 –  部署及設置

    Zookeeper支持單機模式、偽集群模式、集群模式三種部署方式。演示部署環境為CentOS、jdk版本為1.8、Zookeeper版本為3.4.9。

    單機模式

    單機模式適合入門學習使用,只需要一台機器就可以輕鬆搭建Zookeeper服務用於學習和測試。

    1. 進入官網下載Zookeeper的JAR包,下載地址:https://zookeeper.apache.org/releases.html。

    2. 解壓壓縮包,命令:tar -zxvf zookeeper-3.4.9.tar.gz,解壓后目錄格式如下。

    3. 進入conf目錄,複製zoo_sample.cfg文件命名為zoo.cfg。這裏可以直接使用默認的參數,也可以根據自己的需要修改。

    命令:cp zoo_sample.cfg zoo.cfg

    主要配置參數說明

    • clientPort

    客戶端連接的服務器所監聽的TCP端口,默認情況下,服務器會監聽所有的網絡連接的這個端口,除非設置了clientPortAddress參數。客戶端口可以設置為任何值,不同的服務器可以設置不同的端口,默認端口號為2181。

    • tickTime

    tickTime的時長單位為毫秒,Zookeeper集群中使用的超時時間單位通過tickTime指定。tickTime設置了超時時間的下限值,因為最小的超時時間為一個tick時間,客戶端最小會話超時時間為2個tick時間。

    tickTime的默認值是3000毫秒,更低的tickTime值可以更快地發現超時問題,但也導致更高的網絡流量和更高的CPU使用率。

    • dataDir

    dataDir用於配置內存數據庫保存的模糊快照目錄,如果某個服務器為集群中的一台,id文件也保存在該目錄下。

    • dataLogDir

    用於配置事務日誌的保存目錄。服務端在確認一個事務前必須將數據同步到存儲中,如果寫入磁盤過於忙碌會影響到寫入的吞吐能力。因此,比較好的方案是使用專用的日誌存儲設備,將dataLogDir目錄配置指向該設備。

    • maxClientCnxns

    允許每個IP地址發起socket連接的最大數量。Zookeeper通過流量控制和限制值來避免過載情況的發生。當某個IP地址的客戶端建立的連接數大於此值時,服務器會拒絕該IP地址新的連接。

    • initLimit

    對於追隨者最初連接到群首時的超時時間,單位為tick(tickTime)值的倍數。

    當某個追隨者最初與群首建立連接時,它們之間會傳輸相當多的數據,尤其是追隨者落後整體很多時。配置initLimit參數值取決於群首與追隨者之間的網絡傳輸速度以及傳輸數據量的大小。

    但是如果設置值過高,在首次連接到故障的服務器就會消耗更多的時間,同時還會消耗更多的恢復時間。因此在實際部署時,最好進行集群間的網絡基準測試來測試出你所期望的時間。

    • syncLimit

    對於追隨者與群首進行sync操作時的超時值,單位為tick(tickTime)值的倍數。

    追隨者總是會稍落後於群首,如果群首與追隨者無法進行sync操作,而且超過了syncLimit的tick時間,就會放棄該追隨者。

    • leaderServes

    配置值為“yes”或“no”標誌,指示群首服務器是否為客戶端提供服務。擔任群首的服務器需要做很多工作,它需要與所有的追隨者進行通信並會執行所有的變更操作,這意味着群首的負載會比追隨者高很多,如果群首過載,整個系統都有可能受到影響。

    4. 進入bin目錄,可以看到很多sh腳本文件,通過zkServer.sh來啟動zookeeper。

    操作命令:

    啟動命令:.zkServer.sh start

    停止命令:.zkServer.sh stop

    重啟命令:.zkServer.sh restart

    狀態查看命令:.zkServer.sh status

    啟動zookeeper服務后,通過status命令可以看到當前服務狀態、使用的配置文件、運行模式。

     這樣Zookeeper的單機部署模式就已經初步完成了。如果需要修改JVM配置,可以修改zkServver.sh文件中的配置,在如下位置加入你需要的參數。

    偽集群部署

     偽集群部署指在一台機器上部署多個Zookeeper服務。

    1. 首先將之前配置好的Zookeeper服務目錄另外複製兩份,命名為zookeeper-2和zookeeper-3。

    2. 然後分別修改三份的zoo.cfg配置項,添加配置 server.x=[hostname]:n:n[:observer],示例如下。

    server.1=localhost:2887:3887

    server.2=localhost:2888:3888

    server.3=localhost:2889:3889

    Zookeeper服務器需要知道它們如何通信,配置文件中該形式的配置項就指定了服務器x的配置信息,其實x為服務器的ID值(一個整數)。當一個服務器啟動后,就會讀取data目錄下myid文件中的值,之後服務器就會使用這個值作為查找server.x項,通過該項中的數據配置服務器自己。如果需要連接到另外一個服務器y,就會使用server.y項配置信息來與這個服務器進行通信。

    hostname為服務器在網絡中的名稱(ip或者主機名),同時後面跟着兩個端口號,第一個端口號用於事務的發送,第二個端口號用於群首選舉。如果最後一個字段標記了observer屬性(選填),服務器就會進入觀察者模式。

    三份配置文件的myid和兩個配置端口號以及clientPort不可以重複,因為是在一台機器上部署,生產環境集群部署時可以忽略。最終的配置清單如下:

    3. 分別添加myid文件,在data目錄下添加myid文件。

    echo ‘1’ > data/myid

    4. 分別啟動三個Zookeeper服務,啟動成功后通過status命令可以查看服務的狀態。可以看到目前集群里有一台leader和兩台follower。

    集群部署

    生產環境的集群部署步驟和偽集群部署並無太多差異,同樣是修改zoo.cfg配置文件並且添加Zookeeper集群信息,集群部署時,客戶端端口號、事務端口號、選舉端口號都可以保持一致。另外集群部署時需要充分考慮集群機器間的網絡情況來制定合理的超時時間設置。

     

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 菲律賓民答那峨島發生規模6.6強震

    摘錄自2019年10月16日上報、29日自由時報報導

    根據美國地質調查所(USGS)觀測顯示,菲律賓民答那峨島當地時間29日早9時許傳出規模6.6強震。震央位於民答那峨島的達沃市(Davao City)東南方,地震深度僅15.3公里。

    截至下午4:00為止,麥格塞塞(Magsaysay)鎮發言人告訴法新社,一名十多歲的男學生逃離校舍時,遭倒下的牆壓死。另有學生在逃出教室時因「踩踏」受傷,所幸保住性命。 科羅納達爾(Koronadal)鎮當局也表示,當地一名66歲男性被倒下的牆壓死。 截至目前至少有50人遭掉落的瓦礫砸傷,包括一所小學的7名師生在逃離校舍時受傷。

    本月16日,一場規模6.4的淺層強震才襲擊菲律賓南部,至少1人受傷。根據美國地質調查所,震源深度14公里,震央距離民答那峨島哥倫比歐市(Columbio)約7.7公里。地震發生於當地時間晚間7時37分。其中,基達帕灣市(Kidapawan City)達7級震度,預計仍會有餘震,該市市長宣布17日全面停課。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 巴黎共享滑板車亂象多 河中打撈竟成新興工作

    摘錄自2019年10月26日民視新聞報導

    巴黎目前有十多家業者、提供了大概2萬輛電動滑板車,可以自由租借,帶來方便的同時,也帶來了混亂,街頭隨處可見被人亂停、亂丟的電動滑板車,還有不少直接被扔進塞納河。負責清理、回收這些電動滑板車的「巡邏隊」,成了新興工作。

    塞納河畔有人用力拉著繩索,他們可不是在釣魚,而是在「釣電動滑板車」,這家電動滑板車公司的員工,3個鐘頭從河裡打撈了足足15輛。電動滑板車公司員工表示:「就像那輛的手把卡在岸邊,我們不可能直接把它拉起來,你必須下到水裡,把手把轉向才能將它弄起來。」

     

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • 寮國湄公河首座發電水壩啟動 大批民眾抗議

    摘錄自2019年10月29日民視新聞報導

    寮國湄公河下游的第一座水力發電水壩、沙耶武里水壩29日正式啟動,就迎來了大批抗議民眾,他們擔心水壩會對當地環境,造成難以預測的嚴重損害,尤其是加劇糧食跟水源匱乏的危機,大批民眾群聚泰國與鄰近寮國交界處的黎府,跟著師父唸經祈福,抗議沙耶武里水壩正式啟動。

    而隨著這座水壩正式運作,湄公河的命運也步入了重要轉折點。當地環保人士表示,「當沙耶武里水壩正式發電時,湄公河將發生變化,我們將無法預測這種變化的嚴重程度。」

    雖然相關單位打包票,這項工程會保護到湄公河脆弱的生態系統,但環保組織卻反咬建商根本就是「工程先行,研究後補」。另外水壩的安全也令人憂心。去年7月,寮國東南部阿速坡省就發生水壩大潰堤,50億噸的洪水滾滾流向下游,造成40人死亡,失蹤至少上百人,6600人流離失所。

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • 不畏油價跌 美電動車 12 月創最高單月銷售紀錄

    儘管美國汽油零售價格跟隨國際原油同步下挫到每加侖 2 美元的低水準,2014 年美國電動車銷售強勁增加 23%,12 月並創下單月最佳紀錄;顯見電動車已蔚為一股持久的趨勢,不受油價影響。   根據電動車專業網站 InsideEVs 統計,2014 年 12 月美國賣出 12,874 輛電動車,為統計以來最高單月銷售紀錄。至於 2014 年全年,電動車銷售勁揚 23%,達到 119,710 輛。   就個別品牌來看,全美電動車銷售冠軍是日產 Leaf,2014 年總計售出 30,200 輛,遙遙領先亞軍通用汽車雪佛蘭 Volt 的 18,805 輛。起價介於 3.5 萬到 4 萬美元的特斯拉 Model S 排名第 3,全年銷量為 17,300 輛,市占不及 15%。不過,在 12 月 Model S 首度擊敗售價較低的日產 Leaf(約 3 萬美元)。   2014 年美國電動車市場唯一新上市車款是 BMW,分別在 5 月及 8 月推出 i3 與 i8 ,加總的銷量僅 6,647 輛。展望 2015 年,將有數款新型電動車上路,尤其是在休旅車和越野車市場。而特斯拉的 Model X 則預計 2015 年下半年登場,是今年最受期待的電動車。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 產能尚未滿載 特斯拉要到 2020 年才可能獲利

    特斯拉執行長 Elon Musk 在世界汽車業大會 (Automotive News World Congress) 中表示,以目前狀況看來,特斯拉的電動車在 2020 年前都無法開始獲利。   根據華爾街日報報導,特斯拉的 Model 3 電動車在現有會計原則下,至少要等到 2020 年產能滿載時才能正式轉盈。Model 3 是特斯拉功能性較少的電動車,一台可能售價約介於 3 萬到 4 萬美元之間,比起其他 Model S 系列一台動輒超過 10 萬美元來說相對便宜,Model 3 預計在 2017 年推出。特斯拉的股價盤後下跌 7%,來到 189.95 美元。   對於電動車的發展,Musk 也要求其他汽車公司盡速推出自家電動車。特斯拉目前電動車銷售量約為 35,000 台,預計到 2025 年可賣出數百萬台。

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • Tesla 股價大跌 穆斯克荷包失血 28 億美元

    電動車大廠特斯拉(Tesla) 股價重創,不僅投資人損失慘重,公司創辦人穆斯克(Elon Musk)財富更是大失血。根據經濟日報報導,Tesla 股價 2014 年 9 月至今大跌 34%,穆斯克財富也蒸發 28 億美元。   穆斯克表示,Tesla 在中國的銷售低迷,使曾經超搶手的 Tesla 股票價格 14 日大跌 11.56 美元,跌幅約 6%,若和去年 9 月每股 291.42 美元的高價相比,Tesla 股票在這段期間崩跌掉 3 分之 1 的價值。   穆斯克則擁有逾 2,800 萬股的 Tesla 股票,相當流通股總數的 4 分之 1 左右,這意味股價大跌讓穆斯克 28 億美元的財富煙消雲散。不過,雖然上述損失數字很可觀,但穆斯克本身可能不太意外,他在去年 9 月曾警告說,Tesla 的股價「有點太高」,當時投資人不予理會,Tesla 股價還接著上漲至每股 281.19 美元。

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • 實時web應用方案——SignalR(.net core)

    實時web應用方案——SignalR(.net core)

    何為實時

    先從理論上解釋一下兩者的區別。

    大多數傳統的web應用是這樣的:客戶端發起http請求到服務端,服務端返回對應的結果。像這樣:

     

    也就是說,傳統的web應用都是客戶端主動發起請求到服務端。

    那麼實時web應用呢?它不需要主動發起請求,服務端可以主動推送信息到客戶端。

    舉栗子的話,實時聊天工具、web遊戲等都可以算是實時應用。

    什麼是SignalR

    如果想做一個實時應用,最好用web socket。很早以前我也寫過web socket的實現方式,但不夠全面,這裏再補上一篇。

    來說說signalR,它是一款開源的實時框架,可以使用三種方式實現通信(long polling、server sent events、web socket)。它很好的整合了底層技術,讓我們可以不用關注底層技術實現而把精力聚焦在業務實現上。一個完整的signalR包括客戶端和服務端,服務端支持net core/net framework,還支持大部分客戶端,比如瀏覽器和桌面應用。

    回落機制

    為了兼容不同瀏覽器(客戶端)和服務端,signalR採用了回落機制,使得它可以根據情況協商使用不同的底層傳輸方式。假如瀏覽器不支持web socket,就自動降級使用sse,再不行就long polling。當然,也可以禁用這種機制,指定其中一種。

    三種通信方式

    long polling(長輪詢)

    長輪詢是客戶端發起請求到服務端,服務器有數據就會直接返回。如果沒有數據就保持連接並且等待,一直到有新的數據返回。如果請求保持到一段時間仍然沒有返回,這時候就會超時,然後客戶端再次發起請求。

    這種方式優點就是簡單,缺點就是資源消耗太多,基本是不考慮的。

    server sent events(sse)

    如果使用了sse,服務器就擁有了向客戶端推送的能力,這些信息和流信息差不多,期間會保持連接。

    這種方式優點還是簡單,也支持自動重連,綜合來講比long polling好用。缺點也很明顯,不支持舊的瀏覽器不說,還只能發送本文信息,而且瀏覽器對sse還有連接數量的限制(6個)。

    web socket

    web socket允許客戶端和服務端同時向對方發送消息(也就是雙工通信),而且不限制信息類型。雖然瀏覽器同樣有連接數量限制(可能是50個),但比sse強得多。理論上最優先使用。

    進入正題

    開始之前,還需要了解RPC和Hub的概念。

    RPC:全程Remote Procedure Call,字面意思遠程服務調用,可以像調用本地方法一樣調用遠程服務。前端可以調用後端方法,後端也可以調用前端方法。

    Hub:基於RPC,接受從客戶端發過來的消息,也同時負責把服務端的消息發送給客戶端。客戶端可以調用Hub裏面的方法,服務端可以通過Hub調用客戶端裏面的方法。

    好了,概念已經理解清楚了,接下來上代碼。

    在項目里新增Hub類:

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRDemo.Server
    {
        public class SignalRHub : Hub
        {
            /// <summary>
            /// 客戶連接成功時觸發
            /// </summary>
            /// <returns></returns>
            public override async Task OnConnectedAsync()
            {
                var cid = Context.ConnectionId;
    
                //根據id獲取指定客戶端
                var client = Clients.Client(cid);
    
                //向指定用戶發送消息
                await client.SendAsync("Self", cid);
    
                //像所有用戶發送消息
                await Clients.All.SendAsync("AddMsg", $"{cid}加入了聊天室");
            }
        }
    }

    為了讓外部可以訪問,我們還需要一個控制器。在控制器里聲明隨便建一個:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.SignalR;
    using SignalRDemo.Server;
    using System.Threading.Tasks;
    
    namespace SignalRDemo.Controllers
    {
        public class HomeController : Controller
        {
            private readonly IHubContext<SignalRHub> _countHub;
    
            public HomeController(IHubContext<SignalRHub> countHub)
            {
                _countHub = countHub;
            }
    
            /// <summary>
            /// 發送信息
            /// </summary>
            /// <param name="msg"></param>
            /// <param name="id"></param>
            /// <returns></returns>
            public async Task Send(string msg, string id)
            {
                await _countHub.Clients.All.SendAsync("AddMsg", $"{id}:{msg}");
            }
        }
    }

    再然後進入StartUp設置端點:

    endpoints.MapHub<SignalRHub>("/hub");

    完成以後,配置signalr客戶端:

    setupConn = () => {
        conn = new signalR.HubConnectionBuilder()
            .withUrl("/hub")
            .build();
    
        conn.on("AddMsg", (obj) => {
            $('#msgPanel').append(`<p>${obj}</p>`);
        });
    
        conn.on("Finished", () => {
            conn.stop();
            $('#msgPanel').text('log out!');
        });
    
        conn.on("Self", (obj) => {
            $('#userId').text(obj);
        });
    
        conn.start()
            .catch(err => console.log(err));
    }

    要注意withUrl裏面的路徑就是之前設置好的端點。

    運行效果:

     

     

     Hub還支持組操作,比如:

    //將用戶添加到A組
    await
    Groups.AddToGroupAsync(Context.ConnectionId, "GroupA");
    //將用戶踢出A組
    await Groups.RemoveFromGroupAsync(Context.ConnectionId, "GroupA");
    //向A組所有成員廣播消息
    await Clients.Group("GroupA").SendAsync("AddMsg", "群組消息");

    更多操作請參考官方文檔。

    本文演示demo的源碼見git,地址:https://gitee.com/muchengqingxin/SignalRDemo.git

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 十、深度優先 && 廣度優先

    十、深度優先 && 廣度優先

    原文地址

    一、什麼是“搜索”算法?

    • 算法是作用於具體數據結構之上的,深度優先搜索算法和廣度優先搜索算法都是基於“圖”這種數據結構的。
    • 因為圖這種數據結構的表達能力很強,大部分涉及搜索的場景都可以抽象成“圖”。
    • 圖上的搜索算法,最直接的理解就是,在圖中找出從一個頂點出發,到另一個頂點的路徑。
    • 具體方法有很多,兩種最簡單、最“暴力”的方法為深度優先、廣度優先搜索,還有A、 IDA等啟髮式搜索算法。
    • 圖有兩種主要存儲方法,鄰接表和鄰接矩陣。
    • 以無向圖,採用鄰接表存儲為例:
    public class Graph {
        // 頂點的個數
        private int v;
        // 每個頂點後面有個鏈表
        private LinkedList<Integer>[] adj;
    
        public Graph(int v) {
            this.v = v;
            adj = new LinkedList[v];
            for (int i = 0; i < v; i++) {
                adj[i] = new LinkedList<>();
            }
        }
    
        /**
         * 添加邊
         * @param s 頂點
         * @param t 頂點
         */
        public void addEdge(int s,int t){
            // 無向圖一條邊存兩次(聯想微信好友)
            adj[s].add(t);
            adj[t].add(s);
        }
    }
    

    二、廣度優先搜索(BFS)

    • 廣度優先搜索(Breadth-First-Search),簡稱為 BFS。
    • 它是一種“地毯式”層層推進的搜索策略,即先查找離起始頂點最近的,然後是次近的,依次往外搜索

    2.1、實現過程

    /**
     * 圖的廣度優先搜索,搜索一條從 s 到 t 的路徑。
     * 這樣求得的路徑就是從 s 到 t 的最短路徑。
     *
     * @param s 起始頂點
     * @param t 終止頂點
     */
    public void bfs(int s, int t) {
        if (s == t) {
            return;
        }
        // visited 記錄已經被訪問的頂點,避免頂點被重複訪問。如果頂點 q 被訪問,那相應的visited[q]會被設置為true。
        boolean[] visited = new boolean[v];
        visited[s] = true;
        // queue 是一個隊列,用來存儲已經被訪問、但相連的頂點還沒有被訪問的頂點。因為廣度優先搜索是逐層訪問的,只有把第k層的頂點都訪問完成之後,才能訪問第k+1層的頂點。
        // 當訪問到第k層的頂點的時候,需要把第k層的頂點記錄下來,稍後才能通過第k層的頂點來找第k+1層的頂點。
        // 所以,用這個隊列來實現記錄的功能。
        Queue<Integer> queue = new LinkedList<>();
        queue.add(s);
        // prev 用來記錄搜索路徑。當從頂點s開始,廣度優先搜索到頂點t后,prev數組中存儲的就是搜索的路徑。
        // 不過,這個路徑是反向存儲的。prev[w]存儲的是,頂點w是從哪個前驅頂點遍歷過來的。
        // 比如,通過頂點2的鄰接表訪問到頂點3,那prev[3]就等於2。為了正向打印出路徑,需要遞歸地來打印,就是print()函數的實現方式。
        int[] prev = Arrays.stream(new int[v]).map(f -> -1).toArray();
    
        while (queue.size() != 0) {
            int w = queue.poll();
            LinkedList<Integer> wLinked = adj[w]; // 表示:鄰接表存儲時頂點為w,所對應的鏈表
            for (int i = 0; i < wLinked.size(); ++i) {
                int q = wLinked.get(i);
                // 判斷頂點 q 是否被訪問
                if (!visited[q]) {
                    // 未被訪問
                    prev[q] = w;
                    if (q == t) {
                        print(prev, s, t);
                        return;
                    }
                    visited[q] = true;
                    queue.add(q);
                }
            }
        }
    }
    
    // 遞歸打印s->t的路徑
    private void print(int[] prev, int s, int t) {
        if (prev[t] != -1 && t != s) {
            print(prev, s, prev[t]);
        }
        System.out.print(t + " ");
    }
    

    原理如下:

    2.2、複雜度分析

    • 最壞情況下,終止頂點 t 離起始頂點 s 很遠,需要遍歷完整個圖才能找到。
    • 這個時候,每個頂點都要進出一遍隊列,每個邊也都會被訪問一次,所以,廣度優先搜索的時間複雜度是 O(V+E)
    • 其中,V 表示頂點的個數,E 表示邊的個數。
    • 對於一個連通圖來說,也就是說一個圖中的所有頂點都是連通的,E肯定要大於等於 V-1,所以,廣度優先搜索的時間複雜度也可以簡寫為 O(E)。
    • 廣度優先搜索的空間消耗主要在幾個輔助變量 visited 數組、queue 隊列、prev 數組上。
    • 這三個存儲空間的大小都不會超過頂點的個數,所以空間複雜度是 O(V)

    三、深度優先搜索(DFS)

    • 深度優先搜索(Depth-First-Search),簡稱DFS。
    • 最直觀的例子就是“走迷宮,假設站在迷宮的某個岔路口,然後想找到出口。
    • 隨意選擇一個岔路口來走,走着走着發現走不通的時候,就回退到上一個岔路口,重新選擇一條路繼續走,直到最終找到出口。這種走法就是一種深度優先搜索策略。
    • 如下圖所示,在圖中應用深度優先搜索,來找某個頂點到另一個頂點的路徑。
    • 搜索的起始頂點是 s,終止頂點是 t,在圖中尋找一條從頂點 s 到頂點 t 的路徑。
    • 用深度遞歸算法,把整個搜索的路徑標記出來了。實線箭頭表示遍歷,虛線箭頭表示回退。
    • 從圖中可以看出,深度優先搜索找出來的路徑,並不是頂點 s 到頂點 t 的最短路徑。

    3.1、實現過程

    // 全局變量或者類成員變量,標記是否找到終點 t
    boolean found = false;
    
    /**
     * 深度優先搜索
     *
     * @param s 起始頂點
     * @param t 終止頂點
     */
    public void dfs(int s, int t) {
        found = false;
        // 標記頂點是否被訪問
        boolean[] visited = new boolean[v];
        // prev 用來記錄搜索路徑,prev[w] = a 表示 w 頂點的上一級節點為 a
        int[] prev = Arrays.stream(new int[v])
                .map(f -> -1).toArray();
    
        recurDfs(s, t, visited, prev);
        print(prev, s, t);
    }
    
    private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
        if (found == true) {
            return;
        }
        visited[w] = true;
        if (w == t) {
            found = true;
            return;
        }
        LinkedList<Integer> wLinked = adj[w];
        for (int i = 0; i < wLinked.size(); ++i) {
            int q = wLinked.get(i);
            if (!visited[q]) {
                prev[q] = w;
                recurDfs(q, t, visited, prev);
            }
        }
    }
    

    3.2、複雜度分析

    • 深度搜索中每條邊最多會被訪問兩次,一次是遍歷,一次是回退。
    • 所以,深度優先搜索算法的時間複雜度是 O(E), E 表示邊的個數。
    • 深度優先搜索算法的消耗內存主要是 visited、 prev 數組和遞歸調用棧。
    • visited、 prev 數組的大小跟頂點的個數V成正比,遞歸調用棧的最大深度不會超過頂點的個數,所以總的空間複雜度就是 O(V)

    四,兩者對比

    • 廣度優先搜索和深度優先搜索是圖上的兩種最常用、最基本的搜索算法,比起其他高級的搜索算法,比如A、 IDA等,要簡單粗暴,沒有什麼優化,所以,也被
      叫作暴力搜索算法。
    • 所以,這兩種搜索算法僅適用於狀態空間不大,也就是說圖不大的搜索。
    • 廣度優先搜索,通俗的理解就是,地毯式層層推進,從起始頂點開始,依次往外遍歷。
    • 廣度優先搜索需要藉助隊列來實現,遍歷得到的路徑就是,起始頂點到終止頂點的最短路徑。
    • 深度優先搜索用的是回溯思想,非常適合用遞歸實現。換種說法,深度優先搜索是藉助棧來實現的。
    • 在執行效率方面,深度優先和廣度優先搜索的時間複雜度都是 O(E),空間複雜度是 O(V)。

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案

  • springboot的jar為何能獨立運行

    springboot的jar為何能獨立運行

    歡迎訪問我的GitHub

    https://github.com/zq2599/blog_demos
    內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

    能獨立運行的jar文件

    在開發springboot應用時,通過java -jar命令啟動應用是常用的方式,今天就來一起了解這個簡單操作背後的技術;

    開發demo

    開發一個springboot應用作為本次研究的對象,對應的版本信息如下:

    • JDK:1.8.0_211
    • springboot:2.3.1.RELEASE
    • maven:3.6.0

    接下來開發springboot應用,這個應用異常簡單:

    1. springboot應用名為springbootstarterdemo,pom.xml文件內容:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.bolingcavalry</groupId>
        <artifactId>springbootstarterdemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springbootstarterdemo</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    1. 只有一個java類,裏面有個http接口:
    package com.bolingcavalry.springbootstarterdemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Date;
    
    @SpringBootApplication
    @RestController
    public class SpringbootstarterdemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootstarterdemoApplication.class, args);
        }
        @RequestMapping(value = "/hello")
        public String hello(){
            return "hello " + new Date();
        }
    }
    
    1. 編碼完成,在pom.xml所在目錄執行命令
    mvn clean package -U -DskipTests
    
    1. 構建成功后,在target目錄下得到文件springbootstarterdemo-0.0.1-SNAPSHOT.jar
    2. 就是這個springbootstarterdemo-0.0.1-SNAPSHOT.jar,此時執行java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar就能啟動應用,如下圖:

    接下來就用這個springbootstarterdemo-0.0.1-SNAPSHOT.jar來分析jar文件能夠獨立啟動的原因;

    java -jar做了什麼

    • 先要弄清楚java -jar命令做了什麼,在oracle官網找到了該命令的描述:

      If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

    • 再次秀出我蹩腳的英文翻譯:

    1. 使用-jar參數時,後面的參數是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
    2. 該jar文件中包含的是class和資源文件;
    3. 在manifest文件中有Main-Class的定義;
    4. Main-Class的源碼中指定了整個應用的啟動類;(in its source code)
    • 小結一下:
      java -jar會去找jar中的manifest文件,在那裡面找到真正的啟動類;

    探查springbootstarterdemo-0.0.1-SNAPSHOT.jar

    1. springbootstarterdemo-0.0.1-SNAPSHOT.jar是前面的springboot工程的構建結果,是個壓縮包,用常見的壓縮工具就能解壓,我這裏的環境是MacBook Pro,用unzip即可解壓;

    2. 解壓後有很多內容,我們先關注manifest相關的,下圖紅框中就是manifest文件:

    3. 打開上圖紅框中的文件,內容如下:

    Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
    Implementation-Title: springbootstarterdemo
    Implementation-Version: 0.0.1-SNAPSHOT
    Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
     demoApplication
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Build-Jdk-Spec: 1.8
    Spring-Boot-Version: 2.3.1.RELEASE
    Created-By: Maven Jar Plugin 3.2.0
    Implementation-Vendor: Pivotal Software, Inc.
    Main-Class: org.springframework.boot.loader.JarLauncher
    
    1. 在上述內容可見Main-Class的值org.springframework.boot.loader.JarLauncher,這個和前面的java官方文檔對應上了,正是這個JarLauncher類的代碼中指定了真正的啟動類;

    疑惑出現

    1. 在MANIFEST.MF文件中有這麼一行內容:
    Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
     demoApplication
    
    1. 前面的java官方文檔中,只提到過Main-Class ,並沒有提到Start-Class
    2. Start-Class的值是SpringbootstarterdemoApplication,這是我們的java代碼中的唯一類,也只真正的應用啟動類;
    3. 所以問題就來了:理論上看,執行java -jar命令時JarLauncher類會被執行,但實際上是SpringbootstarterdemoApplication被執行了,這其中發生了什麼呢?

    猜測

    動手之前先猜一下,個人覺得原因應該如下:

    1. java -jar命令會啟動JarLauncher;
    2. Start-Class是給JarLauncher用的;
    3. JarLauncher根據Start-Class找到了SpringbootstarterdemoApplication,然後執行它;

    分析JarLauncher

    1. 先下載SpringBoot源碼,我下載的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE

    2. JarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的繼承關係,如下圖,可見JarLauncher繼承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父類Launcher位於最頂層,是個抽象類:

    3. java -jar執行的是JarLauncher的main方法,如下,會實例化一個JarLauncher對象,然後執行其launch方法,並且將所有入參都帶入:

    public static void main(String[] args) throws Exception {
    	new JarLauncher().launch(args);
    }
    
    1. 上面的launch方法在父類Launcher中:
    protected void launch(String[] args) throws Exception {
        // 將jar解壓后運行的方式叫做exploded mode
        // 如果是exploded mode,就不能支持通過URL加載jar
        // 如果不是exploded mode,就可以通過URL加載jar
    	if (!isExploded()) {
    	    // 如果允許通過URL加載jar,就在此註冊對應的處理類
    		JarFile.registerUrlProtocolHandler();
    	}
    	// 創建classLoader
    	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
    	// jarmode是創建docker鏡像時用到的參數,使用該參數是為了生成帶有多個layer信息的鏡像
    	// 這裏暫時不關注jarmode
    	String jarMode = System.getProperty("jarmode");
    	//如果沒有jarmode參數,launchClass的值就來自getMainClass()返回
    	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
    	launch(args, launchClass, classLoader);
    }
    
    1. 可見要重點關注的是getMainClass()方法,在看這個方法之前,我們先去關注一個重要的成員變量archive,是JarLauncher的父類ExecutableArchiveLauncher的archive,如下可見,該變量又來自方法createArchive:
    public ExecutableArchiveLauncher() {
    		try {
    			this.archive = createArchive();
    			this.classPathIndex = getClassPathIndex(this.archive);
    		}
    		catch (Exception ex) {
    			throw new IllegalStateException(ex);
    		}
    	}
    
    1. 方法來自Launcher.createArchive,如下所示,可見成員變量archive實際上是個JarFileArchive對象:
    protected final Archive createArchive() throws Exception {
    		ProtectionDomain protectionDomain = getClass().getProtectionDomain();
    		CodeSource codeSource = protectionDomain.getCodeSource();
    		URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
    		String path = (location != null) ? location.getSchemeSpecificPart() : null;
    		if (path == null) {
    			throw new IllegalStateException("Unable to determine code source archive");
    		}
    		File root = new File(path);
    		if (!root.exists()) {
    			throw new IllegalStateException("Unable to determine code source archive from " + root);
    		}
    		return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
    	}
    
    1. 現在回到getMainClass()方法,可見his.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的內容,然後getValue(START_CLASS_ATTRIBUTE)方法實際上就是從META-INF/MANIFEST.MF中取得了Start-Class的屬性:
    @Override
    	protected String getMainClass() throws Exception {
    	    // 對應的是JarFileArchive.getManifest方法,
    	    // 進去后發現對應的就是JarFile.getManifest方法,
    	    // JarFile.getManifest對應的就是META-INF/MANIFEST.MF文件的內容
    		Manifest manifest = this.archive.getManifest();
    		String mainClass = null;
    		if (manifest != null) {
    		    // 對應的是META-INF/MANIFEST.MF文件中的Start-Class的屬性
    			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
    		}
    		if (mainClass == null) {
    			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
    		}
    		return mainClass;
    	}
    
    1. 從上述分析可知:getMainClass()方法返回的是META-INF/MANIFEST.MF中取得了Start-Class的屬性com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication,再次回到launch方法中,可見最終運行的代碼是launch(args, launchClass, classLoader),它的launchClass參數就是com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:
    protected void launch(String[] args) throws Exception {
    		if (!isExploded()) {
    			JarFile.registerUrlProtocolHandler();
    		}
    		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
    		String jarMode = System.getProperty("jarmode");
    		// 這裏的launchClass等於"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
    		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
    		// 這裏就是啟動SpringbootstarterdemoApplication的地方
    		launch(args, launchClass, classLoader);
    	}
    
    1. 展開launch(args, launchClass, classLoader),最終查到了MainMethodRunner類:
    public class MainMethodRunner {
    
    	private final String mainClassName;
    
    	private final String[] args;
    
    	/**
    	 * Create a new {@link MainMethodRunner} instance.
    	 * @param mainClass the main class
    	 * @param args incoming arguments
    	 */
    	public MainMethodRunner(String mainClass, String[] args) {
    	    // mainClassName被賦值為"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
    		this.mainClassName = mainClass;
    		this.args = (args != null) ? args.clone() : null;
    	}
    
    	public void run() throws Exception {
    	    // 得到SpringbootstarterdemoApplication的Class對象
    		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
    		// 得到SpringbootstarterdemoApplication的main方法對象
    		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
    		mainMethod.setAccessible(true);
    		// 通過反射執行main方法
    		mainMethod.invoke(null, new Object[] { this.args });
    	}
    }
    

    終於,真相大白了;

    小結

    最後盡可能簡短做個小結,先看jar是如何產生的,如下圖,maven插件生成的jar文件中,有常見的class、jar,也有符合java規範的MANIFEST.MF文件,並且,還在MANIFEST.MF文件中額外生成了名為Start-Class的配置,這裏面是我們編寫的應用啟動類SpringbootstarterdemoApplication

    啟動類是JarLauncher,它是如何與MANIFEST.MF文件關聯的呢?從下圖可以看出,最終是通過JarFile類的成員變量manifestSupplier關聯上的:

    再來看看關鍵代碼的執行情況,如下圖:

    至此,SpringBoot的jar獨立運行的基本原理已經清楚,探究的過程中,除了熟悉關鍵代碼流程,還對jar中的文件有了更多了解,如果您正在學習SpringBoot,希望本文能給您一些參考;

    官方文檔

    1. 最後附上SpringBoot官方文檔,可以看到Start-Class描述信息:

    2. 上述文檔明確提到:Start-Class定義的是實際的啟動類,此時的您應該對一切都瞭然於胸,產生本該如此的感慨;

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

    ※超省錢租車方案