一晃开博已经2年多了,回顾两年来,进步也是不小。

一开始自学iOS,然后发现不是做前端的料,就赶紧转为自学Python,总算是后来成功转行。工作中,因为项目需要,又开始学习Java。两年来Web后端方面进步不小。

现在后头看看去年写的原创博客,槽点也是满满。不过不打算删除,放在那里,也是一种记录。

最近,因为项目需要,又花时间学了一点JavaScript。不过项目的框架居然是AngularJS 1.4!@!#$%^$&^*^@$#@!(此处省略吐槽一万字)。为了稍稍接近一下潮流,我自个儿花时间学了点Vue。可能是TypeScript的原因吧,感觉Vue竟然比Angular 4+ 更接近于AngularJS。

再然后就想到了把这个blog更新一下。

更新的主要驱动力在于:

  1. 原博客的样式copy自一个教程,只有少许改动。
  2. 原博客的Python程序虽然使用了tornado框架,但是仍然没有用到非阻塞的特性。因为之前的版本里,前端使用bottle,于tornado通过wsgi接口交互。而tornado在wsgi接口里只是一个单线程的同步框架。当时文档看的不仔细,后来才发现,汗 (-.-‘’‘)
  3. 原博客的数据表结构设计的不合理。一个文档应该允许有多个标签。所以在关系型数据库里,这种情况应该设计两个表,一个文档表,一个标签表,然后关联起来。从而实现一对多关系。那时真的是太菜了呀!
  4. 原博客的发布也很水。先在本地数据库发布,然后手动修改标签完成后,使用shell脚本全量复制本地数据到远端。

于是乎,经过几天的折腾,我搞出来一套新的blog程序。

  1. 前端使用vue来写,部署的时候使用nginx来反向代理。
  2. 后端依然使用Tornado,这次仅仅是用来返回json结构体,所以不需要复杂的前端功能,用不到wsgi接口,所以实现了非阻塞特性。
  3. 数据库系统使用了IBM的Cloudant。Cloudant是一个CoucnDB的商业衍生版。IBM承诺用户可以免费的无限期使用该系统,但是会有一些限制:数据容量不大于1GB,每秒钟请求数量也有限制,不过对于一个个人blog来说“这都不是事儿”。CouchDB,像MongoDB一样,是一个文档型数据库。所以,多标签问题在这里完全不是问题。
  4. 博客发布就更好处理了。CouchDB的最大优点就是他的复制特性。每次都是增量同步。CouchDB使用类似于Git那样的使用revision历史来记录文档变更,可以很好的处理文档冲突问题。

不过这个博客还是有点问题,手机端的显示样式调的不对。我的插件里,本地调试的页面就是正常的,但是远程请求过来的页面就有问题。真是醉了。我果然不是做前端的料。-.-‘’’

这次更新blog系统最大的收获就是CouchDB。

之前只使用MySQL、PostgreSQL这样的关系型数据库,这次算是体验了一下NoSQL。尽管CouchDB没有MongoDB来的主流,但是读一读文档真的是收获不小。

其实CouchDB一开始吸引我的是,其协议使用的REST HTTP api。一开始打算的是直接用前端来调数据库。后来发现想多了。因为直接用js调,会暴露出大量的数据库细节在用户侧请求里。从安全的角度讲这是不对的。何况Cloudant对请求频率有限制,总不能让其他人也知道的我的数据库位置吧。所以还是得有一个api接口封装一下细节。

虽然都是文档型数据库,CouchDB和MongoDB却有很大的不同。CouchDB是一个AP系统,放弃了C强一致性,只实现“最终一致性”。而MongoDB却是一个CP系统,为了实现强一致性,放弃了A可用性。不过这里的可用性,更倾向于是对性能的追求。

