簡單的業務更考驗技術--化腐朽爲神奇

  金庸經典《射鵰英雄傳》裏,黃蓉爲了讓洪七公交本身和靖哥哥武功,每天對師傅美食相待,在作了「玉笛誰家聽落梅」這樣一些世間珍品以後,告訴師傅說今天要作的是"炒白菜"。洪七公露出很是欣賞的眼光,說:「好,我倒要看看你怎樣化腐朽爲神奇。」上週五聽了一個咱們內部的深度學習講座,基本這方面處於初始探索階段。上週六去3w咖啡聽了百度的人工智能講座,他們的深度學習也只限於對代碼的訓練。想想代碼這個東西分支相對來講仍是有限的,因此如今的各類集成開發軟件已經很簡化程序員的工做了,因此看百度作的基於AI的效果仍是有點殺雞用牛刀。咱們部門不涉及大數據,雲計算,人工智能,深度學習這些聽起來高大上的業務和技術,可是紮紮實實作好本身要比用這些包裝好的TensorFlow啥的對底層的理解要深刻的多。特別是在長期作一個業務的過程當中,系統一些潛在的問題會在大腦中不斷的旋轉,一些新知識的攝取很快就可以產生對現有業務的改造想法。好了:Talking is cheap, show me the code.html

  我如今主要在作兩件事,一件事是媒資接口的併發量上不去,我已經跟領導說好了:給我時間,我會搞定的。我腦子裏的方案有A,B,C,D,E。可是這個業務至關重要,實際上作的時候雖然以爲這個架構太老太不合理,也只能先從JVM調優,dubbo參數調優,緩存參數調優這些作起,看看不改動架構的前提下能改善多少。而後再從一些局部的性能消耗點入手,從局部到總體一點點來。上週由於併發量上去以後,CPU使用率太高,jstat看到minor gc至關頻繁,併發量上去以後居然達到每秒4,5次。就先增長了新生代的容量,效果有,可是很小。用jmap看到頻繁的對象是系統的本地緩存,這個是定時任務每次新數據覆蓋的,會有至關多的垃圾回收。並且每次服務層啓動,先運行大量本地緩存,很耗時,服務已經暴露,可是實際還不具有處理能力,結果常常會啓動時出現dubbo線程池滿,一段時間後自行恢復。爲了解決本地緩存的問題,我想採用緩存數據存於redis,用canel訂閱mysql的更新,啓動時只是取一下redis的值,採用redis的哈希結構,能夠直接反序列化成java的hashmap,很快。而後監聽redis更新,不用定時跑。這樣就涉及到一個問題:線上沒有此業務的redis集羣,本地緩存的字典值不少,究竟性能怎樣,須要測試對比。java

  好在我還有另一件事情要作:離線服務,以前作的時候也比較倉促,雖然細節處我作了不少的優化,到處體現java功底,可是跟人家講,我講不出來到底這個有什麼閃光點,太零碎了,人家聽了就一個反應:不就是一個後臺服務嘛。確實從大的架構層作的就不像一個架構師,僅僅增量上作了一個負載分攤,全量只是簡單的主備。像全量這種既消耗時間又消耗資源的,怎麼能從一開始不作分佈式計算呢。因而最近作了一版改造,解決分佈式計算,橫向擴展問題。採用redis做爲中間通訊工具和字典存儲工具,正好和媒資接口的字典數據是同樣的,這樣就能夠用這個項目來進行線上本地緩存性能的測試,而不影響最重要的媒資接口服務。原本也想用搜索中間件來存儲數據,解耦數據庫,由於我最終確定是要作本身的搜索中間件的。可是確實,對於項目來講是可用可不用的東西,還增長維護成本,那就不該該用。想作本身找時間作去。mysql

  

 

  上面是整個系統的架構圖。其中包括了對接搜索部門直到面向用戶終端的整個流程,裏面用到了本身作的離線數據框架epiphany。放在個人github:https://github.com/xiexiaojing/epiphany。能夠經過maven管理下載,pom配置以下(如需引用請注意版本更新):linux

