[開源]KJFramework.Message 高性能二進制消息框架 -- 對於數組的極致性優化


框架的介紹: 


1. 已經完成了對於消息內部類型的支持(int, short, long, uint, ushort, ulong, bool ,float, double, byte, sbyte, byte[], decimal, DateTime, IntPtr, Guid)
2. 支持內部序列化元數據(.NET可序列化的對象)
3. 對於智能對象內部的“智能對象”提供支持。 支持迭代元數據轉換。


[說明:]
KJFramework框架中內置了一套,可以將網絡傳輸實體自動轉換為二進制元數據的能力框架。
使用此框架,將使得用戶不在關心底層傳輸時對於二進制編碼/解碼的繁瑣過程,解放程序員的腦力勞動。
目前此內置框架被初步定義在命名空間:KJFramework.Messages內,擁有獨立Assembly.
對於智能二進制消息框架, 內部提供了對於諸多類型的支持,甚至可以用於在不編寫序列化代碼的情況下,
將一個網絡傳輸實體轉換為二進制操作。
這一切的操作,都全部由內部框架完成,通過簡單的屬性標簽,就可以實現很強大的轉換功能。
目前智能二進制消息框架中,仍保留了內部擴展能力,日后擴展后,將會通過配置文件來增強配置能力。

 

新優化

這是一次,針對 固定字節數量類型的數組優化。 我們為一些常用的數組類型定制了
極致性的托管指針優化方案,通過這個解決方案,直接降低了GC的壓力,指定數組類型
在具有更多元素的場景下序列化/反序列化,將會更快速!
這次更新所影響的數組類型: int[], short[], float[], bool[], Guid[], double[]
經過測試后我們不難發現,此次更新將會在特定場景中平均比原來的解決方案快一倍!
特定場景如下:
   #智能對象內部含有多個數組,並且數組的類型為這次更新所支持的類型
   #數組元素很多

好吧,我們來看一下這次更新的具體改動:
-----------------------------------------------------------
1. 修改了序列化/反序列化引擎的處理流程,為特定類型的數組做特殊優化處理
2. 為這次更新所支持的數組類型新建了ArrayProcessor.
3. 向外部公開了一個數組優化處理器映射類ArrayTypeProcessorMapping,如果使用者不使用此類
則除去本次更新以外的數組類型都會走原來的老分支邏輯。 如果使用者為指定類型數組創建processor
並注冊到開放的映射類中,我們的引擎將會優先使用已經注冊的相應processor!

 

新能力

1. 新增輕量級序列化/反序列化幫助器  

   (1). 增加了DataHelper, 這是一個輕量級的序列化/反序列化幫助器
         哦,是的,框架中除了需要為智能對象提供序列化/反序列化之外,我們的程序中
         還會經常涉及到一些其余數據類型的二進制數據操作,在以往,我們不得不單獨為
         這些類型做數據處理, 現在,這一切都會變得容易起來,那就是我們提供了輕量級的
         DataHelper! 好吧,當你的工作中涉及到int, long, array,等等小數據類型的二進制操作時,
         就來嘗試一下吧。

2. 新增對於.NET可空值類型的支持:


     (1). 這是一個令人振奮人心的消息,不是嗎? 當我們定制協議的時候,再也不會為了某些值類型的協議字段是否需要下發而苦惱了!現在,一切都變得更加美好,如果我們在定制自定義實體的時候,期望某些值類型字段在某些場景下不需要參與序列化,以省去
消息序列化的大小,那么,嘗試把這些字段設置為可空類型吧(比如 int?,double?,short?......等等).
     (2). 通過對於可空類型序列化/反序列化成本的評估,我們發現,為當前框架加入此能力,並不會明顯的拖慢原有框架性能,
只會在原有速度上平均慢去 60~100ms+(量級序列化/反序列化的效果), 所以,我們認為這種成本是可以接受的。
2. 增加了對於可空類型序列化/反序列化的多個單元測試

 

3. 可自定義的固定字節長度類型:

在我們的系統中,通常會存在一種可預知長度的小對象,而這種對象有的時候還是不可缺少的,比如如下這個例子:

 

//這個UID類型只是一個例子,實際中可能還會包含多個能力字段
public class UID : IntellectObject
{
  [IntellectProperty(0)]
  public int UserId { get; set; }
  [IntellectProperty(1)]
  public bool IsDirty { get; set; }
}

 

