1. 程式人生 > >實時大資料處理效能瓶頸

實時大資料處理效能瓶頸



1、業務資料特性
   1、資料來源突然流量增大;
      對於這點,曾見有人將資料快取到硬碟上,待流量變小時再發出去。我認為這樣並不是解決問題的根本辦法,因為在流量波峰的情況下,這樣的序列IO會使效率更低。我的經驗是,使用足夠大的記憶體緩衝,並且提高記憶體的使用率,一般1Gb流量,有個10MB記憶體就夠用了。


   2、業務處理過程的不均勻;
      業務處理過程不均勻往往是由於架構設計的不夠合理造成的,耦合度和序列度過高導致的瓶頸。這種情況下,往往需要修改其架構設計,使用非同步方式來解耦,並行方式替代序列方式。

2、記憶體管理
   1、記憶體的預先分配
      預先分配記憶體一般是比較好的選擇,避免頻繁的記憶體分配和回收;


   2、避免大面積的記憶體操作
      比如大塊記憶體的開闢或memset,如果必須要做這兩種操作,那麼將這兩種操作均勻地分佈在處理過程中。


   3、冗餘拷貝
      記憶體空間與物件緊耦合,每次從buffer或者FIFO出來後都需要拷貝進物件資料段中,這種是不可取的,產生冗餘拷貝。偶覺得buffer或者FIFO應該起到一定的緩衝作用,它們與處理過程鬆耦合,處理完成後緩衝區的指標才可以下移;


   4、避免記憶體資源競爭
      記憶體鎖機制往往是是資料同步的常用機制,但鎖亦會帶來很大的時間開銷,尤其是長年累月的處理過程,所以要避免因為鎖機制帶來資源競爭。一個生產者與一個消費者模式時,使用環形緩衝區是一個比較好的選擇。但由於業務資料的關係,往往資料間的耦合度很大,往往需要採取一定的分流策略,降低資料的耦合度;


   5、記憶體的清理與垃圾資料的判斷
      在實際編碼過程中,預先分配的記憶體經過一段時間後,有可能需要對記憶體進行清空或重置,這樣可能會帶來很大的處理延時,所以對記憶體的清理也會帶來瓶頸。偶的一個經驗是,不宜做大塊記憶體的memset,可以將資料進行業務邏輯上的判斷,看其是否合法,不合法的往往都是垃圾資料。不過這種做法有個前提,那就是記憶體排列必須均勻(固定大小),資料格式明確。

3、執行緒管理
   1、執行緒資源的分配
      在實時資料處理過程中,工作執行緒數量是預先可以確定的,所以我一般是在系統啟動的過程中,按照配置分配與啟動執行緒。


   2、執行緒的工作模式
      在實時資料處理中,資料會源源不斷且洶湧而入。經過試驗,偶得出的結論是,沒有資料需要處理時,才可以短暫地sleep一下,有資料時不應該有任何停頓,更不應該處理一個數據週期後,將執行緒阻塞,然後下一個資料來後再喚醒。這種阻塞與喚醒機制需要很長的CPU週期,理應避免頻繁地阻塞與喚醒執行緒。
      while(1)
      {
        if (IsBreak())
            break;
        if (!GetData())
          {
            usleep(1000*1);
            contiune;
          }
        if (!CoreProc())
           continue;
        if (!ExportData())
           continue;
      }

   3、執行緒的切換
      執行緒或程序一般都是由作業系統來排程的,不同的作業系統會有不同的排程策略,使用者很難控制。
      執行緒切換我基本認為有三類:
      1、作業系統排程策略造成的,如執行緒的時間片到了;
         對於這類切換,使用者無法避免。偶甚至認為提高執行緒優先順序來防止執行緒飢餓等這些措施來保證系統的穩定性都是浮雲。曾在一家公司面試時,所謂的研發部總經理提出將執行緒繫結CPU的辦法來提高效能,我想這在理論上似乎可行,因為我在做效能優化工作以前,也這麼想,並且這麼做過,但這會帶來巨大的CPU開銷,導致這臺伺服器就被幾個執行緒佔用的差不多了,再想部署其他東西就很難了。所以我的理解是不要去試圖用很高深的技術來改變某些東西,做好你該做好的東西就行了。


      2、人為的切換,如sleep函式;
         經過試驗,偶得出的結論是,沒有資料需要處理時,才可以短暫地sleep一下,有資料時不應該有任何停頓。


      3、呼叫了造成執行緒切換的函式,如I/O函式等;
         偶認為在核心處理中,在邏輯運算中不應該夾雜IO操作。如必須有IO,應該另起執行緒來IO,或者使用執行緒池技術和訊息通知機制,如借鑑epoll和IOCP的思想模型。很多log雖然是執行緒安全的,但由於log需要隨時輸出,往往被夾在邏輯運算中,這樣的log輸出就會帶來I/O問題,甚至會造成執行緒排隊,導致核心執行緒效率降低。


   4、執行緒的顆粒度
      在實時資料處理過程中,執行緒一般採用流水結構,所以執行緒的顆粒度很必須合適。關於執行緒顆粒度的問題,在後續的架構設計中我們再討論。

