計算機的世界,版本號(version)無處不在,無論是發佈的軟件、產品,仍是協議、框架。那什麼是版本號呢css
在這裏是這樣定義的:html
Software versioning is a way to categorize the unique states of computer software as it is developed and released.前端
軟件版本號是對開發、發佈中的軟件的狀態的惟一(unique)歸納。簡單來講,協議就是對一組狀態的手工簽名。做爲程序員,咱們常常用md5來簽名,保證數據完整性、可靠性。可是咱們很難說,對軟件或者協議計算MD5,那麼版本號就是手工維護的簽名。python
爲何須要版本號,是由於軟件(如linux內核)、協議(如http)都是在不斷的發展完善中,也許是修復上一個版本的bug,也許是引入新的特性。固然,不能說有了新的版本就立馬拋棄舊的版本,用戶(廣義的,程序員也是用戶)是不會答應的,新版本也許有更高級的功能,但我用不到;新版本也許性能更好,可是不必定穩定。並且,版本升級是一個複雜的事情,維護老系統的程序員早都離職了,誰敢去升級。還有,開源的、免費的產品一旦放出,就再也不屬於開發者了。所以,多個版本的軟件、協議並存是必然的事情,好比在對於Python語言,無論是官方仍是一些開源組織,都呼籲放棄Python2,轉向python3,但python2仍是活得好好的。只要有多個版本 -- 本質是多組不一樣狀態的軟件 -- 存在,咱們就須要用版本號予以區分。linux
軟件、協議中的版本號,其最大的做用在於避免雞同鴨講。當咱們討論問題的時候,首先得明確你們是在相同的語義環境下,其中,版本號就是一個很重要的context,由於同一個術語在不一樣的版本可能表明的意思徹底不同,好比Python中的range函數。程序員
本文地址:http://www.cnblogs.com/xybaby/p/8403461.htmlweb
版本號的形式並無固定的或者約定俗成的格式,徹底取決於軟件、協議的發佈者。mongodb
數字形式(numerically)的版本號是最爲常見的,好比http1.1,iPhone6, python2.7.3,其中 x.y.z 這種格式又是最爲常見的。a表明大版本(major version),不一樣的a也許是不兼容的;b表明小版本(minor version),同一個大版本中的小版本通常是兼容的,小版本通常新增功能;c通常是修bug(revision)。數據庫
在服務化體系之-兼容性與版本號一文中,做者介紹到,在微服務結構中,服務的升級是高頻度的事情,但服務升級的時候,一些接口是兼容的,而另一些接口而是不兼容的。客戶端不可能與服務端同步升級,所以多個版本的服務並存也是常態。那麼在存在多個版本的服務時,客戶端請求如何路由,就依賴於版本號:瀏覽器
服務的版本號,和軟件的版本號同樣,通常整成三位:
第一位:不兼容的大版本, 如1.0 vs 2.0
第二位:兼容的新功能版本,如1.1 vs 1.2
第三位:兼容的BugFix版本,如1.1.0 vs 1.1.1
果拿着低版本的SDK(如1.0.0) 發起請求,會被服務化框架路由到全部的兼容版本上(如1.1.1,1.2.0),但不會到不兼容的版本上的(如2.0.1)。
當咱們使用一個軟件、協議的時候,瞭解其版本號規則也是有好處的,好比Linux內核,也是x.y.z的形式,如2.6.8,可是第二位y卻有特殊的意義:偶數表示穩定版本;奇數表示測試版本.
上面提到了兼容性,兼容性也是一個很普遍的詞彙,在本文中,專指不一樣版本的軟件、協議能協同工做,這個在通訊協議、網絡接口中很是普遍。在《通訊協議序列化》一文中,做者按部就班,從最簡單的緊湊模式過渡到相似protobuf這種高級模式,在這個過程當中,就提到了兼容性。本節內容都是對原文的引用。
在最簡單的版本中,協議架構是這樣的:
1 struct userbase 2 { 3 unsigned short cmd;//1-get, 2-set, 定義一個short,爲了擴展更多命令(理想那麼豐滿) 4 unsigned char gender; //1 – man , 2-woman, 3 - ?? 5 char name[8]; //固然這裏能夠定義爲 string name;或len + value 組合,爲了敘述方便,就使用簡單定長數據 6 }
種編碼方式,稱之爲緊湊模式,意思是除了數據自己外,沒有一點額外冗餘信息,能夠當作是Raw Data。雖然可讀性差,可是節省內存和帶寬。
可是當須要擴展協議內容的時候,問題就來了。好比,A在基本資料裏面加一個生日字段,而後告訴B:
1 struct userbase 2 { 3 unsigned short cmd; 4 unsigned char gender; 5 unsigned int birthday; 6 char name[8]; 7 }
這是B就犯愁了,收到A的數據包,不知道第3個字段究竟是舊協議中的name字段,仍是新協議中birthday。
這是一個兼容性與可擴展性的問題,而引入版本號,加一個version字段就能解決這個問題
1 struct userbase 2 { 3 unsigned short version; 4 unsigned short cmd; 5 unsigned char gender; 6 unsigned int birthday; 7 char name[8]; 8 }
無論之後協議如何演變,只要version字段不一樣,接收方就可以正確解析協議。
Multi-Version Concurrency Control 多版本併發控制
MVCC是一種併發控制( concurrency control )機制,在RDBMS中有普遍應用。併發控制解決的是數據庫事務acid中的I(Isolation,隔離性),好比一個讀操做與一個寫操做併發執行,如何保證讀操做不讀取到寫操做未提交的數據,即避免髒讀(dirty read)。
要實現隔離性,最簡單的方法是加鎖(Lock-Based Concurrency Control),即一條數據記錄同時只容許一個事務操做,好比並發讀寫的話可使用讀寫鎖。加鎖雖然能解決併發控制的問題,可是在長事務中也會出現鎖的爭用甚至是死鎖的狀況。而MVCC經過爲每個數據項保存多分拷貝,每個事務操做的實際上是數據在某一時間點的一份快照,除非事務被最終提交,那麼其餘事務是沒法讀取到中間狀態的,這就達到了隔離性的要求。
加鎖與MVCC常常配合使用,兩者在理念上有明確的區別,加鎖是悲觀的,認爲很大機率會衝突,因此使用這一行數據以前先加鎖,在解鎖以前其餘人都不能使用這條記錄;而MVCC是樂觀的,認爲衝突的機率較小,因此使用時先不加鎖,若是提交的後面發現衝突了,再自行回滾。
對於一個實現了MVCC的數據存儲引擎,以更新一個記錄爲例,並非在原來的記錄上直接更新,而是拷貝、建立一個更高版本的數據記錄,而後在新的版本上更新。這樣即便同時有其餘事務進行讀操做,也是在一個稍微舊一點的版本上讀取,互不影響。只有當更新記錄的事務提交以後,修改數據庫元數據,其餘事務纔會讀取到最新版本的數據記錄。
但MVCC對於併發寫操做就沒有那麼好使了,多個併發寫在提交的時候極可能會衝突,若是發生衝突,就須要回滾,也能夠經過加鎖的方式來避免併發寫。
網上有不少MVCC在工業界上的實現,好比《輕鬆理解MYSQL MVCC 實現機制》這篇文章中對innodb mvcc使用詳細介紹。
MVCC這種思想在分佈式事務中也能夠借鑑,在劉傑的《分佈式原理介紹》中有相應介紹
咋眼一看,彷佛緩存中的版本號與軟件、協議的版本號不是一回事,不過一細想,其實都是對一組狀態的惟一簽名。版本號在緩存中使用很是普遍,其根本做用在於解決緩存過時、不一致的問題。下面給出幾個例子
對於這個,前端開發人員應該都很熟悉,我只是班門弄斧,作個簡單介紹。詳細的能夠參見《前端資源版本控制的那些事兒》
爲了優化網頁的加載、響應速度,通常會開啓瀏覽器的緩存功能,即瀏覽器會緩存資源文件(js、css)。好比下面的index.html引用了兩個資源文件:
<link rel="stylesheet" href="a.css"></link> <script src="a.js"></script>
在緩存時間內訪問頁面時,瀏覽器不會真正發出請求,而是使用緩存的資源文件。
但這樣也會引入新的問題,那就是當服務端修改html文件與資源文件,發佈以後,客戶端會拉取到最新的index.html,可是讀取到的資源文件有可能仍是舊的 -- 讀取到的是瀏覽器緩存的資源文件。這就暴露了任何緩存最重要的問題,緩存過時的問題,當緩存系統的數據與原數據不一致的時候,就不該當再使用緩存中的數據,而是拉取最新的原數據,同時緩存最新的元數據。
可是在瀏覽器緩存這個實例中,瀏覽器是沒法及時感知到緩存的數據已過時。雖然設置了過時時間(expire),但這個過時時間只是單方面的,只能約束客戶端(瀏覽器)的行爲,服務端並不保證在這個過時時間內不更新內容。這個不由讓我想到lease機制,lease機制保證了在過時時間內不會修改原數據,所以經過緩存讀到的數據必定是最新的。
那麼如何避免瀏覽器讀取到過期的緩存資源文件呢,最經常使用,且通常狀況下也夠用的方法就是加上版本號。
<link rel="stylesheet" href="a.css?v=0.01"></link>
<script src="a.js?v=0.01"></script>
這樣當資源文件變化時,只需修改版本號(上面的v),瀏覽器就會去服務器拉取最新的資源文件。固然,若是每次修改資源文件的時候都手動修改這個版本號,也是一個費力切容易出錯的工做,因此通常都會引入自動化腳本,發佈時自動修改版本號。
關於MongoDB,在我以前的文章也有一些介紹。在這裏討論的是MongoDB中元數據(metadata)的緩存,MongoDB中,元數據主要是每個chunk包含的數據範圍(range),以及chunk與shard的映射關係。元數據是整個系統的核心,須要保證高可用性與強一致性。
如上圖所示,是MongoDB最多見的Sharded Cluster結構。其中,config server存儲系統的元數據;shards真正存儲用戶數據;而mongos緩存元數據,利用元數據指定最佳的執行計劃,也就是路由功能。能夠看到,應用(Client app)直接與mongos交互,實際的線上應用,通常也是mongos與應用程序部署在一塊兒,config server 與 shards對用戶是透明的。
既然應用程序利用mongos上緩存的元數據進行路由,那麼緩存的元數據就必須是準確的,與config server強一致的,不然用戶請求就可能被路由到錯誤的shard上。那麼MongoDB是如何解決的呢,答案就在MongoDB Sharded Cluster 路由策略
簡而言之,就是增長版本號,元數據的每一次變動(chunk的分裂與遷移)都會增長版本號。這個版本號,在shard本地和元數據中都會維護,天然mongos緩存的元數據中也是有版本號的。當請求被mongos路由到某一個shard時,會攜帶mongos上的版本號,若是該版本號低於shard上的版本號,那麼說明mongos上緩存的數據已通過期,須要從新從config server上拉取。
在《python屬性查找》中,介紹了屬性查找的順序,而method屬於類屬性,若是一個method在類中沒有找到,那麼會按照mro的順序在基類查找。那麼,對於在一個多層繼承的類體系中,屬性訪問是否是會很慢呢,理論上確實如此,可是實踐測試的話並不會很明顯。緣由就在於在python2.6中,引入了method cache:
Type objects now have a cache of methods that can reduce the work required to find the correct method implementation for a particular class; once cached, the interpreter doesn’t need to traverse base classes to figure out the right method to call.
可見,python解釋器會緩存訪問過的method,這樣就避免了每次訪問的時候遍歷基類。
可是,Python是動態語言,能夠運行時改變代碼的行爲,也就包括增刪method,這個時候緩存就與原始數據不一致了,Python是這麼解決的
The cache is cleared if a base class or the class itself is modified, so the cache should remain correct even in the face of Python’s dynamic nature.
在源碼(2.7.3)中,每個緩存的entry都是以下的struct
1 struct method_cache_entry { 2 unsigned int version; 3 PyObject *name; /* reference to exactly a str or None */ 4 PyObject *value; /* borrowed */ 5 };
核心的函數_PyType_Lookup以下:
1 PyObject * 2 _PyType_Lookup(PyTypeObject *type, PyObject *name) 3 { 4 Py_ssize_t i, n; 5 PyObject *mro, *res, *base, *dict; 6 unsigned int h; 7 8 if (MCACHE_CACHEABLE_NAME(name) && 9 PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { 10 /* fast path */ 11 h = MCACHE_HASH_METHOD(type, name); 12 if (method_cache[h].version == type->tp_version_tag && 13 method_cache[h].name == name) 14 return method_cache[h].value; 15 } 16 17 /* Look in tp_dict of types in MRO */ 18 mro = type->tp_mro; 19 20 /* If mro is NULL, the type is either not yet initialized 21 by PyType_Ready(), or already cleared by type_clear(). 22 Either way the safest thing to do is to return NULL. */ 23 if (mro == NULL) 24 return NULL; 25 26 res = NULL; 27 assert(PyTuple_Check(mro)); 28 n = PyTuple_GET_SIZE(mro); 29 for (i = 0; i < n; i++) { 30 base = PyTuple_GET_ITEM(mro, i); 31 if (PyClass_Check(base)) 32 dict = ((PyClassObject *)base)->cl_dict; 33 else { 34 assert(PyType_Check(base)); 35 dict = ((PyTypeObject *)base)->tp_dict; 36 } 37 assert(dict && PyDict_Check(dict)); 38 res = PyDict_GetItem(dict, name); 39 if (res != NULL) 40 break; 41 } 42 43 if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) { 44 h = MCACHE_HASH_METHOD(type, name); 45 method_cache[h].version = type->tp_version_tag; 46 method_cache[h].value = res; /* borrowed */ 47 Py_INCREF(name); 48 Py_DECREF(method_cache[h].name); 49 method_cache[h].name = name; 50 } 51 return res; 52 }
代碼邏輯並不複雜,分紅三步
step1: 若是該函數有緩存,且緩存版本號與類型當前版本號一致(method_cache[h].version == type->tp_version_tag),那麼直接返回緩存的結果;不然
step2:經過mro,找出method name對用的method實例
step3:緩存該method實例,版本號設置爲類型當前版本號(method_cache[h].version = type->tp_version_tag)
從上面的幾個例子能夠看出,緩存中的版本號有時也是dirty flag或者lazy 思想的運用:當緩存內容過時的時候,並非當即清空或者從新加載新的數據,而是等到從新訪問緩存的時候再比較版本號,若是不一致再拉取最新數據。
本文並無一個明確的主題,都是我平時發現的版本號(version)在各類場景下的使用,比較有趣的是MVCC與緩存中的使用。固然,我相信還有更多更有趣的使用場景,而本人所接觸的領域比較狹窄,權當拋磚引玉,歡迎各位園友指正與補充。