最佳實踐之“全身而退”_如何寫文案

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

全身而退,是一種境界,更是一種智慧!編程,特別是框架設計,需要的就是哲學思維。“全身而退”這四個字,還在當年的內存泄露的踩坑過程中就悟出來了,有圖有真相:

    

有人會問,內存泄露和這個“全身而退”好像搭不上邊?從某些角度看也是,要不我不會單獨放在這裏講。但是也有一點關係,內存泄露不就是內存有分配沒有釋放,何時釋放,一般是程序退出之時。當年作為踩坑專業戶,踩過的坑遠遠不止前面有關“踩坑系列”系列文章提到的,可謂五花八門,也許可以套用一句話:“你們看到過的官場小說遠沒有我經歷過的精彩”:)大家可以看前面一系列踩坑經歷的總結。 

當時踩過很多崩潰的坑,時不時的崩潰,我形容我當時是救火隊長。而當時經過不少的踩坑分析有一個共同的現象,竟然都是退出的時候,就莫名其妙崩潰了。然後這個問題其實也容易重現,你不停的啟動和退出就行了,至少百分之五十的概率。只是因為運行的時間不同,退出崩潰的堆棧還不一樣。

正是這樣子,我們總結開玩笑說,就是經不起折騰。也造就了後面,做項目時,寫完一個類,一定會測試退出。整個系統分不同的時間點反覆重啟、反覆插拔網線。基本達到着魔的程度,不是?原來我控制欲這麼強:)事實也證明了,經過這樣反覆重啟的折騰,新產品落地投入市場,基本沒有這些亂七八糟的問題了!踩坑經驗(二)有截圖作證:

     

很久才會出現一個Bug,而且也可以輕鬆搞定。一般都是新加的功能,或者很特別的情況沒考慮到。

所以,也通過真實的項目驗證了,全身而退的重要性。當然現在和當年的心境也完全不一樣了!事實上,後面經過分佈式洗禮,我的這種方法是正確的。分佈式就是解決這種單點故障。當然分佈式單點故障和單機故障,是不一樣的概念或者說屬性。分佈式中是考慮不可抗拒的原因,集群中單節點的穩定性肯定是集群穩定性的基礎。只是這種測試方法可以用來測試分佈式的故障切換,甚至是故障恢復!集群考慮的是集群的全身而退,事實上,我們在測試過程中也出現過集群整體失效的時候。

那麼我們可以從哪些方面去做到全身而退呢?下面我從實踐經驗中總結以下幾點。 

一、單實例的使用

1.方式一

    

說到單實例,估計絕大部分人都說很簡單。如上的例子,一般的人會說沒問題。從邏輯來說確實沒問題,很簡單,不是嗎?實現類更簡單:

    

就一句話,靜態變量再申明一下。你是不是這樣實現的呢? 這是我們重構的新平台上,依然有這樣使用的。以前的平台那就更多了,基本上是全部。導致的結果就是上面描述的崩潰就是家常便飯。當時在部門範圍內做培訓的時候,我把這個作為考題,結果能指出問題點的少之又少。

2.方式二

後面的修改:

    

這是定義,我們再看看具體實現:

     

平時我對代碼是追求簡潔的,但是在這裏我反而增加了函數,是不是看上去複雜了?No!簡潔必須建立在遵循原理的基礎之上。那為什麼要這麼改,遵循的原理是什麼?

最基本的原理就是單實例首先是一個對象,而且是類的對象,不是簡單變量。那麼類就可能涉及深拷貝,涉及到new和delete,比基礎變量要複雜得多。如果像第一種方式,那麼就可能出現編譯期間開始進行內存分配等操作,而退出的時候也在等着編譯器系統的自動回收。在這種啟動和退出之時非常容易操作已經不存在的對象和內存,而出現了崩潰。

其實,第一種方式使用的是靜態變量,可返回的是引用。從內存的布局而言,屬於系統(內核)管理,無法做到應用層的控制,面對複雜情況的內存使用很難做到全身而退。關於程序的內存布局和分配可以查閱虛擬技術和內存泄露兩篇文檔。

所以基於以上兩點,將單實例定義為類的私有變量,指針形式。那麼只有在程序啟動后要獲取的時候才開始構建類對象,完全可以掌控在自己(程序的用戶態)的手中,而且可以非常靈活的加入調試代碼,比如捕獲異常等。在程序退出之時,亦可以很方便很合理的選擇何時釋放和銷毀對象。

當然,一般編碼規範的公司就會直接用第二種方式,其他人使用的時候直接copy改改就行。同樣的如果是“先輩們”使用了第一種,錯誤也會傳承下去。。。而且大多時候錯誤的東西還很難糾正過來,就說第一種方式竟然被發現在代碼重構后!

所以,編程的時候我們需要有懷疑的精神、反思的精神和專業的引導!

二、架構設計

1.模塊與接口

模塊和層級清晰,接口簡單易用。都有利於全身而退。我們新版的存儲系統中,核心業務有三塊,IO+rewrite,而僅僅只有一個寫模塊I和rewrite模塊之間的接口!讀模塊O和其他都是完全分開。

2.類的五大函數

你聽說的都是類的三大函數吧,我這裏咋變成了五大函數,而且我的五大不是包括原來的三大。

2.1 構造函數和析構函數

只是單純的對成員變量進行賦值。

2.2 初始化函數和反初始化函數

例如內存的分配和釋放,多線程的創建啟動和停止。都應該放在這裏面,而不是上面提到的構造和析構中。在內存泄露中也提到過。其實只有一個目的,好控制!

2.3 複位函數

有異常情況下,不是被動退出,而是能夠主動控制,最壞的情況是複位。將各種變量、狀態、內存和多線程恢復到初始狀態。有的人可能會說,那怎麼不直接用初始化函數呢?這兩種功能還是不一樣的。初始化是系統啟動的時候用的。而複位可能承載更多的判斷,例如打印一些重要變量的信息,以利於開發人員跟蹤異常情況。

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

這五大函數是我建立一個類最基本的模板,也是做到全身而退基本的保障。

一個類要盡量做到職責單一,其實一個函數也是類似的。例如說初始化函數不能省,很多人將初始化和構造放一起,或者複位和初始化放一起。

析構函數只是簡單的初始化變量而已,而例如內存的分配、線程和定時器的啟動最好都到初始化函數中;

假如採用單例模式,則只需用到構造函數,像上面提到的單實例模式用的不好,如果構造函數中包括太多內存和多線程的使用,對退出將是一個災難。

而從另一個角度,虛擬內存的原理,內存只有分配和使用的時候才開始產生缺頁中斷即才開始尋找物理內存。所以何時初始化,何時使用內存可以掌握在開發人員的手中,而不是依賴系統來控制。

所以我認為這也是C++相比JAVA來說更好的優勢,可能有的人認為C++就是很難控制,像內存、指針、繼承,而且還沒有GC機制等等。而我認為剛剛相反,例如指針就是一把雙刃劍,你懂它,它就威力無窮!你看很多開源庫,比如經典的MySQL、高效的Redis、一直很火的Nginx,是吧,指針發揮了太大太大的作用,你能說這些庫不穩定嗎?雖然說我也一直關注和學習Java和Spring,主要學習它們的抽象思想!

3.鎖的設計

細心的你可以發現,上面單實例使用的第二種方式,加了一把鎖。我一般的類設計中,鎖的數量是2~3,大多情況下是2。單實例模式使用一把鎖,狀態機用一把鎖。在死鎖的踩坑經驗總結中曾經提到過,鎖的使用個數少,範圍少。所以你可以回頭看看狀態機和單實例模式鎖的使用。該加的時候一定加鎖,該不要加的時候一定不能加,不能模糊。

鎖或者說多線程同步使用不當,是很容易出問題的,不僅是運行過程中可能出現死鎖,退出的時候也可能出現,可能的情況就是退出失敗,同樣需要手動進行殺進程。

4.時序

前面的函數、接口、類、內存、多線程與鎖都設計好了,對於全身而退,或許已經成功了一半。還有一半在哪裡,時序。就是將這些對象和接口如何串起來,如果做到有序。最常見的是退出和啟動的順序即時序是相反的,這是基本的要求,而對於複雜的情況需要分兩方面來討論。

一方面,每個類、每個模塊都要做到全身而退,即每個類和模塊都需要有序的退出。所以上面類的設計,五大函數最終是為了全身而退,為了有序的釋放內存、停止線程等。我們上文提到的有限狀態機最終也是為了更好的控制線程、為了線程的退出,鎖的設計同樣可以減少干擾提高效率。所以系統的全身而退主要依賴於每個類、每個模塊的全身而退。

另一方面,程序最後的退出其實也是要封裝和集中在一兩個函數中,不能太散。即層級關係要非常明確,類似於樹的結構,最後集中到根節點,也就是一個函數。所以層級設計的目的也應該是,越到外層越好控制!

以上提到的幾點,包括時序,包括單實例模式,也包括最近一系列踩坑經歷,我最後都會歸納到框架或設計,所以也可以說這一切都是為了最終的全身而退。

總之,能隨時隨地的全身而退的代碼是一種境界,經得起各種折騰的代碼才是好代碼,經得起各種折騰的設計才是好的設計!

  

最後,萬事萬物的發展都離不開一個簡單的道理,例如讀一本書: 

從簡單,到複雜,最後到簡單。但這兩個簡單的心境完全不一樣,前者我認為更多的是無知者無畏,後者則是一切瞭然於心。 

 

計算機最終歸結於二進制的運算,最終就是01的組合,組合成各種數據結構和協議,組合成各種調度和算法,等等。

而對應於中國智慧,就是道,抓住事物的本質,以不變應萬變,陰陽論,太極八卦論;對應於中國的萬事開頭難,和全身而退!

也可以對應軟件兩大難點,就是原理的把握和框架的設計。

有了對原理的理解,能抓住事物的本質,就會下筆如有神助,也能最終全身而退!

當然你要達到這一點,得不停的”悟”!

悟的速度依賴”實踐+思考”!

 

肯定不能只依賴CRUD,也不能僅僅依賴改Bug的多少、僅僅依賴參与項目的多少。

有的改10個Bug比不上別人一個Bug,有的參与了10個項目不如比別人開發一個項目。

還是那句話,如果不讀書,行萬里路,也只是一個郵差。

所以,我認為多去“實踐+思考”。

 

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

如何在Spring Boot應用啟動之後立刻執行一段邏輯_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

1. 前言

不知道你有沒有接到這種需求,項目啟動后立馬執行一些邏輯。比如簡單的緩存預熱,或者上線后的廣播之類等等。如果你使用 Spring Boot 框架的話就可以藉助其提供的接口CommandLineRunner和 ApplicationRunner來實現。

2. CommandLineRunner

org.springframework.boot.CommandLineRunner 是Spring Boot提供的一個接口,當你實現該接口並將之注入Spring IoC容器后,Spring Boot應用啟動后就會執行其run方法。一個Spring Boot可以存在多個CommandLineRunner的實現,當存在多個時,你可以實現Ordered接口控制這些實現的執行順序(Order 數值越大優先級越低)。接下來我們來聲明兩個實現並指定順序:

