容器设计模式

容器化引领了云原生风潮。在云原生生态下,容器是最原子的个体,云原生下的容器非常类似上个世纪80年代末OOP引领变革后的模块/组件,而90年代中期GoF引领的设计模式将OOP推向了一个新的高度。因此,基于容器的设计模式方法论也可以驱使分布式系统设计模式的进一步发展,这也是了解容器设计模式的重要所在。Google的容器设计模式的小论文,是对容器设计模式的一个梳理,其文中提到的几种模式。这些模式基本是多年来Google内部Borg沉淀出的,目前,它们在k8s生态也有着广泛的落地。

用作者的话来说,容器是密闭的,包含了依赖关系,并且有一个原子的部署信号(“succeeded”/“failed”),他们很大程度上提升了之前在数据中心或云端部署软件的技术。然而,容器不仅仅只是一个很好的部署工具,容器最终会成为面向对象软件系统中的对象一样,并且因此驱使了分布式系统设计模式的发展。

文中提到了如下几类模式:

单容器管理模式(single-container management patterns)

即最传统的单容器模式。容器为定义接口提供了一个自然的边界,类似于对象边界。容器不仅能够暴露专用应用功能,还能够通过这个接口跟管理系统挂钩。这些接口分为两类:

  • Upward”方向,容器可以通过接口暴露一套丰富的应用程序信息。比如k8s的probe。
  • Downward”方向,容器通过接口提供定义生命周期的能力,从而使得写容器管理系统对容器内的软件组件进行控制管理。比如k8s的优雅关闭接口。

单节点多容器管理模式(single-node, multi-container application patterns)

即同时运行在单个主机上的共生容器。容器管理系统支持同时运行多个容器作为一个整体单元,这就是k8s中的Pod。

这里它又分为如下几种模式:

Sidecar pattern

利用共享存储能力进行同一Pod内的容器协同。

比如k8s官方示例中的tomcat容器和war包容器;业务应用容器和日志收集容器。

Sidecar模式是应用最广泛的一类模式,它进行了应用容器和工具容器的解耦,同时对主应用容器能力进行了扩展和增强。从而带来了如下好处:

  • 容器是资源分配的单元,拆分可以做更细粒度的分配;
  • 容器是打包的单元,拆分可以进行团队解耦;
  • 容器通过镜像机制成为可以复用的单元,拆分可以将工具容器镜像复用多个场景;
  • 容器有自己的控制边界,拆分可以进行故障隔离;
  • 容器是配置的单元,拆分方便部署,升级,回滚.

Ambassador pattern

利用共享网络的能力进行同一个Pod内的容器协同。

这种模式非常类似反向代理。业务容器可以利用Ambassador容器访问外部服务。比如k8s中业务容器通过redis ambassador访问redis服务。

Adapter pattern

利用容器间统一的接口进行协同,

这种模式通常应用与容器的监控管理

比如k8s中利用Prometheus进行分布式监控,Prometheus的Exporter就是对应的一个Adapter容器。

多节点应用模式(multi-node application patterns)

分布式场景下会有很多场景涉及到多个节点协同问题,这些问题可以使用容器的方式解决。因此衍生出如下几类模式:

Leader election pattern

利用容器来解决有状态场景下的选主问题。比如多个节点对应不同的pod,每个pod下有一个选主容器,这样选主容器进行选主行为,业务容器只需要关注业务开发,从选主容器读取选举结果即可。

Work queue pattern

工作队列模式主要利用容器解决基于队列的计算资源调度问题

由于容器天然具有弹性,可以利用多个节点的容器协同来实现动态计算资源调度,具体如上图。

Scatter/gather pattern

分散收集模式主要利用容器解决弹性计算问题。

根容器接受到来自客户端的服务请求,将服务请求fan-out到弹性容器上,并通过容器收集获得结果返回给调用方,具体如上图。

总结

容器的设计模式解决的问题本质上是让业务人员只专注与业务,非业务部分下沉到基础设施层面。这几类模式及类似的Service Mesh解决方案,都是提供了一个无侵入的基础能力,将原来分布式系统全生命周期开发过程中遇到的技术问题下沉到工具类容器中来进行解决。

软件复杂度

通常系统设计时,涉及到的两类复杂性:

  • 实质复杂性 Essential Complexity
  • 附属复杂性 Accidental Complexity

