在本章節,咱們將從編碼的概念開始,談談常見的一些編碼類型,再接着談談信息論中兩個最重要的編碼過程-壓縮和加密,分析它們在網站中的應用,最後深刻理解在分佈式系統中的編碼過程-序列化。html
對信息論有足夠理解的朋友能夠快速略過前4節,閱讀後面的序列化及思考部分。前端
讓咱們回到數百年前的古代,在那個時候可沒有電話,若是有人想給遠方的朋友傳個信,那就只能修書一封,飛鴿傳書了。web
咱們分析下這個過程當中,某人將想說的話(語言),寫到(存儲到)紙上(文字),而後飛鴿傳書(傳輸)。古代識字率比較低,萬一朋友不識字,還得請隔壁的教書先生念信,將文字表述爲語言。算法
在這個場景中,咱們看到了一句話經歷了語言到文字,而後文字再到語言的轉化,這個過程在信息論中被稱爲編碼。所以,廣義上的編碼是信息在不一樣形式下的表示和轉換的過程。數據庫
編碼有兩個重要的組成部分,一是多種表現形式,好比語言和文字,二是不一樣編碼形式間的對應關係,好比「編碼」讀做「[biān mǎ]」,寫做「編碼」。而編碼的主要應用場景是傳輸和存儲。特別的,逆向的編碼過程咱們稱爲解碼。json
固然,編碼自己是一個大主題,咱們今天談到的編碼,將是狹義上的編碼,指計算機中,特別是軟件開發中,不一樣數據格式的表示和轉化過程。後端
現代計算機一般是基於二進制的,用0和1表狀態,二進制屬於進位計數制一種,另外一種十六進制也在計算機中常用,而生活中經常使用的是十進制。瀏覽器
之因此使用二進制,是由於電子計算機利用的是通電、斷電(或高電平、低電平)兩種狀態。這兩種狀態能夠表示爲1/0,正好與二進制相對應,一個狀態表示一個二進制位,單位稱爲bit,而後即可以編碼表示任一數字,好比7=111。安全
再進一步,若是讓第一位表示符號,即正負,那麼負數就能夠編碼了;若是約定前n位表整數位,後m位表小數位,這就是浮點數了。固然在實際中,負數咱們是經過補碼的方式表示,浮點也有單精度和雙精度之分,本文不作進一步闡述。服務器
另外一方面,0、1也與布爾代數理論中的兩種邏輯值「True」、「False」對應,再結合邏輯電路,這就爲計算機提供了邏輯基礎。
Niklaus Wirth寫過一本書《算法 + 數據結構 = 程序》,那麼在這裏,邏輯運算+數值編碼=現代計算機
光有數字的編碼是不夠的,在過去的幾千年,人類留下來的歷史的信息大可能是靠文字記錄,數字從0到9不過10個,而漢字則以十萬計,還不包括一些繁體和變體,其餘語言文字就更別說了。所以,在計算機中,咱們也須要一種特定的編碼規則來表示文字。
ASCII碼是英語字符與二進制位的對應的編碼機制,採用8個二進制位來表示一個字符(8bit),8bit能夠表示從0000000到11111111的數字,共256位。而英文大小寫共52個,再加上數字字符,空格,換行的特殊符號,8bit徹底足夠了。而實際上ASCII碼只規定了128個字符的編碼,只用了8bit的後7位,第一位統一爲0。這就有了另外一個單位字節(byte),即1byte佔8bit。
隨着計算機的推廣,其餘國家有本身的語言和文字,它們也須要在計算機中表示,ASCII只用了後7位,因此最開始一些歐洲的語言好比法語,經過擴展ASCII碼到8位來表示對應語言的字符,這個字符集即是ISO-8859-1。
ISO-8859-1容量仍是256位,其餘語言的文字,好比數以萬計的漢字就沒法加入了,所以1981年中國推出了漢字字符編碼GB2312,爲了兼容ASCII,英文字母仍是用1byte表示,漢字則用兩個字節表示。
既然漢字有對應編碼了,還有日,韓等諸多文字也得編碼。再者,若是咱們用漢字寫郵件發往英文地區,也會有編碼不一樣沒法識別的問題,這些就須要一個統一的解決方案了。
不一樣國家使用不一樣編碼天然就會致使信息溝通的困難,咱們天然就想到了須要一個統一的標準來編碼這個世界上全部的字符,因而Unicode就誕生了。
Unicode之所被稱爲字符集,由於它並非一種編碼規則,僅僅只規定了字符與數值的對應關係-碼點(Code Point),但沒有規定具體實現,好比用幾個字節存儲。
常見的實現有以下幾種:
通常來講,國外的網站廣泛用ISO-8859-1,國內一般使用GBK,面向國際的網站一般基於UTF-8。
編碼的兼容性
本小節咱們談到了多種字符編碼,不少編碼都只適用於特定語言,最後UNICODE的出現才統一編碼標準,這裏就體現了編碼須要考慮的第一個方面-兼容性,在制定編碼時須要評估跨語言,跨系統的場景。
在計算機中廣泛數據的存儲方式是經過文件存放的,因此,各類格式的文件自己就是一種編碼方式,通常來講咱們能夠經過後綴名來區分,好比.txt,.doc,*.gif等等,固然後綴是能夠被篡改,嚴格一點能夠根據文件頭信息來分別。
常見的文件類型好比:
文本文件(*.txt)
文本文件都是以2.2節談到字符編碼方式存儲的,在Windows下,用記事本打開txt文件,點擊文件->另存爲,能夠看到可選的編碼方式,默認是ANSI(這是一種Windows用來兼容不一樣語言的編碼方式),也能夠改成Unicode等等。
音頻文件(*.mp3)
在生活中,咱們一般用MP3來指代音頻文件,實際上MP3只是音頻文件的一種編碼方式,常見的音頻編碼類型有MP三、WMA、OGG…,它們在音質,存儲,兼容性等方面各有所長。
其餘還有視頻文件,壓縮文件等等,在計算機中,每一種文件的格式都是一種編碼格式。
考慮到這麼一段大寫的數據AAABBB,長度爲6。
若是咱們定義一種編碼機制:
AAA=A3 BBB=B3
那麼上面的數據能夠表示爲A3B3,長度僅爲4。一樣的編碼,再給這一段信息D3C3,你也能輕鬆轉義成DDDCCC。咱們能夠看到後一種表示方式的長度更小。這種編碼方式叫遊程編碼。
將數據經過特定的編碼方式減小數據包大小的機制咱們稱之爲壓縮,編碼機制即指壓縮算法,而上面的AAA=A3這一對應關係,稱之爲字典,它的解碼過程天然是解壓了。
在信息論中,壓縮被稱爲信源編碼,這是一種對輸入信息進行編碼,優化信息和壓縮信息的編碼方式。
咱們對比下編碼與壓縮:
咱們再定義一個字典,用01來編碼字符串。
AA=0 BB=1 CC=01
那麼就有以下編碼:
AACCCC=00101
若是咱們換一種編碼方式:
AA=0 BB=01 CC=1
那麼就有
AACCCC=011
由於CC出現了兩次,若是咱們用較短的編碼來表示出現頻率高的字符,就能夠獲得更好的壓縮比(壓縮先後數據大小的比例)。
那麼,如何計算出這樣一個字典?這就有了很是經典的壓縮算法-霍夫曼編碼。
Huffman算法是一種依據字符出現頻率來計算最優壓縮字典的算法。
算法摘要以下
好比如下頻率
編碼可表示爲
其餘壓縮算法還有LZ77,以及基於相似原理的一些變種算法,本文再也不闡述。
在基於這些壓縮算法的常見程序有gzip,winrar,7z等等,咱們將在3.3節介紹壓縮的應用場景。
以下是兩張圖片,都是經過一張圖處理得來的,它們的色彩,清晰度可有差別?
肉眼是很難看出,實際上右圖的像素比左圖少5%,並經過放大到同等大小的。
上一節談到的Huffman算法是一種無損壓縮算法,也就是說,壓縮後的數據能夠經過逆向過程,解壓還原出原數據的。
而像圖片這種容許一部分數據丟失但不影響效果的場景,咱們就能夠用有損壓縮的算法下降文件大小。
有損壓縮是利用了人類視覺,聽覺等方面不敏感的特性,容許壓縮過程當中損失必定的信息;雖然不能徹底恢復原數據,但能夠得到更大的壓縮比。所以,常見的有損壓縮每每應用在圖片,音頻,視頻等文件編碼上。
最後,咱們談談壓縮的一些應用場景。
談編碼的概念時,介紹了編碼是用在傳輸和存儲中的,咱們就從這兩個角度談談壓縮的應用。
在前端部分,諸如Nginx,Tomcat等Web服務器均可以配置gzip壓縮,瀏覽器在發送請求時經過添加Accept-Encoding: gzip;
請求頭,從而實現各種資源的壓縮傳輸。
文件壓縮也是經常使用場景,咱們曾遇到過因爲圖片沒有壓縮,致使一個網頁須要傳輸上百M數據的問題,嚴重影響用戶體驗。
在後端的各種分佈式系統中,一般會根據特定的數據格式自定義壓縮規則,咱們會在第5節談談序列化的例子。
在數據庫和日誌系統中都提供了壓縮功能,好比MySQL就提供了壓縮表選項,能夠在建表時進行配置。騰訊的TMySQL和阿里的AliSQL等還提供針對列壓縮的特性,爲blob/text等大字段進行靈活的壓縮配置。
在分佈式文件系統中,文件的壓縮應用就普遍了,像用戶上傳的圖片,不少網站都會進行統一的格式轉換,針對不一樣的場景,壓縮到不一樣的分辨率,好比縮略圖就是經過高壓縮比的有損壓縮而來。特別的,HDFS(Hadoop Distributed File System)還提供了合併壓縮存儲的方案,咱們會在本書第二部分,分佈式文件系統一文中談談這個話題。
編碼的效率
咱們知道,衡量算法複雜度分爲時間複雜度和空間複雜度,其實說的是算法的效率從空間和時間兩個方面來衡量。而壓縮正是提升了編碼的空間效率,所以,效率是編碼須要考慮的第二個方面。
回到本文開頭談到了寄信場景,假設咱們寄的不是家書,而是一名將軍要下達給部隊的命令函,那這封信就不能白紙黑字的寫清楚了,萬一信使被抓就泄密了,咱們須要作一些加密的手段。
密碼本是最古老的保密方式之一,在北宋的軍事著做《武經總要》記載着一種名爲「字驗」的加密手段。
他們收集了軍隊中經常使用的40種戰鬥狀況,如請弓、請糧料、都將病等等,而後戰前約定一首五言律詩編碼,五言律詩共8行,每行5字,正好40字,每一個字對應一種戰鬥狀況。這樣傳令時只需傳一個字就能夠完成通信了。
如王維的使至塞上:
單車欲問邊,屬國過居延。 徵蓬出漢塞,歸雁入胡天。 大漠孤煙直,長河落日圓。 蕭關逢候騎,都護在燕然。
回到計算機中,咱們爲文本消息定義一種編碼,稱爲字符x+5,每一個字符用它後面的第5個字符代替,如:
A->F B->G
經過這種方式,只要x+5這個規則不被破解,就起到了加密做用,而接收者只須要知道x+5的值,就能夠解碼出x的值。因爲加密和解密都用到了同一個密鑰,因此這個方式咱們稱爲對稱加密。
固然,實際應用中的加密算法比這個複雜的多,一般密鑰長度在幾十到數百位不等。常見的對稱加密算法有DES、IDEA、RC等等。
咱們對比下編碼與加密:
因爲對稱加密只有一個密鑰,一旦公開全部人均可以解密信息,在多方傳輸中存在安全風險。所以也就有了非對稱加密,這種方式將加密密鑰(公鑰)和解密密鑰(私鑰)分開,任何發信人均可以用公鑰發送加密信息,但只有收信人有私鑰解密。
網上有這麼一段形象的解釋:
非對稱加密算法就是別人想要發信息給你。你先造一個保險箱。保險箱關上是不用鑰匙的。把這個保險箱開着遞給別人。別人發信息就把信息放進去而後「砰」一下關上還給你。只有你有鑰匙信息能夠查看信息,除非別人有其它方式破解。
咱們來簡單描述下非對稱加密算法RSA的編解碼過程。
RSA基於這樣一個事實,將兩個大素數相乘是很容易,但從結果中分解出它們則很是耗時。選取大素數可長達數千位,固然這樣的素數選取出來也不容易,一般是用Miller Rabin等素數測試算法來檢測素數,會存在必定的誤判。
令
N=A×B T=(A-1)×(B-1)
則
N=37×23=851 T=(37-1)×(23-1)=36×22=792
2.選取第三個隨機數(E)做爲公鑰,它必須與T互質,則私鑰(D)知足
D×E mod T = 1
如選E(公鑰)=5
則
D×5 mod 792=1 D(私鑰)=317
3.把明文(M)加密爲密文(C):
C=M^E mod N
4.解密爲
M=C^D mod N
如 M(明文)=7
C(密文)=7^5 mod 851 = 638 M(明文)=638^317 mod 851 =7
Hash函數是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數,能夠充當原數據的映射,用於快速查找和校驗。
通常狀況下因爲輸出Hash值的空間一般遠小於輸入的空間,不一樣的輸入可能會計算出相同的Hash值,因此沒法從Hash來惟一的肯定輸入值,會出現值衝突,難以求逆。可是,經過精心設計的Hash算法,如MD5,SHA,其值域極大,在現有計算機條件實現了無衝突,同時求逆也更加不可能。所以,對於一些無需解密的數據,這些安全Hash算法就能用來充當加密工具。
雖然Hash自己不可逆,但並不是不可破解,如字符串123456,咱們用MD5算法計算出它的Hash值爲7e8feb2276322ecddd4423b649dfd4d9,這樣,只要看到是這段hash值的地方,咱們就能夠認爲這段數據是123456。這就有了一種暴力破解方式-彩虹表。
彩虹表是預先將數以百億級的各類字符串,特別是有規律的字符串的Hash計算出來,整個表的大小一般在數百G以上,而後經過碰撞的方式破解Hash值。
既然通常字符串的Hash表是能夠被計算的,咱們能夠經過往輸入項裏面加一段信息,咱們稱之爲鹽(salt),好比123456改成salt123456,而後再計算Hash值。以後每次查詢的時候,都在輸入項前面加上salt,而後再查詢,只要不被人知道鹽值,用普通的彩虹表是沒法破解信息的。
前面談到各類加密方式並不是沒法破解,只是以現有計算機的運算能可能須要十幾甚至上百年的時間。這就有了密碼學領域的另外一個通識,就是加密方式並不是要作到絕對沒法破解,不少信息是有時效性的,只須要在必定年限內沒法破解,過時以後的價值就不大了,這也是不少國家機密會設置保密期的緣由。
所以,就有了另外一種對抗破解的方式-慢Hash,慢哈希是指執行這個哈希函數很是慢,這樣暴力破解須要枚舉遍歷全部可能結果時,就須要花上很是很是長的時間。因爲慢Hash耗計算量,咱們每每會把這些計算量轉嫁到客戶端,讓客戶端計算完Hash傳給服務端校驗。
咱們也從傳輸和存儲方面談談加密的應用。
常見的加密傳輸有HTTPS,SSH等協議,它們每每是非對稱加密與對稱加密算法相結合制定的,咱們會在第二章通訊協議中進一步談談HTTPS的內容。
存儲方面最重要是密碼存儲了,一般基於安全Hash,附加加鹽、慢hash等手段進行復雜的加密。
編碼的安全性
在這一節,咱們談到了多種加密方式,這些方式都是爲了保證數據在存儲和傳輸過程當中的安全,那麼加密天然就是體現了編碼的安全性。
在如今的分佈式系統中,特別是以微服務爲表明的分佈式服務技術,序列化是其中的關鍵技術點之一。
在深刻理解序列化以前,咱們先來準確描述下它的概念。
序列化是將數據結構或對象轉換爲可存儲(如在文件或內存緩衝區)或傳輸(如經過網絡)的格式並稍後重建(可能在不一樣的計算機中)的過程。
數據結構和對象這個不用解釋,但可存儲或傳輸的格式最多見的天然是字節了,除此以外還有基於字符串的JSON。
好比,有這麼一個對象
class Person { String name="Jim"; }
能夠用JSON表示爲{ name:"Jim"}
。所以,一些JSON框架,好比Java中的FastJson,也能夠歸類到序列化框架之中。至於重建的過程即是反序列化了。
咱們對比下編碼的概念:編碼是信息在不一樣形式下的表示和轉換的過程。在這裏,數據結構、對象、字節、json是否是信息的表示方式?反序列化是否是對應解碼?序列化的過程是否是一個編碼過程?
理解這一點,咱們就能夠回答出,序列化的本質是編碼。
既然這樣,咱們就能夠從編碼的角度來分析序列化了。前面的文章,咱們從字符集中談到編碼的兼容性,從壓縮中談到編碼的效率,從加密中談到編碼的安全性,那邊這裏咱們就從這三個方面談談序列化。
序列化的兼容性有這麼三個方面。
開發語言有JAVA,C++等等不少種,若是涉及到多語言應用之間的交互,那麼序列化框架必須有多種語言的實現版本。固然,若是一個公司的內部應用全是用同一語言開發的,天然就無需引入這種兼容性。
平臺方面,好比瀏覽器和服務器這兩個平臺,常見JSON格式就能很好的兼容兩者,因此咱們每每提供基於JSON的API。
以JAVA爲例,JAVA的複雜對象場景包括繼承、組合、泛型、循環依賴等等。
以循環依賴爲例,循環依賴是指兩個以上對象相互引用,如
class A { B b; } class B { A a; }
一旦它們相互引用,一些序列化框架在序列化時,序列化A發現裏面包含B,而後又序列化B,發現B又包含A,又序列化A…,這樣就出現死循環了。
一般在一些JSON序列化框架中會遇到這種問題,JSON格式自己也難以體現循環依賴關係。
現實場景每每更復雜,咱們就遇到過一個接口包含數百個對象,上千字段,各類繼承、泛型,在測試時,四五種序列化框架,僅一種能應對這種複雜場景。
着重提一句,兼容性是一個序列化框架最重要的指標,沒有之一,必要的時候咱們能夠犧牲效率,但序列化自己不能出錯,特別是核心流程上,由於序列化一旦出錯,整個業務邏輯就直接崩潰了。
序列化的場景一般是用於不一樣服務器間的數據傳輸,在JAVA中,咱們會經過jar的方式,讓client和server引入同一個類。
若是有一天由於業務須要,要在某個類中增減字段,但咱們沒法作到client和server同時升級,這樣,client和server的序列化結構就不一致了,一些依賴字段順序的序列化框架這時就每每會出現問題,因此,可以兼容這種不一致場景也是評估一個序列化框架
的指標之一。
咱們談效率無非就是時間和空間。
序列化的時間效率天然就是序列化與反序列化的耗時,在不考慮硬件的場景下,一般取決於語言效率,數據量等等。好比在JAVA,基於反射的方法調用和普通調用是有性能差別的,而序列化每每基於反射操做,所以對這一塊進行優化就能提高必定的效率。
空間效率一般就是壓縮了,好比一個boolean值True,將它序列化成True
,和序列化成T
,二者之間長度是不同的,不少序列化框架就是經過這種自定義的壓縮字典來處理數據。
序列化一般是用於傳輸場景,傳輸的安全性在內網下基本是無需考慮的,對於提供出去的外網服務,好比與客戶端APP的通訊,其安全性每每是基於傳輸協議自己來實現的,好比基於HTTPS來傳輸。
另外一個安全風險來自於注入攻擊,注入攻擊每每是由於本應用來查詢或保存的字符串被當成命令代碼執行了。特別是基於JSON的序列化框架,因爲JSON自己是基於字符串的,若是裏面的字符串被看成代碼執行了,就會產生比較嚴重的後果。
咱們從瞭解編碼是信息的轉換過程開始,從兼容性、效率及安全性三個方面分析了編碼的原理。而後,談到了分佈式系統中的序列化,而序列化的本質是編碼,所以,也從一樣的三個方面分析了序列化。
那麼,咱們能夠進一步:
再進一步:
還再進一步:
如:
回答完這些問題,感受像是造了一把錘子,而後全部的東西當釘子,但這並非個人初衷,回到本書的序中寫到的,這本書真正要寫的是我對第一性原理,結構化思惟,系統思惟這些的思惟方式的應用。
既然講了底層的邏輯思惟了,其實本文最底層的邏輯是用到了邏輯思惟的兩種方式-概括和演繹。咱們從字符,壓縮,加密的編碼中概括出了一套分析編碼的結構,而後演繹出序列化的第一性原理是編碼,用編碼的結構去分析了序列化,框架,甚至現實生活中的產品,最後完成了這一篇系統化的文章。
附本文的導圖。
以上就是編碼的相關內容,咱們會在後面的協議,服務化框架等文章裏面將本章內容關聯起來,進一步完善整個編碼的知識體系。
編碼:隱匿在計算機軟硬件背後的語言 [美] Charles Petzold
信息論、編碼與密碼學 [印]博斯
Serialization and Unserialization:https://web.archive.org/web/20150405013606/http://isocpp.org/wiki/faq/serialization
Hessian協議規範:http://hessian.caucho.com/doc/hessian-serialization.html
做者:初開
發表於:博客園
本文基於 知識共享-署名-非商業性使用-禁止演繹 4.0 國際許可協議發佈,轉載必須保留署名及連接。