優先執行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 優先級最高
 * 該類期望在springboot 啟動后第一順位執行
 * @author felord.cn
 * @since 12:57
 **/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) throws Exception {
        for (String arg : args) {
            log.info("arg = " + arg);
        }
        log.info("i am highOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

第二順序執行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 優先級低於{@code HighOrderCommandLineRunner}
 * @author felord.cn
 * @since 12:59
 **/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {

    @Override
    public void run(String... args) throws Exception {
        log.info("i am lowOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

然後啟動Spring Boot應用后,控制台按照預定的順序打印出了結果:

2020-05-30 23:11:03.685  INFO 11976 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701  INFO 11976 --- [           main] c.f.Application  : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.LowOrderCommandLineRunner   : i am lowOrderRunner

3. ApplicationRunner

在Spring Boot 1.3.0又引入了一個和CommandLineRunner功能一樣的接口ApplicationRunner。CommandLineRunner接收可變參數String... args,而ApplicationRunner 接收一個封裝好的對象參數ApplicationArguments。除此之外它們功能完全一樣,甚至連方法名都一樣。 聲明一個ApplicationRunner並讓它優先級最低:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 優先級最低
 * @author felord.cn
 * @since 13:00
 **/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("i am applicationRunner");
        Set<String> optionNames = args.getOptionNames();
        log.info("optionNames = " + optionNames);
        String[] sourceArgs = args.getSourceArgs();
        log.info("sourceArgs = " + Arrays.toString(sourceArgs));
        List<String> nonOptionArgs = args.getNonOptionArgs();
        log.info("nonOptionArgs = " + nonOptionArgs);
        List<String> optionValues = args.getOptionValues("foo");
        log.info("optionValues = " + optionValues);
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+2;
    }
}

按照順序打印了三個類的執行結果:

2020-06-01 13:02:39.420  INFO 19032 --- [           main] c.f.MybatisResultmapApplication  : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionNames = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionValues = null

Ordered接口並不能被 @Order註解所代替。

4. 傳遞參數

相信很多同學看到這裏都開始對這兩個run方法的入參感興趣了。Spring Boot應用啟動時是可以接受參數的,換句話說也就是Spring Boot的main方法是可以接受參數的。這些參數通過命令行 java -jar yourapp.jar 來傳遞。CommandLineRunner會原封不動照單全收這些接口,這些參數也可以封裝到ApplicationArguments對象中供ApplicationRunner調用。 我們來認識一下ApplicationArguments的相關方法:

  • getSourceArgs() 被傳遞給應用程序的原始參數,返回這些參數的字符串數組。

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

    透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  • getOptionNames() 獲取選項名稱的Set字符串集合。如 --spring.profiles.active=dev --debug 將返回["spring.profiles.active","debug"] 。

  • getOptionValues(String name) 通過名稱來獲取該名稱對應的選項值。如--foo=bar --foo=baz 將返回["bar","baz"]。

  • containsOption(String name) 用來判斷是否包含某個選項的名稱。

  • getNonOptionArgs() 用來獲取所有的無選項參數。

    接下來我們試驗一波,你可以通過下面的命令運行一個 Spring Boot應用 Jar

java -jar yourapp.jar --foo=bar --foo=baz --dev.name=碼農小胖哥 java felordcn

或者在IDEA開發工具中打開Spring Boot應用main方法的配置項,進行如下配置,其他IDE工具同理。

運行Spring Boot應用,將會打印出:

2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=bar
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=baz
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --dev.name=碼農小胖哥
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = java
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = felordcn
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = [--foo=bar, --foo=baz, --dev.name=碼農小胖哥, java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = [java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionValues = [bar, baz]

然後你就可以根據實際需要動態地執行一些邏輯。

5. 總結

今天我們對CommandLineRunner和ApplicationRunner進行了講解,從用法到順序執行,又對Spring Boot傳遞參數進行了介紹和演示,希望對你有所幫助。多多關注:碼農小胖哥,更多編程乾貨分享給你。

關注公眾號:Felordcn 獲取更多資訊

個人博客:https://felord.cn

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

搭建Prometheus平台,你必須考慮的6個因素_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

作者簡介

Loris Degioanni,Sysdig的創始人和CTO,同時還是容器安全工具Falco的創建者。

原文鏈接
https://thenewstack.io/6-things-to-consider-in-a-prometheus-monitoring-platform/

本文轉自Rancher Labs

當前,Prometheus被許多企業和組織廣泛使用,以監控其容器和微服務。但是在這一過程中,大型公司通常會陷入困境:當應用程序數量越來越多的時候,擴展監控指標則是一個十分重大的挑戰。

不斷增長的容器使情況複雜化

相對來說,監控單體環境常常更簡單,因為靜態物理服務器和虛擬機數量是確定的,並且監控指標的數量也是有限的。但是,如今由於容器以及需要向微服務架構遷移,要跟蹤監控的實例程序數量激增。

如果說位於數據中心的服務器是寵物,需要我們不斷關注的話,那麼雲實例則更像牛(因為有很多,你不必關心單個實例),而容器則更像小蜜蜂。它們數量很多,有時每台機器有數百個容器,並且新的容器一直不斷出現,當與諸如Kubernetes的容器編排引擎一起使用時,它們的壽命可能非常短。這使得跟蹤監控它們變得更加困難,而且如果你不小心誤操作的話,它們可能會造成很多損害。

隨着複雜性和分佈式環境的增加,你需要監控的實體數量也在增加。此外,你可能希望監控更多屬性以確保你對正在發生的事情有準確的了解,或者在進行故障排除或事件響應的情況下,可以了解正在發生的事情。在短暫的環境中,後者尤其成問題,因為當你想了解問題的根本原因時,通常相關的資源已經停用,這意味着監控解決方案必須提供一種能夠存儲足夠的歷史記錄以進行取證的方法。

流行的監控工具:Prometheus

越來越多需要雲監控的團隊正在轉向Prometheus,這是一個開源的CNCF項目。Prometheus已成為開發人員用來在雲原生環境中收集和理解指標的首選監控工具。它由一個大型社區支持,有來自700多家公司的6300個貢獻者,有13500個代碼提交和7200個拉取請求。

默認情況下,典型的雲原生應用程序堆棧(如Kubernetes、Ngnix、MongoDB、Kafka、golang等)會暴露Prometheus指標。Prometheus是一個可以垂直彈性伸縮的Go程序,為單個容器或單個主機部署它時十分容易。換言之,一開始使用Prometheus極為容易,你可以輕鬆監控你的第一個Kubernetes集群,但是這也意味着隨着基礎架構的增長,監控會越來越複雜。

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

應用程序增長帶來的擴展問題

隨着環境規模增長,你需要跟蹤監控飛速增長的時間序列數據,並且在數據量達到某個點之後,單個Prometheus實例無法繼續跟蹤監控。這一情況下,最直接的選擇是在整個企業中運行一組Prometheus服務器,但這帶來了一些挑戰。例如,跨數十甚至數百台Prometheus服務器管理和合併數據並不容易。同樣,了解企業工作流程、單點登錄、基於角色的訪問控制以及遵守SLA或合規性也不是容易的問題。隨着應用程序的增長,在不中斷開發人員工作的情況下運行一個全方位的監控解決方案,這將成為一個可管理性和可靠性的問題。

為了解決這一問題,企業採用了許多方法。

簡單的方法是為每個命名空間或每個集群都準備一個單獨的Prometheus服務器。這種方法到一定規模就會難以為繼,此外,它還有一個缺點,那就是會造成大量的斷開的數據孤島。這會使故障排查變得很麻煩,因為大多數問題會跨越多個服務/團隊/集群。不但在每個環境中很難找到相同的指標,你還需要把數據拼接在一起,以試圖了解發生了什麼。

另一個常見方法是使用類似Cortex或Thanos的開源工具來集合多個Prometheus服務器。這些高效的工具可以讓你集中查詢服務器、收集數據然後在統一的dashboard中共享。然而,與任何數據密集型分佈式系統一樣,它們需要大量的技能和資源才能運行。

需要考慮的6個因素

對於那些以Prometheus為起點,然後尋求商業化解決方案以獲得全局監控的公司來說,重要的是,不丟失Prometheus上完成的所有標準化開發工作——dashboard、告警、exporter等。然而,這不是需要考慮的唯一事情,如果你繼續使用Prometheus,需要堅持以下標準:

1、 兼容性,以支持所有Prometheus功能

你的供應商/所使用的工具/SaaS解決方案需要能夠使用任何可產生Prometheus指標的實體程序中消耗數據,無論是本地Kubernetes還是雲服務。相對來說,消耗Prometheus指標微不足道,但是也不要忽略一些小事情,例如將指標提取到存儲中或增加數據時能夠重新標註指標,這樣對你的環境更有意義。這些小事加起來,能夠收集到的數據將會堆積如山、大不相同。

2、 PromQL兼容性

Prometheus查詢語言由Prometheus創建者發明,用於提取存儲在Prometheus中的信息。PromQL能讓你查詢指定服務或指定用戶的指標,它還能匯總或細分數據。例如,你可以使用它显示所有容器中每個應用的CPU使用率。或者僅显示Cassandra容器的數據,並將其显示為每個集群的單個值。可以說,PromQL釋放了Prometheus的真正價值,因此如果將Prometheus的指標集成到一個不完全支持PromQL的產品中,就完全違背了使用Prometheus的初衷。

3、 支持熱插拔

要真正與Prometheus兼容,該解決方案必須能夠支持熱插拔,以便能夠與你現有的dashboard、告警和腳本一起使用。例如,許多使用Prometheus的企業都將Grafana用於dashboard。這個開源工具能夠與Prometheus很好地集成在一起,包括在查詢級別,並且可以用於生成一系列有用的圖表和dashboard。因此,聲稱與Prometheus兼容的商業產品應與Grafana等工具兼容。僅僅說解決方案可以讓你在Grafana中查看数字是遠遠不夠的,你需要能夠按照原樣提取現有的Grafana dashboard,並將它們重新應用於商業解決方案中已安裝的數據。

4、 訪問控制

在評估工具時,訪問控制是另一個你需要考慮的安全問題。能夠使用行業標準協議(包括LDAP、Google Oauth、SAML和OpenID)保護用戶身份驗證,使公司能夠通過基於服務的訪問控制來隔離和保護資源。

5、 故障排查

Kubernetes簡化了部署、彈性伸縮和管理容器化應用程序和微服務。這有助於保持服務的正常運行,但是要識別和解決諸如性能降低、部署失敗和連接錯誤之類的根本問題,你需要能夠從整個環境中收集和可視化基礎架構、應用程序和性能數據。由於無法同時訪問實時信息和上下文數據,因此幾乎不可能關聯環境中的指標,所以你可以更快地解決問題。

6、 與現有告警兼容

最後,如果你正在尋找商業解決方案來幫助解決Prometheus可擴展性問題,請確保它支持所有級別的告警。能夠實現這一目標的關鍵是全面支持Alert Manager功能,而Alert Manager還要求100%的集成和 PromQL兼容性。

如果你找到一個能夠滿足以上標準的商業化工具,你應該能夠輕鬆將其集成到現有的Prometheus中,並且能夠避免公司遇到的可擴展性問題。開發人員有充分的理由喜愛Prometheus,因此在採用商業化方案之前進行全面、盡職的調查將確保他們仍然可以使用自己喜歡的指標。

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

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

六一兒童節,程序員寫給女兒的一封信_貨運

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

今天是六一兒童節,不想寫技術了,就寫點別的吧——給四歲的女兒寫封信。我的讀者群體里大部分都是大學生或者初入職場的新人,一時半會是體會不到做父母的辛酸和樂趣。

但我希望我的故事能給讀者朋友們枯燥的生活增添一丟丟樂趣,這就足夠了,對吧?這也是我一直以來的心愿。

親愛的女兒,你好呀。

我是你的爸爸馬偉青(有些讀者叫我二哥、有些叫王老師、有些叫馬老師、有些叫青哥,我統統接受,一個也不挑),等你能看懂這封信的時候,你已經可以自由地閱讀了。大量的閱讀,對人的一生至關重要,所以咱們家最多的就是書本了——這也是我一直引以為傲的。

媽媽也喜歡給你買書,我覺得她給你買的書都質量蠻高的,比如說甜心英語、長頸鹿卡密爾、馬努和米娜、巧虎、窗邊的小豆豆等等,這些書都是成套的,爸爸讀起來都覺得挺有意思,我想你也一定非常喜歡。

因為你讀書的樣子真的非常專註,非常可愛。

記得你剛從媽媽肚子里剖出來的時候,外婆抱着你,對爸爸說了一句夠驕傲一輩子的話:“長得真像你啊。”不是爸爸要和你媽媽爭寵,而是你的確是爸爸媽媽愛情的結晶,你的到來,豐富了我們的生活和情感,更重要的是,賦予了爸爸媽媽神聖的職責。

如果沒有你,爸爸在業餘時間里,可能還沉浸在 Dota、火炬之光、三國群英傳 7 的遊戲中。想想都覺得無比的可怕,打遊戲沒有錯,但爸爸應該去做一些更积極向上、更富有意義的事情,比如說讀書和寫作。

我比你姑姑大 11 歲,所以她小的時候,爸爸帶的非常多。就連上了小學,爸爸去哪裡她都要跟到哪裡,包括和你媽媽約會的時候。

也許是這樣一份特殊的經歷,爸爸在帶你的過程中顯然比你媽媽更專業一些,就連拍飽嗝(防止你奶吃多了吐)這種高級別的手法爸爸都會。由於媽媽工作上的限制,爸爸總體上帶你的時間確實要比媽媽多一些,陪你去上金寶貝早教課、上兔加熊體能課、上樂高積木、上愛貝英語,包括在小區裏面和小朋友玩,以及日常護理,洗澡洗頭髮,爸爸都能得心應手。

一開始,爸爸也是放不開的,畢竟帶孩子這件事,女性的佔比要比男性的佔比多得多,站在女人堆里,有時候真的是舉足無措啊。

但爸爸硬是挺了過來。關鍵是,還兼顧了家務、工作和寫作,想想都覺得自己挺牛逼的,我想你也這麼覺得吧?

爸爸和媽媽畢竟有一些不同,有的時候會嚴厲一些,所以你從來不鬧爸爸,你的這份克制多多少少會讓爸爸覺得內疚,但我想,對你的性格,對你以後的為人處事會有一些幫助,畢竟社會有美好的一面,也有殘忍的一面。

你長這麼大,只有一件事,讓爸爸至今懷恨在心,就是在你滿月之前,剪手指甲的時候把你手指頭上的肉剪掉了一小塊。鮮紅的血液流出來了你才後知后覺地哭了起來,可把爸爸嚇壞了。從此以後,再沒敢替你剪過指甲。

好了,過往就先煽情到這裏。接下來,爸爸寫一下對你的期許,或許不叫期許,更應該叫做祝福。無論你成為怎樣的你,爸爸永遠都是你背後最強有力的支柱。

第一,爸爸媽媽會努力給你力所能及最好的教育條件。但是,爸爸不希望學校過早的壓榨你的潛力,如果作業真的多到沒有時間去玩,爸爸寧願你不寫,家長會挨批的話,爸爸不嫌丟臉。

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

在爸爸看來,學習固然重要,但如果是通過寫不完的作業來達成的話,我認為是有問題的。爸爸來自於農村,所以讀書上學是唯一改變命運的出路,但你不同,你可以放心大膽地去做更多你喜歡做的事情,不用去承擔後果,以及背負巨大的壓力。

第二,在上學的年紀,一定要多多讀書,你喜歡讀什麼樣的書,爸爸都會給你買,沒有任何限制。

爸爸在上初中的時候,讀書的興趣是最濃烈,最純粹的,但那時候學校不允許讀任何課外書,真的是抹殺了爸爸求知的天性。

人最怕的除了窮,就是無知了。“窮”這個難題,爸爸媽媽會努力幫你解決,但“無知”這個難題,就需要你自己去完成了。而書,是解決這個難題最強有力的武器。

第三,英語非常非常重要,了解我們自己的文化固然重要,吸取別人的長處同樣重要。如果你精通英語的話,周遊世界,探索你感興趣的領域就會容易得多,這是不爭的事實。

爸爸就時常後悔沒有把英語學到極致,否則爸爸會在編程領域更上一層樓的。但爸爸沒有放棄,仍然在盡最大的努力改善中。你現在已經認識四五十個單詞了,這在爸爸看來,太棒了。

第四,女孩子一定要學會撒嬌和賣萌,這也是爸爸從小一直教你的,遇到一些問題時,哭鬧不是最好的解決辦法,變通才是最好的。

你可以像爸爸一樣,做一個情緒化的人,該哭的時候哭,該笑的時候笑,不要做女強人,學會與朋友分享你的痛苦和快樂。

第五,女孩子一定要學會化妝和打扮,把自己收拾的美美的,衣着得體,糟糕的情緒也會逃之夭夭。

體能鍛煉也是必不可少的,一方面是為了保持身體健康,塑造完美體形,另一方面,能夠幫助你養成良好的生活習慣,以及堅強的意志力。

第六,做你自己,做好你自己。不要太計較別人的眼光,把自己活得快活一點,樂觀一點、瀟洒一點。

如果以後爸爸媽媽的觀點和你不一致,不要聽我們的,聽從你自己內心最真實的那個聲音。你雖然是我們的女兒,但生命是你自己的,由你負責。但不管怎樣,爸爸和媽媽,都會做你最堅強的後盾,永遠支持你和愛護你。

就寫這麼多吧。Peace。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻。

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

深入了解ConcurrentHashMap_網頁設計公司

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

在上一篇文章【簡單了解系列】從基礎的使用來深挖HashMap里,我從最基礎的使用中介紹了HashMap,大致是JDK1.7和1.8中底層實現的變化,和介紹了為什麼在多線程下可能會造成死循環,擴容機制是什麼樣的。感興趣的可以先看看。

我們知道,HashMap是非線程安全的容器,那麼為什麼ConcurrentHashMap能夠做到線程安全呢?

底層結構

首先看一下ConcurrentHashMap的底層數據結構,在Java8中,其底層的實現方式與HashMap一樣的,同樣是數組、鏈表再加紅黑樹,具體的可以參考上面的HashMap的文章,下面所有的討論都是基於Java 1.8。

transient volatile Node<K,V>[] table;

volatile關鍵字

對比HashMap的底層結構可以發現,table的定義中多了一個volatile關鍵字。這個關鍵字是做什麼的呢?我們知道所有的共享變量都存在主內存中,就像table。

而線程對變量的所有操作都必須在線程自己的工作內存中完成,而不能直接讀取主存中的變量,這是JMM的規定。所以每個線程都會有自己的工作內存,工作內存中存放了共享變量的副本。而正是因為這樣,才造成了可見性的問題。

ABCD四個線程同時在操作一個共享變量X,此時如果A從主存中讀取了X,改變了值,並且寫回了內存。那麼BCD線程所得到的X副本就已經失效了。此時如果沒有被volatile修飾,那麼BCD線程是不知道自己的變量副本已經失效了。繼續使用這個變量就會造成數據不一致的問題。

內存可見性

而如果加上了volatile關鍵字,BCD線程就會立馬看到最新的值,這就是內存可見性。你可能想問,憑什麼加了volatile的關鍵字就可以保證共享變量的內存可見性?

那是因為如果變量被volatile修飾,在線程進行寫操作時,會直接將新的值寫入到主存中,而不是線程的工作內存中;而在讀操作時,會直接從主存中讀取,而不是線程的工作內存。

基礎使用

首先這個使用與HashMap沒有任何區別,只是實現改成了ConcurrentHashMap。

Map<String, String> map = new ConcurrentHashMap<>();
map.put("微信搜索", "SH的全棧筆記");
map.get("微信搜索"); // SH的全棧筆記

取值

首先我們來看一下get方法的使用,源碼如下。

public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  int h = spread(key.hashCode());
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (e = tabAt(tab, (n - 1) & h)) != null) {
    if ((eh = e.hash) == h) {
      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
        return e.val;
    }
    else if (eh < 0)
      return (p = e.find(h, key)) != null ? p.val : null;
    while ((e = e.next) != null) {
      if (e.hash == h &&
          ((ek = e.key) == key || (ek != null && key.equals(ek))))
        return e.val;
    }
  }
  return null;
}

大概解釋一下這個過程發生了什麼,首先根據key計算出哈希值,如果找到了就直接返回值。如果是紅黑樹的話,就在紅黑樹中查找值,否則就按照鏈表的查找方式查找。

這與HashMap也差不多的,元素會首先以鏈表的方式進行存儲,如果該桶中的元素數量大於TREEIFY_THRESHOLD的值,就會觸發樹化。將當前的鏈錶轉換為紅黑樹。因為如果數量太多的話,鏈表的查詢效率就會變得非常低,時間複雜度為O(n),而紅黑樹的查詢時間複雜度則為O(logn),這個閾值在Java 1.8中的默認值為8,定義如下。

static final int TREEIFY_THRESHOLD = 8;

賦值

put的源碼就不放出來了,放在這大家估計也不會一行一行的去看。所以我就簡單的解釋一下put的過程發生了什麼事,並貼上關鍵代碼就好了。

整個過程,除開併發的一些細節,大致的流程和1.8中的HashMap是差不多的。

  • 首先會根據傳入的key計算出hashcode,如果是第一次被賦值,那自然需要進行初始化table
  • 如果這個key沒有存在過,直接用CAS在當前槽位的頭節點創建一個Node,會用自旋來保證成功
  • 如果當前的Node的hashcode是否等於-1,如果是則證明有其它的線程正在執行擴容操作,當前線程就加入到擴容的操作中去
  • 且如果該槽位(也就是桶)上的數據結構如果是鏈表,則按照鏈表的插入方式,直接接在當前的鏈表的後面。如果數量大於了樹化的閾值就會轉為紅黑樹。
  • 如果這個key存在,就會直接覆蓋。
  • 判斷是否需要擴容

看到這你可能會有一堆的疑問。

例如在多線程的情況下,幾個線程同時來執行put操作時,怎麼保證只執行一次初始化,或者怎麼保證只執行一次擴容呢?萬一我已經寫入了數據,另一個線程又初始化了一遍,豈不是造成了數據不一致的問題。同樣是多線程的情況下, 怎麼保證put值的時候不會被其他線程覆蓋。CAS又是什麼?

接下來我們就來看一下在多線程的情況下,ConcurrentHashMap是如何保證線程安全的。

初始化的線程安全

首先我們來看初始化的源碼。

private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
  while ((tab = table) == null || tab.length == 0) {
    if ((sc = sizeCtl) < 0)
      Thread.yield(); // lost initialization race; just spin
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
      try {
        if ((tab = table) == null || tab.length == 0) {
          int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
          @SuppressWarnings("unchecked")
          Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
          table = tab = nt;
          sc = n - (n >>> 2);
        }
      } finally {
        sizeCtl = sc;
      }
      break;
    }
  }
  return tab;
}

可以看到有一個關鍵的變量,sizeCtl,其定義如下。

private transient volatile int sizeCtl;

sizeCtl使用了關鍵字volatile修飾,說明這是一個多線程的共享變量,可以看到如果是首次初始化,第一個判斷條件if ((sc = sizeCtl) < 0)是不會滿足的,正常初始化的話sizeCtl的值為0,初始化設定了size的話sizeCtl的值會等於傳入的size,而這兩個值始終是大於0的。

CAS

然後就會進入下面的U.compareAndSwapInt(this, SIZECTL, sc, -1)方法,這就是上面提到的CAS,Compare and Swap(Set),比較並交換,Unsafe是位於sun.misc下的一個類,在Java底層用的比較多,它讓Java擁有了類似C語言一樣直接操作內存空間的能力。

例如可以操作內存、CAS、內存屏障、線程調度等等,但是如果Unsafe類不能被正確使用,就會使程序變的不安全,所以不建議程序直接使用它。

compareAndSwapInt的四個參數分別是,實例、偏移地址、預期值、新值。偏移地址可以快速幫我們在實例中定位到我們要修改的字段,此例中便是sizeCtl。如果內存當中的sizeCtl是傳入的預期值,則將其更新為新的值。這個Unsafe類的方法可以保證這個操作的原子性。當你在使用parallelStream進行併發的foreach遍歷時,如果涉及到修改一個整型的共享變量時,你肯定不能直接用i++,因為在多線程下,i++每次操作不能保證原子性。所以你可能會用到如下的方式。

AtomicInteger num = new AtomicInteger();
arr.parallelStream().forEach(item -> num.getAndIncrement());

你可能會好奇,為什麼使用了AtomicInteger就可以保證原子性,跟Unsafe類和CAS又有什麼關係,讓我們接着往下,看getAndIncrement方法的底層實現。

public final int getAndIncrement() {
  return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看到,底層調用的是Unsafe類的方法,這不就聯繫上了嗎,而getAndIncrement的實現又長這樣。

public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
    var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  return var5;
}

沒錯,這裏底層調用了compareAndSwapInt方法。可以看到這裏加了while,如果該方法返回false就一直循環,直到成功為止。這個過程有個的名字,叫自旋。特別高端啊,說人話就是無限循環。

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

什麼情況會返回false呢?那就是var5變量存儲的值,和現在內存中實際var5的值不同,說明這個變量已經被其他線程修改過了,此時通過自旋來重新獲取,直到成功為止,然後自旋結束。

結論

聊的稍微有點多,這小節的問題是如何保證不重複初始化。那就是執行首次擴容時,會將變量sizeCtl設置為-1,因為其被volatile修飾,所以其值的修改對其他線程可見。

其它線程再調用初始化時,就會發現sizeCtl的值為-1,說明已經有線程正在執行初始化的操作了,就會執行Thread.yield(),然後退出。

yield相信大家都不陌生,和sleep不同,sleep可以讓線程進入阻塞狀態,且可以指定阻塞的時間,同時釋放CPU資源。而yield不會讓線程進入阻塞狀態,而且也不能指定時間,它讓線程重新進入可執行狀態,讓出CPU調度,讓CPU資源被同優先級或者高優先級的線程使用,稍後再進行嘗試,這個時間依賴於當前CPU的時間片劃分。

如何保證值不被覆蓋

我們在上一節舉了在併發下i++的例子,說在併發下i++並不是一個具有原子性的操作,假設此時i=1,線程A和線程B同時取了i的值,同時+1,然後此時又同時的寫回。那麼此時i++的值會是2而不是3,在併發下1+1+1=2是可能出現的。

讓我們來看一下ConcurrentHashMap在目標key已經存在時的賦值操作,因為如果不存在會直接調用Unsafe的方法創建一個Node,所以後續的線程就會進入到下面的邏輯中來,由於太長,我省略了一些代碼。

......
V oldVal = null;
synchronized (f) {
  if (tabAt(tab, i) == f) {
    if (fh >= 0) {
      binCount = 1;
      for (Node<K,V> e = f;; ++binCount) {
        ......
      }
    }
    else if (f instanceof TreeBin) {
      Node<K,V> p;
      binCount = 2;
      if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
        oldVal = p.val;
        if (!onlyIfAbsent)
          p.val = value;
      }
    }
  }
}
if (binCount != 0) {
  if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
  if (oldVal != null)
    return oldVal;
  break;
}

