2021年10月20日 星期三

利用 Azure DevOps 備份 Gitea 資料

原本打算使用 Azure DevOps 的 GIT 功能,公司的同事們可以方便分享 GIT 控管,
但是使用者一多,就要另外收費,對於小公司來說也是一筆負擔,
於是想到利用 NAS  架設 GIT,再將資料同步到 DevOps 當成備援,
萬一 NAS 掛了,還可以由 DevOps 上還原回來,
公司也不用額外購買相關硬體備份,
所以我利用 QNap NAS 的 Docker 功能 Contaniner Station ,建立 Gitea 的容器,
再利用 Gitea 的 WebAPI 功能開發一個工具,幫忙建立多個全新的 GIT 資料倉,
幸好有前人寫了 .NET 版的 Gitea.API for .NET DLL 可以直接拿來用
二話不說先抓下來開發小工具,


有興趣的可以自行下載 程式碼
開發環境為 C# VS2019 .NET 4.5 , 參考元件 Gitea.API.DLL V 1.0.1.0


根據先前寫的 Shell , 請參考 : [轉移原有 Git 資料到 Azure DevOps 上]
我甚至將這個功能拆成2個 shell,
一個是抓GIT資料倉下來的功能,一個是推送遠端功能,
目的只是保持資料夾同步,並利用排程自動執行
這樣就可以利用自已建立的 Gitea Server,
不受微軟超過5人要收費的限制,還可以利用 Azure DevOps 當成雲端備援,且不限空間,
若使用亞馬遜的 AWS CodeCommit ,還有流量限制和 Git 指令次數限制,


第一個 Shell 只為了把遠端的資料倉抓到本地端
filename='GitDIR.txt'
#因為是由網路芳鄰抓取,若是HTTPS請換成對應的 URL
sourcePath='W:'

exec < $filename
while read line
do
	#--- 只將 .GIT 資料倉抓取下來
	#檢查資料夾是否存在,若存在就刪除重抓
	if [ -d "$line" ] ; then
		echo "$line 存在"
		rm -rf $line
		echo "刪除資料夾 ./$line/"
	fi
	#從將原始資料倉從 Server 上抓下來,使用 bare 參數表示只抓 .git 隱藏檔內的資料
	echo "抓取 W:/$line/"
	#git clone --bare W:/$line/
	git clone --bare $sourcePath/$line/
done



第二個 Shell 是將本地端的資料倉覆蓋掉遠端的資料倉
filename='GitDIR.txt'
url='https://要上傳的帳號名稱@dev.azure.com/要上傳的帳號名稱/'

## 讀取檔案
exec < $filename
while read line
do
	#--- 將 GIT 轉移到另一個 Git Server
	#檢查資料夾是否存在,若存在就刪除重抓
	if [ -d "$line" ] ; then
		cd ${line}
		#將目前的資料倉同步到 Azure DevOps 的 GIT Server
		git push --mirror $url$line/_git/$line
		cd ..
		pwd=$(dirname $(readlink -f "$0"))
		echo "完成 Push ,切換路徑 : $pwd"
	else
		echo "找不到資料倉 $line "
	fi
	
done


至於如何應用? 就看實際狀況而定了。

轉移原有 Git 資料到 Azure DevOps 上

原先在公司的 GIT Server 因為老闆擔心,所有程式碼放到雲端,萬一被駭客偷走糟了,
所以一直以來我們的做法是放在公司的電腦主機,以網路芳鄰的方式連進去取資料,
同事們就以 VPN 的方式連回辦公室取資料,
我們也針對這些動作寫了一套 Git Shell 幫忙把資料抓下來,否則開發一個產品需要用到四五十個專案,單單同步也夠嗆了...

問題是使用 VPN 的速度本身就不快,所有相關專案同步下來大約要半小時多,確實有點久,
但我們也忍了幾年這麼使用,
最近老闆突然開竅了,同意把版本控管上雲端,於是我們 Survey 了幾家可行方案,決定用 Microsoft Azure DevOps ,