從上述類定義我們不難看出,一個UID類型標示了一個唯一用戶,這種對象,在很多業務場景下都是非常常見的。
然而,在服務間的通訊協議上,這種類型的傳遞也是很頻繁的。那么,如果通訊協議中頻繁的包含此對象,其實是很消耗性能的,為什么呢?
因為,按照此框架的規定,用戶自定義的類型,在序列化/反序列化的時候是需要遵循TLV模式的。這樣的話,每次每一個字段都會帶有4個字節的長度(L).
對於很多系統中的對象,TLV是沒有問題的,但是總有類似於上述的自定義對象,在序列化的時候完全可以把這4個字節的長度給省去。


那么,該怎么辦呢?讓我們來考慮以下消息場景,好吧,我們定義了一個消息,來傳遞該對象:

 

public class DemoMessage : IntellectObject
{
    [IntellectProperty(0)]
    public UID Uid { get; set; }

    /*Other properties.*/
}

 

每一次這種消息的傳遞,我們都不得不使用TLV的形式,將UID對象進行序列化,每次都有字節數量上的冗余。 那么,我們何不進行以下優化呢?

 

#新能力:

我們特地的考慮到了以上需求,為KJFramework.Message消息框架內部增加了對於此場景的支持。對於新版本的更新中,我們外露了一個類型
"FixedTypeManager", 通過該類型,使得用戶可以在程序初始化的時候打入自定義的固定字節數類型需求。
當然,我們需要為這個上述的自定義類型提供自身的處理器,並將處理器加入到框架內部的處理器集合中。 這一切看起來就像是這樣:

 

internal class UIDIntellectTypeProcessor : IntellectTypeProcessor
{
    #region Constructor

    public UIDIntellectTypeProcessor()
    {
        _supportedType = typeof (UID);
    }

    #endregion

    #region Overrides of IntellectTypeProcessor

    /// <summary>
    ///     從第三方客戶數據轉換為元數據
    /// </summary>
    /// <param name="attribute">當前字段標注的屬性</param>
    /// <param name="value">第三方客戶數據</param>
    /// <returns>返回轉換后的元數據</returns>
    /// <exception cref="Exception">轉換失敗</exception>
    public override byte[] Process(IntellectPropertyAttribute attribute, object value)
    {
        UID uid = (UID) value;
        //fixed data length.
        byte[] data = new byte[5];
        Buffer.BlockCopy(BitConverter.GetBytes(uid.UserId), 0, data, 0, 4);
        data[4] = (byte) (uid.IsDirty ? 1 : 0);
        return data;
    }

    /// <summary>
    ///     從元數據轉換為第三方客戶數據
    /// </summary>
    /// <param name="attribute">當前字段標注的屬性</param>
    /// <param name="data">元數據</param>
    /// <returns>返回轉換后的第三方客戶數據</returns>
    /// <exception cref="Exception">轉換失敗</exception>
    public override object Process(IntellectPropertyAttribute attribute, byte[] data)
    {
        return Process(attribute, data, 0, 5);
    }

    /// <summary>
    ///     從元數據轉換為第三方客戶數據
    /// </summary>
    /// <param name="attribute">當前字段標注的屬性</param>
    /// <param name="data">元數據</param>
    /// <param name="offset">元數據所在的偏移量</param>
    /// <param name="length">元數據長度</param>
    /// <returns>返回轉換后的第三方客戶數據</returns>
    /// <exception cref="Exception">轉換失敗</exception>
    public override object Process(IntellectPropertyAttribute attribute, byte[] data, int offset, int length = 0)
    {
        int userId = BitConverter.ToInt32(data, offset);
        bool isDirty = BitConverter.ToBoolean(data, offset + 4);
        return new UID {UserId = userId, IsDirty = isDirty};
    }

    #endregion
}

 

當然,在程序初始化的時候還需要執行一句話,這句話看起來就像這樣:

 

//add UID type to fixed binary data length type.
FixedTypeManager.Add(typeof (UID), 5);

 

這句話的意思是說,我的系統中,我需要為UID類型 設定為支持固定字節數的類型。以后,再遇到UID類型序列化/反序列化的時候,
就會使用給出的字節長度來解析,整個的序列化/反序列化過程中,就不會存在L(4個字節的長度)了。 怎么樣,這一切都變得更簡潔了,不是嗎?
一個完整的Test Case,就像下面這樣~

 