上述代碼在賦值的邏輯外層包了一個synchronized,這個有什麼用呢?

synchronized關鍵字

這個地方也可以換一個方式來理解,那就是synchronized如何保證線程安全的。線程安全,我認為更多的是描述一種風險。在堆內存中的數據由於可以被任何線程訪問到,在沒有任何限制的情況下存在被意外修改的風險。

而synchronized是通過對共享資源加鎖的方式,使同一時間只能有一個線程能夠訪問到臨界區(也就是共享資源),共享資源包括了方法、鎖代碼塊和對象。

那是不是使用了synchronized就一定能保證線程安全呢?不是的,如果不能正確的使用,很可能就會引發死鎖,所以,保證線程安全的前提是正確的使用synchronized。

自動擴容的線程安全

除了初始化、併發的寫入值,還有一個問題值得關注,那就是在多線程下,ConcurrentHashMap是如何保證自動擴容是線程安全的。

擴容的關鍵方案是transfer,但是由於代碼太多了,貼在這個地方可能會影響大家的理解,感興趣的可以自己的看一下。

還是大概說一下自動擴容的過程,我們以一個線程來舉例子。在putVal的最後一步,會調用addCount方法,然後在方法里判讀是否需要擴容,如果容量超過了實際容量 * 負載因子(也就是sizeCtl的值)就會調用transfer方法。