这个概念,最早出现在Brooks的『人月神话』没有银弹章节,文中Brooks将软件开发的困难分为两类:

  • 本质类(essence):软件本身在概念建构上存先天的困难;即问题本身的复杂性。
  • 附属类(accident):将概念上的构思施行于计算机上,使用编程语言表达这些抽象实体,所遭遇到的困难。

Brooks认为,附属类的复杂度最终会随着工具的改善而逐渐淡化,反而是本质性的困难最难以解决。人月神话出版于上个世纪70年代,Brooks本人是IBM System/360研发出身,得出这类结论也比较正常。然而,对于业务类系统,其本质的复杂度可能并不太高,但是随着时间的发展,其本身的附属性复杂度却会越来越高

附属复杂性通常是人们在解决实质复杂度的过程中衍生的,即系统设计的初衷是解决本质复杂性,
但解决方案本身带来了新的问题。

事实上,我们在使用很多软件框架都在一定程度上表现出附属复杂性的问题,这本质上是由于框架/解决方案只适用特定的场景,归根结底是系统设计一定是基于场景的Trade-off。比如我们引入分层架构时,会遇到不同Layer对象的转换复杂度;我们引入DDD/DCI时,领域实体抽象,领域对象管理的复杂度;我们引入云原生架构时,会遇到应用层开发模式转变的复杂度等等。

上面的复杂度更多体现为解决方案的成本,然后解决方案的误用,也会产生更大的附属复杂性,这更多的体现为方案的反模式(Anti-Pattern)。比如,单体架构中的反模式:大泥球/意大利面架构,分层架构中的千层面架构,领域化设计中的贫血模型等等。这部分的附属复杂度是我们更需要重点关注和避免的。

Jib

Jib工具是Google去年推出的针对java的Docker构建工具,Jib解决的问题比较明确,核心是Java镜像文件太大的问题。同时,用其作者的话,I'am a Java Developer, I don't want to have to care about Dockerfiles,Jib通过maven/gradle插件,可以帮助开发者不用写dockerfile就可以build出docker镜像。

Java镜像文件过大,原因包括如下两点:

  • Java运行需要JVM,启动需要JRE,Java的基础镜像非常大,比如openjdk基础镜像达到284M,Google的
    distroless,社区jre-alpine镜像都可以做到100M以内。基础镜像虽然大,但是由于docker的分层镜像机制,配置合适的拉取策略,基础镜像不变也不会增加额外的分发时间。
  • Java应用的镜像通常也非常大。我们现在通常将应用镜像打包层war, fat jar文件。Java生态的依赖关联作的不太好,加上集团内的富二方包的现状,通常app镜像在100M以上。Docker的unionfs虽然是分层的,但是war, fat jar作为一个读写层,任何一个资源文件或class文件的变更,都会导致该层md5变更,从而导致该层的重新拉取和替换。

原因二是需要重点解决的问题,解法也非常简单,借助docker的分层镜像机制,将app镜像进一步分层,结合java应用的特点,进一步分为三层:

  • dependencies
  • resources
  • classes

显然,经常变更的是resources, classes,但是其层镜像非常小。启动拉取时耗时非常短。

具体实现时,jib通过maven插件,实现了docker的pull和push机制,由于是maven插件,可以在maven编译期组织三类资源,然后生成dockerfile针对三类资源的COPY语句。

1
2
3
4
5
6
7
FROM [base image]

COPY libs [path/to/dependencies]
COPY resources [path/to/resources]
COPY classes [path/to/classes]

...

由于一个语句对应一个layer,因此自然实现了分层。

针对denpendencies层,由于snapshot经常发生变化,如果有大量的snapshot jar存在,denpendencies层也会被拉取替换,在具体实现时,可以针对snapshot再单独拉出一层,进一步降低拉取替换粒度。

12-Factor App

12-Factor是早些年heroku工程师开发过程中总结的12条构建应用的原则,其核心的方法论为:

  • 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
  • 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性
  • 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
  • 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
  • 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展

具体来说:

1. Codebase 基准代码

一份基准代码(Codebase),对应一个应用,一个应用可以有多份部署(deploy)。如果多个应用共享一份基准代码,则有悖于12-Factor原则。

2. Dependency 依赖

需要显式声明依赖关系,并通过依赖清单,确切地声明所有依赖项,在运行过程中通过依赖隔离 工具来确保程序不会调用系统中存在但清单中未声明的依赖项。

3. Config 配置

在环境中存储配置。通过环境变量/CMDB将配置排除在代码之外