话说CP系统的发展真是强大,MongoDB、HBase、Redis、Memcache这些常见的NoSQL竟然都是CP系统。关系型数据库都集中在CA这边。CP系统都是利用P特性,让自己的性能压过了CA系统。而AP这面CouchDB名气大于实际应用,只有Cassandra发展比较好,Dynamo也只是偶尔见到。当然,我本来就见得少,不要打我(逃

CouchDB的文档里对CAP特性做过说明:

一致性意味着读写操作之间有同步操作的存在。任何一个追求一致性的模块,在请求压力下不断增加的情况,最终都会成为系统性能的瓶颈。

这一点其实可以类比线程同步使用的锁对象。大量线程阻塞在锁的获取上,开始排队,势必影响到系统的响应延迟和吞吐量。

实际上,CouchDB的基础决定了它更适合成为一个AP系统。CouchDB是用Erlang写的。Erlang是一个最初为电信系统业务设计的编程语言,对并发有很高的要求。网上搜到有说Erlang VM特别针对大核心数量(16+)的系统有过优化,能够很好的利用超常规计算资源。BEAM, 也就是Erlang VM,使用Actor模型作为其并发的基础模型。而Actor模型的特点是,每一个Actor只能顺序处理自己消息队列的消息。而一致性在这种场景下会存在“消息插队”的需要。而Erlang VM的线程模型不允许同步操作,杜绝了“插队”的发生。所以一致性只能在Actor处理完相应消息后才能实现。

成也萧何,败也萧何。CouchDB使用REST HTTP api来读写数据和管理库表。这首先省去了为各门语言编写驱动的需要。任何语言只要能够发送http请求,就能使用CouchDB。但是,目前CouchDB只支持HTTP/1.1,而HTTP/1.1是一个阻塞式的通信协议。每一个连接一次只能处理一个回话。当会话的请求和响应都处理完毕后,才能处理下一个回话。下一个时代的HTTP/2能够通过将会话的数据封装为二进制帧并做好会话标记从而支持连接复用,同时处理多个会话,显著提高了通信效率。可惜的是,CouchDB目前还不支持HTTP/2,这可能是下一个大版本的重大特性吧。CouchDB有一颗高并发的心脏,却只长了一张阻塞式的嘴。心疼30秒。。。

前面说了CouchDB使用revision历史来记录文档的更新。

其实CouchDB里,每一个文档都有一个revision,作为唯一独立标记。哪怕是新的更新文档,也有自己的revision。旧的文档也会保留一段时间。在一段时间后的,数据空间压缩过程中(有点像GC里的内存空间压缩),文档内容消失,但是revision历史会一直保留下来。在多节点系统中,不同节点之间的复制操作也是通过revision记录来同步。

如果更新的请求里的原始revision和当前系统里的文档revision不一样,系统就会拒绝更新。修改操作需要更新一下本地数据,然后在提交修改。很像是Git里的解冲突操作。但是如果是多节点系统里不同的节点同时接收到不同的修改请求并成共修改各自节点后,这一文档的节点间的复制就会失败,并构成一个“文档冲突”。这在Git里更像是两个分支。

CouchDB里,每一个文档都有一个文档id,这个在同一个库里唯一的,自动添加有B树索引。通常,CouchDB的数据读取通过View来实现。这个View有点像关系型数据库里的视图。每一次对文档的修改、增删,视图里都会有相应的修改。定义试图的时候,还需要定义视图的key。key可以有多个。视图的key都有B树索引。CouchDB也支持发送Selector对象,请求特定条件下的指定字段。不过估计效率不高,因为条件字段未必有索引。CouchDB的视图默认使用JavaScript实现Map/Reduce函数对来维护。JavaScript函数使用Mozilla的SpiderMonkey解释执行。

CouchDB有一个特点是“Crash-only”。崩溃和关闭是一回事,CouchDB关闭的时候没有过程,没有内存数据持久化的操作。写操作时突然的发生崩溃不会影响数据头端的完整性。CouchDB的写操作分两步:1、document数据和index数据写入磁盘;2、然后将文档头组成两个相同的连续的头信息,然后同时写入磁盘。第一步失败的话,文档信息丢失,文档头依然完整。第二步失败的话,有一个头信息写入成功,数据头都还完整(系统崩溃的时候只会影响到一个头的信息的写入?)。

多节点CouchDB系统中,如果有一个节点突然下线。当其一段时候后恢复上线时、会从其他节点自动复制数据,最终确保一致性。

搜索前些年关于CouchDB的讨论,发现当时CouchDB的一大槽点是没有数据库分片(sharding)的功能。我在最新的CouchDB文档里看到,现在是支持分片功能的。多节点系统中,数据表分片使用一致性哈希算法,每个节点都存储有多个不连续分片,以实现节点失效时的高可用性。

本文一些内容是道听途说,给出处的意义不大。就给出CouchDB的两个链接吧。一个是官方文档,原理方面的内容挺多,一次看完也吃不透,以后有需要再看;另一个是CouchDB作者写的一本共享书,今天才发现的,还没看过。暂且记在这里。

延伸阅读