[TestMethod]
[Description("對於自定義可固定字節數類型的測試")]
public void FixedTypeManagaerTest()
{
    //regist customer type processor.
    IntellectTypeProcessorMapping.Instance.Regist(new UIDIntellectTypeProcessor());
    //add UID type to fixed binary data length type.
    FixedTypeManager.Add(typeof (UID), 5);
    FixedFieldMessage fieldMessage = new FixedFieldMessage{Uid = new UID {UserId = 1, IsDirty = true}};
    fieldMessage.Bind();
    Assert.IsTrue(fieldMessage.IsBind);
    //head total length(4) + property 0 id(1) + property 0 value(5)
    Assert.IsTrue(fieldMessage.Body.Length == 10);
    PrintBytes(fieldMessage.Body);

    FixedFieldMessage newObj = IntellectObjectEngine.GetObject<FixedFieldMessage>(fieldMessage.Body);
    Assert.IsNotNull(newObj);
    Assert.IsTrue(newObj.Uid.UserId == 1);
    Assert.IsTrue(newObj.Uid.IsDirty);
}

怎么樣,還不錯吧?  快快來體驗吧!

 

與此框架類似的通用組件:

ProtoBuffer - Google.

 

此框架的應用:

可以將此框架應用到網絡對象的傳輸上,也就是說,當我們做一個分布式系統的時候,

只需要使用此框架,我們將無需再關心底層消息對象的序列化和反序列化細節,這一切的

工作將會由框架內部來完成。

 

性能指標:

此框架的基於.NETFRAMEWORK 4.0開發

測試平台:

CPU: Intel(R)Xeon(R)CPU X5670 @2.93GHz @2.93GHz (2處理器) 

System: Windows Server 2008 R2 Enterprise 

定義: 復雜對象,內部包含了多個數組類型的成員,比如string[], int[],

          內部還嵌套了其余類。

*想看看在這里測試的復雜對象到底有多復雜嗎?  附上測試類的代碼

*在我們的測試中,使用的是下列代碼中的TestObject. 從下列代碼中可以看到,此類型擁有很多的數組,而且還包含了其他的子類型 

 

 

public class TestObject : IntellectObject
    {
        private TestObject1 _obj;
        [IntellectProperty(7)]
        public TestObject1 Obj
        {
            get { return _obj; }
            set { _obj = value; }
        }

        private int[] _mm;
        [IntellectProperty(0)]
        public int[] Mm
        {
            get { return _mm; }
            set { _mm = value; }
        }

        private TestObject1[] _pp;
        [IntellectProperty(27)]
        public TestObject1[] Pp
        {
            get { return _pp; }
            set { _pp = value; }
        }


        private String[] _uu;
        [IntellectProperty(28)]
        public String[] Uu
        {
            get { return _uu; }
            set { _uu = value; }
        }


        private TestObject2[] _nn;
        [IntellectProperty(30)]
        public TestObject2[] Nn
        {
            get { return _nn; }
            set { _nn = value; }
        }

        private String[] _jj;
        [IntellectProperty(26)]
        public String[] Jj
        {
            get { return _jj; }
            set { _jj = value; }
        }

        private int _wokao;
        [IntellectProperty(4)]
        public int Wokao
        {
            get { return _wokao; }
            set { _wokao = value; }
        }

        private int _wocao;
        [IntellectProperty(2)]
        public int Wocao
        {
            get { return _wocao; }
            set { _wocao = value; }
        }

        private string _woqunimade;
        [IntellectProperty(3)]
        public string Woqunimade
        {
            get { return _woqunimade; }
            set { _woqunimade = value; }
        }

        private byte[] _metadata;
        [IntellectProperty(13)]
        public byte[] Metadata
        {
            get { return _metadata; }
            set { _metadata = value; }
        }


        private byte _metadata1;
        [IntellectProperty(15)]
        public byte Metadata1
        {
            get { return _metadata1; }
            set { _metadata1 = value; }
        }

        private TestObject2 _obj2;
        [IntellectProperty(16)]
        public TestObject2 Obj2
        {
            get { return _obj2; }
            set { _obj2 = value; }
        }

        private DateTime _time;
        [IntellectProperty(100)]
        public DateTime Time
        {
            get { return _time; }
            set { _time = value; }
        }

    }



    public class TestObject1 : IntellectObject
    {
        private string _haha;
        [IntellectProperty(0)]
        public string Haha
        {
            get { return _haha; }
            set { _haha = value; }
        }

        private Colors _colors;
        [IntellectProperty(1)]
        public Colors Colors
        {
            get { return _colors; }
            set { _colors = value; }
        }
    }



    [Serializable]
    public class TestObject2 : IClassSerializeObject
    {
        private int _nice;
        public int Nice
        {
            get { return _nice; }
            set { _nice = value; }
        }
    }

 

 

 

 

 *請注意: 由於性能的提升, 我們在2012年02月28日更新了性能指標!