<dependency>
  <groupId>com.brmayi</groupId>
  <artifactId>epiphany</artifactId>
  <version>0.7</version>
</dependency>

框架核心思想:git

  將離線數據服務劃分爲全量服務,增量服務和手動處理服務三部分。全量和增量採用redis做爲做業調度和管理機制。在redis宕機時各個服務獨立運行,產生相同的輸出,結果集是在正常狀況下的n倍,n爲服務器單元。其中全量服務由於原型是在咱們項目的離線服務基礎上進行開發,數據量大,文件壓縮後是幾十G的數據量級,因此數據存於磁盤。每一個服務經過redis獲取將處理數據的區間,各自處理。服務器的磁盤採用async同步處理結果。爲了高可用,採用的是分步計算,結果冗餘。獲取方能夠將其中一個磁盤做爲主磁盤做爲hadoop的節點或者採用linux的async同步,或者ftp,nfs等手段拉取數據。增量服務能夠採用消息隊列等手段進行數據傳遞,若是消息多,消息體大,能夠用消息傳遞更新的id,內容可存於磁盤,中間數據庫,緩存等,讓調用方來進行拉取。手動處理服務直接採用netty處理客戶端的http請求。整個框架運行不需任何外部容器。直接用jvm運行main方法。容錯可根據須要採用簡單主備或者failover=roundrobin。程序員

框架使用方法:github

   整個架構體系已經在框架內部處理,業務方只需實現DataService接口,將數據傳入框架,而後按照本身的需求啓動服務便可。DataService接口定義以下:redis

package com.brmayi.epiphany.service;

import java.util.List;

import com.brmayi.epiphany.exception.EpiphanyException;
/**
 * 
 * 	通用文件處理類:這是業務代碼的核心類
 *            
 *            .==.       .==.
 *           //'^\\     //^'\\
 *          // ^^\(\__/)/^ ^^\\
 *         //^ ^^ ^/6  6\ ^^^ \\
 *        //^ ^^ ^/( .. )\^ ^^ \\
 *       // ^^  ^/\|v""v|/\^^ ^ \\
 *      // ^^/\/  / '~~' \ \/\^ ^\\
 *      ----------------------------------------
 *      HERE BE DRAGONS WHICH CAN CREATE MIRACLE
 *       
 *      @author 靜兒(987489055@qq.com)
 *
 */
public interface DataService {
	/**
	 * 根據ID進行業務數據處理
	 * @param dealIds 處理ID
	 * @param path 要保存到的磁盤路徑,不須要保存磁盤,能夠爲null
	 * @throws EpiphanyException 拋出通用異常
	 */
	public void dealDataByIds(List<Long> dealIds, String path) throws EpiphanyException;
	
	/**
	 * 根據時間區間獲取id列表
	 * @param beginTime 開始時間
	 * @param endTime 結束時間
	 * @return id列表
	 * @throws EpiphanyException 拋出通用異常
	 */
	public List<Long> getIds(String beginTime, String endTime) throws EpiphanyException;
	
	/**
	 * 根據開始結束ID處理數據
	 * @param beginId 開始ID 
	 * @param endId 結束ID
	 * @param path 要保存到的磁盤路徑,不須要保存磁盤,能夠爲null
	 * @throws EpiphanyException 拋出通用異常
	 */
	public void dealDataByBeginEnd(long beginId, long endId, String path) throws EpiphanyException;
	
	/**
	 * 取得最大ID
	 * @return 最大ID
	 * @throws EpiphanyException 拋出通用異常
	 */
	public long getMaxId() throws EpiphanyException;
	
	
	/**
	 * 取得最小ID
	 * @return 最小ID
	 * @throws EpiphanyException 拋出通用異常
	 */
	public long getMinId() throws EpiphanyException;
}

