有趣的版本号

  计算机的世界,版本号(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 路由策略

相关文章
相关标签/搜索