序列化復雜對象(DEBUG):

     .次數 100000: 1100(ms) *此值根據測試機器的配置不同而不同,僅供參考
     .Gen0回收次數: 30
     .Gen1回收次數: 12
     .Gen2回收次數: 1

反序列化復雜對象(DEBUG):

     .次數 100000: 863(ms) *此值根據測試機器的配置不同而不同,僅供參考
     .Gen0回收次數: 22
     .Gen1回收次數: 1
     .Gen2回收次數: 0



序列化復雜對象(RELEASE):

     .次數 100000: 950(ms) *此值根據測試機器的配置不同而不同,僅供參考
     .Gen0回收次數: 30
     .Gen1回收次數: 12
     .Gen2回收次數: 1

反序列化復雜對象(RELEASE):

     .次數 100000: 610(ms) *此值根據測試機器的配置不同而不同,僅供參考
     .Gen0回收次數: 22
     .Gen1回收次數: 1
     .Gen2回收次數: 0



*具體的測試截圖, 請查看源代碼Pictures目錄下的圖片.  

 

 

 

 

數據段格式圖:

 

 

 

 

 

 

更高的自定義需求:  

  在此框架中,對於每一個可以序列化的類型(int, string .....等等),都會為其配備一個智能類型處理器(IIntellectTypeProcessor),在框架的使用中,這些處理器都是默認的,如果,您感覺還有更好的實現能夠加速

當前的序列化或者反序列化流程,那么,您可以在您的系統初始化的時候,使用自己的智能類型處理器來替換系統

現有的。 這樣,就達到了可自定義類型序列化和反序列化的標准和算法 :)  當然,您也可以選擇添加一個新的處理器。

  其次,在KJFramework.Message中,我們能看到,每一個可序列化的字段,都需要一個智能屬性標記[IntellectPropery]

而每個這種標記都需要為序列化的字段提供一個唯一的數字序號,就比如:[IntellectProperty(0)] ,如果當您的自定義類型,需要為一個特殊的字段做特殊處理的時候,可以選擇為一個特殊的編號字段來定制一個特殊的智能類型處理器。

比如,我們的登錄消息,如果有一個字段用來存儲密碼,而我們恰巧需要為此字段進行MD5加密。

  那么,該怎么辦呢?  當然,辦法有很多種,我們可以選擇先進行MD5的加密,然后再賦值,也可以選擇如下的方式:

 

/*以下為代碼示例*/
public class LogonMessage : IntellectObject
{
  [IntellectProperty(0)]
  public string UserName{get;set;}
  
   //可以看到,Password字段的序號為1. 
   //我們就可以單獨添加一個字段處理器 來處理每一個消息實體內包含有序號1的字段
  [IntellectProperty(1)]
  public string Password{get;set;}
}

 

  *請不用擔心,KJFramework.Message內部會對此操作做特殊的支持,好來完成您的特殊需求 :)

 

 

更專業的需求:

   現在我們已經為一個智能對象(IntellectObject)加入了描述自身的能力(New)。 也就是說,使用者隨時隨地

都可以通過如下調用 來得到當前對象的字符串形式描述信息。

   IntellectObject.ToString(); 

  當得到了一個對象的描述信息后,我們就能夠很輕松的將其記錄到日志文件中,這是不是很方便呢?

在日后,我們將會不停地更新這套框架,這也就意味着,會有越來越多的功能在以后會被加入,

如果您感興趣,我們希望您能夠持續關注。

 

附:

   此框架目前已經能夠達到初步的商用層次,完全可以當做網絡消息協議的序列化和反序列化轉換層來使用,

  相信使用過GOOGLE的ProtoBuffer的朋友都應該明白了。我會一直更新此框架,如果您的項目中有此類需求,

  那么您可以選擇嘗試一下這套框架, 不會讓您失望的 :)

 

   項目地址:http://message.codeplex.com/ 

   目前此項目已經發布了Release版本,您可以選擇直接下載二進制文件使用,或者下載源代碼 :)

   如果在使用此框架的途中,有任何問題,您也可以選擇與我聯系,我將提供最優的技術支持。

    QQ:250623008

    Email: Kevin.Jee@live.cn

 

 

 

謝謝. 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM