java併發編程 –併發問題的根源及主要解決方法

目錄

  • 併發問題的根源在哪
    • 緩存導致的可見性
    • 線程切換帶來的原子性
    • 編譯器優化帶來的有序性
  • 主要解決辦法
    • 避免共享
    • Immutability(不變性)
    • 管程及其他工具

併發問題的根源在哪

首先,我們要知道併發要解決的是什麼問題?併發要解決的是單進程情況下硬件資源無法充分利用的問題。而造成這一問題的主要原因是CPU-內存-磁盤三者之間速度差異實在太大。如果將CPU的速度比作火箭的速度,那麼內存的速度就像火車,而最慘的磁盤,基本上就相當於人雙腿走路。

這樣造成的一個問題,就是CPU快速執行完它的任務的時候,很長時間都會在等待磁盤或是內存的讀寫。

計算機的發展有一部分就是如何重複利用資源,解決硬件資源之間效率的不平衡,而後就有了多進程,多線程的發展。並且演化出了各種為多進程(線程)服務的東西:

  • CPU增加緩存機制,平衡與內存的速度差異
  • 增加了多個概念,CPU時間片,程序計數器,線程切換等,用以更好得服務併發場景
  • 編譯器的指令優化,希望在內部充分利用硬件資源

但是這樣一來,也會帶來新的併發問題,歸結起來主要有三個。

  • 由於緩存導致的可見性問題
  • 線程切換帶來的原子性問題
  • 編譯器優化帶來的有序性問題

我們分別介紹這幾個:

緩存導致的可見性

CPU為了平衡與內存之間的性能差異,引入了CPU緩存,這樣CPU執行指令修改數據的時候就可以批量直接讀寫CPU緩存的內存,一個階段后再將數據寫回到內存。

但由於現在多核CPU技術的發展,各個線程可能運行在不同CPU核上面,每個CPU核各有各自的CPU緩存。前面說到對變量的修改通常都會先寫入CPU緩存,再寫回內存。這就會出現這樣一種情況,線程1修改了變量A,但此時修改后的變量A只存儲在CPU緩存中。這時候線程B去內存中讀取變量A,依舊只讀取到舊的值,這就是可見性問題。

線程切換帶來的原子性

為了更充分得利用CPU,引入了CPU時間片時間片的概念。進程或線程通過爭用CPU時間片,讓CPU可以更加充分得利用。

比如在進行讀寫磁盤等耗時高的任務時,就可以將寶貴的CPU資源讓出來讓其他線程去獲取CPU並執行任務。

但這樣的切換也會導致問題,那就是會破壞線程某些任務的原子性。比如java中簡單的一條語句count += 1。

映射到CPU指令有三條,讀取count變量指令,變量加1指令,變量寫回指令。雖然在高級語言(java)看來它就是一條指令,但實際上確是三條CPU指令,並且這三條指令的原子性無法保證。也就是說,可能在執行到任意一條指令的時候被打斷,CPU被其他線程搶佔了。而這個期間變量值可能會被修改,這裏就會引發數據不一致的情況了。所以高併發場景下,很多時候都會通過鎖實現原子性。而這個問題也是很多併發問題的源頭。

編譯器優化帶來的有序性

因為現在程序員編寫的都是高級語言,編譯器需要將用戶的代碼轉成CPU可以執行的指令。

同時,由於計算機領域的不斷髮展,編譯器也越來越智能,它會自動對程序員編寫的代碼進行優化,而優化中就有可能出現實際執行代碼順序和編寫的代碼順序不一樣的情況。

而這種破壞程序有序性的行為,在有些時候會出現一些非常微妙且難以察覺的併發編程bug。

舉個簡單的例子,我們常見的單例模式是這樣的:

public class Singleton {
 
 private Singleton() {}

 private static Singleton sInstance;

 public static Singleton getInstance() {

    if (sInstance == null) {	//第一次驗證是否為null
      synchronized (Singleton.class) {   //加鎖
        if (sInstance == null) {	  //第二次驗證是否為null
          sInstance = new Singleton();  //創建對象
                 }
             }
         }
    return sInstance;
    }

}

即通過兩段判斷加鎖來保證單例的成功生成,但在極小的概率下,可能會出現異常情況。原因就出現在sInstance = new Singleton();這一行代碼上。這行代碼,我們理解的執行順序應該是這樣:

  1. 為Singleton象分配一個內存空間。
  2. 在分配的內存空間實例化對象。
  3. 把Instance 引用地址指向內存空間。

但在實際編譯的過程中,編譯器有可能會幫我們進行優化,優化完它的順序可能變成如下:

  1. 為Singleton對象分配一個內存空間。
  2. 把instance 引用地址指向內存空間。
  3. 在分配的內存空間實例化對象。

按照優化完的順序,當併發訪問的時候,可能會出現這樣的情況

  1. A線程進入方法進行第1次instance == null判斷。
  2. 此時A線程發現instance 為null 所以對Singleton.class加鎖。
  3. 然後A線程進入方法進行第2次instance == null判斷。
  4. 然後A線程發現instance 為null,開始進行對象實例化。
  5. 為對象分配一個內存空間。
    6.把Instance 引用地址指向內存空間(而就在這個指令完成后,線程B進入了方法)。
  6. B線程首先進入方法進行第1次instance == null判斷。
  7. B線程此時發現instance 不為null ,所以它會直接返回instance (而此時返回的instance 是A線程還沒有初始化完成的對象)

最終線程B拿到的instance 是一個沒有實例化對象的空內存地址,所以導致instance使用的過程中造成程序錯誤。解決辦法很簡單,可以給sInstance對象加上一個關鍵字,volatile,這樣編譯器就不會亂優化,有關volatile的具體內容後續再細說。

主要解決辦法

通過上面的介紹,其實可以歸納無論是CPU緩存,線程切換還是編譯器優化亂序,出現問題的核心都是因為多個線程要併發讀寫某個變量或併發執行某段代碼。那麼我們可以控制,一次只讓一個線程執行變量讀寫就可以了,這就是互斥

