原文连接:
http://www.confluent.io/blog/apache-kafka-samza-and-the-Unix-philosophy-of-distributed-data
做者:Martin Kleppmann
译者:杰微刊-macsokolot(@gmail.com) javascript
当我在为个人书作研究时,我意识到现代软件工程仍然须要从20世纪70年代学习不少东西。在这样一个快速发展的领域,咱们每每有一种倾向,认为旧观念一无可取——所以,最终咱们不得不一次又一次地为一样的教训买单,这真艰难。尽管如今电脑已经愈来愈快,数据量也愈来愈大,需求也愈来愈复杂,许多老观点至今仍有很大的用武之地。 css
在这篇文章中,我想强调一个陈旧的观念,但它如今更应该被关注:Unix哲学(philosophy)。我将展现这种哲学与主流数据库设计方式大相径庭的缘由;并探索若是现代分布式数据系统从Unix中学到了一些皮毛,那它在今天将发展成什么样子。 html
特别是,我以为Unix管道与ApacheKafka有不少类似之处,正是因为这些类似性使得那些大规模应用拥有良好的架构特性。但在咱们深刻了解它以前,让我稍稍跟你提一下关于Unix哲学的基础。或许,你以前就已经见识过Unix工具的强大之处——但我仍是用一个你们相互都能讨论的具体例子来开始吧。
假设你有一个web服务器,每次有请求,它就向日志文件里写一个条目。假设使用nginx的默认访问日志格式,那么这行日志可能看起来像这样: java
216.58.210.78 - - [27/Feb/2015:17:55:11 +0000] "GET /css/typography.css HTTP/1.1"
200 3377 "http://martin.kleppmann.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X
10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36" linux
(这里实际上只有一行,分红多行只是方便阅读。)此行的日志代表,服务器在2015年2月27日17:55:11从客户端地址216.58.210.78收到了一个文件请求/css/typography.css。它还记录了其余各类细节,包括浏览器的用户代理字符串。 nginx
许多工具可以利用这些日志文件,并生成您的网站流量报告,但为了练练手,咱们创建一个本身的工具,使用一些基本的Unix工具,在咱们的网站上肯定5个最热门的网址。首先,咱们须要提取出被请求的URL路径,这里咱们可使用awk. web
awk并不知道nginx的日志格式——它只是将日志文件看成文本文件处理。默认状况下,awk一次只能处理一行输入,一行靠空格分隔,使之可以做为变量的空格分隔部件$1, $2, etc。在nginx的日志示例中,请求的URL路径是第7个空格分隔部件: sql
如今咱们已经提取出了路径,接下来就能够肯定服务器上5个最热门的网站,以下所示: shell
这一系列的命令执行后输出的结果是这样的: 数据库
4189 /favicon.ico
3631 /2013/05/24/improving-security-of-ssh-private-keys.html
2124 /2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html
1369 /
915 /css/typography.css
若是你并不熟悉Unix工具的话,上述命令看起来有点难懂,但它真的很强大。这几条简单的命令可以在几秒钟内处理千兆字节的日志文件,并且你能够根据须要,很是容易地更改分析内容。好比说,你如今想统计访问次数最多的客户端IP地址,而不是最热门的那几个网页,只需更改awk的参数'{print $1}'
按需求使用这些组合命令awk, sed, grep, sort, uniq , xargs的话,海量数据分析可以在几分钟内完成,其性能表现让人出乎意料。这不是巧合,是Unix设计哲学的结果。
Unix哲学就是一套设计准则, 在20世纪60年代末与70年代初,这些准则是在设计和实现Unix系统时才逐渐出现的。关于Unix哲学有很是多的阐述,但有两点脱颖而出,由Doug McIlroy, Elliot Pinson 和Berk Tague在1978年描述以下:
1. 每一个程序只作好一件事。若是有新的任务需求,那就编写一个新的程序而不是在一个旧的程序上加一个新的“功能”,使其愈来愈复杂。
2. 指望每一个程序的输出都能是其余程序的输入,即便是未知的程序。
这些准则是能把各类程序链接成管道的基础,而有了管道就能完成复杂的处理任务。这里的核心思想就是一个程序不知道或者说不须要关心它的输入是从哪里来的,输出要往哪里去:多是一个文件,或者操做系统的其余程序,又或者是彻底由某个开发者开发的程序。
操做系统附带的工具都是通用的,可是它们被设计成可以组合起来执行特定任务的较大的程序。
Unix的设计者遵循这种程序设计方法所带来的好处有点像几十年后出现的Agile 和DevOps的成果:脚本与自动化,快速原型编码(rapid prototyping),增量迭代,友好的测试(being friendly to experimentation),以及将大型项目分解成可控的模块。再加上CA的改变。(Plus ?a change.)
当你在shell里为2个命令加上管道的标示符,那么shell就会同时启动这2个命令程序,而后将第一个程序处理的输出结果做为第二个程序的输入。这种链接机构由操做系统提供管道系统调用服务。
请注意,这种线性处理不是由程序自己来完成的,而是靠shell——这就使得每一个程序之间是“松耦合”,这使得程序不用担忧它们的输入从哪里来,输出要往哪里去。
1964年,管道(Pipe)由Doug McIlroy发明,他首次在Bell实验室内部备忘录里将其描述为:“咱们须要一些链接各类程序的方法就像花园里的软管——当它成为另外一种必要的消息数据时,须要拧入其余的消息段。” Dennis Richie后来将他的观点写进了备忘录。
他们也很早就意识到进程间的通讯机制(管道)与读写文件机制很是类似。咱们如今称之为输入重定向(用一个文件内容做为一个程序的输入)和输出重定向(将一个程序的结果输出到一个文件)。
Unix程序之因此可以有这么高的组合灵活性,是由于这些程序都遵循相同的接口:大多数程序都有一个数据输入流(stdin)和两个输出流(stdout常规数据输出流和stderr错误与诊断信息输出流)。
程序一般除了读stdin流和写stdout流以外,它们还能够作其它的事,好比读取和写入文件,在网络上通讯,或者绘制一个用户界面。然而,该stdin/stdout通讯被认为是数据从一个Unix工具流向另外一个的最主要的途径。
其实,最使人高兴的事莫过于任何人可使用任意语言轻松地实现stdin/stdout接口。你能够开发本身的工具,只要其遵循这个接口,那么你的工具能和其余标准工具同样高效,并能做为操做系统的一部分。
举个例子,当你想分析一个web服务器的日志文件,或许你想知道来自每一个国家的访问量有多少。可是这个日志并无告诉你国家信息,只是告诉了你IP地址,那么你能够经过IP地理数据库将IP地址转换成国家。默认状况下,你的操做系统并无附带这个数据库,可是你能够编写一个将IP地址放进stdin流,将输出国家放进stdout流的工具。
一旦你把这个工具写好了,你就能够将它使用在咱们以前讨论过的数据处理管道里,它将会工做地很好。若是你已经使用了Unix一段时间,那么这样作彷佛很容易,可是我想强调这样作很是了不得:你本身的代码程序与操做系统附带的那些工具地位是同样的。
图形用户界面的程序和Web应用彷佛不那么容易可以被拓展或者像这样串起来。你不能用管道将Gmail传送给一个独立的搜索引擎应用,而后将结果输出到wiki上。可是如今是个例外,跟往常不同的是,如今也有程序可以像Unix工具同样可以协同工做。
换个话题。在Unix系统开发的同时,关系型数据模型就被提出来了,不久就演变成了SQL,被运用到不少主流的数据库中。许多数据库实际上仍在Unix系统上运行。这是否意味着它们也遵循Unix哲学?
在大多数据库系统中数据流与Unix工具中很是不一样。不一样于使用stdin流和stdout流做为通讯渠道,数据库系统中使用DB server以及多个client。客户端(Client)发送查询(queries)来读取或写入服务器上的数据,server端处理查询(queries)并发送响应给客户端(Client)。这种关系从根本上是不对称的:客户和服务器都是不一样的角色。
Unix系统里可组合性和拓展性是指什么?客户端(Clients)能作任何他们喜欢的事(由于他们是程序代码),可是DB Server大可能是在作存储和检索数据的工做,运行你写的任意代码并非它们的首要任务。
也就是说,许多数据库提供了一些方法,你能用本身的代码去扩展数据库服务器功能。例如,在许多关系型数据库中,让你本身写存储过程,基本的程序语言如PL / SQL(和一些让你在通用编程语言上能运行代码好比JavaScript)。然而,你能够在存储过程当中所作的事情是有限的。
其余拓展方式,像某些数据库支持用户自定义数据类型(这是Postgres的早期设计目标),或者支持可插拔的数据引擎。基本上,这些都是插件的接口:
你能够在数据库服务器中运行你的代码,只要你的模块遵循一个特定用途的数据库服务器的插件接口。
这种扩展方式并非与咱们看到的Unix工具那样的可组合性同样。这种插件接口彻底由数据库服务器控制,并从属于它。你写的扩展代码就像是数据库服务器家中一个访客,而不是一个平等的合做伙伴。
这种设计的结果是,你不能用管道将一个数据库与另外一个链接起来,即便他们有相同的数据模型。你也不能将本身的代码插入到数据库的内部处理管道(除非该服务器已明确提供了一个扩展点,如触发器)。
我以为数据库设计是很以自我为中心的。数据库彷佛认为它是你的宇宙的中心:这多是你要存储和查询数据,数据真正来源,和全部查询最终抵达的惟一地方。你获得管道数据最近的方式是经过批量加载和批量倾倒(bulk-dumping)(备份)操做,但这些操做不能真正使用到数据库的任何特性,如查询规划和索引。
若是数据库遵循Unix的设计思想,那么它将是基于一小部分核心原语,你能够很容易地进行结合,拓展和随意更换。而实际上,数据库犹如极其复杂,庞大的野兽。Unix也认可操做系统不会让你真的随心所欲,可是它鼓励你去拓展它,你或许只需一个程序就能实现数据库系统想要实现全部的功能。
在只有一个数据库的简单应用中,这种设计可能还不错。
然而,在许多复杂的应用中,他们用各类不一样的方式处理他们的数据:对于OLTP须要快速随机存取,数据分析须要大序列扫描,全文搜索须要倒排索引,用于链接的数据图索引,推荐引擎须要机器学习系统,消息通知须要的推送机制,快速读取须要各类不一样的缓存表示数据,等等。
一个通用数据库能够尝试将全部这些功能集中在一个产品上(“一个适合全部”),但十有八九,这个数据库不会为了某个特定的功能而只执行一个工具程序。在实践中,你能够常常经过联合各类不一样的数据存储和检索系统获得最好的结果:例如,你能够把相同的数据并将其存储在关系数据库中,方便其随机访问,在Elasticsearch进行全文搜索,在Hadoop中作柱状格式分析,并以非规范化格式在memcached中缓存。
当你须要整合不一样的数据库,缺少Unix风格的组合性对于整合来讲是一个严重的限制。(我已经完成了从Postgres中用管道将数据输出到其余应用程序,但这还有很长的路要走,直到咱们能够简单地用管道将任一数据库中的数据导出到其余数据库。)
咱们说Unix工具可组合性是由于它们都实现相同的接口——stdin,stdout和stderr——它们都是文件描述符,即:能够像文件同样读写的字节流。这个接口很简单以至于任何人均可以很容易地实现它,但它也足够强大,你可使用它作任何东西。
由于全部的Unix工具实现相同的接口,咱们把它称为一个统一的接口。这就是为何你能够绝不犹豫地用管道将gunzip数据输出WC中去,即便开发这两个工具的做者可能历来没有交流过。这就像乐高积木,它们都用相同的模式实现节位和槽位,让你堆乐高积木的时候可以为所欲为,不用管它们的形状,大小和颜色。
Unix文件描述符的统一接口并不只仅适用于输入和输出的过程,它是一个很是普遍的应用模式。若是你在文件系统上打开一个文件,你将获得一个文件描述符。管道和Unix套接字提供一个文件标识符,这个标示符可以在同一机器上为其它程序提供一个通讯通道。在Linux中,/dev下的虚拟文件是设备驱动程序的接口,因此你在这里面能够跟USB端口甚至GPU打交道。/proc下的虚拟文件是内核的API,可是它是以文件形式存在,你可使用相同的工具,以普通文件的方式访问它。
即便是经过TCP链接到另一台机器上的程序也是一个文件描述符,虽然BSD套接字API(最经常使用来创建TCP链接)不像Unix。Plan 9显示,即便是网络能够被彻底集成到相同的统一接口中去。
完整内容进入此连接查看:http://www.jointforce.com/jfperiodical/article/1036?f=jf_tg_bky