1. [TxF] #1. 初探 Transactional NTFS

    其實想用 TxF (Transactional NTFS, 交易式的 NTFS) 已經很久了,不過老是被一些雜事卡著,到過年期間才有空好好研究一下。這篇主要是介紹而已,就不講太多 Code, 先以瞭解 TxF 是什麼,及如何善用它等等方面為主。詳細的用法,就等後面幾篇吧!

    Transactional NTFS, 中文是 “交易式NTFS”,或是常見到的縮寫 “TxF”,早期的相關文章也有人寫 “TxFS”。這是在 Windows Vista / 2008 推出時,首次正式提供的功能。雖然它叫作 Transactional NTFS, 實際上它並不是一個新的檔案系統,而是一組新的 API (跟原有的檔案處理 API 幾乎是一對一的對應), 支援你用交易的方式操作檔案。一起推出的還有 Transactional Registry (TxR),一樣是有對應的 windows API,只不過它處理的對像是 windows registry,不是檔案…。

    用這種方式處理檔案的讀寫動作,有種很神奇的感覺,過去都只在資料庫裡有機會這樣用,現在檔案的處理也可以了。配合像是 DTC 這類交易協調器的支援,甚至可以把檔案的處理及資料庫的處理,通通都包裝成一個交易來進行,一但任何一個環節失敗,都可以回復到最初的狀態,感覺好像是在用 DB,而不是寫檔案… 目前官方並沒有推出 managed code 的含式庫,現在要用只有幾種選擇:

    1. 直接用 C / C++ 呼叫 win32 api
    2. 用 P/Invoke,在 C# 裡呼叫 win32 api
    3. 找那些別人 (非官方) 包裝好的 .net class library ..

    這些用起來都有點不踏實,畢竟用 P/Invoke 不是長久之計,總覺的遲早會被替換掉。不過即使如此,這項還是掩蓋不了這技術的價值。我貼一段自己寫的 sample code,讓還沒用過的人體會一下,寫檔案還支援交易處理的 “爽度” …

    // 建立 KTM transaction object
    IntPtr transaction = CreateTransaction(
        IntPtr.Zero,
        IntPtr.Zero,
        0, 0, 0, 0,
        null);
    
    string[] files = new string[] {
        @"c:\file1.txt",
        @"c:\file2.txt",
        @"c:\file3.txt"};
    
    try
    {
        foreach (string file in files)
        {
            // 使用支援交易的 delete file API
            if (DeleteFileTransactedW(file, transaction) == false)
            {
                // 刪除失敗
                throw new InvalidOperationException();
            }
        }
    
        // 認可交易
        CommitTransaction(transaction);
    }
    catch (Exception ex)
    {
        // 還原交易
        RollbackTransaction(transaction);
    }
    CloseHandle(transaction);
    

    範例裡用到的幾個 method, 像是 CreateTransaction(), DeleteFileTransactedW(), CommitTransaction(), RollbackTransaction() … 等等,都是透過 P/Invoke 的方式呼叫的 win32 api… 除了用的型別不如 pure .net class library 般直覺之外,這樣的 code 也已經很簡單了,短短卅行就可以搞定…

    雖然這樣的 code 實在不大合我胃口,但是它畢竟是個堪用的方案… 對於 code 有潔癖的,可以考慮其它的用法。前面是最基本的 API call,如果你不滿意,MS自家的技術 DTC (Distributed Transaction Coordinator) 當然也支援 TxF。DTC 可以提供額外的好處,就是允許你做分散式的交易管理。意思是你配合 DTC,就可以把 Local File I/O 跟 database access 整合在同一個交易範圍內。

    這邊的 sample code 我就不貼了,在 managed code 裡去呼叫到 COM 的那堆介面 (啥 QueryInterface 的) 實在跟 .NET programming 的 style 有點格格不入… 在 C# 的世界裡,應該用 TransactionScope 才對。在 MS 的世界裡,TxF + TxR + DB 都可以是 TransactionScope 內的一部份。這部份的 Sample Code 我一樣先不貼了,不然貼一堆 code 又沒篇幅說明,感覺很混…

    其實,MS 該做的都做了,唯一缺的就是它竟然沒正式的併入 .NET Framework 內的一員… 如果 TxF 真的是你想用的東西,倒是有個 OpenSource Project 可以考慮一下: AlphaFS, 它的目標是能替換掉 namespace System.IO.*, 所以很多你常用的 class library, 它都有對等一樣用法的版本,當然它提供了更多的功能及改善… 其中 TxF 的支援就在內,你想用 TxF 來開發軟體的話,這是個不錯的選擇…

    總之,這篇只是個開始,目的是想先 “預覽” 一下 TxF 的能耐,及未來它配 DTC / TransactionScope 後,能怎麼應用它的方式,還有其它可用的相關資源。接下來我會陸續整理一些相關的研究心得.. (別太期待,大概一兩週生一篇就很偷笑了 XD),下回見 !

    參考資訊:

    1. AlphaFS: Brining Advanced Windows FileSystem Support to .NET
      http://alphafs.codeplex.com/
    2. MSDN magazine (July 2007): Enhance Your Apps With File System Transactions
      http://msdn.microsoft.com/en-us/magazine/cc163388.aspx
    3. B# .NET BLOG: Windows Vista - Introducing TxF In C#
      Part 1: Transacted File Delete
      Part 2: Using System.Transactions and the DTC
      Part 3: CreateFileTransacted Demo
    4. Code Project: Windows Vista TxF / TxR
      http://www.codeproject.com/KB/vista/KTM.aspx
    5. BLOG: Because we can
      http://blogs.msdn.com/because_we_can/archive/2005/05/18/419809.aspx
      Discussion and explanation relating to the Transactional NTFS feature coming in Longhorn, plus any other interesting anecdotes…
    6. Performance Consoderations for Transactional NTFS
      http://msdn.microsoft.com/en-us/library/ee240893(VS.85).aspx
    7. When to Use Transactional NTFS
      http://msdn.microsoft.com/en-us/library/aa365738(VS.85).aspx

    2010/03/18 系列文章: 交易式 (Transactional) NTFS .NET C# MSDN SQL Transactional NTFS 作業系統

  2. [Tips] 用 “磁碟鏡像" 更換硬碟 #2, Windows 2003 的跨距磁區

    上一篇,介紹了如何用windows server内建的磁碟鏡像 (Mirror) 更換硬碟後,這次剛好有windows 2003,就拿來試了一下...

    廢話就不多說了,先來看一下 2003 的步驟,再來看看跟 2008 / 2008R2 差在那裡:

    1. 原本的樣子,磁碟1 (8.00GB) 是舊的硬碟,磁碟2 (16.00GB) 是要換上來的新硬碟:
      image001

    2. 磁碟1 + 磁碟2 做成鏡像 (MIRROR):
      image002


      等待重新同步化 (Resync) 完成:
      image003


    3. Resync 完成後,移除鏡像:
      image004


      移除鏡像完成後的狀態:
      image005 

    4. 用 2003 的延伸磁區,把磁碟2後面沒用到的空間也併進 D: 來
      image006


      合併之後的狀態:
      image007 



    整個步驟跟前一篇都差不多,主要都是靠鏡像(MIRROR)搬完資料後,再把新硬碟多的可用空間併進來。唯一的差別就在這裡: 2003 的 "延伸磁區" (英文: extend volume) 是可以把兩個硬碟,或是兩個分割區併在一起使用。在磁碟管理員還是看的到這些分割區的存在。這就有點像 JBOD (Just a Bunch Of Disks) 的模式。

    不過在 2008 之後 (其實 vista 也是),這個功能就改了。原本的名字 extend volumn 現在改成直接擴大原分割區,把原分割區後面可用的空間都納進來,就像你砍掉再重建一個大的分割區一樣,只是資料會留著不會掉。當然有 extend 也有相對的 shrink volume, 這功能會把分割區縮小,騰出空間來讓你多切一個分割區...。而原本 JBOD 模式的功能,則改為 span disk, 用起來效果就如上圖一樣,當然你願意的話,也可以把多顆硬碟 (可以不同容量) 通通併成一個來使用,最多可以併到 32 顆硬碟...。

    沒經過這次更換硬碟,還真沒發現 2008 / vista 總算內建這組 extend / shrink volume 的功能進來。雖然很陽春,不過已經很實用了,在過去這種動作是得搬出像 partition magic 這類軟體才能做的到,而這種東西每次用起來心裡都會毛毛的,深怕一不小心就把資料都給毀了...。

    這篇小品文章就記到這裡,希望有幫到需要的人 :D

    2010/03/11 Tips 技術隨筆 有的沒的

  3. [Tips] 用 “磁碟鏡像" 無痛更換硬碟

    老是寫一堆像外星文,沒人看的懂的 multi-threading 文章,偶爾也來換換口味吧。前陣子把 SERVER 的兩顆 750GB HDD (RAID1) 升級了一下,升級成兩顆 1.5TB HDD (RAID1)。更換硬碟是小事,不過這個硬碟上有些服務,如網站,資料庫,還有一些重要資料,跟分享資料夾,想一想要更換也是挺囉唆的...。

    想了幾個辦法,不過都不符合我既懶又挑毛病的個性... 原本考慮的更換方式有:

    1. 硬上... 新硬碟裝上去,檔案COPY過去,能停的服務就停掉,花一堆時間搬資料,然後再恢復原服務 (如 SQL、IIS等等)。至於比較麻煩的,像是目錄分享的,只好移掉再重建。中斷服務的時間就是從關機裝硬碟,一直到全部完成為止。
    2. 用 disk clone 的工具,如 true image / ghost 之類的軟體。不過這樣通常得停機做 clone, 750GB 也是要執行好一段時間,加上我這次買的是 Advanced Format 的硬碟,用這類工具會有效能的問題,事後還得校正回來… ouch, 算了...

    想來想去,我用的是 windows server, 有內建的 Mirror set, 就拿來用一用好了。我真正作的是把 mirror set 的兩顆都升級,不過為了簡化說明,我底下的例子就只以替換一般的硬碟就好,反正道理是一樣的。

    說穿了不值錢,就是用 mirror 的磁碟複製特性,加上 extend volume 的功能,我除了需要關機裝上新硬碟之外,其它包含資料複製的所有時間,原服務都不用中斷 (當然速度會慢一點),所有服務的設定也都不用修改,算是既無腦又防呆的完美方案... 只要簡單的按幾下滑鼠就可以達成我的目標。

     

    直接來看看怎麼作的吧! 很簡單,先利用 mirror 把資料轉移到新硬碟... 然後中斷 mirror, 再用 extend volume 把磁區大小調大就可以收工了。來看分解步驟:

     

    1. 我原本的磁碟組態是長這個樣子 (圖我是事後用 VM 模擬的),其中 Disk 1 (8.00GB) 就是我要換掉的...
      image 


    2. 關機裝上一顆新的硬碟 Disk 2 之後,變成這樣 (Disk 2 (16.00GB) 是新的硬碟):
      image


    3. 把 disk1 / disk2 做成磁碟鏡像之後,就變成這樣:
       image


    4. 鏡像做好,Resync 完成後,就可以中斷鏡像了。中斷之後變成這個樣子:
      image 


    5. 目前為止,看來磁碟轉移已經完成了,剩下就是想辦法把後面的空間吃進來。接下來的就用 Extend Volume 括大 D: 的大小:
      image 
      image

     

    之後就大功告成了。這方法不但簡單,而且整個過程中,全程 D:\ 都可以正常的使用。除了 (1) --> (2) 需要關機裝硬碟之外, (2) ~ (5) 全程,放在 D:\ 的 SQL DB,IIS 網站,還有 pagefile 通通都正常運作中。有了 windows server 的磁碟陣列還真是好用啊 :D

    不過,事情也是有黑暗面的... 這個方法是有幾個小缺點啦...

    1. 被迫使用 "動態磁碟" :
      dynamic disk 其實不是什麼缺點啦,不過你要是會用到其它 OS,像 linux 之類的,或是用其它的磁碟管理軟體,可能就不認得了。這是缺點之一..
    2. 只有 windows server 可以使用:
      desktop os (windows 2000 pro, xp, vista, win7) 都只支援部份的磁碟管理功能,這作法關鍵的 mirror 是不支援的... 只能乾瞪眼 @@
    3. Extend Volume 只適用 windows 2008 以上的版本:
      記得 windows 2003 只支援到 span volume, 在 disk manager 裡還是會顯示兩個 partition, 只不過只會有一個磁碟機代號,容量會是兩個加起來的而已。一樣啦,是不會有什麼大問題,看起來不大爽而已 XD

    偶爾換個口味,貼些小品文章,這邊我也不是很專業,有啥更好的作法也歡迎留 comment 啊 :D

    2010/03/06 Tips 技術隨筆 敗家 有的沒的

  4. 升級到 BlogEngine.NET 1.6.0.0 了!

    1.6.0.0 出來一陣子了,不過到過年才有空升級... 主要的原因只有一個,就是最近 spam comments 實在太多了 =_=,新版對於這類問題的處理比較像樣一點..

    其它改進還有 nested comments 跟其它一堆改進,就不一一列出來了,有興趣的人可到官方網站去看看。

    試了一下,升級後沒啥大問題,除了 CSS 有點走樣之外... 如果各位有發現什麼地方漏掉了,請再通知我 :D

    祝大家新年快樂 :D

    2010/02/19 BlogEngine.NET 有的沒的

  5. [設計案例] 清除Cache物件 #2. Create Custom CacheDependency

    上一篇廢話了這麼多,其實重點只有一個,我這次打算利用 CacheDependency 的機制,只要一聲令下,我想移除的 cache item 就會因為 CacheDependency 的關係自動失效,而不用很辛苦的拿著 cache key 一個一個移除。 我的想法是用 tags 的概念,建立起一套靠某個 tag 就能對應到一組 cache item,然後將它移除。開始之前先來想像一下 code 寫好長什麼樣子:

    透過 tags 來控制 cache items 的範例程式[copy code]
            static void Main(string[] args)
            {
                string[] urls = new string[] {
                    "http://columns.chicken-house.net/",
                    // 共 50 組網址... 略
                };
    
                foreach (string url in urls)
                {
                    DownloadData(new Uri(url));
                }
    
                Console.ReadLine();
                TaggingCacheDependency.DependencyDispose("funp.com");
                Console.ReadLine();
            }
    
            private static void Info(string key, object value, CacheItemRemovedReason reason)
            {
                    Console.WriteLine("Remove: {0}", key);
            }
    
            private static byte[] DownloadData(Uri sourceURL)
            {
                byte[] buffer = (byte[])HttpRuntime.Cache[sourceURL.ToString()];
    
                if (buffer == null)
                {
                    // 直接到指定網址下載。略...
                    buffer = null;
    
                    HttpRuntime.Cache.Add(
                        sourceURL.ToString(),
                        buffer,
                        new TaggingCacheDependency(sourceURL.Host, sourceURL.Scheme),
                        Cache.NoAbsoluteExpiration,
                        TimeSpan.FromSeconds(600),
                        CacheItemPriority.NotRemovable,
                        Info);
                }
    
                return buffer;
            }
        }
    
        這段 sample code 做的事很簡單,程式準備了 50 個網址清單,用 for-loop 一個一個下載。下載的 method: DownloadData(Uri sourceURL) 會先檢查 cache 是否已經有資料,沒有才真正下載 (不過下載的細節不是本篇要講的,所以就直接略過了...)。 而主程式的最後一行,則是想要把指定網站 ( funp.com ) 下載的所有資料,都從 cache 移除。為了方便觀看程式結果,我特地加上了 callback method, 當 cache item 被移除時, 會在畫面顯示資訊: image 由執行結果來看,果然被移出 cache 的都是來在 funp.com 的網址... 接著來看看程式碼中出現的 TaggingCacheDependecny 是怎麼實作的。相關的 code 如下:
    TaggingCacheDependency 的實作 [copy code]
        public class TaggingCacheDependency : CacheDependency
        {
            private static Dictionary<string, List<TaggingCacheDependency>> _lists = new Dictionary<string, List<TaggingCacheDependency>>();
    
            public TaggingCacheDependency(params string[] tags)
            {
                foreach (string tag in tags)
                {
                    if (_lists.ContainsKey(tag) == false)
                    {
                        _lists.Add(tag, new List<TaggingCacheDependency>());
                    }
                    _lists[tag].Add(this);
                }
                this.SetUtcLastModified(DateTime.MinValue);
                this.FinishInit();
            }
    
            public static void DependencyDispose(string tag)
            {
                if (_lists.ContainsKey(tag) == true)
                {
                    foreach (TaggingCacheDependency tcd in _lists[tag])
                    {
                        tcd.NotifyDependencyChanged(null, EventArgs.Empty);
                    }
                    _lists[tag].Clear();
                    _lists.Remove(tag);
                }
            }
        }
      30行不到... 其實程式很簡單,TaggingCacheDependency 繼承自 CacheDependency, 額外宣告一個靜態的 Dictionary<string, List<TaggingCacheDependency>> 來處理各個標簽及 TaggingCacheDependency 的關係,剩下的就沒什麼了。呼叫 DependencyDispose( ) 就可以通知 .NET Cache 機制,將相關的 cache item 移除。 用法很簡單,當你要把任何物件放進 cache 時,只要用 TaggingCacheDependency 物件來標示它的 tag:
    把物件加進 Cache, 配上 TaggingCacheDependency ...[copy code]
                    HttpRuntime.Cache.Add(
                        sourceURL.ToString(),
                        buffer,
                        new TaggingCacheDependency(sourceURL.Host, sourceURL.Scheme),
                        Cache.NoAbsoluteExpiration,
                        TimeSpan.FromSeconds(600),
                        CacheItemPriority.NotRemovable,
                        Info);
    在這個例子裡 (line 4), 直接在 TaggingCacheDependency 物件的 constructor 上直接標上 tags, 在此例是直接把網址的 hostname, scheme 兩個部份當作 tag, 未來就可以依照這兩種資訊直接讓 cache 裡的相關物件失效。 而要下令讓 Cache 內有標上某個 tag 的 cache item 失效,只要這行:  
    將標為 "funp.com" 的 cache item 設為失效的 cache item[copy code]
                TaggingCacheDependency.DependencyDispose("funp.com");
      結果就會如同上面的程式範例一樣,還留在 cache 的該網址下載資料,在這一瞬間通通都會被清掉...   用這種方式,是不是比拿到 key 再去呼叫 Cache.Remove( key ) 的方式簡單多了呢? 同時也能夠更快速的處理複雜的移除機制。其實運用 tagging 的方式只是一例,需要的話你也可以設計合適的 CacheDependency 類別。 以下是本篇文章的兩個附加參考檔案:
    Download File - URL清單
     

    2009/12/19 設計案例: 清除 Cache 物件 .NET C# MSDN Tips 技術隨筆 有的沒的 物件導向