而在某些時候,互斥還不夠,還需要一定的條件。比如一個生產者一個消費者併發,生產者向隊列存東西,消費者向隊列拿東西。那麼生產者寫的時候要保證存的時候隊列不是滿的,消費者要保證拿的時候隊列非空。這種線程與線程間需要通信協作的情況,稱為同步同步可以說是更複雜的互斥

既然知道了併發編程的根源以及同步和互斥,那我們來看看有哪些解決的思路。其實一共也就三種:

  • 避免共享
  • Immutability(不變性)
  • 管程及其他工具

下面我們分別說說這三種方案的優缺點

避免共享

我們先來說說避免共享,其實避免共享說是線程本地存儲技術,在java中指的一般就是Threadlocal。ThreadLocal會為每個線程提供一個本地副本,每個線程都只會修改自己的ThreadLocal變量。這樣一來就不會出現共享變量,也就不會出現衝突了。

其實現原理是在ThreadLocal內部維護一個ThreadLocalMap,每次有線程要獲取對應變量的時候,先獲取當前線程,然後根據不同線程取不同的值,典型的以空間換時間。

所以ThreadLocal還是比較適用於需要共享資源,且資源佔用空間不大的情況。比如一些連接的session啊等等。但是這種模式應用場景也較為有限,比如需要同步情況就難以勝任。

Immutability(不變性)

Immutability在函數式中用得比較多,函數式編程的一個主要目的是要寫出無副作用的代碼,有關什麼是無副作用可以參考我以前的文章Scala函數式編程指南(一) 函數式思想介紹。而無副作用的一個主要特點就是變量都是Immutability即不可變的,即創建對象后不會再修改對象,比如scala默認的變量和數據結構都是不可變的。而在java中,不變性變量即通過final修飾的變量,如String,Long,Double等類型都是Immutability的,它們的內部實現都是基於final關鍵字的。

那這又和併發編程有什麼關係呢?其實啊,併發問題很大部分原因就是因為線程切換破壞了原子性,這又導致線程隨意對變量的讀寫破壞了數據的一致性。而不變性就不必擔心這個問題,因為變量都是不變,不可寫只能讀的。在這種編程模式下,你要修改一個變量,那麼只能新生成一個。這樣做的好處很明顯,但壞處也是顯而易見,那就是引入了額外的編程複雜度,喪失了代碼的可讀性和易用性。

因為如此,不變性的併發解決方案其實相對而已沒那麼廣泛,其中比較有代表性的算是Actor併發編程模型,我以前也有討論過,有興趣可以看看Actor模型淺析 一致性和隔離性,這種編程模型和常規併發解決方案有很顯著的差異。按我的了解,Acctor模式多用在分佈式系統的一些協調功能,比如維持集群中多個機器的心跳通信等等。如果在單機併發環境下,還是下面要介紹的管程類工具才是利器。

管程及其他工具

其實最早的操作系統中,解決併發問題用的是信號量,信號量通過兩個原子操作wait(S),和signal(S)(俗稱P,V操作)來實現訪問資源互斥和同步。比如下面這個小例子:

//整型信號量定義
int S;

//P操作
wait(S){
  while(S<=0);
  S--;
}

//V操作
signal(S){
  S++;
}

雖然信號量方便有效,但信號量要對每個共享資源都實現對應的P和V操作,這使得併發編程中可能要出現大量的P,V操作,並且這部分內容難以抽象出來。

為了更好地實現同步互斥,於是就產生了管程(即Monitor,也有翻譯為監視器),值得一提的是,管程也有幾種模型,分別是:Hasen模型,Hoare模型和MESA模型。其中MESA模型應用最廣泛,java也是參考自MESA模型。這裏簡單介紹下管程的理論知識,這部分內容參考自進程同步機制—–為進程併發執行保駕護航,希望了解更多管程理論知識的童鞋可以看看。

我們來通過一個經典的生產-消費隊列來解釋,如下圖

我們先解釋下圖中右半部分的內容,右上角有一個等待調用的線程隊列,管程中每次只能有一個線程在執行任務,所以多個任務需要等待。然後是各個名詞的意思,生產-消費需要往隊列寫入和取出東西,這裏的隊列就是共享變量對共享資源進行操作稱之為過程(入隊和出隊兩個過程)。而向隊列寫入和取出是有條件的,寫入的時候隊列必須是非滿的,取出的時候隊列必須是非空的,這兩個條件被稱為條件變量

然後再來看看左半部分的內容,假設線程T1讀取共享變量(即隊列),此時發現隊列為空(條件變量之一),那麼T1此時需要等待,去哪裡等呢?去條件變量隊列不能為空對應的隊列中去等待。此時另一個線程T2向共享變量隊列寫數據,通過了條件變量隊列不能滿,那麼寫完后就會通知線程T1。但因為管程的限制,管程中只能有一個線程在執行,所以T1線程不能立即執行,它會回到右上角的線程等待隊列等待(不同的管程模型在這裡是有分歧的,比如Hasen模型是立即中斷T2線程讓隊列中下一個線程執行)。

解釋完這個圖,管程的概念也就呼之欲出了,

hansen對管程的定義如下:一個管程定義了一個數據結構和能力為併發進程所執行(在該數據結構上)的一組操作,這組操作能同步進程和改變管程中的數據。

本質上,管程是對共享資源以及對共享資源的操作抽象成變量和方法,要操作共享變量僅能通過管程提供的方法(比如上面的入隊和出隊)間接訪問。所以你會發現管程其實和面向對象的理念是十分相近的,在java中,主要提供了低層次了synchronized關鍵字和wait(),notify()等方法。同時還提供了高層次的ReenTrantLock和Condition來實現管程模型。

以上~

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

【其他文章推薦】

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

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

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

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

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

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

中國研擬汽車投資管理等新規,新能源智慧車料續受惠

經濟參考報報導,中國 2018 年前 7 個月新能源汽車年增 68.6%,保持持續增長態勢,並成為拉動整體汽車市場成長的重要力量。據中國國家發改委等部門瞭解,目前包括新的汽車投資管理規定等多項政策正在加緊推進,鼓勵技術、模式等創新,未來新能源智慧化汽車將迎來更多利多。

中國發改委表示,目前新的汽車產業投資管理規定已完成向社會公開徵求意見,正在加緊對徵求意見稿進行進一步完善,以期儘快發布。

據了解,中國此次準備發佈的新汽車投資管理辦法被稱為「最嚴燃油車產業政策」,未來新建獨立燃油車項目將被禁止,而現有汽車企業擴大燃油汽車生產能力也要同時滿足上兩個年度汽車產能利用率均高於全行業平均水準、上兩個年度新能源汽車產量佔比均高於全行業平均水準等四個條件。

在此同時,中國官方對於新能源汽車的准入門檻也大幅提高,要求新建的獨立純電動汽車企業專案要有純電動汽車持續開發能力,純電動乘用車建設規模不低於 10 萬輛,以及純電動商用車不低於 5,000 輛;此外,對新建新能源汽車企業的股東也提出了要求。

多位專家和業內人士表示,這將大大推動未來新能源汽車市場的發展,並提高了廠商、投資者,以及消費者對新能源汽車市場的信心。同時,對新能源汽車市場准入門檻的提高也將進一步提升新能源汽車市場的發展品質,推進企業加大在產品和技術方面的投入,實現優勝汰劣。

中國發改委產業協調司副司長蔡榮華表示,未來將進一步推動新能源智慧化汽車產業發展,發改委也將積極推動新能源智慧化汽車創新發展戰略儘快推出,鼓勵技術創新和模式創新,努力打造有利於新能源智慧汽車發展的生態系統和環境。

(本文內容由 授權使用。首圖來源:)

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

【其他文章推薦】

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

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

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

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

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

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

RocketMQ系列(七)事務消息(數據庫|最終一致性)

終於到了今天了,終於要講RocketMQ最牛X的功能了,那就是事務消息。為什麼事務消息被吹的比較熱呢?近幾年微服務大行其道,整個系統被切成了多個服務,每個服務掌管着一個數據庫。那麼多個數據庫之間的數據一致性就成了問題,雖然有像XA這種強一致性事務的支持,但是這種強一致性在互聯網的應用中並不適合,人們還是更傾向於使用最終一致性的解決方案,在最終一致性的解決方案中,使用MQ保證各個系統之間的數據一致性又是首選。

RocketMQ為我們提供了事務消息的功能,它使得我們投放消息和其他的一些操作保持一個整體的原子性。比如:向數據庫中插入數據,再向MQ中投放消息,把這兩個動作作為一個原子性的操作。貌似其他的MQ是沒有這種功能的。

但是,縱觀全網,講RocketMQ事務消息的博文中,幾乎沒有結合數據庫的,都是直接投放消息,然後講解事務消息的幾個狀態,雖然講的也沒毛病,但是和項目中事務最終一致性的落地方案還相距甚遠。包括我自己在內,在項目中,服務化以後,用MQ保證事務的最終一致性,在網上一搜,根本沒有落地的方案,都是侃侃而談。於是,我寫下這篇博文,結合數據庫,來談一談RocketMQ的事務消息到底怎麼用。

基礎概念

要使用RocketMQ的事務消息,要實現一個TransactionListener的接口,這個接口中有兩個方法,如下:

/**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

/**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
LocalTransactionState checkLocalTransaction(final MessageExt msg);

RocketMQ的事務消息是基於兩階段提交實現的,也就是說消息有兩個狀態,prepared和commited。當消息執行完send方法后,進入的prepared狀態,進入prepared狀態以後,就要執行executeLocalTransaction方法,這個方法的返回值有3個,也決定着這個消息的命運,

  • COMMIT_MESSAGE:提交消息,這個消息由prepared狀態進入到commited狀態,消費者可以消費這個消息;
  • ROLLBACK_MESSAGE:回滾,這個消息將被刪除,消費者不能消費這個消息;
  • UNKNOW:未知,這個狀態有點意思,如果返回這個狀態,這個消息既不提交,也不回滾,還是保持prepared狀態,而最終決定這個消息命運的,是checkLocalTransaction這個方法。

當executeLocalTransaction方法返回UNKNOW以後,RocketMQ會每隔一段時間調用一次checkLocalTransaction,這個方法的返回值決定着這個消息的最終歸宿。那麼checkLocalTransaction這個方法多長時間調用一次呢?我們在BrokerConfig類中可以找到,

 /**
  * Transaction message check interval.
  */
@ImportantField
private long transactionCheckInterval = 60 * 1000;

這個值是在brokder.conf中配置的,默認值是60*1000,也就是1分鐘。那麼會檢查多少次呢?如果每次都返回UNKNOW,也不能無休止的檢查吧,

/**
 * The maximum number of times the message was checked, if exceed this value, this message will be discarded.
 */
@ImportantField
private int transactionCheckMax = 5;

這個是檢查的最大次數,超過這個次數,如果還返回UNKNOW,這個消息將被刪除。

事務消息中,TransactionListener這個最核心的概念介紹完后,我們看看代碼如何寫吧。

落地案例

我們在數據庫中有一張表,具體如下:

CREATE TABLE `s_term` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `term_year` year(4) NOT NULL ,
  `type` int(1) NOT NULL DEFAULT '1' ,
  PRIMARY KEY (`id`)
) 

字段的具體含義大家不用管,一會我們將向這張表中插入一條數據,並且向MQ中投放消息,這兩個動作是一個原子性的操作,要麼全成功,要麼全失敗。

我們先來看看事務消息的客戶端的配置,如下:

@Bean(name = "transactionProducer",initMethod = "start",destroyMethod = "shutdown")
public TransactionMQProducer transactionProducer() {
    TransactionMQProducer producer = new
        TransactionMQProducer("TransactionMQProducer");
    producer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    producer.setTransactionListener(transactionListener());
    return producer;
}

@Bean
public TransactionListener transactionListener() {
    return new TransactionListenerImpl();
}

我們使用TransactionMQProducer生命生產者的客戶端,並且生產者組的名字叫做TransactionMQProducer,後面NameServer的地址沒有變化。最後就是設置了一個TransactionListener監聽器,這個監聽器的實現我們也定義了一個Bean,返回的是我們自定義的TransactionListenerImpl,我們看看裡邊怎麼寫的吧。

