[开源]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.
對于智能二進制消息框架, 內部提供了對于諸多類型的支持,甚至可以用于在不編寫序列化代碼的情況下,
將一個網絡傳輸實體轉換為二進制操作。
這一切的操作,都全部由內部框架完成,通過簡單的屬性標簽,就可以實現很強大的轉換功能。
目前智能二進制消息框架中,仍保留了內部擴展能力,日后擴展后,將會通過配置文件來增強配置能力。
新能力
1. 新增對于.NET可空值類型的支持:
(1). 這是一個令人振奮人心的消息,不是嗎? 當我們定制協議的時候,再也不會為了某些值類型的協議字段是否需要下發而苦惱了!現在,一切都變得更加美好,如果我們在定制自定義實體的時候,期望某些值類型字段在某些場景下不需要參與序列化,以省去
消息序列化的大小,那么,嘗試把這些字段設置為可空類型吧(比如 int?,double?,short?......等等).
(2). 通過對于可空類型序列化/反序列化成本的評估,我們發現,為當前框架加入此能力,并不會明顯的拖慢原有框架性能,
只會在原有速度上平均慢去 60~100ms+(量級序列化/反序列化的效果), 所以,我們認為這種成本是可以接受的。
2. 增加了對于可空類型序列化/反序列化的多個單元測試
2. 可自定義的固定字節長度類型:
在我們的系統中,通常會存在一種可預知長度的小對象,而這種對象有的時候還是不可缺少的,比如如下這個例子:
View Code?
//這個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個字節的長度給省去。
那么,該怎么辦呢?讓我們來考慮以下消息場景,好吧,我們定義了一個消息,來傳遞該對象:
View Code?
public class DemoMessage : IntellectObject
{
[IntellectProperty(0)]
public UID Uid { get; set; }
/*Other properties.*/
}
每一次這種消息的傳遞,我們都不得不使用TLV的形式,將UID對象進行序列化,每次都有字節數量上的冗余。 那么,我們何不進行以下優化呢?
#新能力:
我們特地的考慮到了以上需求,為KJFramework.Message消息框架內部增加了對于此場景的支持。對于新版本的更新中,我們外露了一個類型
"FixedTypeManager", 通過該類型,使得用戶可以在程序初始化的時候打入自定義的固定字節數類型需求。
當然,我們需要為這個上述的自定義類型提供自身的處理器,并將處理器加入到框架內部的處理器集合中。 這一切看起來就像是這樣:
View Code?
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,就像下面這樣~
View Code?
[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. 從下列代碼中可以看到,此類型擁有很多的數組,而且還包含了其他的子類型
View Code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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的加密,然后再賦值,也可以選擇如下的方式:
View Code?
1
2
3
4
5
6
7
8
9
10
11
/*以下為代碼示例*/
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
轉載于:https://blog.51cto.com/515632/791810
總結
以上是生活随笔為你收集整理的[开源]KJFramework.Message 智能二进制消息框架 -- 性能提升的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一天一点T-SQL:使用登录触发器进行安
- 下一篇: DHCP服务器在企业网络中的应用