4、I/O處理
   1、I/O與核心處理非同步
      I/O的速度與CPU的速度相比是龜速,如果將I/O和CPU的邏輯運算同步序列,勢必會造成邏輯運算執行緒阻塞,大大降低核心處理速度,所以建議將I/O獨立出一個執行緒,這個執行緒只做I/O操作,與核心處理分開,實現非同步操作。

   2、按塊I/O
      頻繁的I/O一定會帶來效率的下降,所以最好按照一定的緩衝區大小進行I/O,會大大提升I/O效率。I/O操作不外乎socket和檔案,對這兩種一般都適用,socket的緩衝區比較明確。不過檔案IO最好使用順序儲存的方式,減少磁頭尋道時間,檔案操作最害怕的可能就是寫隨機檔案了。
   
   3、阻塞式I/O
      在實時資料處理系統中,往往需要保證傳輸的可靠性,自接到資料後,就不能丟失。這裡並不是說非阻塞式IO就不能達成這樣的目標,而是在後續的大資料處理過程中,往往都需要使用強並行模式,如此使用非阻塞式IO就會造成系統複雜度大大提高。在我的經驗中,IO只要是非同步的,使用阻塞方式並不會造成效率的降低。
      在這裡說句閒話,高併發處理一般都採用非同步非阻塞式IO,因為其要支援多路同時IO,其對資料通道比較敏感,須區分是哪個連結或者通道,往往是短連結居多。而海量資料處理則對資料通道不是很敏感,其只關心資料流量,所有的資料進來,都是按照預先分配的通道進行處理,一般都是長連結過程。

5、邏輯處理與演算法
   演算法是個巨大的話題,這裡我只說說我的一些小體會。
   1、靜態資料的比較與查詢
      這裡說的靜態資料一般是指系統的啟動之初就裝載的資料,這類的資料我一般使用XML檔案來描述,所以字串居多。如果在處理過程中,進行字串比較,會非常地耗時。建議使用<key,value>的方式進行儲存和查詢,這個效率會大大提高。在不超過500萬條的情況下,使用STL的map就足矣,如果再大,就使用hashmap。
      
   2、動態資料的比較與查詢
      在實時大資料處理中,資料往往都是無序的,所以一般使用hash比較多。hash分開鏈地址法和拉鍊地址法,在使用中,拉鍊地址法往往使用的比較多。但開鏈地址法卻為我們提供一種思路,即查詢時,可以使用唯一的key一次性定位到某個offset,我想這和MapReduce有異曲同工之妙。

   3、避免大規模的迴圈
      在邏輯處理過程中,應該避免大規模的迴圈,尤其是在迴圈裡再開闢記憶體等耗時操作。