public class TransactionListenerImpl implements TransactionListener {
    @Autowired
    private TermMapper termMapper;

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {

        Integer termId = (Integer)arg;
        Term term = termMapper.selectById(termId);
        System.out.println("executeLocalTransaction termId="+termId+" term:"+term);
        if (term != null) return COMMIT_MESSAGE;

        return LocalTransactionState.UNKNOW;
    }

	@Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        String termId = msg.getKeys();
        Term term = termMapper.selectById(Integer.parseInt(termId));
        System.out.println("checkLocalTransaction termId="+termId+" term:"+term);
        if (term != null) {
            System.out.println("checkLocalTransaction:COMMIT_MESSAGE");
            return COMMIT_MESSAGE;
        }
        System.out.println("checkLocalTransaction:ROLLBACK_MESSAGE");
        return ROLLBACK_MESSAGE;
    }
}

在這個類中,我們要實現executeLocalTransaction和checkLocalTransaction兩個方法,其中executeLocalTransaction是在執行完send方法后立刻執行的,裡邊我們根據term表的id去查詢,如果能夠查詢出結果,就commit,消費端可以消費這個消息,如果查詢不到,就返回一個UNKNOW,說明過一會會調用checkLocalTransaction再次檢查。在checkLocalTransaction方法中,我們同樣用termId去查詢,這次如果再查詢不到就直接回滾了。

好了,事務消息中最重要的兩個方法都已經實現了,我們再來看看service怎麼寫吧,

@Autowired
private TermMapper termMapper;
@Autowired
@Qualifier("transactionProducer")
private TransactionMQProducer producer;

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    Term term = new Term();
    term.setTermYear(2020);
    term.setType(1);
    int insert = termMapper.insert(term);

    Message message = new Message();
    message.setTopic("cluster-topic");
    message.setKeys(term.getId()+"");
    message.setBody(new String("this is transaction mq "+new Date()).getBytes());

    TransactionSendResult sendResult = producer
        .sendMessageInTransaction(message, term.getId());
    System.out.println("sendResult:"+sendResult.getLocalTransactionState() 
                       +" 時間:"+new Date());
}
  • 在sendTransactionMQ方法上,我們使用了@Transactional註解,那麼在這個方法中,發生任何的異常,數據庫事務都會回滾;
  • 然後,我們創建Term對象,向數據庫中插入Term;
  • 構建Mesaage的信息,將termId作為message的key;
  • 使用sendMessageInTransaction發送消息,傳入message和termId,這兩個參數和executeLocalTransaction方法的入參是對應的。

最後,我們在test方法中,調用sendTransactionMQ方法,如下:

@Test
public void sendTransactionMQ() throws InterruptedException {
    try {
        transactionService.sendTransactionMQ();
    } catch (Exception e) {
        e.printStackTrace();
    }

    Thread.sleep(600000);
}

整個生產端的代碼就是這些了,消費端的代碼沒有什麼變化,就不給大家貼出來了。接下來,我們把消費端的應用啟動起來,消費端的應用最好不要包含生產端的代碼,因為TransactionListener實例化以後,就會進行監聽,而我們在消費者端是不希望看到TransactionListener中的日誌的。

我們運行一下生產端的代碼,看看是什麼情況,日誌如下:

executeLocalTransaction termId=15 term:com.example.rocketmqdemo.entity.Term@4a3509b0
sendResult:COMMIT_MESSAGE 時間:Wed Jun 17 08:56:49 CST 2020
  • 我們看到,先執行的是executeLocalTransaction這個方法,termId打印出來了,發送的結果也出來了,是COMMIT_MESSAGE,那麼消費端是可以消費這個消息的;
  • 注意一下兩個日誌的順序,先執行的executeLocalTransaction,說明在執行sendMessageInTransaction時,就會調用監聽器中的executeLocalTransaction,它的返回值決定着這個消息是否真正的投放到隊列中;

再看看消費端的日誌,

msgs.size():1
this is transaction mq Wed Jun 17 08:56:49 CST 2020

消息被正常消費,沒有問題。那麼數據庫中有沒有termId=15的數據呢?我們看看吧,

數據是有的,插入數據也是成功的。

這樣使用就真的正確的嗎?我們改一下代碼看看,在service方法中拋個異常,讓數據庫的事務回滾,看看是什麼效果。改動代碼如下:

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    ……
    throw new Exception("數據庫事務異常");
}

拋出異常后,數據庫的事務會回滾,那麼MQ呢?我們再發送一個消息看看,

生產端的日誌如下:

executeLocalTransaction termId=16 term:com.example.rocketmqdemo.entity.Term@5d6b5d3d
sendResult:COMMIT_MESSAGE 時間:Wed Jun 17 09:07:15 CST 2020

java.lang.Exception: 數據庫事務異常
  • 從日誌中,我們可以看到,消息是投放成功的,termId=16,事務的返回狀態是COMMIT_MESSAGE;
  • 最後拋出了我們定義的異常,那麼數據庫中應該是不存在這條消息的啊;

我們先看看數據庫吧,

數據庫中並沒有termId=16的數據,那麼數據庫的事務是回滾了,而消息是投放成功的,並沒有保持原子性啊。那麼為什麼在執行executeLocalTransaction方法時,能夠查詢到termId=16的數據呢?還記得MySQL的事務隔離級別嗎?忘了的趕快複習一下吧。在事務提交前,我們是可以查詢到termId=16的數據的,所以消息提交了,看看消費端的情況,

msgs.size():1
this is transaction mq Wed Jun 17 09:07:15 CST 2020

消息也正常消費了,這明顯不符合我們的要求,我們如果在微服務之間使用這種方式保證數據的最終一致性,肯定會有大麻煩的。那我們該怎麼使用s呢?我們可以在executeLocalTransaction方法中,固定返回UNKNOW,數據插入數據庫成功也好,失敗也罷,我們都返回UNKNOW。那麼這個消息是否投放到隊列中,就由checkLocalTransaction決定了。checkLocalTransaction肯定在sendTransactionMQ后執行,而且和sendTransactionMQ不在同一事務中。我們改一下程序吧,

