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

来源:http://www.cnblogs.com/xiexj/archive/2017/07/17/7190521.html
-Advertisement-
Play Games

金庸經典《射雕英雄傳》里,黃蓉為了讓洪七公交自己和靖哥哥武功,天天對師傅美食相待,在做了“玉笛誰家聽落梅”這樣一些世間珍品之後,告訴師傅說今天要做的是"炒白菜"。洪七公露出非常欣賞的眼光,說:“好,我倒要看看你怎樣化腐朽為神奇。”上周五聽了一個我們內部的深度學習講座,基本這方面處於初始探索階段。上周 ...


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

  我現在主要在做兩件事,一件事是媒資介面的併發量上不去,我已經跟領導說好了:給我時間,我會搞定的。我腦子裡的方案有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功底,但是跟人家講,我講不出來到底這個有什麼閃光點,太零碎了,人家聽了就一個反應:不就是一個後臺服務嘛。確實從大的架構層做的就不像一個架構師,僅僅增量上做了一個負載分攤,全量只是簡單的主備。像全量這種既消耗時間又消耗資源的,怎麼能從一開始不做分散式計算呢。於是最近做了一版改造,解決分散式計算,橫向擴展問題。採用redis作為中間通信工具和字典存儲工具,正好和媒資介面的字典數據是一樣的,這樣就可以用這個項目來進行線上本地緩存性能的測試,而不影響最重要的媒資介面服務。本來也想用搜索中間件來存儲數據,解耦資料庫,因為我最終肯定是要做自己的搜索中間件的。但是確實,對於項目來說是可用可不用的東西,還增加維護成本,那就不應該用。想做自己找時間做去。

  

 

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

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

框架核心思想:

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

框架使用方法:

   整個架構體系已經在框架內部處理,業務方只需實現DataService介面,將數據傳入框架,然後按照自己的需求啟動服務即可。DataService介面定義如下:

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 靜兒([email protected])
 *
 */
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;
}

深入技術細節:

  ☆ 關於壓縮:壓縮是遞歸操作,如果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點半送到他好回來吃!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 註意 if(i%prime[j]==0) 不要寫成if(!i%prime[j]) ...
  • 三木運算,三元運算 name = 值1 if 條件 else 值2 深拷貝淺拷貝 str創建一個值不能修改,如果修改在,創建一個 對於int和str 賦值,深拷貝,淺拷貝地址是不變的 對於dict,list,tup 淺拷貝僅拷貝最外層 深拷貝是除了最內層都拷貝 函數 動態參數 萬能參數 *arges ...
  • MultiProcessing模塊是一個優秀的類似多線程MultiThreading模塊處理併發的包 之前接觸過一點這個庫,但是並沒有深入研究,這次閑著無聊就研究了一下,算是解惑吧。 ...
  • 歐拉篩素數: 時間複雜度:O(n) 主要思路:對於每一個合數,讓他的最大的約數把他篩去 ...
  • 題目鏈接 DESCRIPTION INPUT 題目鏈接 DESCRIPTION INPUT INPUT INPUT OUTPUT OUTPUT SAMPLE INPUT 1 4 2 1 2 5 2 3 5 3 4 5 5 5 SAMPLE INPUT 1 4 2 1 2 5 2 3 5 3 4 5 ...
  • 位運算符 &與 |或 ^異或 <<左移 >>右移 >>>無符號右移 ~取反 註意:位運算是針對整數運算的 int i = 6,j = 10; 方式一:利用第三方變數 int k = i; i = j; j = k; 方式二:利用加減法 i = i + j; j = i - j; >j = i + j ...
  • 2017-07-15,這是我學習python的第一天。 首先,python是一門當下很火熱的開發語言,它的創始人是Guido Van Rossum。就目前情況而言,python語言的熱度持續上升,已經超過C#排名第四。Python崇尚優美,簡潔,清晰,是一種優秀並且廣泛使用的語言。 一、Python ...
  • 十進位轉成十六進位: Integer.toHexString(int i) 十進位轉成八進位 Integer.toOctalString(int i) 十進位轉成二進位 Integer.toBinaryString(int i) 十六進位轉成十進位 Integer.valueOf("FFFF",16 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...