深刻技術細節:sql

  ☆ 關於壓縮:壓縮是遞歸操做,若是java棧設置很大,壓縮操做會很是消耗CPU。因此框架設計時,業務方可設置全量的線程數,可是壓縮是異步用另外的線程池來管理,這個線程池的容量是全量線程數的一半。好比咱們線上用的是24核高配物理機,如今上面有多個服務進行復用。個人離線服務是視頻和專輯兩個部分,有數據通用的邏輯,可是是獨立的業務,因此我用一個工程來進行項目管理,可是用的是兩個獨立進程,採用兩個腳本分開部署。千萬級數據,每一個業務全量都使用10個線程。在改造前的那一版採用的是專輯400個線程,視頻660個線程,用50個線程的線程池來跑。測試發現改造後的10個線程速度並不比改造前差多少。緣由是追加操做和文件大小關係不是很大,開銷要小於新建文件的開銷。線程少減小了資源開銷和上下文切換。還有就是壓縮操做,大文件的壓縮效率要高不少。由於用的是哈夫曼系的gz壓縮,減小了頭文件的字符映射。數據庫

   ☆ Redis的哈希結構:這個結構看起來是對java的hashmap的很好的對應。可是實際使用的時候,若是map的key(對應於redis哈希中的field)大於1000,插入效率急劇降低。由於redis是單線程的IO,而一個map對應的redis的key是一個,全部這些寫操做會被映射到一個redis節點,效率很低。我試圖將一個3w7k的字典map放入redis。結果運行了近一個小時,插入了20402條後再也插不進去了,鏈接超時,運行幾回都沒能插入更多。

  ☆ 巧用對象池:我在框架中封裝了有限制的對象和無限制的對象池來做爲線程池進行一些異步調用。無限制的對象池是由於對象的總數在其餘地方有限制。而有限制的對象池是爲了防止對象在異常時過多資源佔用。而異步有點地方是爲了提升效率,有些地方又是必須的。好比我在程序中一個方法調用mysql取數據,而這個方法處理完數據後還要給MQ發消息,消息體特別大,發送時間特別長。長時間mysql不斷開,就會鏈接超時異常。

 一點感悟:

  一我的的智商決定了學習的速度和領悟能力。而對情商決定了在一條路上能走多遠。對一個項目的熱愛能夠深刻到對用到的每條sql都對其性能作深刻的研究。而對於整個項目的架構更能夠深刻到linux的內核方面。因此足夠用心就會掌握更多的技術。而寫一個本身的框架會對國內的框架有一個更好的理解和容忍度。好比我在寫框架的時候用到的默認值和建議值都是基於我本身的項目。由於這個框架在咱們內部不少的離線項目均可以用,我在考慮他們的具體環境怎樣設置更加合適。可是再遠一點,別人用的時候,怎麼設置合理,性能曲線我還在研究中。像dubbo這種開源框架也沒能在這方面給出一個特別好的文檔。

週末輕鬆一下:

  週末在家開電腦,兒子在旁邊千萬不要打開數據庫。不然他的小手在鍵盤上劃一下,你就會見識到什麼叫真正的噩夢。

  兒子特別黏我,我總想找藉口把兒子推給他爸。昨晚他有粘着個人時候,我說:跟你爸下象棋去。兒子找了半天,藍棋子少兩個,因此他想在手機上玩。我說:將紅棋子那兩個也拿走就能夠公平的玩了嘛。他爹平靜的說:恩,沒有車和將隨便下。 當場笑的肚子疼。

  我要寫文,他爹帶着兒子去外面玩。臨走很溫柔的說:你手機快沒電了,記得充。我一會兒就感動了,好細心,暖男。再一想,前面他還說過讓我在家訂好烤串,5點半送到他好回來吃!其實我要表達的意思是:人家之因此擔憂我手機沒電,只是怕他的烤串送來聯繫不到我/哭笑

相關文章
相關標籤/搜索