雖然 Azure DevOps 前5人免費,超過每人每月收6美元,比亞馬遜的 AWS CodeCommit 超過5人每人每月1美元來得貴,
但 Azure DevOps 不限空間和流量,也沒 Git 指令限制,還是比較符合目前所需,

Azure  提供 CLI 方式,可直接對 DevOps 下指令,
參考官網如何安裝 Azure CLI ,使用 PowerShell 安裝並登入成功後,寫個自動建立資料倉的 PowerShell,記得副檔名要改成 .ps1 (Win10後可由檔案總管執行 ps1 其它版本好像不行,我統一都是在 powershell 中切換目錄執行)

$file = Get-Content ".\GitFolder.txt"
	foreach ($element in $file) 
	{ 
		Write-Host "Creat " $element 
		az devops project create --name $element
	}
	Write-Output "OK 完成"
Read-Host -Prompt '按下 Enter 結束 !'

這個 Shell 主要功能就是把 GitFolder.txt 裡面所列的 GIT 資料倉名稱全部建立到 DevOps 上,

建立完成後就準備把舊的 GIT 資料倉搬移到 DevOps 上了,
這個做法很簡單,
1. 先將目前所有 GIT 資料倉抓一份到本地端
2. 推送到目的 GIT server

就這樣,2步驟

將以下語法存成 xxxx.sh 的檔案,使用 Git Bash 執行就可以了
filename='GitFolder.txt'
url='https://要上傳的帳號名稱@dev.azure.com/要上傳的帳號名稱/'

#因為是由網路芳鄰抓取,若是HTTPS請換成對應的 URL
sourcePath='W:'

## 讀取檔案
exec < $filename
while read line
do
	#--- 將 GIT 轉移到另一個 Git Server
	#檢查資料夾是否存在,若存在就刪除重抓
	if [ -d "$line" ] ; then
		echo "$line 存在"
		rm -rf $line
		echo "刪除資料夾 ./$line/"
	fi
	
	#先將原始資料倉從 Server 上抓下來,使用 bare 參數表示只抓 .git 隱藏檔內的資料
	echo "抓取 $sourcePath/$line/"
	#git clone --bare W:/$line/ 
	git clone --bare $sourcePath/$line/ 
	cd ${line}
	#將抓下來的最新資料倉同步到新的 GIT Server
	git push --mirror $url$line/_git/$line
	cd ..
	#刪除剛抓下來的資料倉,若使用 rm -d 刪除資料夾會有錯誤,因為資料夾不是空的
	rm -rf $line
	echo "刪除資料夾 ./$line/"
	
done


做完以上步驟,就成功把舊的 GIT 資料倉轉移到 Azure DevOps 上了。

2021年7月16日 星期五

如何提升寫入 Log Performance

撰寫程式時,多少都需要寫 LOG 記錄,
一般使用時都不成問題,不過當需要大量寫入資料就會發現一點點的效能差異影響是很大的,
之前寫了一個 MQTT Broker(Server) ,平時自己用都沒問題,
但最近測試大量連線和封包傳送,就發現速度慢了一截,
LOG 的第三方元件我使用 NLOG,
平時有需要,就一筆一筆寫入 Log,
若需要大量寫入,我會修改 NLog 設定,以 Buffer 方式寫入
大概的設定如下所示
 
一般不使用 Buffer方式
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true">
  <targets>
    <target Name="TraceTarget" xsi:type="File" layout="${level:uppercase=true:padding=-5} ${date:format=yyyy\-MM\-dd\ HH\:mm\:ss\.fffffff} - ${message}" fileName="${basedir}/Log/Trace/Trace.log" archiveFileName="${basedir}/Log/Trace/Trace${date:yyyyMMdd\_HH}.{#}.log" archiveAboveSize="5242880" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" maxArchiveFiles="100" encoding="UTF-8" />
  </targets>
  <rules>
    <logger Name="Trace" minlevel="DEBUG" writeTo="TraceTarget" LogWvalue="DEBUG" />
  </rules>
</nlog>