計算分區的範圍

因為ConcurrentHashMap是支持多線程同時擴容的,所以為了避免每個線程處理的數量不均勻,也為了提高效率,其對當前的所有桶按數量(也就是上面提到的槽位)進行分區,每個線程只處理自己分到的區域內的桶的數據即可。

當前線程計算當前stride的代碼如下。

stride = (NCPU > 1) ? (n >>> 3) / NCPU : n);

如果計算出來的值小於設定的最小範圍,也就是private static final int MIN_TRANSFER_STRIDE = 16;,就把當前分區範圍設置為16。

初始化nextTable

nextTable也是一個共享變量,定義如下,用於存放在正在擴容之後的ConcurrentHashMap的數據,當且僅當正在擴容時才不為空。

private transient volatile Node<K,V>[] nextTable;

如果當前transfer方法傳入的nextTab(這是個局部變量,比上面提到的nextTable少了幾個字母,不要搞混了)是null,說明是當前線程是第一個調用擴容操作的線程,就需要初始化一個size為原來容量2被的nextTable,核心代碼如下。

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 可以看到傳入的初始化容量是n << 1。

初始化成功之後就更新共享變量nextTable的值,並設置transferIndex的值為擴容前的length,這也是一個共享的變量,表示擴容使還未處理的桶的下標。

設置分區邊界

一個新的線程加入擴容操作,在完成上述步驟后,就會開始從現在正在擴容的Map中找到自己的分區。例如,如果是第一個線程,那麼其取到的分區就會如下。

start = nextIndex - 1;
end = nextIndex > stride ? nextIndex - stride : 0;
// 實際上就是當還有足夠的桶可以分的時候,線程分到的分區為 [n-stride, n - 1]

可以看到,分區是從尾到首進行的。而如果是首次進入的線程,nextIndex 的值會被初始化為共享變量transferIndex 的值。

Copy分區內的值

當前線程在自己劃分到的分區內開始遍歷,如果當前桶是null,那麼就生成一個 ForwardingNode,代碼如下。

ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

並把當前槽位賦值為fwd,你可以把ForwardingNode理解為一個標誌位,如果有線程遍歷到了這個桶, 發現已經是ForwardingNode了,就代表這個桶已經被處理過了,就會跳過這個桶。

如果這個桶沒有被處理過,就會開始給當前的桶加鎖,我們知道ConcurrentHashMap會在多線程的場景下使用,所以當有線程正在擴容的時候,可能還會有線程正在執行put操作,所以如果當前Map正在執行擴容操作,如果此時再寫入數據,很可能會造成的數據丟失,所以要對桶進行加鎖。

總結

對比在1.7中採用的Segment分段鎖的臃腫設計,1.8中直接使用了CAS和Synchronized來保證併發下的線程安全。總的來說,在1.8中,ConcurrentHashMap和HashMap的底層實現都差不多,都是數組、鏈表和紅黑樹的方式。其主要區別就在於應用場景,非併發的情況可以使用HashMap,而如果要處理併發的情況,就需要使用ConcurrentHashMap。關於ConcurrentHashMap就先聊到這裏。

本文使用 mdnice 排版

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

啪啪,打臉了!領導說:try-catch必須放在循環體外!_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

哈嘍,親愛的小夥伴們,技術學磊哥,進步沒得說!歡迎來到新一期的性能解讀系列,我是磊哥。

今天給大家帶來的是關於 try-catch 應該放在循環體外,還是放在循環體內的文章,我們將從性能和業務場景分析這兩個方面來回答此問題。

很多人對 try-catch 有一定的誤解,比如我們經常會把它(try-catch)和“低性能”直接畫上等號,但對 try-catch 的本質(是什麼)卻缺少着最基礎的了解,因此我們也會在本篇中對 try-catch 的本質進行相關的探索。

小貼士:我會盡量用代碼和評測結果來證明問題,但由於本身認知的局限,如有不當之處,請讀者朋友們在評論區指出。

性能評測

話不多說,我們直接來開始今天的測試,本文我們依舊使用 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基準測試套件)來進行測試。

首先在 pom.xml 文件中添加 JMH 框架,配置如下:

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
   <groupId>org.openjdk.jmh</groupId>
   <artifactId>jmh-core</artifactId>
   <version>{version}</version>
</dependency>

完整測試代碼如下:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * try - catch 性能測試
 */
@BenchmarkMode(Mode.AverageTime) // 測試完成時間
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 預熱 1 輪,每次 1s
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 測試 5 輪,每次 3s
@Fork(1) // fork 1 個線程
@State(Scope.Benchmark)
@Threads(100)
public class TryCatchPerformanceTest {
    private static final int forSize = 1000; // 循環次數
    public static void main(String[] args) throws RunnerException {
        // 啟動基準測試
        Options opt = new OptionsBuilder()
                .include(TryCatchPerformanceTest.class.getSimpleName()) // 要導入的測試類
                .build();
        new Runner(opt).run(); // 執行測試
    }