4. Backing Services 后端服务

把后端服务当作附加资源,不区分远程还是本地

5. Build, Release, Run 构建,发布,运行

严格分离构建,发布,运行三个阶段。

6. Process 进程

以一个或多个无状态进程运行应用,必要的状态都需要被服务化到后端服务,确保进程share-nothing

7. Port binding 端口绑定

通过端口绑定来提供服务,避免通过IPC等方式来通信,服务间通过服务发现来进行服务通信

8. Concurrency 并发

进程是一等公民,可以通过就水平扩展app来实现并发性,或者通过线程模型进行扩展,确保应用进程的shard-nothing,水平分区特性。

9. Disposability 易处理

应用应当具备快速启动及优雅终止的健壮特性,可以瞬间开启或停止,从而允许系统进行快速弹性扩缩容,同时应当追求最小启动时间,可以快速改变部署和及时故障恢复的特性。

10. Dev/prod parity 开发环境与线上环境等价

保持开发,预发布,线上环境相同

11. Logs 日志

把日志当作事件流,通过集中式的日志服务,执行日志收集、聚合、索引及分析等操作。

12. Admin Processes 管理进程

后台管理任务当作一次性进程运行。一次性管理进程应该和正常的常驻进程 使用同样的环境(代码、配置),从而避免同步问题,保持一致性。

12-Factor原则,基本可以认为是云原生架构下的应用构建方针,按照这种方针,应用构建过程中会非常自然的使用到CNCF云原生定义中代表技术的 容器化微服务不可变基础设施服务网格声明式API

12-Factor非常强调应用的无状态,比如2,3,4,10。这些原则要求应用对环境进行松耦合,而这部分正式容器化的基石,Docker技术的出现,通过分层镜像机制完成了应用与环境的解耦,进而完成了应用层面的不可变。而随着容器化的深入已经及kubernetes类的容器编排技术的出现,为了更好的满足9的特性,不可变会下沉更底层的基础设施,这样就进一步衍生出了不可变基础设施(Pouch这类富容器显然是违背这类原则的,这也是集团在做去富容器化的根本原因),在不可变基础设施支持的环境中,任意的设施可以被替换,被收容扩容,应用的弹性能力进一步增强。

为了更好的实现9里的弹性能力,应用的启动时间及健壮性品质要求应用尽可能的轻量级及高度自治,因此,微服务乃至Function成为了一等公民。

然而微服务应用的高度自治性带来了固有的复杂性。管理好服务间通信对于保证端到端的性能及可靠性非常重要,因此服务间通信层开始出现,不可变基础设施要求其需要以sidecar的方式存在,于是服务网格类技术出现。

云原生环境下,我们管理(运维)应用类比linux下管理进程,我们应该只关注状态,不关注过程,因此k8s倡导的声明式API成为了我们DevOps的最佳工具。

施展-中国史纲 听后感1

最近把施展在得到上的中国史纲50讲听完了,挺有感触,从时空维度及逻辑维度看中国历史,确实值得回味。

先列下课表:

作者先从中国历史的概念出发,引出课程的基本逻辑,然后通过封建、豪族、平民社会三个阶段大体回顾了整个中国古代史。 之后从转型、革命两个角度讲述了中国的近代史,最后通过中国与世界的关系,得出了中国是世界枢纽的结论。

中国历史

所谓的『崖山之后无中国』是指宋亡之后,汉族政权被北方游牧政权取代,很多人认为这不能叫中国了。

作者为了回答这个问题,从时间和空间两个维度来梳理历史,来佐证中国一直是一个插大规模的,多元成分的国家。同时,作者提出欲望和秩序是历史的第一性原则,作者认为历史是各个行为主体不断博弈的过程,博弈的内在动力是欲望,博弈的外部约束是秩序。整个这个逻辑,贯串通篇。

那么,到底何为中国历史?这个要分开来开,中国是今天中国疆域内的各个地区,彼此在历史上相互联系,相互作用的体系;历史不仅包括事实,还包括基于事实的意义。

接下来,作者从四个时间点,和五个空间维度来阐述中国历史:

  • 商周之变:中国概念诞生,中华文明中最初的普世主义理想出现
  • 周秦之变:中原从分封割据状态进入大一统,中国历史开始超越中原,中原和草原的相互塑造和对抗开始成为中国历史的脉络。
  • 唐宋之变:中国的社会结构从豪族进入平民,大一统开始。中国文化迎来大爆发
  • 古今之变:中国从古代社会向现代社会艰难转型。