@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    return LocalTransactionState.UNKNOW;
}

其他的地方不用改,我們再發送一下消息,

sendResult:UNKNOW 時間:Wed Jun 17 09:56:59 CST 2020
java.lang.Exception: 數據庫事務異常

checkLocalTransaction termId=18 term:null
checkLocalTransaction:ROLLBACK_MESSAGE
  • 事務消息發送的結果是UNKNOW,然後拋出異常,事務回滾;
  • checkLocalTransaction方法,查詢termId=18的數據,為null,消息再回滾;

又看了一下消費端,沒有日誌。數據庫中也沒有termId=18的數據,這才符合我們的預期,數據庫插入不成功,消息投放不成功。我們再把拋出異常的代碼註釋掉,看看能不能都成功。

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    ……
    //throw new Exception("數據庫事務異常");
}

再執行一下發送端程序,日誌如下:

sendResult:UNKNOW 時間:Wed Jun 17 10:02:57 CST 2020
checkLocalTransaction termId=19 term:com.example.rocketmqdemo.entity.Term@3b643475
checkLocalTransaction:COMMIT_MESSAGE
  • 發送結果返回UNKNOW;
  • checkLocalTransaction方法查詢termId=19的數據,能夠查到;
  • 返回COMMIT_MESSAGE,消息提交到隊列中;

先看看數據庫中的數據吧,

termId=19的數據入庫成功了,再看看消費端的日誌,

msgs.size():1
this is transaction mq Wed Jun 17 10:02:56 CST 2020

消費成功,這才符合我們的預期。數據插入數據庫成功,消息投放隊列成功,消費消息成功。

總結

事務消息最重要的就是TransactionListener接口的實現,我們要理解executeLocalTransaction和checkLocalTransaction這兩個方法是干什麼用的,以及它們的執行時間。再一個就是和數據庫事務的結合,數據庫事務的隔離級別大家要知道。把上面這幾點掌握了,就可以靈活的使用RocketMQ的事務消息了。

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

【其他文章推薦】

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

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

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

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

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

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

碳交易收益回饋弱勢 加州的能源正義之路

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

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

中國車廠挺禁售燃油車政策研究,有助新能源車加快發展

證券日報報導,中國工信部副部長辛國斌於今年9月初透露,工信部已啟動停止生產銷售傳統燃油汽車時間表的研究。靠研發生產電池起家的中國民營車企比亞迪董事長王傳福預測,中國2030年起將禁售傳統燃油汽車。而已在中國傳統燃油車做到自主品牌第一的長安汽車總裁朱華榮,更喊出2025年長安汽車將開始全面停止銷售傳統意義的燃油車,實現全系列產品的電氣化的目標。   據了解,德國宣布到2030年、法國宣布到2040年、英國宣布到2040年將禁止銷售傳統柴油車和汽油車,印度也宣布2030年要淘汰全部汽油車和柴油車。如果按照長安汽車2025年停售燃油車的時間表在中國全國執行,則中國將是第一個禁售傳統燃油車的國家。   對於長安汽車所提出的2025年停售傳統燃油車,市場分析,這可能與中國電動汽車百人會理事長陳清泰於9月下旬舉辦的中國電動汽車百人會常州論壇上表態有關,當時陳清泰提到,「最遲到2025年,電動汽車的性價比將達到或超過傳統燃油車」。   陳清泰的表態比王傳福2030年禁售傳統燃油車的表態晚了三天,但卻比王傳福更有分量,因為中國電動汽車百人會是一個與中國政府有著親密關係的非官方機構,機構顧問委員會有一批在職高官,例如科技部部長萬鋼、工信部部長苗圩。陳清泰非官方的表態可能接近中國官方接下來公布的停止生產銷售傳統燃油汽車的時間。   而中國在發展電動汽車方面,隨著政府後期出臺的新能源汽車補貼政策,目前中國新能源汽車市場幾乎被享受優惠政策的自主品牌車企壟斷;加上2018年至2019年「雙積分(車企油耗積分+新能源汽車積分)」制度的展開,中國車企在內有市場、外有政策支持的情況下,可能相較歐美國家先一步步入全面電動汽車時代。   (本文內容由授權使用。首圖來源:public domain CC0)  

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

【其他文章推薦】

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

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

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

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

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

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

倫敦一些地區要在路燈內安裝充電站,讓電動車充電更方便

 

 

隨著電動車逐漸興起,充電的問題變得越來越受到關注,當使用者在城市中開著電動車時,究竟該去哪裡尋找充電設施?Electrek 報導指出,倫敦一些地區似乎正試著透過在路燈柱內安裝充電站的計畫,來解決這個問題。

在充電的問題上,北歐國家似乎具有「先天」優勢,為了面臨嚴峻的冬季氣溫,他們原先在街道上就廣泛設有協助汽車引擎啟動的加熱器(block heater),因此也能夠運用同樣系統讓電動車能在街道上進行充電。

其他沒有這麼寒冷的地方就不同了,在沒有類似基礎設施的情況下,一些公司正在思考相關方案來解決這個問題;像是特斯拉就推出了城市專用的充電站,雖然因為功率低使得充電速度較慢,但也不失為市區內的一個選擇。

另一方面,位在倫敦的肯辛頓與切爾西區(RBKC)的行政當局則選擇了不同的做法,他們已經和能源供應商OVO Energy 和近期獲得西門子投資的德國充電公司ubitricity 簽約,要在都市中現有的燈柱內安裝充電站。

之所以做出這項決定,當地的交通委員會主席Cllr Gerard Hargreaves 表示,是因為居民的充電需求正隨著電動車持續增長,但多數人都無法在附近街道的停車處找到充電設施,讓電動車的充電變得難以進行。

Hargreaves 認為,透過在復古路燈內設置充電設施,駕駛人在住家附近就可以直接充電,倫敦的空氣汙染問題也得以緩解。「除此之外,在路燈內設置意味著不需要額外的基礎建設,更具成本效益的同時也不會影響市容。」

肯辛頓與切爾西區目前計畫安裝的是ubitricity 提供的「SimpleSockets」充電系統,最大輸出功率為4.6 kW。

OVO 表示,SimpleSockets 將會設立在付費和非付費停車格附近的路燈內,24 小時提供使用,每度電只需15 便士(約台幣6 元),這讓電動車不僅更為方便,花費也將更貼近一般人生活。

雖然SimpleSockets 每度電收取的費用與該區的電費規定相當,但Electrek 報導也指出,用戶必須每月繳納7.99 英鎊(約台幣320 元)的訂閱費,同時向ubitricity 購買199 英鎊(約台幣7,960 元)的電纜,才能使用這項收費標準。

當然用戶也可以選擇不繳納訂閱費,但使用上還是必須花100 多英鎊(約台幣4,000 元)購買使用的電纜,同時每度電的收費也將提高到19 便士(約台幣7.6 元),只是即使如此,也遠比英國國內的其他充電選擇好上許多。

ubitricity 目前已經開始在肯辛頓與切爾西區內進行安裝,目標在1 月底前要安裝完成50 個在路燈座內的充電裝置。

(合作媒體:。首圖來源: 臉書)  

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

詳說tcp粘包和半包

tcp服務端和客戶端建立連接後會長時間維持這個連接,用於互相傳遞數據,tcp是以流的方式傳輸數據的,就像一個水管里的水一樣,從一頭不斷的流向另一頭。
理想情況下,發送的數據包都是獨立的,

現實要複雜一些,發送方和接收方都有各自的緩衝區。
發送緩衝區:應用不斷的把數據發送到緩衝區,系統不斷的從緩衝區取數據發送到接收端。
接收緩衝區:系統把接收到的數據放入緩衝區,應用不斷的從緩衝區獲取數據。
當發送方快速的發送多個數據包時,每個數據包都小於緩衝區,tcp會將多次寫入的數據放入緩衝區,一次發送出去,服務器在接收到數據流無法區分哪部分數據包獨立的,這樣產生了粘包。

或者接收方因為各種原因沒有從緩衝區里讀取數據,緩衝區的數據會積壓,等再取出數據時,也是無法區分哪部分數據包獨立的,一樣會產生粘包。
發送方的數據包大於緩存區了,其中有一部分數據會在下一次發送,接收端一次接收到時的數據不是完整的數據,就會出現半包的情況。

我們可以還原一下粘包和半包,寫一個測試代碼
服務端

func main() {
	l, err := net.Listen("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	fmt.Println("listen to 8899")
	for {
		conn, err := l.Accept()
		if err != nil {
			panic(err)
		} else {
			go handleConn(conn)
		}
	}
}

func handleConn(conn net.Conn) {
	defer conn.Close()
	var buf [1024]byte
	for {
		n, err := conn.Read(buf[:])
		if err != nil {
			break
		} else {
			fmt.Printf("recv: %s \n", string(buf[0:n]))
		}
	}
}

客戶端

func main() {
	data := []byte("~測試數據:一二三四五~")
	conn, err := net.Dial("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	for i := 0; i < 2000; i++ {
		if _, err = conn.Write(data); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
	}
}

查看一下輸出

recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一� 
recv: ��三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~

正常情況下輸出是recv: ~測試數據:一二三四五~,發生粘包的時候會輸出多個數據包,當有半包的情況下輸出的是亂碼數據,再下一次會把剩下的半包數據也輸出。
要解決也簡單的就想辦法確定數據的邊界,常見的處理方式:

  • 固定長度: 比如規定所有的數據包長度為100byte,如果不夠則補充至100長度。優點就是實現很簡單,缺點就是空間有極大的浪費,如果傳遞的消息中大部分都比較短,這樣就會有很多空間是浪費的,同樣浪費的還有流量。
  • 分隔符:用分隔符來確定數據的邊界,這樣做比較簡單也不浪費空間,但數據包內就不能包含相應的分隔符,如果有會造成錯誤的解析。
  • 數據頭:通過數據頭部來解析數據包長度,比如用4個字節來當數據頭,保存每個實數據包的長度。

個人更推薦數據頭方式來確定數據邊界,在發送和接收數據時做好規定,每個數據包是不定長的,比如4字節的包頭+真實的數據可以根據自己的業務進行擴展,比如上更多的包頭或者包尾,加上數據校驗等。
我修改一下上面的代碼:
客戶端

	data := []byte("~測試數據:一二三四五~")
	conn, err := net.Dial("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	for i := 0; i < 2000; i++ {
		var buf [4]byte
		bufs := buf[:]
		binary.BigEndian.PutUint32(bufs, uint32(len(data)))
		if _, err := conn.Write(bufs); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
		if _, err = conn.Write(data); err != nil {
			fmt.Printf("write failed , err : %v\n", err)
			break
		}
	}

服務端

func main() {
	l, err := net.Listen("tcp", ":8899")
	if err != nil {
		panic(err)
	}
	fmt.Println("listen to 8899")
	for {
		conn, err := l.Accept()
		if err != nil {
			panic(err)
		} else {
			go handleConn(conn)
		}
	}
}
func handleConn(conn net.Conn) {
	defer conn.Close()
	for {
		var msgSize int32
		err := binary.Read(conn, binary.BigEndian, &msgSize)
		if err != nil {
			break
		}
		buf := make([]byte, msgSize)
		_, err = io.ReadFull(conn, buf)
		if err != nil {
			break
		}
		fmt.Printf("recv: %s \n", string(buf))
	}
}

執行再看一下輸出,沒有粘包或者半包的情況

recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~ 
recv: ~測試數據:一二三四五~

也可以像第一個例子一樣用一個指定大小的buf var buf [1024]byte,每次從conn里取出指定大小的數據,然後進行數據解析,如果發現有半包的情況,就再讀取一次,加上上次未解析的數據,再次重新解析。

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

.Net Core服務監控報警指標上報Prometheus+Grafana

前言

簡單集成Prometheus+Grafana,指標的上報收集可視化。

Prometheus

Prometheus是一個監控平台,監控從HTTP端口收集受監控目標的指標。在微服務的架構里Prometheus多維度的數據收集是非常強大的 我們首先下載安裝Prometheusnode_exporter,node_exporter用於監控CPU、內存、磁盤、I/O等信息

  • Prometheus下載地址
  • node_exporter下載地址

下載完成后解壓以管理員運行 prometheus.exe 訪問 http://localhost:9090/ 出現一下頁面說明啟動成功啦

.Net Core獲取指標

有了Prometheus,我們還需要給Prometheus提供獲取監控數據的接口,我們新建一個WebApi項目,並導入prometheus-net.AspNetCore包,在Configure中加入UseMetricServer中間件

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

    app.UseMetricServer();
    
}

啟動項目訪問http://localhost:5000/metrics就可以看基本的一些監控信息啦,包括線程數,句柄數,3個GC的回收計數等信息。

# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 29
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 44441600
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 69660672
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 2464584
# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592448124.2853072
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 413
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225187631104
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 1.171875

Help 是收集指標的說明,Type收集指標的類型

但是作為HTTP應用怎麼能沒有HTTP的監控和計數呢,只需要加加入UseHttpMetrics中間件就可以對HTTP請求監控和計數,主要注意的是UseHttpMetrics最好放在UseEndpointsUseRouting中間

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMetricServer();
    
    app.UseRouting();
    
    app.UseHttpMetrics();

    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

啟動項目繼續訪問http://localhost:5000/metrics

# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge

可以看到已經有了,我們隨便請求一下服務看看效果,會幫我們記錄下總耗時,總請求數,和每次請求的耗時數

但是單單有上面那些數據好像還不太好定位一下很奇葩的問題,這時候我們可以獲取Runtime的一些數據,方法童謠很簡單。導入prometheus-net.DotNetRuntime 包,它可以幫助我們看到如下指標

  • 垃圾回收的收集頻率和時間
  • 服務佔用堆大小
  • 對象堆分配的字節
  • JIT編譯和JIT CPU消耗率
  • 線程池大小,調度延遲以及增長/縮小的原因
  • 鎖爭用情況

我們只需要在ProgramMain方法中啟動收集器就可以啦。

public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder.Default().StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

啟動項目繼續訪問http://localhost:5000/metrics測試一下

# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 75141120
# HELP dotnet_gc_pause_ratio The percentage of time the process spent paused for garbage collection
# TYPE dotnet_gc_pause_ratio gauge
dotnet_gc_pause_ratio 0
# HELP http_requests_received_total Provides the count of HTTP requests that have been processed by the ASP.NET Core pipeline.
# TYPE http_requests_received_total counter
# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections
# TYPE dotnet_gc_collection_seconds histogram
dotnet_gc_collection_seconds_sum 0
dotnet_gc_collection_seconds_count 0
dotnet_gc_collection_seconds_bucket{le="0.001"} 0
dotnet_gc_collection_seconds_bucket{le="0.01"} 0
dotnet_gc_collection_seconds_bucket{le="0.05"} 0
dotnet_gc_collection_seconds_bucket{le="0.1"} 0
dotnet_gc_collection_seconds_bucket{le="0.5"} 0
dotnet_gc_collection_seconds_bucket{le="1"} 0
dotnet_gc_collection_seconds_bucket{le="10"} 0
dotnet_gc_collection_seconds_bucket{le="+Inf"} 0
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 4925936
# HELP dotnet_threadpool_num_threads The number of active threads in the thread pool
# TYPE dotnet_threadpool_num_threads gauge
dotnet_threadpool_num_threads 0
# HELP dotnet_threadpool_scheduling_delay_seconds A breakdown of the latency experienced between an item being scheduled for execution on the thread pool and it starting execution.
# TYPE dotnet_threadpool_scheduling_delay_seconds histogram
dotnet_threadpool_scheduling_delay_seconds_sum 0.015556
dotnet_threadpool_scheduling_delay_seconds_count 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.001"} 0
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.01"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.05"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.5"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="10"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="+Inf"} 10
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 50892800
# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 32
# HELP dotnet_jit_method_seconds_total Total number of seconds spent in the JIT compiler
# TYPE dotnet_jit_method_seconds_total counter
dotnet_jit_method_seconds_total 0
dotnet_jit_method_seconds_total{dynamic="false"} 0.44558800000000004
dotnet_jit_method_seconds_total{dynamic="true"} 0.004122000000000001
# HELP dotnet_gc_pinned_objects The number of pinned objects
# TYPE dotnet_gc_pinned_objects gauge
dotnet_gc_pinned_objects 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592449942.6063592
# HELP dotnet_gc_heap_size_bytes The current size of all heaps (only updated after a garbage collection)
# TYPE dotnet_gc_heap_size_bytes gauge
# HELP http_request_duration_seconds The duration of HTTP requests processed by an ASP.NET Core application.
# TYPE http_request_duration_seconds histogram
# HELP dotnet_contention_seconds_total The total amount of time spent contending locks
# TYPE dotnet_contention_seconds_total counter
dotnet_contention_seconds_total 0
# HELP dotnet_gc_pause_seconds The amount of time execution was paused for garbage collection
# TYPE dotnet_gc_pause_seconds histogram
dotnet_gc_pause_seconds_sum 0
dotnet_gc_pause_seconds_count 0
dotnet_gc_pause_seconds_bucket{le="0.001"} 0
dotnet_gc_pause_seconds_bucket{le="0.01"} 0
dotnet_gc_pause_seconds_bucket{le="0.05"} 0
dotnet_gc_pause_seconds_bucket{le="0.1"} 0
dotnet_gc_pause_seconds_bucket{le="0.5"} 0
dotnet_gc_pause_seconds_bucket{le="1"} 0
dotnet_gc_pause_seconds_bucket{le="10"} 0
dotnet_gc_pause_seconds_bucket{le="+Inf"} 0
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225201872896
# HELP dotnet_gc_finalization_queue_length The number of objects waiting to be finalized
# TYPE dotnet_gc_finalization_queue_length gauge
dotnet_gc_finalization_queue_length 0
# HELP dotnet_threadpool_io_num_threads The number of active threads in the IO thread pool
# TYPE dotnet_threadpool_io_num_threads gauge
dotnet_threadpool_io_num_threads 3
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 436
# HELP dotnet_gc_collection_reasons_total A tally of all the reasons that lead to garbage collections being run
# TYPE dotnet_gc_collection_reasons_total counter
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.890625
# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge
# HELP dotnet_threadpool_adjustments_total The total number of changes made to the size of the thread pool, labeled by the reason for change
# TYPE dotnet_threadpool_adjustments_total counter
# HELP dotnet_jit_cpu_ratio The amount of total CPU time consumed spent JIT'ing
# TYPE dotnet_jit_cpu_ratio gauge
dotnet_jit_cpu_ratio 0.5728901224489797
# HELP process_cpu_count The number of processor cores available to this process.
# TYPE process_cpu_count gauge
process_cpu_count 8
# HELP dotnet_build_info Build information about prometheus-net.DotNetRuntime and the environment
# TYPE dotnet_build_info gauge
dotnet_build_info{version="3.3.1.0",target_framework=".NETCoreApp,Version=v5.0",runtime_version=".NET Core 5.0.0-preview.2.20160.6",os_version="Microsoft Windows 10.0.18363",process_architecture="X64"} 1
# HELP dotnet_jit_method_total Total number of methods compiled by the JIT compiler
# TYPE dotnet_jit_method_total counter
dotnet_jit_method_total{dynamic="false"} 830
dotnet_jit_method_total{dynamic="true"} 30
# HELP dotnet_gc_cpu_ratio The percentage of process CPU time spent running garbage collections
# TYPE dotnet_gc_cpu_ratio gauge
dotnet_gc_cpu_ratio 0
# HELP dotnet_threadpool_scheduled_total The total number of items the thread pool has been instructed to execute
# TYPE dotnet_threadpool_scheduled_total counter
dotnet_threadpool_scheduled_total 16
# HELP dotnet_gc_allocated_bytes_total The total number of bytes allocated on the small and large object heaps (updated every 100KB of allocations)
# TYPE dotnet_gc_allocated_bytes_total counter
dotnet_gc_allocated_bytes_total{gc_heap="soh"} 3008088
dotnet_gc_allocated_bytes_total{gc_heap="loh"} 805392
# HELP dotnet_contention_total The number of locks contended
# TYPE dotnet_contention_total counter
dotnet_contention_total 0

