Hessian最早是用於Java的二進位的Web服務,官方 定義:
The Hessian binary web service protocol makes web services usable without requiring a large framework, and without learning yet another alphabet soup of protocols. Because it is a binary protocol, it is well-suited to sending binary data without any need to extend the protocol with attachments.
後來被廣泛用於其它的編譯語言象Python,C++,C#,PHP,Ruby,Erlang等。
其實Hessian除了用於Web方法調用外,還有一個常用的功能就是跨語言的序列化。正是有了高效、緊湊的序列化,Hessian才廣為流傳。
Hessian序列化協議不管是1.0.2規範 還是2.0草案 都是十分完善的,但其中最大的問題就是關於字串(或者XML,在Hessian裡XML的序列化基本與字串一樣)的序列化。
下面是Hessian字串序列化的定義:
1.0:
string ::= (s b16 b8 utf-8-data)* S b16 b8 utf-8-data
2.0:
# UTF-8 encoded character string split into 64k chunksstring ::= x52 b1 b0 <utf8-data> string # non-final chunk ::= 'S' b1 b0 <utf8-data> # string of length # 0-65535 ::= [x00-x1f] <utf8-data> # string of length # 0-31 ::= [x30-x34] <utf8-data> # string of length # 0-1023
我們看到,不管是1.0還是2.0,字串都是“分段”傳輸的。二個版本不同之處在於2.0隻是針對0-31長度和0-1023長度的短字串作了
特殊最佳化。
所謂“分段”就是單個字串的最大長度不能大於65536(2 ^ 16),如果大於這個長度就要按65536進行分段。
舉個例子,假設有個字串長度為65537,那麼Hessian序列化大致為:
s xFF xFF <UTF8-DATA> S x00 x01 <UTF8-DATA>
第一個小寫s表示是字串的分段中的一段,大寫的S表示是最後一段。在S的後面就是2個位元組的長度,注意這裡不是整段位元組的長度,
而是字串的長度!接下來就是UTF8編碼的字串。
照理說,按這個定義不會出現多大問題,為字元國際化以及長、短字串都作了相關的最佳化處理。
但事實上,當字串很大時(為何會很大?想想如果輸出一個很大的XML結果集(2M以上)),這種方案的時間會耗費大量的資源,極為低效。
(1)字元編碼問題。
Hessian字串採用了對英文友好的UTF8編碼,是為減少傳輸的大小。但UTF8是多位元組編碼,且不說對於非英文字元,它增加了傳輸的開銷,
重要的是採用UTF8編碼和解碼必須進行逐位元組的低效率的掃描。當字元多時,這個會成為整個序列化的瓶頸。
從效率的角度來講,個人覺得Hessian採用UTF16最好。UTF8雖然小了一點,處理的開銷可不只是大了一點點。
(2)分段問題。
分段帶來傳輸的一點效能優勢和分開檢驗的好處,但拆分和組合一個巨大的尤其是必須按位元組掃描的字串時,這個帶來了很大的開銷。
(3)字元長度問題。(重點)
Hessian儲存了每個字元段的字元的個數,但沒有儲存整個段位元組的長度。這樣在還原序列化時,只能逐位元組掃描,沒有辦法進行讀取最佳化。逐位元組的驗證UTF8和讀取UTF8字串,在C++、C#或者Java這樣高效率的語言來講,本身不是太大問題,但對於PHP、Python或者Ruby、Erlang等非Unicode的低效能語言來說,就是要命的事情了。
作一簡單測試,當PHP解析Hession 3M的字串(XML),耗時竟然超過30秒,30M得半小時了!而C#都不超過1秒!
看來,想要讓PHP等語言快點,除了標明標明字串的字元個數外還必須標明實際位元組大小,儘管這樣破壞了標準:
string ::= (s b16 b8 C16 C8 utf-8-data)* S b16 b8 C16 C8 utf-8-data
C16和C8就是實際字串在該段的位元組數。有了這個這個標記,PHP等語言就可高效處理Hessian大字串了,30M 1秒鐘!
如果你有更好的辦法,請不吝指教!