在时间轴上,作者通过五个空间进行了进一步阐述:

  • 草原
  • 中原
  • 海洋
  • 西域
  • 高原

商周之变

商周时期发展出了『中国』这个概念,古代所谓的中国是指全世界的中心,文明意义的中心。达到文明最高水准的地方,就是中国。商周之变后,周朝用中国概念表达了普世主义理想(天命降于周王),之后通过分封制建立了真正意义的封建社会。

战争逻辑

随着周朝的发展,战争逻辑发生了变化

春秋初期,贵族之间的战争,其礼仪性远远大于实用性。但是楚国出现,它不再遵循礼仪制,于是,战争的实用性开始超越礼仪性。

同时,两个技术变迁加速了这个过程: 首先,铁器和牛耕的出现,使得平民的劳动效率提升,平民开始开垦私田,从而导致贵族的井田制瓦解,贵族力量削弱。其次,竹简这种知识传播技术的出现, 使得游士出现,进而衍生出官僚制,于是变法出现,中国进入战国时代。于是,平民开始成为君主可以利用的政治力量,战争逻辑和规模彻底变化。

当战争规模大到诸侯国承受不起的时候,中国就开始走向大一统。

百家争鸣

同时,在春秋后期,由于天下大乱,礼崩乐坏,人民理解世界的坐标没了,人民意识里世界的意义丧失, 从而引发了思想大爆发

百家争鸣,从对待传统(旧礼仪)的态度,大体可以分为三类:

  • 复古:儒家
  • 开新:法家
  • 出世:道家

从此,这三个思想流派,走向了政治。

周秦之变

统治思想

秦国在春秋后期,利用了法家思想,积极进行变法,变法大大的提高了战争效率,从而完成了中原的统一。但是在统一后,秦在对内统治仍然采用法家思想,从而导致帝国二世亡国。

楚王项羽推翻秦二世之后,试图恢复分封制(类似周王的分封),于是诸侯国兴起,诸侯国仍然基于法家逻辑来治国,最终导致了新一轮的大一统。刘邦得权,西汉建立。

汉高祖刘邦意识到法家可以征服天下,但是却不能仅仅用法家治理天下。 汉高祖得权后采用了道家的无为而治,内在的原因是,其开国伙伴跟他类似合伙人关系,力量均衡。但是他出台了若干法令,意图将社会彻底打为散沙化,从而使得大宗族拆散为小家庭。 同时,他采用了同姓封王机制,这也给历史埋下伏笔。

汉武帝上台后,采用了『罢黜百家、独尊儒术』的儒学思想。儒家思想的兴起,使得儒学的解释能力,在儒生集团手里, 这样儒生以这种方式对皇帝形成一个反制能力。 之后的君主大多数都采用『外儒内法』的方式,对外表现为儒家,对内追求效率还是用法家。

中原 vs 草原

汉朝之后,中原和草原慢慢的交织在了一起。

先说『汉人』的概念, 汉人这个词是汉朝之后出现,它通常是指,接受儒家文化,并按儒家文化指导生活的人。 由于儒家文化要求人民遵循三纲五常、三从四德等交往规范,因此,儒家文化要求人们有特定的人际关系,而这个的前提是定居,定居又要求汉人需要进行农耕,因此,儒家文化存在地理依赖性。
这个地理依赖性要求其降水量 >= 400mm,这就是汉人早期一直在长城以南的原因。

汉人的定居方式也影响了其继承秩序,其继承秩序的核心是君位继承的稳定性,因此采用的方式是嫡长子资格 即所谓的父终子及。

再看草原,草原生活游牧化,因此,征税成本高,所以无法中央财政,也无法进行官僚制。游牧民族采用的小部落搭建的熟人社会,熟人社会的极限是邓巴数(150左右),当超越邓巴数时,部落就会分裂,因此早期的游牧民族一直没有统一的帝国。

但是,秦后的中原农耕帝国的统一,导致了游牧帝国建立,这是因为,农耕帝国的统一,保证了中原和草原贸易的稳定性,客观上促成了其帝国的建立。

再说游牧帝国的继承逻辑,游牧帝国关注的是君主的战斗力,因此采用的是兄终弟及的方式,而这种方式,在第一代人都死掉之后,就会产生周期性的继承危机,这就是所谓的『胡虏无百年之运』