可以看到非常多的信息啦,但是我們有時候不需要這麼多指標也可以自定義。


public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder
        .Customize()
        .WithContentionStats()
        .WithJitStats()
        .WithThreadPoolSchedulingStats()
        .WithThreadPoolStats()
        .WithGcStats()
        .StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

JIT,GC和線程的監控是會影響到一點點性能,我們可以通過sampleRate這個枚舉的值來控制採樣頻率

public static void Main(string[] args)
{
    DotNetRuntimeStatsBuilder
        .Customize()
        //每5個事件個採集一個
        .WithContentionStats(sampleRate: SampleEvery.FiveEvents)
        //每10事件採集一個
        .WithJitStats(sampleRate: SampleEvery.TenEvents)
        //每100事件採集一個
        .WithThreadPoolSchedulingStats(sampleRate: SampleEvery.HundredEvents)
        .WithThreadPoolStats()
        .WithGcStats()
        .StartCollecting();
    CreateHostBuilder(args).Build().Run();
}

有了這些指標我們需要Prometheus來收集我們Api的指標,只需要修改prometheus.yml文件然後重啟Prometheus就可以了。

scrape_configs:  
- job_name: mydemo  
  scrape_interval: 15s  
  scrape_timeout: 10s  
  metrics_path: /metrics  
  scheme: http  
  static_configs:  
  - targets:  
    - localhost:5000   

啟動Api項目和Prometheus,選中dotnet_collection_count_total點擊Excute可以看到Api的指標是正常上報的。

Prometheus有了數據我們就需要一個炫酷的UI去展示上報的數據啦。

Grafana

Prometheus有了數據就差一個漂亮的UI來展示的我們的指標了。Grafana是一個Go編寫的開源應用,用於把指標數據可視化。是當下流行的時序數據展示工具。先下載,直接下載exe安裝,完成后能打開http://localhost:3000/頁面就安裝成功了

  • 下載地址

先添加數據源,選擇Prometheus為數據源,並配置。

添加儀錶盤

Import via panel json中加入下面這個json,點擊load,

  • 儀錶盤json

選擇數據源,點擊Import就能看到儀錶盤了

還可以去這裏添加很多現有的儀錶盤。複製ID添加儀錶盤。

參考文章

prometheus-net
.NetCore下使用Prometheus實現系統監控和警報系列

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

日本提議取消捕鯨禁令 保育組織斥危險又魯莽

摘錄自2018年9月10日自由時報報導

日本將在今年的國際捕鯨委員會(IWC)會議上,提議取消32年前制定的商業捕鯨禁令,希望建立「可持續捕鯨委員會」為商業捕鯨國家制定捕撈配額。保育人士憤怒表示,日本企圖公然推翻一直以來維持的狩獵禁令,若通過日本的提議,會導制危險的商業捕鯨行為。

《法新社》報導,為期一週的國際捕鯨委員會第67屆會議本週一(10日)在巴西弗洛里亞諾波利斯(Florianopolis)舉行,日本將提議廢止捕鯨禁令,稱小鬚鯨和其他鯨魚等總量已經恢復,建議IWC認同將恢復數量的物種訂定新的捕撈配額,建立「可持續捕鯨委員會」,替允許國民商業捕鯨的國家制定相關配額。日本還指出,實施捕鯨禁令不只是為了保育,也是為了再度開放捕鯨。

英國國際人道協會(HSI UK)也說,如果日本提案通過,將再次開放捕鯨季節,這會是幾十年來,最危險和魯莽企圖的商業捕鯨行為。

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

2017上海國際汽車新能源及智慧技術展覽會

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新