天天看點

有趣的版本号

  計算機的世界,版本号(version)無處不在,不管是釋出的軟體、産品,還是協定、架構。那什麼是版本号呢

  

有趣的版本号

  在這裡是這樣定義的:

Software versioning is a way to categorize the unique states of computer software as it is developed and released.

  軟體版本号是對開發、釋出中的軟體的狀态的唯一(unique)概括。簡單來說,協定就是對一組狀态的手工簽名。作為程式員,我們經常用md5來簽名,保證資料完整性、可靠性。但是我們很難說,對軟體或者協定計算MD5,那麼版本号就是手工維護的簽名。

  為什麼需要版本号,是因為軟體(如linux核心)、協定(如http)都是在不斷的發展完善中,也許是修複上一個版本的bug,也許是引入新的特性。當然,不能說有了新的版本就立馬抛棄舊的版本,使用者(廣義的,程式員也是使用者)是不會答應的,新版本也許有更進階的功能,但我用不到;新版本也許性能更好,但是不一定穩定。而且,版本更新是一個複雜的事情,維護老系統的程式員早都離職了,誰敢去更新。還有,開源的、免費的産品一旦放出,就不再屬于開發者了。是以,多個版本的軟體、協定并存是必然的事情,比如在對于Python語言,不管是官方還是一些開源組織,都呼籲放棄Python2,轉向python3,但python2還是活得好好的。隻要有多個版本 -- 本質是多組不同狀态的軟體 -- 存在,我們就需要用版本号予以區分。

  軟體、協定中的版本号,其最大的作用在于避免雞同鴨講。當我們讨論問題的時候,首先得明确大家是在相同的語義環境下,其中,版本号就是一個很重要的context,因為同一個術語在不同的版本可能代表的意思完全不一樣,比如Python中的range函數。

  本文位址:http://www.cnblogs.com/xybaby/p/8403461.html

  版本号的形式并沒有固定的或者約定俗成的格式,完全取決于軟體、協定的釋出者。

  數字形式(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這種進階模式,在這個過程中,就提到了相容性。本節内容都是對原文的引用。

  在最簡單的版本中,協定架構是這樣的:

  種編碼方式,稱之為緊湊模式,意思是除了資料本身外,沒有一點額外備援資訊,可以看成是Raw Data。雖然可讀性差,但是節省記憶體和帶寬。

  但是當需要擴充協定内容的時候,問題就來了。比如,A在基本資料裡面加一個生日字段,然後告訴B:

  這是B就犯愁了,收到A的資料包,不知道第3個字段到底是舊協定中的name字段,還是新協定中birthday。

  這是一個相容性與可擴充性的問題,而引入版本号,加一個version字段就能解決這個問題

  不管以後協定如何演變,隻要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引用了兩個資源檔案:

  在緩存時間内通路頁面時,浏覽器不會真正送出請求,而是使用緩存的資源檔案。

  但這樣也會引入新的問題,那就是當服務端修改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

  核心的函數_PyType_Lookup如下:

有趣的版本号
有趣的版本号

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

通信協定序列化

服務化體系之-相容性與版本号

輕松了解MYSQL MVCC 實作機制

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

MongoDB Sharded Cluster 路由政策

本文版權歸作者xybaby(博文位址:http://www.cnblogs.com/xybaby/)所有,歡迎轉載和商用,請在文章頁面明顯位置給出原文連結并保留此段聲明,否則保留追究法律責任的權利,其他事項,可留言咨詢。