农耕帝国和游牧帝国是交织在一起的,农耕帝国的统一促成了游牧帝国的建立,反过来游牧帝国用军事压力改变中原帝国,并能在中原帝国衰败时输入秩序,重建社会,建立同时统治中原和草原的庞大帝国。

这种庞大帝国的特质是采用二元帝国的治理逻辑,帝国在长城以南,以中原儒家方式统治,统治者身份是皇帝;而在长城以北则,按草原游牧方式统治,统治者身份为大可汗。历史上,所有的统治,不遵循这个逻辑,帝国都不会长久。

豪族社会

这个时间坐标,中国进入了豪族社会。

先说豪族的来源

  • 战国贵族的后代
  • 文景之治发展起来的大商人
  • 地方的大侠,豪强

在汉武帝时期,随着草原的崛起,草原给中原带来了巨大的军事和财政压力,这双重压力,导致了流民增多,流民为了个人生计开始投靠豪族。同时,地方官僚也开始与豪族结盟,豪族的势力逐步增强,中国彻底从封建社会走向了豪族社会。

于是,皇族、寒族、豪族之间的斗争史拉开大幕。王莽横空出世抑制豪族,希望重新恢复帝国超投的统治力。最终失败。而刘秀本身作为豪族的领袖,建立东汉,与豪族共治。东汉历史拉开。

东汉有文化的豪族,逐渐变成士族,世家大族,他们垄断了知识和地位,并主导了社会舆论,这就是历史上的贤臣。而皇上对抗世家大族,组织了宦官、太监、外戚进行对抗,这就是所谓的奸臣。于是皇帝、外戚、宦官、世家大族的博弈关系拉开帷幕。

三国时期,诸葛亮、曹操等寒族与士族冲突,最终三国归晋,士族大获全胜。

获胜的司马士族,却面临了不同的历史大势:

  • 气候变化,历史上的一个小冰期,草原政权侵入。
  • 中原社会豪族化,不断削弱帝国政府力量。
  • 士族的堕落,出现了所谓的魏晋玄学。
  • 皇族内斗,八王之乱。

历史大势交织下,西晋灭亡,东晋在士族支持下建立。 东晋采用了政权和军权分离的方式,这最终也造成了东晋灭亡。

南北朝对峙,南朝不断努力向外传播文化,以便跟北朝进行正统性竞争,中华世界在这个过程中发展为草原-中原-海洋的三重宏达结构。从五胡乱华到北魏,中国最终建立了胡汉混一的二元帝国,

2018个人计划

技术

  • 分布式欠的债,8.824的最终实验完成,剩余的4到5篇论文要读完。今年上半年读完Designing data intensive applications,伊利诺伊香槟分校的cloud computing的五个课程拖太久今年完成。
  • 架构相关,工作相关的架构自不必说,再读下eric evens DDD,重新读一遍vernon的 实现领域驱动设计,今年读完uncle bob去年的神书clean architecture。posa系列自始自终没有系统的梳理式阅读,今年好好梳理下,产出整个系列的模式相关的笔记。
  • 技术基础。jvm再体系化梳理下,这种东西是学的快忘的更快,垃圾回收算法手册去年没有读完,今年一定要读完,好好读一遍现代操作系统
  • 应用层面,从应用的层面好好读读dockerkubernetes的原理及部分代码的核心实现,实践层面深入熟悉spark。研究区块链

认知

  • 超级版图,硅谷钢铁侠,枪炮病菌与钢铁
  • 薛兆丰的经济学通识
  • 得到上学习三个专栏
  • 设计领导力

基础

  • 程序员的数学
  • 读完 什么是数学
  • 扇贝听力 刷完 六级听力文章训练
  • 扇贝单词 每周至少刷5天

个人

  • 每周坚持锻炼3次
  • 养成早起的习惯,11点半争取躺下
  • gtd和番茄钟习惯,提高工作效率

2017年度总结

时间真快,年初写年度计划的场景及那份雄心壮志还是历历在目。不过,时间快,不光走的快,变化也是飞快。没想到到了年底一切都变了。

首先,将近三年的创业生涯画上了句号。14年年底的雄心壮志已经凉到了冰点,以至于对未来机会的选择也不免唯唯诺诺。

无论是面试还是跟别人闲聊多少都会聊到创业的得失,无非是得到了综合能力的提升,失去了技术的线性或指数成长。不过,仔细的思考下,那种套路化的回答更多的是一种包装,作为一个标榜有所追求的技术人,失去的远大于得到。

