有趣的版本號

  計算機的世界,版本號(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字段不一樣,接收方就可以正確解析協議。

MVCC

  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這種思想在分佈式事務中也能夠借鑑,在劉傑的《分佈式原理介紹》中有相應介紹

緩存中的版本號

  咋眼一看,彷佛緩存中的版本號與軟件、協議的版本號不是一回事,不過一細想,其實都是對一組狀態的惟一簽名。版本號在緩存中使用很是普遍,其根本做用在於解決緩存過時、不一致的問題。下面給出幾個例子

web中的版本號

  對於這個,前端開發人員應該都很熟悉,我只是班門弄斧,作個簡單介紹。詳細的能夠參見《前端資源版本控制的那些事兒

  爲了優化網頁的加載、響應速度,通常會開啓瀏覽器的緩存功能,即瀏覽器會緩存資源文件(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,在我以前的文章也有一些介紹。在這裏討論的是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 cache

  在《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 }
_PyType_Lookup

 

  代碼邏輯並不複雜,分紅三步

  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與緩存中的使用。固然,我相信還有更多更有趣的使用場景,而本人所接觸的領域比較狹窄,權當拋磚引玉,歡迎各位園友指正與補充。

references

通訊協議序列化

服務化體系之-兼容性與版本號

輕鬆理解MYSQL MVCC 實現機制

前端資源版本控制的那些事兒

MongoDB Sharded Cluster 路由策略

相關文章
相關標籤/搜索