使用 Buffer 方式
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true">
  <targets>
    <target xsi:type="BufferingWrapper" Name="TraceBufferingWrapper" bufferSize="100">
      <target Name="TraceTarget" xsi:type="File" layout="${level:uppercase=true:padding=-5} ${date:format=yyyy\-MM\-dd\ HH\:mm\:ss\.fffffff} - ${message}" fileName="${basedir}/Log/Trace/Trace.log" archiveFileName="${basedir}/Log/Trace/Trace${date:yyyyMMdd\_HH}.{#}.log" archiveAboveSize="5242880" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" maxArchiveFiles="100" encoding="UTF-8" />
    </target>
  </targets>
  <rules>
    <logger Name="Trace" minlevel="DEBUG" writeTo="TraceBufferingWrapper" LogWvalue="DEBUG" />
  </rules>
</nlog>

雖然這樣的寫法就能提升效能,不過程式中寫 Log 的次數頻繁,內部效能還需要再進一步修正,
利用 ConcurrentQueue 先將要寫的 Log 內容推到 Queue 中,每隔 500 ms 再將 Queue 中的內容取出,並寫入 Log
    private string _logSetFile = @".\LogSetting.xml";
    ConcurrentQueue<string> _logQ = new ConcurrentQueue<string>();
    System.Timers.Timer _timer = new System.Timers.Timer();

    public LogTest()
    {
        if (System.IO.File.Exists(_logSetFile) == false)
        {
            try
            { Logger.CreatDefaultLogSettingFile(_logSetFile); }
            catch (Exception ex)
            { MessageBox.Show(ex.ToString()); return; }
        }
        Logger.Initialze(_logSetFile);

        _timer.Elapsed += LogTimeElapsed;
        _timer.Interval = 500;

    }
    
    public void StartLogTestWithQ()
    {
        Logger.CreateInstance("LogTestWithQ");
        _timer.Start();
        _sw2.Start();
        for (int i = 1; i <= 10000; i++)
        {
            _logQ.Enqueue("第 " + i.ToString() + " 筆");
        }
        _sw2.Done();
        _logQ.Enqueue("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
        MessageBox.Show("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
    }
    public void StartLogTestWithoutQ()
    {
        Logger.CreateInstance("LogTestWithoutQ");
        _sw2.Start();
        for (int i = 1; i <= 10000; i++)
        {
            Logger.LogMessage("LogTestWithoutQ", "第 " + i.ToString() + " 筆", LogLevel.Debug);
        }
        _sw2.Done();
        Logger.LogMessage("LogTestWithoutQ", "測試完成花費時間  " + _sw2.ElapsedTime().ToString(), LogLevel.Debug);
        MessageBox.Show("測試完成花費時間  " + _sw2.ElapsedTime().ToString());
    }

    /// <summary>
    /// 利用時間計數,一次寫入 Log ,不要占用太多資源
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="e"></param>
    private void LogTimeElapsed(object obj, System.Timers.ElapsedEventArgs e)
    {
        StringBuilder sb = new StringBuilder();
        while (_logQ.Count > 0)
        {
            string result = "";
            if (_logQ.TryDequeue(out result))
            {
                Logger.LogMessage("LogTestWithQ", result, LogLevel.Debug);
            }
        }

    }


經過測試果然需要 Log Queue 搭配 NLog Buffer 的方式速度才會快
測試寫入 10000 筆資料,結果如下
//LogTestWithQ none Buffer (Log File 寫入時間長,但程式執行時間短)
DEBUG 2021-07-16 10:52:22.2869563 - 第 1 筆
DEBUG 2021-07-16 10:52:28.7547517 - 測試完成花費時間  0.0014641

//LogTestWithoutQ none Buffer (速度最慢)
DEBUG 2021-07-16 10:52:11.8478062 - 第 1 筆
DEBUG 2021-07-16 10:52:16.2525868 - 測試完成花費時間  4.4130297


//LogTestWithQ use Buffer (***速度最快)
DEBUG 2021-07-16 11:01:58.9000957 - 第 1 筆
DEBUG 2021-07-16 11:01:58.9549269 - 測試完成花費時間  0.0018626

//LogTestWithoutQ use Buffer
DEBUG 2021-07-16 11:01:53.5231884 - 第 1 筆
DEBUG 2021-07-16 11:01:53.6112196 - 測試完成花費時間  0.102756

熱門文章