这是一个信息爆炸的时代,个人综合能力的提升(至少对于我这种技术人)通过自驱的获取信息之前可以有一个显著的提升,技术人通常缺乏全局视野,缺乏一定的逻辑判断力,对我而言,创业是扇窗户,打开后我才知道自己的局限,然后便是信息的获取,格局的提升,但事实上,作为一个个体对格局提升的认知直到创业后才意识到显然过去是不合格的,创业对我来说最大的收获是让我清楚的认识到自己的综合素质是很低的,并且有了提升的方向。

然后,技术成长失去的确实实实在在的,实践力,领域深度都有一定的下降,好在中后期实实在在的感受到了这一点,通过一定的自驱弥补了一些损失,细想起来,这也是创业的一段收获,在明显会走技术下坡的路上,唯有自驱能减少下降的斜率!

回到年度的计划,技术完成度不到60%,尤其是深度学习及基础学科这部分。不过仔细想想当时的目标未免不切实际,虽年初开始接触深度学习,但是系统化的学习这个领域需要太多的基础沉淀,这不经过系统的训练是很难完成的,创业后期我明显感到,创业的失败概率极大,大概率还是会回大公司做架构师,所以后期的技术明显转向了架构领域,这导致整个计划的完成度都有很大的偏差。

年尾了,工作也换了,糟心的2007终于没了,希望2018,一切顺利

论文-Mesa

最近换工作,面试时和聊天时,多次聊到OLAP及Google Mesa,正好最近有空,于是就把Mesa论文仔细读了一遍,论文地址为Mesa: Geo-Replicated, Near Real-Time, Scalable Data Warehousing

Mesa是Google在VLDB 2014(Hangzhou)发布的数据仓库解决方案,论文标题为Mesa: Geo-Replicated, Near Real-Time, Scalable Data Warehousing,从论文标题看,Mesa强调跨数据中心、准实时、伸缩性。目前,mesa应用于google的广告业务,处理P级别数据,每秒row updates达到百万两集,每天接收10亿+次检索,返回的数据行在trillion量级。

目标

跟传统的数据仓库相比,mesa有如下的设计目标

  • atomic update:即单个action导致的不同层面的数据更新局部原子性。比如一个ad click,可能影响千级别一致性视图,这个action的数据层面更新要保证原子性
  • consistency & correctnetss: 数据的一致性及正确性
  • availability: 系统是高可用的
  • near real-time update throughput: 高TPS,分钟级的跨view、dc的数据一致性
  • query performance: 高查询性能,99分位响应时间在100ms左右
  • scalability:高伸缩性
  • online data & metadata transformation: schema动态变更

用论文里的描述

Mesa is a distributed, replicated, and highly available data processing, storage, and query system for structured data.

作者解释了为何没有使用BigTable, Megastore, Spanner/F1作为其解决方法。BigTable满足row ACID,无法保证批量更新的原子性;而Megastore, Spanner/F1作为OLTP服务,虽然提供跨dc的数据强一致,但是高吞吐量的数据更新,尤其是峰值更新,支持不好。但是在metadata管理上,mesa使用了BigTable及Colossues, 而跨数据中心的数据同步,则使用了Spanner里用到的Paxos。

数据存储

TODO.

论文-spinnaker

Spinnaker是IBM & Linkedin Team在2011年VLDB发布的一个k/v存储服务,其设计实现基本上就是典型的paxos/raft的rsm(replicated-state-machine)的典型应用,系统实现思路很清晰,比较值得一读。

Spinnaker主要针对一个datacenter内的数据同步,考虑到datacenter内的网络分区概率极低,Spinnaker严格上说是一个CA系统。它采用3-way replica,跟传统的最终一致性的存储系统做对比,Spinnaker在读操作上甚至要更快,而在写操作上,它也只是有5-10%的性能损失。

数据模型

Spinnaker的数据模型及API跟Bigtable及Yahoo PNUTS非常类似,数据基于row及table组织,基本是如下的pattern:

row key -> { col name : col val, ……}

Data Model

作为k/v存储,其数据格式显然也没有做预设的schema,基于col name & col val来进行schema的变更;由于其数据模型里没有column family,其查询便利性上较之Bigtable还是有些差别。

基本APIs

  • insert
  • delete
  • get
  • test_and_set

