標籤: 南投搬家公司費用

  • 厄瓜多油價漲惹民怨 示威者衝進國民議會爆衝突

    摘錄自2019年10月8日中央社報導

    抗議厄瓜多總統莫雷諾(Lenin Moreno)政府取消燃料補助導致油價飆漲的示威活動越演越烈,警方和示威者8日在國民議會爆發衝突。

    根據Ecuavisa頻道報導,示威者成功突破國會大廈封鎖線,當中有許多是手持棍棒和鞭子的原住民男子。示威者衝進會議室霸占講台,但幾分鐘後就遭到安全部隊驅離。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

  • LeetCode 80,不使用外部空間的情況下對有序數組去重

    LeetCode 80,不使用外部空間的情況下對有序數組去重

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是LeetCode專題的第49篇文章,我們一起來看LeetCode的第80題,有序數組去重II(Remove Duplicates from Sorted Array II)。

    這題的官方難度是Medium,通過率是43.3%,點贊1104,反對690。這題的通過率有一點點高,然後點贊比也不是很高。說明這題偏容易,並且大家的評價偏低。也的確如此,我個人覺得,大家評價不好的主要原因還是這題偏容易了一些。

    題面

    其實從題目的標題當中我們已經可以得到很多信息了,實際上也的確如此,這題的題面和標題八九不離十,需要我們對一個有序的數組進行去重。不過去重的條件是最多允許一個元素出現兩次,也就是要將多餘的元素去掉。並且題目還限制了需要我們在原數組進行操作,對於空間複雜度的要求是。由於我們去除了元素之後會帶來數組長度的變化,所以我們最後需要返回完成之後數組的長度。

    這是一種常規的做法,在C++以及一些古老的語言當中數組是不能變更長度的。我們想要在原數組上刪除數據,只能將要刪除的數據移動到數組末尾,然後返回變更之後的數組長度。這樣下游就通過返回的數組長度得知變更之後的數量變化。由於新晉的一些語言,比如Java、Python都支持數組長度變動,所以很少在這些語言的代碼當中看到這樣的用法了。

    樣例

    Given nums = [0,0,1,1,1,1,2,3,3],
    
    Your function should return length = 7, with the first seven elements of nums being modified to 0, 0, 1, 1, 2, 3 and 3 respectively.
    
    It doesn't matter what values are set beyond the returned length. 

    在這個樣例當中,由於1出現了4次,所以我們需要刪除掉2個1,那麼刪除之後的數組長度也會減少2,所以我們需要返回7,表示刪除之後的新的數組的有效長度是7。並且保證原數組當中前5個元素是[0, 0, 1, 1, 2, 3]

    題解

    刪除重複的元素本身並不複雜,唯一麻煩的是我們怎麼在不引入額外存儲的情況下完成這一點。如果你能抓住數組是有序的這一點,應該很容易想通:既然數組是有序的,那麼相同的元素必然排在一起。

    既然相同的元素排在一起,那麼我們可以利用一個變量存儲當前元素出現的次數。如果遇到不同的元素,則將次數置為1。這樣我們就可以判斷出究竟哪些元素需要刪除,哪些元素需要保留了。

    但是這就又引入了另外一個問題,我們怎麼來刪除這些重複的元素呢?因為我們不能引入額外的數組,需要在當前數組上完成。我們可以先假設沒有這個限制,我們會怎麼做?

    new_nums = []
    cur = None
    for i in range(n):
        if cur == nums[i]:
            count += 1
     else:
            count = 1
            cur = nums[i]
        if count > 2:
            continue
        new_nums.append(nums[i])
    

    由於有這個限制,所以我們要做的就是把new_nums這個數組去掉,其實去掉是很簡單的,因為我們可以讓nums這個數組自己覆蓋自己。因為產出的數據的數量一定是小於等於數組長度的,所以不會出現數組越界的問題。我們只需要維護一個下標記錄nums數組當中允許覆蓋的位置即可。

    這個也是非常常見的做法,我們在之前的題目當中也曾經見到過。

    class Solution:
        def removeDuplicates(self, nums: List[int]) -> int:
            # start是起始覆蓋指針,指向第一個可以覆蓋的位置
            start, cur, cnt = 0, None, 0
            n = len(nums)
            if n == 0:
                return 0
            for i in range(n):
                if cur == nums[i]:
                    cnt += 1
                else:
                    cnt = 1
                    cur = nums[i]
                # 如果數量超過2,說明當前元素應該捨棄,則continue
                if cnt > 2:
                    continue
                # 否則用當前元素覆蓋start位置,並且start移動一位
                else:
                    nums[start] = nums[i]
                    start += 1
            return start
    

    關於這段代碼,還有一個簡化版本,我們可以把cnt變量也省略掉。因為元素是有序的,我們可以直接用nums[i]和nums[i-2]進行判斷,如果相等,那麼說明重複的元素一定超過了兩個,當前元素需要跳過。

    簡化之後的代碼如下:

    class Solution(object):
        def removeDuplicates(self, nums):
            """  :type nums: List[int]  :rtype: int  """
            i = 0
            for n in nums:
                if i < 2 or n != nums[i - 2]:
                    nums[i] = n
                    i += 1
            return i
    

    總結

    今天的題目不難,總體來說算是Medium偏低難度,主要有兩點值得稱道。第一點是C++風格inplace變更數組的做法,第二點就是數組自我覆蓋的方法。除此之外,題目幾乎沒什麼難度,我想大家應該都能想出解法來。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

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

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 新能源汽車利好消息層出不窮 下半年產銷量有望爆發式增長—第五屆新能源汽車峰會暨展覽會

    中汽協發佈的最新資料顯示,今年上半年中國新能源汽車生產20692輛,銷售20477輛,同比分別增長2.3倍和2.2倍,產銷量已經超過去年全年的1.7萬輛。其中純電動汽車產銷分別完成12185輛和11777輛,插電式混合動力汽車產銷分別完成8507輛和8700輛。在新政頻發、充電基礎設施和電池產業鏈發展及車企爭奪多番力量推動下,新能源汽車下半年產銷量有望呈爆發式增長。

    新政4連發護航

    2014年初以來中國中央政府確定了40個新能源汽車示範城市或區域,預計未來2年內累計推廣規模將超過30萬輛。而且近期北京、上海、廣東、江蘇等9個示範區域的實施細則陸續落地。補貼政策從購車、用車、配套等多環節提升新能源汽車經濟性,驅動產業快速發展。

    國家機關購買新能源汽車管理辦法發佈。中國中央政府門戶網站7月13日刊登消息,《政府機關及公共機構購買新能源汽車實施方案》(下稱《方案》)印發。主要內容包括,2014年至2016年,中央國家機關以及88個新能源汽車推廣應用城市的政府機關及公共機構購買的新能源汽車占當年配備更新總量的比例不低於30%,以後逐年提高。方案明確規定至2016年公車採購中購買的新能源汽車占當年配備更新總量的比例不低於30%,這意味著新能源車迎來超過300億元的市場份額。《方案》的出臺將加快政府機關採購新能源汽車的速度。

    國務院:《關於加快新能源汽車推廣應用的指導意見》。《意見》明確,要以純電驅動為新能源汽車發展的主要戰略取向,重點發展純電動汽車、插電式混合動力汽車和燃料電池汽車,以市場主導和政府扶持相結合,建立長期穩定的新能源汽車發展政策體系,創造良好發展環境,加快培育市場,促進新能源汽車產業健康發展。

    發改委出臺電動汽車用電價格政策,私家車執行居民電價。為貫徹落實國務院辦公廳《關於加快新能源汽車推廣應用的指導意見》精神,利用價格杠杆促進電動汽車推廣應用,近日,國家發展改革委下發《關於電動汽車用電價格政策有關問題的通知》,確定對電動汽車充換電設施用電實行扶持性電價政策。《通知》的下發,將有利於降低電動汽車使用成本,在鼓勵消費者購買使用電動汽車、促進電動汽車推廣應用方面發揮積極作用。

    中國財政部、工信部頒佈新能源汽車免征購置稅。8月6日,財政部、工信部等3部門發佈《關於免征新能源汽車車輛購置稅的公告》,這是自7月份以來,國家相關部門發佈的第四個新能源車政策。上述公告稱,自2014年9月1日至2017年12月31日,對購置的新能源汽車免征車輛購置稅;對免征車輛購置稅的新能源汽車,由工業和資訊化部、國家稅務總局通過發佈《免征車輛購置稅的新能源汽車車型目錄》實施管理。

    充電樁基礎設施繼續完善

    中國政策引導給力。《政府機關及公共機構購買新能源汽車實施方案》要求政府和公共機關配建相應的充電基礎設施。充電介面與新能源汽車數量比例不低於1:1。同時,北京即將出臺《關於推進物業管理區域新能源小客車自用充電設施安裝的通知》,物業不配合充電樁建設將被罰分。此外,針對新能源車推廣,即將出臺配套檔要求,新建社區停車位配建充電樁的不低於18%,這也將作為規劃審批條件之一。充電樁建設方面,此前上海市發佈了相關辦法,鼓勵社會企業開展充電設施建設和服務,對公共充電設施的投資給予30%的補貼。

    國際中德電動汽車充電項目正式啟動。7月8日,默克爾與中國工信部部長苗圩在清華大學共同拉動代表中德電動車合作專案的手柄,中德電動汽車充電項目正式啟動。根據合作協定,未來中國和德國電動車將實現充電介面標準完全統一,雙方還將簽署充電通信協定,最終實現充電設施的完全共用。這意味著未來比亞迪等中國電動車品牌,將與寶馬、奧迪等德國汽車品牌使用相同的充電設施。

    中國國網或將退出城市充電設施建設,引入更多社會資本參與建設。繼開放電動汽車充換電設施市場之後,國家電網在充電設施建設領域將再退一步,退出城市中的電動汽車充電設施建設市場,專注於交通幹道上的充電設施建設。多個接近國家電網的人士透露,國家電網的這一決定已經下發各級省級電網公司。決定的主體內容即是:國網將全面退出城市充電設施建領域,引入更多社會資本參與建設,國網則全力推進交通幹道,也即城際互聯充電網路建設。

    電池產業鏈蓄勢待發

    寶馬欲同賓士等競爭對手共用最新電池技術。寶馬集團日前表示,為了實現規模經濟,對於同競爭對手共用最新電池技術持開放態度。前不久寶馬和三星SDI簽約共同開發電池單元技術,並擴大電池訂單量。

    富士康逾20億投資安徽設鋰電池生產線。富士康子公司鋰科科技將在安慶市經開區投資20.9億元,分期建設高分子聚合物電芯及電池組生產專案。富士康還在安徽醞釀其他投資專案,“其中會有新能源汽車方向。”富士康從2005年收購臺灣安泰電業後才正式進入汽車領域,目前主要涉及汽車電子及新能源汽車兩大板塊,分別由安泰電業及鋰科科技兩家公司主導。

    松下與特斯拉達成協議,總投資60億建超級電池廠。松下與特斯拉就美國建設電動汽車電池工廠一事達成基本協議。松下的總投資額為1千億日元左右。松下已向特斯拉出資,同時向該公司的電動汽車供應鋰離子電池。電池工廠將在特斯拉主導下建設,將配合需求,分階段擴大產能,松下的初期投資預計為200億~300億日元。松下已將汽車領域定位為成長戰略的支柱,將加快擴大作為環保車核心零部件的電池業務。

    三星SDI正與寶馬、大眾、克萊斯勒、福特及印度馬恒達等車企磋商有關電動車電池組供應計畫,也準備參與到中國政府大力推進的新能源車產業。三星SDI在全球電動車專案已達10餘個。同時,繼松下之後,三星SDI從2015年起也將為大眾汽車提供電池。大眾新開發的D-segment中型電動車也將採用三星電池。據瞭解,在2014年初的底特律車展上,大眾就已經採用了三星SDI的電池。三星SDI相關負責人員表示,“寶馬在中國市場快速發展其電動車專案且已初見成效,相信今後電動車電池的供貨量將會高速增長,三星與寶馬的合作也將呈現雙贏局面”。

    八家汽車巨頭加盟EPRI 制定和完善電動汽車行業標準。近日八家已經涉足電動汽車領域的巨頭本田,寶馬,克萊斯勒,通用,福特,賓士,三菱和豐田宣佈同其他7家企業共同成立電力科學研究院(EPRI),來制定和完善混合動力和純電動汽車的儀錶、內部結構、充電等相關標準。近日網通社在對將推出的新電池技術進行統計後發現:全新材質電池以及現有鋰離子電池的改進,正成為電動汽車發展的趨勢,未來隨著新電池技術的推出,電動車續航里程有望達到特斯拉四倍,達到1600公里之多。

    車企近來動作頻頻

    寶馬集團發佈未來戰略佈局中國市場。日前,寶馬集團在其新聞發佈會上確認,寶馬集團與華晨中國汽車控股有限公司(以下簡稱“華晨汽車”)的合資協議延長至2028年;基於這一合作基礎,寶馬集團公佈了接下來在中國市場的一系列戰略佈局,包括擴大在華產能、拓展國產產品線,以及在中國投產先進的發動機等,展示了寶馬集團對中國市場更加強勁的投入。寶馬集團對於中國市場寄予厚望,認為中國有望成為世界上最大的新能源汽車市場。目前,寶馬集團和華晨寶馬可以提供的電動汽車解決方案包括BMW i系列、採用新能源動力的BMW車型,以及之諾品牌產品,以期在中國“綠色交通”發展過程中搶佔先機。

    比亞迪牽手廣汽成立新能源客車公司。8月4日晚間,比亞迪和廣汽集團同時發佈公告稱,二者將成立合資公司生產新能源客車,雙方聯手佈局新能源汽車市場。據業內分析,比亞迪攜手廣汽集團將有助於發揮二者在汽車製造銷售方面的優勢。廣汽集團固有的汽車製造能力及銷售管道將為比亞迪目前上市的新能源車型拓展銷路。比亞迪整車製造能力方面的缺陷也可借力廣汽集團彌補,公司可以專攻擴大電池產能。

    陳虹醞釀前瞻技術部,上汽研發資源向新能源傾斜。上汽集團董事長陳虹要對上汽研發體系進行整合,醞釀推出前瞻性技術部,未來每年都會投入3億用於前瞻科技研究,主要涉及新能源、輕量化及車聯網三大領域。

    上汽集團宣佈要與阿裡巴巴聯合造新能源車。據悉,上汽和阿裡的“互聯網汽車”從源頭上重新定義汽車和車載系統。其目標是使使用者通過汽車與網路無縫對接,徹底改變當前車載系統功能簡單、使用者體驗不佳的種種局限。

    樂視與北汽或將打造“樂視電動汽車”。北汽董事長徐和誼也在密會樂視CEO賈躍亭,並向外界釋放信號表示願意“代工生產樂視汽車”。而近日。搜狐前副總編、汽車事業部總經理何毅從搜狐離職加盟樂視,未來或負責組建汽車團隊。北汽對於新能源車的發展信心滿滿,稱2年內將打造“中國的特斯拉”。

    目前國內新能源汽車在售車型接近20款,預計未來兩年還將投放接近30款新車,投放速度明顯加快。隨著產品投放加快,各車企對於新能源汽車產能的準備,尤其是電池等產業鏈的配套能力進行了充分準備,特斯拉完成消費者教育,消費者開始考慮購買新能源汽車,分時租賃等新商業模式引進,進一步促進新能源銷量提升。新能源政策的不斷落地,及特斯拉的鯰魚攪動效應,無論是中國資本市場還是實體企業,都掀起了一股新能源汽車熱。

    在此背景下,將於11月12日-14日在中國北京召開。大會由中國汽車工業協會和決策者會議聯合主辦,交通大學汽車工程研究院協辦,得到日本汽車工業協會和眾多一線整車商鼎力支持。主題為新增長局勢下的中國新能源汽車產業動態聚焦,專注於新能源整車商項目、戰略規劃以及對核心設備的需求、對於新能源汽車電氣以及動力系統、電控系統、智慧汽車創新、動力電池沖換電基建建設系統的案例分析以及核心技術的全面探討。作為中國電動汽車行業領先的峰會,新能源汽車峰會歷經4年發展,無論是參會企業,還是贊助商數量都位居國內前列。歡迎政府機關行業協會、海內外汽車生產商、科研單位、大學院校、汽車電池生產商、核心零部件提供商、整體服務解決方案提供商及其他服務提供者來電諮詢。

    【本屆參數統計】

    600+業內權威專家業內專業人士,400+專業參展觀眾,來自于320+行業知名企業單位,23+個國家
    120+位參會代表來自語全球領先整車商,以及110+核心零部件提供商企業代表
    40+ 知名權威發言人,為您敘說新能源汽車行業熱點資訊
    16+ 小時商務交流機會,貫穿於雞尾酒會,小組討論,交流午宴及提問互動環節
    6 場專題討論,為您深度解析關注行業熱點
    5 年歷史,鑄就行業年度盛會

    【展會特色】

    實效性:展會期間將進行一對一會談、頒獎典禮,突出實效和品牌,做大做深供求雙方專業化配對洽談工作,為廣大業內人士及下游應用企業提供集中領略行業最新趨勢的機會。
    品牌化:作為中國電動汽車行業最早商業化運作的峰會,歷經四年發展,無論是參會數量,還是贊助商數量,在國內同行業峰會中,都是雄踞前列,深受業界同仁的認可和讚揚,其知名度和美譽度在業內廣為流傳。成為中國電動汽車行業名符其實的第一會。 
    國際化:往屆嘉賓有來自美國、日本、韓國、德國、丹麥、義大利、臺灣等國家和地區的國際企業參會,已經成為電動汽車行業的資訊分享、技術交流、貿易採購平臺。
    專業化:是國內目前唯一的電動汽車行業的專業峰會之一,一年一屆。內容包含電動汽車(含混合動力)的整車、零部件、管理系統、充電站及相關配套設施等. 將吸引來自中國電動汽車企業超320家企業巨頭高層參觀,雲集政府機關行業協會,整車商,大學院校及研究院,零部件百強企業,核心技術設備提供商。
    全媒體曝光:主辦方將基於網站、雜誌、微信、微博等多媒體平臺,在展前、展中、展後分別做全方位即時報導,預計將會實現超過10萬人次覆蓋。

    【2014年新能源汽車頒獎典禮獎項設置】

    年度優秀電動汽車電池生廠商獎
    年度優秀新能源汽車諮詢公司獎
    年度優秀新能源汽車解決方案提供商獎
    終身成就獎
    企業社會責任商獎
    優秀核心零部件提供商獎
    優秀新能源汽車服務商獎
    優秀新能源汽車技術提供商獎

    【展商評價】

    “是一個尋找合作夥伴的理想場所,本次參展讓我們受益頗多,明年會一如既往支援綠色汽車大會!”—— Shinry Technologies Co., Ltd
    “通過很好的管道將我們的產品展現在客戶面前,非常滿意。”—— AGC Automtive
    “綠色汽車大會提供一個行業人士交流的平臺,參會參展企業眾多,達到了我們的參展期望值。”—— Thermal Hazard Technology
    “我們同時參加了峰會和展覽,非常值得推薦的活動!”—— 捷特科
    “非常滿意,無論是活動內容,參會嘉賓,還是規模層次都很出色,會推薦個同事。”—— W.E.T. Automotive Systems (China) Ltd

      展會網站:  

    連絡人: 邱小姐(Elva Qiu)

    電話 : +86 21 63931899-2041

    手機:+86 18930215786

    郵箱:

    QQ:1147789586

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

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

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 鴻海再加碼大陸 布局電動車市場

    鴻海 13 日對外表示,將發行上限 240 億元無擔保普通公司債募資,以償還短期負債。另外,將透過轉投資第三地,赴大陸投資重慶元創汽車整線集成公司約新台幣 4.9 億元,強化車用相關布局。   重慶元創汽車整線集成公司主要從事汽車模、夾、檢具的設計開發與製造。市場人士預期,鴻海該項投資主要為電動車事業播種,未來有機會整合其自動化相關應用在汽車生產線上。   鴻海近期對電動車相當有興趣,市場預期,鴻海要將製造消費性電子的技術,應用在生產電動車上,讓電動車的價格更親民。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

  • 新來的”大神”用策略模式把if else給”優化”了,技術總監說:能不能想好了再改?

    新來的”大神”用策略模式把if else給”優化”了,技術總監說:能不能想好了再改?

    本文來自作者投稿,原作者:上帝愛吃蘋果

    目前在魔都,貝殼找房是我的僱主,平時關注一些 java 領域相關的技術,希望你們能在這篇文章中找到些有用的東西。個人水平有限,如果文章有錯誤還請指出,在留言區一起交流。

    我想大家肯定都或多或少的看過各種“策略模式”的講解、佈道等等,這篇文章就是來好好“澄清”一下策略模式,並嘗試回答以下的問題:

    1. 策略模式是如何優化業務邏輯代碼結構的?

    2. 殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?

    3. 有沒有什麼更好的代碼結構來實現策略模式的嗎?

    策略模式是如何優化業務邏輯代碼結構的?

    要回答這個問題,我們還得先扒一扒策略模式的定義,從定義着手來理解它

    策略模式的教科書定義

    它的定義很精簡:一個類的行為或其算法可以在運行時更改。我們把它降維到代碼層面,用人話翻譯一下就是,運行時我給你這個類的方法傳不同的“key”,你這個方法會執行不同的業務邏輯。細品一下,這不就是 if else 乾的事嗎?

    策略模式優化了什麼?

    其實策略模式的核心思想和 if else如出一轍,根據不同的key動態的找到不同的業務邏輯,那它就只是如此嗎?

    實際上,我們口中的策略模式其實就是在代碼結構上調整,用接口+實現類+分派邏輯來使代碼結構可維護性好點。

    一般教科書上講解到接口與實現類就結束了,其他博客上會帶上提及分派邏輯。這裏就不啰嗦了。

    小結一下,即使用了策略模式,你該寫的業務邏輯照常寫,到邏輯分派的時候,還是變相的if else。而它的優化點是抽象了出了接口,將業務邏輯封裝成一個一個的實現類,任意地替換。在複雜場景(業務邏輯較多)時比直接 if else 來的好維護些。

    殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?!

    我想小夥伴們經常有這樣的不滿,我的業務邏輯就3 4 行,你給我整一大堆類定義?有必要這麼麻煩嗎?我看具體的業務邏輯還需要去不同的類中,簡單點行不行。

    其實我們所不滿的就是策略模式帶來的缺點:

    1、策略類會增多

    2、業務邏輯分散到各個實現類中,而且沒有一個地方可以俯視整個業務邏輯

    針對傳統策略模式的缺點,在這分享一個實現思路,這個思路已經幫我們團隊解決了多個複雜if else的業務場景,理解上比較容易,代碼上需要用到Java8的特性——利用Map與函數式接口來實現。

    直接show代碼結構:為了簡單演示一個思路,代碼用String 類型來模擬一個業務BO

    其中:

    1. getCheckResult() 為傳統的做法

    2. getCheckResultSuper()則事先在Map中定義好了“判斷條件”與“業務邏輯”的映射關係,具體講解請看代碼註釋

    /**
     * 某個業務服務類
     */
    @Service
    public class BizService {
    
        /**
         * 傳統的 if else 解決方法
         * 當每個業務邏輯有 3 4 行時,用傳統的策略模式不值得,直接的if else又顯得不易讀
         */
        public String getCheckResult(String order) {
            if ("校驗1".equals(order)) {
                return "執行業務邏輯1";
            } else if ("校驗2".equals(order)) {
                return "執行業務邏輯2";
            }else if ("校驗3".equals(order)) {
                return "執行業務邏輯3";
            }else if ("校驗4".equals(order)) {
                return "執行業務邏輯4";
            }else if ("校驗5".equals(order)) {
                return "執行業務邏輯5";
            }else if ("校驗6".equals(order)) {
                return "執行業務邏輯6";
            }else if ("校驗7".equals(order)) {
                return "執行業務邏輯7";
            }else if ("校驗8".equals(order)) {
                return "執行業務邏輯8";
            }else if ("校驗9".equals(order)) {
                return "執行業務邏輯9";
            }
            return "不在處理的邏輯中返回業務錯誤";
        }
    
        /**
         * 業務邏輯分派Map
         * Function為函數式接口,下面代碼中 Function<String, String> 的含義是接收一個Stirng類型的變量,返回一個String類型的結果
         */
        private Map<String, Function<String, String>> checkResultDispatcher = new HashMap<>();
    
        /**
         * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
         */
        @PostConstruct
        public void checkResultDispatcherInit() {
            checkResultDispatcher.put("校驗1", order -> String.format("對%s執行業務邏輯1", order));
            checkResultDispatcher.put("校驗2", order -> String.format("對%s執行業務邏輯2", order));
            checkResultDispatcher.put("校驗3", order -> String.format("對%s執行業務邏輯3", order));
            checkResultDispatcher.put("校驗4", order -> String.format("對%s執行業務邏輯4", order));
            checkResultDispatcher.put("校驗5", order -> String.format("對%s執行業務邏輯5", order));
            checkResultDispatcher.put("校驗6", order -> String.format("對%s執行業務邏輯6", order));
            checkResultDispatcher.put("校驗7", order -> String.format("對%s執行業務邏輯7", order));
            checkResultDispatcher.put("校驗8", order -> String.format("對%s執行業務邏輯8", order));
            checkResultDispatcher.put("校驗9", order -> String.format("對%s執行業務邏輯9", order));
        }
    
        public String getCheckResultSuper(String order) {
            //從邏輯分派Dispatcher中獲得業務邏輯代碼,result變量是一段lambda表達式
            Function<String, String> result = checkResultDispatcher.get(order);
            if (result != null) {
                //執行這段表達式獲得String類型的結果
                return result.apply(order);
            }
            return "不在處理的邏輯中返回業務錯誤";
        }
    }
    

    通過http調用一下看看效果:

    /**
     * 模擬一次http調用
     */
    @RestController
    public class BizController {
    
        @Autowired
        private BizService bizService;
    
        @PostMapping("/v1/biz/testSuper")
        public String test2(String order) {
            return bizService.getCheckResultSuper(order);
        }
    }
    

    這是個簡單的demo演示,之後會舉一些複雜的場景案例。

    魯迅曾說過,“每解決一個問題,就會因出更多的問題”。我們一起來看看這樣的實現有什麼好處,會帶來什麼問題。

    好處很直觀:

    1. 在一段代碼里直觀的看到”判斷條件”與業務邏輯的映射關係
    2. 不需要單獨定義接口與實現類,直接使用現有的函數式接口(什麼?不知道函數式接口?快去了解),而實現類直接就是業務代碼本身。

    不好的點:

    1. 需要團隊成員對lambda表達式有所了解(什麼?Java14都出來了還有沒用上Java8新特性的小夥伴?)

    接下來我舉幾個在業務中經常遇到的if else場景,並用Map+函數式接口的方式來解決它

    在真實業務場景問題的解決思路

    有的小夥伴會說,我的判斷條件有多個啊,而且很複雜,你之前舉個例子只有單個判斷邏輯,而我有多個判斷邏輯該怎麼辦呢?

    很好解決:寫一個判斷邏輯的方法,Map的key由方法計算出

    /**
     * 某個業務服務類
     */
    @Service
    public class BizService {
    
        private Map<String, Function<String, String>> checkResultDispatcherMuti = new HashMap<>();
    
        /**
         * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
         */
        @PostConstruct
        public void checkResultDispatcherMuitInit() {
            checkResultDispatcherMuti.put("key_訂單1", order -> String.format("對%s執行業務邏輯1", order));
            checkResultDispatcherMuti.put("key_訂單1_訂單2", order -> String.format("對%s執行業務邏輯2", order));
            checkResultDispatcherMuti.put("key_訂單1_訂單2_訂單3", order -> String.format("對%s執行業務邏輯3", order));
        }
    
        public String getCheckResultMuti(String order, int level) {
            //寫一段生成key的邏輯:
            String ley = getDispatcherKey(order, level);
    
            Function<String, String> result = checkResultDispatcherMuti.get(ley);
            if (result != null) {
                //執行這段表達式獲得String類型的結果
                return result.apply(order);
            }
            return "不在處理的邏輯中返回業務錯誤";
        }
    
        /**
         * 判斷條件方法
         */
        private String getDispatcherKey(String order, int level) {
            StringBuilder key = new StringBuilder("key");
            for (int i = 1; i <= level; i++) {
                key.append("_" + order + i);
            }
            return key.toString();
        }
    }
    

    用http模擬一下:

    /**
     * 模擬一次http調用
     */
    @RestController
    public class BizController {
    
        @Autowired
        private BizService bizService;
    
        @PostMapping("/v1/biz/testMuti")
        public String test1(String order, Integer level) {
            return bizService.getCheckResultMuti(order, level);
        }
    }
    

    只要設計好你的key的生成規則就好。

    還有小夥伴會問:我的業務邏輯有很多很多行,在checkResultDispatcherMuitInit()方法的Map中直接寫不會很長嗎?

    直接寫當然長了,我們可以抽象出一個service服務專門放業務邏輯,然後在定義中調用它就好了:

    提供一個業務邏輯單元:

    /**
     * 提供業務邏輯單元
     */
    @Service
    public class BizUnitService {
    
        public String bizOne(String order) {
            return order + "各種花式操作1";
        }
        public String bizTwo(String order) {
            return order + "各種花式操作2";
        }
        public String bizThree(String order) {
            return order + "各種花式操作3";
        }
    }
    
    /**
     * 某個業務服務類
     */
    @Service
    public class BizService {
        @Autowired
        private BizUnitService bizUnitService;
    
        private Map<String, Function<String, String>> checkResultDispatcherComX = new HashMap<>();
    
        /**
         * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
         */
        @PostConstruct
        public void checkResultDispatcherComXInit() {
            checkResultDispatcherComX.put("key_訂單1", order -> bizUnitService.bizOne(order));
            checkResultDispatcherComX.put("key_訂單1_訂單2", order -> bizUnitService.bizTwo(order));
            checkResultDispatcherComX.put("key_訂單1_訂單2_訂單3", order -> bizUnitService.bizThree(order));
        }
    
        public String getCheckResultComX(String order, int level) {
            //寫一段生成key的邏輯:
            String ley = getDispatcherComXKey(order, level);
    
            Function<String, String> result = checkResultDispatcherComX.get(ley);
            if (result != null) {
                //執行這段表達式獲得String類型的結果
                return result.apply(order);
            }
            return "不在處理的邏輯中返回業務錯誤";
        }
    
        /**
         * 判斷條件方法
         */
        private String getDispatcherComXKey(String order, int level) {
            StringBuilder key = new StringBuilder("key");
            for (int i = 1; i <= level; i++) {
                key.append("_" + order + i);
            }
            return key.toString();
        }
    }
    

    調用結果:

    總結

    最後,我們一起嘗試回答以下幾個問題:

    1. 策略模式是如何優化業務邏輯代碼結構的?

    抽象了出了接口,將業務邏輯封裝成一個一個的實現類,任意地替換。在複雜場景(業務邏輯較多)時比直接 if else 來的好維護些。

    2. 殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?!

    我們所不滿的其實就是傳統接口實現的缺點: 1、策略類會很多。 2、業務邏輯分散到各個實現類中,而且沒有一個地方可以俯覽整個業務邏輯

    3. 有沒有什麼更好的代碼結構來實現策略模式的嗎?

    針對傳統策略模式的缺點,分享了利用Map與函數式接口來實現的思路。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

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

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • Spark學習筆記(三)-Spark Streaming

    Spark學習筆記(三)-Spark Streaming

    Spark Streaming支持實時數據流的可擴展(scalable)、高吞吐(high-throughput)、容錯(fault-tolerant)的流處理(stream processing)。

     

                                                        架構圖

     

    特性如下:

     

    • 可線性伸縮至超過數百個節點;

    • 實現亞秒級延遲處理;

    • 可與Spark批處理和交互式處理無縫集成;

    • 提供簡單的API實現複雜算法;

    • 更多的流方式支持,包括Kafka、Flume、Kinesis、Twitter、ZeroMQ等。

     

    原理

     

    Spark在接收到實時輸入數據流后,將數據劃分成批次(divides the data into batches),然後轉給Spark Engine處理,按批次生成最後的結果流(generate the final stream of results in batches)。 

     

     

    API

     

    DStream

     

    DStream(Discretized Stream,離散流)是Spark Stream提供的高級抽象連續數據流。

     

    • 組成:一個DStream可看作一個RDDs序列。

    • 核心思想:將計算作為一系列較小時間間隔的、狀態無關的、確定批次的任務,每個時間間隔內接收的輸入數據被可靠存儲在集群中,作為一個輸入數據集。

     

     

    • 特性:一個高層次的函數式編程API、強一致性以及高校的故障恢復。

    • 應用程序模板:

    • 模板1

    • 模板2

     

    WordCount示例

     

     

    Input DStream

     

    Input DStream是一種從流式數據源獲取原始數據流的DStream,分為基本輸入源(文件系統、Socket、Akka Actor、自定義數據源)和高級輸入源(Kafka、Flume等)。

     

    • Receiver:
    • 每個Input DStream(文件流除外)都會對應一個單一的Receiver對象,負責從數據源接收數據並存入Spark內存進行處理。應用程序中可創建多個Input DStream并行接收多個數據流。

    • 每個Receiver是一個長期運行在Worker或者Executor上的Task,所以會佔用該應用程序的一個核(core)。如果分配給Spark Streaming應用程序的核數小於或等於Input DStream個數(即Receiver個數),則只能接收數據,卻沒有能力全部處理(文件流除外,因為無需Receiver)。

    • Spark Streaming已封裝各種數據源,需要時參考官方文檔。

     

    Transformation Operation

     

    • 常用Transformation

     

    * map(func) :對源DStream的每個元素,採用func函數進行轉換,得到一個新的DStream;

    * flatMap(func):與map相似,但是每個輸入項可用被映射為0個或者多個輸出項;

    * filter(func):返回一個新的DStream,僅包含源DStream中滿足函數func的項;

    * repartition(numPartitions):通過創建更多或者更少的分區改變DStream的并行程度;

    * union(otherStream):返回一個新的DStream,包含源DStream和其他DStream的元素;

    * count():統計源DStream中每個RDD的元素數量;

    * reduce(func):利用函數func聚集源DStream中每個RDD的元素,返回一個包含單元素RDDs的新DStream;

    * countByValue():應用於元素類型為K的DStream上,返回一個(K,V)鍵值對類型的新DStream,每個鍵的值是在原DStream的每個RDD中的出現次數;

    * reduceByKey(func, [numTasks]):當在一個由(K,V)鍵值對組成的DStream上執行該操作時,返回一個新的由(K,V)鍵值對組成的DStream,每一個key的值均由給定的recuce函數(func)聚集起來;

    * join(otherStream, [numTasks]):當應用於兩個DStream(一個包含(K,V)鍵值對,一個包含(K,W)鍵值對),返回一個包含(K, (V, W))鍵值對的新DStream;

    * cogroup(otherStream, [numTasks]):當應用於兩個DStream(一個包含(K,V)鍵值對,一個包含(K,W)鍵值對),返回一個包含(K, Seq[V], Seq[W])的元組;

    * transform(func):通過對源DStream的每個RDD應用RDD-to-RDD函數,創建一個新的DStream。支持在新的DStream中做任何RDD操作。

     

    • updateStateByKey(func)

    • updateStateByKey可對DStream中的數據按key做reduce,然後對各批次數據累加

    • WordCount的updateStateByKey版本

     

    • transform(func)

    • 通過對原DStream的每個RDD應用轉換函數,創建一個新的DStream。

    • 官方文檔代碼舉例

     

    • Window operations

    • 窗口操作:基於window對數據transformation(個人認為與Storm的tick相似,但功能更強大)。

    • 參數:窗口長度(window length)和滑動時間間隔(slide interval)必須是源DStream批次間隔的倍數。

    • 舉例說明:窗口長度為3,滑動時間間隔為2;上一行是原始DStream,下一行是窗口化的DStream。

    • 常見window operation

    有狀態轉換包括基於滑動窗口的轉換和追蹤狀態變化(updateStateByKey)的轉換。

    基於滑動窗口的轉換

    * window(windowLength, slideInterval) 基於源DStream產生的窗口化的批數據,計算得到一個新的DStream;

    * countByWindow(windowLength, slideInterval) 返迴流中元素的一個滑動窗口數;

    * reduceByWindow(func, windowLength, slideInterval) 返回一個單元素流。利用函數func聚集滑動時間間隔的流的元素創建這個單元素流。函數func必須滿足結合律,從而可以支持并行計算;

    * reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) 應用到一個(K,V)鍵值對組成的DStream上時,會返回一個由(K,V)鍵值對組成的新的DStream。每一個key的值均由給定的reduce函數(func函數)進行聚合計算。注意:在默認情況下,這個算子利用了Spark默認的併發任務數去分組。可以通過numTasks參數的設置來指定不同的任務數;

    * reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]) 更加高效的reduceByKeyAndWindow,每個窗口的reduce值,是基於先前窗口的reduce值進行增量計算得到的;它會對進入滑動窗口的新數據進行reduce操作,並對離開窗口的老數據進行“逆向reduce”操作。但是,只能用於“可逆reduce函數”,即那些reduce函數都有一個對應的“逆向reduce函數”(以InvFunc參數傳入);

    * countByValueAndWindow(windowLength, slideInterval, [numTasks]) 當應用到一個(K,V)鍵值對組成的DStream上,返回一個由(K,V)鍵值對組成的新的DStream。每個key的值都是它們在滑動窗口中出現的頻率。

    • 官方文檔代碼舉例 

     

    • join(otherStream, [numTasks])

    • 連接數據流

    • 官方文檔代碼舉例1

    • 官方文檔代碼舉例2

     

    Output Operation

     

     

    緩存與持久化

     

    • 通過persist()將DStream中每個RDD存儲在內存。

    • Window operations會自動持久化在內存,無需显示調用persist()。

    • 通過網絡接收的數據流(如Kafka、Flume、Socket、ZeroMQ、RocketMQ等)執行persist()時,默認在兩個節點上持久化序列化后的數據,實現容錯。

     

    Checkpoint

     

    • 用途:Spark基於容錯存儲系統(如HDFS、S3)進行故障恢復。

    • 分類:

    • 元數據檢查點:保存流式計算信息用於Driver運行節點的故障恢復,包括創建應用程序的配置、應用程序定義的DStream operations、已入隊但未完成的批次。

    • 數據檢查點:保存生成的RDD。由於stateful transformation需要合併多個批次的數據,即生成的RDD依賴於前幾個批次RDD的數據(dependency chain),為縮短dependency chain從而減少故障恢復時間,需將中間RDD定期保存至可靠存儲(如HDFS)。

    • 使用時機:

    • Stateful transformation:updateStateByKey()以及window operations。

    • 需要Driver故障恢復的應用程序。

    • 使用方法

    • Stateful transformation

    streamingContext.checkpoint(checkpointDirectory)

     

    • 需要Driver故障恢復的應用程序(以WordCount舉例):如果checkpoint目錄存在,則根據checkpoint數據創建新StreamingContext;否則(如首次運行)新建StreamingContext。

     

    • checkpoint時間間隔

    • 方法:

    dstream.checkpoint(checkpointInterval)

     

    • 原則:一般設置為滑動時間間隔的5-10倍。

    • 分析:checkpoint會增加存儲開銷、增加批次處理時間。當批次間隔較小(如1秒)時,checkpoint可能會減小operation吞吐量;反之,checkpoint時間間隔較大會導致lineage和task數量增長。

     

    性能調優

     

    降低批次處理時間

     

    • 數據接收并行度

    • 增加DStream:接收網絡數據(如Kafka、Flume、Socket等)時會對數據反序列化再存儲在Spark,由於一個DStream只有Receiver對象,如果成為瓶頸可考慮增加DStream。

    • 設置“spark.streaming.blockInterval”參數:接收的數據被存儲在Spark內存前,會被合併成block,而block數量決定了Task數量;舉例,當批次時間間隔為2秒且block時間間隔為200毫秒時,Task數量約為10;如果Task數量過低,則浪費了CPU資源;推薦的最小block時間間隔為50毫秒。

    • 顯式對Input DStream重新分區:在進行更深層次處理前,先對輸入數據重新分區。

    inputStream.repartition(<number of partitions>)

     

    • 數據處理并行度:reduceByKey、reduceByKeyAndWindow等operation可通過設置“spark.default.parallelism”參數或顯式設置并行度方法參數控制。

    • 數據序列化:可配置更高效的Kryo序列化。

     

    設置合理批次時間間隔

     

    • 原則:處理數據的速度應大於或等於數據輸入的速度,即批次處理時間大於或等於批次時間間隔。

    • 方法:

    • 先設置批次時間間隔為5-10秒以降低數據輸入速度;

    • 再通過查看log4j日誌中的“Total delay”,逐步調整批次時間間隔,保證“Total delay”小於批次時間間隔。

     

    內存調優

     

    • 持久化級別:開啟壓縮,設置參數“spark.rdd.compress”。

    • GC策略:在Driver和Executor上開啟CMS。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

  • 深入理解React:事件機制原理

    深入理解React:事件機制原理

    目錄

    • 序言
    • DOM事件流
      • 事件捕獲階段、處於目標階段、事件冒泡階段
      • addEventListener 方法
    • React 事件概述
    • 事件註冊
      • document 上註冊
      • 回調函數存儲
    • 事件分發
    • 小結
    • 參考

    1.序言

    React 有一套自己的事件系統,其事件叫做合成事件。為什麼 React 要自定義一套事件系統?React 事件是如何註冊和觸發的?React 事件與原生 DOM 事件有什麼區別?帶着這些問題,讓我們一起來探究 React 事件機制的原理。為了便於理解,此篇分析將盡可能用圖解代替貼 React 源代碼進行解析。

    2.DOM事件流

    首先,在正式講解 React 事件之前,有必要了解一下 DOM 事件流,其包含三個流程:事件捕獲階段、處於目標階段和事件冒泡階段。

    W3C協會早在1988年就開始了DOM標準的制定,W3C DOM標準可以分為 DOM1、DOM2、DOM3 三個版本。

    從 DOM2 開始,DOM 的事件傳播分三個階段進行:事件捕獲階段、處於目標階段和事件冒泡階段。

    (1)事件捕獲階段、處於目標階段和事件冒泡階段

    示例代碼:

    <html>
        <body>
            <div id="outer">
    	    <p id="inner">Click me!</p>
    	</div>
        </body>
    </html>
    

    上述代碼,如果點擊 <p>元素,那麼 DOM 事件流如下圖:

    (1)事件捕獲階段:事件對象通過目標節點的祖先 Window 傳播到目標的父節點。

    (2)處於目標階段:事件對象到達事件目標節點。如果阻止事件冒泡,那麼該事件對象將在此階段完成后停止傳播。

    (3)事件冒泡階段:事件對象以相反的順序從目標節點的父項開始傳播,從目標節點的父項開始到 Window 結束。

    (2)addEventListener 方法

    DOM 的事件流中同時包含了事件捕獲階段和事件冒泡階段,而作為開發者,我們可以選擇事件處理函數在哪一個階段被調用。

    addEventListener() 方法用於為特定元素綁定一個事件處理函數。addEventListener 有三個參數:

    element.addEventListener(event, function, useCapture)
    

    另外,如果一個元素(element)針對同一個事件類型(event),多次綁定同一個事件處理函數(function),那麼重複的實例會被拋棄。當然如果第三個參數capture值不一致,此時就算重複定義,也不會被拋棄掉。

    3.React 事件概述

    React 根據W3C 規範來定義自己的事件系統,其事件被稱之為合成事件 (SyntheticEvent)。而其自定義事件系統的動機主要包含以下幾個方面:

    (1)抹平不同瀏覽器之間的兼容性差異。最主要的動機。

    (2)事件”合成”,即事件自定義。事件合成既可以處理兼容性問題,也可以用來自定義事件(例如 React 的 onChange 事件)。

    (3)提供一個抽象跨平台事件機制。類似 VirtualDOM 抽象了跨平台的渲染方式,合成事件(SyntheticEvent)提供一個抽象的跨平台事件機制。

    (4)可以做更多優化。例如利用事件委託機制,幾乎所有事件的觸發都代理到了 document,而不是 DOM 節點本身,簡化了 DOM 事件處理邏輯,減少了內存開銷。(React 自身模擬了一套事件冒泡的機制)

    (5)可以干預事件的分發。V16引入 Fiber 架構,React 可以通過干預事件的分發以優化用戶的交互體驗。

    注:「幾乎」所有事件都代理到了 document,說明有例外,比如audiovideo標籤的一些媒體事件(如 onplay、onpause 等),是 document 所不具有,這些事件只能夠在這些標籤上進行事件進行代理,但依舊用統一的入口分發函數(dispatchEvent)進行綁定。

    4.事件註冊

    React 的事件註冊過程主要做了兩件事:document 上註冊、存儲事件回調。

    (1)document 上註冊

    在 React 組件掛載階段,根據組件內的聲明的事件類型(onclick、onchange 等),在 document 上註冊事件(使用addEventListener),並指定統一的回調函數 dispatchEvent。換句話說,document 上不管註冊的是什麼事件,都具有統一的回調函數 dispatchEvent。也正是因為這一事件委託機制,具有同樣的回調函數 dispatchEvent,所以對於同一種事件類型,不論在 document 上註冊了幾次,最終也只會保留一個有效實例,這能減少內存開銷。

    示例代碼:

    function TestComponent() {
      handleFatherClick=()=>{
    		// ...
      }
    
      handleChildClick=()=>{
    		// ...
      }
    
      return <div className="father" onClick={this.handleFatherClick}>
    	<div className="child" onClick={this.handleChildClick}>child </div>
      </div>
    }
    

    上述代碼中,事件類型都是onclick,由於 React 的事件委託機制,會指定統一的回調函數 dispatchEvent,所以最終只會在 document 上保留一個 click 事件,類似document.addEventListener('click', dispatchEvent),從這裏也可以看出 React 的事件是在 DOM 事件流的冒泡階段被觸發執行。

    (2)存儲事件回調

    React 為了在觸發事件時可以查找到對應的回調去執行,會把組件內的所有事件統一地存放到一個對象中(listenerBank)。而存儲方式如上圖,首先會根據事件類型分類存儲,例如 click 事件相關的統一存儲在一個對象中,回調函數的存儲採用鍵值對(key/value)的方式存儲在對象中,key 是組件的唯一標識 id,value 對應的就是事件的回調函數。

    React 的事件註冊的關鍵步驟如下圖:

    5.事件分發

    事件分發也就是事件觸發。React 的事件觸發只會發生在 DOM 事件流的冒泡階段,因為在 document 上註冊時就默認是在冒泡階段被觸發執行。

    其大致流程如下:

    1. 觸發事件,開始 DOM 事件流,先後經過三個階段:事件捕獲階段、處於目標階段和事件冒泡階段
    2. 當事件冒泡到 document 時,觸發統一的事件分發函數 ReactEventListener.dispatchEvent
    3. 根據原生事件對象(nativeEvent)找到當前節點(即事件觸發節點)對應的 ReactDOMComponent 對象
    4. 事件的合成
      1. 根據當前事件類型生成對應的合成對象
      2. 封裝原生事件對象和冒泡機制
      3. 查找當前元素以及它所有父級
      4. 在 listenerBank 中查找事件回調函數併合成到 events 中
    5. 批量執行合成事件(events)內的回調函數
    6. 如果沒有阻止冒泡,會將繼續進行 DOM 事件流的冒泡(從 document 到 window),否則結束事件觸發

    注:上圖中阻止冒泡是指調用stopImmediatePropagation 方法阻止冒泡,如果是調用stopPropagation阻止冒泡,document 上如果還註冊了同類型其他的事件,也將會被觸發執行,但會正常阻斷 window 上事件觸發。了解兩者之間的詳細區別

    示例代碼:

    class TestComponent extends React.Component {
    
      componentDidMount() {
        this.parent.addEventListener('click', (e) => {
          console.log('dom parent');
        })
        this.child.addEventListener('click', (e) => {
          console.log('dom child');
        })
        document.addEventListener('click', (e) => {
          console.log('document');
        })
        document.body.addEventListener('click', (e) => {
          console.log('body');
        })
        window.addEventListener('click', (e) => {
          console.log('window');
        })
      }
    
      childClick = (e) => {
        console.log('react child');
      }
    
      parentClick = (e) => {
        console.log('react parent');
      }
    
      render() {
        return (
          <div class='parent' onClick={this.parentClick} ref={ref => this.parent = ref}>
            <div class='child' onClick={this.childClick} ref={ref => this.child = ref}>
              Click me!
            </div>
          </div>)
      }
    }
    

    點擊 child div 時,其輸出如下:

    在 DOM 事件流的冒泡階段先後經歷的元素:child <div> -> parent <div> -> <body> -> <html> -> document -> window,因此上面的輸出符合預期。

    6.小結

    React 合成事件和原生 DOM 事件的主要區別:

    (1)React 組件上聲明的事件沒有綁定在 React 組件對應的原生 DOM 節點上。

    (2)React 利用事件委託機制,將幾乎所有事件的觸發代理(delegate)在 document 節點上,事件對象(event)是合成對象(SyntheticEvent),不是原生事件對象,但通過 nativeEvent 屬性訪問原生事件對象。

    (3)由於 React 的事件委託機制,React 組件對應的原生 DOM 節點上的事件觸發時機總是在 React 組件上的事件之前。

    7.參考

    javascript中DOM0,DOM2,DOM3級事件模型解析

    Event dispatch and DOM event flow

    EventTarget.addEventListener() – Web API 接口參考| MDN

    合成事件

    談談React事件機制和未來(react-events)

    React源碼解讀系列 – 事件機制

    一文吃透 react 事件機制原理

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

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

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • Python異常處理

    Python異常處理

    Python中的異常處理

    異常分類

      程序中難免出現錯誤,總共可分為兩種。

     

      1.邏輯錯誤

      2.語法錯誤

     

      對於剛接觸編程的人來說,這兩個錯誤都會經常去犯,但是隨着經驗慢慢的積累,語法錯誤的情況會越來越少反而邏輯錯誤的情況會越來越多(因為工程量巨大)。不論多麼老道的程序員都不可避免出現這兩種錯誤。

     

    異常的三大信息

      異常其實就是程序運行時發生錯誤的信號,我們寫代碼的過程中不可避免也最害怕的就是出現異常,然而當程序拋出異常時實際上會分為三部分,即三大信息。

     

     

    常用的異常類

      在Python中一切皆對象,異常本身也是由一個類生成的,NameError其實本身就是一個異常類,其他諸如此類的異常類還有很多。

    Python中常見的異常類 
    AttributeError 試圖訪問一個對象沒有的屬性,比如foo.x,但是foo並沒有屬性x
    IOError 輸入/輸出異常;基本上是無法打開文件
    ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤
    IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊
    IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5]
    KeyError 試圖訪問字典里不存在的鍵
    KeyboardInterrupt Ctrl+C被按下
    NameError 試圖使用一個還未被賦予對象的變量
    SyntaxError Python代碼非法,代碼不能編譯(其實就是語法錯誤,寫錯了)
    TypeError 傳入對象類型與要求的不符合
    UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是由於另有一個同名的 全局變量,導致你以為正在訪問它
    ValueError 傳入一個調用者不期望的值,即使值的類型是正確的

     

    異常處理

      我們可以來用某些方法進行異常捕捉,當出現異常時我們希望代碼以另一種邏輯運行,使得我們的程序更加健壯,這個就叫做異常處理。異常處理是非常重要的,本身也並不複雜,千萬不可馬虎大意。

      但是切記不可濫用異常處理,這會使得你的代碼可讀性變差。

     

    if else處理異常

      ifelse本身就具有處理異常的功能,他們更多的是在我們能預測到可能出現的範圍內進行規避異常,對於我們不能預測的異常來說就顯得不是那麼的好用。如下:

    # ==== if else 處理異常 ====
    
    while 1:
        select = input("請輸入数字0進行關機:").strip()
        if select.isdigit():  # 我們可以防止用戶輸入非数字的字符
            if select == "0":
                print("正在關機...")
                break
        print("輸入有誤,請重新輸入")
    
    # ==== 執行結果 ====
    
    """
    請輸入数字0進行關機:關機
    輸入有誤,請重新輸入
    請輸入数字0進行關機:關機啊
    輸入有誤,請重新輸入
    請輸入数字0進行關機:0
    正在關機...
    """

      這種異常處理機制雖然非常簡單,但是並不靈活,我們可以使用更加簡單的方式來處理他們。

     

    try except處理異常

     

      try:代表要檢測可能出現異常的代碼塊

      except:當異常出現后的處理情況

     

      執行流程:

        try中檢測的代碼塊 —> 如果有異常 —> 執行except代碼塊 —> 執行正常邏輯代碼 —> 程序結束

        try中檢測的代碼塊 —> 如果沒有異常 —> 執行完畢try中的代碼塊 —> 執行正常邏輯代碼 —> 程序結束

     

    # ====  try except 執行流程 有異常的情況 ====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[10])  # 出錯點...
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    有異常執行我except...
    正常邏輯代碼...
    """ 
    # ====  try except 執行流程 無異常的情況 ====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[2])
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    3
    繼續執行我try...
    正常邏輯代碼...
    """ 
    # ==== try except 處理異常 ====
    
    while 1:
        try:  # try檢測可能出錯的語句,一旦出錯立馬跳轉到except語句塊執行代碼。
            select = int(input("請輸入数字0進行關機:").strip())
            if select == 0:
                print("正在關機...")
                break
            print("輸入有誤,請重新輸入...")
        except ValueError as e:  # 當執行完except的代碼塊后,程序運行結束,其中e代表的是異常信息。
            print("錯誤信息是:",e)
            print("輸入有誤,請重新輸入")
    
    # ==== 執行結果 ====
    
    """
    請輸入数字0進行關機:1
    輸入有誤,請重新輸入...
    請輸入数字0進行關機:tt
    錯誤信息是: invalid literal for int() with base 10: 'tt'
    輸入有誤,請重新輸入
    請輸入数字0進行關機:0
    正在關機...
    """

     

    多段except捕捉多異常

      我們可以使用try和多段except的語法來檢測某一代碼塊,可以更加方便的應對更多類型的錯誤,Ps不常用:

    # ==== 多段 except 捕捉多異常 ====
    
    while 1:
    
        li = [1,2,3,4]
        dic = {"name":"Yunya","age":18}
    
        li_index = input("請輸入索引:")
        dic_key = input("請輸入鍵的名稱:")
    
        if li_index.isdigit():
            li_index = int(li_index)
    
        try:
            print(li[li_index])
            print(dic[dic_key])
        except IndexError as e1:  # 注意,先拋出的錯誤會直接跳到其處理的except代碼塊,而try下面的語句將不會被執行。
            print("索引出錯啦!")
        except KeyError as e2:
            print("鍵出錯啦!")
    
    # ==== 執行結果 ====
    
    """
    請輸入索引:10
    請輸入鍵的名稱:gender
    索引出錯啦!
    請輸入索引:2
    請輸入鍵的名稱:gender
    3
    鍵出錯啦!
    """

     

    元組捕捉多異常

      使用多段except捕捉多異常會顯得特別麻煩,這個時候我們可以使用(異常類1,異常類2)來捕捉多異常,但是需要注意的是,對比多段except捕捉多異常來說,這種方式的處理邏輯會顯得較為複雜(因為只有一段處理邏輯),如下:

    # ====  元組捕捉多異常 ====
    
    while 1:
    
        li = [1,2,3,4]
        dic = {"name":"Yunya","age":18}
    
        li_index = input("請輸入索引:")
        dic_key = input("請輸入鍵的名稱:")
    
        if li_index.isdigit():
            li_index = int(li_index)
    
        try:
            print(li[li_index])
            print(dic[dic_key])
        except (IndexError,KeyError) as e:  # 使用()的方式可以同時捕捉很多異常。
            print("出錯啦!")
    
    # ==== 執行結果 ====
    
    """
    請輸入索引:10
    請輸入鍵的名稱:gender
    出錯啦!
    請輸入索引:2
    請輸入鍵的名稱:gender
    3
    出錯啦!
    """

      可以看到,不管是那種錯誤都只有一種應對策略,如果我們想要多種應對策略就只能寫if判斷來判斷異常類型再做處理。所以就會顯得很麻煩,如下:

    # ====  元組捕捉多異常 ====
    
    while 1:
    
        li = [1,2,3,4]
        dic = {"name":"Yunya","age":18}
    
        li_index = input("請輸入索引:")
        dic_key = input("請輸入鍵的名稱:")
    
        if li_index.isdigit():
            li_index = int(li_index)
    
        try:
            print(li[li_index])
            print(dic[dic_key])
        except (IndexError,KeyError) as e:
            # 判斷異常類型再做出相應的對應策略
            if isinstance(e,IndexError):
                print("索引出錯啦!")
            elif isinstance(e,KeyError):
                print("鍵出錯啦!")
    
    # ==== 執行結果 ====
    
    """
    請輸入索引:10
    請輸入鍵的名稱:gender
    索引出錯啦!
    請輸入索引:2
    請輸入鍵的名稱:gender
    3
    鍵出錯啦!
    """

     

    萬能異常Exception

      我們可以捕捉Exception類引發的異常,它是所有異常類的基類。(Exception類的父類則是BaseException類,而BaseException的父類則是object類)

    # ====  萬能異常Exception ====
    
    while 1:
    
        li = [1,2,3,4]
        dic = {"name":"Yunya","age":18}
    
        li_index = input("請輸入索引:")
        dic_key = input("請輸入鍵的名稱:")
    
        if li_index.isdigit():
            li_index = int(li_index)
    
        try:
            print(li[li_index])
            print(dic[dic_key])
        except Exception as e:  #使用 Exception來捕捉所有異常。
            # 判斷異常類型再做出相應的對應策略
            if isinstance(e,IndexError):
                print("索引出錯啦!")
            elif isinstance(e,KeyError):
                print("鍵出錯啦!")
    
    # ==== 執行結果 ====
    
    """
    請輸入索引:10
    請輸入鍵的名稱:gender
    索引出錯啦!
    請輸入索引:2
    請輸入鍵的名稱:gender
    3
    鍵出錯啦!
    """

     

     

    try except else聯用

      這種玩法比較少,else代表沒有異常發生的情況下執行的代碼,執行順序如下:

     

      try中檢測的代碼塊 —> 如果有異常 —> 終止try中的代碼塊繼續執行 —> 執行except代碼塊 —> 執行正常邏輯代碼 —> 程序結束

      try中檢測的代碼塊 —> 如果沒有異常 —> 執行完畢try中的代碼塊 —> 執行else代碼塊 —> 執行正常邏輯代碼 —> 程序結束

     

    # ====  try except else聯用 有異常的情況====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[10])  # 出錯點...
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    else:
        print("沒有異常執行我else...")
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    有異常執行我except...
    正常邏輯代碼...
    """
    # ====  try except else聯用 無異常的情況====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[2])
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    else:
        print("沒有異常執行我else...")
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    3
    繼續執行我try...
    沒有異常執行我else...
    正常邏輯代碼...
    """

     

    try except finally聯用

      finally代表不論拋異常與否都會執行,因此常被用作關閉系統資源的操作,關於try,except,else,finally他們的優先級如下:

     

      有異常的情況下:

        try代碼塊

        終止try代碼塊繼續執行

        except代碼塊

        finally代碼塊

        正常邏輯代碼

     

      無異常的情況下:

        try代碼塊

        else代碼塊

        finally代碼塊

        正常邏輯代碼

     

    # ====  try except else finally 執行流程 有異常的情況 ====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[10])  # 出錯點...
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    else:
        print("沒有異常執行我else...")
    finally:
        print("不管有沒有異常都執行我finally...")
    
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    有異常執行我except...
    不管有沒有異常都執行我finally...
    正常邏輯代碼...
    """
    # ====  try except else finally 執行流程 無異常的情況 ====
    
    li = [1,2,3]
    
    try:
        print("開始執行我try了...")
        print(li[2])
        print("繼續執行我try...")
    except IndexError as e:
        print("有異常執行我except...")
    else:
        print("沒有異常執行我else...")
    finally:
        print("不管有沒有異常都執行我finally...")
    
    print("正常邏輯代碼...")
    
    # ==== 執行結果 ====
    
    """
    開始執行我try了...
    3
    繼續執行我try...
    沒有異常執行我else...
    不管有沒有異常都執行我finally...
    正常邏輯代碼...
    """

     

    自定義異常

    raise主動拋出異常

      在某些時候我們可能需要主動的去阻止程序的運行,主動的拋出一個異常。可以使用raise來進行操作。這個是一種非常常用的手段。

    # ====  raise使用方法  ====
    
    print("----1----")
    print("----2----")
    print("----3----")
    raise Exception("我也不知道是什麼類型的異常...")
    print("----4----")
    print("----5----")
    print("----6----")
    
    # ==== 執行結果 ====
    
    """
    ----1----
    ----2----
    ----3----
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 6, in <module>
        raise Exception("我也不知道是什麼類型的異常...")
    Exception: 我也不知道是什麼類型的異常...
    
    Process finished with exit code 1
    """

     

    自定義異常類

      前面已經說過一切皆對象,異常也來自一個對象。因此我們也可以自己來定製一個對象。注意,自定義異常類必須繼承BaseException類。

    # ====  自定義異常類  ====
    
    class MyError(BaseException):
        pass
    
    raise MyError("我的異常")
    
    
    
    # ==== 執行結果 ====
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 6, in <module>
        raise MyError("我的異常")
    __main__.MyError: 我的異常
    """

     

    擴展:斷言assert

      斷言是一個十分裝逼的使用,假設多個函數進行計算,我們已經有了預期的結果只是在做一個算法的設計。如果函數的最後的結果不是我們本來預期的結果那麼寧願讓他停止運行也不要讓錯誤繼續擴大,在這種情況下就可以使用斷言操作,使用斷言會拋出一個AssertionError類的異常。

    # ====  斷言assert  ====
    
    def calculate():
        """假設在做非常複雜的運算"""
        return 3+2*5
    
    res = calculate()
    
    assert res == 25  # AssertionError
    
    print("算法測試通過!你真的太厲害了")
    
    # ==== 執行結果 ====
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/元類編程.py", line 8, in <module>
        assert res == 25
    AssertionError
    """

     

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

  • 雷諾與法國富豪攜手 搶進電動車市場

    法國雷諾集團(Renault SA)與法國富豪波洛黑(Vincent Bollore)決定合作,共同製造電動車,瞄準零排放汽車日益強勁的需求,及正於各地興起的環保汽車租用潮流。   雷諾汽車將於 2015 下半年開始生產波洛黑的電動車款「藍車(Bluecar)」,但並未透露生產目標。該車款自 2011 年起,被使用於巴黎名為「Autolib」的電動車租賃共享計畫中,此計畫同時也於里昂及波爾多等地運行。   此外,雷諾汽車與波洛黑亦宣布,將成立一策略聯盟,其中波洛黑擁有 70% 股權,雷諾股權則達 30%。該聯盟將於法國及歐洲其他地方提供車輛共享服務。另外並進行研究,以協助雷諾製造一款 3 人座、使用波洛黑所生產電池的電動車。

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

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

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 使用Kubernetes、K3s和Traefik2進行本地開發

    使用Kubernetes、K3s和Traefik2進行本地開發

    作者簡介

    Vyacheslav,擁有運維和項目管理經驗的軟件工程師

    這篇文章將承接我此前搭建的本地Docker開發環境,具體步驟已經放在在以下網址:

    https://github.com/Voronenko/traefik2-compose-template

    除了經典的docker化的項目之外,我還有其他的Kubernetes項目。儘管Kubernetes已經成為容器編排的事實標準,但是不得不承認Kubernetes是一個既消耗資源又消耗金錢的平台。由於我並不經常需要外部集群,因此我使用輕量級K3s發行版來進行Kubernetes本地開發。

    K3s是為IoT和邊緣計算而構建的經過認證的Kubernetes發行版之一,還能夠按產品規模部署到VM。

    我使用K3s的方式是這樣的:在我的工作筆記本上本地安裝K3s,儘管有時我需要在本地部署較重的測試工作負載,為此,我準備了兩個神器——兩個運行ESXi的外部Intel NUCs。

    默認情況下,K3s安裝Traefik 1.x作為ingress,如果你對此十分滿意,那麼無需往下繼續閱讀了。

    在我的場景中,我同時會牽涉到好幾個項目,特別是經典的docker和docker swarm,因此我經常遇到在獨立模式下部署Traefik的情況。

    因此,本文其餘部分將深入介紹如何將外部traefik2配置為K3s集群的ingress。

    安裝Kubernetes K3s系列集群

    你可以按照常規方式使用命令curl -sfL https://get.k3s.io | sh -安裝K3s,或者你可以使用輕量實用程序k3sup安裝(https://github.com/alexellis/k3sup)。具體步驟在之前的文章介紹過。

    與我們的設置不同的是,我們使用命令--no-deploy traefik專門安裝了不帶traefik組件的K3s。

    export CLUSTER_MASTER=192.168.3.100
    export CLUSTER_DEPLOY_USER=slavko
    k3sup install --ip $CLUSTER_MASTER --user $CLUSTER_DEPLOY_USER --k3s-extra-args '--no-deploy traefik'
    

    執行后,你將獲得使用kubectl所需的連接詳細信息。安裝K3s后,你可以快速檢查是否可以看到節點。

    # Test your cluster with - export path to k3s cluster kubeconfig:
    export KUBECONFIG=/home/slavko/kubeconfig
    kubectl get node -o wide
    

    注:這裏沒有固定的安裝模式,你甚至可以使用docker-compose自行啟動它。

    server:
      image: rancher/k3s:v0.8.0
      command: server --disable-agent --no-deploy traefik
      environment:
        - K3S_CLUSTER_SECRET=somethingtotallyrandom
        - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
        - K3S_KUBECONFIG_MODE=666
      volumes:
        # k3s will generate a kubeconfig.yaml in this directory. This volume is mounted
        # on your host, so you can then 'export KUBECONFIG=/somewhere/on/your/host/out/kubeconfig.yaml',
        # in order for your kubectl commands to work.
        - /somewhere/on/your/host/out:/output
        # This directory is where you put all the (yaml) configuration files of
        # the Kubernetes resources.
        - /somewhere/on/your/host/in:/var/lib/rancher/k3s/server/manifests
      ports:
        - 6443:6443
    
    node:
      image: rancher/k3s:v0.8.0
      privileged: true
      links:
        - server
      environment:
        - K3S_URL=https://server:6443
        - K3S_CLUSTER_SECRET=somethingtotallyrandom
      volumes:
        # this is where you would place a alternative traefik image (saved as a .tar file with
        # 'docker save'), if you want to use it, instead of the traefik:v2.0 image.
        - /sowewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images
    

    配置Traefik 2,與Kubernetes一起使用

    在文章開頭提到的鏈接中,我已經在我的系統中安裝了Traefik 2,並根據該鏈接內容,服務於一些需求。現在是時候配置Traefik 2 Kubernetes後端了。

    Traefik 2使用CRD(自定義資源定義)來完成這一點。定義的最新示例可以在以下鏈接中找到,但這些示例僅適用於Traefik 2也作為Kubernetes工作負載的一部分執行的情況:

    https://docs.traefik.io/reference/dynamic-configuration/kubernetes-crd/

    對於外部Traefik 2,我們僅需要以下描述的定義子集。

    我們引入一系列自定義資源定義,以允許我們來描述我們的Kubernetes服務將會如何暴露到外部,traefik-crd.yaml

    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: ingressroutes.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: IngressRoute
        plural: ingressroutes
        singular: ingressroute
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: ingressroutetcps.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: IngressRouteTCP
        plural: ingressroutetcps
        singular: ingressroutetcp
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: middlewares.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: Middleware
        plural: middlewares
        singular: middleware
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: tlsoptions.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: TLSOption
        plural: tlsoptions
        singular: tlsoption
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: traefikservices.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: TraefikService
        plural: traefikservices
        singular: traefikservice
      scope: Namespaced  
    

    同時,我們需要集群角色traefik-ingress-controller,以提供對服務、端點和secret的只讀訪問權限以及自定義的traefik.containo.us組,traefik-clusterrole.yaml

    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: traefik-ingress-controller
    
    rules:
      - apiGroups:
          - ""
        resources:
          - services
          - endpoints
          - secrets
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - extensions
        resources:
          - ingresses
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - extensions
        resources:
          - ingresses/status
        verbs:
          - update
      - apiGroups:
          - traefik.containo.us
        resources:
          - middlewares
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - ingressroutes
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - ingressroutetcps
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - tlsoptions
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - traefikservices
        verbs:
          - get
          - list
          - watch
    

    最後,我們需要系統服務賬號traefik-ingress-controller與之前創建的集群角色traefik-ingress-controller相關聯。

    ---
    kind: ServiceAccount
    apiVersion: v1
    metadata:
      namespace: kube-system
      name: traefik-ingress-controller
    
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: traefik-ingress-controller
    
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: traefik-ingress-controller
    subjects:
      - kind: ServiceAccount
        name: traefik-ingress-controller
        namespace: kube-system
    

    我們應用以上資源之後:

    apply:
      kubectl apply -f traefik-crd.yaml
      kubectl apply -f traefik-clusterrole.yaml
      kubectl apply -f traefik-service-account.yaml
    

    我們已經準備好開始調整Traefik 2

    將Traefik 2指向K3s集群

    根據Traefik文檔的建議,當Traefik部署到Kubernetes中時,它將讀取環境變量KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT或KUBECONFIG來構造端點。

    /var/run/secrets/kubernetes.io/serviceaccount/token中查找訪問token,而SSL CA證書將在/var/run/secrets/kubernetes.io/serviceaccount/ca.crt.中查找。當部署到Kubernetes內部時,兩者都會自動提供掛載。

    當無法找到環境變量時,Traefik會嘗試使用external-cluster客戶端連接到Kubernetes API server。這一情況下,需要設置endpoint。具體來說,可以將其設置為kubectl代理使用的URL,以使用相關的kubeconfig授予的身份驗證和授權連接到Kubernetes集群。

    Traefik 2可以使用任何受支持的配置類型來靜態配置-toml、yaml或命令行交換。

    [providers.kubernetesCRD]
      endpoint = "http://localhost:8080"
      token = "mytoken"
    
    providers:
      kubernetesCRD:
        endpoint = "http://localhost:8080"
        token = "mytoken"
        # ...
    
    --providers.kubernetescrd.endpoint=http://localhost:8080 
    --providers.kubernetescrd.token=mytoken
    

    第一次運行時,如果你在外部有Traefik,很有可能沒有traefik-ingress-controller訪問token來指定mytoken。那麼,你需要執行以下命令:

    # Check all possible clusters, as your .KUBECONFIG may have multiple contexts:
    kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
    
    # Output kind of
    # Alias tip: k config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
    # Cluster name  Server
    # default  https://127.0.0.1:6443
    
    # You are interested in: "default", if you did not name it differently
    
    # Select name of cluster you want to interact with from above output:
    export CLUSTER_NAME="default"
    
    # Point to the API server referring the cluster name
    export APISERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}")
    # usually https://127.0.0.1:6443
    
    # Gets the token value
    export TOKEN=$(kubectl get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='traefik-ingress-controller')].data.token}" --namespace kube-system|base64 --decode)
    
    # Explore the API with TOKEN
    

    如果成功了,你應該收到以下響應:

    {
      "kind": "APIVersions",
      "versions": [
        "v1"
      ],
      "serverAddressByClientCIDRs": [
        {
          "clientCIDR": "0.0.0.0/0",
          "serverAddress": "192.168.3.100:6443"
        }
      ]
    

    以及一些事實,如token:

    eyJhbGciOiJSUzI1NiIsImtpZCI6IjBUeTQyNm5nakVWbW5PaTRRbDhucGlPeWhlTHhxTXZjUDJsRmNacURjVnMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlci10b2tlbi12emM3diIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ5NTc3ZTkxLTdlNjQtNGMwNi1iZDgyLWNkZTk0OWM4MTI1MSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTp0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlciJ9.Mk8EBS4soO8uX-uSnV3o4qZKR6Iw6bgeSmPhHbJ2fjuqFgLnLh4ggxa-N9AqmCsEWiYjSi5oKAu986UEC-_kGQh3xaCYsUwlkM8147fsnwCbomSeGIct14JztVL9F8JwoDH6T0BOEjn-J9uY8-fUKYL_Y7uTrilhFapuILPsj_bFfgIeOOapRD0XshKBQV9Qzg8URxyQyfzl68ilm1Q13h3jLj8CFE2RlgEUFk8TqYH4T4fhfpvV-gNdmKJGODsJDI1hOuWUtBaH_ce9w6woC9K88O3FLKVi7fbvlDFrFoJ2iVZbrRALPjoFN92VA7a6R3pXUbKebTI3aUJiXyfXRQ
    

    根據上次響應的API server的外部地址:https://192.168.3.100:6443

    同樣,提供的token中沒有任何特殊之處:這是JWT的token,你可以使用https://jwt.io/#debugger-io,檢查它的內容。

    {
      "alg": "RS256",
      "kid": "0Ty426ngjEVmnOi4Ql8npiOyheLxqMvcP2lFcZqDcVs"
    }
    {
      "iss": "kubernetes/serviceaccount",
      "kubernetes.io/serviceaccount/namespace": "kube-system",
      "kubernetes.io/serviceaccount/secret.name": "traefik-ingress-controller-token-vzc7v",
      "kubernetes.io/serviceaccount/service-account.name": "traefik-ingress-controller",
      "kubernetes.io/serviceaccount/service-account.uid": "d9577e91-7e64-4c06-bd82-cde949c81251",
      "sub": "system:serviceaccount:kube-system:traefik-ingress-controller"
    }
    

    正確的配置非常重要,因此請確保對APISERVER的兩個調用均返回合理的響應。

    export APISERVER=YOURAPISERVER
    export TOKEN=YOURTOKEN
    
    curl -X GET $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure
    
    curl -X GET $APISERVER/api/v1/endpoints --header "Authorization: Bearer $TOKEN" --insecure
    

    創建其他訪問token

    控制器循環確保每個服務賬戶都有一個帶有API token的secret,可以像我們之前那樣被發現。

    此外,你還可以為一個服務賬戶創建額外的token,創建一個ServiceAccountToken類型的secret,併為服務賬戶添加一個註釋,控制器會用生成的token來更新它。

    ---
    apiVersion: v1
    kind: Secret
    namespace: kube-system
    metadata:
      name: traefik-manual-token
      annotations:
        kubernetes.io/service-account.name: traefik-ingress-controller
    type: kubernetes.io/service-account-token
    
    # Any tokens for non-existent service accounts will be cleaned up by the token controller.
    
    # kubectl describe secrets/traefik-manual-token
    

    用以下命令創建:

    kubectl create -f ./traefik-service-account-secret.yaml
    kubectl describe secret traefik-manual-token
    

    刪除/無效:

    kubectl delete secret traefik-manual-token
    

    對外部traefik 2的更改構成定義

    我們需要在文章開頭給出的鏈接中獲得的traefik2配置進行哪些更改?

    https://github.com/Voronenko/traefik2-compose-template

    a) 我們在新文件夾kubernetes_data中存儲ca.crt文件,該文件用於驗證對Kubernetes授權的調用。這是可以在kubeconfig文件的clusters-> cluster-> certificate-authority-data下找到的證書。

    該volume將映射在/var/run/secrets/kubernetes.io/serviceaccount下以獲取官方Traefik 2鏡像

    volumes:
        ...
          - ./kubernetes_data:/var/run/secrets/kubernetes.io/serviceaccount
    

    b) 調整Traefik 2 kubernetescrd後端以提供3個參數:endpoint、證書路徑和token。請注意,作為外部Traefik作為docker容器,你需要指定正確的endpoint地址,並確保以安全的方式進行。

      - "--providers.kubernetescrd=true"
          - "--providers.kubernetescrd.endpoint=https://192.168.3.100:6443"
          - "--providers.kubernetescrd.certauthfilepath=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
          - "--providers.kubernetescrd.token=YOURTOKENWITHOUTANYQUOTES
    

    如果你都執行正確了,那麼你現在應該在Traefik UI上看到了一些希望。如果你沒有看到traefik,或者在運行Traefik時有問題,你可以查看之後的故障排除部分。

    現在是時候通過Trafik 2暴露一些Kubernetes服務了,以確保Traefik 2能夠作為ingress工作。讓我們來看經典案例whoami服務,whoami-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: whoami
    
    spec:
      ports:
        - protocol: TCP
          name: web
          port: 80
      selector:
        app: whoami
    
    ---
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      namespace: default
      name: whoami
      labels:
        app: whoami
    
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: whoami
      template:
        metadata:
          labels:
            app: whoami
        spec:
          containers:
            - name: whoami
              image: containous/whoami
              ports:
                - name: web
                  containerPort: 80
    
    

    並且以http或https的方式暴露它,whoami.k.voronenko.net全限定域名下的whoami-ingress-route.yaml

    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: ingressroute-notls
      namespace: default
    spec:
      entryPoints:
        - web
      routes:
        - match: Host(`whoami.k.voronenko.net`)
          kind: Rule
          services:
            - name: whoami
              port: 80
    
    ---
    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: ingressroute-tls
      namespace: default
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`whoami.k.voronenko.net`)
          kind: Rule
          services:
            - name: whoami
              port: 80
      tls:
        certResolver: default
    

    然後應用它:

    kubectl apply -f whoami-service.yaml
      kubectl apply -f whoami-ingress-route.yaml
    

    應用后,你應該會在Traefik dashboard上看到一些希望,即KubernetesCRD後端。

    正如你所看到的,Traefik已經檢測到我們的K3s Kubernetes集群上運行的新工作負載,而且它與我們在同一個盒子上的經典Docker工作負載(如portainer)很好地共存。

    讓我們檢查一下Traefik 2是否將Traefik路由到了我們的Kubernetes工作負載:如你所見,你可以在http和https endpoint上成功地接觸到whoami工作負載,瀏覽器接受你的證書為可信任的“綠標籤”。

    我們的目標達到了!我們在本地筆記本上配置了Traefik 2。Traefik 2將你的docker或Kubernetes工作流暴露在http或https endpoint上。帶可選的 letsencrypt 的 Traefik 2 將負責 https。

    故障排查

    正如你所知,在配置過程可能存在多個問題,你可以考慮使用一些分析工具,如:

    https://github.com/Voronenko/dotfiles/blob/master/Makefile#L185

    我特別建議:

    a) VMWare octant:這是一個基於Web的功能強大的Kubernetes dashboard,你可以在上面使用你的kubeconfig

    b) Rakess:這是一個獨立工具也是一個kubectl插件,用於显示Kubernetes服務器資源的訪問矩陣(https://github.com/corneliusweig/rakkess)

    檢查系統賬戶的憑據

    rakkess --sa kube-system:traefik-ingress-controller
    

    c) kubectl

    檢查哪些角色與服務賬戶相關聯

    kubectl get clusterrolebindings -o json | jq -r '
      .items[] |
      select(
        .subjects // [] | .[] |
        [.kind,.namespace,.name] == ["ServiceAccount","kube-system","traefik-ingress-controller"]
      ) |
      .metadata.name'
    

    d) Traefik 文檔:例如kubernetescrd後端提供了更多配置開關的方式。

    --providers.kubernetescrd  (Default: "false")
            Enable Kubernetes backend with default settings.
        --providers.kubernetescrd.certauthfilepath  (Default: "")
            Kubernetes certificate authority file path (not needed for in-cluster client).
        --providers.kubernetescrd.disablepasshostheaders  (Default: "false")
            Kubernetes disable PassHost Headers.
        --providers.kubernetescrd.endpoint  (Default: "")
            Kubernetes server endpoint (required for external cluster client).
        --providers.kubernetescrd.ingressclass  (Default: "")
            Value of kubernetes.io/ingress.class annotation to watch for.
        --providers.kubernetescrd.labelselector  (Default: "")
            Kubernetes label selector to use.
        --providers.kubernetescrd.namespaces  (Default: "")
            Kubernetes namespaces.
        --providers.kubernetescrd.throttleduration  (Default: "0")
            Ingress refresh throttle duration
        --providers.kubernetescrd.token  (Default: "")
            Kubernetes bearer token (not needed for in-cluster client).
        --providers.kubernetesingress  (Default: "false")
            Enable Kubernetes backend with default settings.
        --providers.kubernetesingress.certauthfilepath  (Default: "")
            Kubernetes certificate authority file path (not needed for in-cluster client).
        --providers.kubernetesingress.disablepasshostheaders  (Default: "false")
            Kubernetes disable PassHost Headers.
        --providers.kubernetesingress.endpoint  (Default: "")
            Kubernetes server endpoint (required for external cluster client).
        --providers.kubernetesingress.ingressclass  (Default: "")
            Value of kubernetes.io/ingress.class annotation to watch for.
        --providers.kubernetesingress.ingressendpoint.hostname  (Default: "")
            Hostname used for Kubernetes Ingress endpoints.
        --providers.kubernetesingress.ingressendpoint.ip  (Default: "")
            IP used for Kubernetes Ingress endpoints.
        --providers.kubernetesingress.ingressendpoint.publishedservice  (Default: "")
            Published Kubernetes Service to copy status from.
        --providers.kubernetesingress.labelselector  (Default: "")
            Kubernetes Ingress label selector to use.
        --providers.kubernetesingress.namespaces  (Default: "")
            Kubernetes namespaces.
        --providers.kubernetesingress.throttleduration  (Default: "0")
            Ingress refresh throttle duration
        --providers.kubernetesingress.token  (Default: "")
            Kubernetes bearer token (not needed for in-cluster client).
    

    e) 確保Traefik有足夠的權限可以訪問apiserver endpoint

    如果你希望Traefik為你查詢信息:通過在配置中放置一些錯誤的apiserver地址,可以查看訪問的endpoint和查詢順序。有了這些知識和你的Traefik Kubernetes token,你就可以使用Traefik憑證檢查這些endpoint是否可以訪問。

    traefik_1    | E0421 12:30:12.624877       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1.Endpoints: Get https://192.168.3.101:6443/api/v1/endpoints?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625341       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1.Service: Get https://192.168.3.101:6443/api/v1/services?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625395       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1beta1.Ingress: Get https://192.168.3.101:6443/apis/extensions/v1beta1/ingresses?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625449       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.Middleware: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/middlewares?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625492       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.IngressRoute: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/ingressroutes?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625531       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.TraefikService: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/traefikservices?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625572       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.TLSOption: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/tlsoptions?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625610       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.IngressRouteTCP: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/ingressroutetcps?limit=500&resourceVersion=0:
    

    f) 記錄K3s本身

    安裝腳本將自動檢測你的操作系統是使用systemd還是openrc並啟動服務。使用openrc運行時,將在/var/log/k3s.log中創建日誌。使用systemd運行時,將在/var/log/syslog中創建日誌,並使用journalctl -u k3s查看。

    在那裡,你可能會得到一些提示,例如:

    кві 21 15:42:44 u18d k3s[612]: E0421 15:42:44.936960     612 authentication.go:104] Unable to authenticate the request due to an error: invalid bearer token
    

    這將為你提供有關K8s Traefik起初使用時出現問題的線索,Enjoy your journey!

    相關代碼你可以在以下鏈接中找到:

    https://github.com/Voronenko/k3s-mini

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案