    @Benchmark
    public int innerForeach() {
        int count = 0;
        for (int i = 0; i < forSize; i++) {
            try {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    @Benchmark
    public int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < forSize; i++) {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

以上代碼的測試結果為:

從以上結果可以看出,程序在循環 1000 次的情況下,單次平均執行時間為:

  • 循環內包含 try-catch 的平均執行時間是 635 納秒 ±75 納秒,也就是 635 納秒上下誤差是 75 納秒;
  • 循環外包含 try-catch 的平均執行時間是 630 納秒,上下誤差 38 納秒。

也就是說,在沒有發生異常的情況下,除去誤差值,我們得到的結論是:try-catch 無論是在 for 循環內還是 for 循環外,它們的性能相同,幾乎沒有任何差別。

try-catch的本質

要理解 try-catch 的性能問題,必須從它的字節碼開始分析,只有這樣我能才能知道 try-catch 的本質到底是什麼,以及它是如何執行的。

此時我們寫一個最簡單的 try-catch 代碼:

public class AppTest {
    public static void main(String[] args) {
        try {
            int count = 0;
            throw new Exception("new Exception");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然後使用 javac 生成字節碼之後,再使用 javap -c AppTest 的命令來查看字節碼文件:

 javap -c AppTest 
警告: 二進制文件AppTest包含com.example.AppTest
Compiled from "AppTest.java"
public class com.example.AppTest {
  public com.example.AppTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: new           #2                  // class java/lang/Exception
       5: dup
       6: ldc           #3                  // String new Exception
       8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
      11: athrow
      12: astore_1
      13: aload_1
      14: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      17: return
    Exception table:
       from    to  target type
           0    12    12   Class java/lang/Exception
}

從以上字節碼中可以看到有一個異常表:

Exception table:
       from    to  target type
          0    12    12   Class java/lang/Exception

參數說明:

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

  • from:表示 try-catch 的開始地址;
  • to:表示 try-catch 的結束地址;
  • target:表示異常的處理起始位;
  • type:表示異常類名稱。

從字節碼指令可以看出,當代碼運行時出錯時,會先判斷出錯數據是否在 from 到 to 的範圍內,如果是則從 target 標誌位往下執行,如果沒有出錯,直接 goto 到 return。也就是說,如果代碼不出錯的話,性能幾乎是不受影響的,和正常的代碼的執行邏輯是一樣的。

業務情況分析

雖然 try-catch 在循環體內還是循環體外的性能是類似的,但是它們所代碼的業務含義卻完全不同,例如以下代碼:

public class AppTest {
    public static void main(String[] args) {
        System.out.println("循環內的執行結果:" + innerForeach());
        System.out.println("循環外的執行結果:" + outerForeach());
    }
    
    // 方法一
    public static int innerForeach() {
        int count = 0;
        for (int i = 0; i < 6; i++) {
            try {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    // 方法二
    public static int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < 6; i++) {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

以上程序的執行結果為:

java.lang.Exception: new Exception

at com.example.AppTest.innerForeach(AppTest.java:15)

at com.example.AppTest.main(AppTest.java:5)

java.lang.Exception: new Exception

at com.example.AppTest.outerForeach(AppTest.java:31)

at com.example.AppTest.main(AppTest.java:6)

循環內的執行結果:5

循環外的執行結果:3

可以看出在循環體內的 try-catch 在發生異常之後,可以繼續執行循環;而循環外的 try-catch 在發生異常之後會終止循環。

因此我們在決定 try-catch 究竟是應該放在循環內還是循環外,不取決於性能(因為性能幾乎相同),而是應該取決於具體的業務場景。

例如我們需要處理一批數據,而無論這組數據中有哪一個數據有問題,都不能影響其他組的正常執行,此時我們可以把 try-catch 放置在循環體內;而當我們需要計算一組數據的合計值時,只要有一組數據有誤,我們就需要終止執行,並拋出異常,此時我們需要將 try-catch 放置在循環體外來執行。

總結

本文我們測試了 try-catch 放在循環體內和循環體外的性能,發現二者在循環很多次的情況下性能幾乎是一致的。然後我們通過字節碼分析,發現只有當發生異常時,才會對比異常表進行異常處理,而正常情況下則可以忽略 try-catch 的執行。但在循環體內還是循環體外使用 try-catch,對於程序的執行結果來說是完全不同的,因此我們應該從實際的業務出發,來決定到 try-catch 應該存放的位置,而非性能考慮。

關注公眾號「Java中文社群」回復“乾貨”,獲取 50 篇原創乾貨 Top 榜。

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

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

【大廠面試01期】高併發場景下,如何保證緩存與數據庫一致性?_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

PS:本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,如果想要了解更多大廠面試相關的內容及獲取《大廠面試指北》離線PDF版,請掃描下方二維碼碼關注公眾號“大廠面試”,謝謝大家了!項目地址:https://github.com/NotFound9/interviewGuide

《大廠面試指北》項目截圖:

獲取《大廠面試指北》離線PDF版,請掃描下方二維碼關注公眾號“大廠面試”

面試題:高併發場景下,如何保證緩存與數據庫一致性?

問題分析

我們日常開發中,對於緩存用的最多的場景就像下圖一樣,可能僅僅是對數據進行緩存,減輕數據庫壓力,縮短接口響應時間。

這種方案在不需要考慮高併發得去寫緩存,高併發得讀寫緩存時,是不會有問題,但是如果是在高併發場景下,要保證緩存和數據庫的一致性,至少需要解決以下問題:

高併發寫時的數據不一致問題

高併發讀寫時,請求執行各步驟的順序是不可控的。假設此時有一個請求A,B都在在執行寫流程,請求A是需要將某個數據改成1,請求B是需要將某個數據改為2,執行操作如下時就會導致數據不一致的問題:

1.請求A執行操作1.1刪除緩存。

2.請求A執行操作1.2更新數據庫,將值改為1。

3.請求B執行操作1.1刪除緩存。

4.請求B執行操作1.2更新數據庫,將值改為2

5.假設說請求B所在服務器網絡延遲比較低,請求B先更新緩存,此時緩存中的key對應的value是2。

6.請求A更新緩存,將緩存中B更新的數據進行覆蓋,將key對應的值改為1。

此時數據庫中是B修改后的數據,值為2,而緩存中的數據是1,這樣在緩存過期錢,用戶讀到的都是臟數據,與數據庫不一致。

高併發讀寫時的數據不一致的問題

高併發讀寫時,請求執行各步驟的順序是不可控的。假設此時有一個請求A在執行寫流程,將原值由1改成2,請求B執行讀流程,執行操作如下時就會導致數據不一致的問題:

1.寫請求A執行1.1操作刪除緩存key,value是原值1。

2.讀請求B執行2.1操作發現緩存中沒有數據,就去執行2.2操作讀數據庫,讀到舊數據,值為1。

3.寫請求A執行1.2操作更新數據庫,將數據由1改為2。

4.寫請求A執行1.3操作更新緩存,此時緩存中的數據key對應的value是2。

5.讀請求B執行2.3操作更新緩存,將之前讀到的舊數據1設置到緩存中,此時緩存中的數據key對應的value是1。

所以如果說讀請求B所在服務器網絡延遲比較高,去執行2.3操作比寫請求A晚,就會導致寫請求A更新完緩存后,讀請求B使用之前讀到的舊數據去更新緩存,此時緩存中數據就與數據庫中的不一致。

解決方案

保證數據一致性,網上有很多種方案,例如:

1.先刪除緩存,再更新數據庫。

2.先更新數據庫,再刪除緩存。

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

3.先刪除緩存,再更新數據庫,然後異步延遲一段時間再去刪一次緩存。

但是這些方案都是存在各種各樣的問題,這裏篇幅有限,只給出目前相對正確的三套方案,目前的這些方案也有自己的局限性。

方案1.寫請求串行化

寫請求

1.寫請求更新之前先獲取分佈式鎖,獲得之後才能去數據庫更新這個數據,獲取不到就進行等待,超時后就返回更新失敗。

2.更新完之後去刷新緩存,如果刷新失敗,放到內存隊列中進行重試(重試時取數據庫最新數據更新緩存)。

讀請求

讀請求發現緩存中沒有數據時,直接去讀取數據庫,讀完更新緩存。

總結

這種技術方案通過對寫請求的實現串行化來保證數據一致性,但是會導致吞吐量變低。比較適合銀行相關的業務,因為對於銀行項目來說,保證數據一致性比可用性更加重要,就像是去存款機存錢,取錢時,為了保證賬戶安全,都是會讓用戶執行操作后,等待一段時間才能獲得反饋,這段時間其實取款機是不可用的。

方案2.先更新數據庫,異步刪除緩存,刪除失敗后重試

1.先更新數據庫

2.異步刪除緩存(如果數據庫是讀寫分離的,那麼刪除緩存時需要延遲刪除,否則可能會在刪除緩存時,從庫還沒有收到更新后的數據,其他讀請求就去從庫讀到舊數據然後設置到緩存中。)

3.刪除緩存失敗時,將刪除的key放到內存隊列或者是消息隊列中進行異步重試

發散思考

在更新完數據庫后,我們為什麼不直接更新,而是採用刪除緩存呢?

這是因為直接更新緩存的話,在高併發場景下,有多個更新請求時,難以保證后更新數據庫的請求會後更新緩存,也就是上面的高併發寫問題。如果採用刪除緩存,可以讓下次讀時讀取數據庫,更新緩存,保證一致性。

方案3.業務項目更新數據庫,其他項目訂閱binlog更新

1.業務項目直接更新數據庫。

2.cannal項目會讀取數據庫的binlog,然後解析后發消息到kafka。

3.然後緩存更新項目訂閱topic,從kafka接收到更新數據庫操作的消息后,更新緩存,更新緩存失敗時,新建異步線程去重試或者將操作發到消息隊列,後續再進行處理。

總結:

但是這種方案在更新數據庫后,緩存中還是舊值,必須等緩存更新項目消費消息后,更新緩存,緩存中才是最新值。所以更新操作完成與更新生效之間會有一定的延遲。

最後

大家有了解其他的技術方案,歡迎進群一起討論!

評論裏面有朋友問延時雙刪策略是什麼?

這裏解釋一下:延時雙刪策略就是先刪除緩存,再更新數據庫,再異步過一小段時間后刪除緩存(時間取決於MySQL主從同步的時間)。

是因為MySQL如果是讀寫分離時(寫請求寫主庫,讀請求讀從庫),我們更新主庫后,需要一段時間,從庫才會收到更新。

如果是寫請求更新主庫后,第二次立即刪除緩存,MySQL從庫還沒有收到更新,還是舊數據,那麼讀請求直接從庫讀到舊數據,設置到緩存的數據就是舊數據,就會數據不一致,所以這也是延時雙刪策略提出的初衷。

參考鏈接:

https://www.cnblogs.com/-wenli/p/11474164.html

https://www.cnblogs.com/rjzheng/p/9041659.html

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

Python驗證碼識別_台中搬家

※台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

項目地址:https://github.com/kerlomz/captcha_trainer

編譯版下載地址: https://github.com/kerlomz/captcha_trainer/releases/tag/v1.0

注意:若使用雲服務器 (Windows Server版) 遇到閃退,請按照步驟:我的電腦——屬性——管理——添加角色和功能——勾選桌面體驗,點擊安裝,安裝之後重啟即可。

2020/06/01編外:

想必各位只是偶然間搜到這篇文章,網上文章參差不齊,標題黨很多,能跑起來的開源代碼很少,對於能跑起來的代碼,也經常遇到以下問題如:內存泄漏,網絡參數寫死導致更換訓練集報錯,網絡跑其他樣本識別率低,沒有調用示例等等。

再往下看之前,我可以向你們保證,它絕對會是你所見過的所有驗證碼有關的文章中最實用,最接近生產水平的。

  1. 對小白: 你可以不需要動手寫任何一行代碼。
  2. 對小企業: 它的可用性和穩定性是經得起考驗的,在性能上也是同行領先的,可以放心入坑。

因為小編打算轉行了,離開這個行業之前總要留下點什麼證明自己來過,總有人和我說的這個部署不會調用,可能你們想要的是一行pip就搞定環境的,所以今天給你們安排了麻瓜OCR(MuggleOCR)。
https://pypi.org/project/muggle-ocr
它整合了簡單驗證碼識別通用模型+印刷文字通用識別,並且支持調用本文框架訓練的模型。調用只需要三行核心代碼:

import time
# STEP 1
import muggle_ocr
import os
# STEP 2
sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.OCR)
root_dir = r"./imgs"
for i in os.listdir(root_dir):
    n = os.path.join(root_dir, i)
    with open(n, "rb") as f:
        b = f.read()
    st = time.time()
    # STEP 3
    text = sdk.predict(image_bytes=b)
    print(i, text, time.time() - st)

這真的很簡單,應付一般的文字識別和驗證碼都足夠了。(文字識別過幾天會更新一下新模型,畢竟0601模型就跑了半天。

1. 前言

本項目適用於Python3.7,GPU>=NVIDIA GTX1050Ti,原master分支新增了GUI配置界面以及編譯版本了,是時候寫一篇新的文章了。

長話短說,開門見山,網絡上現有的代碼以教學研究為主,本項目是為實用主義者定製的,只要基本的環境安裝常識,便可很好的訓練出期望的模型,重定義幾個簡單的參數任何人都能使用深度學習技術訓練一個商業化成品。

筆者選用的時下最為流行的CNN+BLSTM+CTC(CRNN)進行端到端的不定長驗證碼識別,代碼中預留了CNNX(搜不到因為是小編自己拼湊的)/MobileNet/DenseNet121/ResNet50等選項,可以在配置界面中直接選用。首先,介紹個大概吧。

網格結構 predict-CPU predict-GPU 模型大小
CNN5+Bi-LSTM+H64+CTC 15ms 8ms 2mb
CNN5+CrossEntropy 8ms 2ms 1.5mb

H16/H64指的是Bi-LSTM的隱藏神經元個數UnitsNum,所以本項目使用GPU訓練,使用CPU進行預測。預測服務部署項目源碼請移步此處:https://github.com/kerlomz/captcha_platform
部署項目的編譯版下載地址:https://github.com/kerlomz/captcha_platform/releases

2.環境依賴:

花了超長篇幅介紹了訓練環境的基本搭建,主要是給尚未入門的讀者看的,老鳥們隨便跳過,若不希望在環境上面浪費時間的,歡迎使用編譯版,可在文章開頭找到下載地址。

關於CUDA和cuDNN版本的問題,不少人很糾結,這裏就列出官方通過pip安裝的TensorFlow的版本對應表:

Linux

Version Python version Compiler Build tools cuDNN CUDA
tensorflow_gpu-1.14.0 3.7 GCC 4.8 Bazel 0.15.0 7.6 9

Windows

Version Python version Compiler Build tools cuDNN CUDA
tensorflow_gpu-1.14.0 3.7 MSVC 2015 update 3 Bazel 0.15.0 7.6 10

如果希望使用上面對應之外的搭配的CUDA和cuDNN,可以自行編譯TensorFlow,或者去Github上搜索TensorFlow Wheel找到第三方編譯的對應版本的whl安裝包。提前預警,若是自己編譯將會苦難重重,坑很多,這裏就不展開了。

2.1 本項目環境依賴

目前在以下主流操作系統平台均測試通過:

操作系統 最低支持版本
Ubuntu 16.04
Windows 7 SP1
MacOS N/A

本訓練項目主要的環境依賴清單如下

依賴 最低支持版本
Python 3.7
TensorFlow-GPU 1.14.0
Opencv-Python 4.1.2.30
Numpy 1.16.0
Pillow 4.3.0
PyYaml 3.13
tqdm N/A

2.1.1 Ubuntu 16.04 下的 Python 3.7

1)先安裝Python環境(有Python 3.7環境的可以忽略)

sudo apt-get install openssl  
sudo apt-get install libssl-dev
sudo apt-get install libc6-dev gcc  
sudo apt-get install -y make build-essential zlib1g-dev libbz2-dev libreadline-dev $ libsqlite3-dev wget curl llvm tk-dev 
wget https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tgz
tar -vxf Python-3.7.6.tar.xz
cd Python-3.7.6
./configure --prefix=/usr/local  --enable-shared
make -j8
sudo make install -j8

經過上面指令就安裝好Python3.7環境了,如果提示找不到libpython3.7m.so.1.0就到/usr/local/lib路徑下將該文件複製一份到/usr/lib和/usr/lib64路徑下。
2)安裝相關依賴(這一步Windows和Linux通用)
可以直接在項目路徑下執行pip3 install -r requirements.txt安裝所有依賴,注意這一步是安裝在全局Python環境下的,強烈建議使用虛擬環境進行項目間的環境隔離,如Virtualenv或Anaconda等等。
我一般使用的是Virtualenv,有修改代碼需要的,建議安裝PyCharm作為Python IDE

virtualenv -p /usr/bin/python3 venv # venv is the name of the virtual environment.
cd venv/ # venv is the name of the virtual environment.
source bin/activate # to activate the current virtual environment.
cd captcha_trainer # captcha_trainer is the project path.
pip3 install -r requirements.txt

2.1.2 Ubuntu 16.04 下的 CUDA/cuDNN

網上看到過很多教程,我自己也部署過很多次,Ubuntu 16.04遇到的坑還是比較少的。14.04支持就沒那麼好,如果主板不支持關閉SecureBoot的話千萬不要安裝Desktop版,因為安裝好之後一定會無限循環在登陸界面無法進入桌面。
網上教程說要加驅動黑名單什麼的我直接跳過了,親測沒那個必要。就簡單的幾步:
1. 下載好安裝包
注意下載runfile類型的安裝包,deb安裝會自動安裝默認驅動,極有可能導致登陸循環
NVIDIA 驅動下載:https://www.geforce.cn/drivers
CUDA 下載地址:https://developer.nvidia.com/cuda-downloads
cuDNN 下載地址:https://developer.nvidia.com/cudnn (需要註冊NVIDIA賬號且登陸,下載deb安裝包)

2. 關閉圖形界面
Ctrl+alt+F1進入字符界面,關閉圖形界面

sudo service lightdm stop

3. 安裝Nvidia Driver

命令中的版本自己對應下載的版本改,在上面的下載地址根據自己的顯卡型號下載最新版,切記是runfile格式的安裝包。

sudo chmod a+x NVIDIA-Linux-x86_64-384.90.run //獲取執行權限
sudo ./NVIDIA-Linux-x86_64-384.90.run –no-x-check –no-nouveau-check –no-opengl-files //安裝驅動

安裝成功以後使用以下命令驗證,如果显示顯卡信息則表示安裝成功

nvidia-smi

4. 安裝CUDA

1)先安裝一些系統依賴庫

sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev
  1. 執行安裝程序,按指示無腦繼續就好了,如果提示是否安裝驅動選不安裝。
sudo sh cuda_9.0.176_384.81_linux.run

安裝完如果環境變量沒配上去,就寫到 ~/.bashrc 文件的尾部

export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

然後在終端執行 sudo ldconfig更新,安裝完畢就可以重啟機器重啟圖形界面了。

sudo service lightdm start

2.1.3 Windows 系統

在Windows其實簡單很多,只要到官網下載安裝包無腦安裝就可以了,下載連接同Ubuntu,先安裝Python,顯卡驅動,CUDA,然後下載對應的cuDNN替換到對應路徑即可。

3 使用

在訓練之前,有不少群友經常問我“訓練4位數英文数字需要多少樣本?”諸如此類的問題,我這裏統一做個回復,樣本數量主要是看樣本的特徵複雜度而定。

這裏可以提供幾個參考依據: 是否變形?是否旋轉?是否有複雜背景干擾?是否多種字體?字符集(分類數)多大?位數(標籤數)多少?

  1. 一般簡單的幾百個樣本(需要自行調整 驗證集大小 和 驗證批次大小 )即可。
  2. 稍微複雜的幾千個樣本一般都能搞定。
  3. 特別複雜的幾萬樣本起。
  4. 中文這種幾千個分類的一般十萬起。

注:只準備一百個不到樣本的親們,千萬不要嘗試訓練測試,因為根本跑不起來。

入手的第一步環境搭建好了,那就是準備跑代碼了,還是有幾個必要的條件,巧婦難為無米之炊,首先,既然是訓練,要先有訓練集,有一個新手嘗鮮的訓練集,是mnist手寫識別的例子,可以在騰訊雲下載:https://share.weiyun.com/5pzGF4V
,現在萬事俱備,只欠東風。

3.1 定義一個模型

本項目基於參數化配置,不需要改動任何代碼,可以通過可視化界面操作訓練幾乎任何字符型圖片驗證碼。訓練框架界面可以大致劃分為幾個部分:

  1. Neural Network – 神經網絡區
  2. Project Configuration – 項目配置區
  3. Sample Source – 樣本源配置區
  4. Training Configuration – 訓練配置區
  5. Buttons – 功能控制區

依此類推的訓練配置的步驟如下:

  1. 神經網絡區 的配置項看起來很多,對於新手來說,可以直接使用默認的配置:CNNX+GRU+CTC+C1組合(CNN前置網絡+GRU+CTC+單通道)。
  2. 項目配置區 的配置項在網絡選好之後配置項目名,按回車或者點擊空白處確認。
  3. 樣本源配置區 的配置項用來配置樣本源的路徑,訓練樣本是根據此路徑進行打包成TFRecords格式,驗證樣本可以不指定,使用[Validation Set Num]參數隨機從訓練集總抽樣成驗證集。
  4. 訓練配置區 的配置項負責定義訓練完成的條件如:結束準確率,結束COST,結束Epochs,批次大小
  5. 功能控制區 的配置項,設置完上面步驟,先點擊[Make Dataset] 打包樣本,再點擊[Start Training]開始訓練。

以下部分有基礎的讀者們可以了解一下:

如若使用CrossEntropy作為解碼器需要注意標籤數LabelNum和圖片尺寸需要滿足的關係,因為網絡為多標籤而設計(一般的多標籤採用直接連接多個分類器),卷積層的輸出 outputs 經過了以下變換:

Reshape([label_num, int(outputs_shape[1] / label_num)])

為了保證運算 int(outputs_shape[1] / label_num) 能夠取得正整數,也意味着他們之間存在某種關係,對於CNN5+Cross Entropy的網絡結構,Conv2D層的步長皆為1,那麼需要保證以下關係成立:

\[mod(\frac{輸入寬度\times輸入高度\times輸出層參數}{池化步長^{池化層數}\times標籤數})= 0 \]

所以有時候需要Resize網絡輸入的Shape

網絡 池化步長^池化層數 輸出層參數
CNN5 16 64
CNNX 8 64
ResNet50 16 1024
DenseNet 32 2048

例如使用CNN5+CrossEntropy組合,則輸入寬度與輸入高度需要滿足:

\[mod(\frac{輸入寬度\times輸入高度\times64}{16\times標籤數})= 0 \]

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

同理如果CNN5+RNN+CTC,卷積層之後的輸出經過以下變換:

Reshape([-1, outputs_shape[2] * outputs_shape[3]])

原輸出(batch_size, outputs_shape[1], outputs_shape[2], outputs_shape[3]),RNN層的輸入輸出要求為(batch, timesteps, num_classes),為了接入RNN經過以上操作,那麼又引出一個Time Step的概念,所以timesteps的值也是 outputs_shape[1],而CTC Loss要求的輸入為 [batch_size, frames, num_labels],若是 timesteps 小於標籤數則無法計算損失,也就無法找損失函數中找到極小值,梯度何以下降。timesteps 最合理的值一般是標籤數的2倍,為了達到目的,也可以通過Resize網絡輸入的Shape解決,一般情況timesteps直接關聯於圖片寬度,大多情況只要按比例放大寬度即可。

ExtractRegex 參數:

注意:如果訓練集的命名格式和我提供的新手訓練集不一樣,請根據實際情況修改ExtractRegex的正則表達式。目前只支持在yaml配置文件中直接修改,尚未提供GUI界面修改的支持。 DatasetPath 和SourcePath參數允許多個路徑,這種操作適用於需要將多種樣本訓練為一個模型,或者希望訓練一套通用泛化模型的人。
字符集Category其實大多數情況下不需要修改,一般的圖形驗證碼離不開数字和英文,而且一般來說是大小寫不敏感的,不區分大小寫,因為打碼平台收集的訓練集質量參差不齊,有些大寫有些小寫,不如全部統一為小寫,默認ALPHANUMERIC_LOWER則會自動將大寫的轉為小寫,字符集可定製化很靈活,除了配置備註上提供的幾種類型,還可以訓練中文,自定義字符集用list表示,示例如下:

Category: ['常', '世', '寧', '慢', '南', '制', '根', '難']

如果是單標籤分類,可以配合LabelNum=1,例如:

Category: ["航母", "雨靴", "毛線", "安全帽", "調色板", "海鷗", "日曆", "網球拍", ......]

其文件名示例:航母_1231290424123.png

如果是多標籤分類,可以配合LabelSplit=&,例如:

Category: ["航母", "雨靴", "毛線", "安全帽", "調色板", "海鷗", "日曆", "網球拍", ......]

其文件名示例:航母&雨靴&毛線_1231290424123.png

可以自己根據收集訓練集的實際字符集使用率來定義,也可以無腦網上找3500常用字來訓練,注意:中文字符集一般比数字英文大很多,剛開始收斂比較慢,需要更久的訓練時間,也需要更多的樣本量,請量力而行

形如上圖的圖片能輕鬆訓練到95%以上的識別率。
ImageWidth、ImageHeight只要和當前圖片尺寸匹配即可,其實這裏的配置主要是為了方便後面的部署智能策略。

Pretreatment參數:

該參數是用來做圖片預處理的,例如形如以下的GIF動圖,

可以使用ConcatFrames參數選取幀對兩幀進行水平拼接,適用於處理滾動型GIF,而閃爍型GIF可以使用BlendFrames參數進行融合。

3.2 開始訓練

  1. 經過 採集標註樣本形如 xxx_隨機數.png
  2. 打包樣本
    通過GUI界面的 [Make Dataset] 或者 make_dataset.py 直接打包。
    注意:使用源碼運行本項目的功能模塊需要具備一定的語言基礎,參數修改的部分和示例已預留好,盡量不修改核心類或函數的代碼以免出現錯誤。

按照上面的介紹,配置只要修改極少數的參數對應的值,就可以開啟正式的訓練之旅了,具體操作如下:
可以直接使用 PyCharm 的 Run,執行 trains.py,也可以在激活Virtualenv下使用終端亦或在安裝依賴的全局環境下執行,但本文建議全程使用GUI界面進行操作,使用GUI僅需啟動 app.py 即可。

python3 trains.py

剩下的就是等了,看過程,等結果。
正常開始訓練的模樣應該是這樣的:

訓練結束會在項目的out路徑下生成一個包含pb文件的graph目錄和包含yaml文件的model目錄,下面該到部署環節了。

3.3 部署

真的很有必要認真的介紹一下部署項目,比起訓練,這個部署項目傾注了筆者更多的心血,為什麼呢?
項目地址:https://github.com/kerlomz/captcha_platform

如希望將本系統集成於自己的項目中的可以參考python-sdk的使用:
https://pypi.org/project/muggle-ocr/
該項目的核心基於 captcha_platform/sdk/pb/sdk.py 可以根據需要自行修改,抑或直接使用MuggleOCR 調用訓練框架生產的模型。(具體調用方法可點擊上面鏈接有對應的文檔介紹)

編譯版:https://github.com/kerlomz/captcha_platform/releases,使用編譯版無需安裝Python和TensorFlow環境。

真的值得了解的幾點

  1. 同時管理多個模型,支持模型熱拔插
  2. 靈活的版本控制
  3. 支持批量識別
  4. 服務智能路由策略

首先筆者重寫了TensorFlow的Graph會話管理,設計會話池,允許同時管理多模型,實現多模型動態部署方案。

1) 訓練好的 pb模型只要放在部署項目的graph路徑下,yaml模型配置文件放在model, 即可被服務發現並加載。(用SDK調用時,兩者置於同一目錄下)

2) 如果需要卸載一個正在服務的模型,只需要在model中刪除該模型的yaml配置文件,在graph中刪除對應的pb模型即可。

3) 如果需要更新一個已經服務中的模型,只需修改新版的模型yaml配置文件的版本號高於原模型的版本號,按先放pb後放yaml的順序,服務便會自動發現新版的模型並加載使用,舊的模型將因版本低於新版模型不會被調用,可以按照上述的卸載方法卸載已被棄用的模型釋放內存。
上面的操作中無需重啟服務,完全的無縫切換

其次,一套服務想要服務於各式各樣的圖像識別需求,可以定義一套策略,訓練時將所有尺寸一樣的圖片訓練成一個模型,服務根據圖片尺寸自動選擇使用哪個模型,這樣的設計使定製化和通用性共存,等積累到一定多樣的訓練集時可以將所有的訓練集合到一起訓練一個通用模型,亦可以彼此獨立,每個模型的疊加僅僅增加了少量的內存或顯存,網上的方案大多是不同的模型單獨部署一套服務,每個進程加載了一整套TensorFlow框架勢必是過於龐大和多餘的。

用到批量識別需求的人相對少很多這裏就不展開介紹了。但是這裏給出一個12306的例子:

FieldParam:
  CorpParams: [
    {
      "start_pos": [118, 0],
      "interval_size": [0, 0],
      "corp_num": [1, 1],
      "corp_size": [60, 30]
    },
    {
      "start_pos": [5, 40],
      "interval_size": [5, 5],
      "corp_num": [4, 2],
      "corp_size": [66, 66]
    }
  ]
  OutputCoord: True

該參數可以用於大圖的裁剪組成一批小圖作為一個批次的輸入,改用法可以避免多次調用。

但是識別項目提供了多套可選的服務有:gRPC,Flask,Tornado,Sanic,其中Flask和Tornado提供了加密接口,類似於微信公眾號開發接口的SecretKey和AccessKey接口,感興趣的可以在demo.py中閱讀調用源碼了解。

部署的使用可以經過package.py編譯為可執行文件,這樣可以免去更換機器環境安裝的煩惱,部署項目安裝流程同訓練項目,項目中提供的requirements.txt已經將所需的依賴都列清楚了,強烈建議部署項目安裝cpu版TensorFlow。

本項目部署推薦使用Tornado版,功能最齊全,性能最為穩定。

Linux:

  1. Tornado:
# 端口 19952
python3 tornado_server.py
  1. Flask
# 方案1,裸啟動, 端口 19951
python flask_server.py 
# 方案2,使用gunicorn,端口 5000
pip install gunicorn 
gunicorn -c deploy.conf.py flask_server:app
  1. Sanic:
# 端口 19953
python3 sanic_server.py
  1. gRPC:
# 端口 50054
python3 grpc_server.py
  1. 編譯版(基於Tornado)
# 前台運行
./captcha_platform_tornado
#後台運行
nohup ./captcha_platform_tornado &

Windows:
Windows平台下都是通過python3 xxx_server.py啟動對應的服務,注意,Tornado、Flask、Sanic的性能在Windows平台都大打折扣,gRPC是Google開源的RPC服務,有較為優越的性能。
編譯版直接運行編譯后的exe可執行文件即可。

3.4 調用/測試

1. Tornado服務:

請求地址 Content-Type 參數形式 請求方法
http://localhost:19952/captcha/v1 application/json JSON POST

具體參數:

參數名 必選 類型 說明
image Yes String Base64 編碼
model_name No String 模型名,yaml配置中可綁定
need_color No String 顏色過濾,black/red/blue/yellow/green/white
output_split No String 多標籤分割字符

請求為JSON格式,形如:{“image”: “base64編碼后的圖像二進制流”}

返回結果:

參數名 類型 說明
message String 識別結果或錯誤消息
code String 狀態碼
success String 是否請求成功

該返回為JSON格式,形如:{“message”: “xxxx”, “code”: 0, “success”: true}

2. Flask服務:

請求地址 Content-Type 參數形式 請求方法
http://localhost:19951/captcha/v1 application/json JSON POST

請求參數和返回格式同上

3. Sanic服務:

請求地址 Content-Type 參數形式 請求方法
http://localhost:19953/captcha/v1 application/json JSON POST

請求參數和返回格式同上

4. gRPC服務:
需要安裝依賴,grpcio、grpcio_tools和對應的grpc.proto文件,可以直接從項目中的示例代碼demo.py中提取。

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./grpc.proto

grpcio、grpcio_tools 是根據 grpc.proto 使用上述命令生成的。

class GoogleRPC(object):

    def __init__(self, host: str):
        self._url = '{}:50054'.format(host)
        self.true_count = 0
        self.total_count = 0

    def request(self, image, model_type=None, model_site=None):

        import grpc
        import grpc_pb2
        import grpc_pb2_grpc
        channel = grpc.insecure_channel(self._url)
        stub = grpc_pb2_grpc.PredictStub(channel)
        response = stub.predict(grpc_pb2.PredictRequest(
            image=image, split_char=',', model_type=model_type, model_site=model_site
        ))
        return {"message": response.result, "code": response.code, "success": response.success}

if __name__ == '__main__':
    result = GoogleRPC().request("base64編碼后的圖片二進制流")
    print(result)

3.5 奇技淫巧

該項目還可以直接用於識別帶顏色的驗證碼,部署項目middleware/impl/color_extractor.py基於k-means實現了顏色分離模塊,可用於處理如下形式的驗證碼:

還有一種方案是同時預測驗證碼和每個字符對應的顏色,不過這需要修改現有的神經網絡進行支持,在最後一層修改為雙輸出,一個輸出顏色,一個輸出對應字符,這對於樣本標註的要求較高,也提高的成本,所以如果能用無限生成樣本,那問題就迎刃而解了,比如上圖,筆者就寫了樣本生成代碼,感興趣的可以移步:
https://www.jianshu.com/p/da1b972e24f2
其實還有很多很多技巧,例如,用生成的樣本代替訓練集,其實網上的圖片驗證碼大多是採用開源的,稍作修改而已,大多數情況都能被近似生成出來,上述展示的驗證碼圖片不代表任何實際的網站,如有雷同,純屬巧合,該項目只能用於學習和交流用途,不得用於非法用途。

後記

如果文章描述不夠詳盡或需要技術支持的,可以加群 857149419 諮詢,或在開源項目中提issue,很榮幸能為開源社區貢獻綿薄之力。

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

※台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

Postman+Newman+Git+Jenkins接口自動化測試_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

一、Postman

   1、創建Collection,在Collection中創建接口請求,如下圖所示。

   2、編寫接口對應的斷言Test和Pre-request Script,如下圖所示。

   3、配置接口對應的環境變量或全局變量。

   4、導出接口集合Collection和導出對應接口的環境變量,導出生成的是JSON文件。

  如下圖所示,導出生成的接口集合和環境變量JOSN文件

  如大家對以上Postman的操作疑惑的,可參考之前我寫過的Postman(一)兩篇博客。

 二、Newman

 1、簡介

  newman是Postman的命令集合運行器,可以通過它直接從命令行運行和測試Postman,以便後續在持續集成服務器構建。

  Newman 是 Postman 推出的一個 nodejs 庫,直接來說就是 Postman 的json文件可以在命令行執行的插件。

  官方文檔:https://www.npmjs.com/package/newman

2、安裝

  淘寶鏡像全局安裝newman的命令:

1 npm install -g newman --registry=https://registry.npm.taobao.org

   安裝成功,如下圖所示,显示newman成功安裝好的版本號。

   驗證newman是否安裝成功,查看newman版本號命令:newman -version

 3、常用Newman語法

  列舉一些常用的Newman命令語法。

1、運行命令 newman run URL連接   或   newman run 集合文件名
2、-e,- environment <path>    指定Postman環境的URL或路徑。
3、-g,–globals    指定全局變量的文件地址或url 
4、-d,—-iteration-data   指定用於迭代的數據源文件路徑
5、-n,–iteration-count     指定迭代次數
6、–export-globals     導出全局環境變量
7、–timeout (ms)     設置整個集合運行完成執行的時間
8、–export-collection     導出集合文件
9、–timeport-request (ms)    指定等待請求返迴響應的時間
10、–timeout-script (ms)    指定等待腳本執行完成的時間

  我們可通過cmd終端查看newman run運行時可以所帶的相關參數。newman run -h查看所有相關參數語法,如下圖所示。

 4、Newman實例

  該newman run命令允許您指定要運行的集合。您可以輕鬆地從Postman App中將Postman Collection導出為json文件,並使用Newman運行它。

1 newman run examples/sample-collection.json

  例:運行Postman導出的接口集合及環境變量,打開cmd,定位至Json文件目錄,輸入newman運行命令。

1 newman run Test.postman_collection.json -e mukeOnline.postman_environment.json

  run運行接口集合的命令,-e 帶入接口集合的環境變量

  我們在cmd終端定位至接口集合和環境變量的JSON文件目錄,運行命令行結果如下圖:

 

   由上面的運行可看出在cmd終端運行的結果不太方便測試人員的查看與測試報告的形成。那麼我們如何利用newman命令來生成輸出測試報告文件?

指定測試報告的導出格式:目前支持四種格式:cli,json,html,junit

  –reporter-{{reporter-name}}-{{reporter-option}}

支持同時導出多種測試報告格式

  JSON –reporter-json-export <path>

  HTML –reporter-html-export <path>

  JUNIT/XML –reporter-junit-export <path>

  我們還拿上面的集合和環境變量來舉例,生成HTML報告時需要安裝html套件,命令行中執行:

1 npm install -g newman-reporter-html --registry=https://registry.npm.taobao.org

  安裝newman-reporter-html,如下圖

   運行命令,即可在文件目錄下查看生成的HTML文件形式的測試報告。

1 newman run Test.postman_collection.json -e mukeOnline.postman_environment.json -r html --reporter-html-export report.html

  生成的HTML測試報告文件,如下圖所示。

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

  我們可以看出用上面HTML生成的報告樣式比較簡單,怎麼讓我們的報告顯得高大上呢?那麼我們就得使用Htmlextra套件。

  首先安裝使用htmlextra套件生成比較高級的html報告,使用方法和html的相同,需要安裝htmlextra,命令行中輸入:

1 npm install -g newman-reporter-htmlextra

  安裝成功,如下圖所示。

   我們把上面的接口集合用例再執行一下,看看這個測試報告的效果如何。newman運行命令如下。

1 newman run Test.postman_collection.json -e mukeOnline.postman_environment.json -r htmlextra --reporter-html-export htmlReport.html

  測試報告結果如下圖,可看出報告的樣式比之前好多了。

三、Git

  下載GitExtensions和Git,將Postman導出的json文件上傳至github上,拉取下來可進行迭代。這裏不過多說明,可自行查找資料。

 四、Jenkins

1、簡介

  Jenkins是一個開源的、可擴展的持續集成、交付、部署(軟件/代碼的編譯、打包、部署)的基於web界面的平台。允許持續集成和持續交付項目,無論用的是什麼平台,可以處理任何類型的構建或持續集成。

2、安裝

  下載Jenkins:https://www.jenkins.io/download/,下載后並安裝。

  安裝時出現的問題:

(1)問題一:注意安裝Jenkins之前先安裝tomcat和Java jdk,否進入不了Jenkins頁面。當安裝好tomcat和java jdk后,Jenkins頁面仍卡着不動。

  解決方法:查看並修改配置文件設置,重啟tomcat服務。進入Jenkins根目錄,打開hudson.model.UpdateCenter.xml文件,將文件中的url,修改為http://updates.jenkins.io/update-center.json,即去掉https中的s或更改為

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json,修改好后,重新啟動tomcat服務即可。

(2)問題二:進入Jenkins頁面,到配置安裝組件時,出現No such plugin: cloudbees-folder的報錯。

  解決方法:打開鏈接“http://ftp.icm.edu.pl/packages/jenkins/plugins/cloudbees-folder/”,在最下面找到並打開“latest”目錄。將目錄中的“cloudbees-folder.hpi”下載下來後放在“jenkins安裝目錄\war\WEB-INF\detached-plugins”文件夾中。在“控制面板-管理工具-服務”中找到“jenkins”服務,重啟服務,重啟tomcat服務。重新訪問http://localhost:8080/即可。

 

3、Jenkins配置Git與newman

  新建Item,選擇Freestyle project

  任務的Git地址、證書與分支的相關配置

  設置構建newman運行命令

1 newman run Test.postman_collection.json -e mukeOnline.postman_environment.json

   設置好后保存,點擊Build Now

   生成構建結果

   構建後生成Junit報告,Git上新建空xml文件,nweman運行命令指向該xml文件。構建前運行命令修改為

newman run Test.postman_collection.json -e mukeOnline.postman_environment.json -r junit --reporter-junit-export newman\xmlReport.xml

  運行結果測試報告可在Test Result中可查看。

 

   設置定時任務

  Build periodically 每天上午10點構建一次  H 10 * * *,如下圖所示。

  以上就是簡單說明了一下Postman+Newman+Git+Jenkins接口自動化測試的設計與執行。後期有待優化與完善,請各位看官多多指教。

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

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

怎麼才能識破賣車銷售詭計 底價提車回家?_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

對於一些包牌價比我們自己完成一系列上牌過程的便宜,是因為它們在購車發票上進行“少開”,購置稅是按發票價進行收取,它們只要少開數額就可以少交購置稅以及保險,中間的差價就是它們的盈利額。但是對於我們消費者來說若是車輛發生重大事故、泡水、自燃或者盜搶,保險公司是按發票價為基準進行賠償,這無疑增加了一部分風險。

前言

買車的過程,其實就是一個和銷售鬥智斗勇的過程,稍有不留心就很有可能讓你沒了幾千元,或者是買到一些沒用的東西,而且整個購車過程都是那麼複雜,我們應該如何應對呢?還有我們在購車的時候怎樣才能最低價買到自己心儀的汽車呢?

盡量選擇全款購車而不是貸款購車

全款購車的時候雙方就相當交換貨物而已,手續可以達到最簡化,僅僅需要拿到車輛合格證、發票就可以完成這次交易。但是貸款購車的話4S店多半強制讓你在店裡購買車險以及上牌,而且對於原本僅僅是500元左右的上牌費用,在他們手上就成了2000以上的費用了,並且也有個人資料泄露的危險。保險方面也要比起自行購買要高400-1000元。所以說全款購車是比較省心省錢的。

選擇提裸車而不是落地價

很多4S美而言之“一條龍服務”,會為用戶提供上牌、保險等一系列服務,但是正正和上一理由一樣,這樣會給你增加額外的費用。對於一些包牌價比我們自己完成一系列上牌過程的便宜,是因為它們在購車發票上進行“少開”,購置稅是按發票價進行收取,它們只要少開數額就可以少交購置稅以及保險,

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

中間的差價就是它們的盈利額。但是對於我們消費者來說若是車輛發生重大事故、泡水、自燃或者盜搶,保險公司是按發票價為基準進行賠償,這無疑增加了一部分風險。

不選購精品

在購車以後,銷售基本會“乘勝追擊”向你推薦各種精品,例如汽車貼膜、迎賓踏板、發動機下護板等,而且這些產品的價格基本都是外面的一倍有餘,會美而言之“原裝件”,但實際上汽車廠商並沒有做到這類型的精品,我們在某寶上只要搜索“原裝件”就能找到很多打着“原廠”符號的精品,並且會發現這些產品都是一些小作坊的產品。

對比各類型的金融產品

對於選擇貸款購車的人來說,利息以及手續費都是一筆不小的開支,而事實上每一個金融機構的利息以及手續費都是不一樣的,所以我們要合理對比,有些金融機構甚至是有着廠家貼息的,這樣無疑能給你省下上萬元,這種貼息的品牌多半以上汽、日產為主,具體還是要多方面比較、詢問,選擇合適的金融產品。

明確購車所需費用

對於一些無良的4S店,他們總是給你設置各種各樣的雜費,貸款手續費、出庫費層出不窮。所以我們在買車的時候一定要白紙黑字明確各方面的費用,對於所贈送的東西也要將其記錄下來並雙方簽字,這樣就不會出現突然收取不該有的費用或者是該有的優惠、贈品沒有落實的情況。

所以買車過程講究的就是不怕麻煩、細心、謹慎以及理性,這樣才能以最低價格買到自己心儀的汽車,而對於不熟悉流程或者是汽車的新手朋友最好還是尋求一位“老司機”一同購車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。