前三个api自不必说,基本就是常规的kv操作;对于test_and_set, 由于数据存在版本,client端可以基于version做conditionalPut、conditionalDel等操作,从而实现test_and_set语义;

比如conditionalDelete(key, colname, v),只有当前数据版本为v时,才进行delete操作。

架构

数据分布层面,Spinnaker基于key-range进行分布;一个range内的数据集的多个replica集合叫cohort,cohort基于chained-declustering方式进行replica逻辑关联。

上图就是一个典型的数据分布架构,为了系统简化直接基于Zookeeper来做meta-data管理;

Cluster

对于单个节点,节点通过模块话组织进行数据commit, membership管理, election等动作,具体如下图:

Node

Replication Protocol

Spinnaker协议跟Raft, Zab比较类似,采用基于选举的paxos-like方案,其协议分为两个阶段:

  • p1: leader election
  • p2: quorum (using multi-paxos)

简单来说就是,首先选出cohort leader(leader crash会重新选举), 然后log发送给cohort leader,leader基于multi-paxos协议进行quorum的log一致性同步(注意此时节点未做commit,也就是未commit到RSM中)。 多数派节点ack后,leader commit然后返回client,之后发出async commit操作通知节点进行commit操作。

Protocal

显然在这个时序里,leader返回之后时间点,client->get(),到follower节点是可能拿到过期数据的,而leader节点是可以拿到最新数据。这就是Spinnnaker里的timeline consistency。

恢复

每个节点为所有的partition数据维护一个共享log(规避随机写问题),类似write-ahead-log,确保数据的可恢复性。

如果follower挂掉后又重新加入

  • leader传输log给follower,follower会追进度
  • 直到追上后,follower投入运行

如果leader挂掉

  • 触发p1的election
  • leader会重新propose所有的uncommited消息
  • 如果是quorum,开始update

需要指出的是,重新选举后leader会将所有的uncommit log进行重新的分发,这一块跟Raft协议有了比较大的简化,不确定正确性是否理论严格。

分布式一致性简史

来自 https://betathoughts.blogspot.com/2007/06/brief-history-of-consensus-2pc-and.html

第一个一致性问题是从 Lamport 大神的 1978年Time论文指出,虽然他没有明确提出consensus or agreement的概念, 但他提出了分布式状态机的概念(复制状态机, RSM), 一组确定状态机,从相同初始状态开始,确保他们以相同顺序处理相同消息,可以确保每个状态机都是其他的状态机的副本。 关键是,哪个消息是下一个需要处理的消息,达成一致, 这就是一致性问题。

1979年 Jim Gray描述了2PC,但是2PC会Block,随后,Dale Skeen在1981年提出3PC, 但是找到好的3PC算法花了近25年。

1985年 著名的FLP Impossiblility被提出。 即一个异步系统即使只有一个进程出错,分布式一致性也不可能达到。跟海森堡的测不准原理有些神识。此时 Consensus变成,让一系列处理器在一个value上达成共识的问题。 核心是没法区分,是低速执行,还是终止。

这时,人们意识到分布式算法有两个非常重要的属性, safety liveness 前者:坏的事情不会发生,后者:好的事情最终会发生。

虽然,2pc保证safety,但是liveness非常不好。

同时:分布式系统被分为同步,异步两种, 同步看做异步的特例, 它的消息传输时间有上界。

1982年 Byzantine Generals Problem: 进程会说谎。

1986年,一致性和事务聚在一起。 最好的一致性算法是Byzantine Generals,但是代价太高,根部无法用于事务处理。

1987年 Jim Gray, 指出分布式事务是一个新的一致性问题,不是bg的退化版本,分布式事务是uniform consensus(2010年) 所有进程必须在一个value上达成一致,即使是那些出错进程。

进入90年代,曙光来临:

  • 1990年 Lamport提出 Paxos

  • 1996年 Lampson引用Paxos

  • 2001年 Lamport 发表 Paxos Made Simple

Paxos及Paxos-like的出现,大有一统分布式一致性的趋势。

而针对分布式系统的同步异步问题,人们发现:系统简单划分同步异步有些宽泛

1988年 Dwork Lynch Stockmeyer定义了部分同步系统:

进程已给点bound的速率运行,消息传输时间有界,但边界的实际取值无法得知
处理速度和传输的边界一直,只是在未来某个未知时间开始。

2005年 Lamport Jim Gray将Paxos应用的分布式事务提交中, 用于容错,解决block问题。进行了整体融合。