diff --git a/README.md b/README.md index 0e2ee3dc87..cf83d4772e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ LCTT 是“Linux中国”([https://linux.cn/](https://linux.cn/))的翻译 LCTT 已经拥有几百名活跃成员,并欢迎更多的Linux志愿者加入我们的团队。 -![logo](http://img.linux.net.cn/static/image/common/lctt_logo.png) +![logo](https://linux.cn/static/image/common/lctt_logo.png) LCTT 的组成 ------------------------------- diff --git a/published/20160728 I've been Linuxing since before you were born.md b/published/20160728 I've been Linuxing since before you were born.md new file mode 100644 index 0000000000..65d6bb980a --- /dev/null +++ b/published/20160728 I've been Linuxing since before you were born.md @@ -0,0 +1,66 @@ +一个老奶奶的唠叨:当年我玩 Linux 时…… +===================== + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/images/life/OSDC_Penguin_Image_520x292_12324207_0714_mm_v1a.png?itok=WfAkwbFy) + +在很久以前,那时还没有 Linux 系统。真的没有!之前也从未存在过。不像现在,Linux 系统随处可见。那时有各种流派的 Unix 系统、有苹果的操作系统、有微软的 Windows 操作系统。 + +比如说 Windows,它的很多东西都改变了,但是依然保持不变的东西的更多。尽管它已经增加了 20GB 以上的鬼知道是什么的东西,但是 Windows 还是大体保持不变(除了不能在 DOS 的提示下实际做些什么了)。嘿,谁还记得 Gorilla.bas 那个出现在 DOS 系统里的炸香蕉的游戏吗?多么美好的时光啊!不过互联网却不会忘记,你可以在 Kongregate.com 这个网站玩这个游戏的 Flash版本。 + +苹果系统也改变了,从一个鼓励你 hack 的友善系统变成了一个漂亮而密实的、根本不让你打开的小盒子,而且还限制了你使用的硬件接口。1998 年:软盘没了。2012 年:光驱没了。而 12 英寸的 MacBook 只有一个单一的 USB Type-C 接口,提供了电源、蓝牙、无线网卡、外部存储、视频输出和其它的一些配件的接口。而你要是想一次连接多个外设就不得不背着一堆转接器,这真是太令人抓狂了!然后就轮到耳机插孔了,没错,这唯一一个苹果世界里留着的非专有的标准硬件接口了,也注定要消失了。(LCTT 译注:还好,虽然最新的 IPhone 7 上没有耳机插孔了,但是新发布的 Macbook 上还有) + +还有一大堆其它的操作系统,比如:Amiga、BeOS、OS/2 ,如果你愿意的话,你能找到几十个操作系统。我建议你去找找看,太容易找到了。Amiga、 BeOS 和 OS/2 这些系统都值得关注,因为它们有强大的功能,比如 32 位的多任务和高级图形处理能力。但是市场的影响击败了强大的系统性能,因此技术上并不出众的 Apple 和 Windows 操作系统统治了市场的主导地位,而那些曾经的系统也逐渐销声匿迹了。 + +然后 Linux 系统出现了,世界也因此改变了。 + +### 第一款电脑 + +我曾经使用过的第一款电脑是 Apple IIc ,大概在 1994年左右,而那个时候 Linux 系统刚出来 3 年。这是我从一个朋友那里借来的,用起来各方面都还不错,但是很不方便。所以我自己花了将近 500 美元买了一台二手的 Tandy 牌的电脑。这对于卖电脑的人来说是一件很伤心的事,因为新电脑要花费双倍的价钱。那个时候,电脑贬值的速度非常快。这个电脑当时看起来强劲得像是个怪物:一个英特尔 386SX 的 CPU,4MB的内存,一个 107MB 的硬盘,14 英寸的彩色 CRT 显示器,运行 MS-DOS 5 和 Windows 3.1 系统。 + +我曾无数次的拆开那个可怜的怪物,并且多次重新安装 Windows 和 DOS 系统。因为 Windows 桌面用的比较少,所以我的大部分工作都是在 DOS 下完成的。我喜欢玩血腥暴力的视频游戏,包括 Doom、Duke Nukem、Quake 和 Heretic。啊!那些美好的,让人心动的 8 位图像! + +那个时候硬件的发展一直落后于软件,因此我经常升级硬件。现在我们能买到满足我们需求的任何配置的电脑。我已经好多年都没有再更新我的任何电脑硬件了。 + +### 《比特杂志(Computer bits)》 + +回到那些曾经辉煌的年代,电脑商店布满大街小巷,找到本地的一家网络服务提供商(ISP)也不用走遍整个街区。ISP 那个时候真的非比寻常,它们不是那种冷冰冰的令人讨论的超级大公司,而是像美国电信运营商和有线电视公司这样的好朋友。他们都非常友好,并且提供各种各样的像 BBS、文件下载、MUD (多玩家在线游戏)等的额外服务。 + +我花了很多的时间在电脑商店购买配件,但是很多时候我一个女人家去那里会让店里的员工感到吃惊,我真的很无语了,这怎么就会让一些人看不惯了。我现成已经是一位 58 岁的老家伙了,但是他们还是一样的看不惯我。我希望我这个女电脑迷在我死之前能被他们所接受。 + +那些商店的书架上摆满了《比特杂志(Computer bits)》。有关《比特杂志》的[历史刊物](https://web.archive.org/web/20020122193349/http://computerbits.com/)可以在互联网档案库(Internet Archive)中查到。《比特杂志》是当地一家免费报纸,有很多关于计算机方面的优秀的文章和大量的广告。可惜当时的广告没有网络版,因此大家不能再看到那些很有价值的关于计算机方面的详细信息了。你知道现在的广告商们有多么的抓狂吗?他们埋怨那些安装广告过滤器的人,致使科技新闻变成了伪装的广告。他们应该学习一下过去的广告模式,那时候的广告里有很多有价值的信息,大家都喜欢阅读。我从《比特杂志》和其它的电脑杂志的广告中学到了所有关于计算机硬件的知识。《电脑购买者杂志(Computer Shopper)》更是非常好的学习资料,其中有上百页的广告和很多高质量的文章。 + +![](https://opensource.com/sites/default/files/resize/march2002-300x387.jpg) + +《比特杂志》的出版人 Paul Harwood 开启了我的写作生涯。我的第一篇计算机专业性质的文章就是在《比特杂志》发表的。 Paul,仿佛你一直都在我的身旁,谢谢你。 + +在互联网档案库中,关于《比特杂志》的分类广告信息已经几乎查询不到了。分类广告模式曾经给出版商带来巨大的收入。免费的分类广告网站 Craigslist 在这个领域独占鳌头,同时也扼杀了像《比特杂志》这种以传统的报纸和出版为主的杂志行业。 + +其中有一些让我最难以忘怀的记忆就是一个 12 岁左右的吊儿郎当的小屁孩,他脸上挂满了对我这种光鲜亮丽的女人所做工作的不屑和不理解的表情,他在我钟爱的电脑店里走动,递给我一本《比特杂志》,当成给初学者的一本好书。我翻开杂志,指着其中一篇我写的关于 Linux 系统的文章给他看,他说“哦,我明白了”。他尴尬的脸变成了那种正常生理上都不可能呈现的颜色,然后很仓促地夹着尾巴溜走了。(不是的,我亲爱的、诚实的读者们,他不是真的只有 12 岁,而应该是 20 来岁。他现在应该比以前更成熟懂事一些了吧!) + +### 发现 Linux + +我第一次了解到 Linux 系统是在《比特杂志》上,大概在 1997 年左右。我一开始用的一些 Linux操作系统版本是 Red Hat 5 和 Mandrake Linux(曼德拉草)。 Mandrake 真是太棒了,它是第一款易安装型的 Linux 系统,并且还附带图形界面和声卡驱动,因此我马上就可以玩 Tux Racer 游戏了。不像那时候大多数的 Linux 迷们,因为我之前没接触过 Unix系统,所以我学习起来比较难。但是一切都还顺利吧,因为我学到的东西都很有用。相对于我在 Windows 中的体验,我在 Windows 中学习的大部分东西都是徒劳,最终只能放弃返回到 DOS 下。 + +玩转电脑真是充满了太多的乐趣,后来我转行成为计算机自由顾问,去帮助一些小型公司的 IT 部门把数据迁移到 Linux 服务器上,这让 Windows 系统或多或少的失去一些用武之地。通常情况下我们都是在背地里偷偷地做这些工作的,因为那段时期微软把 Linux 称为毒瘤,诬蔑 Linux 系统是一种共产主义,用来削弱和吞噬我们身体里的珍贵血液的阴谋。 + +### Linux 赢了 + +我持续做了很多年的顾问工作,也做其它一些相关的工作,比如:电脑硬件修理和升级、布线、系统和网络管理,还有运行包括 Apple、Windows、Linux 系统在内的混合网络。Apple 和 Windows 系统故意不兼容对方,因此这两个系统真的是最头疼,也最难整合到同一网络中。但是在 Linux 系统和其它开源软件中最有趣的一件事是总有一些人能够处理这些厂商之间的兼容性问题。 + +现在已经大不同了。在系统间的互操作方面一直存在一些兼容性问题,而且也没有桌面 Linux 系统的 1 级 OEM 厂商。我觉得这是因为微软和苹果公司在零售行业在大力垄断造成的。也许他们这样做反而帮了我们帮大忙,因为我们可以从 ZaReason 和 System76 这样独立的 Linux 系统开发商得到更好的服务和更高性能的系统。他们对 Linux 系统都很专业,也特别欢迎我们这样的用户。 + +Linux 除了在零售行业的桌面版系统占有一席之地以外,从嵌入式系统到超级计算机以及分布式计算系统等各个方面都占据着主要地位。开源技术掌握着软件行业的发展方向,所有的软件行业的前沿技术,比如容器、集群以及人工智能的发展都离不开开源技术的支持。从我的第一台老式的 386SX 电脑到现在,Linux 和开源技术都取得了巨大的进步。 + +如果没有 Linux 系统和开源,这一切都不可能发生。 + +-------------------------------------------------------------------------------- + +via: https://opensource.com/life/16/7/my-linux-story-carla-schroder + +作者:[Carla Schroder][a] +译者:[rusking](https://github.com/rusking) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://opensource.com/users/carlaschroder diff --git a/published/20160809 How to Monitor Docker Containers using Grafana on Ubuntu.md b/published/20160809 How to Monitor Docker Containers using Grafana on Ubuntu.md new file mode 100644 index 0000000000..a9d5fb2833 --- /dev/null +++ b/published/20160809 How to Monitor Docker Containers using Grafana on Ubuntu.md @@ -0,0 +1,217 @@ +如何在 Ubuntu 上使用 Grafana 监控 Docker +========= + +Grafana 是一个有着丰富指标的开源控制面板。在可视化大规模测量数据的时候是非常有用的。根据不同的指标数据,它提供了一个强大、优雅的来创建、分享和浏览数据的方式。 + +它提供了丰富多样、灵活的图形选项。此外,针对数据源(Data Source),它支持许多不同的存储后端。每个数据源都有针对特定数据源的特性和功能所定制的查询编辑器。Grafana 提供了对下述数据源的正式支持:Graphite、InfluxDB、OpenTSDB、 Prometheus、Elasticsearch 和 Cloudwatch。 + +每个数据源的查询语言和能力显然是不同的,你可以将来自多个数据源的数据混合到一个单一的仪表盘上,但每个面板(Panel)被绑定到属于一个特定组织(Organization)的特定数据源上。它支持验证登录和基于角色的访问控制方案。它是作为一个独立软件部署,使用 Go 和 JavaScript 编写的。 + +在这篇文章,我将讲解如何在 Ubuntu 16.04 上安装 Grafana 并使用这个软件配置 Docker 监控。 + +### 先决条件 + +- 安装好 Docker 的服务器 + +### 安装 Grafana + +我们可以在 Docker 中构建我们的 Grafana。 有一个官方提供的 Grafana Docker 镜像。请运行下述命令来构建Grafana 容器。 + +``` +root@ubuntu:~# docker run -i -p 3000:3000 grafana/grafana + +Unable to find image 'grafana/grafana:latest' locally +latest: Pulling from grafana/grafana +5c90d4a2d1a8: Pull complete +b1a9a0b6158e: Pull complete +acb23b0d58de: Pull complete +Digest: sha256:34ca2f9c7986cb2d115eea373083f7150a2b9b753210546d14477e2276074ae1 +Status: Downloaded newer image for grafana/grafana:latest +t=2016-07-27T15:20:19+0000 lvl=info msg="Starting Grafana" logger=main version=3.1.0 commit=v3.1.0 compiled=2016-07-12T06:42:28+0000 +t=2016-07-27T15:20:19+0000 lvl=info msg="Config loaded from" logger=settings file=/usr/share/grafana/conf/defaults.ini +t=2016-07-27T15:20:19+0000 lvl=info msg="Config loaded from" logger=settings file=/etc/grafana/grafana.ini +t=2016-07-27T15:20:19+0000 lvl=info msg="Config overriden from command line" logger=settings arg="default.paths.data=/var/lib/grafana" +t=2016-07-27T15:20:19+0000 lvl=info msg="Config overriden from command line" logger=settings arg="default.paths.logs=/var/log/grafana" +t=2016-07-27T15:20:19+0000 lvl=info msg="Config overriden from command line" logger=settings arg="default.paths.plugins=/var/lib/grafana/plugins" +t=2016-07-27T15:20:19+0000 lvl=info msg="Path Home" logger=settings path=/usr/share/grafana +t=2016-07-27T15:20:19+0000 lvl=info msg="Path Data" logger=settings path=/var/lib/grafana +t=2016-07-27T15:20:19+0000 lvl=info msg="Path Logs" logger=settings path=/var/log/grafana +t=2016-07-27T15:20:19+0000 lvl=info msg="Path Plugins" logger=settings path=/var/lib/grafana/plugins +t=2016-07-27T15:20:19+0000 lvl=info msg="Initializing DB" logger=sqlstore dbtype=sqlite3 + +t=2016-07-27T15:20:20+0000 lvl=info msg="Executing migration" logger=migrator id="create playlist table v2" +t=2016-07-27T15:20:20+0000 lvl=info msg="Executing migration" logger=migrator id="create playlist item table v2" +t=2016-07-27T15:20:20+0000 lvl=info msg="Executing migration" logger=migrator id="drop preferences table v2" +t=2016-07-27T15:20:20+0000 lvl=info msg="Executing migration" logger=migrator id="drop preferences table v3" +t=2016-07-27T15:20:20+0000 lvl=info msg="Executing migration" logger=migrator id="create preferences table v3" +t=2016-07-27T15:20:20+0000 lvl=info msg="Created default admin user: [admin]" +t=2016-07-27T15:20:20+0000 lvl=info msg="Starting plugin search" logger=plugins +t=2016-07-27T15:20:20+0000 lvl=info msg="Server Listening" logger=server address=0.0.0.0:3000 protocol=http subUrl= +``` + +我们可以通过运行此命令确认 Grafana 容器的工作状态 `docker ps -a` 或通过这个URL访问 `http://Docker IP:3000`。 + +所有的 Grafana 配置设置都使用环境变量定义,在使用容器技术时这个是非常有用的。Grafana 配置文件路径为 `/etc/grafana/grafana.ini`。 + +### 理解配置项 + +Grafana 可以在它的 ini 配置文件中指定几个配置选项,或可以使用前面提到的环境变量来指定。 + +#### 配置文件位置 + +通常配置文件路径: + +- 默认配置文件路径 : `$WORKING_DIR/conf/defaults.ini` +- 自定义配置文件路径 : `$WORKING_DIR/conf/custom.ini` + +PS:当你使用 deb、rpm 或 docker 镜像安装 Grafana 时,你的配置文件在 `/etc/grafana/grafana.ini`。 + +#### 理解配置变量 + +现在我们看一些配置文件中的变量: + +- `instance_name`:这是 Grafana 服务器实例的名字。默认值从 `${HOSTNAME}` 获取,其值是环境变量` HOSTNAME`,如果该变量为空或不存在,Grafana 将会尝试使用系统调用来获取机器名。 +- `[paths]`:这些路径通常都是在 init.d 脚本或 systemd service 文件中通过命令行指定。 + - `data`:这个是 Grafana 存储 sqlite3 数据库(如果使用)、基于文件的会话(如果使用),和其他数据的路径。 + - `logs`:这个是 Grafana 存储日志的路径。 +- `[server]` + - `http_addr`:应用监听的 IP 地址,如果为空,则监听所有的接口。 + - `http_port`:应用监听的端口,默认是 3000,你可以使用下面的命令将你的 80 端口重定向到 3000 端口:`$iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000` + - `root_url` : 这个 URL 用于从浏览器访问 Grafana 。 + - `cert_file` : 证书文件的路径(如果协议是 HTTPS)。 + - `cert_key` : 证书密钥文件的路径(如果协议是 HTTPS)。 +- `[database]`:Grafana 使用数据库来存储用户和仪表盘以及其他信息,默认配置为使用内嵌在 Grafana 主二进制文件中的 SQLite3。 + - `type`:你可以根据你的需求选择 MySQL、Postgres、SQLite3。 + - `path`:仅用于选择 SQLite3 数据库时,这个是数据库所存储的路径。 + - `host`:仅适用 MySQL 或者 Postgres。它包括 IP 地址或主机名以及端口。例如,Grafana 和 MySQL 运行在同一台主机上设置如: `host = 127.0.0.1:3306`。 + - `name`:Grafana 数据库的名称,把它设置为 Grafana 或其它名称。 + - `user`:数据库用户(不适用于 SQLite3)。 + - `password`:数据库用户密码(不适用于 SQLite3)。 + - `ssl_mode`:对于 Postgres,使用 `disable`,`require`,或 `verify-full` 等值。对于 MySQL,使用 `true`,`false`,或 `skip-verify`。 + - `ca_cert_path`:(只适用于 MySQL)CA 证书文件路径,在多数 Linux 系统中,证书可以在 `/etc/ssl/certs` 找到。 + - `client_key_path`:(只适用于 MySQL)客户端密钥的路径,只在服务端需要用户端验证时使用。 + - `client_cert_path`:(只适用于 MySQL)客户端证书的路径,只在服务端需要用户端验证时使用。 + - `server_cert_name`:(只适用于 MySQL)MySQL 服务端使用的证书的通用名称字段。如果 `ssl_mode` 设置为 `skip-verify` 时可以不设置。 +- `[security]` + - `admin_user`:这个是 Grafana 默认的管理员用户的用户名,默认设置为 admin。 + - `admin_password`:这个是 Grafana 默认的管理员用户的密码,在第一次运行时设置,默认为 admin。 + - `login_remember_days`:保持登录/记住我的持续天数。 + - `secret_key`:用于保持登录/记住我的 cookies 的签名。 + +### 设置监控的重要组件 + +我们可以使用下面的组件来创建我们的 Docker 监控系统。 + +- `cAdvisor`:它被称为 Container Advisor。它给用户提供了一个资源利用和性能特征的解读。它会收集、聚合、处理、导出运行中的容器的信息。你可以通过[这个文档](https://github.com/google/cadvisor)了解更多。 +- `InfluxDB`:这是一个包含了时间序列、度量和分析数据库。我们使用这个数据源来设置我们的监控。cAdvisor 只展示实时信息,并不保存这些度量信息。Influx Db 帮助保存 cAdvisor 提供的监控数据,以展示非某一时段的数据。 +- `Grafana Dashboard`:它可以帮助我们在视觉上整合所有的信息。这个强大的仪表盘使我们能够针对 InfluxDB 数据存储进行查询并将他们放在一个布局合理好看的图表中。 + +### Docker 监控的安装 + +我们需要一步一步的在我们的 Docker 系统中安装以下每一个组件: + +#### 安装 InfluxDB + +我们可以使用这个命令来拉取 InfluxDB 镜像,并部署了 influxDB 容器。 + +``` +root@ubuntu:~# docker run -d -p 8083:8083 -p 8086:8086 --expose 8090 --expose 8099 -e PRE_CREATE_DB=cadvisor --name influxsrv tutum/influxdb:0.8.8 +Unable to find image 'tutum/influxdb:0.8.8' locally +0.8.8: Pulling from tutum/influxdb +a3ed95caeb02: Already exists +23efb549476f: Already exists +aa2f8df21433: Already exists +ef072d3c9b41: Already exists +c9f371853f28: Already exists +a248b0871c3c: Already exists +749db6d368d0: Already exists +7d7c7d923e63: Pull complete +e47cc7808961: Pull complete +1743b6eeb23f: Pull complete +Digest: sha256:8494b31289b4dbc1d5b444e344ab1dda3e18b07f80517c3f9aae7d18133c0c42 +Status: Downloaded newer image for tutum/influxdb:0.8.8 +d3b6f7789e0d1d01fa4e0aacdb636c221421107d1df96808ecbe8e241ceb1823 + + -p 8083:8083 : user interface, log in with username-admin, pass-admin + -p 8086:8086 : interaction with other application + --name influxsrv : container have name influxsrv, use to cAdvisor link it. +``` + +你可以测试 InfluxDB 是否安装好,通过访问这个 URL `http://你的 IP 地址:8083`,用户名和密码都是 ”root“。 + +![InfluxDB Administration 2016-08-01 14-10-08](http://blog.linoxide.com/wp-content/uploads/2016/07/InfluxDB-Administration-2016-08-01-14-10-08-1-1024x530.png) + +我们可以在这个界面上创建我们所需的数据库。 + +![createDB influx](http://blog.linoxide.com/wp-content/uploads/2016/07/createDB-influx-1024x504.png) + +#### 安装 cAdvisor + +我们的下一个步骤是安装 cAdvisor 容器,并将其链接到 InfluxDB 容器。你可以使用此命令来创建它。 + +``` +root@ubuntu:~# docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --publish=8080:8080 --detach=true --link influxsrv:influxsrv --name=cadvisor google/cadvisor:latest -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086 +Unable to find image 'google/cadvisor:latest' locally +latest: Pulling from google/cadvisor +09d0220f4043: Pull complete +151807d34af9: Pull complete +14cd28dce332: Pull complete +Digest: sha256:8364c7ab7f56a087b757a304f9376c3527c8c60c848f82b66dd728980222bd2f +Status: Downloaded newer image for google/cadvisor:latest +3bfdf7fdc83872485acb06666a686719983a1172ac49895cd2a260deb1cdde29 +root@ubuntu:~# + + --publish=8080:8080 : user interface + --link=influxsrv:influxsrv: link to container influxsrv + -storage_driver=influxdb: set the storage driver as InfluxDB + Specify what InfluxDB instance to push data to: + -storage_driver_host=influxsrv:8086: The ip:port of the database. Default is ‘localhost:8086’ + -storage_driver_db=cadvisor: database name. Uses db ‘cadvisor’ by default +``` + +你可以通过访问这个地址来测试安装 cAdvisor 是否正常 `http://你的 IP 地址:8080`。 这将为你的 Docker 主机和容器提供统计信息。 + +![cAdvisor - Docker Containers 2016-08-01 14-24-18](http://blog.linoxide.com/wp-content/uploads/2016/07/cAdvisor-Docker-Containers-2016-08-01-14-24-18-776x1024.png) + +#### 安装 Grafana 控制面板 + +最后,我们需要安装 Grafana 仪表板并连接到 InfluxDB,你可以执行下面的命令来设置它。 + +``` +root@ubuntu:~# docker run -d -p 3000:3000 -e INFLUXDB_HOST=localhost -e INFLUXDB_PORT=8086 -e INFLUXDB_NAME=cadvisor -e INFLUXDB_USER=root -e INFLUXDB_PASS=root --link influxsrv:influxsrv --name grafana grafana/grafana +f3b7598529202b110e4e6b998dca6b6e60e8608d75dcfe0d2b09ae408f43684a +``` + +现在我们可以登录 Grafana 来配置数据源. 访问 `http://你的 IP 地址:3000` 或 `http://你的 IP 地址`(如果你在前面做了端口映射的话): + +- 用户名 - admin +- 密码 - admin + +一旦我们安装好了 Grafana,我们可以连接 InfluxDB。登录到仪表盘并且点击面板左上方角落的 Grafana 图标(那个火球)。点击数据源(Data Sources)来配置。 + +![addingdatabsource](http://blog.linoxide.com/wp-content/uploads/2016/08/addingdatabsource-1-1024x804.png) + +现在你可以添加新的图形(Graph)到我们默认的数据源 InfluxDB。 + +![panelgraph](http://blog.linoxide.com/wp-content/uploads/2016/08/panelgraph-1024x576.png) + +我们可以通过在测量(Metric)页面编辑和调整我们的查询以调整我们的图形。 + +![Grafana - Grafana Dashboard 2016-08-01 14-53-40](http://blog.linoxide.com/wp-content/uploads/2016/08/Grafana-Grafana-Dashboard-2016-08-01-14-53-40-1024x504.png) + +![Grafana - Grafana Dashboard](http://blog.linoxide.com/wp-content/uploads/2016/08/Grafana-Grafana-Dashboard-1024x509.png) + +关于 Docker 监控,你可用[从此了解][1]更多信息。 感谢你的阅读。我希望你可以留下有价值的建议和评论。希望你有个美好的一天。 + +------ + +via: http://linoxide.com/linux-how-to/monitor-docker-containers-grafana-ubuntu/ + +作者:[Saheetha Shameer][a] +译者:[Bestony](https://github.com/bestony) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://linoxide.com/author/saheethas/ +[1]: https://github.com/vegasbrianc/docker-monitoring diff --git a/published/20160820 Protocol Buffer Basics C++.md b/published/20160820 Protocol Buffer Basics C++.md new file mode 100644 index 0000000000..69ea73184b --- /dev/null +++ b/published/20160820 Protocol Buffer Basics C++.md @@ -0,0 +1,410 @@ +C++ 程序员 Protocol Buffers 基础指南 +============================ + +这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示: + +* 在 `.proto` 文件中定义消息格式 +* 使用 protocol buffer 编译器 +* 使用 C++ protocol buffer API 读写消息 + +这不是一个关于在 C++ 中使用 protocol buffers 的全面指南。要获取更详细的信息,请参考 [Protocol Buffer Language Guide][1] 和 [Encoding Reference][2]。 + +### 为什么使用 Protocol Buffers + +我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字、ID、邮件地址和联系电话。 + +如何序列化和获取结构化的数据?这里有几种解决方案: + +* 以二进制形式发送/接收原生的内存数据结构。通常,这是一种脆弱的方法,因为接收/读取代码必须基于完全相同的内存布局、大小端等环境进行编译。同时,当文件增加时,原始格式数据会随着与该格式相关的软件而迅速扩散,这将导致很难扩展文件格式。 +* 你可以创造一种 ad-hoc 方法,将数据项编码为一个字符串——比如将 4 个整数编码为 `12:3:-23:67`。虽然它需要编写一次性的编码和解码代码且解码需要耗费一点运行时成本,但这是一种简单灵活的方法。这最适合编码非常简单的数据。 +* 序列化数据为 XML。这种方法是非常吸引人的,因为 XML 是一种适合人阅读的格式,并且有为许多语言开发的库。如果你想与其他程序和项目共享数据,这可能是一种不错的选择。然而,众所周知,XML 是空间密集型的,且在编码和解码时,它对程序会造成巨大的性能损失。同时,使用 XML DOM 树被认为比操作一个类的简单字段更加复杂。 + +Protocol buffers 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 Protocol buffers,你需要写一个 `.proto` 说明,用于描述你所希望存储的数据结构。利用 `.proto` 文件,protocol buffer 编译器可以创建一个类,用于实现对高效的二进制格式的 protocol buffer 数据的自动化编码和解码。产生的类提供了构造 protocol buffer 的字段的 getters 和 setters,并且作为一个单元来处理读写 protocol buffer 的细节。重要的是,protocol buffer 格式支持格式的扩展,代码仍然可以读取以旧格式编码的数据。 + +### 在哪可以找到示例代码 + +示例代码被包含于源代码包,位于“examples”文件夹。可在[这里][4]下载代码。 + +### 定义你的协议格式 + +为了创建自己的地址簿应用程序,你需要从 `.proto` 开始。`.proto` 文件中的定义很简单:为你所需要序列化的每个数据结构添加一个消息(message),然后为消息中的每一个字段指定一个名字和类型。这里是定义你消息的 `.proto` 文件 `addressbook.proto`。 + +``` +package tutorial; + +message Person { + required string name = 1; + required int32 id = 2; + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; + optional PhoneType type = 2 [default = HOME]; + } + + repeated PhoneNumber phone = 4; +} + +message AddressBook { + repeated Person person = 1; +} +``` + +如你所见,其语法类似于 C++ 或 Java。我们开始看看文件的每一部分内容做了什么。 + +`.proto` 文件以一个 package 声明开始,这可以避免不同项目的命名冲突。在 C++,你生成的类会被置于与 package 名字一样的命名空间。 + +下一步,你需要定义消息(message)。消息只是一个包含一系列类型字段的集合。大多标准的简单数据类型是可以作为字段类型的,包括 `bool`、`int32`、`float`、`double` 和 `string`。你也可以通过使用其他消息类型作为字段类型,将更多的数据结构添加到你的消息中——在以上的示例,`Person` 消息包含了 `PhoneNumber` 消息,同时 `AddressBook` 消息包含 `Person` 消息。你甚至可以定义嵌套在其他消息内的消息类型——如你所见,`PhoneNumber` 类型定义于 `Person` 内部。如果你想要其中某一个字段的值是预定义值列表中的某个值,你也可以定义 `enum` 类型——这儿你可以指定一个电话号码是 `MOBILE`、`HOME` 或 `WORK` 中的某一个。 + +每一个元素上的 `= 1`、`= 2` 标记确定了用于二进制编码的唯一“标签”(tag)。标签数字 1-15 的编码比更大的数字少需要一个字节,因此作为一种优化,你可以将这些标签用于经常使用的元素或 repeated 元素,剩下 16 以及更高的标签用于非经常使用的元素或 `optional` 元素。每一个 `repeated` 字段的元素需要重新编码标签数字,因此 `repeated` 字段适合于使用这种优化手段。 + +每一个字段必须使用下面的修饰符加以标注: + +* `required`:必须提供该字段的值,否则消息会被认为是 “未初始化的”(uninitialized)。如果 `libprotobuf` 以调试模式编译,序列化未初始化的消息将引起一个断言失败。以优化形式构建,将会跳过检查,并且无论如何都会写入该消息。然而,解析未初始化的消息总是会失败(通过 parse 方法返回 `false`)。除此之外,一个 `required` 字段的表现与 `optional` 字段完全一样。 +* `optional`:字段可能会被设置,也可能不会。如果一个 `optional` 字段没被设置,它将使用默认值。对于简单类型,你可以指定你自己的默认值,正如例子中我们对电话号码的 `type` 一样,否则使用系统默认值:数字类型为 0、字符串为空字符串、布尔值为 false。对于嵌套消息,默认值总为消息的“默认实例”或“原型”,它的所有字段都没被设置。调用 accessor 来获取一个没有显式设置的 `optional`(或 `required`) 字段的值总是返回字段的默认值。 +* `repeated`:字段可以重复任意次数(包括 0 次)。`repeated` 值的顺序会被保存于 protocol buffer。可以将 repeated 字段想象为动态大小的数组。 + +你可以查找关于编写 `.proto` 文件的完整指导——包括所有可能的字段类型——在 [Protocol Buffer Language Guide][6] 里面。不要在这里面查找与类继承相似的特性,因为 protocol buffers 不会做这些。 + +> **`required` 是永久性的** + +>在把一个字段标识为 `required` 的时候,你应该特别小心。如果在某些情况下你不想写入或者发送一个 `required` 的字段,那么将该字段更改为 `optional` 可能会遇到问题——旧版本的读者(LCTT 译注:即读取、解析旧版本 Protocol Buffer 消息的一方)会认为不含该字段的消息是不完整的,从而有可能会拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google 的一些工程师得出了一个结论:使用 `required` 弊多于利;他们更愿意使用 `optional` 和 `repeated` 而不是 `required`。当然,这个观点并不具有普遍性。 + +### 编译你的 Protocol Buffers + +既然你有了一个 `.proto`,那你需要做的下一件事就是生成一个将用于读写 `AddressBook` 消息的类(从而包括 `Person` 和 `PhoneNumber`)。为了做到这样,你需要在你的 `.proto` 上运行 protocol buffer 编译器 `protoc`: + +1. 如果你没有安装编译器,请[下载这个包][4],并按照 README 中的指令进行安装。 +2. 现在运行编译器,指定源目录(你的应用程序源代码位于哪里——如果你没有提供任何值,将使用当前目录)、目标目录(你想要生成的代码放在哪里;常与 `$SRC_DIR` 相同),以及你的 `.proto` 路径。在此示例中: + +``` +protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto +``` + +因为你想要 C++ 的类,所以你使用了 `--cpp_out` 选项——也为其他支持的语言提供了类似选项。 + +在你指定的目标文件夹,将生成以下的文件: + +* `addressbook.pb.h`,声明你生成类的头文件。 +* `addressbook.pb.cc`,包含你的类的实现。 + +### Protocol Buffer API + +让我们看看生成的一些代码,了解一下编译器为你创建了什么类和函数。如果你查看 `addressbook.pb.h`,你可以看到有一个在 `addressbook.proto` 中指定所有消息的类。关注 `Person` 类,可以看到编译器为每个字段生成了读写函数(accessors)。例如,对于 `name`、`id`、`email` 和 `phone` 字段,有下面这些方法:(LCTT 译注:此处原文所指文件名有误,径该之。) + +```c++ +// name +inline bool has_name() const; +inline void clear_name(); +inline const ::std::string& name() const; +inline void set_name(const ::std::string& value); +inline void set_name(const char* value); +inline ::std::string* mutable_name(); + +// id +inline bool has_id() const; +inline void clear_id(); +inline int32_t id() const; +inline void set_id(int32_t value); + +// email +inline bool has_email() const; +inline void clear_email(); +inline const ::std::string& email() const; +inline void set_email(const ::std::string& value); +inline void set_email(const char* value); +inline ::std::string* mutable_email(); + +// phone +inline int phone_size() const; +inline void clear_phone(); +inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const; +inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone(); +inline const ::tutorial::Person_PhoneNumber& phone(int index) const; +inline ::tutorial::Person_PhoneNumber* mutable_phone(int index); +inline ::tutorial::Person_PhoneNumber* add_phone(); +``` + +正如你所见到,getters 的名字与字段的小写名字完全一样,并且 setter 方法以 set_ 开头。同时每个单一(singular)(`required` 或 `optional`)字段都有 `has_` 方法,该方法在字段被设置了值的情况下返回 true。最后,所有字段都有一个 `clear_` 方法,用以清除字段到空(empty)状态。 + +数字型的 `id` 字段仅有上述的基本读写函数集合(accessors),而 `name` 和 `email` 字段有两个额外的方法,因为它们是字符串——一个是可以获得字符串直接指针的`mutable_` 的 getter ,另一个为额外的 setter。注意,尽管 `email` 还没被设置(set),你也可以调用 `mutable_email`;因为 `email` 会被自动地初始化为空字符串。在本例中,如果你有一个单一的(`required` 或 `optional`)消息字段,它会有一个 `mutable_` 方法,而没有 `set_` 方法。 + +`repeated` 字段也有一些特殊的方法——如果你看看 `repeated` 的 `phone` 字段的方法,你可以看到: + +* 检查 `repeated` 字段的 `_size`(也就是说,与 `Person` 相关的电话号码的个数) +* 使用下标取得特定的电话号码 +* 更新特定下标的电话号码 +* 添加新的电话号码到消息中,之后你便可以编辑。(`repeated` 标量类型有一个 `add_` 方法,用于传入新的值) + +为了获取 protocol 编译器为所有字段定义生成的方法的信息,可以查看 [C++ generated code reference][5]。 + +#### 枚举和嵌套类 + +与 `.proto` 的枚举相对应,生成的代码包含了一个 `PhoneType` 枚举。你可以通过 `Person::PhoneType` 引用这个类型,通过 `Person::MOBILE`、`Person::HOME` 和 `Person::WORK` 引用它的值。(实现细节有点复杂,但是你无须了解它们而可以直接使用) + +编译器也生成了一个 `Person::PhoneNumber` 的嵌套类。如果你查看代码,你可以发现真正的类型为 `Person_PhoneNumber`,但它通过在 `Person` 内部使用 `typedef` 定义,使你可以把 `Person_PhoneNumber` 当成嵌套类。唯一产生影响的一个例子是,如果你想要在其他文件前置声明该类——在 C++ 中你不能前置声明嵌套类,但是你可以前置声明 `Person_PhoneNumber`。 + +#### 标准的消息方法 + +所有的消息方法都包含了许多别的方法,用于检查和操作整个消息,包括: + +* `bool IsInitialized() const;` :检查是否所有 `required` 字段已经被设置。 +* `string DebugString() const;` :返回人类可读的消息表示,对调试特别有用。 +* `void CopyFrom(const Person& from);`:使用给定的值重写消息。 +* `void Clear();`:清除所有元素为空(empty)的状态。 + +上面这些方法以及下一节要讲的 I/O 方法实现了被所有 C++ protocol buffer 类共享的消息(Message)接口。为了获取更多信息,请查看 [complete API documentation for Message][7]。 + +#### 解析和序列化 + +最后,所有 protocol buffer 类都有读写你选定类型消息的方法,这些方法使用了特定的 protocol buffer [二进制格式][8]。这些方法包括: + +* `bool SerializeToString(string* output) const;`:序列化消息并将消息字节数据存储在给定的字符串中。注意,字节数据是二进制格式的,而不是文本格式;我们只使用 `string` 类作为合适的容器。 +* `bool ParseFromString(const string& data);`:从给定的字符创解析消息。 +* `bool SerializeToOstream(ostream* output) const;`:将消息写到给定的 C++ `ostream`。 +* `bool ParseFromIstream(istream* input);`:从给定的 C++ `istream` 解析消息。 + +这些只是两个用于解析和序列化的选择。再次说明,可以查看 `Message API reference` 完整的列表。 + +> **Protocol Buffers 和面向对象设计** + +> Protocol buffer 类通常只是纯粹的数据存储器(像 C++ 中的结构体);它们在对象模型中并不是一等公民。如果你想向生成的 protocol buffer 类中添加更丰富的行为,最好的方法就是在应用程序中对它进行封装。如果你无权控制 `.proto` 文件的设计的话,封装 protocol buffers 也是一个好主意(例如,你从另一个项目中重用一个 `.proto` 文件)。在那种情况下,你可以用封装类来设计接口,以更好地适应你的应用程序的特定环境:隐藏一些数据和方法,暴露一些便于使用的函数,等等。**但是你绝对不要通过继承生成的类来添加行为。**这样做的话,会破坏其内部机制,并且不是一个好的面向对象的实践。 + +### 写消息 + +现在我们尝试使用 protocol buffer 类。你的地址簿程序想要做的第一件事是将个人详细信息写入到地址簿文件。为了做到这一点,你需要创建、填充 protocol buffer 类实例,并且将它们写入到一个输出流(output stream)。 + +这里的程序可以从文件读取 `AddressBook`,根据用户输入,将新 `Person` 添加到 `AddressBook`,并且再次将新的 `AddressBook` 写回文件。这部分直接调用或引用 protocol buffer 类的代码会以“// pb”标出。 + +```c++ +#include +#include +#include +#include "addressbook.pb.h" // pb +using namespace std; + +// This function fills in a Person message based on user input. +void PromptForAddress(tutorial::Person* person) { + cout << "Enter person ID number: "; + int id; + cin >> id; + person->set_id(id); // pb + cin.ignore(256, '\n'); + + cout << "Enter name: "; + getline(cin, *person->mutable_name()); // pb + + cout << "Enter email address (blank for none): "; + string email; + getline(cin, email); + if (!email.empty()) { // pb + person->set_email(email); // pb + } + + while (true) { + cout << "Enter a phone number (or leave blank to finish): "; + string number; + getline(cin, number); + if (number.empty()) { + break; + } + + tutorial::Person::PhoneNumber* phone_number = person->add_phone(); //pb + phone_number->set_number(number); // pb + + cout << "Is this a mobile, home, or work phone? "; + string type; + getline(cin, type); + if (type == "mobile") { + phone_number->set_type(tutorial::Person::MOBILE); // pb + } else if (type == "home") { + phone_number->set_type(tutorial::Person::HOME); // pb + } else if (type == "work") { + phone_number->set_type(tutorial::Person::WORK); // pb + } else { + cout << "Unknown phone type. Using default." << endl; + } + } +} + +// Main function: Reads the entire address book from a file, +// adds one person based on user input, then writes it back out to the same +// file. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; // pb + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; // pb + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!input) { + cout << argv[1] << ": File not found. Creating a new file." << endl; + } else if (!address_book.ParseFromIstream(&input)) { // pb + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + // Add an address. + PromptForAddress(address_book.add_person()); // pb + + { + // Write the new address book back to disk. + fstream output(argv[1], ios::out | ios::trunc | ios::binary); + if (!address_book.SerializeToOstream(&output)) { // pb + cerr << "Failed to write address book." << endl; + return -1; + } + } + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); // pb + + return 0; +} +``` + +注意 `GOOGLE_PROTOBUF_VERIFY_VERSION` 宏。它是一种好的实践——虽然不是严格必须的——在使用 C++ Protocol Buffer 库之前执行该宏。它可以保证避免不小心链接到一个与编译的头文件版本不兼容的库版本。如果被检查出来版本不匹配,程序将会终止。注意,每个 `.pb.cc` 文件在初始化时会自动调用这个宏。 + +同时注意在程序最后调用 `ShutdownProtobufLibrary()`。它用于释放 Protocol Buffer 库申请的所有全局对象。对大部分程序,这不是必须的,因为虽然程序只是简单退出,但是 OS 会处理释放程序的所有内存。然而,如果你使用了内存泄漏检测工具,工具要求全部对象都要释放,或者你正在写一个 Protocol Buffer 库,该库可能会被一个进程多次加载和卸载,那么你可能需要强制 Protocol Buffer 清除所有东西。 + +### 读取消息 + +当然,如果你无法从它获取任何信息,那么这个地址簿没多大用处!这个示例读取上面例子创建的文件,并打印文件里的所有内容。 + +```c++ +#include +#include +#include +#include "addressbook.pb.h" // pb +using namespace std; + +// Iterates though all people in the AddressBook and prints info about them. +void ListPeople(const tutorial::AddressBook& address_book) { // pb + for (int i = 0; i < address_book.person_size(); i++) { // pb + const tutorial::Person& person = address_book.person(i); // pb + + cout << "Person ID: " << person.id() << endl; // pb + cout << " Name: " << person.name() << endl; // pb + if (person.has_email()) { // pb + cout << " E-mail address: " << person.email() << endl; // pb + } + + for (int j = 0; j < person.phone_size(); j++) { // pb + const tutorial::Person::PhoneNumber& phone_number = person.phone(j); // pb + + switch (phone_number.type()) { // pb + case tutorial::Person::MOBILE: // pb + cout << " Mobile phone #: "; + break; + case tutorial::Person::HOME: // pb + cout << " Home phone #: "; + break; + case tutorial::Person::WORK: // pb + cout << " Work phone #: "; + break; + } + cout << phone_number.number() << endl; // ob + } + } +} + +// Main function: Reads the entire address book from a file and prints all +// the information inside. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; // pb + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; // pb + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!address_book.ParseFromIstream(&input)) { // pb + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + ListPeople(address_book); + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); // pb + + return 0; +} +``` + +### 扩展 Protocol Buffer + +或早或晚在你发布了使用 protocol buffer 的代码之后,毫无疑问,你会想要 "改善" + protocol buffer 的定义。如果你想要新的 buffers 向后兼容,并且老的 buffers 向前兼容——几乎可以肯定你很渴望这个——这里有一些规则,你需要遵守。在新的 protocol buffer 版本: + + * 你绝不可以修改任何已存在字段的标签数字 + * 你绝不可以添加或删除任何 `required` 字段 + * 你可以删除 `optional` 或 `repeated` 字段 + * 你可以添加新的 `optional` 或 `repeated` 字段,但是你必须使用新的标签数字(也就是说,标签数字在 protocol buffer 中从未使用过,甚至不能是已删除字段的标签数字)。 + +(对于上面规则有一些[例外情况][9],但它们很少用到。) + +如果你能遵守这些规则,旧代码则可以欢快地读取新的消息,并且简单地忽略所有新的字段。对于旧代码来说,被删除的 `optional` 字段将会简单地赋予默认值,被删除的 `repeated` 字段会为空。新代码显然可以读取旧消息。然而,请记住新的 `optional` 字段不会呈现在旧消息中,因此你需要显式地使用 `has_` 检查它们是否被设置或者在 `.proto` 文件在标签数字后使用 `[default = value]` 提供一个合理的默认值。如果一个 `optional` 元素没有指定默认值,它将会使用类型特定的默认值:对于字符串,默认值为空字符串;对于布尔值,默认值为 false;对于数字类型,默认类型为 0。注意,如果你添加一个新的 `repeated` 字段,新代码将无法辨别它被留空(left empty)(被新代码)或者从没被设置(被旧代码),因为 `repeated` 字段没有 `has_` 标志。 + +### 优化技巧 + +C++ Protocol Buffer 库已极度优化过了。但是,恰当的用法能够更多地提高性能。这里是一些技巧,可以帮你从库中挤压出最后一点速度: + +* 尽可能复用消息对象。即使它们被清除掉,消息也会尽量保存所有被分配来重用的内存。因此,如果我们正在处理许多相同类型或一系列相似结构的消息,一个好的办法是重用相同的消息对象,从而减少内存分配的负担。但是,随着时间的流逝,对象可能会膨胀变大,尤其是当你的消息尺寸(LCTT 译注:各消息内容不同,有些消息内容多一些,有些消息内容少一些)不同的时候,或者你偶尔创建了一个比平常大很多的消息的时候。你应该自己通过调用 [SpaceUsed][10] 方法监测消息对象的大小,并在它太大的时候删除它。 +* 对于在多线程中分配大量小对象的情况,你的操作系统内存分配器可能优化得不够好。你可以尝试使用 google 的 [tcmalloc][11]。 + +### 高级用法 + +Protocol Buffers 绝不仅用于简单的数据存取以及序列化。请阅读 [C++ API reference][12] 来看看你还能用它来做什么。 + +protocol 消息类所提供的一个关键特性就是反射(reflection)。你不需要针对一个特殊的消息类型编写代码,就可以遍历一个消息的字段并操作它们的值。一个使用反射的有用方法是 protocol 消息与其他编码互相转换,比如 XML 或 JSON。反射的一个更高级的用法可能就是可以找出两个相同类型的消息之间的区别,或者开发某种“协议消息的正则表达式”,利用正则表达式,你可以对某种消息内容进行匹配。只要你发挥你的想像力,就有可能将 Protocol Buffers 应用到一个更广泛的、你可能一开始就期望解决的问题范围上。 + +反射是由 [Message::Reflection interface][13] 提供的。 + +-------------------------------------------------------------------------------- + +via: https://developers.google.com/protocol-buffers/docs/cpptutorial + +作者:[Google][a] +译者:[cposture](https://github.com/cposture) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://developers.google.com/protocol-buffers/docs/cpptutorial +[1]: https://developers.google.com/protocol-buffers/docs/proto +[2]: https://developers.google.com/protocol-buffers/docs/encoding +[3]: https://developers.google.com/protocol-buffers/docs/downloads +[4]: https://developers.google.com/protocol-buffers/docs/downloads.html +[5]: https://developers.google.com/protocol-buffers/docs/reference/cpp-generated +[6]: https://developers.google.com/protocol-buffers/docs/proto +[7]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message +[8]: https://developers.google.com/protocol-buffers/docs/encoding +[9]: https://developers.google.com/protocol-buffers/docs/proto#updating +[10]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.SpaceUsed.details +[11]: http://code.google.com/p/google-perftools/ +[12]: https://developers.google.com/protocol-buffers/docs/reference/cpp/index.html +[13]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.Reflection diff --git a/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md b/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md new file mode 100644 index 0000000000..65be6bea8b --- /dev/null +++ b/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md @@ -0,0 +1,447 @@ +新手指南 - 通过 Docker 在 Linux 上托管 .NET Core +===== + +这篇文章基于我之前的文章 [.NET Core 入门][1]。首先,我把 RESTful API 从 .NET Core RC1 升级到了 .NET Core 1.0,然后,我增加了对 Docker 的支持并描述了如何在 Linux 生产环境里托管它。 + +![](http://blog.scottlogic.com/nsoper/assets/noob.png) + +我是首次接触 Docker 并且距离成为一名 Linux 高手还有很远的一段路程。因此,这里的很多想法是来自一个新手。 + +### 安装 + +按照 https://www.microsoft.com/net/core 上的介绍在你的电脑上安装 .NET Core 。这将会同时在 Windows 上安装 dotnet 命令行工具以及最新的 Visual Studio 工具。 + +### 源代码 + +你可以直接到 [GitHub](https://github.com/niksoper/aspnet5-books/tree/blog-docker) 上找最到最新完整的源代码。 + +### 转换到 .NET CORE 1.0 + +自然地,当我考虑如何把 API 从 .NET Core RC1 升级到 .NET Core 1.0 时想到的第一个求助的地方就是谷歌搜索。我是按照下面这两条非常全面的指导来进行升级的: + +- [从 DNX 迁移到 .NET Core CLI][2] +- [从 ASP.NET 5 RC1 迁移到 ASP.NET Core 1.0][3] + +当你迁移代码的时候,我建议仔细阅读这两篇指导,因为我在没有阅读第一篇指导的情况下又尝试浏览第二篇,结果感到非常迷惑和沮丧。 + +我不想描述细节上的改变因为你可以看 GitHub 上的[提交](https://github.com/niksoper/aspnet5-books/commit/b41ad38794c69a70a572be3ffad051fd2d7c53c0)。这儿是我所作改变的总结: + +- 更新 `global.json` 和 `project.json` 上的版本号 +- 删除 `project.json` 上的废弃章节 +- 使用轻型 `ControllerBase` 而不是 `Controller`, 因为我不需要与 MVC 视图相关的方法(这是一个可选的改变)。 +- 从辅助方法中去掉 `Http` 前缀,比如:`HttpNotFound` -> `NotFound` +- `LogVerbose` -> `LogTrace` +- 名字空间改变: `Microsoft.AspNetCore.*` +- 在 `Startup` 中使用 `SetBasePath`(没有它 `appsettings.json` 将不会被发现) +- 通过 `WebHostBuilder` 来运行而不是通过 `WebApplication.Run` 来运行 +- 删除 Serilog(在写文章的时候,它不支持 .NET Core 1.0) + +唯一令我真正头疼的事是需要移动 Serilog。我本可以实现自己的文件记录器,但是我删除了文件记录功能,因为我不想为了这次操作在这件事情上花费精力。 + +不幸的是,将有大量的第三方开发者扮演追赶 .NET Core 1.0 的角色,我非常同情他们,因为他们通常在休息时间还坚持工作但却依旧根本无法接近靠拢微软的可用资源。我建议阅读 Travis Illig 的文章 [.NET Core 1.0 发布了,但 Autofac 在哪儿][4]?这是一篇关于第三方开发者观点的文章。 + +做了这些改变以后,我可以从 `project.json` 目录恢复、构建并运行 dotnet,可以看到 API 又像以前一样工作了。 + +### 通过 Docker 运行 + +在我写这篇文章的时候, Docker 只能够在 Linux 系统上工作。在 [Windows](https://docs.docker.com/engine/installation/windows/#/docker-for-windows) 系统和 [OS X](https://docs.docker.com/engine/installation/mac/) 上有 beta 支持 Docker,但是它们都必须依赖于虚拟化技术,因此,我选择把 Ubuntu 14.04 当作虚拟机来运行。如果你还没有安装过 Docker,请按照[指导](https://docs.docker.com/engine/installation/linux/ubuntulinux/)来安装。 + +我最近阅读了一些关于 Docker 的东西,但我直到现在还没有真正用它来干任何事。我假设读者还没有关于 Docker 的知识,因此我会解释我所使用的所有命令。 + +#### HELLO DOCKER + +在 Ubuntu 上安装好 Docker 之后,我所进行的下一步就是按照 https://www.microsoft.com/net/core#docker 上的介绍来开始运行 .NET Core 和 Docker。 + +首先启动一个已安装有 .NET Core 的容器。 + +``` +docker run -it microsoft/dotnet:latest +``` + +`-it` 选项表示交互,所以你执行这条命令之后,你就处于容器之内了,可以如你所希望的那样执行任何 bash 命令。 + +然后我们可以执行下面这五条命令来在 Docker 内部运行起来微软 .NET Core 控制台应用程序示例。 + +``` +mkdir hwapp +cd hwapp +dotnet new +dotnet restore +dotnet run +``` + +你可以通过运行 `exit` 来离开容器,然后运行 `Docker ps -a` 命令,这会显示你创建的那个已经退出的容器。你可以通过上运行命令 `Docker rm ` 来清除容器。 + +#### 挂载源代码 + +我的下一步骤是使用和上面相同的 microsoft/dotnet 镜像,但是将为我们的应用程序以[数据卷](https://docs.docker.com/engine/tutorials/dockervolumes/1)的方式挂载上源代码。 + +首先签出有相关提交的仓库: + +``` +git clone https://github.com/niksoper/aspnet5-books.git +cd aspnet5-books/src/MvcLibrary +git checkout dotnet-core-1.0 +``` + +现在启动一个容器来运行 .NET Core 1.0,并将源代码放在 `/book` 下。注意更改 `/path/to/repo` 这部分文件来匹配你的电脑: + +``` +docker run -it \ +-v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \ +microsoft/dotnet:latest +``` + +现在你可以在容器中运行应用程序了! + +``` +cd /books +dotnet restore +dotnet run +``` + +作为一个概念性展示这的确很棒,但是我们可不想每次运行一个程序都要考虑如何把源代码安装到容器里。 + +#### 增加一个 DOCKERFILE + +我的下一步骤是引入一个 Dockerfile,这可以让应用程序很容易在自己的容器内启动。 + +我的 Dockerfile 和 `project.json` 一样位于 `src/MvcLibrary` 目录下,看起来像下面这样: + + +``` +FROM microsoft/dotnet:latest + +# 为应用程序源代码创建目录 +RUN mkdir -p /usr/src/books +WORKDIR /usr/src/books + +# 复制源代码并恢复依赖关系 + +COPY . /usr/src/books +RUN dotnet restore + +# 暴露端口并运行应用程序 +EXPOSE 5000 +CMD [ "dotnet", "run" ] +``` + +严格来说,`RUN mkdir -p /usr/src/books` 命令是不需要的,因为 `COPY` 会自动创建丢失的目录。 + +Docker 镜像是按层建立的,我们从包含 .NET Core 的镜像开始,添加另一个从源代码生成应用程序,然后运行这个应用程序的层。 + +添加了 Dockerfile 以后,我通过运行下面的命令来生成一个镜像,并使用生成的镜像启动一个容器(确保在和 Dockerfile 相同的目录下进行操作,并且你应该使用自己的用户名)。 + +``` +docker build -t niksoper/netcore-books . +docker run -it niksoper/netcore-books +``` + +你应该看到程序能够和之前一样的运行,不过这一次我们不需要像之前那样安装源代码,因为源代码已经包含在 docker 镜像里面了。 + +#### 暴露并发布端口 + +这个 API 并不是特别有用,除非我们需要从容器外面和它进行通信。 Docker 已经有了暴露和发布端口的概念,但这是两件完全不同的事。 + +据 Docker [官方文档](https://docs.docker.com/engine/reference/builder/#/expose): + +> `EXPOSE` 指令通知 Docker 容器在运行时监听特定的网络端口。`EXPOSE` 指令不能够让容器的端口可被主机访问。要使可被访问,你必须通过 `-p` 标志来发布一个端口范围或者使用 `-P` 标志来发布所有暴露的端口 + +`EXPOSE` 指令只是将元数据添加到镜像上,所以你可以如文档中说的认为它是镜像消费者。从技术上讲,我本应该忽略 `EXPOSE 5000` 这行指令,因为我知道 API 正在监听的端口,但把它们留下很有用的,并且值得推荐。 + +在这个阶段,我想直接从主机访问这个 API ,因此我需要通过 `-p` 指令来发布这个端口,这将允许请求从主机上的端口 5000 转发到容器上的端口 5000,无论这个端口是不是之前通过 Dockerfile 暴露的。 + +``` +docker run -d -p 5000:5000 niksoper/netcore-books +``` + +通过 `-d` 指令告诉 docker 在分离模式下运行容器,因此我们不能看到它的输出,但是它依旧会运行并监听端口 5000。你可以通过 `docker ps` 来证实这件事。 + +因此,接下来我准备从主机向容器发起一个请求来庆祝一下: + +``` +curl http://localhost:5000/api/books +``` + +它不工作。 + +重复进行相同 `curl` 请求,我看到了两个错误:要么是 `curl: (56) Recv failure: Connection reset by peer`,要么是 `curl: (52) Empty reply from server`。 + +我返回去看 docker run 的[文档](https://docs.docker.com/engine/reference/run/#/expose-incoming-ports),然后再次检查我所使用的 `-p` 选项以及 Dockerfile 中的 `EXPOSE` 指令是否正确。我没有发现任何问题,这让我开始有些沮丧。 + +重新振作起来以后,我决定去咨询当地的一个 Scott Logic DevOps 大师 - Dave Wybourn(也在[这篇 Docker Swarm 的文章](http://blog.scottlogic.com/2016/08/30/docker-1-12-swarm-mode-round-robin.html)里提到过),他的团队也曾遇到这个实际问题。这个问题是我没有配置过 [Kestral](https://docs.asp.net/en/latest/fundamentals/servers.html#kestrel),这是一个全新的轻量级、跨平台 web 服务器,用于 .NET Core 。 + +默认情况下, Kestrel 会监听 http://localhost:5000。但问题是,这儿的 `localhost` 是一个回路接口。 + +据[维基百科](https://en.wikipedia.org/wiki/Localhost): + +> 在计算机网络中,localhost 是一个代表本机的主机名。本地主机可以通过网络回路接口访问在主机上运行的网络服务。通过使用回路接口可以绕过任何硬件网络接口。 + +当运行在容器内时这是一个问题,因为 `localhost` 只能够在容器内访问。解决方法是更新 `Startup.cs` 里的 `Main` 方法来配置 Kestral 监听的 URL: + +``` +public static void Main(string[] args) +{ + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseUrls("http://*:5000") // 在所有网络接口上监听端口 5000 + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); +} +``` + +通过这些额外的配置,我可以重建镜像,并在容器中运行应用程序,它将能够接收来自主机的请求: + +``` +docker build -t niksoper/netcore-books . +docker run -d -p 5000:5000 niksoper/netcore-books +curl -i http://localhost:5000/api/books +``` + +我现在得到下面这些相应: + +``` +HTTP/1.1 200 OK +Date: Tue, 30 Aug 2016 15:25:43 GMT +Transfer-Encoding: chunked +Content-Type: application/json; charset=utf-8 +Server: Kestrel + +[{"id":"1","title":"RESTful API with ASP.NET Core MVC 1.0","author":"Nick Soper"}] +``` + +### 在产品环境中运行 KESTREL + +[微软的介绍](https://docs.asp.net/en/latest/publishing/linuxproduction.html#why-use-a-reverse-proxy-server): + +> Kestrel 可以很好的处理来自 ASP.NET 的动态内容,然而,网络服务部分的特性没有如 IIS,Apache 或者 Nginx 那样的全特性服务器那么好。反向代理服务器可以让你不用去做像处理静态内容、缓存请求、压缩请求、SSL 端点这样的来自 HTTP 服务器的工作。 + +因此我需要在我的 Linux 机器上把 Nginx 设置成一个反向代理服务器。微软介绍了如何[发布到 Linux 生产环境下](https://docs.asp.net/en/latest/publishing/linuxproduction.html)的指导教程。我把说明总结在这儿: + +1. 通过 `dotnet publish` 来给应用程序产生一个自包含包。 +2. 把已发布的应用程序复制到服务器上 +3. 安装并配置 Nginx(作为反向代理服务器) +4. 安装并配置 [supervisor](http://supervisord.org/)(用于确保 Nginx 服务器处于运行状态中) +5. 安装并配置 [AppArmor](https://wiki.ubuntu.com/AppArmor)(用于限制应用的资源使用) +6. 配置服务器防火墙 +7. 安全加固 Nginx(从源代码构建和配置 SSL) + +这些内容已经超出了本文的范围,因此我将侧重于如何把 Nginx 配置成一个反向代理服务器。自然地,我通过 Docker 来完成这件事。 + +### 在另一个容器中运行 NGINX + +我的目标是在第二个 Docker 容器中运行 Nginx 并把它配置成我们的应用程序容器的反向代理服务器。 + +我使用的是[来自 Docker Hub 的官方 Nginx 镜像](https://hub.docker.com/_/nginx/)。首先我尝试这样做: + +``` +docker run -d -p 8080:80 --name web nginx +``` + +这启动了一个运行 Nginx 的容器并把主机上的 8080 端口映射到了容器的 80 端口上。现在在浏览器中打开网址 `http://localhost:8080` 会显示出 Nginx 的默认登录页面。 + +现在我们证实了运行 Nginx 是多么的简单,我们可以关闭这个容器。 + +``` +docker rm -f web +``` + +### 把 NGINX 配置成一个反向代理服务器 + +可以通过像下面这样编辑位于 `/etc/nginx/conf.d/default.conf` 的配置文件,把 Nginx 配置成一个反向代理服务器: + +``` +server { + listen 80; + + location / { + proxy_pass http://localhost:6666; + } +} +``` + +通过上面的配置可以让 Nginx 将所有对根目录的访问请求代理到 `http://localhost:6666`。记住这里的 `localhost` 指的是运行 Nginx 的容器。我们可以在 Nginx容器内部利用卷来使用我们自己的配置文件: + +``` +docker run -d -p 8080:80 \ +-v /path/to/my.conf:/etc/nginx/conf.d/default.conf \ +nginx +``` + +注意:这把一个单一文件从主机映射到容器中,而不是一个完整目录。 + +### 在容器间进行通信 + +Docker 允许内部容器通过共享虚拟网络进行通信。默认情况下,所有通过 Docker 守护进程启动的容器都可以访问一种叫做“桥”的虚拟网络。这使得一个容器可以被另一个容器在相同的网络上通过 IP 地址和端口来引用。 + +你可以通过监测(inspect)容器来找到它的 IP 地址。我将从之前创建的 `niksoper/netcore-books` 镜像中启动一个容器并监测(inspect)它: + +``` +docker run -d -p 5000:5000 --name books niksoper/netcore-books +docker inspect books +``` + +![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-ip.PNG) + +我们可以看到这个容器的 IP 地址是 `"IPAddress": "172.17.0.3"`。 + +所以现在如果我创建下面的 Nginx 配置文件,并使用这个文件启动一个 Nginx容器, 它将代理请求到我的 API : + +``` +server { + listen 80; + + location / { + proxy_pass http://172.17.0.3:5000; + } +} +``` + +现在我可以使用这个配置文件启动一个 Nginx 容器(注意我把主机上的 8080 端口映射到了 Nginx 容器上的 80 端口): + +``` +docker run -d -p 8080:80 \ +-v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \ +nginx +``` + +一个到 `http://localhost:8080` 的请求将被代理到应用上。注意下面 `curl` 响应的 `Server` 响应头: + +![](http://blog.scottlogic.com/nsoper/assets/nginx-proxy-response.PNG) + +### DOCKER COMPOSE + +在这个地方,我为自己的进步而感到高兴,但我认为一定还有更好的方法来配置 Nginx,可以不需要知道应用程序容器的确切 IP 地址。另一个当地的 Scott Logic DevOps 大师 Jason Ebbin 在这个地方进行了改进,并建议使用 [Docker Compose](https://docs.docker.com/compose/)。 + +概况描述一下,Docker Compose 使得一组通过声明式语法互相连接的容器很容易启动。我不想再细说 Docker Compose 是如何工作的,因为你可以在[之前的文章](http://blog.scottlogic.com/2016/01/25/playing-with-docker-compose-and-erlang.html)中找到。 + +我将通过一个我所使用的 `docker-compose.yml` 文件来启动: + +``` +version: '2' +services: + books-service: + container_name: books-api + build: . + + reverse-proxy: + container_name: reverse-proxy + image: nginx + ports: + - "9090:8080" + volumes: + - ./proxy.conf:/etc/nginx/conf.d/default.conf +``` + +*这是版本 2 语法,所以为了能够正常工作,你至少需要 1.6 版本的 Docker Compose。* + +这个文件告诉 Docker 创建两个服务:一个是给应用的,另一个是给 Nginx 反向代理服务器的。 + +### BOOKS-SERVICE + +这个与 docker-compose.yml 相同目录下的 Dockerfile 构建的容器叫做 `books-api`。注意这个容器不需要发布任何端口,因为只要能够从反向代理服务器访问它就可以,而不需要从主机操作系统访问它。 + +### REVERSE-PROXY + +这将基于 nginx 镜像启动一个叫做 `reverse-proxy` 的容器,并将位于当前目录下的 `proxy.conf` 文件挂载为配置。它把主机上的 9090 端口映射到容器中的 8080 端口,这将允许我们在 `http://localhost:9090` 上通过主机访问容器。 + +`proxy.conf` 文件看起来像下面这样: + +``` +server { + listen 8080; + + location / { + proxy_pass http://books-service:5000; + } +} +``` + +这儿的关键点是我们现在可以通过名字引用 `books-service`,因此我们不需要知道 `books-api` 这个容器的 IP 地址! + +现在我们可以通过一个运行着的反向代理启动两个容器(`-d` 意味着这是独立的,因此我们不能看到来自容器的输出): + +``` +docker compose up -d +``` + +验证我们所创建的容器: + +``` +docker ps +``` + +最后来验证我们可以通过反向代理来控制该 API : + +``` +curl -i http://localhost:9090/api/books +``` + +### 怎么做到的? + +Docker Compose 通过创建一个新的叫做 `mvclibrary_default` 的虚拟网络来实现这件事,这个虚拟网络同时用于 `books-api` 和 `reverse-proxy` 容器(名字是基于 `docker-compose.yml` 文件的父目录)。 + +通过 `docker network ls` 来验证网络已经存在: + +![](http://blog.scottlogic.com/nsoper/assets/docker-network-ls.PNG) + +你可以使用 `docker network inspect mvclibrary_default` 来看到新的网络的细节: + +![](http://blog.scottlogic.com/nsoper/assets/network-inspect.PNG) + +注意 Docker 已经给网络分配了子网:`"Subnet": "172.18.0.0/16"`。/16 部分是无类域内路由选择(CIDR),完整的解释已经超出了本文的范围,但 CIDR 只是表示 IP 地址范围。运行 `docker network inspect bridge` 显示子网:`"Subnet": "172.17.0.0/16"`,因此这两个网络是不重叠的。 + +现在用 `docker inspect books-api` 来确认应用程序的容器正在使用该网络: + +![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-books-api.PNG) + +注意容器的两个别名(`"Aliases"`)是容器标识符(`3c42db680459`)和由 `docker-compose.yml` 给出的服务名(`books-service`)。我们通过 `books-service` 别名在自定义 Nginx 配置文件中来引用应用程序的容器。这本可以通过 `docker network create` 手动创建,但是我喜欢用 Docker Compose,因为它可以干净简洁地将容器创建和依存捆绑在一起。 + +### 结论 + +所以现在我可以通过几个简单的步骤在 Linux 系统上用 Nginx 运行应用程序,不需要对主机操作系统做任何长期的改变: + +``` +git clone https://github.com/niksoper/aspnet5-books.git +cd aspnet5-books/src/MvcLibrary +git checkout blog-docker +docker-compose up -d +curl -i http://localhost:9090/api/books +``` + +我知道我在这篇文章中所写的内容不是一个真正的生产环境就绪的设备,因为我没有写任何有关下面这些的内容,绝大多数下面的这些主题都需要用单独一篇完整的文章来叙述。 + +- 安全考虑比如防火墙和 SSL 配置 +- 如何确保应用程序保持运行状态 +- 如何选择需要包含的 Docker 镜像(我把所有的都放入了 Dockerfile 中) +- 数据库 - 如何在容器中管理它们 + +对我来说这是一个非常有趣的学习经历,因为有一段时间我对探索 ASP.NET Core 的跨平台支持非常好奇,使用 “Configuratin as Code” 的 Docker Compose 方法来探索一下 DevOps 的世界也是非常愉快并且很有教育意义的。 + +如果你对 Docker 很好奇,那么我鼓励你来尝试学习它 或许这会让你离开舒适区,不过,有可能你会喜欢它? + +-------------------------------------------------------------------------------- + +via: http://blog.scottlogic.com/2016/09/05/hosting-netcore-on-linux-with-docker.html + +作者:[Nick Soper][a] +译者:[ucasFL](https://github.com/ucasFL) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://blog.scottlogic.com/nsoper +[1]: http://blog.scottlogic.com/2016/01/20/restful-api-with-aspnet50.html +[2]: https://docs.microsoft.com/en-us/dotnet/articles/core/migrating-from-dnx +[3]: https://docs.asp.net/en/latest/migration/rc1-to-rtm.html +[4]: http://www.paraesthesia.com/archive/2016/06/29/netcore-rtm-where-is-autofac/ + + + + + + diff --git a/published/20160914 Down and dirty with Windows Nano Server 2016.md b/published/20160914 Down and dirty with Windows Nano Server 2016.md new file mode 100755 index 0000000000..20a49755fa --- /dev/null +++ b/published/20160914 Down and dirty with Windows Nano Server 2016.md @@ -0,0 +1,92 @@ +一起来看看 Windows Nano Server 2016 +==== + +![](http://images.techhive.com/images/article/2016/04/pokes-fun-at-1164459_1280-100654917-primary.idge.jpg) + +> 对于远程 Windows 服务器管理,Nano Server 是快速的功能强大的工具,但是你需要知道你在做的是什么。 + +下面谈论[即将到来的 Windows Server 2016 的 Nano 版本][1],带远程管理和命令行设计,考虑到了私有云和数据中心。但是,谈论它和动手用它区别还是很大的,让我们深入看下去。 + +没有本地登录,且所有的程序、工具和代理都是 64 位,安装升级快捷,只需要非常少的时间就能重启。它非常适合做计算节点(无论在不在集群)、存储主机、DNS 服务器、IIS web 服务器,以及在容器中提供主机服务或者虚拟化的客户操作系统。 + +Nano 服务器也并不是太好玩的,你必须知道你要完成什么。你会看着远程的 PowerShell 的连接而不知所措,但如果你知道你要做什么的话,那它就非常方便和强大。 + +微软为设置 Nano 服务器提供了一个[快速入门手册][2] ,在这我给大家具体展示一下。 + +首先,你必须创建一个 .vhd 格式的虚拟磁盘文件。在步骤一中,我有几个文件不在正确的位置的小问题,Powershell 总是提示不匹配,所以我不得不反复查找文件的位置方便我可以用 ISO 信息(需要把它拷贝粘贴到你要创建 .vhd 文件的服务器上)。当你所有的东西都位置正确了,你可以开始创建 .vhd 文件的步骤了。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-1-100682371-large.idge.jpg) + +*步骤一:尝试运行 New-NanoServerImage 脚本时,很多文件路径错误,我把文件位置的问题搞定后,就能进行下去创建 .vhd 文件了(如图所示)* + +接下来,你可以用创建 VM 向导在 Hyper-V 里面创建 VM 虚拟机,你需要指定一个存在的虚拟磁盘同时指向你新创建的 .vhd 文件。(步骤二) + +![](http://images.techhive.com/images/article/2016/09/nano-server-2-100682368-large.idge.jpg) + +*步骤二:连接虚拟磁盘(一开始创建的)* + +当你启动 Nano 服务器的时候,你或许会发现有内存错误,提示你已经分配了多少内存,如果你还有其他 VM 虚拟机的话, Hyper-V 服务器剩余了多少内存。我已经关闭了一些虚机以增加可用内存,因为微软说 Nano 服务器最少需要 512M 内存,但是它又推荐你至少 800M,最后我分配了 8G 内存因为我给它 1G 的时候根本不能用,为了方便,我也没有尝试其他的内存配置。 + +最后我终于到达登录界面,到达 Nano Server Recovery Console 界面(步骤三),Nano 服务器的基本命令界面。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-3-100682369-large.idge.jpg) + +*步骤三:Nano 服务器的恢复窗口* + +本来我以为进到这里会很美好,但是当我弄明白几个细节(如何加入域,弹出个磁盘,添加用户),我明白一些配置的细节,用几个参数运行 New-NanoServerImage cmdlet 会变得很简单。 + +然而,当你的服务器运行时,也有办法确认它的状态,就像步骤四所示,这一切都始于一个远程 PowerShell 连接。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-4-100682370-large.idge.jpg) + +*步骤四:从 Nano 服务器恢复窗口得到的信息,你可以从远程运行一个 Powershell 连接。* + +微软展示了如何创建连接,但是尝试了四个不同的方法,我发现 [MSDN][4] 提供的是最好的方式,步骤五展示。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-5-100682372-large.idge.jpg) + +*步骤五:创建到 Nano 服务器的 PowerShell 远程连接* + +提示:创建远程连接,你可以用下面这条命令: + +``` +Enter-PSSession –ComputerName "192.168.0.100"-Credential ~\Administrator. +``` + +如果你提前知道这台服务器将要做 DNS 服务器或者集群中的一个计算节点,可以事先在 .vhd 文件中加入一些角色和特定的软件包。如果不太清楚,你可以先建立 PowerShell 连接,然后安装 NanoServerPackage 并导入它,你就可以用 Find-NanoServerPackage 命令来查找你要部署的软件包了(步骤六)。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-6-100682373-large.idge.jpg) + +*步骤六:你安装完并导入 NanoServerPackage 后,你可以找到你需要启动服务器的工具以及设置的用户和你需要的一些其他功能包。* + +我测试了安装 DNS 安装包,用 `Install-NanoServerPackage –Name Microsoft-NanoServer-DNS-Package` 这个命令。安装好后,我用 `Enable-WindowsOptionalFeature –Online –FeatureName DNS-Server-Full-Role` 命令启用它。 + +之前我并不知道这些命令,之前也从来没运行过,也没有弄过 DNS,但是现在稍微研究一下我就能用 Nano 服务器建立一个 DNS 服务并且运行它。 + +接下来是用 PowerShell 来配置 DNS 服务器。这个复杂一些,需要网上研究一下。但是它也不是那么复杂,当你学习了使用 cmdlet ,就可以用 `Add-DNSServerPrimaryZone` 添加 zone,用 `Add-DNSServerResourceRecordA` 命令在 zone 中添加记录。 + +做完这些命令行的工作,你可能需要验证是否起效。你可以快速浏览一下 PowerShell 命令,没有太多的 DNS 命令(使用 `Get-Command` 命令)。 + +如果你需要一个图形化的配置,你可以从一个图形化的主机上用图形管理器打开 Nano 服务器的 IP 地址。右击需要管理的服务器,提供你的验证信息,你连接好后,在图形管理器中右击然后选择 Add Roles and Features,它会显示你安装好的 DNS 服务,如步骤七所示。 + +![](http://images.techhive.com/images/article/2016/09/nano-server-7-100682374-large.idge.jpg) + +*步骤七:通过图形化界面验证 DNS 已经安装* + +不用麻烦登录服务器的远程桌面,你可以用服务器管理工具来进行操作。当然你能验证 DNS 角色不代表你能够通过 GUI 添加新的角色和特性,它有命令行就足够了。你现在可以用 Nano 服务器做一些需要的调整了。 + +-------------------------------------------------------------------------------- + +via: http://www.infoworld.com/article/3119770/windows-server/down-and-dirty-with-windows-nano-server-2016.html + +作者:[J. Peter Bruzzese][a] +译者:[jiajia9linuxer](https://github.com/jiajia9linuxer) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://www.infoworld.com/author/J.-Peter-Bruzzese/ +[1]: http://www.infoworld.com/article/3049191/windows-server/nano-server-a-slimmer-slicker-windows-server-core.html +[2]: https://technet.microsoft.com/en-us/windows-server-docs/compute/nano-server/getting-started-with-nano-server +[3]: https://technet.microsoft.com/en-us/windows-server-docs/get-started/system-requirements--and-installation +[4]: https://msdn.microsoft.com/en-us/library/mt708805(v=vs.85).aspx diff --git a/translated/talk/my-open-source-story/20160415 A four year, action-packed experience with Wikipedia.md b/published/201610/20160415 A four year, action-packed experience with Wikipedia.md similarity index 52% rename from translated/talk/my-open-source-story/20160415 A four year, action-packed experience with Wikipedia.md rename to published/201610/20160415 A four year, action-packed experience with Wikipedia.md index b2be6a7a01..f76494adf0 100644 --- a/translated/talk/my-open-source-story/20160415 A four year, action-packed experience with Wikipedia.md +++ b/published/201610/20160415 A four year, action-packed experience with Wikipedia.md @@ -3,31 +3,30 @@ ![](https://opensource.com/sites/default/files/styles/image-full-size/public/images/life/wikipedia_checkuser_lead.jpg?itok=4lVDjSSM) - -我自认为自己是个奥迪亚的维基人。我通过写文章和纠正错误的文章贡献[奥迪亚][1]知识(在印度的[奥里萨邦][2]的主要的语言 )给很多维基项目,像维基百科和维基文库,我也为用印地语和英语写的维基文章做贡献。 +我自认为自己是个奥迪亚的维基人。我通过写文章和纠正文章错误给很多维基项目贡献了[奥迪亚(Odia)][1]知识(这是在印度的[奥里萨邦][2]的主要语言),比如维基百科和维基文库,我也为用印地语和英语写的维基文章做贡献。 ![](https://opensource.com/sites/default/files/resize/1st_day_at_odia_wikipedia_workshop_image_source_facebook-200x133.jpg) -我对维基的爱从我第 10 次考试(像在美国的 10 年级学生的年级考试)之后看到的英文维基文章[孟加拉解放战争][3]开始。一不小心我打开了印度维基文章的链接,,并且开始阅读它. 在文章左边有用奥迪亚语写的东西, 所以我点击了一下, 打开了一篇在奥迪亚维基上的 [????/Bhārat][4] 文章. 发现了用母语写的维基让我很激动! +我对维基的爱从我第 10 次考试(像在美国的 10 年级学生的年级考试)之后看到的英文维基文章[孟加拉解放战争][3]开始。一不小心我打开了印度维基文章的链接,并且开始阅读它。在文章左边有用奥迪亚语写的东西,所以我点击了一下, 打开了一篇在奥迪亚维基上的 [ଭାରତ/Bhārat][4] 文章。发现了用母语写的维基让我很激动! ![](https://opensource.com/sites/default/files/resize/introducing_wikipedia_at_google_io_image_by_gdg_bhubaneswar-251x166.png) -一个邀请读者参加 2014 年 4 月 1 日第二次布巴内斯瓦尔的研讨会的标语引起了我的好奇。我过去从来没有为维基做过贡献, 只用它搜索过, 我并不熟悉开源和社区贡献流程。加上,我只有 15 岁。我注册了。在研讨会上有很多语言爱好者,我是中间最年轻的一个。尽管我害怕我父亲还是鼓励我去参与。他起了非常重要的作用—他不是一个维基媒体人,和我不一样,但是他的鼓励给了我改变奥迪亚维基的动力和参加社区活动的勇气。 +一个邀请读者参加 2014 年 4 月 1 日召开的第二届布巴内斯瓦尔研讨会的旗帜广告引起了我的好奇。我过去从来没有为维基做过贡献,只用它做过研究,我并不熟悉开源和社区贡献流程。再加上,当时我只有 15 岁。我注册了,在研讨会上有很多语言爱好者,他们全比我大。尽管我害怕,我父亲还是鼓励我去参与。他起了非常重要的作用—他不是一个维基媒体人,和我不一样,但是他的鼓励给了我改变奥迪亚维基的动力和参加社区活动的勇气。 -我相信奥迪亚语言和文学需要改进很多错误的想法和知识缺口所以,我帮助组织关于奥迪亚维基的活动和和研讨会,我完成了如下列表: +我觉得关于奥迪亚语言和文学的知识很多需要改进,有很多错误的观念和知识缺口,所以,我帮助组织关于奥迪亚维基的活动和和研讨会,我完成了如下列表: -* 发起3次主要的 edit-a-thons 在奥迪亚维基:2015 年妇女节,2016年妇女节, abd [Nabakalebara edit-a-thon 2015][5] +* 在奥迪亚维基发起 3 次主要的 edit-a-thons :2015 年妇女节、2016年妇女节、abd [Nabakalebara edit-a-thon 2015][5] * 在全印度发起了征集[檀车节][6]图片的比赛 * 在谷歌的两大事件([谷歌I/O大会扩展][7]和谷歌开发节)中代表奥迪亚维基 -* 在2015[Perception][8]和第一次[Open Access India][9]会议 +* 在2015 [Perception][8] 和第一次 [Open Access India][9] 会议 ![](https://opensource.com/sites/default/files/resize/bengali_wikipedia_10th_anniversary_cc-by-sa4.0_biswaroop_ganguly-251x166.jpg) -我只编辑维基项目到了去年,在 2015 年一月,当我出席[孟加拉语维基百科的十周年会议][10]和[毗瑟挐][11]活动时,[互联网和社会中心][12]主任,邀请我参加[培训培训师][13] 计划。我的灵感始于扩展奥迪亚维基,为[华丽][14]的活动举办的聚会和培训新的维基人。这些经验告诉我作为一个贡献者该如何为社区工作。 +我在维基项目当编辑直到去年( 2015 年 1 月)为止,当我出席[孟加拉语维基百科的十周年会议][10]和[毗瑟挐][11]活动时,[互联网和社会中心][12]主任,邀请我参加[培训师培训计划][13]。我的开始超越奥迪亚维基,为[GLAM][14]的活动举办聚会和培训新的维基人。这些经验告诉我作为一个贡献者该如何为社区工作。 -[Ravi][15],在当时维基的主任,在我的旅程也发挥了重要作用。他非常相信我让我参与到了[Wiki Loves Food][16],维基共享中的公共摄影比赛,组织方是[2016 印度维基会议][17]。在2015的 Loves Food 活动期间,我的团队在维基共享中加入了 10,000+ 有 CC BY-SA 协议的图片。Ravi 进一步巩固了我的承诺,和我分享很多关于维基媒体运动的信息,和他自己在 [奥迪亚维基百科13周年][18]的经历。 +[Ravi][15],在当时印度维基的总监,在我的旅程也发挥了重要作用。他非常相信我,让我参与到了 [Wiki Loves Food][16],维基共享中的公共摄影比赛,组织方是 [2016 印度维基会议][17]。在 2015 的 Loves Food 活动期间,我的团队在维基共享中加入了 10000+ 采用 CC BY-SA 协议的图片。Ravi 进一步坚定了我的信心,和我分享了很多关于维基媒体运动的信息,以及他自己在 [奥迪亚维基百科 13 周年][18]的经历。 -不到一年后,在 2015 年十二月,我成为了网络与社会中心的[获取知识的程序][19]的项目助理( CIS-A2K 运动)。我自豪的时刻之一是在普里的研讨会,我们从印度带了 20 个新的维基人来编辑奥迪亚维基媒体社区。现在,我的指导者在一个普里非正式聚会被叫作[WikiTungi][20]。我和这个小组一起工作,把 wikiquotes 变成一个真实的计划项目。在奥迪亚维基我也致力于缩小性别差距。[八个女编辑][21]也正帮助组织聚会和研讨会,参加 [Women's History month edit-a-thon][22]。 +不到一年后,在 2015 年十二月,我成为了网络与社会中心的[获取知识计划][19]的项目助理( CIS-A2K 运动)。我自豪的时刻之一是在印度普里的研讨会,我们给奥迪亚维基媒体社区带来了 20 个新的维基人。现在,我在一个名为 [WikiTungi][20] 普里的非正式聚会上指导着维基人。我和这个小组一起工作,把奥迪亚 Wikiquotes 变成一个真实的计划项目。在奥迪亚维基我也致力于缩小性别差距。[八个女编辑][21]也正帮助组织聚会和研讨会,参加 [Women's History month edit-a-thon][22]。 在我四年短暂而令人激动的旅行之中,我也参与到 [维基百科的教育项目][23],[通讯团队][24],两个全球的 edit-a-thons: [Art and Feminsim][25] 和 [Menu Challenge][26]。我期待着更多的到来! @@ -38,8 +37,8 @@ via: https://opensource.com/life/16/4/my-open-source-story-sailesh-patnaik 作者:[Sailesh Patnaik][a] -译者:[译者ID](https://github.com/hkurj) -校对:[校对者ID](https://github.com/校对者ID) +译者:[hkurj](https://github.com/hkurj) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出 diff --git a/published/201610/20160510 Aspiring sys admin works his way up in Linux.md b/published/201610/20160510 Aspiring sys admin works his way up in Linux.md new file mode 100644 index 0000000000..9ce7f09138 --- /dev/null +++ b/published/201610/20160510 Aspiring sys admin works his way up in Linux.md @@ -0,0 +1,38 @@ +有理想,有追求的系统管理员会在 Linux 中成长 +=============================================== + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/images/business/BIZ_workplay.png?itok=uQqFssrf) + +我第一次看到运行着的 Linux 操作系统是在我的首份工作时,大约是在 2001 年。当时我在澳大利亚的一家汽车业供应商担任客户经理,和公司的 IT 工程师共用一间办公室。他做了一台 CD 刻录工作站(一个可以同时刻录好几张 CD 的巨大的东西),然后我们可以把汽车零配件目录刻录到 CD 并送给客户。刻录工作站最初是为 Windows 设计的,但是他一直没能让刻录机正常工作。最终他放弃在 Windows 上使用的念头并且换成在 Linux 上使用,刻录机完美工作了。 + +对我来说,Linux 的一切都是晦涩难懂的。大部分工作都在看起来像 DOS(LCTT 译注:磁盘操作系统,早期的个人电脑操作系统) 的命令行下完成,但它更加强大(后来我才认识到这一点)。我从 1993 年就开始使用 Mac 电脑,在那个时候,在我看来命令行界面有点过时了。 + +直到几年后——我记得是 2009 年,我才真正的认识 Linux。那时我已经移民荷兰并且在一家零售供应商那里找到了工作。那是一个只有 20 人的小公司,我除了做一个关键客户经理的正常工作,还无意间成了个一线 IT 技术支持。只要东西有了故障,他们总是会在花大价钱请外部 IT 顾问之前先来询问我。 + +我的一个同事因为点击了一封似乎来自 DHL(LCTT 译注:全球著名的邮递和物流集团 Deutsche Post DHL 旗下公司) 的电子邮件所附带的一个 .exe 文件而受到了一次网络钓鱼攻击。(是的,这的确发生了。)他的电脑被完全入侵了,他什么事都做不了。甚至完全格式化电脑都不起作用,好像病毒只靠它丑陋的脑袋就能活下来。我在后来才了解到这种情况可能是病毒被复制到了 MBR(主引导记录)里。而此时,为了节约成本,公司已经终止了和外部 IT 顾问的合同。 + +于是我帮助同事安装了 Ubuntu 操作系统让他继续工作,这的确很有效。他的电脑再次嗡嗡的运转了,而且我找到了他们工作必需的所有应用。我承认,从某些方面来说这不是最优雅的解决方案,不过他(包括我)喜欢这个操作系统的流畅度和稳定性。 + +然而我的同事在 Windows 世界形成的观念根深蒂固,他无法习惯新的使用方式,开始不停的抱怨。(很耳熟吧?) + +虽然我的同事不能忍受新的使用方式,但我发现到这对我这个 Mac 用户已经不是一个问题。它们之间有很多相似点,我被迷住了。所以我在我工作用的笔记本电脑上又安装了一个 Ubuntu 组成双系统,我的工作效率提高了,让机器做我想做的事也变得更容易。从那时起我有规律的使用了几个 Linux 发行版,但我最喜欢 Ubuntu 和 Elementary。 + +当时我失业了,因此我有大量的时间给自己充电。由于我一直对 IT 行业很感兴趣,所以我致力于研究对 Linux 操作系统的管理。但是目前得到一个展示学识的机会太难了,因为我多年来所学的 95% 都不能简单的通过邮寄一份简历展示出来。我需要一个入职面试来展示我所知道的东西,所以我进行了 Linux 认证得到证书,希望能给我一些帮助。 + +我对开源做贡献已经有好一阵子了。刚开始时是为 xTuple ERP(LCTT 译注:世界领先的开源 ERP 软件)做翻译工作(英语译德语),现在已经转为在 Twitter 上做 Mozilla 的“客户服务”,提交 bug 报告等。我为自由和开源软件做推广宣传(取得了不同程度的成功)并且尽我所能在财政方面支持一些 FOSS(LCTT 译注:自由及开源软件)倡导组织(DuckDuckGo、bof.nl、EFF、GIMP、LibreCAD、维基百科以及其他)。目前我也在准备设立一个本地的隐私咖啡馆(LCTT 译注:这是荷兰的一个旨在强化网上个人隐私安全的咖啡馆/志愿者组织,参见: https://privacycafe.bof.nl/)。 + +除此之外,我已经开始写我的第一本书。这应该是一本涉及计算机隐私和安全领域的简易实用手册,适用于普通计算机用户,我希望它能在今年年底自主出版。(这本书遵循知识共享许可协议 CC。)至于这本书的内容,正如你预料的那样我会详细解释为什么隐私如此重要,以及“我没有什么需要隐藏“这种思想错的有多么离谱。但是最主要的部分还是指导读者如何避免让人讨厌的广告追踪,加密你的硬盘和邮件,通过 OTR(LCTT 译注:一种安全协议,为即时通讯提供加密保护)网上聊天,如何使用 TOR(LCTT 译注:第二代洋葱路由,可以在因特网上进行匿名交流)等等。虽然起初说这是一个手册,但我尽量会使用随和的语调来叙述,并且会根据精彩的我亲身经历的故事让它更加通俗易懂。 + +我依然爱着我所有的 Mac,只要买得起(主要因为它那伟大的构造),我还会使用它们,但是我的 Linux 一直在虚拟机里完成我的几乎所有的日常工作。没有什么不可思议的,不是自夸:文档编辑(LibreOffice 和 Scribus),建立我的网页和博客(Wordpress 和 Jekyll),编辑图片(Shotwell 和 Gimp),听音乐(Rhythmbox),以及几乎其他任何工作。 + +不论我最后怎么找到工作,Linux 永远都是我必用的操作系统。 + +-------------------------------------------------------------------------------- + +via: https://opensource.com/life/16/5/my-linux-story-rene-raggl + +作者:[Rene Raggl][a] +译者:[fuowang](https://github.com/fuowang) +校对:[wxy](https://github.com/wxy) + +[a]: https://opensource.com/users/rraggl diff --git a/published/201610/20160511 4 Container Networking Tools to Know.md b/published/201610/20160511 4 Container Networking Tools to Know.md new file mode 100644 index 0000000000..30df23eada --- /dev/null +++ b/published/201610/20160511 4 Container Networking Tools to Know.md @@ -0,0 +1,61 @@ +4 个你需要了解的容器网络工具 +=========== + +![](https://www.linux.com/sites/lcom/files/styles/rendered_file/public/network-crop.jpeg?itok=Na1tb9aR) + +> [Creative Commons Zero][1] + +有如此之多的各种新的云计算技术、工具和技术需要我们跟进,到底从哪里开始学习是一个艰难的决定。这一系列[下一代云计算技术][2]的文章旨在让你快速了解新兴和快速变化领域的重大项目和产品,比如软件定义网络(SDN)、容器,以及其交叉领域:容器网络。 + +对于企业容器部署,容器和网络之间的关系仍然是一个挑战。容器需要网络功能来连接分布式应用程序。根据一篇最新的[企业网络星球][3]的文章,一部分的挑战是“以隔离的方式部署容器,在提供隔离自己容器内数据的所需功能的同时,保持有效的连接性”。 + +流行的容器平台 [Docker][4],使用了软件定义虚拟网络来连接容器与本地网络。此外,它使用 Linux 的桥接功能和虚拟可扩展局域网(VXLAN)技术,可以在同一 Swarm 或容器集群内互相沟通。Docker 的插件架构也支持其他网络管理工具来管理容器网络,比如下面的提到的工具。 + +容器网络上的创新使得容器可以跨主机连接到其他容器上。这使开发人员可以在开发环境中,在一个主机上部署一个容器来运行一个应用,然后可以过渡到测试环境中,进而到生产环境中,使应用可以持续集成,敏捷开发,快速部署。 + +容器网络工具有助于实现容器网络的可扩展性,主要是通过: + +1. 使复杂的,多主机系统能够跨多个容器主机进行分发。 +2. 允许构建跨越多个公有云和私有云平台上的大量主机的容器系统。 + +![](https://www.linux.com/sites/lcom/files/styles/floated_images/public/john-willis_k.jpg?itok=lTsH9eqI) + +*John Willis speaking 在 Open Networking Summit 2016.* + +要获取更多信息,查看 [Docker 网络教程][5],是由 Brent Salisbury 和 John Willis 在最近的 [Open Networking Summit (ONS)][6]讲演的。更多关于 ONS 的演讲内容可以在[这里][7]找到。 + +你应该知道的容器网络工具和项目包括下述: + +- [Calico][8] -- Calico 项目(源自 [Metaswitch][9])利用边界网关协议(BGP)和集成的云编排系统来保证虚拟机和容器之间的 IP 通信安全。 +- [Flannel][10] -- Flannel (之前叫 rudder) 源自 [CoreOS][11],它提供了一个覆盖网络,可以作为一个现有的 SDN 解决方案的替代品。 +- [Weaveworks][12] -- Weaveworks 项目管理容器的工具包括 [Weave Net][13]、Weave Scope、Weave Flux。Weave Net 是一种用于构建和部署 Docker 容器的网络工具。 +- [Canal][14] -- 就在本周,CoreOS 和 Tigera 宣布了新的开源项目 Canal 的信息。据其声明,Canal 项目旨在结合部分 Calico 和 Flannel,“构造网络安全策略到网络架构和云管理平台之中”。 + +你可以通过 Linux 基金会的免费“云基础设施技术”课程来了解更多关于容器管理、软件定义网络和其他下一代云技术,这是一个在 edX 上提供的大规模公开在线课程。[课程注册目前已经开放][15],课程内容于 6 月开放。 + +------ + +via: https://www.linux.com/news/4-container-networking-tools-know + +作者:[AMBER ANKERHOLZ][a] +译者:[Bestony](https://github.com/Bestony) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://www.linux.com/users/aankerholz +[1]: https://www.linux.com/licenses/category/creative-commons-zero +[2]: https://www.linux.com/news/5-next-gen-cloud-technologies-you-should-know +[3]: http://www.enterprisenetworkingplanet.com/datacenter/datacenter-blog/container-networking-challenges-for-the-enterprise.html +[4]: https://docs.docker.com/engine/userguide/networking/dockernetworks/ +[5]: https://youtu.be/Le0bEg4taak +[6]: http://events.linuxfoundation.org/events/open-networking-summit +[7]: https://www.linux.com/watch-videos-from-ons2016 +[8]: https://www.projectcalico.org/ +[9]: http://www.metaswitch.com/cloud-network-virtualization +[10]: https://coreos.com/blog/introducing-rudder/ +[11]: https://coreos.com/ +[12]: https://www.weave.works/ +[13]: https://www.weave.works/products/weave-net/ +[14]: https://github.com/tigera/canal +[15]: https://training.linuxfoundation.org/linux-courses/system-administration-training/introduction-to-cloud-infrastructure-technologies?utm_source=linuxcom&utm_medium=article&utm_campaign=cloud%20mooc%20article%201 diff --git a/published/201610/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md b/published/201610/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md new file mode 100644 index 0000000000..71cb8fed2c --- /dev/null +++ b/published/201610/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md @@ -0,0 +1,176 @@ +Python 单元测试:assertTrue 是真值,assertFalse 是假值 +=========================== + +在这篇文章中,我们将介绍单元测试的布尔断言方法 `assertTrue` 和 `assertFalse` 与身份断言 `assertIs` 之间的区别。 + +### 定义 + +下面是目前[单元测试模块文档][1]中关于 `assertTrue` 和 `assertFalse` 的说明,代码进行了高亮: + + +> `assertTrue(expr, msg=None)` + +> `assertFalse(expr, msg=None)` + +>> 测试该*表达式*是真值(或假值)。 + +>> 注:这等价于 + +>> `bool(expr) is True` + +>> 而不等价于 + +>> `expr is True` + +>> (后一种情况请使用 `assertIs(expr, True)`)。 + +[Mozilla 开发者网络中定义 `真值`][2] 如下: + +> 在一个布尔值的上下文环境中能变成“真”的值 + +在 Python 中等价于: + +``` +bool(expr) is True +``` + +这个和 `assertTrue` 的测试目的完全匹配。 + +因此该文档中已经指出 `assertTrue` 返回真值,`assertFalse` 返回假值。这些断言方法从接受到的值构造出一个布尔值,然后判断它。同样文档中也建议我们根本不应该使用 `assertTrue` 和 `assertFalse`。 + +### 在实践中怎么理解? + +我们使用一个非常简单的例子 - 一个名称为 `always_true` 的函数,它返回 `True`。我们为它写一些测试用例,然后改变代码,看看测试用例的表现。 + +作为开始,我们先写两个测试用例。一个是“宽松的”:使用 `assertTrue` 来测试真值。另外一个是“严格的”:使用文档中建议的 `assertIs` 函数。 + +``` +import unittest + +from func import always_true + + +class TestAlwaysTrue(unittest.TestCase): + + def test_assertTrue(self): + """ + always_true returns a truthy value + """ + result = always_true() + + self.assertTrue(result) + + def test_assertIs(self): + """ + always_true returns True + """ + result = always_true() + + self.assertIs(result, True) +``` + +下面是 `func.py` 中的非常简单的函数代码: + +``` +def always_true(): + """ + I'm always True. + + Returns: + bool: True + """ + return True +``` + +当你运行时,所有测试都通过了: + +``` +always_true returns True ... ok +always_true returns a truthy value ... ok + +---------------------------------------------------------------------- +Ran 2 tests in 0.004s + +OK +``` + +开心ing~ + +现在,某个人将 `always_true` 函数改变成下面这样: + +``` +def always_true(): + """ + I'm always True. + + Returns: + bool: True + """ + return 'True' +``` + +它现在是用返回字符串 `"True"` 来替代之前反馈的 `True` (布尔值)。(当然,那个“某人”并没有更新文档 - 后面我们会增加难度。) + +这次结果并不如开心了: + +``` +always_true returns True ... FAIL +always_true returns a truthy value ... ok + +====================================================================== +FAIL: always_true returns True +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/tmp/assertttt/test.py", line 22, in test_is_true + self.assertIs(result, True) +AssertionError: 'True' is not True + +---------------------------------------------------------------------- +Ran 2 tests in 0.004s + +FAILED (failures=1) +``` + +只有一个测试用例失败了!这意味着 `assertTrue` 给了我们一个误判(false-positive)。在它不应该通过测试时,它通过了。很幸运的是我们第二个测试是使用 `assertIs` 来写的。 + +因此,跟手册上了解到的信息一样,为了保证 `always_true` 的功能和更严格测试的结果保持一致,应该使用 `assertIs` 而不是 `assertTrue`。 + +### 使用断言的辅助方法 + +使用 `assertIs` 来测试返回 `True` 和 `False` 来冗长了。因此,如果你有个项目需要经常检查是否是返回了 `True` 或者 `False`,那们你可以自己编写一些断言的辅助方法。 + +这好像并没有节省大量的代码,但是我个人觉得提高了代码的可读性。 + +``` +def assertIsTrue(self, value): + self.assertIs(value, True) + +def assertIsFalse(self, value): + self.assertIs(value, False) +``` + +### 总结 + +一般来说,我的建议是让测试越严格越好。如果你想测试 `True` 或者 `False`,听从[文档][3]的建议,使用 `assertIs`。除非不得已,否则不要使用 `assertTrue` 和 `assertFalse`。 + +如果你面对的是一个可以返回多种类型的函数,例如,有时候返回布尔值,有时候返回整形,那么考虑重构它。这是代码的异味。在 Python 中,抛出一个异常比使用 `False` 表示错误更好。 + +此外,如果你确实想使用断言来判断函数的返回值是否是真,可能还存在第二个代码异味 - 代码是正确封装了吗?如果 `assertTrue` 和 `assertFalse` 是根据正确的 `if` 语句来执行,那么值得检查下你是否把所有你想要的东西都封装在合适的位置。也许这些 `if` 语句应该封装在测试的函数中。 + +测试开心! + + +-------------------------------------------------------------------------------- + +via: http://jamescooke.info/python-unittest-asserttrue-is-truthy-assertfalse-is-falsy.html + +作者:[James Cooke][a] +译者:[chunyang-wen](https://github.com/chunyang-wen) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://jamescooke.info/pages/hello-my-name-is-james.html +[1]:https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTrue +[2]:https://developer.mozilla.org/en-US/docs/Glossary/Truthy +[3]:https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTrue diff --git a/published/201610/20160604 How to Build Your First Slack Bot with Python.md b/published/201610/20160604 How to Build Your First Slack Bot with Python.md new file mode 100755 index 0000000000..8ac40095ad --- /dev/null +++ b/published/201610/20160604 How to Build Your First Slack Bot with Python.md @@ -0,0 +1,317 @@ +如何运用 Python 建立你的第一个 Slack 聊天机器人? +============ + +[聊天机器人(Bot)](https://www.fullstackpython.com/bots.html) 是一种像 [Slack](https://slack.com/) 一样的实用的互动聊天服务方式。如果你之前从来没有建立过聊天机器人,那么这篇文章提供了一个简单的入门指南,告诉你如何用 Python 结合 [Slack API](https://api.slack.com/) 建立你第一个聊天机器人。 + +我们通过搭建你的开发环境, 获得一个 Slack API 的聊天机器人令牌,并用 Pyhon 开发一个简单聊天机器人。 + +### 我们所需的工具 + +我们的聊天机器人我们将它称作为“StarterBot”,它需要 Python 和 Slack API。要运行我们的 Python 代码,我们需要: + +* [Python 2 或者 Python 3](https://www.fullstackpython.com/python-2-or-3.html) +* [pip](https://pip.pypa.io/en/stable/) 和 [virtualenv](https://virtualenv.pypa.io/en/stable/) 来处理 Python [应用程序依赖关系](https://www.fullstackpython.com/application-dependencies.html) +* 一个可以访问 API 的[免费 Slack 账号](https://slack.com/),或者你可以注册一个 [Slack Developer Hangout team](http://dev4slack.xoxco.com/)。 +* 通过 Slack 团队建立的官方 Python [Slack 客户端](https://github.com/slackhq/python-slackclient)代码库 +* [Slack API 测试令牌](https://api.slack.com/tokens) + +当你在本教程中进行构建时,[Slack API 文档](https://api.slack.com/) 是很有用的。 + +本教程中所有的代码都放在 [slack-starterbot](https://github.com/mattmakai/slack-starterbot) 公共库里,并以 MIT 许可证开源。 + +### 搭建我们的环境 + +我们现在已经知道我们的项目需要什么样的工具,因此让我们来搭建我们所的开发环境吧。首先到终端上(或者 Windows 上的命令提示符)并且切换到你想要存储这个项目的目录。在那个目录里,创建一个新的 virtualenv 以便和其他的 Python 项目相隔离我们的应用程序依赖关系。 + +``` +virtualenv starterbot + +``` + +激活 virtualenv: + +``` +source starterbot/bin/activate + +``` + +你的提示符现在应该看起来如截图: + +![已经激活的 starterbot 的 virtualenv的命令提示符](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/virtualenv-activate.png) + +这个官方的 slack 客户端 API 帮助库是由 Slack 建立的,它可以通过 Slack 通道发送和接收消息。通过这个 `pip` 命令安装 slackclient 库: + +``` +pip install slackclient + +``` + +当 `pip` 命令完成时,你应该看到类似这样的输出,并返回提示符。 + +![在已经激活的 virtualenv 用 pip 安装 slackclient 的输出](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/pip-install-slackclient.png) + +我们也需要为我们的 Slack 项目获得一个访问令牌,以便我们的聊天机器人可以用它来连接到 Slack API。 + +### Slack 实时消息传递(RTM)API + +Slack 允许程序通过一个 [Web API](https://www.fullstackpython.com/application-programming-interfaces.html) 来访问他们的消息传递通道。去这个 [Slack Web API 页面](https://api.slack.com/) 注册建立你自己的 Slack 项目。你也可以登录一个你拥有管理权限的已有账号。 + +![使用 Web API页面的右上角登录按钮](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/sign-in-slack.png) + +登录后你会到达 [聊天机器人用户页面](https://api.slack.com/bot-users)。 + +![定制聊天机器人用户页面](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/custom-bot-users.png) + +给你的聊天机器人起名为“starterbot”然后点击 “Add bot integration” 按钮。 + +![添加一个bot integration 并起名为“starterbot”](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/starterbot.jpg) + +这个页面将重新加载,你将看到一个新生成的访问令牌。你还可以将标志改成你自己设计的。例如我给的这个“Full Stack Python”标志。 + +![为你的新 Slack 聊天机器人复制和粘贴访问令牌](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/slack-token.png) + +在页面底部点击“Save Integration”按钮。你的聊天机器人现在已经准备好连接 Slack API。 + +Python 开发人员的一个常见的做法是以环境变量输出秘密令牌。输出的 Slack 令牌名字为`SLACK_BOT_TOKEN`: + +``` +export SLACK_BOT_TOKEN='你的 slack 令牌粘帖在这里' + +``` + +好了,我们现在得到了将这个 Slack API 用作聊天机器人的授权。 + +我们建立聊天机器人还需要更多信息:我们的聊天机器人的 ID。接下来我们将会写一个简短的脚本,从 Slack API 获得该 ID。 + +### 获得我们聊天机器人的 ID + +这是最后写一些 Python 代码的时候了! 我们编写一个简短的 Python 脚本获得 StarterBot 的 ID 来热身一下。这个 ID 基于 Slack 项目而不同。 + +我们需要该 ID,当解析从 Slack RTM 上发给 StarterBot 的消息时,它用于对我们的应用验明正身。我们的脚本也会测试我们 `SLACK_BOT_TOKEN` 环境变量是否设置正确。 + +建立一个命名为 print_bot_id.py 的新文件,并且填入下面的代码: + +``` +import os +from slackclient import SlackClient + + +BOT_NAME = 'starterbot' + +slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) + + +if __name__ == "__main__": + api_call = slack_client.api_call("users.list") + if api_call.get('ok'): + # retrieve all users so we can find our bot + users = api_call.get('members') + for user in users: + if 'name' in user and user.get('name') == BOT_NAME: + print("Bot ID for '" + user['name'] + "' is " + user.get('id')) + else: + print("could not find bot user with the name " + BOT_NAME) + +``` + +我们的代码导入 SlackClient,并用我们设置的环境变量 `SLACK_BOT_TOKEN` 实例化它。 当该脚本通过 python 命令执行时,我们通过会访问 Slack API 列出所有的 Slack 用户并且获得匹配一个名字为“satrterbot”的 ID。 + +这个获得聊天机器人的 ID 的脚本我们仅需要运行一次。 + +``` +python print_bot_id.py + +``` + +当它运行为我们提供了聊天机器人的 ID 时,脚本会打印出简单的一行输出。 + +![在你的 Slack 项目中用 Python 脚本打印 Slack 聊天机器人的 ID](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/printed-bot-id.png) + +复制这个脚本打印出的唯一 ID。并将该 ID 作为一个环境变量 `BOT_ID` 输出。 + +``` +(starterbot)$ export BOT_ID='bot id returned by script' + +``` + +这个脚本仅仅需要运行一次来获得聊天机器人的 ID。 我们现在可以在我们的运行 StarterBot 的 Python应用程序中使用这个 ID 。 + +### 编码我们的 StarterBot + +现在我们拥有了写我们的 StarterBot 代码所需的一切。 创建一个新文件命名为 starterbot.py ,它包括以下代码。 + +``` +import os +import time +from slackclient import SlackClient + +``` + +对 `os` 和 `SlackClient` 的导入我们看起来很熟悉,因为我们已经在 theprint_bot_id.py 中用过它们了。 + +通过我们导入的依赖包,我们可以使用它们获得环境变量值,并实例化 Slack 客户端。 + +``` +# starterbot 的 ID 作为一个环境变量 +BOT_ID = os.environ.get("BOT_ID") + +# 常量 +AT_BOT = "<@" + BOT_ID + ">:" +EXAMPLE_COMMAND = "do" + +# 实例化 Slack 和 Twilio 客户端 +slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) + +``` + +该代码通过我们以输出的环境变量 `SLACK_BOT_TOKEN 实例化 `SlackClient` 客户端。 + +``` +if __name__ == "__main__": + READ_WEBSOCKET_DELAY = 1 # 1 从 firehose 读取延迟 1 秒 + if slack_client.rtm_connect(): + print("StarterBot connected and running!") + while True: + command, channel = parse_slack_output(slack_client.rtm_read()) + if command and channel: + handle_command(command, channel) + time.sleep(READ_WEBSOCKET_DELAY) + else: + print("Connection failed. Invalid Slack token or bot ID?") + +``` + +Slack 客户端会连接到 Slack RTM API WebSocket,然后当解析来自 firehose 的消息时会不断循环。如果有任何发给 StarterBot 的消息,那么一个被称作 `handle_command` 的函数会决定做什么。 + +接下来添加两个函数来解析 Slack 的输出并处理命令。 + +``` +def handle_command(command, channel): + """ + Receives commands directed at the bot and determines if they + are valid commands. If so, then acts on the commands. If not, + returns back what it needs for clarification. + """ + response = "Not sure what you mean. Use the *" + EXAMPLE_COMMAND + \ + "* command with numbers, delimited by spaces." + if command.startswith(EXAMPLE_COMMAND): + response = "Sure...write some more code then I can do that!" + slack_client.api_call("chat.postMessage", channel=channel, + text=response, as_user=True) + +def parse_slack_output(slack_rtm_output): + """ + The Slack Real Time Messaging API is an events firehose. + this parsing function returns None unless a message is + directed at the Bot, based on its ID. + """ + output_list = slack_rtm_output + if output_list and len(output_list) > 0: + for output in output_list: + if output and 'text' in output and AT_BOT in output['text']: + # 返回 @ 之后的文本,删除空格 + return output['text'].split(AT_BOT)[1].strip().lower(), \ + output['channel'] + return None, None + +``` + +`parse_slack_output` 函数从 Slack 接受信息,并且如果它们是发给我们的 StarterBot 时会作出判断。消息以一个给我们的聊天机器人 ID 的直接命令开始,然后交由我们的代码处理。目前只是通过 Slack 管道发布一个消息回去告诉用户去多写一些 Python 代码! + +这是整个程序组合在一起的样子 (你也可以 [在 GitHub 中查看该文件](https://github.com/mattmakai/slack-starterbot/blob/master/starterbot.py)): + +``` +import os +import time +from slackclient import SlackClient + +# starterbot 的 ID 作为一个环境变量 +BOT_ID = os.environ.get("BOT_ID") + +# 常量 +AT_BOT = "<@" + BOT_ID + ">:" +EXAMPLE_COMMAND = "do" + +# 实例化 Slack 和 Twilio 客户端 +slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) + +def handle_command(command, channel): + """ + Receives commands directed at the bot and determines if they + are valid commands. If so, then acts on the commands. If not, + returns back what it needs for clarification. + """ + response = "Not sure what you mean. Use the *" + EXAMPLE_COMMAND + \ + "* command with numbers, delimited by spaces." + if command.startswith(EXAMPLE_COMMAND): + response = "Sure...write some more code then I can do that!" + slack_client.api_call("chat.postMessage", channel=channel, + text=response, as_user=True) + +def parse_slack_output(slack_rtm_output): + """ + The Slack Real Time Messaging API is an events firehose. + this parsing function returns None unless a message is + directed at the Bot, based on its ID. + """ + output_list = slack_rtm_output + if output_list and len(output_list) > 0: + for output in output_list: + if output and 'text' in output and AT_BOT in output['text']: + # 返回 @ 之后的文本,删除空格 + return output['text'].split(AT_BOT)[1].strip().lower(), \ + output['channel'] + return None, None + +if __name__ == "__main__": + READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose + if slack_client.rtm_connect(): + print("StarterBot connected and running!") + while True: + command, channel = parse_slack_output(slack_client.rtm_read()) + if command and channel: + handle_command(command, channel) + time.sleep(READ_WEBSOCKET_DELAY) + else: + print("Connection failed. Invalid Slack token or bot ID?") + +``` + +现在我们的代码已经有了,我们可以通过 `python starterbot.py` 来运行我们 StarterBot 的代码了。 + +![当 StarterBot 开始运行而且连接到 API 的输出通道](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/starterbot-running.png) + +在 Slack 中创建新通道,并且把 StarterBot 邀请进来,或者把 StarterBot 邀请进一个已经存在的通道中。 + +![在 Slack 界面创建一个新通道并且邀请 StarterBot](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/create-channel.png) + +现在在你的通道中给 StarterBot 发命令。 + +![在你的 Slack 通道里给你的 StarterBot 发命令](https://www.fullstackpython.com/source/static/img/160604-simple-python-slack-bot/working-starterbot.png) + +如果你从聊天机器人得到的响应中遇见问题,你可能需要做一个修改。正如上面所写的这个教程,其中一行 `AT_BOT = "<@" + BOT_ID + ">:"`,在“@starter”(你给你自己的聊天机器人起的名字)后需要一个冒号。从`AT_BOT` 字符串后面移除`:`。Slack 似乎需要在 `@` 一个人名后加一个冒号,但这好像是有些不协调的。 + +### 结束 + +好吧,你现在已经获得一个简易的聊天机器人,你可以在代码中很多地方加入你想要创建的任何特性。 + +我们能够使用 Slack RTM API 和 Python 完成很多功能。看看通过这些文章你还可以学习到什么: + +* 附加一个持久的[关系数据库](https://www.fullstackpython.com/databases.html) 或者 [NoSQL 后端](https://www.fullstackpython.com/no-sql-datastore.html) 比如 [PostgreSQL](https://www.fullstackpython.com/postgresql.html)、[MySQL](https://www.fullstackpython.com/mysql.html) 或者 [SQLite](https://www.fullstackpython.com/sqlite.html) ,来保存和检索用户数据 +* 添加另外一个与聊天机器人互动的通道,比如 [短信](https://www.twilio.com/blog/2016/05/build-sms-slack-bot-python.html) 或者[电话呼叫](https://www.twilio.com/blog/2016/05/add-phone-calling-slack-python.html) +* [集成其它的 web API](https://www.fullstackpython.com/api-integration.html),比如 [GitHub](https://developer.github.com/v3/)、[Twilio](https://www.twilio.com/docs) 或者 [api.ai](https://docs.api.ai/) + +有问题? 通过 Twitter 联系我 [@fullstackpython](https://twitter.com/fullstackpython) 或 [@mattmakai](https://twitter.com/mattmakai)。 我在 GitHub 上的用户名是 [mattmakai](https://github.com/mattmakai)。 + +这篇文章感兴趣? Fork 这个 [GitHub 上的页面](https://github.com/mattmakai/fullstackpython.com/blob/gh-pages/source/content/posts/160604-build-first-slack-bot-python.markdown)吧。 + +-------------------------------------------------------------------------------- +via: https://www.fullstackpython.com/blog/build-first-slack-bot-python.html + +作者:[Matt Makai][a] +译者:[jiajia9llinuxer](https://github.com/jiajia9linuxer) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出aa + +[a]: https://www.fullstackpython.com/about-author.html diff --git a/published/201610/20160622 Part III - How to apply Advanced Mathematical Processing Effects on Audio files with Octave 4.0 on Ubuntu.md b/published/201610/20160622 Part III - How to apply Advanced Mathematical Processing Effects on Audio files with Octave 4.0 on Ubuntu.md new file mode 100644 index 0000000000..81d89dabfd --- /dev/null +++ b/published/201610/20160622 Part III - How to apply Advanced Mathematical Processing Effects on Audio files with Octave 4.0 on Ubuntu.md @@ -0,0 +1,133 @@ +科学音频处理(三):如何使用 Octave 的高级数学技术处理音频文件 +===== + +我们的数字音频处理技术第三部分涵盖了信号调制内容,将解释如何进行调幅(Amplitude Modulation)、颤音效果(Tremolo Effect)和频率变化(Frequency Variation)。 + +### 调制 + +#### 调幅 + +正如它的名字暗示的那样, 影响正弦信号的振幅变化依据传递的信息而不断改变。正弦波因为承载着大量的信息被称作载波(carrier)。这种调制技术被用于许多的商业广播和市民信息传输波段(AM)。 + +#### 为何要使用调幅技术? + +**调制发射** + +假设信道是免费资源,有天线就可以发射和接收信号。这要求有效的电磁信号发射天线,它的大小和要被发射的信号的波长应该是同一数量级。很多信号,包括音频成分,通常在 100 赫兹或更低。对于这些信号,如果直接发射,我们就需要建立长达 300 公里的天线。如果通过信号调制将信息加载到 100MHz 的高频载波中,那么天线仅仅需要 1 米(横向长度)。 + +**集中调制与多通道** + +假设多个信号占用一个通道,调制可以将不同的信号不同频域位置,以便接收者选择该特定信号。使用集中调制(“复用”)的应用有遥感探测数据、立体声调频收音机和长途电话等。 + +**克服设备限制的调制** + +信号处理设备,比如过滤器、放大器,以及可以用它们简单组成的设备,它们的性能依赖于信号在频域中的境况以及高频率和低频信号的关系。调制可以用于传递信号到频域中的更容易满足设计需求的位置。调制也可以将“宽带信号“(高频和低频的比例很大的信号)转换成”窄带“信号。 + +**音频特效** + +许多音频特效由于引人注目和处理信号的便捷性使用了调幅技术。我们可以说出很多,比如颤音、合唱、镶边等等。这种实用性就是我们关注它的原因。 + +### 颤音效果 + +颤音效果是调幅最简单的应用,为实现这样的效果,我们会用周期信号改变(乘)音频信号,使用正弦或其他。 + +``` +>> tremolo='tremolo.ogg'; +>> fs=44100; +>> t=0:1/fs:10; +>> wo=2*pi*440*t; +>> wa=2*pi*1.2*t; +>> audiowrite(tremolo, cos(wa).*cos(wo),fs); +``` + +![Tremolo](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/tremolo.png) + +这将创造一个正弦形状的信号,它的效果就像‘颤音’。 + +![Tremolo Shape](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/tremoloshape.png) + +### 在真实音频文件中的颤音 + +现在我们将展示真实世界中的颤音效果。首先,我们使用之前记录过男性发声 ‘A’ 的音频文件。这个信号图就像下面这样: + +``` +>> [y,fs]=audioread('A.ogg'); +>> plot(y); +``` + +![Vocal](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/avocalmale.png) + +现在我们将创建一个完整的正弦信号,使用如下的参数: + +- 增幅 = 1 +- 频率= 1.5Hz +- 相位 = 0 + +``` +>> t=0:1/fs:4.99999999; +>> t=t(:); +>> w=2*pi*1.5*t; +>> q=cos(w); +>> plot(q); +``` + +注意: 当我们创建一组时间值时,默认情况下,它是以列的格式呈现,如, 1x220500 的值。为了乘以这样的值,必须将其变成行的形式(220500x1)。这就是 `t=t(:)` 命令的作用。 + +![Sinusodial](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/sinusoidal.png) + +我们将创建第二份 ogg 音频格式的文件,它包含了如下的调制信号: + +``` +>> tremolo='tremolo.ogg'; +>> audiowrite(tremolo, q.*y,fs); +``` + +![](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/tremsignal1.png) + +![Tremolo Signal](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/tremolsignal1.png) + +### 频率变化 + +我们可以改变频率实现一些有趣的音效,比如原音变形,电影音效,多人比赛。 + +#### 正弦频率调制的影响 + +这是正弦调制频率变化的演示代码,根据方程: + +``` +Y=Ac*Cos(wo*Cos(wo/k)) +``` + +这里: + +- Ac = 增幅 +- wo = 基频 +- k = 标量除数 + +``` +>> fm='fm.ogg'; +>> fs=44100; +>> t=0:1/fs:10; +>> w=2*pi*442*t; +>> audiowrite(fm, cos(cos(w/1500).*w), fs); +>> [y,fs]=audioread('fm.ogg'); +>> figure (); plot (y); +``` + +信号图: + +![](https://www.howtoforge.com/images/ubuntu-octave-audio-processing-part-3/big/fmod.png) + +你可以使用几乎任何类型的周期函数频率调制。本例中,我们仅仅用了一个正弦函数。请大胆的改变函数频率,用复合函数,甚至改变函数的类型。 + +-------------------------------------------------------------------------------- + +via: https://www.howtoforge.com/tutorial/ubuntu-octave-audio-processing-part-3/ + +作者:[David Duarte][a] +译者:[theArcticOcean](https://github.com/theArcticOcean) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://www.howtoforge.com/tutorial/ubuntu-octave-audio-processing-part-3/ diff --git a/published/201610/20160701 CANONICAL CONSIDERING TO DROP 32 BIT SUPPORT IN UBUNTU.md b/published/201610/20160701 CANONICAL CONSIDERING TO DROP 32 BIT SUPPORT IN UBUNTU.md new file mode 100644 index 0000000000..2e2b93d166 --- /dev/null +++ b/published/201610/20160701 CANONICAL CONSIDERING TO DROP 32 BIT SUPPORT IN UBUNTU.md @@ -0,0 +1,41 @@ +Canonical 正考虑移除 Ubuntu 的 32 位支持 +======================================================== + +![](https://itsfoss.com/wp-content/uploads/2016/06/Ubuntu-32-bit-goes-for-a-toss-.jpg) + +之前,[Dimitri John Ledkov][1] 在 [Ubuntu 邮件列表][2] 发送了一则消息,称将在 Ubuntu 18.10 中取消 32 位支持。他说越来越多的软件已经有了 64 位支持,而且为古老的 32 位架构提供安全支持将变得更加困难。 + +Ledkov 同时表示,构建 32 位镜像并不是没有成本的,而要耗费 Canonical 不少的资源。 + +> 构建 32 位镜像并不是“免费的”,它的代价就是占用了我们的构建服务器资源、QA 和校验时间。尽管我我们有可扩展的构建环境,但 32 位支持还需要为所有包进行构建和自动测试,而且 ISO 也需要在我们的各种架构上进行测试。同时这还会占据大量的镜像空间和带宽。 + +Ledkov 计划着,Ubuntu 16.10、17.04、17.10 还会继续提供 32 位内核、网络安装器和云镜像,但移除桌面版和服务器版的 32 位 ISO 镜像。18.04 LTS 将会移除 32 位内核、网络安装器和云镜像,但在 64 位架构中兼容运行 32 位程序。然后在 18.10 中结束 32 位支持,并将传统的 32 位应用放在 snap、容器和虚拟机中。 + +但是,Ledkov 的这份计划还未被大家接受,但它表明了 32 位支持迟早要被遗弃。(LCTT 译注:我们已经知道 16.10 依旧有 32 为支持。) + +### 好消息 + +当然,使用 32 位系统的用户也不必伤心。这个并不会影响用于拯救老式电脑的发行版。[Ubuntu MATE][4] 的创建者 [Martin Wimpress][3] 在 Googl+ 的讨论中透露,这些改变这是影响着主线上的 Ubuntu 而已。 + +>18.04 将继续存在 32 位架构支持,分支版本可以继续选择构建 32 位镜像。但还是会存在安全隐患,一些大型应用,如 Firefox、Chromium、LibreOffice,已经凸显了在一些旧版的 LTS 的更新安全问题。所以,这些分支版本需要注意其 32 位支持期限。 + +### 思考 + +从安全的角度,我可以理解他们为什么要移除 32 位支持,但是这可能会导致一部分人离开主线 Ubuntu 而投入另一个喜爱的发行版或者另一种架构的怀抱。值得庆幸的是,我们还可以选择 [轻量级 Linux 发行版][5]。 + +-------------------------------------------------------------------------------- + +via: https://itsfoss.com/ubuntu-32-bit-support-drop/ + +作者:[John Paul][a] +译者:[GHLandy](https://github.com/GHLandy) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://itsfoss.com/author/john/ +[1]: https://plus.google.com/+DimitriJohnLedkov +[2]: https://lists.ubuntu.com/archives/ubuntu-devel-discuss/2016-June/016661.html +[3]: https://twitter.com/m_wimpress +[4]: http://ubuntu-mate.org/ +[5]: https://itsfoss.com/lightweight-linux-beginners/ diff --git a/published/201610/20160724 Terminator A Linux Terminal Emulator With Multiple Terminals In One Window.md b/published/201610/20160724 Terminator A Linux Terminal Emulator With Multiple Terminals In One Window.md new file mode 100755 index 0000000000..294f38b43e --- /dev/null +++ b/published/201610/20160724 Terminator A Linux Terminal Emulator With Multiple Terminals In One Window.md @@ -0,0 +1,84 @@ +Terminator:一款一个窗口包含多个终端的 Linux 终端仿真器 +============================================================================= + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/lots-of-terminals-in-terminator_1.jpg?659) + +为了通过命令行和系统互动,每一款 Linux 发行版都有一款默认的终端仿真器。但是,默认的终端应用可能不适合你。为了大幅提升你工作的速度,有好多款终端应用提供了更多的功能,可以同时执行更多的任务。这些有用的终端仿真器就包括 Terminator,这是一款 Linux 系统下支持多窗口的自由开源的终端仿真器。 + +### 什么是 Linux 终端仿真器 + +Linux 终端仿真器是一个让你和 shell 交互的程序。所有的 Linux 发行版都会自带一款 Linux 终端应用让你向 shell 传递命令。 + +### Terminator,一款自由开源的 Linux 终端应用 + +Terminator 是一款 Linux 终端模拟器,提供了你的默认的终端应用不支持的多个特性。它提供了在一个窗口创建多个终端的功能,以加快你的工作速度。除了多窗口外,它也允许你修改其它特性,例如字体、字体颜色、背景色等等。让我们看看我们如何安装它,并且如何在不同的 Linux 发行版下使用 Terminator。 + +### 如何在 Linux 下安装 Terminator? + +#### 在基于 Ubuntu 的发行版上安装 Terminator + +Terminator 在默认的 Ubuntu 仓库就可以使用。所以你不需要添加额外的 PPA。只需要使用 APT 或者“软件应用”在 Ubuntu 下直接安装。 + +``` +sudo apt-get install terminator +``` + +假如你的默认的仓库中 Terminator 不可用,只需要使用源码编译 Terminator 即可。 + +- [下载源码][1] + +下载 Terminator 源码并且解压到你的桌面。现在打开你的默认的终端,然后 `cd` 到解压的目录。 + +现在就可以使用下面的命令来安装 Terminator 了: + +``` +sudo ./setup.py install +``` + +#### 在 Fedora 及衍生的操作系统上安装 Terminator + +``` +dnf install terminator +``` + +#### 在 OpenSuse 上安装 Terminator + +参见此文:[在 OPENSUSE 上安装][2]。 + +### 如何在一个窗口使用多个终端? + +安装好 Terminator 之后,你可以简单的在一个窗口打开多个终端。只需要右键点击并切分。 + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/multiple-terminals-in-terminator_orig.jpg) + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/multiple-terminals-in-terminator-emulator.jpg?697) + +只要你愿意,你可以创建尽可能多的终端,只要你能管理得过来。 + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/lots-of-terminals-in-terminator.jpg?706) + +### 定制终端 + +右键点击终端,并单击属性。现在你可以定制字体、字体颜色、标题颜色和背景,还有终端字体颜色和背景。 + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/customize-terminator-interface.jpg?702) + +![](http://www.linuxandubuntu.com/uploads/2/1/1/5/21152474/free-terminal-emulator_orig.jpg) + +### 结论:什么是你最喜欢的终端模拟器 + +Terminator 是一款先进的终端模拟器,它可以让你自定义界面。如果你还没有从你默认的终端模拟器中切换过来的话,你可以尝试一下它。我知道你将会喜欢上它。如果你正在使用其他的自由开源的终端模拟器的话,请让我们知道你最喜欢的那一款。不要忘了和你的朋友分享这篇文章。或许你的朋友正在寻找类似的东西。 + +-------------------------------------------------------------------------------- + +via: http://www.linuxandubuntu.com/home/terminator-a-linux-terminal-emulator-with-multiple-terminals-in-one-window + +作者:[author][a] +译者:[yangmingming](https://github.com/yangmingming) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://www.linuxandubuntu.com/home/terminator-a-linux-terminal-emulator-with-multiple-terminals-in-one-window +[1]: https://launchpad.net/terminator/+download +[2]: http://software.opensuse.org/download.html?project=home%3AKorbi123&package=terminator diff --git a/published/201610/20160729 Best Password Manager all platform.md b/published/201610/20160729 Best Password Manager all platform.md new file mode 100644 index 0000000000..daf55cceb7 --- /dev/null +++ b/published/201610/20160729 Best Password Manager all platform.md @@ -0,0 +1,459 @@ +全平台最佳密码管理工具大全:支持 Windows、Linux、Mac、Android、iOS 以及企业应用 +=============== + +![](https://4.bp.blogspot.com/-uMOdpnxBV9w/V5x4YW54SbI/AAAAAAAAo_E/o-gUmO46UB0Ji2IMzd_xdY5pVsCcJnFwQCLcB/s1600/free-best-password-manager-2016.png) + +当谈到你的网络安全的防护时,从各种网络威胁的角度来看,仅安装一个防病毒软件或运行一个[安全的 Linux 操作系统][1],并不意味你就是足够安全的。 + +今天大多数网络用户都容易受到网络攻击,并不是因为他们没有使用最好的防病毒软件或其他安全措施,而是因为他们使用了[弱密码][2]来保护他们自己的网上帐号。 + +密码是你抵御网络威胁的最后一道防线。只要回顾一下最近的一些数据泄露和网络攻击,包括大众关注的 [OPM(美国联邦人事管理局)][3]和婚外情网站 [Ashley Madison][4] 的数据泄露,都导致成千上万的记录的网上曝光。 + +虽然你不能控制数据泄露,但创建强壮的、可以抵御字典和[暴力破解][5]的密码仍然是重要的。 + +你知道,你的密码越长,它越难破解。 + +### 如何保证在线服务的安全? + +安全研究人员喋喋不休地劝说在线用户为他们的各种网上账户创建长的、复杂和各异的密码。这样,如果一个网站被攻破,你在其他网站上的帐户是安全的,不会被黑客攻击。 + +理想的情况下,你的密码必须至少16个字符长,应该包含数字、符号、大写字母和小写字母,而最重要的是,甚至你都不知道的密码才是最安全的密码。 + +密码应该不重复,而且不包含任何字典中的词汇、代词,你的用户名或 ID 号,以及任何其它预定义的字母或数字序列。 + +我知道记住这样的复杂的密码字符串是一个实在痛苦的过程,除非我们是一个人型超级计算机,为不同的在线账户记住不同的密码并不是一个轻松的任务。 + +问题是,现在人们注册了大量的在线网站和服务,为每一个帐户创建和记住不同的密码通常是很难的。 + +不过,幸运的是,我们可以让这个事情变得很轻松,不断涌现的面向桌面计算机和智能电话的口令管理器可以显著地降低你密码记忆难度,从而治愈你设置弱密码的坏毛病。 + +### 密码管理器是什么? + +![](https://1.bp.blogspot.com/-LY7pI45tMq0/V5r_XV083RI/AAAAAAAAo6M/MivILg0_4Vs7UgLKZJqM5vhvYujQCCcpgCLcB/s1600/best-password-manager-software.png) + +在过去的几年里,密码管理软件已经取得了长足的进展,它是一个很好的系统,不但可以让你为不同的网站创建复杂的密码,而且能让你记住它们。 + +密码管理器是一个为你的个人电脑、网站,应用程序和网络创建、存储和整理密码的软件。 + +密码管理器可以生成密码,也可以作为表单填充器,它可以自动在网站的登录表单中输入你的用户名和密码。 + +所以,如果你想为你的多个在线帐户设置超级安全的密码,但你又不想全部记住它们,密码管理器是你最好的选择。 + +### 密码管理器如何工作? + +通常,密码管理器可以为您生成冗长、复杂,而且更重要的是唯一的密码字符串,然后以加密形式存储它们,以保护该机密数据免受黑客对您的 PC 或移动设备的物理访问。 + +加密的文件只能通过“主密码”访问。因此,所有你需要做的只是记住一个“主密码”,用来打开你的密码管理器或保险库,从而解锁你所有的其他密码。 + +然而,你需要确保你的主密码是超级安全的,至少 16 个字符。 + +### 哪个是最好的密码管理器?如何选择? + +我一直在推荐密码管理器,但大多数读者总是问: + +- 哪个密码管理器最好? +- 哪个密码管理器最安全?帮帮我! + +所以,今天我要介绍给你一些最好的密码管理器,它们可在 Windows、Mac、Linux、Android、iOS 和企业中使用。 + +在为你的设备选择一个好的密码管理器之前,你应该检查以下功能: + +- 跨平台应用 +- 零知识模型 +- 提供双因素认证(或者多因素身份验证) + +注意:一旦采用,就要按照密码管理器的方式来用,因为如果你仍然为你的重要在线帐户使用弱密码的话,没有人可以从恶意黑客那里拯救你。 + +### Windows 最佳密码管理工具 + +![](https://2.bp.blogspot.com/-8MUEI5RctdA/V5sDM_oCoaI/AAAAAAAAo60/LX4AktoS_f0JeYDORSqmDZMfmsbOa6QnACLcB/s1600/Best-Password-Manager-for-Windows.png) + +Windows 用户最容易受到网络攻击,因为 Windows 操作系统一直是黑客最喜欢的目标。所以,对于 Windows 用户来说,使用一个好的密码管理器是重要的。 + +除了下述以外,Windows 其它的密码管理器还有:Password Safe、LockCrypt、1Password。 + +#### 1. Keeper 密码管理器(跨平台) + +![](https://1.bp.blogspot.com/-9ISKGyTAX9U/V5xec18I21I/AAAAAAAAo8E/i8IqZXXpDwMGe8wnI6Adj3qduR_Qm5o3ACLcB/s1600/keeper-Password-Manager-for-mac-os-x.png) + +Keeper 是一个安全、易用而稳健的密码管理器,支持 Windows、Mac、iPhone、iPad 和 iPod 设备。 + +通过使用军事级 256 位 AES 加密技术,Keeper 密码管理器可以让您的数据安全在窥探之下保持安全。 + +它的安全的数字保险柜,用于保护和管理您的密码,以及其他秘密信息。Keeper 密码管理器应用程序支持双因素身份验证,可用于各大主流操作系统。 + +它还有一个称为“自毁”的重要的安全功能,启用后,如果不正确的错误地输入主密码达五次以上,它将删除设备中的所有记录! + +但您不必担心,因为此操作不会删除存储在 Keeper 上的云安全保险柜上的备份记录。 + +下载 Keeper 密码管理器: [Windows、Linux 和 Mac][6] | [iOS][7] | [Android][8] | [Kindle][9] + +#### 2. Dashlane 密码管理器(跨平台) + +![](https://3.bp.blogspot.com/-2BuFpcAe9K8/V5xjugOWPuI/AAAAAAAAo9A/wpooAjcH74EzxfNJwrFu-Mcn0IkwiRGjACLcB/s1600/Dashlane-Password-Manager-for-Android.png) + +DashLane 密码管理器有点新,但它几乎为每个平台提供了极棒的功能。 + +DashLane 密码管理器通过在本地计算机上使用 AES-256 加密技术来加密您的个人信息和帐户密码,然后将其同步到其在线服务器,以便您可以从任何地方访问您的帐户数据库。 + +DashLane 最好的一点是它有一个自动密码更改器,可以自动更改您的帐户的密码,而不必自己处理。 + +DashLane 密码管理器 Android 版为您的 Android 手机提供了安全的密码管理工具:密码保险库和在线商店及其他网站的表单自动填充器。 + +Android 的 DashLane 密码管理器在单个设备上使用完全免费,如要在多个设备上访问,您可以购买该应用的收费的高级版本。 + +下载 DashLane 密码管理器: [Windows][10] 和 [Mac][11] | [iOS][12] | [Android][13] + +#### 3. LastPass 密码管理器(跨平台) + +![](https://3.bp.blogspot.com/--o_hWTgXh2M/V5sAjw7FlYI/AAAAAAAAo6U/Ajmvt0rgRAQE3M_YeYurpbsUoLBN8OTLwCLcB/s1600/LastPass-Password-Manager-for-Windows.png) + +LastPass 是 Windows 用户最好的密码管理器之一,它可以通过扩展插件、移动应用程序,甚至桌面应用程序支持所有的浏览器和操作系统。 + +LastPass 是一个非常强大的基于云的密码管理器软件,它使用 AES-256 加密技术来加密您的个人信息和帐户密码,甚至提供各种双因素身份验证选项,以确保没有其他人可以登录您的密码保险柜中。 + +LastPass 密码管理器是免费的,收费的高级版本支持指纹读取器。 + +下载 LastPass 密码管理器: [Windows、Mac 和 Linux][14] | [iOS][15] | [Android][16] + +### Mac OS X 最佳密码管理器 + +![](https://2.bp.blogspot.com/-lEim3E-0wcg/V5sFhOVYK7I/AAAAAAAAo7A/z6Lp8_ULdJAD8ErZ1a-FevXPO8nR3JKNACLcB/s1600/Best-Password-Manager-for-mac-os-x.png) + +人们经常说,Mac 电脑比 Windows 更安全,“Mac 没有病毒”,但它是不完全正确的。 + +作为证据,你可以阅读我们以前的关于对 Mac 和 iOS 用户进行网络攻击的文章,然后自己决定需要不需要一个密码管理器。 + +除了下述以外,Mac OS X 其它的密码管理器还有:1Password,Dashlane,LastPass,OneSafe,PwSafe。 + +#### 1. LogMeOnce 密码管理器(跨平台) + +![](https://4.bp.blogspot.com/-fl64fXK2bdA/V5sHL215j_I/AAAAAAAAo7M/fbn4EsrQMkU3tWWfiAsWlTgKKXb0oEzlwCLcB/s1600/LogMeOnce-Password-Manager-for-Mac-os-x.png) + +LogMeOnce 密码管理套件是 Mac OS X 上的最佳密码管理器之一,并且可以在 Windows,iOS 和 Android 设备上同步您的密码。 + +LogMeOnce 是最好的收费的企业密码管理软件之一,提供各种功能和选项,包括 Mugshot(嫌犯照片)功能。 + +如果您的手机被盗,LogMeOnce Mugshot 功能可以跟踪小偷的位置,并秘密拍摄试图在未经许可访问您的帐户的入侵者的照片。 + +LogmeOnce 使用军用级 AES-256 加密技术保护您的密码,并提供双因素身份验证,以确保即使掌握了主密码,窃贼也无法窃取您的帐户。 + +下载 LogMeOnce 密码管理器: [Windows and Mac][17] | [iOS][18] | [Android][19] + +#### 2. KeePass 密码管理器(跨平台) + +![](https://4.bp.blogspot.com/-XWwdG1z9sDw/V5sA7azAy6I/AAAAAAAAo6c/dkkfMRuxDoE_gi5OMRvDOUFq15P5NRO6QCLcB/s1600/Keepass-Password-Manager-for-Windows.png) + +虽然 LastPass 是最好的密码管理器之一,有些人不喜欢基于云的密码管理器。 + +KeePass 是一个流行的 Windows 密码管理器应用程序,但也有浏览器扩展和 KeePass 的移动应用程序。 + +用于 Windows 的 KeePass 密码管理器将您的帐户密码存储在您的 PC 上,因此您仍然可以控制它们,也可以放在 Dropbox 上,因此您可以使用多个设备访问它。 + +KeePass 使用当前已知的最安全的加密技术加密您的密码和登录信息:默认情况下为 AES 256 位加密,或可选地用 Twofish 256 位加密技术。 + +KeePass 不仅仅是免费的,它也是开源的,这意味着它的代码和完整性可以被任何人检查,从而赢得了更多的信任度。 + +下载 KeePass 密码管理器: [Windows 和 Linux][20] | [Mac][21] | [iOS][22] | [Android][23] + +#### 3. 苹果 iCloud 钥匙串 + +![](https://4.bp.blogspot.com/-vwY_dmsKIBg/V5xfhIZGxxI/AAAAAAAAo8M/OjPrBsp9GysF-bK3oqHtW74hKNYO61W9QCLcB/s1600/Apple-iCloud-Keychain-Security.png) + +苹果推出了 iCloud 钥匙串密码管理系统,提供了一种方便的、可以在您获准的 Apple 设备(包括 Mac OS X、iPhone 和 iPad)上安全地存储和自动同步所有登录凭据、Wi-Fi 密码和信用卡号码的方式。 + +您的钥匙串中的密码数据使用 256 位 AES 加密技术进行加密,并使用椭圆曲线非对称加密和密钥封装。 + +此外,iCloud 钥匙串还会生成新的、独特的和强大的密码,用于保护您的计算机和帐户。 + +主要限制:钥匙串不能与 Apple Safari 之外的其他浏览器一起使用。 + +参阅:[如何设置 iCloud 钥匙串?][24] + +### Linux 最佳密码管理器 + +![](https://1.bp.blogspot.com/-2zDAqYEQTQA/V5xgbo_OcQI/AAAAAAAAo8Y/hWzGLW7R4vse3QnpCM5-qmSHLtzK5M1VACLcB/s1600/best-Password-Manager-for-linux.png) + +毫无疑问,一些 Linux 发行版是地球上最安全的操作系统,但正如我上面所说,采用 Linux 不能完全保护您的在线帐户免受黑客攻击。 + +有许多跨平台密码管理器可用于在所有设备上同步所有帐户的密码,例如 LastPass、KeePass、RoboForm 密码管理器。 + +下面我列出了两个 Linux 上流行和安全的开源密码管理器: + +#### 1. SpiderOak 加密密码管理器(跨平台) + +![](https://4.bp.blogspot.com/-SZkmP7dpXZM/V5xiKeuT4KI/AAAAAAAAo8s/QhvfBz3OX78IUit_HLym0sdKxlz99qFfgCLcB/s1600/SpiderOak-Encryptr-Password-Manager-for-linux.png) + +爱德华·斯诺登推荐的由 SpiderOak 开发的 Encryptr 密码管理器是一个零知识的基于云的密码管理器,使用 Crypton JavaScript 框架加密保护您的密码。 + +它是一个跨平台、开源和免费的密码管理器,使用端到端加密,可以完美地工作于 Ubuntu、Debian Linux Mint 和其它 Linux 发行版。 + +Encryptr 密码管理器应用程序本身非常简单,带有一些基本功能。 + +Encryptr 软件允许您加密三种类型的内容:密码、信用卡号码和常规的键值对。 + +下载 Encryptr 密码管理器: [Windows、Linux 和 Mac][25] | [iOS][26] | [Android][27] + +#### 2. EnPass 密码管理器(跨平台) + +![](https://4.bp.blogspot.com/-_IF81t9rL7U/V5xhBIPUSHI/AAAAAAAAo8c/6-kbLXTl2G0EESTH4sP9KvZLzTFlCyypACLcB/s1600/EnPass-Password-Manager-for-Linux.png) + +Enpass 是一个优秀的安全导向的 Linux 密码管理器,在其它平台也能很好地工作。 + +Enpass 可让您使用第三方云服务(包括 Google 云端硬盘、Dropbox、OneDrive 或 OwnCloud)备份和恢复存储的密码。 + +它确保提供高级别的安全性,并通过主密码保护您的数据,在将备份上传到云上之前,使用开源加密引擎 SQLCipher 的 256 位 AES 加密技术进行加密。 + +> “我们不会在我们的服务器上托管您的 Enpass 数据,因此,我们不需要注册,您的数据只存储在您的设备上,”Enpass 说。 + +此外,默认情况下,当您离开计算机时,Enpass 会锁定自己,并且每隔 30 秒清除剪贴板内存,以防止您的密码被任何其他恶意软件窃取。 + +下载 EnPass 密码管理器: [Windows][28]、[Linux][29] | [Mac][30] | [iOS][31] | [Android][32] + +#### 3. RoboForm 密码管理器(跨平台) + +![](https://3.bp.blogspot.com/-g8Qf9V1EdqU/V5sBkDk614I/AAAAAAAAo6k/5ZTr9LyosU82F16GxajewvU4sWYyJFq5gCLcB/s1600/Roboform-Password-Manager-for-Windows.png) + +你可以很容易地在 Windows 操作系统上找到好的密码管理器,但 RoboForm 免费密码管理器软件不止于此。 + +除了创建复杂的密码并记住它们,RoboForm 还提供了一个智能表单填充功能,以节省您在浏览网络的时间。 + +RoboForm 使用军用级 AES 加密技术来加密您的登录信息和帐户密码,密钥是通过您的 RoboForm 主密码获得的。 + +RoboForm 适用于 IE、Chrome 和 Firefox 等浏览器,以及适用于 iOS、Android 和 Windows Phone 等移动平台。 + +下载 RoboForm 密码管理器: [Windows 和 Mac][33] | [Linux][34] | [iOS][35] | [Android][36] + +### Android 最佳密码管理器 + +![](https://1.bp.blogspot.com/-1PXI2KDrDEU/V5xigbW8lgI/AAAAAAAAo8w/Zv5hrdOcbSU7LA0kYrNpvJ1rxjg7EoOewCLcB/s1600/best-Password-Manager-for-android.png) + +目前全球有超过一半的人使用 Android 设备,因此 Android 用户保护他们的在线帐户、避免黑客总是试图访问这些设备成为一种必要。 + +Android 上一些最好的密码管理器应用程序包括 1Password、Keeper、DashLane、EnPass、OneSafe、mSecure 和 SplashID Safe。 + +#### 1. 1Password 密码管理器(跨平台) + +![](https://4.bp.blogspot.com/--w3s9SoWgYA/V5xjJwVRUTI/AAAAAAAAo84/BSucybvPdtUKYYcRtDbn-_2cOz-mfMA9gCLcB/s1600/1password-Password-Manager-for-android.png) + +1Password 密码管理器 Anroid 版是管理你的所有账户密码的最佳应用程序之一。 + +1Password 密码管理器为每个帐号创建强大、独特和安全的密码,并为你全部记住,你只需要轻轻一点即可登录。 + +1Password 密码管理器软件通过 AES-256 加密技术保护您的登录名和密码,并通过您的 Dropbox 帐户将其同步到所有设备,或者存储在本地,你可以用任何其他应用程序来进行同步。 + +最近,Android 版本的 1Password 密码管理器应用程序了添加指纹支持来解锁所有的密码,而不是使用您的主密码。 + +下载 1Password 密码管理器: [Windows 和 Mac][37] | [iOS][38] | [Android][39] + +#### 2. mSecure密码管理器(跨平台) + +![](https://4.bp.blogspot.com/-nvjjS2dWfPc/V5xkEdAOYvI/AAAAAAAAo9I/EDGfA5hzacIq46gWG-6BD2UPHwQAHD-pgCLcB/s1600/mSecure-password-manager-for-android.png) + +就像其他流行的密码管理器解决方案,Android 下的 mSecure 密码管理器自动生成安全密码,并使用 256 位Blowfish 加密技术存储它们。 + +令人印象深刻的独特功能是 mSecure 密码管理器软件提供了在输入 5、10 或 20 次错误的密码后(根据您的设置)自毁数据库的功能。 + +您还可以使用 Dropbox 或通过专用 Wi-Fi 网络同步所有设备。不管什么情况下,无论您的云帐户的安全性如何,您的所有数据都会在设备之间安全而加密地传输。 + +下载 mSecure 密码管理软件: [Windows 和 Mac][40] | [iOS][41] | [Android][42] + +### iOS 最佳密码管理器 + +![](https://4.bp.blogspot.com/-SOXYw_9mFq0/V5xk6Kl8-DI/AAAAAAAAo9Q/AMbEl_t3HjAJ4ZX7gLVoa33z-myE4bK5wCLcB/s1600/best-Password-Manager-for-ios-iphone.png) + +正如我所说,苹果的 iOS 也很容易发生网络攻击,所以你可以使用一些 iOS 下最好的密码管理器应用程序来保护你的在线帐户,它们包括 Keeper、OneSafe、Enpass、mSecure、LastPass、RoboForm、SplashID Safe 和 LoginBox Pro 。 + +#### 1. OneSafe 密码管理器(跨平台) + +![](https://2.bp.blogspot.com/-HPEJpqeOs00/V5xlSh7OUxI/AAAAAAAAo9Y/d5qkOy3BieMSxjGrnrnH4fvzUzAzDqhCgCLcB/s1600/onesafe-password-manager-for-ios.png) + +OneSafe 是 iOS 设备最好的密码管理器应用程序之一,它不仅可以存储您的帐户密码,还可以存储敏感文档、信用卡详细信息、照片等。 + +iOS 的 OneSafe 密码管理器应用程序使用 AES-256 加密技术(移动设备上可用的最高级别)和 Touch ID 将您的数据用主密码加密。 你还可以为给定文件夹设置附加密码。 + +iOS 的 OneSafe 密码管理器还提供了一个支持自动填充登录的应用内浏览器,因此您无需每次都输入登录详细信息。 + +除此之外,OneSafe 还为您的帐户的密码提供了高级安全功能,如自动锁定、入侵检测、自毁模式、诱饵安全和双重保护。 + +下载 OneSafe 密码管理器:[iOS][43] | [Mac][44] | [Android][45] | [Windows][46] + +#### 2. SplashID 安全密码管理器(跨平台) + +![](https://1.bp.blogspot.com/-FcNub2p-QNE/V5xmDW7QXvI/AAAAAAAAo9o/23VuGUAMCYYS64kKlUqBcfx3JIfBr5gTgCLcB/s1600/SplashID-Safe-password-manager-for-ios.png) + +SplashID Safe 是 iOS 中最古老、最好的密码管理工具之一,它允许用户将其登录数据和其他敏感信息安全地存储在加密记录中。 + +您的所有信息,包括网站登录信息、信用卡和社会保障数据、照片和文件附件,都受到 256 位的加密保护。 + +用于 iOS 的 SplashID Safe 密码管理器还提供了网络自动填充选项,这意味着您不必在登录时复制粘贴密码。 + +免费版本的 SplashID Safe 具有基本的记录存储功能,但您可以选择收费版本,提供跨设备同步以及其它收费功能。 + +下载 SplashID 安全密码管理器:[Windows 和 Mac][47] | [iOS][48] | [Android][49] + +#### 3. LoginBox Pro 密码管理器 + +![](https://3.bp.blogspot.com/-4GzhwZFXDHQ/V5xogkDk49I/AAAAAAAAo90/69rmVdKD-VUG0kHJXIqE2x-mVlWZEDrYwCLcB/s1600/LoginBox-Pro-Password-Manager-for-ios.png) + +LoginBox Pro 是另一个 iOS 设备上极棒的密码管理器应用程序。该应用程序提供了一个单击登录到你访问的任何网站的功能,使密码管理器应用程序成为登录密码保护的互联网网站的最安全和最快的方式。 + +iOS 的 LoginBox 密码管理器应用程序的把密码管理器和浏览器结合到一起。 + +从您下载那一刻起,所有登录操作(包括输入信息、点击按钮、复选框或回答安全提问)都会通过 LoginBox 密码管理器自动完成。 + +为了安全起见,LoginBox 密码管理器使用硬件加速的 AES 加密技术和密码来加密您的数据并将其保存在您的设备上。 + +下载 LoginBox 密码管理器:[iOS][50] | [Android][51] + +### 最佳在线密码管理器 + +使用在线密码管理器工具是保护你的个人和私人信息安全,免于黑客和心怀恶意的人攻击的最简单方法。 + +在这里,我列出了一些最好的在线密码管理器,你可以依靠它们保持自己的线上安全: + +#### 1. Google 在线密码管理器 + +![](https://2.bp.blogspot.com/-HCSzj5tKgwY/V5xqVjjtfgI/AAAAAAAAo-A/OYcgv-S5wmQlAskF1jrEGQAy98ogMnXTgCLcB/s1600/google-online-password-manager.png) + +你知道 Google 有自己的专用密码管理器吗? + +Google Chrome 有一个内置的密码管理器工具,当你使用 Chrome 登录网站或网络服务时,你可以选择用它保存密码。 + +你所储存的所有帐户密码都会与你的 Google 帐户同步,方便你通过同一个 Google 帐户在所有装置上使用。 + +Chrome 密码管理器可让你通过网络管理你的所有帐户的密码。 + +因此,如果您喜欢使用其他浏览器,例如 Windows 10 上的 Microsoft Edge 或 iPhone 上的 Safari,只需访问[passwords.google.com][52],您就会看到一个列表,其中包含您保存在 Chrome 之中所有密码。Google 的双重身份验证会保护此列表。 + +#### 2. Clipperz 在线密码管理器 + +![](https://2.bp.blogspot.com/-gs8b_N_k6CA/V5xrvzbUIKI/AAAAAAAAo-M/vsTXHZNErkQu6g8v9V1R2FxLkdppZq_GACLcB/s1600/Clipperz-Online-Password-Manager.png) + +Clipperz 是一个免费的跨平台最佳在线密码管理器,不需要你下载任何软件。Clipperz 在线密码管理器使用书签栏或侧栏来直接登录。 + +Clipperz 还提供了其软件的密码管理器的离线版本,允许你将密码下载到一个[加密的磁盘][53]或 USB 盘里面,以便你在旅行时可以随身携带,并在离线时访问你的帐户密码。 + +Clipperz 在线密码管理器的一些功能还包括密码强度指示器、应用程序锁定、SSL 安全连接、一次性密码和密码生成器。 + +Clipperz 在线密码管理器可以在任何支持 JavaScript 的浏览器上工作。 + +#### 3. Passpack 在线密码管理器 + +![](https://4.bp.blogspot.com/-ng91nPnzbWI/V5xsarl2mqI/AAAAAAAAo-Q/zJlFK-63vugeoyymDL26c5mPiWNsGQjuACLcB/s1600/Passpack-Free-Online-Password-Manager.png) + +Passpack 是一个优秀的在线密码管理器,拥有一套极具竞争力的功能,可为你的不同在线帐户创建、存储和管理密码。 + +PassPack 在线密码管理器还允许你与你的家人或同事安全地共享你的密码,以轻松管理多个项目、团队成员、客户和员工。 + +你的各个帐户的用户名和密码使用 PassPack 服务器上的 AES-256 加密技术进行加密,即使黑客访问其服务器也无法读取你的登录信息。 + +将 PassPack 在线密码管理器工具栏下载到 Web 浏览器并正常浏览 Web。每当你登录任何受密码保护的网站时,PassPack 会保存你的登录数据,以便你不必在其网站上手动保存你的用户名和密码。 + +### 最佳企业密码管理器 + +在过去 12 个月的过程中,我们看到了互联网历史上最大的数据泄露,而且这种情况年复一年的增多。 + +据统计,大多数员工甚至不知道如何在线保护他们自己,这导致公司的业务处于风险之中。 + +为了在组织中保持密码共享机制安全,有一些专门为企业使用而设计的密码管理工具,例如 Vaultier、CommonKey、Meldium、PassWork 和 Zoho Vault。 + +#### 1. Meldium 企业密码管理软件 + +![](https://3.bp.blogspot.com/-3rKr3KUpuiQ/V5xs8JR7pVI/AAAAAAAAo-c/VF1tmKbwPzoJmNvA3Ym69CizG7_VqM6ywCLcB/s1600/Meldium-Enterprise-Password-Manager.png) + +LogMeIn 的 [Meldium 密码管理工具][54]附带一个一键式单点登录解决方案,可帮助企业安全快速地访问网络应用。 + +它会自动将用户记录到应用和网站中,而无需输入用户名和密码,也可以跟踪组织内的密码使用情况。 + +Meldium 密码管理器非常适合在您的团队成员内共享帐户,而无需共享实际密码,这有助于组织保护自己免受网络钓鱼攻击。 + +#### 2. Zoho Vault 密码管理软件 + +![](https://2.bp.blogspot.com/-J-N_1wOYxmI/V5xtrz42QWI/AAAAAAAAo-o/QF4n4QAF7ZMBd7uIRdjM6Hdd1MHwsXWQACLcB/s1600/zoho-vault--Enterprise-Password-Manager.png) + +[Zoho Vault][55] 是企业用户最好的密码管理器之一,可帮助您的团队快速、安全地共享密码和其他敏感信息,同时监控每个用户的使用情况。 + +您所有的团队成员需要下载 Zoho 浏览器扩展。 Zoho Vault 密码管理器将自动填充您团队存储在共享保险柜中的密码。 + +Zoho Vault 还提供了一些功能,可让您监控团队的密码使用情况和安全级别,以便您可以知道谁在使用哪个登录。 + +Zoho Vault 企业级软件包甚至会在更改或访问密码时发出警告。 + +### 更多安全性,请使用双重身份验证 + +![](https://4.bp.blogspot.com/-jDnJBDoibtQ/V5xuHVHukRI/AAAAAAAAo-w/1Erjgk-IvKs__TXwYDz-8Groz9hWEElZgCLcB/s1600/two-factor-authentication-password-security.png) + +无论你的密码有多强大,黑客仍然有可能找到一些或其他方式侵入你的帐户。 + +双因素身份验证旨在解决这个问题。对一个密码取而代之的是,它要求你输入第二个口令,这会通过短信发送到你的手机上,或通过电子邮件发送到你的电子邮件地址上。 + +因此,我建议你启用[双因素身份验证][56],并使用密码管理器软件来保护你的在线帐户和敏感信息免受黑客攻击。 + +------ + +via: https://thehackernews.com/2016/07/best-password-manager.html + +作者:[Swati Khandelwal][a] +译者:[Bestony](https://github.com/Bestony) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://thehackernews.com/2016/07/best-password-manager.html#author-info +[1]: http://thehackernews.com/2016/03/subgraph-secure-operating-system.html +[2]: http://thehackernews.com/2016/01/password-security-manager.html +[3]: http://thehackernews.com/2015/09/opm-hack-fingerprint.html +[4]: http://thehackernews.com/2015/08/ashley-madison-accounts-leaked-online.html +[5]: http://thehackernews.com/2013/05/cracking-16-character-strong-passwords.html +[6]: https://keepersecurity.com/download.html +[7]: https://itunes.apple.com/us/app/keeper-password-manager-digital/id287170072?mt=8 +[8]: https://play.google.com/store/apps/details?id=com.callpod.android_apps.keeper +[9]: http://www.amazon.com/gp/mas/dl/android?p=com.callpod.android_apps.keeper +[10]: https://www.dashlane.com/download +[11]: https://www.dashlane.com/passwordmanager/mac-password-manager +[12]: https://itunes.apple.com/in/app/dashlane-free-secure-password/id517914548?mt=8 +[13]: https://play.google.com/store/apps/details?id=com.dashlane&hl=en +[14]: https://lastpass.com/misc_download2.php +[15]: https://itunes.apple.com/us/app/lastpass-for-premium-customers/id324613447?mt=8&ign-mpt=uo%3D4 +[16]: https://play.google.com/store/apps/details?id=com.lastpass.lpandroid +[17]: https://www.logmeonce.com/download/ +[18]: https://itunes.apple.com/us/app/logmeonce-free-password-manager/id972000703?ls=1&mt=8 +[19]: https://play.google.com/store/apps/details?id=log.me.once +[20]: http://keepass.info/download.html +[21]: https://itunes.apple.com/us/app/kypass-companion/id555293879?ls=1&mt=12 +[22]: https://itunes.apple.com/de/app/ikeepass/id299697688?mt=8 +[23]: https://play.google.com/store/apps/details?id=keepass2android.keepass2android +[24]: https://support.apple.com/en-in/HT204085 +[25]: https://spideroak.com/opendownload +[26]: https://itunes.apple.com/us/app/spideroak/id360584371?mt=8 +[27]: https://play.google.com/store/apps/details?id=com.spideroak.android +[28]: https://www.enpass.io/download-enpass-for-windows/ +[29]: https://www.enpass.io/download-enpass-linux/ +[30]: https://itunes.apple.com/app/enpass-password-manager-best/id732710998?mt=12 +[31]: https://itunes.apple.com/us/app/enpass-password-manager/id455566716?mt=8 +[32]: https://play.google.com/store/apps/details?id=io.enpass.app&hl=en +[33]: http://www.roboform.com/download +[34]: http://www.roboform.com/for-linux +[35]: https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=331787573&mt=8 +[36]: https://play.google.com/store/apps/details?id=com.siber.roboform +[37]: https://1password.com/downloads/ +[38]: https://itunes.apple.com/in/app/1password-password-manager/id568903335?mt=8 +[39]: https://play.google.com/store/apps/details?id=com.agilebits.onepassword&hl=en +[40]: https://www.msecure.com/desktop-app/ +[41]: https://itunes.apple.com/in/app/msecure-password-manager/id292411902?mt=8 +[42]: https://play.google.com/store/apps/details?id=com.mseven.msecure&hl=en +[43]: https://itunes.apple.com/us/app/onesafe/id455190486?ls=1&mt=8 +[44]: https://itunes.apple.com/us/app/onesafe-secure-password-manager/id595543758?ls=1&mt=12 +[45]: https://play.google.com/store/apps/details?id=com.lunabee.onesafe +[46]: https://www.microsoft.com/en-us/store/apps/onesafe/9wzdncrddtx9 +[47]: https://splashid.com/downloads.php +[48]: https://itunes.apple.com/app/splashid-safe-password-manager/id284334840?mt=8 +[49]: https://play.google.com/store/apps/details?id=com.splashidandroid&hl=en +[50]: https://itunes.apple.com/app/loginbox-pro/id579954762?mt=8 +[51]: https://play.google.com/store/apps/details?id=com.mygosoftware.android.loginbox +[52]: https://passwords.google.com/ +[53]: http://thehackernews.com/2014/01/Kali-linux-Self-Destruct-nuke-password.html +[54]: https://www.meldium.com/ +[55]: https://www.zoho.com/vault/password-management-tools.html +[56]: http://thehackernews.com/2016/07/two-factor-authentication.html \ No newline at end of file diff --git a/published/201610/20160805 Introducing React Native Ubuntu.md b/published/201610/20160805 Introducing React Native Ubuntu.md new file mode 100644 index 0000000000..c84288c4df --- /dev/null +++ b/published/201610/20160805 Introducing React Native Ubuntu.md @@ -0,0 +1,36 @@ +React Native Ubuntu 简介 +===================== + +在 Canonical 的 Webapps 团队,我们总在寻找可以为开发者所用的 web 和 web 相关技术。我们想让每个人生活更轻松,让 web 开发者更加熟悉工具的使用,并且在 Ubuntu 上提供一个使用它们的简单途径。 + +我们提供对 web 应用以及创建和打包 Cordova 应用的支持,这使得在 Ubuntu 上使用任意 web 框架来创造美妙的应用体验成为可能。 + +其中一个可以在这些情景中使用的主流框架就是 React.js。React.js 是一个拥有声明式编程模型和强大的组件系统的 UI 框架,它主要侧重于 UI 的构建,所以你可以在你喜欢的任何地方用上它。 + +然而这些应用场景太广泛了,有时候你可能需要更高的性能,或者能够直接用原生 UI 组件来开发,但是在一个不太熟悉的场景中使用它可能不合时宜。如果你熟悉 React.js,那么通过 React Native 来开发可以毫不费力地将你所有现有的知识和工具迁移到完全的原生组件开发中。React Native 是 React.js 的姐妹项目,你可以用同样的方式和代码来创建一个直接使用原生组件并且拥有原生级别性能的应用,而且这就和你期待的一样轻松快捷。 + +![](http://i.imgur.com/ZsSHWXP.png) + +我们很高兴地宣布随着我们对 HTML5 应用的支持,现在可以在 Ubuntu 平台上开发 React Native 应用了。你可以移植你现有的 iOS 或 Android 版本的 React Native 应用,或者利用你的 web 开发技能来创建一个新的应用。 + +你可以在 [这里][1] 找到 React Native Ubuntu 的源代码,要开始使用时,跟随 [README-ubuntu.md][2] 的指导,并创建你的第一个应用吧。 + +Ubuntu 的支持包括生成软件包的功能。通过 React Native CLI,构建一个 snap 软件包只需要简单执行 `react-native package-ubuntu --snap` 这样的命令。还可以为 Ubuntu 设备构建一个 click 包,这意味着 React Native Ubuntu 应用从一开始就可以放到 Ubuntu 商店了。 + +在不久的将来会有很多关于在 Ubuntu 上开发一个 React Native 应用你所需要了解的东西的博文,例如创建应用、开发流程以及打包并发布到商店等等。还会有一些关于怎样开发新型的可复用的模块的信息,这些模块可以给运行时环境增加额外的功能,并且可以发布为 npm 模块。 + +赶快去实践一下吧,看看你能创造出些什么来。 + +-------------------------------------------------------------------------------- + +via: https://developer.ubuntu.com/en/blog/2016/08/05/introducing-react-native-ubuntu/ + +作者:[Justin McPherson][a] +译者:[Mars Wong](https://github.com/OneNewLife) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://developer.ubuntu.com/en/blog/authors/justinmcp/ +[1]: https://github.com/CanonicalLtd/react-native +[2]: https://github.com/CanonicalLtd/react-native/blob/ubuntu/README-ubuntu.md diff --git a/published/201610/20160807 Going Serverless with AWS Lambda and API Gateway.md b/published/201610/20160807 Going Serverless with AWS Lambda and API Gateway.md new file mode 100644 index 0000000000..17e9b642f3 --- /dev/null +++ b/published/201610/20160807 Going Serverless with AWS Lambda and API Gateway.md @@ -0,0 +1,241 @@ +通过 AWS 的 Lambda 和 API Gateway 走向 Serverless +============================ + +近来,在计算领域出现了很多关于 serverless 的讨论。serverless 是一个概念,它允许你提供代码或可执行程序给某个服务,由服务来为你执行它们,而你无需自己管理服务器。这就是所谓的执行即服务(execution-as-a-service),它带来了很多机会,同时也遇到了它独有的挑战。 + +### 简短回忆下计算领域的发展 + +早期,出现了……好吧,这有点复杂。很早的时候,出现了机械计算机,后来又有了埃尼阿克 ENIAC(Electronic Numerical Integrator And Computer,很早的电子计算机),但是都没有规模生产。直到大型机出现后,计算领域才快速发展。 + +- 上世纪 50 年代 - 大型机 +- 上世纪 60 年代 - 微型机 +- 1994 - 机架服务器 +- 2001 - 刀片服务器 +- 本世纪初 - 虚拟服务器 +- 2006 - 服务器云化 +- 2013 - 容器化 +- 2014 - serverless(计算资源服务化) + +> 这些日期是大概的发布或者流行日期,无需和我争论时间的准确性。 + +计算领域的演进趋势是执行的功能单元越来越小。每一次演进通常都意味着运维负担的减小和运维灵活性的增加。 + +### 发展前景 + +喔,Serverless!但是,serverless 能给我们带来什么好处? 我们将面临什么挑战呢? + +**未执行代码时无需付费。**我认为,这是个巨大的卖点。当无人访问你的站点或用你的 API 时,你无需付钱。没有持续支出的基础设施成本,仅仅支付你需要的部分。换句话说,这履行了云计算的承诺:“仅仅支付你真正用的资源”。 + +**无需维护服务器,也无需考虑服务器安全。**服务器的维护和安全将由你的服务提供商来处理(当然,你也可以架设自己的 serverless 主机,只是这似乎是在向错误的方向前进)。由于你的执行时间也是受限的,安全补丁也被简化了,因为完全不需要重启。这些都应该由你的服务提供商无缝地处理。 + +**无限的可扩展性。**这是又一个大的好处。假设你又开发了一个 Pokemon Go, 与其频繁地把站点下线维护升级,不如用 serverless 来不断地扩展。当然,这也是个双刃剑,大量的账单也会随之而来。如果你的业务的利润强依赖于站点上线率的话,serverless 确实能帮上忙。 + +**强制的微服务架构。**这也有两面性,一方面,微服务似乎是一种好的构建灵活可扩展的、容错的架构的方式。另一方面,如果你的业务没有按照这种方式设计,你将很难在已有的架构中引入 serverless。 + +### 但是现在你被限制在**他们的**平台上 + +**受限的环境。**你只能用服务提供商提供的环境,你想在 Rust 中用 serverless?你可能不会太幸运。 + +**受限的预装包。**你只有提供商预装的包。但是你或许能够提供你自己的包。 + +**受限的执行时间。**你的 Function 只可以运行这么长时间。如果你必须处理 1TB 的文件,你可能需要有一个解决办法或者用其他方案。 + +**强制的微服务架构。**参考上面的描述。 + +**受限的监视和诊断能力。**例如,你的代码**在**干什么? 在 serverless 中,基本不可能在调试器中设置断点和跟踪流程。你仍然可以像往常一样记录日志并发出统计度量,但是这带来的帮助很有限,无法定位在 serverless 环境中发生的难点问题。 + +### 竞争领域 + +自从 2014 年出现 AWS Lambda 以后,serverless 的提供商已经增加了一些。下面是一些主流的服务提供商: + +- AWS Lambda - 起步最早的 +- OpenWhisk - 在 IBM 的 Bluemix 云上可用 +- Google Cloud Functions +- Azure Functions + +这些平台都有它们的相对优势和劣势(例如,Azure 支持 C#,或者紧密集成在其他提供商的平台上)。这里面最大的玩家是 AWS。 + +### 通过 AWS 的 Lambda 和 API Gateway 构建你的第一个 API + +我们来试一试 serverless。我们将用 AWS Lambda 和 API Gateway 来构建一个能返回 [Jimmy][2] 所说的“Guru Meditations”的 API。 + +所有代码在 [GitHub][1] 上可以找到。 + +API文档: + +``` +POST / +{ + "status": "success", + "meditation": "did u mention banana cognac shower" +} +``` + +### 怎样组织工程文件 + +文件结构树: + +``` +. +├── LICENSE +├── README.md +├── server +│ ├── __init__.py +│ ├── meditate.py +│ └── swagger.json +├── setup.py +├── tests +│ └── test_server +│ └── test_meditate.py +└── tools + ├── deploy.py + ├── serve.py + ├── serve.sh + ├── setup.sh + └── zip.sh +``` + +AWS 中的信息(想了解这里究竟在做什么的详细信息,可查看源码 `tools/deploy.py`)。 + +- **API。**实际构建的对象。它在 AWS 中表示为一个单独的对象。 +- **执行角色。**在 AWS 中,每个 Function 作为一个单独的角色执行。在这里就是 meditations。 +- **角色策略。**每个 Function 作为一个角色执行,每个角色需要权限来干活。我们的 Lambda Function 不干太多活,故我们只添加一些日志记录权限。 +- **Lambda Function。**运行我们的代码的地方。 +- **Swagger。** Swagger 是 API 的规范。API Gateway 支持解析 swagger 的定义来为 API 配置大部分资源。 +- **部署。**API Gateway 提供部署的概念。我们只需要为我们的 API 用一个就行(例如,所有的都用生产或者 yolo等),但是得知道它们是存在的,并且对于真正的产品级服务,你可能想用开发和暂存环境。 +- **监控。**在我们的业务崩溃的情况下(或者因为使用产生大量账单时),我们想以云告警查看方式为这些错误和费用添加一些监控。注意你应该修改 `tools/deploy.py` 来正确地设置你的 email。 + +### 代码 + +Lambda Function 将从一个硬编码列表中随机选择一个并返回 guru meditations,非常简单: + +``` +import logging +import random + + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + + +def handler(event, context): + + logger.info(u"received request with id '{}'".format(context.aws_request_id)) + + meditations = [ + "off to a regex/", + "the count of machines abides", + "you wouldn't fax a bat", + "HAZARDOUS CHEMICALS + RKELLY", + "your solution requires a blood eagle", + "testing is broken because I'm lazy", + "did u mention banana cognac shower", + ] + + meditation = random.choice(meditations) + + return { + "status": "success", + "meditation": meditation, + } +``` + +### deploy.py 脚本 + +这个脚本相当长,我没法贴在这里。它基本只是遍历上述“AWS 中的信息”下的项目,确保每项都存在。 + +### 我们来部署这个脚本 + +只需运行 `./tools/deploy.py`。 + +基本完成了。不过似乎在权限申请上有些问题,由于 API Gateway 没有权限去执行你的 Function,所以你的 Lambda Function 将不能执行,报错应该是“Execution failed due to configuration error: Invalid permissions on Lambda function”。我不知道怎么用 botocore 添加权限。你可以通过 AWS console 来解决这个问题,找到你的 API, 进到 `/POST` 端点,进到“integration request”,点击“Lambda Function”旁边的编辑图标,修改它,然后保存。此时将弹出一个窗口提示“You are about to give API Gateway permission to invoke your Lambda function”, 点击“OK”。 + +当你完成后,记录下 `./tools/deploy.py` 打印出的 URL,像下面这样调用它,然后查看你的新 API 的行为: + +``` +$ curl -X POST https://a1b2c3d4.execute-api.us-east-1.amazonaws.com/prod/ +{"status": "success", "meditation": "the count of machines abides"} +``` + +### 本地运行 + +不幸的是,AWS Lambda 没有好的方法能在本地运行你的代码。在这个例子里,我们将用一个简单的 flask 服务器来在本地托管合适的端点,并调用 handler 函数。 + +``` +from __future__ import absolute_import + +from flask import Flask, jsonify + +from server.meditate import handler + + +app = Flask(__name__) + +@app.route("/", methods=["POST"]) +def index(): + + class FakeContext(object): + aws_request_id = "XXX" + + return jsonify(**handler(None, FakeContext())) + +app.run(host="0.0.0.0") +``` + +你可以在仓库中用 `./tools/serve.sh` 运行它,像这样调用: + +``` +$ curl -X POST http://localhost:5000/ +{ + "meditation": "your solution requires a blood eagle", + "status": "success" +} +``` + +### 测试 + +你总是应该测试你的代码。我们的测试方法是导入并运行我们的 handler 函数。这是最基本的 python 测试方法: + +``` +from __future__ import absolute_import + +import unittest + +from server.meditate import handler + + +class SubmitTestCase(unittest.TestCase): + + def test_submit(self): + + class FakeContext(object): + + aws_request_id = "XXX" + + response = handler(None, FakeContext()) + + self.assertEquals(response["status"], "success") + self.assertTrue("meditation" in response) +``` + +你可以在仓库里通过 nose2 运行这个测试代码。 + +### 更多前景 + +- **和 AWS 服务的无缝集成。**通过 boto,你可以完美地轻易连接到任何其他的 AWS 服务。你可以轻易地让你的执行角色用 IAM 访问这些服务。你可以从 S3 取文件或放文件到 S3,连接到 Dynamo DB,调用其他 Lambda Function,等等。 +- **访问数据库。**你也可以轻易地访问远程数据库。在你的 Lambda handler 模块的最上面连接数据库,并在handler 函数中执行查询。你很可能必须从它的安装位置上传相关的包内容才能使它正常工作。可能你也需要静态编译某些库。 +- **调用其他 webservices。**API Gateway 也是一种把 webservices 的输出从一个格式转换成另一个格式的方法。你可以充分利用这个特点通过不同的 webservices 来代理调用,或者当业务变更时提供后向兼容能力。 + +-------------------------------------------------------------------------------- + +via: http://blog.ryankelly.us/2016/08/07/going-serverless-with-aws-lambda-and-api-gateway.html + +作者:[Ryan Kelly][a] +译者:[messon007](https://github.com/messon007) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://github.com/f0rk/blog.ryankelly.us/ +[1]: https://github.com/f0rk/meditations +[2]: http://blog.ryankelly.us/2016/07/11/jimmy.html diff --git a/translated/tech/20160809 Part 3 - Let’s Build A Web Server.md b/published/201610/20160809 Part 3 - Let’s Build A Web Server.md similarity index 70% rename from translated/tech/20160809 Part 3 - Let’s Build A Web Server.md rename to published/201610/20160809 Part 3 - Let’s Build A Web Server.md index 41a128a8a7..cec997dc3a 100644 --- a/translated/tech/20160809 Part 3 - Let’s Build A Web Server.md +++ b/published/201610/20160809 Part 3 - Let’s Build A Web Server.md @@ -1,13 +1,13 @@ -translating by StdioA - 搭个 Web 服务器(三) ===================================== ->“当我们必须创造时,才能够学到更多。” ——伯爵 +>“当我们必须创造时,才能够学到更多。” ——皮亚杰 -在本系列的第二部分中,你创造了一个可以处理基本 HTTP GET 请求的、朴素的 WSGI 服务器。当时我问了一个问题:“你该如何让你的服务器在同一时间处理多个请求呢?”在这篇文章中,你会找到答案。系好安全带,我们要认真起来,全速前进了!你将会体验到一段非常快速的旅程。准备好你的 Linux,Mac OS X(或者其他 *nix 系统),还有你的 Python. 本文中所有源代码均可在 [GitHub][1] 上找到。 +在本系列的[第二部分](https://linux.cn/article-7685-1.html)中,你创造了一个可以处理基本 HTTP GET 请求的、朴素的 WSGI 服务器。当时我问了一个问题:“你该如何让你的服务器在同一时间处理多个请求呢?”在这篇文章中,你会找到答案。系好安全带,我们要认真起来,全速前进了!你将会体验到一段非常快速的旅程。准备好你的 Linux、Mac OS X(或者其他 *nix 系统),还有你的 Python。本文中所有源代码均可在 [GitHub][1] 上找到。 -首先,我们来回顾一下 Web 服务器的基本结构,以及服务器处理来自客户端的请求时,所需的必要步骤。你在第一及第二部分中创建的轮询服务器只能够在同一时间内处理一个请求。在处理完当前请求之前,它不能够打开一个新的客户端连接。所有请求为了等待服务都需要排队,在服务繁忙时,这个队伍可能会排的很长,一些客户端可能会感到不开心。 +### 服务器的基本结构及如何处理请求 + +首先,我们来回顾一下 Web 服务器的基本结构,以及服务器处理来自客户端的请求时,所需的必要步骤。你在[第一部分](https://linux.cn/article-7662-1.html)及[第二部分](https://linux.cn/article-7685-1.html)中创建的轮询服务器只能够一次处理一个请求。在处理完当前请求之前,它不能够接受新的客户端连接。所有请求为了等待服务都需要排队,在服务繁忙时,这个队伍可能会排的很长,一些客户端可能会感到不开心。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it1.png) @@ -53,7 +53,7 @@ if __name__ == '__main__': serve_forever() ``` -为了观察到你的服务器在同一时间只能处理一个请求,我们对服务器的代码做一点点修改:在将响应发送至客户端之后,将程序阻塞 60 秒。这个修改只需要一行代码,来告诉服务器进程暂停 60 秒钟。 +为了观察到你的服务器在同一时间只能处理一个请求的行为,我们对服务器的代码做一点点修改:在将响应发送至客户端之后,将程序阻塞 60 秒。这个修改只需要一行代码,来告诉服务器进程暂停 60 秒钟。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it2.png) @@ -84,7 +84,7 @@ HTTP/1.1 200 OK Hello, World! """ client_connection.sendall(http_response) - time.sleep(60) # 睡眠语句,阻塞该进程 60 秒 + time.sleep(60) ### 睡眠语句,阻塞该进程 60 秒 def serve_forever(): @@ -126,88 +126,85 @@ $ curl http://localhost:8888/hello ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it3.png) -当你等待足够长的时间(60 秒以上)后,你会看到第一个 `curl` 程序完成,而第二个 `curl` 在屏幕上输出了“Hello, World!”,然后休眠 60 秒,进而停止运行。 +当你等待足够长的时间(60 秒以上)后,你会看到第一个 `curl` 程序完成,而第二个 `curl` 在屏幕上输出了“Hello, World!”,然后休眠 60 秒,进而终止。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it4.png) -这两个程序这样运行,是因为在服务器在处理完第一个来自 `curl` 的请求之后,只有等待 60 秒才能开始处理第二个请求。这个处理请求的过程按顺序进行(也可以说,迭代进行),一步一步进行,在我们刚刚给出的例子中,在同一时间内只能处理一个请求。 +这样运行的原因是因为在服务器在处理完第一个来自 `curl` 的请求之后,只有等待 60 秒才能开始处理第二个请求。这个处理请求的过程按顺序进行(也可以说,迭代进行),一步一步进行,在我们刚刚给出的例子中,在同一时间内只能处理一个请求。 现在,我们来简单讨论一下客户端与服务器的交流过程。为了让两个程序在网络中互相交流,它们必须使用套接字。你应当在本系列的前两部分中见过它几次了。但是,套接字是什么? ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_socket.png) -套接字是一个交互通道的端点的抽象形式,它可以让你的程序通过文件描述符来与其它程序进行交流。在这篇文章中,我只会单独讨论 Linux 或 Mac OS X 中的 TCP/IP 套接字。这里有一个重点概念需要你去理解:TCP 套接字对。 +套接字(socket)是一个通讯通道端点(endpoint)的抽象描述,它可以让你的程序通过文件描述符来与其它程序进行交流。在这篇文章中,我只会单独讨论 Linux 或 Mac OS X 中的 TCP/IP 套接字。这里有一个重点概念需要你去理解:TCP 套接字对(socket pair)。 -> TCP 连接使用的套接字对是一个由 4 个元素组成的元组,它确定了 TCP 连接的两端:本地 IP 地址、本地端口、远端 IP 地址及远端端口。一个套接字对独一无二地确定了网络中的每一个 TCP 连接。在连接一端的两个值:一个 IP 地址和一个端口,通常被称作一个套接字。[1][4] +> TCP 连接使用的套接字对是一个由 4 个元素组成的元组,它确定了 TCP 连接的两端:本地 IP 地址、本地端口、远端 IP 地址及远端端口。一个套接字对唯一地确定了网络中的每一个 TCP 连接。在连接一端的两个值:一个 IP 地址和一个端口,通常被称作一个套接字。(引自[《UNIX 网络编程 卷1:套接字联网 API (第3版)》][4]) ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_socketpair.png) -所以,元组 {10.10.10.2:49152, 12.12.12.3:8888} 就是一个能够在客户端确定 TCP 连接两端的套接字对,而元组 {12.12.12.3:8888, 10.10.10.2:49152} 则是在服务端确定 TCP 连接两端的套接字对。在这个例子中,确定 TCP 服务端的两个值(IP 地址 `12.12.12.3` 及端口 `8888`),代表一个套接字;另外两个值则代表客户端的套接字。 +所以,元组 `{10.10.10.2:49152, 12.12.12.3:8888}` 就是一个能够在客户端确定 TCP 连接两端的套接字对,而元组 `{12.12.12.3:8888, 10.10.10.2:49152}` 则是在服务端确定 TCP 连接两端的套接字对。在这个例子中,确定 TCP 服务端的两个值(IP 地址 `12.12.12.3` 及端口 `8888`),代表一个套接字;另外两个值则代表客户端的套接字。 一个服务器创建一个套接字并开始建立连接的基本工作流程如下: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_server_socket_sequence.png) -1. 服务器创建一个 TCP/IP 套接字。我们可以用下面那条 Python 语句来创建: +1. 服务器创建一个 TCP/IP 套接字。我们可以用这条 Python 语句来创建: -``` -listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -``` + ``` + listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ``` +2. 服务器可能会设定一些套接字选项(这个步骤是可选的,但是你可以看到上面的服务器代码做了设定,这样才能够在重启服务器时多次复用同一地址): -2. 服务器可能会设定一些套接字选项(这个步骤是可选的,但是你可以看到上面的服务器代码做了设定,这样才能够在重启服务器时多次复用同一地址)。 - -``` -listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -``` - -3. 然后,服务器绑定一个地址。绑定函数可以将一个本地协议地址赋给套接字。若使用 TCP 协议,调用绑定函数时,需要指定一个端口号,一个 IP 地址,或两者兼有,或两者兼无。[1][4] - -``` -listen_socket.bind(SERVER_ADDRESS) -``` + ``` + listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + ``` +3. 然后,服务器绑定一个地址。绑定函数 `bind` 可以将一个本地协议地址赋给套接字。若使用 TCP 协议,调用绑定函数 `bind` 时,需要指定一个端口号,一个 IP 地址,或两者兼有,或两者全无。(引自[《UNIX网络编程 卷1:套接字联网 API (第3版)》][4]) + ``` + listen_socket.bind(SERVER_ADDRESS) + ``` 4. 然后,服务器开启套接字的监听模式。 -``` -listen_socket.listen(REQUEST_QUEUE_SIZE) -``` + ``` + listen_socket.listen(REQUEST_QUEUE_SIZE) + ``` -监听函数只应在服务端调用。它会通知操作系统内核,标明它会接受所有向该套接字发送请求的链接。 +监听函数 `listen` 只应在服务端调用。它会通知操作系统内核,表明它会接受所有向该套接字发送的入站连接请求。 -以上四步完成后,服务器将循环接收来自客户端的连接,一次循环处理一条。当有连接可用时,`accept` 函数将会返回一个已连接的客户端套接字。然后,服务器从客户端套接字中读取请求数据,将它在标准输出流中打印出来,并向客户端回送一条消息。然后,服务器会关闭这个客户端连接,并准备接收一个新的客户端连接。 +以上四步完成后,服务器将循环接收来自客户端的连接,一次循环处理一条。当有连接可用时,接受请求函数 `accept` 将会返回一个已连接的客户端套接字。然后,服务器从这个已连接的客户端套接字中读取请求数据,将数据在其标准输出流中输出出来,并向客户端回送一条消息。然后,服务器会关闭这个客户端连接,并准备接收一个新的客户端连接。 这是客户端使用 TCP/IP 协议与服务器通信的必要步骤: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_client_socket_sequence.png) -下面是一段示例代码,使用这段代码,客户端可以连接你的服务器,发送一个请求,并打印响应内容: +下面是一段示例代码,使用这段代码,客户端可以连接你的服务器,发送一个请求,并输出响应内容: ``` import socket -# 创建一个套接字,并连接值服务器 +### 创建一个套接字,并连接值服务器 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', 8888)) -# 发送一段数据,并接收响应数据 +### 发送一段数据,并接收响应数据 sock.sendall(b'test') data = sock.recv(1024) print(data.decode()) ``` -在创建套接字后,客户端需要连接至服务器。我们可以调用 `connect` 函数来完成这个操作: +在创建套接字后,客户端需要连接至服务器。我们可以调用连接函数 `connect` 来完成这个操作: ``` sock.connect(('localhost', 8888)) ``` -客户端只需提供待连接服务器的 IP 地址(或主机名),及端口号,即可连接至远端服务器。 +客户端只需提供待连接的远程服务器的 IP 地址(或主机名),及端口号,即可连接至远端服务器。 -你可能已经注意到了,客户端不需要调用 `bind` 及 `accept` 函数,就可以与服务器建立连接。客户端不需要调用 `bind` 函数是因为客户端不需要关注本地 IP 地址及端口号。操作系统内核中的 TCP/IP 协议栈会在客户端调用 `connect` 函数时,自动为套接字分配本地 IP 地址及本地端口号。这个本地端口被称为临时端口,也就是一个短暂开放的端口。 +你可能已经注意到了,客户端不需要调用 `bind` 及 `accept` 函数,就可以与服务器建立连接。客户端不需要调用 `bind` 函数是因为客户端不需要关注本地 IP 地址及端口号。操作系统内核中的 TCP/IP 协议栈会在客户端调用 `connect` 函数时,自动为套接字分配本地 IP 地址及本地端口号。这个本地端口被称为临时端口(ephemeral port),即一个短暂开放的端口。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_ephemeral_port.png) -服务器中有一些端口被用于承载一些众所周知的服务,它们被称作通用端口:如 80 端口用于 HTTP 服务,22 端口用于 SSH 服务。打开你的 Python shell,与你在本地运行的服务器建立一个连接,来看看内核给你的客户端套接字分配了哪个临时端口(在尝试这个例子之前,你需要运行服务器程序 `webserver3a.py` 或 `webserver3b.py`): +服务器中有一些端口被用于承载一些众所周知的服务,它们被称作通用(well-known)端口:如 80 端口用于 HTTP 服务,22 端口用于 SSH 服务。打开你的 Python shell,与你在本地运行的服务器建立一个连接,来看看内核给你的客户端套接字分配了哪个临时端口(在尝试这个例子之前,你需要运行服务器程序 `webserver3a.py` 或 `webserver3b.py`): ``` >>> import socket @@ -222,12 +219,11 @@ sock.connect(('localhost', 8888)) 在我开始回答我在第二部分中提出的问题之前,我还需要快速讲解一些概念。你很快就会明白这些概念为什么非常重要。这两个概念,一个是进程,另外一个是文件描述符。 -什么是进程?进程就是一个程序执行的实体。举个例子:当你的服务器代码被执行时,它会被载入内存,而内存中表现此次程序运行的实体就叫做进程。内核记录了进程的一系列有关信息——比如进程 ID——来追踪它的运行情况。当你在执行轮询服务器 `webserver3a.py` 或 `webserver3b.py` 时,你只启动了一个进程。 +什么是进程?进程就是一个程序执行的实体。举个例子:当你的服务器代码被执行时,它会被载入内存,而内存中表现此次程序运行的实体就叫做进程。内核记录了进程的一系列有关信息——比如进程 ID——来追踪它的运行情况。当你在执行轮询服务器 `webserver3a.py` 或 `webserver3b.py` 时,你其实只是启动了一个进程。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_server_process.png) 我们在终端窗口中运行 `webserver3b.py`: -Start the server webserver3b.py in a terminal window: ``` $ python webserver3b.py @@ -240,7 +236,7 @@ $ ps | grep webserver3b | grep -v grep 7182 ttys003 0:00.04 python webserver3b.py ``` -`ps` 命令显示,我们刚刚只运行了一个 Python 进程 `webserver3b`。当一个进程被创建时,内核会为其分配一个进程 ID,也就是 PID。在 UNIX 中,所有用户进程都有一个父进程;当然,这个父进程也有进程 ID,叫做父进程 ID,缩写为 PPID。假设你默认使用 BASH shell,那当你启动服务器时,一个新的进程会被启动,同时被赋予一个 PID,而它的父进程 PID 会被设为 BASH shell 的 PID。 +`ps` 命令显示,我们刚刚只运行了一个 Python 进程 `webserver3b.py`。当一个进程被创建时,内核会为其分配一个进程 ID,也就是 PID。在 UNIX 中,所有用户进程都有一个父进程;当然,这个父进程也有进程 ID,叫做父进程 ID,缩写为 PPID。假设你默认使用 BASH shell,那当你启动服务器时,就会启动一个新的进程,同时被赋予一个 PID,而它的父进程 PID 会被设为 BASH shell 的 PID。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_ppid_pid.png) @@ -248,11 +244,11 @@ $ ps | grep webserver3b | grep -v grep ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_pid_ppid_screenshot.png) -另外一个需要了解的概念,就是文件描述符。什么是文件描述符?文件描述符是一个非负整数,当进程打开一个现有文件、创建新文件或创建一个新的套接字时,内核会将这个数返回给进程。你以前可能听说过,在 UNIX 中,一切皆是文件。内核会根据一个文件描述符来为一个进程打开一个文件。当你需要读取文件或向文件写入时,我们同样通过文件描述符来定位这个文件。Python 提供了高层次的文件(或套接字)对象,所以你不需要直接通过文件描述符来定位文件。但是,在高层对象之下,我们就是用它来在 UNIX 中定位文件及套接字:整形的文件描述符。 +另外一个需要了解的概念,就是文件描述符。什么是文件描述符?文件描述符是一个非负整数,当进程打开一个现有文件、创建新文件或创建一个新的套接字时,内核会将这个数返回给进程。你以前可能听说过,在 UNIX 中,一切皆是文件。内核会按文件描述符来找到一个进程所打开的文件。当你需要读取文件或向文件写入时,我们同样通过文件描述符来定位这个文件。Python 提供了高层次的操作文件(或套接字)的对象,所以你不需要直接通过文件描述符来定位文件。但是,在高层对象之下,我们就是用它来在 UNIX 中定位文件及套接字,通过这个整数的文件描述符。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_process_descriptors.png) -一般情况下,UNIX shell 会将一个进程的标准输入流的文件描述符设为 0,标准输出流设为 1,而标准错误打印的文件描述符会被设为 2。 +一般情况下,UNIX shell 会将一个进程的标准输入流(STDIN)的文件描述符设为 0,标准输出流(STDOUT)设为 1,而标准错误打印(STDERR)的文件描述符会被设为 2。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_it_default_descriptors.png) @@ -289,7 +285,7 @@ hello 3 ``` -我还想再提一件事:不知道你有没有注意到,在我们的第二个轮询服务器 `webserver3b.py` 中,当你的服务器休眠 60 秒的过程中,你仍然可以通过第二个 `curl` 命令连接至服务器。当然 `curl` 命令并没有立刻输出任何内容而是挂在哪里,但是既然服务器没有接受连接,那它为什么不立即拒绝掉连接,而让它还能够继续与服务器建立连接呢?这个问题的答案是:当我在调用套接字对象的 `listen` 方法时,我为该方法提供了一个 `BACKLOG` 参数,在代码中用 `REQUEST_QUEUE_SIZE` 变量来表示。`BACKLOG` 参数决定了在内核中为存放即将到来的连接请求所创建的队列的大小。当服务器 `webserver3b.py` 被挂起的时候,你运行的第二个 `curl` 命令依然能够连接至服务器,因为内核中用来存放即将接收的连接请求的队列依然拥有足够大的可用空间。 +我还想再提一件事:不知道你有没有注意到,在我们的第二个轮询服务器 `webserver3b.py` 中,当你的服务器休眠 60 秒的过程中,你仍然可以通过第二个 `curl` 命令连接至服务器。当然 `curl` 命令并没有立刻输出任何内容而是挂在哪里,但是既然服务器没有接受连接,那它为什么不立即拒绝掉连接,而让它还能够继续与服务器建立连接呢?这个问题的答案是:当我在调用套接字对象的 `listen` 方法时,我为该方法提供了一个 `BACKLOG` 参数,在代码中用 `REQUEST_QUEUE_SIZE` 常量来表示。`BACKLOG` 参数决定了在内核中为存放即将到来的连接请求所创建的队列的大小。当服务器 `webserver3b.py` 在睡眠的时候,你运行的第二个 `curl` 命令依然能够连接至服务器,因为内核中用来存放即将接收的连接请求的队列依然拥有足够大的可用空间。 尽管增大 `BACKLOG` 参数并不能神奇地使你的服务器同时处理多个请求,但当你的服务器很繁忙时,将它设置为一个较大的值还是相当重要的。这样,在你的服务器调用 `accept` 方法时,不需要再等待一个新的连接建立,而可以立刻直接抓取队列中的第一个客户端连接,并不加停顿地立刻处理它。 @@ -297,7 +293,7 @@ hello ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_checkpoint.png) -- 迭代服务器 +- 轮询服务器 - 服务端套接字创建流程(创建套接字,绑定,监听及接受) - 客户端连接创建流程(创建套接字,连接) - 套接字对 @@ -308,6 +304,8 @@ hello - 文件描述符 - 套接字的 `listen` 方法中,`BACKLOG` 参数的含义 +### 如何并发处理多个请求 + 现在,我可以开始回答第二部分中的那个问题了:“你该如何让你的服务器在同一时间处理多个请求呢?”或者换一种说法:“如何编写一个并发服务器?” ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc2_service_clients.png) @@ -368,13 +366,13 @@ def serve_forever(): while True: client_connection, client_address = listen_socket.accept() pid = os.fork() - if pid == 0: # 子进程 - listen_socket.close() # 关闭子进程中复制的套接字对象 + if pid == 0: ### 子进程 + listen_socket.close() ### 关闭子进程中复制的套接字对象 handle_request(client_connection) client_connection.close() - os._exit(0) # 子进程在这里退出 - else: # 父进程 - client_connection.close() # 关闭父进程中的客户端连接对象,并循环执行 + os._exit(0) ### 子进程在这里退出 + else: ### 父进程 + client_connection.close() ### 关闭父进程中的客户端连接对象,并循环执行 if __name__ == '__main__': serve_forever() @@ -386,13 +384,13 @@ if __name__ == '__main__': $ python webserver3c.py ``` -然后,像我们之前测试轮询服务器那样,运行两个 `curl` 命令,来看看这次的效果。现在你可以看到,即使子进程在处理客户端请求后会休眠 60 秒,但它并不会影响其它客户端连接,因为他们都是由完全独立的进程来处理的。你应该看到你的 `curl` 命令立即输出了“Hello, World!”然后挂起 60 秒。你可以按照你的想法运行尽可能多的 `curl` 命令(好吧,并不能运行特别特别多 ^_^),所有的命令都会立刻输出来自服务器的响应“Hello, World!”,并不会出现任何可被察觉到的延迟行为。试试看吧。 +然后,像我们之前测试轮询服务器那样,运行两个 `curl` 命令,来看看这次的效果。现在你可以看到,即使子进程在处理客户端请求后会休眠 60 秒,但它并不会影响其它客户端连接,因为他们都是由完全独立的进程来处理的。你应该看到你的 `curl` 命令立即输出了“Hello, World!”然后挂起 60 秒。你可以按照你的想法运行尽可能多的 `curl` 命令(好吧,并不能运行特别特别多 `^_^`),所有的命令都会立刻输出来自服务器的响应 “Hello, World!”,并不会出现任何可被察觉到的延迟行为。试试看吧。 -如果你要理解 `fork()`,那最重要的一点是:你调用了它一次,但是它会返回两次:一次在父进程中,另一次是在子进程中。当你创建了一个新进程,那么 `fork()` 在子进程中的返回值是 0。如果是在父进程中,那 `fork()` 函数会返回子进程的 PID。 +如果你要理解 `fork()`,那最重要的一点是:**你调用了它一次,但是它会返回两次** —— 一次在父进程中,另一次是在子进程中。当你创建了一个新进程,那么 `fork()` 在子进程中的返回值是 0。如果是在父进程中,那 `fork()` 函数会返回子进程的 PID。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc2_how_fork_works.png) -我依然记得在第一次看到它并尝试使用 `fork()` 的时候,我是多么的入迷。它在我眼里就像是魔法一样。这就好像我在读一段顺序执行的代码,然后“砰”地一声,代码变成了两份,然后出现了两个实体,同时并行地运行相同的代码。讲真,那个时候我觉得它真的跟魔法一样神奇。 +我依然记得在第一次看到它并尝试使用 `fork()` 的时候,我是多么的入迷。它在我眼里就像是魔法一样。这就好像我在读一段顺序执行的代码,然后“砰!”地一声,代码变成了两份,然后出现了两个实体,同时并行地运行相同的代码。讲真,那个时候我觉得它真的跟魔法一样神奇。 当父进程创建出一个新的子进程时,子进程会复制从父进程中复制一份文件描述符: @@ -401,38 +399,39 @@ $ python webserver3c.py 你可能注意到,在上面的代码中,父进程关闭了客户端连接: ``` -else: # parent +else: ### parent client_connection.close() # close parent copy and loop over ``` -不过,既然父进程关闭了这个套接字,那为什么子进程仍然能够从来自客户端的套接字中读取数据呢?答案就在上面的图片中。内核会使用描述符引用计数器来决定是否要关闭一个套接字。当你的服务器创建一个子进程时,子进程会复制父进程的所有文件描述符,内核中改描述符的引用计数也会增加。如果只有一个父进程及一个子进程,那客户端套接字的文件描述符引用数应为 2;当父进程关闭客户端连接的套接字时,内核只会减少它的引用计数,将其变为 1,但这仍然不会使内核关闭该套接字。子进程也关闭了父进程中 `listen_socket` 的复制实体,因为子进程不需要关注新的客户端连接,而只需要处理已建立的客户端连接中的请求。 +不过,既然父进程关闭了这个套接字,那为什么子进程仍然能够从来自客户端的套接字中读取数据呢?答案就在上面的图片中。内核会使用描述符引用计数器来决定是否要关闭一个套接字。当你的服务器创建一个子进程时,子进程会复制父进程的所有文件描述符,内核中该描述符的引用计数也会增加。如果只有一个父进程及一个子进程,那客户端套接字的文件描述符引用数应为 2;当父进程关闭客户端连接的套接字时,内核只会减少它的引用计数,将其变为 1,但这仍然不会使内核关闭该套接字。子进程也关闭了父进程中 `listen_socket` 的复制实体,因为子进程不需要关注新的客户端连接,而只需要处理已建立的客户端连接中的请求。 ``` -listen_socket.close() # 关闭子进程中的复制实体 +listen_socket.close() ### 关闭子进程中的复制实体 ``` 我们将会在后文中讨论,如果你不关闭那些重复的描述符,会发生什么。 -你可以从你的并发服务器源码看到,父进程的主要职责为:接受一个新的客户端连接,复制出一个子进程来处理这个连接,然后继续循环来接受另外的客户端连接,仅此而已。服务器父进程并不会处理客户端连接——子进程才会做这件事。 +你可以从你的并发服务器源码中看到,父进程的主要职责为:接受一个新的客户端连接,复制出一个子进程来处理这个连接,然后继续循环来接受另外的客户端连接,仅此而已。服务器父进程并不会处理客户端连接——子进程才会做这件事。 -打个岔:当我们说两个事件并发执行时,我们在说什么? -A little aside. What does it mean when we say that two events are concurrent? +打个岔:当我们说两个事件并发执行时,我们所要表达的意思是什么? ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc2_concurrent_events.png) 当我们说“两个事件并发执行”时,它通常意味着这两个事件同时发生。简单来讲,这个定义没问题,但你应该记住它的严格定义: -> 如果你阅读代码时,无法判断两个事件的发生顺序,那这两个事件就是并发执行的。[2][5] +> 如果你不能在代码中判断两个事件的发生顺序,那这两个事件就是并发执行的。(引自[《信号系统简明手册 (第二版): 并发控制深入浅出及常见错误》][5]) 好的,现在你又该回顾一下你刚刚学过的知识点了。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_checkpoint.png) - 在 Unix 中,编写一个并发服务器的最简单的方式——使用 `fork()` 系统调用; -- 当一个进程复制出另一个进程时,它会变成刚刚复制出的进程的父进程; +- 当一个进程分叉(`fork`)出另一个进程时,它会变成刚刚分叉出的进程的父进程; - 在进行 `fork` 调用后,父进程和子进程共享相同的文件描述符; -- 系统内核通过描述符引用计数来决定是否要关闭该描述符对应的文件或套接字; -- 服务器父进程的主要职责:现在它做的只是从客户端接受一个新的连接,复制出子进程来处理这个客户端连接,然后开始下一轮循环,去接收新的客户端连接。 +- 系统内核通过描述符的引用计数来决定是否要关闭该描述符对应的文件或套接字; +- 服务器父进程的主要职责:现在它做的只是从客户端接受一个新的连接,分叉出子进程来处理这个客户端连接,然后开始下一轮循环,去接收新的客户端连接。 + +### 进程分叉后不关闭重复的套接字会发生什么? 我们来看看,如果我们不在父进程与子进程中关闭重复的套接字描述符会发生什么。下面是刚才的并发服务器代码的修改版本,这段代码(`webserver3d.py` 中,服务器不会关闭重复的描述符): @@ -470,15 +469,15 @@ def serve_forever(): clients = [] while True: client_connection, client_address = listen_socket.accept() - # 将引用存储起来,否则在下一轮循环时,他们会被垃圾回收机制销毁 + ### 将引用存储起来,否则在下一轮循环时,他们会被垃圾回收机制销毁 clients.append(client_connection) pid = os.fork() - if pid == 0: # 子进程 - listen_socket.close() # 关闭子进程中多余的套接字 + if pid == 0: ### 子进程 + listen_socket.close() ### 关闭子进程中多余的套接字 handle_request(client_connection) client_connection.close() - os._exit(0) # 子进程在这里结束 - else: # 父进程 + os._exit(0) ### 子进程在这里结束 + else: ### 父进程 # client_connection.close() print(len(clients)) @@ -503,7 +502,7 @@ Hello, World! ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_child_is_active.png) -所以,为什么 `curl` 不终止呢?原因就在于多余的文件描述符。当子进程关闭客户端连接时,系统内核会减少客户端套接字的引用计数,将其变为 1。服务器子进程退出了,但客户端套接字并没有被内核关闭,因为该套接字的描述符引用计数并没有变为 0,所以,这就导致了连接终止包(在 TCP/IP 协议中称作 `FIN`)不会被发送到客户端,所以客户端会一直保持连接。这里就会出现另一个问题:如果你的服务器在长时间运行,并且不关闭重复的文件描述符,那么可用的文件描述符会被消耗殆尽: +所以,为什么 `curl` 不终止呢?原因就在于文件描述符的副本。当子进程关闭客户端连接时,系统内核会减少客户端套接字的引用计数,将其变为 1。服务器子进程退出了,但客户端套接字并没有被内核关闭,因为该套接字的描述符引用计数并没有变为 0,所以,这就导致了连接终止包(在 TCP/IP 协议中称作 `FIN`)不会被发送到客户端,所以客户端会一直保持连接。这里也会出现另一个问题:如果你的服务器长时间运行,并且不关闭文件描述符的副本,那么可用的文件描述符会被消耗殆尽: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_out_of_descriptors.png) @@ -529,7 +528,7 @@ virtual memory (kbytes, -v) unlimited file locks (-x) unlimited ``` -你可以从上面的结果看到,在我的 Ubuntu box 中,系统为我的服务器进程分配的最大可用文件描述符(文件打开)数为 1024。 +你可以从上面的结果看到,在我的 Ubuntu 机器中,系统为我的服务器进程分配的最大可用文件描述符(文件打开)数为 1024。 现在我们来看一看,如果你的服务器不关闭重复的描述符,它会如何消耗可用的文件描述符。在一个已有的或新建的终端窗口中,将你的服务器进程的最大可用文件描述符设为 256: @@ -607,15 +606,18 @@ if __name__ == '__main__': $ python client3.py --max-clients=300 ``` -过一会,你的服务器就该爆炸了。这是我的环境中出现的异常截图: +过一会,你的服务器进程就该爆了。这是我的环境中出现的异常截图: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_too_many_fds_exc.png) -这个例子很明显——你的服务器应该关闭重复的描述符。但是,即使你关闭了多余的描述符,你依然没有摆脱险境,因为你的服务器还有一个问题,这个问题在于“僵尸”! +这个例子很明显——你的服务器应该关闭描述符副本。 + +#### 僵尸进程 + +但是,即使你关闭了描述符副本,你依然没有摆脱险境,因为你的服务器还有一个问题,这个问题在于“僵尸(zombies)”! ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_zombies.png) - 没错,这个服务器代码确实在制造僵尸进程。我们来看看怎么回事。重新运行你的服务器: ``` @@ -636,13 +638,13 @@ vagrant 9099 0.0 1.2 31804 6256 pts/0 S+ 16:33 0:00 python webserve vagrant 9102 0.0 0.0 0 0 pts/0 Z+ 16:33 0:00 [python] ``` -你看到第二行中,pid 为 9102,状态为 Z+,名字里面有个 `` 的进程了吗?那就是我们的僵尸进程。这个僵尸进程的问题在于:你无法将它杀掉。 +你看到第二行中,pid 为 9102,状态为 `Z+`,名字里面有个 `` 的进程了吗?那就是我们的僵尸进程。这个僵尸进程的问题在于:你无法将它杀掉! ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_kill_zombie.png) 就算你尝试使用 `kill -9` 来杀死僵尸进程,它们仍旧会存活。自己试试看,看看结果。 -这个僵尸到底是什么,为什么我们的服务器会造出它们呢?一个僵尸进程是一个已经结束的进程,但它的父进程并没有等待它结束,并且也没有收到它的终结状态。如果一个进程在父进程退出之前退出,系统内核会把它变为一个僵尸进程,存储它的部分信息,以便父进程读取。内核保存的进程信息通常包括进程 ID,进程终止状态,以及进程的资源占用情况。OK,所以僵尸进程确实有存在的意义,但如果服务器不管这些僵尸进程,你的系统调用将会被阻塞。我们来看看这个要如何发生。首先,关闭你的服务器;然后,在一个新的终端窗口中,使用 `ulimit` 命令将最大用户进程数设为 400(同时,要确保你的最大可用描述符数大于这个数字,我们在这里设为 500): +这个僵尸到底是什么,为什么我们的服务器会造出它们呢?一个僵尸进程(zombie)是一个已经结束的进程,但它的父进程并没有等待(`waited`)它结束,并且也没有收到它的终结状态。如果一个进程在父进程退出之前退出,系统内核会把它变为一个僵尸进程,存储它的部分信息,以便父进程读取。内核保存的进程信息通常包括进程 ID、进程终止状态,以及进程的资源占用情况。OK,所以僵尸进程确实有存在的意义,但如果服务器不管这些僵尸进程,你的系统将会被壅塞。我们来看看这个会如何发生。首先,关闭你运行的服务器;然后,在一个新的终端窗口中,使用 `ulimit` 命令将最大用户进程数设为 400(同时,要确保你的最大可用描述符数大于这个数字,我们在这里设为 500): ``` $ ulimit -u 400 @@ -661,33 +663,35 @@ $ python webserver3d.py $ python client3.py --max-clients=500 ``` -然后,过一会,你的服务器应该会再次爆炸,它会在创建新进程时抛出一个 `OSError: 资源暂时不可用` 异常。但它并没有达到系统允许的最大进程数。这是我的环境中输出的异常信息截图: +然后,过一会,你的服务器进程应该会再次爆了,它会在创建新进程时抛出一个 `OSError: 资源暂时不可用` 的异常。但它并没有达到系统允许的最大进程数。这是我的环境中输出的异常信息截图: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc3_resource_unavailable.png) -你可以看到,如果服务器不管僵尸进程,它们会引发问题。我会简单探讨一下僵尸进程问题的解决方案。 +你可以看到,如果服务器不管僵尸进程,它们会引发问题。接下来我会简单探讨一下僵尸进程问题的解决方案。 我们来回顾一下你刚刚掌握的知识点: ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_checkpoint.png) -- 如果你不关闭重复的描述符,客户端就不会在请求处理完成后终止,因为客户端连接没有被关闭; -- 如果你不关闭重复的描述符,长久运行的服务器最终会把可用的文件描述符(最大文件打开数)消耗殆尽; -- 当你创建一个新进程,而父进程不等待子进程,也不在子进程结束后收集它的终止状态,它会变为一个僵尸进程; -- 僵尸通常都会吃东西,在我们的例子中,僵尸进程会占用资源。如果你的服务器不管僵尸进程,它最终会消耗掉所有的可用进程(最大用户进程数); -- 你不能杀死僵尸进程,你需要等待它。 +- 如果你不关闭文件描述符副本,客户端就不会在请求处理完成后终止,因为客户端连接没有被关闭; +- 如果你不关闭文件描述符副本,长久运行的服务器最终会把可用的文件描述符(最大文件打开数)消耗殆尽; +- 当你创建一个新进程,而父进程不等待(`wait`)子进程,也不在子进程结束后收集它的终止状态,它会变为一个僵尸进程; +- 僵尸通常都会吃东西,在我们的例子中,僵尸进程会吃掉资源。如果你的服务器不管僵尸进程,它最终会消耗掉所有的可用进程(最大用户进程数); +- 你不能杀死(`kill`)僵尸进程,你需要等待(`wait`)它。 -所以,你需要做什么来处理僵尸进程呢?你需要修改你的服务器代码,来等待僵尸进程,并收集它们的终止信息。你可以在代码中使用系统调用 `wait` 来完成这个任务。不幸的是,这个方法里理想目标还很远,因为在没有终止的子进程存在的情况下调用 `wait` 会导致程序阻塞,这会阻碍你的服务器处理新的客户端连接请求。那么,我们有其他选择吗?嗯,有的,其中一个解决方案需要结合信号处理以及 `wait` 系统调用。 +### 如何处理僵尸进程? + +所以,你需要做什么来处理僵尸进程呢?你需要修改你的服务器代码,来等待(`wait`)僵尸进程,并收集它们的终止信息。你可以在代码中使用系统调用 `wait` 来完成这个任务。不幸的是,这个方法离理想目标还很远,因为在没有终止的子进程存在的情况下调用 `wait` 会导致服务器进程阻塞,这会阻碍你的服务器处理新的客户端连接请求。那么,我们有其他选择吗?嗯,有的,其中一个解决方案需要结合信号处理以及 `wait` 系统调用。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc4_signaling.png) -这是它的工作流程。当一个子进程退出时,内核会发送 `SIGCHLD` 信号。父进程可以设置一个信号处理器,它可以异步响应 `SIGCHLD` 信号,并在信号响应函数中等待子进程收集终止信息,从而阻止了僵尸进程的存在。 +这是它的工作流程。当一个子进程退出时,内核会发送 `SIGCHLD` 信号。父进程可以设置一个信号处理器,它可以异步响应 `SIGCHLD` 信号,并在信号响应函数中等待(`wait`)子进程收集终止信息,从而阻止了僵尸进程的存在。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part_conc4_sigchld_async.png) -顺便,异步事件意味着父进程无法提前知道事件的发生时间。 +顺便说一下,异步事件意味着父进程无法提前知道事件的发生时间。 -修改你的服务器代码,设置一个 `SIGCHLD` 信号处理器,在信号处理器中等待终止的子进程。修改后的代码如下(webserver3e.py): +修改你的服务器代码,设置一个 `SIGCHLD` 信号处理器,在信号处理器中等待(`wait`)终止的子进程。修改后的代码如下(webserver3e.py): ``` ####################################################### @@ -722,7 +726,7 @@ HTTP/1.1 200 OK Hello, World! """ client_connection.sendall(http_response) - # 挂起进程,来允许父进程完成循环,并在 "accept" 处阻塞 + ### 挂起进程,来允许父进程完成循环,并在 "accept" 处阻塞 time.sleep(3) @@ -738,12 +742,12 @@ def serve_forever(): while True: client_connection, client_address = listen_socket.accept() pid = os.fork() - if pid == 0: # 子进程 - listen_socket.close() # 关闭子进程中多余的套接字 + if pid == 0: ### 子进程 + listen_socket.close() ### 关闭子进程中多余的套接字 handle_request(client_connection) client_connection.close() os._exit(0) - else: # 父进程 + else: ### 父进程 client_connection.close() if __name__ == '__main__': @@ -766,7 +770,7 @@ $ curl http://localhost:8888/hello ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc4_eintr.png) -刚刚发生了什么?`accept` 调用失败了,错误信息为 `EINTR` +刚刚发生了什么?`accept` 调用失败了,错误信息为 `EINTR`。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc4_eintr_error.png) @@ -822,20 +826,20 @@ def serve_forever(): client_connection, client_address = listen_socket.accept() except IOError as e: code, msg = e.args - # 若 'accept' 被打断,那么重启它 + ### 若 'accept' 被打断,那么重启它 if code == errno.EINTR: continue else: raise pid = os.fork() - if pid == 0: # 子进程 - listen_socket.close() # 关闭子进程中多余的描述符 + if pid == 0: ### 子进程 + listen_socket.close() ### 关闭子进程中多余的描述符 handle_request(client_connection) client_connection.close() os._exit(0) - else: # 父进程 - client_connection.close() # 关闭父进程中多余的描述符,继续下一轮循环 + else: ### 父进程 + client_connection.close() ### 关闭父进程中多余的描述符,继续下一轮循环 if __name__ == '__main__': @@ -854,7 +858,7 @@ $ python webserver3f.py $ curl http://localhost:8888/hello ``` -看到了吗?没有 EINTR 异常出现了。现在检查一下,确保没有僵尸进程存活,调用 `wait` 函数的 `SIGCHLD` 信号处理器能够正常处理被终止的子进程。我们只需使用 `ps` 命令,然后看看现在没有处于 Z+ 状态(或名字包含 `` )的 Python 进程就好了。很棒!僵尸进程没有了,我们很安心。 +看到了吗?没有 EINTR 异常出现了。现在检查一下,确保没有僵尸进程存活,调用 `wait` 函数的 `SIGCHLD` 信号处理器能够正常处理被终止的子进程。我们只需使用 `ps` 命令,然后看看现在没有处于 `Z+` 状态(或名字包含 `` )的 Python 进程就好了。很棒!僵尸进程没有了,我们很安心。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_checkpoint.png) @@ -862,6 +866,8 @@ $ curl http://localhost:8888/hello - 使用 `SIGCHLD` 信号处理器可以异步地等待子进程终止,并收集其终止状态; - 当使用事件处理器时,你需要牢记,系统调用可能会被打断,所以你需要处理这种情况发生时带来的异常。 +#### 正确处理 SIGCHLD 信号 + 好的,一切顺利。是不是没问题了?额,几乎是。重新尝试运行 `webserver3f.py` 但我们这次不会只发送一个请求,而是同步创建 128 个连接: ``` @@ -882,7 +888,7 @@ $ ps auxw | grep -i python | grep -v grep ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc5_signals_not_queued.png) -这个问题的解决方案依然是设置 `SIGCHLD` 事件处理器。但我们这次将会用 `WNOHANG` 参数循环调用 `waitpid`,来保证所有处于终止状态的子进程都会被处理。下面是修改后的代码,`webserver3g.py`: +这个问题的解决方案依然是设置 `SIGCHLD` 事件处理器。但我们这次将会用 `WNOHANG` 参数循环调用 `waitpid` 来替代 `wait`,以保证所有处于终止状态的子进程都会被处理。下面是修改后的代码,`webserver3g.py`: ``` ####################################################### @@ -904,13 +910,13 @@ def grim_reaper(signum, frame): while True: try: pid, status = os.waitpid( - -1, # 等待所有子进程 - os.WNOHANG # 无终止进程时,不阻塞进程,并抛出 EWOULDBLOCK 错误 + -1, ### 等待所有子进程 + os.WNOHANG ### 无终止进程时,不阻塞进程,并抛出 EWOULDBLOCK 错误 ) except OSError: return - if pid == 0: # 没有僵尸进程存在了 + if pid == 0: ### 没有僵尸进程存在了 return @@ -939,20 +945,20 @@ def serve_forever(): client_connection, client_address = listen_socket.accept() except IOError as e: code, msg = e.args - # 若 'accept' 被打断,那么重启它 + ### 若 'accept' 被打断,那么重启它 if code == errno.EINTR: continue else: raise pid = os.fork() - if pid == 0: # 子进程 - listen_socket.close() # 关闭子进程中多余的描述符 + if pid == 0: ### 子进程 + listen_socket.close() ### 关闭子进程中多余的描述符 handle_request(client_connection) client_connection.close() os._exit(0) - else: # 父进程 - client_connection.close() # 关闭父进程中多余的描述符,继续下一轮循环 + else: ### 父进程 + client_connection.close() ### 关闭父进程中多余的描述符,继续下一轮循环 if __name__ == '__main__': serve_forever() @@ -970,17 +976,19 @@ $ python webserver3g.py $ python client3.py --max-clients 128 ``` -现在来查看一下,确保没有僵尸进程存在。耶!没有僵尸的生活真美好 ^_^ +现在来查看一下,确保没有僵尸进程存在。耶!没有僵尸的生活真美好 `^_^`。 ![](https://ruslanspivak.com/lsbaws-part3/lsbaws_part3_conc5_no_zombies.png) -恭喜!你刚刚经历了一段很长的旅程,我希望你能够喜欢它。现在你拥有了自己的建议并发服务器,并且这段代码能够为你在继续研究生产级 Web 服务器的路上奠定基础。 +### 大功告成 -我将会留一个作业:你需要将第二部分中的 WSGI 服务器升级,将它改造为一个并发服务器。你可以在[这里][12]找到更改后的代码。但是,当你实现了自己的版本之后,你才应该来看我的代码。你已经拥有了实现这个服务器所需的所有信息。所以,快去实现它吧 ^_^ +恭喜!你刚刚经历了一段很长的旅程,我希望你能够喜欢它。现在你拥有了自己的简易并发服务器,并且这段代码能够为你在继续研究生产级 Web 服务器的路上奠定基础。 + +我将会留一个作业:你需要将第二部分中的 WSGI 服务器升级,将它改造为一个并发服务器。你可以在[这里][12]找到更改后的代码。但是,当你实现了自己的版本之后,你才应该来看我的代码。你已经拥有了实现这个服务器所需的所有信息。所以,快去实现它吧 `^_^`。 然后要做什么呢?乔希·比林斯说过: -> “我们应该做一枚邮票——专注于一件事,不达目的不罢休。” +> “就像一枚邮票一样——专注于一件事,不达目的不罢休。” 开始学习基本知识。回顾你已经学过的知识。然后一步一步深入。 @@ -990,13 +998,13 @@ $ python client3.py --max-clients 128 下面是一份书单,我从这些书中提炼出了这篇文章所需的素材。他们能助你在我刚刚所述的几个方面中发掘出兼具深度和广度的知识。我极力推荐你们去搞到这几本书看看:从你的朋友那里借,在当地的图书馆中阅读,或者直接在亚马逊上把它买回来。下面是我的典藏秘籍: -1. [UNIX网络编程 (卷1):套接字联网API (第3版)][6] -2. [UNIX环境高级编程 (第3版)][7] -3. [Linux/UNIX系统编程手册][8] -4. [TCP/IP详解 (卷1):协议 (第2版) (爱迪生-韦斯莱专业编程系列)][9] -5. [信号系统简明手册 (第二版): 并发控制深入浅出及常见错误][10]. 这本书也可以从[作者的个人网站][11]中买到。 +1. [《UNIX 网络编程 卷1:套接字联网 API (第3版)》][6] +2. [《UNIX 环境高级编程(第3版)》][7] +3. [《Linux/UNIX 系统编程手册》][8] +4. [《TCP/IP 详解 卷1:协议(第2版)][9] +5. [《信号系统简明手册 (第二版): 并发控制深入浅出及常见错误》][10],这本书也可以从[作者的个人网站][11]中免费下载到。 -顺便,我在撰写一本名为《搭个 Web 服务器:从头开始》的书。这本书讲解了如何从头开始编写一个基本的 Web 服务器,里面包含本文中没有的更多细节。订阅邮件列表,你就可以获取到这本书的最新进展,以及发布日期。 +顺便,我在撰写一本名为《搭个 Web 服务器:从头开始》的书。这本书讲解了如何从头开始编写一个基本的 Web 服务器,里面包含本文中没有的更多细节。订阅[原文下方的邮件列表][13],你就可以获取到这本书的最新进展,以及发布日期。 -------------------------------------------------------------------------------- @@ -1004,7 +1012,7 @@ via: https://ruslanspivak.com/lsbaws-part3/ 作者:[Ruslan][a] 译者:[StdioA](https://github.com/StdioA) -校对:[校对者ID](https://github.com/校对者ID) +校对:[wxy](https://github.com/wxy) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 @@ -1013,12 +1021,13 @@ via: https://ruslanspivak.com/lsbaws-part3/ [1]: https://github.com/rspivak/lsbaws/blob/master/part3/ [2]: https://github.com/rspivak/lsbaws/blob/master/part3/webserver3a.py [3]: https://github.com/rspivak/lsbaws/blob/master/part3/webserver3b.py -[4]: https://ruslanspivak.com/lsbaws-part3/#fn:1 -[5]: https://ruslanspivak.com/lsbaws-part3/#fn:2 -[6]: http://www.amazon.com/gp/product/0131411551/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0131411551&linkCode=as2&tag=russblo0b-20&linkId=2F4NYRBND566JJQL -[7]: http://www.amazon.com/gp/product/0321637739/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321637739&linkCode=as2&tag=russblo0b-20&linkId=3ZYAKB537G6TM22J -[8]: http://www.amazon.com/gp/product/1593272200/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1593272200&linkCode=as2&tag=russblo0b-20&linkId=CHFOMNYXN35I2MON -[9]: http://www.amazon.com/gp/product/0321336313/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321336313&linkCode=as2&tag=russblo0b-20&linkId=K467DRFYMXJ5RWAY +[4]: http://www.epubit.com.cn/book/details/1692 +[5]: http://www.amazon.com/gp/product/1441418687/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1441418687&linkCode=as2&tag=russblo0b-20&linkId=QFOAWARN62OWTWUG +[6]: http://www.epubit.com.cn/book/details/1692 +[7]: http://www.epubit.com.cn/book/details/1625 +[8]: http://www.epubit.com.cn/book/details/1432 +[9]: http://www.epubit.com.cn/book/details/4232 [10]: http://www.amazon.com/gp/product/1441418687/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1441418687&linkCode=as2&tag=russblo0b-20&linkId=QFOAWARN62OWTWUG [11]: http://greenteapress.com/semaphores/ [12]: https://github.com/rspivak/lsbaws/blob/master/part3/webserver3h.py +[13]: https://ruslanspivak.com/lsbaws-part1/ \ No newline at end of file diff --git a/published/201610/20160811 5 best linux init system.md b/published/201610/20160811 5 best linux init system.md new file mode 100644 index 0000000000..df16c0ea6c --- /dev/null +++ b/published/201610/20160811 5 best linux init system.md @@ -0,0 +1,103 @@ +现代 Linux 的五大初始化系统(1992-2015) +============================================ + +在 Linux 和其他类 Uniux 系统中,init(初始化)进程是系统启动时由内核执行的第一个进程,其进程 ID(PID)为 1,并静默运行在后台,直到系统关闭。 + +init 进程负责启动其他所有的进程,比如守护进程、服务和其他后台进程,因此,它是系统中其它所有进程之母(偏偏叫做“父进程”)。某个进程可以启动许多个子进程,但在这个过程中,某个子进程的父进程结束之后,该子进程的父进程会变成 init 进程。 + +![](http://www.tecmint.com/wp-content/uploads/2016/08/Linux-init-Systems.png) + +这么多年过去了,许多的初始化系统在主流 Linux 脱颖而出,和本文中,我将你来看看在 Linux 操作系统最好的初始化系统。 + +### 1. System V Init + +System V (SysV) 是一个在类 Unix 系统中最为成熟而且大受欢迎的初始化方案,是 Unix/Linux 系统中所有进程的父进程。SysV 是第一个商业 Unix 系统设计的初始化方案。 + +除了 Gentoo 使用自主的初始化系统、Slackware 使用 BSD 风格的初始化方案外,几乎所有的 Linux 发行版都率先使用 SysV 作为初始化方案。 + +随着时间的推移,由于一些设计上的缺陷,有几个 SysV 初始化替换方案已经开发出来,用以为 Linux 创建更加高效和完美的初始化系统。 + +尽管这些替代方案都超越了 SysV 并提供了更多新特性,但它们仍然和原始 SysV 初始化脚本保持兼容。 + +### 2. SystemD + +SystemD 是一个 Linux 平台中相对较新的初始化方案。它由 Fedora 15 引入,集成了各类工具以便更好的管理系统。主要目的是:系统初始化、管理和跟踪引导进程中和系统运行时所有的系统进程。 + +Systemd 全面有别于其他传统的 Unix 初始化系统,特别是在启动系统和服务管理方面。它同样兼容 SysV 和 LBS 初始化脚本。 + +其中较为突出的特性如下: + +- 纯粹、简单、高效的设计 +- 启动时的并发和并行处理 +- 更好的 API +- 开启可选进程的移除功能 +- 使用 journald 来支持事件日志 +- 使用 systemd calender timers 来支持任务计划 +- 以二进制文件存储日志 +- 保存 systemd 的状态以待今后查看 +- 与 GNOME 更好整合实现等 + +查看 Systemd 初始化系统简介: + +### 3. Upstart + +Upstart 是一个基于事件的初始化系统,由 Ubuntu 的制作团队开发的,用以替代 SysV。它可以启动不同的系统任务和进程、在系统运行时校验进程并在系统关闭时结束进程。 + +它是一个使用 SysV 和 Systemd 启动脚本的混合初始化系统,Upstart 中值得一提的特性如下: + +- Ubuntu 的原生初始化系统,但可以运行在其他所有的发行版中 +- 基于事件启动/结束的任务和服务 +- 启动/结束任务和服务时生成事件 +- 可以由其他系统进程发送事件 +- 使用 D-Bus 和 init 进程通信 +- 用户可以启动/结束其各自的进程 +- 可以再现崩溃的进程等 + +访问 Upstart 主页: + +### 4. OpenRC + +OpenRC 是一个基于依赖关系的类 Unix 系统初始化方案,兼容 SysV。基本可以说是 SysV 的升级版,但必须要清楚记住的是:OpenRC 并非只是完全替代 /sbin/init 文件。 + +它所提供的出色特性如下: + +- 可运行在包括 Gentoo 和 BSD 在内的多数 Linux 系统之中 +- 支持硬件触发的初始化脚本(LCTT 译注:如硬件热插拔所触发的) +- 支持单个配置文件 +- 不支持单个服务配置文件 +- 以守护进程的方式运行 +- 并行服务启动等 + +访问 OpenRC 主页: + +### 5. runit + +runit 同样是一个跨平台初始化系统,可以运行在 GNU/Linux、Solaris、BSD 和 Mac OS X 中,用替代 SysV,同时提供服务监控。 + +相比于 SysV 和其他 Linux 初始化系统,它提供了一些好用和卓越的组件,如下: + +- 服务监控:每个服务都关联一个服务目录 +- 清理进程状态,以保证每个进程处于干净状态 +- 可靠的日志机制 +- 快速的系统启动和关闭 +- 可移植 +- 打包方便 +- 代码体积小等 + +访问 runit 主页: + +正如我之前所说的,Linux 中的初始化系统负责启动和管理所有的进程。此外,SysV 是 Linux 系统中主要的初始化系统,但由于一些性能缺陷,系统开发者已经开发出几个替代品。 + +在这里,我已经介绍了几个可用的替代方案,但你可能觉得有一些其他的初始化系统值得在此提及。请在下方的评论区将你的想法告诉我们。 + +-------------------------------------------------------------------------------- + +via: http://www.tecmint.com/best-linux-init-systems/ + +作者:[Aaron Kili](http://www.tecmint.com/author/aaronkili/) +译者:[GHLandy](https://github.com/GHLandy) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + + diff --git a/published/201610/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md b/published/201610/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md new file mode 100755 index 0000000000..5d63f8ee30 --- /dev/null +++ b/published/201610/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md @@ -0,0 +1,147 @@ +如何使用 SSHFS 通过 SSH 挂载远程的 Linux 文件系统或者目录 +============================ + +写这篇文章的主要目的就是提供一步一步的指导,关于如何使用 SSHFS 通过 SSH 挂载远程的 Linux 文件系统或目录。 + +这篇文章对于那些无论出于什么目的,希望在他们本地的系统中挂载远程的文件系统的用户或者系统管理员有帮助。我们通过 Linux 系统中的一个安装了 SSHFS 客户端进行实际测试,并且成功的挂载了远程的文件系统。 + +在我们进一步安装之前,让我们了解一下 SSHFS 的相关内容,以及它是如何工作的。 + +![](http://www.tecmint.com/wp-content/uploads/2012/08/Sshfs-Mount-Remote-Linux-Filesystem-Directory.png) +*Sshfs 挂载远程的 Linux 文件系统或者目录* + +### 什么是 SSHFS? + +SSHFS(Secure SHell FileSystem)是一个客户端,可以让我们通过 SSH 文件传输协议(SFTP)挂载远程的文件系统并且在本地机器上和远程的目录和文件进行交互。 + +SFTP 是一种通过 SSH 协议提供文件访问、文件传输和文件管理功能的安全文件传输协议。因为 SSH 在网络中从一台电脑到另一台电脑传输文件的时候使用数据加密通道,并且 SSHFS 内置在 FUSE(用户空间的文件系统)内核模块,允许任何非特权用户在不修改内核代码的情况下创建他们自己的文件系统。 + +在这篇文章中,我们将会向你展示在任意 Linux 发行版上如何安装并且使用 SSHFS 客户端,在本地 Linux 机器上挂载远程的 Linux 文件系统或者目录。 + +#### 步骤1:在 Linux 系统上安装 SSHFS + +默认情况下,sshfs 包不存在所有的主流 Linux 发行版中,你需要在你的 Linux 系统中启用 [epel 仓库][1],在 Yum 命令行的帮助下安装 SSHFS 及其依赖。 + +``` +# yum install sshfs +# dnf install sshfs 【在 Fedora 22+ 发行版上】 +$ sudo apt-get install sshfs 【基于 Debian/Ubuntu 的系统】 +``` + +#### 步骤2:创建 SSHFS 挂载目录 + +当你安装 SSHFS 包之后,你需要创建一个挂载点目录,在这儿你将要挂载你的远程文件系统。例如,我们在 /mnt/tecmint 下创建挂载目录。 + +``` +# mkdir /mnt/tecmint +$ sudo mkdir /mnt/tecmint 【基于 Debian/Ubuntu 的系统】 +``` + +### 步骤 3:使用 SSHFS 挂载远程的文件系统 + +当你已经创建你的挂载点目录之后,现在使用 root 用户运行下面的命令行,在 /mnt/tecmint 目录下挂载远程的文件系统。视你的情况挂载目录可以是任何目录。 + +下面的命令行将会在本地的 /mnt/tecmint 目录下挂载一个叫远程的一个 /home/tecmint 目录。(不要忘了使用你的 IP 地址和挂载点替换 x.x.x.x)。 + +``` +# sshfs tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint +$ sudo sshfs -o allow_other tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint 【基于 Debian/Ubuntu 的系统】 +``` + +如果你的 Linux 服务器配置为基于 SSH 密钥授权,那么你将需要使用如下所示的命令行指定你的公共密钥的路径。 + +``` +# sshfs -o IdentityFile=~/.ssh/id_rsa tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint +$ sudo sshfs -o allow_other,IdentityFile=~/.ssh/id_rsa tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint 【基于 Debian/Ubuntu 的系统】 +``` + +#### 步骤 4:验证远程的文件系统挂载成功 + +如果你已经成功的运行了上面的命令并且没有任何错误,你将会看到挂载在 /mnt/tecmint 目录下的远程的文件和目录的列表 + +``` +# cd /mnt/tecmint +# ls +[root@ tecmint]# ls +12345.jpg ffmpeg-php-0.6.0.tbz2 Linux news-closeup.xsl s3.jpg +cmslogs gmd-latest.sql.tar.bz2 Malware newsletter1.html sshdallow +epel-release-6-5.noarch.rpm json-1.2.1 movies_list.php pollbeta.sql +ffmpeg-php-0.6.0 json-1.2.1.tgz my_next_artical_v2.php pollbeta.tar.bz2 +``` + +#### 步骤 5:使用 df -hT 命令检查挂载点 + +如果你运行 df -hT命令,你将会看到远程文件系统的挂载点。 + +``` +# df -hT +``` + +样本输出: + +``` +Filesystem Type Size Used Avail Use% Mounted on +udev devtmpfs 730M 0 730M 0% /dev +tmpfs tmpfs 150M 4.9M 145M 4% /run +/dev/sda1 ext4 31G 5.5G 24G 19% / +tmpfs tmpfs 749M 216K 748M 1% /dev/shm +tmpfs tmpfs 5.0M 4.0K 5.0M 1% /run/lock +tmpfs tmpfs 749M 0 749M 0% /sys/fs/cgroup +tmpfs tmpfs 150M 44K 150M 1% /run/user/1000 +tecmint@192.168.0.102:/home/tecmint fuse.sshfs 324G 55G 253G 18% /mnt/tecmint +``` + +#### 步骤 6:永久挂载远程文件系统 + +为了永久的挂载远程的文件系统,你需要修改一个叫 `/etc/fstab` 的文件。照着做,使用你最喜欢的编辑器打开文件。 + +``` +# vi /etc/fstab +$ sudo vi /etc/fstab 【基于 Debian/Ubuntu 的系统】 +``` + +移动到文件的底部并且添加下面的一行,保存文件并退出。下面条目表示使用默认的设置挂载远程的文件系统。 + +``` +sshfs#tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint fuse.sshfs defaults 0 0 +``` + +确保服务器之间允许 [SSH 无密码登录][2],这样系统重启之后才能自动挂载文件系统。 + +如果你的服务器配置为基于 SSH 密钥的认证方式,请加入如下行: + +``` +sshfs#tecmint@x.x.x.x:/home/tecmint/ /mnt/tecmint fuse.sshfs IdentityFile=~/.ssh/id_rsa defaults 0 0 +``` + +接下来,你需要更新 fstab 文件使修改生效。 + +``` +# mount -a +$ sudo mount -a 【基于 Debian/Ubuntu 的系统】 +``` + +#### 步骤 7:卸载远程的文件系统 + +为了卸载远程的文件系统,只需要发出以下的命令即可。 + +``` +# umount /mnt/tecmint +``` + +目前为止就这样了,如果你在挂载远程文件系统的时候遇到任何问题或者需要任何帮助,请通过评论联系我们,如果你感觉这篇文章非常有用,请分享给你的朋友们。 + + +------------------------------------------------------------------------------- + +via: http://www.tecmint.com/sshfs-mount-remote-linux-filesystem-directory-using-ssh/ + +作者:[Ravi Saive][a] +译者:[yangmingming](https://github.com/yangmingming) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://www.tecmint.com/author/admin/ +[1]: https://linux.cn/article-2324-1.html +[2]: https://linux.cn/article-5444-1.html diff --git a/published/20160812 What is copyleft.md b/published/201610/20160812 What is copyleft.md similarity index 100% rename from published/20160812 What is copyleft.md rename to published/201610/20160812 What is copyleft.md diff --git a/published/20160816 Deploying React with Zero Configuration.md b/published/201610/20160816 Deploying React with Zero Configuration.md similarity index 100% rename from published/20160816 Deploying React with Zero Configuration.md rename to published/201610/20160816 Deploying React with Zero Configuration.md diff --git a/published/201610/20160820 Building your first Atom plugin.md b/published/201610/20160820 Building your first Atom plugin.md new file mode 100644 index 0000000000..618c802086 --- /dev/null +++ b/published/201610/20160820 Building your first Atom plugin.md @@ -0,0 +1,754 @@ +制作你的第一个 Atom 文本编辑器插件 +=========== + +这篇教程将会教你怎么制作你的第一个 Atom 文本编辑器的插件。我们将会制作一个山寨版的 [Sourcerer][2],这是一个从 StackOverflow 查询并使用代码片段的插件。到教程结束时,你将会制作好一个将编程问题(用英语描述的)转换成获取自 StackOverflow 的代码片段的插件,像这样: + +![](https://cloud.githubusercontent.com/assets/6755555/17759382/836dd780-64ab-11e6-8f6a-329f66f01fd7.gif) + +#### 教程须知 + +Atom 文本编辑器是用 web 技术创造出来的。我们将完全使用 JavaScript 的 EcmaScript 6 规范来制作插件。你需要熟悉以下内容: + +- 使用命令行 +- JavaScript 编程 +- [Promises][14] +- [HTTP][16] + +#### 教程的仓库 + +你可以跟着教程一步一步走,或者看看 [放在 GitHub 上的仓库][3],这里有插件的源代码。这个仓库的历史提交记录包含了这里每一个标题。 + +### 开始 + +#### 安装 Atom + +根据 [Atom 官网][16] 的说明来下载 Atom。我们同时还要安装上 `apm`(Atom 包管理器的命令行工具)。你可以打开 Atom 并在应用菜单中导航到 `Atom > Install Shell Commands` 来安装。打开你的命令行终端,运行 `apm -v` 来检查 `apm` 是否已经正确安装好,安装成功的话打印出来的工具版本和相关环境信息应该是像这样的: + +``` +apm -v +> apm 1.9.2 +> npm 2.13.3 +> node 0.10.40 +> python 2.7.10 +> git 2.7.4 +``` + +#### 生成骨架代码 + +让我们使用 Atom 提供的一个实用工具创建一个新的 **package**(软件包)来开始这篇教程。 + +- 启动编辑器,按下 `Cmd+Shift+P`(MacOS)或者 `Ctrl+Shift+P`(Windows/Linux)来打开命令面板(Command Palette)。 +- 搜索“Package Generator: Generate Package”并点击列表中正确的条目,你会看到一个输入提示,输入软件包的名称:“sourcefetch”。 +- 按下回车键来生成这个骨架代码包,它会自动在 Atom 中打开。 + +如果你在侧边栏没有看到软件包的文件,依次按下 `Cmd+K` `Cmd+B`(MacOS)或者 `Ctrl+K` `Ctrl+B`(Windows/Linux)。 + +![](https://cloud.githubusercontent.com/assets/6755555/17759387/8387a354-64ab-11e6-97db-ea469f008bef.gif) + +> 命令面板(Command Palette)可以让你通过模糊搜索来找到并运行软件包。这是一个执行命令比较方便的途径,你不用去找导航菜单,也不用刻意去记快捷键。我们将会在整篇教程中使用这个方法。 + +#### 运行骨架代码包 + +在开始编程前让我们来试用一下这个骨架代码包。我们首先需要重启 Atom,这样它才可以识别我们新增的软件包。再次打开命令面板,执行 `Window: Reload` 命令。 + +重新加载当前窗口以确保 Atom 执行的是我们最新的源代码。每当需要测试我们对软件包的改动的时候,就需要运行这条命令。 + +通过导航到编辑器菜单的 `Packages > sourcefetch > Toggle` 或者在命令面板执行 `sourcefetch:toggle` 来运行软件包的 `toggle` 命令。你应该会看到屏幕的顶部出现了一个小黑窗。再次运行这条命令就可以隐藏它。 + +![](https://cloud.githubusercontent.com/assets/6755555/17759386/83799fc0-64ab-11e6-9f0c-0df9b1dbff8b.gif) + +#### “toggle”命令 + +打开 `lib/sourcefetch.js`,这个文件包含有软件包的逻辑和 `toggle` 命令的定义。 + +``` +toggle() { + console.log('Sourcefetch was toggled!'); + return ( + this.modalPanel.isVisible() ? + this.modalPanel.hide() : + this.modalPanel.show() + ); +} +``` + +`toggle` 是这个模块导出的一个函数。根据模态面板的可见性,它通过一个[三目运算符][17] 来调用 `show` 和 `hide` 方法。`modalPanel` 是 [Panel][18](一个由 Atom API 提供的 UI 元素) 的一个实例。我们需要在 `export default` 内部声明 `modalPanel` 才可以让我们通过一个实例变量 `this` 来访问它。 + +``` +this.subscriptions.add(atom.commands.add('atom-workspace', { + 'sourcefetch:toggle': () => this.toggle() +})); +``` + +上面的语句让 Atom 在用户运行 `sourcefetch:toggle` 的时候执行 `toggle` 方法。我们指定了一个 [匿名函数][19] `() => this.toggle()`,每次执行这条命令的时候都会执行这个函数。这是[事件驱动编程][20](一种常用的 JavaScript 模式)的一个范例。 + +#### Atom 命令 + +命令只是用户触发事件时使用的一些字符串标识符,它定义在软件包的命名空间内。我们已经用过的命令有: + +- `package-generator:generate-package` +- `Window:reload` +- `sourcefetch:toggle` + +软件包对应到命令,以执行代码来响应事件。 + +### 进行你的第一次代码更改 + +让我们来进行第一次代码更改——我们将通过改变 `toggle` 函数来实现逆转用户选中文本的功能。 + +#### 改变 “toggle” 函数 + +如下更改 `toggle` 函数。 + +``` +toggle() { + let editor + if (editor = atom.workspace.getActiveTextEditor()) { + let selection = editor.getSelectedText() + let reversed = selection.split('').reverse().join('') + editor.insertText(reversed) + } +} +``` + +#### 测试你的改动 + +- 通过在命令面板运行 `Window: Reload` 来重新加载 Atom。 +- 通过导航到 `File > New` 来创建一个新文件,随便写点什么并通过光标选中它。 +- 通过命令面板、Atom 菜单或者右击文本然后选中 `Toggle sourcefetch` 来运行 `sourcefetch:toggle` 命令。 + +更新后的命令将会改变选中文本的顺序: + +![](https://cloud.githubusercontent.com/assets/6755555/17759381/836acd60-64ab-11e6-84dc-4ef4471a361f.gif) + +在 [sourcefetch 教程仓库][4] 查看这一步的全部代码更改。 + +### Atom 编辑器 API + +我们添加的代码通过用 [TextEditor API][21] 来访问编辑器内的文本并进行操作。让我们来仔细看看。 + +``` +let editor +if (editor = atom.workspace.getActiveTextEditor()) { /* ... */ } +``` + +头两行代码获取了 [TextEditor][5] 实例的一个引用。变量的赋值和后面的代码被包在一个条件结构里,这是为了处理没有可用的编辑器实例的情况,例如,当用户在设置菜单中运行该命令时。 + +``` +let selection = editor.getSelectedText() +``` + +调用 `getSelectedText` 方法可以让我们访问到用户选中的文本。如果当前没有文本被选中,函数将返回一个空字符串。 + +``` +let reversed = selection.split('').reverse().join('') +editor.insertText(reversed) +``` + +我们选中的文本通过一个 [JavaScript 字符串方法][6] 来逆转。最后,我们调用 `insertText` 方法来将选中的文本替换为逆转后的文本副本。通过阅读 [Atom API 文档][5],你可以学到更多关于 TextEditor 的不同的方法。 + +### 浏览骨架代码 + +现在我们已经完成第一次代码更改了,让我们浏览骨架代码包的代码来深入了解一下 Atom 的软件包是怎样构成的。 + +#### 主文件 + +主文件是 Atom 软件包的入口文件。Atom 通过 `package.json` 里的条目设置来找到主文件的位置: + +``` +"main": "./lib/sourcefetch", +``` + +这个文件导出一个带有生命周期函数(Atom 在特定的事件发生时调用的处理函数)的对象。 + +- **activate** 会在 Atom 初次加载软件包的时候调用。这个函数用来初始化一些诸如软件包所需的用户界面元素的对象,以及订阅软件包命令的处理函数。 +- **deactivate** 会在软件包停用的时候调用,例如,当用户关闭或者刷新编辑器的时候。 +- **serialize** Atom 调用它在使用软件包的过程中保存软件包的当前状态。它的返回值会在 Atom 下一次加载软件包的时候作为一个参数传递给 `activate`。 + +我们将会重命名我们的软件包命令为 `fetch`,并移除一些我们不再需要的用户界面元素。按照如下更改主文件: + +``` +'use babel'; + +import { CompositeDisposable } from 'atom' + +export default { + + subscriptions: null, + + activate() { + this.subscriptions = new CompositeDisposable() + + this.subscriptions.add(atom.commands.add('atom-workspace', { + 'sourcefetch:fetch': () => this.fetch() + })) + }, + + deactivate() { + this.subscriptions.dispose() + }, + + fetch() { + let editor + if (editor = atom.workspace.getActiveTextEditor()) { + let selection = editor.getSelectedText() + selection = selection.split('').reverse().join('') + editor.insertText(selection) + } + } +}; +``` + +### “启用”命令 + +为了提升性能,Atom 软件包可以用时加载。我们可以让 Atom 在用户执行特定的命令的时候才加载我们的软件包。这些命令被称为 **启用命令**,它们在 `package.json` 中定义: + +``` +"activationCommands": { + "atom-workspace": "sourcefetch:toggle" +}, +``` + +更新一下这个条目设置,让 `fetch` 成为一个启用命令。 + +``` +"activationCommands": { + "atom-workspace": "sourcefetch:fetch" +}, +``` + +有一些软件包需要在 Atom 启动的时候被加载,例如那些改变 Atom 外观的软件包。在那样的情况下,`activationCommands` 会被完全忽略。 + +### “触发”命令 + +#### 菜单项 + +`menus` 目录下的 JSON 文件指定了哪些菜单项是为我们的软件包而建的。让我们看看 `menus/sourcefetch.json`: + +``` +"context-menu": { + "atom-text-editor": [ + { + "label": "Toggle sourcefetch", + "command": "sourcefetch:toggle" + } + ] +}, +``` + +这个 `context-menu` 对象可以让我们定义右击菜单的一些新条目。每一个条目都是通过一个显示在菜单的 `label` 属性和一个点击后执行的命令的 `command` 属性来定义的。 + +``` +"context-menu": { + "atom-text-editor": [ + { + "label": "Fetch code", + "command": "sourcefetch:fetch" + } + ] +}, +``` + +同一个文件中的这个 `menu` 对象用来定义插件的自定义应用菜单。我们如下重命名它的条目: + +``` +"menu": [ + { + "label": "Packages", + "submenu": [ + { + "label": "sourcefetch", + "submenu": [ + { + "label": "Fetch code", + "command": "sourcefetch:fetch" + } + ] + } + ] + } +] +``` + +#### 键盘快捷键 + +命令还可以通过键盘快捷键来触发。快捷键通过 `keymaps` 目录的 JSON 文件来定义: + +``` +{ + "atom-workspace": { + "ctrl-alt-o": "sourcefetch:toggle" + } +} +``` + +以上代码可以让用户通过 `Ctrl+Alt+O`(Windows/Linux) 或 `Cmd+Alt+O`(MacOS) 来触发 `toggle` 命令。 + +重命名引用的命令为 `fetch`: + +``` +"ctrl-alt-o": "sourcefetch:fetch" +``` + +通过执行 `Window: Reload` 命令来重启 Atom。你应该会看到 Atom 的右击菜单更新了,并且逆转文本的功能应该还可以像之前一样使用。 + +在 [sourcefetch 教程仓库][7] 查看这一步所有的代码更改。 + +### 使用 NodeJS 模块 + +现在我们已经完成了第一次代码更改并且了解了 Atom 软件包的结构,让我们介绍一下 [Node 包管理器(npm)][22] 中的第一个依赖项模块。我们将使用 **request** 模块发起 HTTP 请求来下载网站的 HTML 文件。稍后将会用到这个功能来扒 StackOverflow 的页面。 + +#### 安装依赖 + +打开你的命令行工具,切换到你的软件包的根目录并运行: + +``` +npm install --save request@2.73.0 +apm install +``` + +这两条命令将 `request` 模块添加到我们软件包的依赖列表并将模块安装到 `node_modules` 目录。你应该会在 `package.json` 看到一个新条目。`@` 符号的作用是让 npm 安装我们这篇教程需要用到的特定版本的模块。运行 `apm install` 是为了让 Atom 知道使用我们新安装的模块。 + +``` +"dependencies": { + "request": "^2.73.0" +} +``` + +#### 下载 HTML 并将记录打印在开发者控制台 + +通过在 `lib/sourcefetch.js` 的顶部添加一条引用语句引入 `request` 模块到我们的主文件: + +``` +import { CompositeDisposable } from 'atom' +import request from 'request' +``` + +现在,在 `fetch` 函数下面添加一个新函数 `download` 作为模块的导出项: + +``` +export default { + + /* subscriptions, activate(), deactivate() */ + + fetch() { + ... + }, + + download(url) { + request(url, (error, response, body) => { + if (!error && response.statusCode == 200) { + console.log(body) + } + }) + } +} +``` + +这个函数用 `request` 模块来下载一个页面的内容并将记录输出到控制台。当 HTTP 请求完成之后,我们的[回调函数][23]会将响应体作为参数来被调用。 + +最后一步是更新 `fetch` 函数以调用 `download` 函数: + +``` +fetch() { + let editor + if (editor = atom.workspace.getActiveTextEditor()) { + let selection = editor.getSelectedText() + this.download(selection) + } +}, +``` + +`fetch` 函数现在的功能是将 selection 当作一个 URL 传递给 `download` 函数,而不再是逆转选中的文本了。让我们来看看这次的更改: + +- 通过执行 `Window: Reload` 命令来重新加载 Atom。 +- 打开开发者工具。为此,导航到菜单中的 `View > Developer > Toggle Developer Tools`。 +- 新建一个文件,导航到 `File > New`。 +- 输入一个 URL 并选中它,例如:`http://www.atom.io`。 +- 用上述的任意一种方法执行我们软件包的命令: + +![](https://cloud.githubusercontent.com/assets/6755555/17759384/836ea91c-64ab-11e6-8fbe-7d15fb482c6d.gif) + +> **开发者工具**让 Atom 软件包的调试更轻松。每个 `console.log` 语句都可以将信息打印到交互控制台,你还可以使用 `Elements` 选项卡来浏览整个应用的可视化结构——即 HTML 的[文本对象模型(DOM)][8]。 + +在 [sourcefetch 教程仓库][9] 查看这一步所有的代码更改。 + +### 用 Promises 来将下载好的 HTML 插入到编辑器中 + +理想情况下,我们希望 `download` 函数可以将 HTML 作为一个字符串来返回,而不仅仅是将页面的内容打印到控制台。然而,返回文本内容是无法实现的,因为我们要在回调函数里面访问内容而不是在 `download` 函数那里。 + +我们会通过返回一个 [Promise][24] 来解决这个问题,而不再是返回一个值。让我们改动 `download` 函数来返回一个 Promise: + +``` +download(url) { + return new Promise((resolve, reject) => { + request(url, (error, response, body) => { + if (!error && response.statusCode == 200) { + resolve(body) + } else { + reject({ + reason: 'Unable to download page' + }) + } + }) + }) +} +``` + +Promises 允许我们通过将异步逻辑封装在一个提供两个回调方法的函数里来返回获得的值(`resolve` 用来处理请求成功的返回值,`reject` 用来向使用者报错)。如果请求返回了错误我们就调用 `reject`,否则就用 `resolve` 来处理 HTML。 + +让我们更改 `fetch` 函数来使用 `download` 返回的 Promise: + +``` +fetch() { + let editor + if (editor = atom.workspace.getActiveTextEditor()) { + let selection = editor.getSelectedText() + this.download(selection).then((html) => { + editor.insertText(html) + }).catch((error) => { + atom.notifications.addWarning(error.reason) + }) + } +}, +``` + +在我们新版的 `fetch` 函数里,我们通过在 `download` 返回的 Promise 调用 `then` 方法来对 HTML 进行操作。这会将 HTML 插入到编辑器中。我们同样会通过调用 `catch` 方法来接收并处理所有的错误。我们通过用 [Atom Notification API][25] 来显示警告的形式来处理错误。 + +看看发生了什么变化。重新加载 Atom 并在一个选中的 URL 上执行软件包命令: + +![](https://cloud.githubusercontent.com/assets/6755555/17759379/8357bb08-64ab-11e6-9bd2-6f63b8f50dcc.gif) + +如果这个 URL 是无效的,一个警告通知将会弹出来: + +![](https://cloud.githubusercontent.com/assets/6755555/17759378/833ab09e-64ab-11e6-9896-2f874b0fdc8a.gif) + +在 [sourcefetch 教程仓库][10] 查看这一步所有的代码更改。 + +#### 编写一个爬虫来提取 StackOverflow 页面的代码片段 + +下一步涉及用我们前面扒到的 StackOverflow 的页面的 HTML 来提取代码片段。我们尤其关注那些来自采纳答案(提问者选择的一个正确答案)的代码。我们可以在假设这类答案都是相关且正确的前提下大大简化我们这个软件包的实现。 + +#### 使用 jQuery 和 Chrome 开发者工具来构建查询 + +这一部分假设你使用的是 [Chrome][26] 浏览器。你接下来可以使用其它浏览器,但是提示可能会不一样。 + +让我们先看看一张典型的包含采纳答案和代码片段的 StackOverflow 页面。我们将会使用 Chrome 开发者工具来浏览 HTML: + +- 打开 Chrome 并跳到任意一个带有采纳答案和代码的 StackOverflow 页面,比如像这个用 Python 写的 [hello world][27] 的例子或者这个关于 [用 `C` 来读取文本内容的问题][28]。 +- 滚动窗口到采纳答案的位置并选中一部分代码。 +- 右击选中文本并选择 `检查`。 +- 使用元素侦察器来检查代码片段在 HTML 中的位置。 + +注意文本结构应该是这样的: + +``` +
+ ... + ... +
+        
+          ...snippet elements...
+        
+      
+ ... + ... +
+``` + +- 采纳的答案通过一个 class 为 `accepted-answer` 的 `div` 来表示 +- 代码块位于 `pre` 元素的内部 +- 呈现代码片段的元素就是里面那一对 `code` 标签 + +![](https://cloud.githubusercontent.com/assets/6755555/17759380/83689a90-64ab-11e6-89b2-7172c03baae7.gif) + +现在让我们写一些 `jQuery` 代码来提取代码片段: + +- 在开发者工具那里点击 **Console** 选项卡来访问 Javascript 控制台。 +- 在控制台中输入 `$('div.accepted-answer pre code').text()` 并按下回车键。 + +你应该会看到控制台中打印出采纳答案的代码片段。我们刚刚运行的代码使用了一个 jQuery 提供的特别的 `$` 函数。`$` 接收要选择的**查询字符串**并返回网站中的某些 HTML 元素。让我们通过思考几个查询案例看看这段代码的工作原理: + +``` +$('div.accepted-answer') +> [
] +``` + +上面的查询会匹配所有 class 为 `accepted-answer` 的 `
` 元素,在我们的案例中只有一个 div。 + +``` +$('div.accepted-answer pre code') +> [...] +``` + +在前面的基础上改造了一下,这个查询会匹配所有在之前匹配的 `
` 内部的 `
` 元素内部的 `` 元素。
+
+```
+$('div.accepted-answer pre code').text()
+> "print("Hello World!")"
+```
+
+`text` 函数提取并连接原本将由上一个查询返回的元素列表中的所有文本。这也从代码中去除了用来使语法高亮的元素。
+
+### 介绍 Cheerio
+
+我们的下一步涉及使用我们创建好的查询结合 [Cheerio][29](一个服务器端实现的 jQuery)来实现扒页面的功能。
+
+#### 安装 Cheerio
+
+打开你的命令行工具,切换到你的软件包的根目录并执行:
+
+```
+npm install --save cheerio@0.20.0
+apm install
+```
+
+#### 实现扒页面的功能
+
+在 `lib/sourcefetch.js` 为 `cheerio` 添加一条引用语句:
+
+```
+import { CompositeDisposable } from 'atom'
+import request from 'request'
+import cheerio from 'cheerio'
+```
+
+现在创建一个新函数 `scrape`,它用来提取 StackOverflow HTML 里面的代码片段:
+
+```
+fetch() {
+  ...
+},
+
+scrape(html) {
+  $ = cheerio.load(html)
+  return $('div.accepted-answer pre code').text()
+},
+
+download(url) {
+  ...
+}
+```
+
+最后,让我们更改 `fetch` 函数以传递下载好的 HTML 给 `scrape` 而不是将其插入到编辑器:
+
+```
+fetch() {
+  let editor
+  let self = this
+
+  if (editor = atom.workspace.getActiveTextEditor()) {
+    let selection = editor.getSelectedText()
+    this.download(selection).then((html) => {
+      let answer = self.scrape(html)
+      if (answer === '') {
+        atom.notifications.addWarning('No answer found :(')
+      } else {
+        editor.insertText(answer)
+      }
+    }).catch((error) => {
+      console.log(error)
+      atom.notifications.addWarning(error.reason)
+    })
+  }
+},
+```
+
+我们扒取页面的功能仅仅用两行代码就实现了,因为 cheerio 已经替我们做好了所有的工作!我们通过调用 `load` 方法加载 HTML 字符串来创建一个 `$` 函数,然后用这个函数来执行 jQuery 语句并返回结果。你可以在官方 [开发者文档][30] 查看完整的 `Cheerio API`。
+
+### 测试更新后的软件包
+
+重新加载 Atom 并在一个选中的 StackOverflow URL 上运行 `soucefetch:fetch` 以查看到目前为止的进度。
+
+如果我们在一个有采纳答案的页面上运行这条命令,代码片段将会被插入到编辑器中:
+
+![](https://cloud.githubusercontent.com/assets/6755555/17759383/836e26b8-64ab-11e6-9f16-321903470ce2.gif)
+
+如果我们在一个没有采纳答案的页面上运行这条命令,将会弹出一个警告通知:
+
+![](https://cloud.githubusercontent.com/assets/6755555/17759388/838d3864-64ab-11e6-8091-b4d15bd56025.gif)
+
+我们最新的 `fetch` 函数给我们提供了一个 StackOverflow 页面的代码片段而不再是整个 HTML 内容。要注意我们更新的 `fetch` 函数会检查有没有答案并显示通知以提醒用户。
+
+在 [sourcefetch 教程仓库][11] 查看这一步所有的代码更改。
+
+### 实现用来查找相关的 StackOverflow URL 的谷歌搜索功能
+
+现在我们已经将 StackOverflow 的 URL 转化为代码片段了,让我们来实现最后一个函数——`search`,它应该要返回一个相关的 URL 并附加一些像“hello world”或者“快速排序”这样的描述。我们会通过一个非官方的 `google` npm 模块来使用谷歌搜索功能,这样可以让我们以编程的方式来搜索。
+
+#### 安装这个 Google npm 模块
+
+通过在软件包的根目录打开命令行工具并执行命令来安装 `google` 模块:
+
+```
+npm install --save google@2.0.0
+apm install
+```
+
+#### 引入并配置模块
+
+在 `lib/sourcefetch.js` 的顶部为 `google` 模块添加一条引用语句:
+
+```
+import google from "google"
+```
+
+我们将配置一下 `google` 以限制搜索期间返回的结果数。将下面这行代码添加到引用语句下面以限制搜索返回最热门的那个结果。
+
+```
+google.resultsPerPage = 1
+```
+
+#### 实现 search 函数
+
+接下来让我们来实现我们的 `search` 函数:
+
+```
+fetch() {
+  ...
+},
+
+search(query, language) {
+  return new Promise((resolve, reject) => {
+    let searchString = `${query} in ${language} site:stackoverflow.com`
+
+    google(searchString, (err, res) => {
+      if (err) {
+        reject({
+          reason: 'A search error has occured :('
+        })
+      } else if (res.links.length === 0) {
+        reject({
+          reason: 'No results found :('
+        })
+      } else {
+        resolve(res.links[0].href)
+      }
+    })
+  })
+},
+
+scrape() {
+  ...
+}
+```
+
+以上代码通过谷歌来搜索一个和指定的关键词以及编程语言相关的 StackOverflow 页面,并返回一个最热门的 URL。让我们看看这是怎样来实现的:
+
+```
+let searchString = `${query} in ${language} site:stackoverflow.com`
+```
+
+我们使用用户输入的查询和当前所选的语言来构造搜索字符串。比方说,当用户在写 Python 的时候输入“hello world”,查询语句就会变成 `hello world in python site:stackoverflow.com`。字符串的最后一部分是谷歌搜索提供的一个过滤器,它让我们可以将搜索结果的来源限制为 StackOverflow。
+
+```
+google(searchString, (err, res) => {
+  if (err) {
+    reject({
+      reason: 'A search error has occured :('
+    })
+  } else if (res.links.length === 0) {
+    reject({
+      reason: 'No results found :('
+    })
+  } else {
+    resolve(res.links[0].href)
+  }
+})
+```
+
+我们将 `google` 方法放在一个 `Promise` 里面,这样我们可以异步地返回我们的 URL。我们会传递由 `google` 返回的所有错误并且会在没有可用的搜索结果的时候返回一个错误。否则我们将通过 `resolve` 来解析最热门结果的 URL。
+
+### 更新 fetch 来使用 search
+
+我们的最后一步是更新 `fetch` 函数来使用 `search` 函数:
+
+```
+fetch() {
+  let editor
+  let self = this
+
+  if (editor = atom.workspace.getActiveTextEditor()) {
+    let query = editor.getSelectedText()
+    let language = editor.getGrammar().name
+
+    self.search(query, language).then((url) => {
+      atom.notifications.addSuccess('Found google results!')
+      return self.download(url)
+    }).then((html) => {
+      let answer = self.scrape(html)
+      if (answer === '') {
+        atom.notifications.addWarning('No answer found :(')
+      } else {
+        atom.notifications.addSuccess('Found snippet!')
+        editor.insertText(answer)
+      }
+    }).catch((error) => {
+      atom.notifications.addWarning(error.reason)
+    })
+  }
+}
+```
+
+让我们看看发生了什么变化:
+
+- 我们选中的文本现在变成了用户输入的 `query`
+- 我们使用 [TextEditor API][21] 来获取当前编辑器选项卡使用的 `language`
+- 我们调用 `search` 方法来获取一个 URL,然后通过在得到的 Promise 上调用 `then` 方法来访问这个 URL
+
+我们不在 `download` 返回的 Promise 上调用 `then` 方法,而是在前面 `search` 方法本身链式调用的另一个 `then` 方法返回的 Promise 上面接着调用 `then` 方法。这样可以帮助我们避免[回调地狱][31]  
+
+在 [sourcefetch 教程仓库][12] 查看这一步所有的代码更改。
+
+### 测试最终的插件
+
+大功告成了!重新加载 Atom,对一个“问题描述”运行软件包的命令来看看我们最终的插件是否工作,不要忘了在编辑器右下角选择一种语言。
+
+![](https://cloud.githubusercontent.com/assets/6755555/17759382/836dd780-64ab-11e6-8f6a-329f66f01fd7.gif)
+
+### 下一步
+
+现在你知道怎么去 “hack” Atom 的基本原理了,通过 [分叉 sourcefetch 这个仓库并添加你的特性][13] 来随心所欲地实践你所学到的知识。
+
+--------------------------------------------------------------------------------
+
+via: https://github.com/blog/2231-building-your-first-atom-plugin
+
+作者:[NickTikhonov][a]
+译者:[OneNewLife](https://github.com/OneNewLife)
+校对:[wxy](https://github.com/wxy)
+
+本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
+
+[a]: https://github.com/NickTikhonov
+[1]: https://education.github.com/experts
+[2]: https://github.com/NickTikhonov/sourcerer
+[3]: https://github.com/NickTikhonov/sourcefetch-guide
+[4]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/89e174ab6ec6e270938338b34905f75bb74dbede
+[5]: https://atom.io/docs/api/latest/TextEditor
+[6]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
+[7]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/aa3ec5585b0aa049393351a30be14590df09c29a
+[8]: https://www.wikipedia.com/en/Document_Object_Model
+[9]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/85992043e57c802ca71ff6e8a4f9c477fbfd13db
+[10]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/896d160dca711f4a53ff5b182018b39cf78d2774
+[11]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/039a1e1e976d029f7d6b061b4c0dac3eb4a3b5d2
+[12]: https://github.com/NickTikhonov/sourcefetch-tutorial/commit/aa9d0b5fc4811a70292869730e0f60ddf0bcf2aa
+[13]: https://github.com/NickTikhonov/sourcefetch-tutorial
+[14]: https://developers.google.com/web/fundamentals/getting-started/primers/promises
+[15]: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
+[16]: https://atom.io/
+[17]: https://en.wikipedia.org/wiki/%3F:
+[18]: https://atom.io/docs/api/v1.9.4/Panel
+[19]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
+[20]: https://en.wikipedia.org/wiki/Event-driven_programming
+[21]: https://atom.io/docs/api/v1.11.1/TextEditor
+[22]: https://www.npmjs.com/
+[23]: http://recurial.com/programming/understanding-callback-functions-in-javascript/
+[24]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
+[25]: https://atom.io/docs/api/v1.11.1/NotificationManager
+[26]: https://www.google.com/chrome/
+[27]: http://stackoverflow.com/questions/1077347/hello-world-in-python
+[28]: http://stackoverflow.com/questions/3463426/in-c-how-should-i-read-a-text-file-and-print-all-strings
+[29]: https://www.npmjs.com/package/cheerio
+[30]: https://github.com/cheeriojs/cheerio
+[31]: http://callbackhell.com/
diff --git a/translated/tech/20160823 The infrastructure behind Twitter -  efficiency and optimization.md b/published/201610/20160823 The infrastructure behind Twitter -  efficiency and optimization.md
similarity index 51%
rename from translated/tech/20160823 The infrastructure behind Twitter -  efficiency and optimization.md
rename to published/201610/20160823 The infrastructure behind Twitter -  efficiency and optimization.md
index 11d8040a4b..f47d262379 100644
--- a/translated/tech/20160823 The infrastructure behind Twitter -  efficiency and optimization.md	
+++ b/published/201610/20160823 The infrastructure behind Twitter -  efficiency and optimization.md	
@@ -1,26 +1,26 @@
-Twitter背后的基础设施:效率与优化
+揭秘 Twitter 背后的基础设施:效率与优化篇
 ===========
 
-过去我们曾经发布过一些关于 [Finagle](https://twitter.github.io/finagle/) , [Manhattan](https://blog.twitter.com/2014/manhattan-our-real-time-multi-tenant-distributed-database-for-twitter-scale) 这些项目的文章,还写过一些针对大型事件活动的架构优化的文章,例如天空之城,超级碗, 2014 世界杯,全球新年夜庆祝活动等。在这篇基础设施系列文章中,我主要聚焦于 Twitter 的一些关键设施和组件。我也会写一些我们在系统的扩展性,可靠性,效率性方面的做过的改进,例如我们基础设施的历史,遇到过的挑战,学到的教训,做过的升级,以及我们现在前进的方向等等。
+过去我们曾经发布过一些关于 [Finagle](https://twitter.github.io/finagle/) 、[Manhattan](https://blog.twitter.com/2014/manhattan-our-real-time-multi-tenant-distributed-database-for-twitter-scale) 这些项目的文章,还写过一些针对大型事件活动的[架构优化](https://blog.twitter.com/2013/new-tweets-per-second-record-and-how)的文章,例如天空之城、超级碗、2014 世界杯、全球新年夜庆祝活动等。在这篇基础设施系列文章中,我主要聚焦于 Twitter 的一些关键设施和组件。我也会写一些我们在系统的扩展性、可靠性、效率方面的做过的改进,例如我们基础设施的历史,遇到过的挑战,学到的教训,做过的升级,以及我们现在前进的方向等等。
 
-> 天空之城:2013年8月2日,宫崎骏的《天空之城》在NTV迎来其第14次电视重播,剧情发展到高潮之时,Twitter的TPS(Tweets Per Second)也被推上了新的高度——143,199 TPS,是平均值的25倍,这个记录保持至今 -- 译者注。
+> 天空之城:2013 年 8 月 2 日,宫崎骏的《天空之城(Castle in the Sky)》在 NTV 迎来其第 14 次电视重播,剧情发展到高潮之时,Twitter 的 TPS(Tweets Per Second)也被推上了新的高度——143,199 TPS,是平均值的 25 倍,这个记录保持至今。-- LCTT 译注
 
 ### 数据中心的效率优化
 
 #### 历史
 
-当前Twitter硬件和数据中心的规模已经超过大多数公司。但达到这样的规模不是一蹴而就的,系统是随着软硬件的升级优化一步步成熟起来的,过程中我们也曾经犯过很多错误。
+当前 Twitter 硬件和数据中心的规模已经超过大多数公司。但达到这样的规模不是一蹴而就的,系统是随着软硬件的升级优化一步步成熟起来的,过程中我们也曾经犯过很多错误。
 
-有个一时期我们的系统故障不断。软件问题,硬件问题,甚至底层设备问题不断爆发,常常导致系统运营中断。随着 Twitter 在客户、服务、媒体上的影响力不断扩大,构建一个高效、可靠的系统来提供服务成为我们的战略诉求。
+有个一时期我们的系统故障不断。软件问题、硬件问题,甚至底层设备问题不断爆发,常常导致系统运营中断。出现故障的地方存在于各个方面,必须综合考虑才能确定其风险和受到影响的服务。随着 Twitter 在客户、服务、媒体上的影响力不断扩大,构建一个高效、可靠的系统来提供服务成为我们的战略诉求。
 
-> Twitter系统故障的界面被称为失败鲸(Fail Whale),如下图 -- 译者注
-![Fail Whale](https://upload.wikimedia.org/wikipedia/en/d/de/Failwhale.png)
+> Twitter系统故障的界面被称为失败鲸(Fail Whale),如下图 -- LCTT 译注
+> ![Fail Whale](https://upload.wikimedia.org/wikipedia/en/d/de/Failwhale.png)
 
 #### 挑战
 
-一开始,我们的软件是直接安装在服务器,这意味着软件可靠性依赖硬件,电源、网络以及其他的环境因素都是威胁。这种情况下,如果要增加容错能力,就需要统筹考虑物理设备和在上面运行的服务。
+一开始,我们的软件是直接安装在服务器,这意味着软件可靠性依赖硬件,电源、网络以及其他的环境因素都是威胁。这种情况下,如果要增加容错能力,就需要统筹考虑这些互不关联的物理设备因素及在上面运行的服务。
 
-最早采购数据中心方案的时候,我们都还是菜鸟,对于站点选择、运营和设计都非常不专业。我们先直接租用主机,业务增长后我们改用主机托管。早期遇到的问题主要是因为设备故障、数据中心设计问题、维护问题以及人为操作失误。我们也在持续迭代我们的硬件设计,从而增强硬件和数据中心的容错性。
+最早采购数据中心方案的时候,我们都还是菜鸟,对于站点选择、运营和设计都非常不专业。我们先直接托管主机,业务增长后我们改用租赁机房。早期遇到的问题主要是因为设备故障、数据中心设计问题、维护问题以及人为操作失误。我们也在持续迭代我们的硬件设计,从而增强硬件和数据中心的容错性。
 
 服务中断的原因有很多,其中硬件故障常发生在服务器、机架交换机、核心交换机这地方。举一个我们曾经犯过的错误,硬件团队最初在设计服务器的时候,认为双路电源对减少供电问题的意义不大 -- 他们真的就移除了一块电源。然而数据中心一般给机架提供两路供电来提高冗余性,防止电网故障传导到服务器,而这需要两块电源。最终我们不得不在机架上增加了一个 ATS 单元(AC transfer switch 交流切换开关)来接入第二路供电。
 
@@ -28,9 +28,9 @@ Twitter背后的基础设施:效率与优化
 
 #### 我们学到的教训以及技术的升级、迁移和选型
 
-我们学到的第一个教训就是要先建模,将可能出故障的地方(例如建筑的供电和冷却系统、硬件、光线网络等)和运行在上面的服务之间的依赖关系弄清楚,这样才能更好地分析,从而优化设计提升容错能力。
+我们学到的第一个教训就是要先建模,将可能出故障的地方(例如建筑的供电和冷却系统、硬件、光纤网络等)和运行在上面的服务之间的依赖关系弄清楚,这样才能更好地分析,从而优化设计提升容错能力。
 
-我们增加了更多的数据中心提升地理容灾能力,减少自然灾害的影响。而且这种站点隔离也降低了软件的风险,减少了例如软件部署升级和系统故障的风险。这种多活的数据中心架构提供了代码灰度发布的能力,减少代码首次上线时候的影响。
+我们增加了更多的数据中心提升地理容灾能力,减少自然灾害的影响。而且这种站点隔离也降低了软件的风险,减少了例如软件部署升级和系统故障的风险。这种多活的数据中心架构提供了代码灰度发布(staged code deployment)的能力,减少代码首次上线时候的影响。
 
 我们设计新硬件使之能够在更高温度下正常运行,数据中心的能源效率因此有所提升。
 
@@ -46,9 +46,9 @@ Twitter背后的基础设施:效率与优化
 
 Twitter 是一个很大的公司,它对硬件的要求对任何团队来说都是一个不小的挑战。为了满足整个公司的需求,我们的首要工作是能检测并保证购买的硬件的品质。团队重点关注的是性能和可靠性这两部分。对于硬件我们会做系统性的测试来保证其性能可预测,保证尽量不引入新的问题。
 
-随着我们一些关键组件的负荷越来越大(如 Mesos , Hadoop , Manhattan , MySQL 等),市面上的产品已经无法满足我们的需求。同时供应商提供的一些高级服务器功能,例如 Raid 管理或者电源热切换等,可靠性提升很小,反而会拖累系统性能而且价格高昂,例如一些 Raid 控制器价格高达系统总报价的三分之一,还拖累了 SSD 的性能。
+随着我们一些关键组件的负荷越来越大(如 Mesos、Hadoop、Manhattan、MySQL 等),市面上的产品已经无法满足我们的需求。同时供应商提供的一些高级服务器功能,例如 Raid 管理或者电源热切换等,可靠性提升很小,反而会拖累系统性能而且价格高昂,例如一些 Raid 控制器价格高达系统总报价的三分之一,还拖累了 SSD 的性能。
 
-那时,我们也是 MySQL 数据库的一个大型用户。SAS(Serial Attached SCSI,串行连接 SCSI )设备的供应和性能都有很大的问题。我们大量使用 1 u 的服务器,它的驱动器和回写缓存一起也只能支撑每秒 2000 次顺序 IO。为了获得更好的效果,我们只得不断增加 CPU 核心数并加强磁盘能力。我们那时候找不到更节省成本的方案。
+那时,我们也是 MySQL 数据库的一个大型用户。SAS(Serial Attached SCSI,串行连接 SCSI )设备的供应和性能都有很大的问题。我们大量使用 1U 规格的服务器,它的磁盘和回写缓存一起也只能支撑每秒 2000 次的顺序 IO。为了获得更好的效果,我们只得不断增加 CPU 核心数并加强磁盘能力。我们那时候找不到更节省成本的方案。
 
 后来随着我们对硬件需求越来越大,我们可以成立了一个硬件团队,从而自己来设计更便宜更高效的硬件。
 
@@ -58,62 +58,59 @@ Twitter 是一个很大的公司,它对硬件的要求对任何团队来说都
 
 - 2012 - 采用 SSD 作为我们 MySQL 和 Key-Value 数据库的主要存储。
 - 2013 - 我们开发了第一个定制版 Hadoop 工作站,它现在是我们主要的大容量存储方案。
-- 2013 - 我们定制的解决方案应用在 Mesos 、 TFE( Twitter Front-End )以及缓存设备上。
+- 2013 - 我们定制的解决方案应用在 Mesos、TFE( Twitter Front-End )以及缓存设备上。
 - 2014 - 我们定制的 SSD Key-Value 服务器完成开发。
 - 2015 - 我们定制的数据库解决方案完成开发。
 - 2016 - 我们开发了一个 GPU 系统来做模糊推理和训练机器学习。
 
 #### 学到的教训
 
-硬件团队的工作本质是通过做取舍来优化TCO(总体拥有成本),最终达到达到降低 CAPEX(资本支出)和 OPEX(运营支出)的目的。概括来说,服务器降成本就是:
+硬件团队的工作本质是通过做取舍来优化 TCO(总体拥有成本),最终达到达到降低 CAPEX(资本支出)和 OPEX(运营支出)的目的。概括来说,服务器降成本就是:
 
 1. 删除无用的功能和组件
 2. 提升利用率
 
-Twitter 的设备总体来说有这四大类:存储设备、计算设备、数据库和 GPU 。 Twitter 对每一类都定义了详细的需求,让硬件工程师更针对性地设计产品,从而优化掉那些用不到或者极少用的冗余部分。例如,我们的存储设备就专门为 Hadoop 优化,设备的购买和运营成本相比于 OEM 产品降低了 20% 。同时,这样做减法还提高了设备的性能和可靠性。同样的,对于计算设备,硬件工程师们也通过移除无用的特性获得了效率提升。
+Twitter 的设备总体来说有这四大类:存储设备、计算设备、数据库和 GPU 。 Twitter 对每一类都定义了详细的需求,让硬件工程师更针对性地设计产品,从而优化掉那些用不到或者极少用的冗余部分。例如,我们的存储设备就专门为 Hadoop 优化过,设备的购买和运营成本相比于 OEM 产品降低了 20% 。同时,这样做减法还提高了设备的性能和可靠性。同样的,对于计算设备,硬件工程师们也通过移除无用的特性获得了效率提升。
 
 一个服务器可以移除的组件总是有限的,我们很快就把能移除的都扔掉了。于是我们想出了其他办法,例如在存储设备里,我们认为降低成本最好的办法是用一个节点替换多个节点,并通过 Aurora/Mesos 来管理任务负载。这就是我们现在正在做的东西。
 
-对于这个我们自己新设计的服务器,首先要通过一系列的标准测试,然后会再做一系列负载测试,我们的目标是一台新设备至少能替换两台旧设备。大多数的提升都比较简单,例如增加 CPU 的进程数,同时我们的测试也比较出新 CPU 的 单线程能力提高了 20~50% ,对应能耗降低了 25% ,这都是我们测试环节需要做的工作。
+对于这个我们自己新设计的服务器,首先要通过一系列的标准测试,然后会再做一系列负载测试,我们的目标是一台新设备至少能替换两台旧设备。最大的性能提升来自增加 CPU 的线程数,我们的测试结果表示新 CPU 的 单线程能力提高了 20~50% 。同时由于整个服务器的线程数增加,我们看到单线程能效提升了 25%。
 
-这个新设备首次部署的时候,监控发现新设备只能替换 1.5 台旧设备,这比我们的目标低了很多。对性能数据检查后发现,我们之前新硬件的部分指标是错的,而这正是我们在做性能测试需要发现的问题。
+这个新设备首次部署的时候,监控发现新设备只能替换 1.5 台旧设备,这比我们的目标低了很多。对性能数据检查后发现,我们之前对负载特性的一些假定是有问题的,而这正是我们在做性能测试需要发现的问题。
 
-对此我们硬件团队开发了一个模型,用来预测在不同的硬件配置下当前 Aurora 任务的打包效率。这个模型正确的预测了新旧硬件的性能比例。模型还指出了我们一开始没有考虑到的存储需求,并因此建议我们增加 CPU 核心数。另外,它还预测,如果我们修改内存的配置,那系统的性能还会有较大提高。
+对此我们硬件团队开发了一个模型,用来预测在不同的硬件配置下当前 Aurora 任务的填充效率。这个模型正确的预测了新旧硬件的性能比例。模型还指出了我们一开始没有考虑到的存储需求,并因此建议我们增加 CPU 核心数。另外,它还预测,如果我们修改内存的配置,那系统的性能还会有较大提高。
 
-硬件配置的改变都需要花时间去操作,所以我们的硬件工程师们就首先找出几个关键痛点。例如我们和站点工程团队一起调整任务顺序来降低存储需求,这种修改很简单也很有效,新设备可以代替 1.85 个旧设备了。
+硬件配置的改变都需要花时间去操作,所以我们的硬件工程师们就首先找出几个关键痛点。例如我们和 SRE(Site Reliability Engineer,网站可靠性工程师)团队一起调整任务顺序来降低存储需求,这种修改很简单也很有效,新设备可以代替 1.85 个旧设备了。
 
-为了更好的优化效率,我们对新硬件的配置做了修改,扩大了内存和磁盘容量就将 CPU 利用率提高了20% ,而这只增加了非常小的成本。同时我们的硬件工程师也和生产的伙伴一起优化发货顺序来降低货运成本。后续的观察发现我们的自己的新设备实际上可以代替 2.4 台旧设备,这个超出了预定的目标。
+为了更好的优化效率,我们对新硬件的配置做了修改,只是扩大了内存和磁盘容量就将 CPU 利用率提高了20% ,而这只增加了非常小的成本。同时我们的硬件工程师也和合作生产厂商一起为那些服务器的最初出货调整了物料清单。后续的观察发现我们的自己的新设备实际上可以代替 2.4 台旧设备,这个超出了预定的目标。
 
 ### 从裸设备迁移到 mesos 集群
 
-直到2012年为止,软件团队在 Twitter 开通一个新服务还需要自己操心硬件:配置硬件的规格需求,研究机架尺寸,开发部署脚本以及处理硬件故障。同时,系统中没有所谓的“服务发现”机制,当一个服务需要调用一个另一个服务时候,需要读取一个 YAML 配置文件,这个配置文件中有目标服务对应的主机 IP 和端口信息(端口信息是由一个公共 wiki 页面维护的)。随着硬件的替换和更新,YAML 配置文件里的内容也会不断的编辑更新。每次更新都需要花几个小时甚至几天来重启在各个服务,从而将新配置刷新到所有服务的缓存里,所以我们只能尽量一次增加多个配置并且按次序分别重启。我们经常遇到重启过程中 cache 不一致导致的问题,因为有的主机在使用旧的配置有的主机在用新的。有时候一台主机的异常(例如它正在重启)会导致整个站点都无法正常工作。 
+直到 2012 年为止,软件团队在 Twitter 开通一个新服务还需要自己操心硬件:配置硬件的规格需求,研究机架尺寸,开发部署脚本以及处理硬件故障。同时,系统中没有所谓的“服务发现”机制,当一个服务需要调用一个另一个服务时候,需要读取一个 YAML 配置文件,这个配置文件中有目标服务对应的主机 IP 和端口信息(预留的端口信息是由一个公共 wiki 页面维护的)。随着硬件的替换和更新,YAML 配置文件里的内容也会不断的编辑更新。在缓存层做修改意味着我们可以按小时或按天做很多次部署,每次添加少量主机并按阶段部署。我们经常遇到在部署过程中 cache 不一致导致的问题,因为有的主机在使用旧的配置有的主机在用新的。有时候一台主机的异常(例如在部署过程中它临时宕机了)会导致整个站点都无法正常工作。 
 
-在 2012/2013 年的时候,Twitter 开始尝试两个新事物:服务发现(来自 ZooKeeper 集群和 Finagle 核心模块中的一个库)和 Mesos(包括基于 Mesos 的一个自研的计划任务框架 Aurora ,它现在也是 Apache 基金会的一个项目)。
+在 2012/2013 年的时候,Twitter 开始尝试两个新事物:服务发现(来自 ZooKeeper 集群和 [Finagle](https://twitter.github.io/finagle/) 核心模块中的一个库)和 [Mesos](http://mesos.apache.org/)(包括基于 Mesos 的一个自研的计划任务框架 Aurora ,它现在也是 Apache 基金会的一个项目)。
 
 服务发现功能意味着不需要再维护一个静态 YAML 主机列表了。服务或者在启动后主动注册,或者自动被 mesos 接入到一个“服务集”(就是一个 ZooKeeper 中的 znode 列表,包含角色、环境和服务名信息)中。任何想要访问这个服务的组件都只需要监控这个路径就可以实时获取到一个正在工作的服务列表。
 
-现在我们通过 Mesos/Aurora ,而不是使用脚本(我们曾经是 Capistrano 的重度用户)来获取一个主机列表、分发代码并规划重启任务。现在软件团队如果想部署一个新服务,只需要将软件包上传到一个叫 Packer 的工具上(它是一个基于 HDFS 的服务),再在 Aurora 配置上描述文件(需要多少 CPU ,多少内存,多少个实例,启动的命令行代码),然后 Aurora 就会自动完成整个部署过程。 Aurora 先找到可用的主机,从 Packer 下载代码,注册到“服务发现”,最后启动这个服务。如果整个过程中遇到失败(硬件故障、网络中断等等), Mesos/Aurora 会自动重选一个新主机并将服务部署上去。
+现在我们通过 Mesos/Aurora ,而不是使用脚本(我们曾经是 [Capistrano](https://github.com/capistrano/capistrano) 的重度用户)来获取一个主机列表、分发代码并规划重启任务。现在软件团队如果想部署一个新服务,只需要将软件包上传到一个叫 Packer 的工具上(它是一个基于 HDFS 的服务),再在 Aurora 配置上描述文件(需要多少 CPU ,多少内存,多少个实例,启动的命令行代码),然后 Aurora 就会自动完成整个部署过程。 Aurora 先找到可用的主机,从 Packer 下载代码,注册到“服务发现”,最后启动这个服务。如果整个过程中遇到失败(硬件故障、网络中断等等), Mesos/Aurora 会自动重选一个新主机并将服务部署上去。
 
 #### Twitter 的私有 PaaS 云平台
 
-Mesos/Aurora 和服务发现这两个功能给我们带了革命性的变化。虽然在接下来几年里,我们碰到了无数 bug ,伤透了无数脑筋,学到了分布式系统里的无数教训,但是这套架还是非常赞的。以前大家一直忙于处理硬件搭配和管理,而现在,大家只需要考虑如何优化业务以及需要多少系统能力就可以了。同时,我们也从根本上解决了 CPU 利用率低的问题,以前服务直接安装在服务器上,这种方式无法充分利用服务器资源,任务协调能力也很差。现在 Mesos 允许我们把多个服务打包成一个服务包,增加一个新服务只需要修改硬件配额,再改一行配置就可以了。
+Mesos/Aurora 和服务发现这两个功能给我们带了革命性的变化。虽然在接下来几年里,我们碰到了无数 bug ,伤透了无数脑筋,学到了分布式系统里的无数教训,但是这套架还是非常赞的。以前大家一直忙于处理硬件搭配和管理,而现在,大家只需要考虑如何优化业务以及需要多少系统能力就可以了。同时,我们也从根本上解决了 Twitter 之前经历过的 CPU 利用率低的问题,以前服务直接安装在服务器上,这种方式无法充分利用服务器资源,任务协调能力也很差。现在 Mesos 允许我们把多个服务打包成一个服务包,增加一个新服务只需要修改配额,再改一行配置就可以了。
 
-在两年时间里,多数“无状态”服务迁移到了 Mesos 平台。一些大型且重要的服务(包括我们的用户服务和广告服务)是最先迁移上去的。因为它们的体量巨大,所以他们从这些服务里获得的好处也最多。
+在两年时间里,多数“无状态”服务迁移到了 Mesos 平台。一些大型且重要的服务(包括我们的用户服务和广告服务系统)是最先迁移上去的。因为它们的体量巨大,所以它们从这些服务里获得的好处也最多,这也降低了它们的服务压力。
 
 我们一直在不断追求效率提升和架构优化的最佳实践。我们会定期去测试公有云的产品,和我们自己产品的 TCO 以及性能做对比。我们也拥抱公有云的服务,事实上我们现在正在使用公有云产品。最后,这个系列的下一篇将会主要聚焦于我们基础设施的体量方面。
 
-特别感谢 Jennifer Fraser, David Barr, Geoff Papilion, Matt Singer, Lam Dong 对这篇文章的贡献。
-
-
-
+特别感谢 [Jennifer Fraser][1]、[David Barr][2]、[Geoff Papilion][3]、 [Matt Singer][4]、[Lam Dong][5] 对这篇文章的贡献。
 
 --------------------------------------------------------------------------------
 
-via: https://blog.twitter.com/2016/the-infrastructure-behind-twitter-efficiency-and-optimization?utm_source=webopsweekly&utm_medium=email
+via: https://blog.twitter.com/2016/the-infrastructure-behind-twitter-efficiency-and-optimization
 
 作者:[mazdakh][a]
-译者:[译者ID](https://github.com/译者ID)
-校对:[校对者ID](https://github.com/校对者ID)
+译者:[eriwoon](https://github.com/eriwoon)
+校对:[wxy](https://github.com/wxy)
 
 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
 
@@ -121,9 +118,5 @@ via: https://blog.twitter.com/2016/the-infrastructure-behind-twitter-efficiency-
 [1]: https://twitter.com/jenniferfraser
 [2]: https://twitter.com/davebarr
 [3]: https://twitter.com/gpapilion
-[4]: https://twitter.com/lamdong
-
-
-
-
-
+[4]: https://twitter.com/mattbytes
+[5]: https://twitter.com/lamdong
\ No newline at end of file
diff --git a/published/20160912 15 Top Open Source Artificial Intelligence Tools.md b/published/201610/20160912 15 Top Open Source Artificial Intelligence Tools.md
similarity index 100%
rename from published/20160912 15 Top Open Source Artificial Intelligence Tools.md
rename to published/201610/20160912 15 Top Open Source Artificial Intelligence Tools.md
diff --git a/published/201610/20160912 8 best practices for building containerized applications.md b/published/201610/20160912 8 best practices for building containerized applications.md
new file mode 100644
index 0000000000..7ed7784ee9
--- /dev/null
+++ b/published/201610/20160912 8 best practices for building containerized applications.md	
@@ -0,0 +1,74 @@
+8 个构建容器应用的最佳实践
+====
+
+![](https://opensource.com/sites/default/files/styles/image-full-size/public/images/business/containers_2015-2-osdc-lead.png?itok=0yid3gFY)
+
+容器是未来在共有云和私有云进行应用开发的主要趋势,但是容器到底是什么,为什么它们成为了一种广受欢迎的部署机制,而且你需要怎样来修改你的应用来为容器化的环境优化它?
+
+### 什么是容器?
+
+容器技术的历史始于 2000 年的 SELinux 和 2005 年的 Solaris zones。今天,容器是由包括 SELinux、Linux 命名空间和控制组(cgroup)等几项内核特性构成,提供了用户进程、网络空间和文件系统空间的隔离。
+
+### 为什么它们如此流行?
+
+最近容器技术大规模的应用在很大程度上是由于旨在使容器更加易于使用的标准的发展,例如 Docker 镜像格式和分布模型,这个标准使用不可变镜像(immutable image),这正是容器运行时环境的起点,不可变镜像可以保证开发团队发布的镜像就是经过测试的,和部署到生产环境中的镜像是同样的镜像。
+
+容器所提供的轻量级隔离为一个应用组件提供了一个更好的抽象。在容器中运行的组件将不会干扰其它可能直接运行在虚拟机上的应用。它们可以避免对系统资源的争夺,而且除非它们共享一个持久卷,否则不会阻止对同一个文件的写请求。容器使得日志和指标采集的实践得以标准化,而且它们可以在物理机和虚拟机上支持更大的用户密度,所有的这些优点将导致更低的部署成本。
+
+### 我们应该如何构建一个基于容器的应用呢?
+
+将应用改为运行在容器中并不是什么很高的要求。主要的 Linux 发行版都有提供了基础镜像,任何可以在虚拟机上运行的程序都可以在上面运行。但是容器化应用的趋势是遵循如下最佳实践:
+
+#### 1. 实例是一次性的
+
+你的应用的任何实例都不需要小心地保持运行。如果你的一个运行了许多容器的系统崩溃了,你还能够转移到其它可用的系统去创建新的容器。
+
+#### 2. 重试而不是崩溃
+
+当你的应用的一个服务依赖于另一个服务的时候,在另一个服务不可用的时候它应该不会崩溃。例如,你的 API 服务正在启动而且监测到数据库不能连接。你应该设计它使得其不断重试连接,而不是运行失败和拒绝启动。当数据库连接断开的时候 API 可以返回 503 状态码,告诉客户端服务现在不可用。应用应该已经遵守了这个实践,但是如果你正在一个一次性实例的容器环境中工作,那么对这个实践的需要会更加明显。
+
+#### 3. 持久性数据是特殊的
+
+容器是基于共享镜像启动,它使用了写时复制(COW)文件系统。如果容器的进程选择写入文件,那么这些写的内容只有在直到容器存在时才存在。当容器被删除的时候,写时复制文件系统中的那一层会被删除。提供给容器一个挂载的文件系统目录,使之在容器存活之外也能持久保存,这需要另外的配置,而且会额外消耗物理存储。明确的抽象定义了什么存储是持久的,催生出了实例是一次性的观点。拥有一个抽象层也使得容器编制引擎可以处理挂载和卸载持久卷的复杂请求,以便这些持久卷可以用于容器。
+
+#### 4. 使用 stdout 而不是日志文件
+
+现在你或许会思考,如果持久的数据是特殊的,那么我用日志文件来做什么事情?容器运行时环境和编制引擎项目所采用的方法是进程应该[写入 stdout/stderr][1],而且具有归档和维护[容器日志][2]的基础设施。
+
+#### 5. 敏感信息(以及其它配置信息)也是特殊的
+
+你绝不应该将敏感信息例如密码、密钥和证书硬编码到你的镜像中。通常在你的应用与开发服务、测试服务,或者生产服务相交互时,这些敏感信息通常都是不同的。大多数开发者并没有访问生产环境的敏感信息的权限,所以如果敏感信息被打包到镜像中,那么必须创建一个新的镜像层来覆盖这个开发服务的敏感信息。基于这一点来看,你再也不能使用与你们开发团队所创建的和质量测试所测试的相同的镜像了,而且也失去了不可修改的镜像的好处。相反的,这些值应该被存储在环境变量中文件中,它们会在容器启动时导入。
+
+#### 6. 不要假设服务的协同定位
+
+在一个编排好的容器环境中,你会希望让编排器将你的容器发送到任何最适合的节点。最适合意味着很多事情:它应该基于那个节点现在拥有最多的空间、容器所需的服务质量、容器是否需要持久卷,等等。这可能意味这你的前端、API 和数据库容器最终都会放在不同的节点。尽管给每个节点强制分配一个 API 容器是可以做到的(参考 Kubernetes 的  [DaemonSets][3]),但这种方式应该留给执行监控节点自身这类任务的容器。
+
+#### 7. 冗余/高可用计划
+
+即使你没有那么多负载需要高可用性的配置,你也不应该以单路方式编写服务,否则会阻止它运行多份拷贝。这将会允许你运用滚动式部署,使得将负载从一个节点移动到另外一个节点非常容易,或者将服务从一个版本更新到下一个版本而不需要下线。
+
+#### 8. 实现就绪检查和灵活性检查
+
+应用在响应请求之前会有一定的启动时间是一件很正常的事情,例如,一个 API 服务器需要填充内存数据缓存。容器编排引擎需要一种方法来检测你的容器是否准备好服务用户请求。为一个新的容器提供就绪检查可以允许我们进行滚动式部署,使得旧容器可以继续运行直到不再需要它,这可以防止服务宕机。类似的,一个存活检查也是一种容器编排引擎持续检查容器是否在健康可用状态的方法。决定容器健康或者说“存活”应该由容器应用的创建者说了算。一个不再存活的容器将会被结束,而且一个新的容器会被创建来替代它。
+
+### 想查找更多资料?
+
+我将会出席十月份的格雷丝霍普计算机女性峰会(Grace Hopper Celebration of Women in Computing),你可以在这里来看一下关于我的访谈:[应用的容器化:是什么,为什么,和如何实现][4]。今年不去 GHC 吗?那你可以在 [OpenShift][5] 和 [Kubernetes][6] 的项目站点来了解关于容器、编排和应用的相关内容。
+
+--------------------------------------------------------------------------------
+
+via: https://opensource.com/life/16/9/8-best-practices-building-containerized-applications
+
+作者:[Jessica Forrester][a]
+译者:[LinuxBars](https://github.com/LinuxBars)
+校对:[wxy](https://github.com/wxy)
+
+本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
+
+[a]: https://opensource.com/users/jwforres
+[1]: https://docs.docker.com/engine/reference/commandline/logs/
+[2]: http://kubernetes.io/docs/getting-started-guides/logging/
+[3]: http://kubernetes.io/docs/admin/daemons/
+[4]: https://www.eiseverywhere.com/ehome/index.php?eventid=153076&tabid=351462&cid=1350690&sessionid=11443135&sessionchoice=1&
+[5]: https://www.openshift.org/
+[6]: http://kubernetes.io/
diff --git a/published/201610/20160912 Content Security Policy, Your Future Best Friend.md b/published/201610/20160912 Content Security Policy, Your Future Best Friend.md
new file mode 100644
index 0000000000..90cd08e208
--- /dev/null
+++ b/published/201610/20160912 Content Security Policy, Your Future Best Friend.md	
@@ -0,0 +1,231 @@
+内容安全策略(CSP),防御 XSS 攻击的好助手
+=====
+
+很久之前,我的个人网站被攻击了。我不知道它是如何发生的,但它确实发生了。幸运的是,攻击带来的破坏是很小的:一小段 JavaScript 被注入到了某些页面的底部。我更新了 FTP 和其它的口令,清理了一些文件,事情就这样结束了。
+
+有一点使我很恼火:在当时,还没有一种简便的方案能够使我知道那里有问题,更重要的是能够保护网站的访客不被这段恼人的代码所扰。
+
+现在有一种方案出现了,这种技术在上述两方面都十分的成功。它就是内容安全策略(content security policy,CSP)。
+
+### 什么是 CSP?
+
+其核心思想十分简单:网站通过发送一个 CSP 头部,来告诉浏览器什么是被授权执行的与什么是需要被禁止的。
+
+这里有一个 PHP 的例子:
+
+```
+");
+?>
+```
+
+#### 一些指令
+
+你可以定义一些全局规则或者定义一些涉及某一类资源的规则:
+
+```
+default-src 'self' ;
+     # self = 同端口,同域名,同协议 => 允许
+```
+
+基础参数是 `default-src`:如果没有为某一类资源设置指令规则,那么浏览器就会使用这个默认参数值。
+
+```
+script-src 'self' www.google-analytics.com ;
+     # 来自这些域名的 JS 文件 => 允许
+```
+
+在这个例子中,我们已经授权了 www.google-analytics.com 这个域名来源的 JavaScript 文件使用到我们的网站上。我们也添加了 `'self'` 这个关键词;如果我们通过 `script-src` 来重新设置其它的规则指令,它将会覆盖 `default-src` 规则。
+
+如果没有指明协议(scheme)或端口,它就会强制选择与当前页面相同的协议或端口。这样做防止了混合内容(LCTT 译注:混合内容指 HTTPS 页面中也有非 HTTPS 资源,可参见: https://developer.mozilla.org/zh-CN/docs/Security/MixedContent )。如果页面是 https://example.com,那么你将无法加载 http://www.google-analytics.com/file.js 因为它已经被禁止了(协议不匹配)。然而,有一个例外就是协议的提升是被允许的。如果 http://example.com 尝试加载 https://www.google-analytics.com/file.js,接着协议或端口允许被更改以便协议的提升。
+
+```
+style-src 'self' data: ;
+     # Data-Uri 嵌入 CSS => 允许
+```
+
+在这个例子中,关键词 `data:` 授权了在 CSS 文件中 data 内嵌内容。
+
+在 CSP 1 规范下,你也可以设置如下规则:
+
+- `img-src` 有效的图片来源
+- `connect-src` 应用于 XMLHttpRequest(AJAX),WebSocket 或 EventSource
+- `font-src` 有效的字体来源
+- `object-src` 有效的插件来源(例如,``,``,``)
+- `media-src` 有效的 `