我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。

我看「刷月饼」事件

前因后果见 知乎:阿里月饼事件;关于本文的讨论见 V2EX: 关于刷月饼事件我来唱点反调

可能发得有点晚了,是因为我不是有感于事件本身,而是看了大家的讨论一边倒地黑阿里,以至于有一些逻辑显得不是那么合理,所以我想 仅就几个细节问题 谈一谈我的想法。

这是不是一个价值观问题

我觉得这是一个价值观问题,就好像就是有人觉得「刷月饼」不妥甚至很严重,也有人认为没什么大不了。所谓价值观就是指你如何看待一件事情的对错、如何进行选择,很难说谁对谁错,但这两种人的价值观的确是不同的。公司因为价值观不同开除一个员工其实也没什么不对,对双方都有好处,如果被开除的人不觉得是自己的错,那么也很难说是对他的伤害。

下单但没有购买算不算得到了「利益」

既然下单是需要抢的,说明这个订单很可能对应着一份库存,你下单成功了意味着你得到了这样一份权利 —— 你可以在未来的一个小时里决定要不要买这个月饼,你甚至可以等有人出了一个合适的价格之后转卖这个月饼,可以说这种购买的权利就是「抢月饼」的核心利益,所以订单最后是否成交不影响刷月饼获利的这个事实。

信息安全相关职位刷月饼是否可以减轻责任

的确可能这个职位的工作之一就是去发现漏洞,但这也是在有工作安排的情况下,而且发现漏洞(活动规则设计不合理可以认为是广义的漏洞)后也不应该去利用它为自己谋利(是否算谋利已在前一小节讨论)。所谓 Geek 精神不应该被当作一个挡箭牌,在一个大的群体里还是要去遵守普适的规则,刷月饼就好像钻公司规章中的空子。打一个比方的话就好像办公室桌上有十几块月饼给大家吃,并没有限制一个人可以拿几块,但你一个人就拿了一大半,这显然不妥。

抢月饼网站设计不合理开发者是否有责任

我觉得这本来就是一个内部的活动,要考虑一个投入产出的性价比,不可能做到和对外的服务一样稳定和周全,在公司内部大家是有一个基本的信任的。因为往往一个公司的同事之间有着共同的利益和相近的价值观,没必要将方方面面都限制死,相信如果为抢月饼设计一个事无巨细、毫无漏洞的规则也不是大家希望看到的。

抢火车票和抢月饼是否是一回事

和前一点有点像,对外和对内是有分别的,从抢火车票这一点来说,你对其他买不到火车票的人是不负有任何的责任的,只要你不违反法律和协议你可以做任何事情;但在公司内则不同,大家需要有基本信任,即使没有明文的规则写明,当然你也可以不遵守这种「潜规则」,后果大概就是因为价值观不符被开除了,只要补偿给到位,开除当然也是合法的。

写在后

说实话,当我说出这些想法之后我有点怀疑人生,有点怀疑当我的意见和别人不同的时候我是否应该说出来,因为有的时候说出来会招致一些攻击,也会被一些本来是朋友的人划为异类。每当我看到这种一边倒的舆论的时候,我总会有点警惕,因为一边倒意味着既然大家的目标是一样的,即使过程有一些小差错也无所谓,当你指出这些问题的时候,就会有人说你在洗地。没错,腾讯抄袭的时候我给腾讯洗、百度搞竞价排名的时候我给百度洗。

回到讨论本身,我简单补充几点:

  • 如果有公司真的会因为「价值观」而开除一名员工,我觉得是值得尊敬的,因为这家公司会在意在一起工作的同事是什么样的人,只有这样才能构建出一种凝聚力。当然,这在大公司是很难做到一碗水端平的,也很难说这是否是阿里开除几名员工的真正原因。
  • 很多人比较在意最后两点中的「双重标准」,我觉得这是一个很正常的事情,对家人、对同事,和对陌生人我们是有不同的要求和期待的,在公司抢月饼要比在陌生人间抢火车票有更多的道德约束我觉得也是合理的。

Docker 与容器化技术实践

这篇文章由我 7 月末在 Connext 2016 进行的一次技术分享整理而来。

RP 主机

我在高一的时候开始尝试搭建自己的网站,当时市面上的「虚拟主机」基本上只提供 PHP 环境,限制也比较多。于是我在 Linode 以每月 20 美元的价格买了一台 Linux VPS 用来搭建网站,但当时我的零花钱无法负担这个开销,于是尝试性地公开出售服务器资源,为此我编写了一套叫 RootPanel 的虚拟主机管理系统。

和其他虚拟主机不同的是,RP 主机的用户可以有非常大的权限 —— 可以登录 SSH, 运行 Node.js、Python 之类的程序;而 RootPanel 则通过 Web 的界面允许用户使用 MySQL、MongoDB 数据库,并且通过 Nginx 共享 80 端口,RootPanel 会检查用户的请求是否符合权限要求,然后去与 Nginx 这些系统服务交互。

当时 Docker 还没有出现,我用了一些比较「传统」的方式来隔离 RP 主机上用户的权限:

  • 文件系统:Unix users(文件权限)、quota-tools(磁盘空间)
  • CPU 和内存:自行编写脚本来调整进程优先级(CPU 超限时)和杀进程(内存超限时)
  • 进程和网络:因为 Unix 本身的权限,无权向其他进程发送信号

在运行 Web 服务时,后端程序(例如 PHP-FPM、Python 的 uwsgi、Node.js 应用进程)本身由用户运行,以 Unix Socket 的方式提供服务(Unix Socket 会遵守 Unix 的文件权限机制),然后可以在 RootPanel 的 Web 界面上配置从域名到 Unix Socket 的映射(RootPanel 会检查你配置的 Unix Socket 是否在你的 home 目录中等),由 Nginx 完成反向代理,实现共享 80 端口。

Redis 和 Memache 这种轻量级数据库也是由用户自行运行的,通过 Unix Socket 提供服务来避免被其他用户访问到。出于性能考虑,所有用户会共同使用同一个 MySQL 和 MongoDB,用户可以在 RootPanel 的 Web 上创建和管理数据库,RootPanel 会为每个用户分配一个用户名和密码,使用这些数据库本身的用户机制进行权限控制。

当然 RP 主机现在已经被关掉了,详见 RP 主机和 GreenShadow 关闭计划

「隔离」和「资源控制」

到后来 2014 年初的时候我发现了 Docker, 它是一个基于 Linux 的轻量级虚拟化技术,可以以非常低的成本来创建与主机隔离的、可以独立进行资源控制的「容器」。

在前面 RP 主机的例子中,我们虽然一定程度地解决了这两个问题,但并不完美。隔离方面 RP 主机只做到了权限的隔离,但用户依然可以看到其他用户和它们的进程、网络链接;资源控制方面,CPU 和内存都依赖于脚本进行控制,控制的粒度和准确性显然不如利用内核本身的特性。

Docker 使用 Linux 2.6 提供的 namespaces 特性来隔离容器之间的文件系统(mount namespace)、主机名(UTS namespace)、进程(PID & IPC namespace)、网络(network namespace)、用户(user namespace)。使容器中的进程只能看到与自己有关的系统资源,完全感觉不到主机上其他的容器的存在。

Docker 还使用了 Linux 2.6 提供的 cgroups 特性来统计和限制容器的系统资源,包括 CPU(cpuset & cpu & cpuacct cgroup)、内存(memory cgroup)、磁盘 IO(blkio cgroup)等。在资源控制方面,因为是由内核执行的,因此可以进行非常细粒度的控制,例如在 CPU 上,既可以为容器设置权重,也可以直接设置最大使用率。

联合文件系统

在解决了隔离和资源控制之后,我们可以允许容器自由地修改容器内的文件系统,每个容器可以使用不同的发行版、运行不同版本的系统服务。但为了允许容器去自定义它们的文件系统,我们必须要为每个容器挂载一个单独的根目录,这样将会占用大量的磁盘空间。

为了解决这个问题,Docker 基于「联合文件系统(AUFS、OverlayFS)」实现了一个「镜像」的功能。联合文件系统是一种可以将不同的目录,以分层「叠加」的方式挂载为一个文件系统的文件系统。Docker 会将不同容器间共同的部分作为一个共用的只读层(例如发行版就是一个层),然后为每个容器再叠加一个可写的层,容器对文件系统的修改会写入到这个可写的层,而不是共享的层,在容器运行结束后,这个可写的层也可以固化为一个只读的层,被其他容器复用(这就是 docker build 的过程)。

将应用封装为镜像

Docker 的容器实际上都是从 Docker 镜像创建出来的,可以说镜像是容器的模板,这个概念类似于进程是由可执行文件创建出来的,但镜像不仅仅包含可执行文件,而是包含了一个程序允许所需要的所有环境。

我们可以通过 Dockerfile 来创建镜像,Dockerfile 中包含了若干指令,这些指令会在容器中被执行,而这些指令对文件系统的修改,会作为构建出的镜像中的一个「层」。

Docker 镜像让应用的「交付」变得简单了,在理想的情况下,Dockerfile 中包含了构建应用所需要的运行环境的指令,而镜像则是一次构建的结果,Docker 为镜像提供了二进制级别的兼容性,镜像可以被传输到其他 Linux 主机上直接运行,交付一个应用就像发送一个可执行文件那么简单。基于我们前面提到的联合文件系统,Docker 镜像在传输时只会传输新的层,如果不同的镜像基于同一个基础的镜像(层)来构建,那么并不会产生额外的传输和存储开销。

无状态的容器

在一个服务器端系统中,包含了大量业务逻辑、需要频繁修改的「应用进程」是最不稳定的部分,它可能会出错、会崩溃重启、会占用大量的计算资源,因此我们必须要能够快速地对包含应用的容器进行调整。为了做到这一点,一个得到了广泛认同的实践就是将应用实现为「无状态」的,即不在内存中持久性地保存数据,而是将这些状态存储到专门的数据库(Redis 等)中,这些数据库会有自己的分布式解决方案,而不必我们操心。

这样我们便可以随时停止和启动一个应用容器,而不必担心数据丢失或状态不同步,同时无状态的应用对容器数量也毫不关心,我们可以根据业务的负载情况随时调整容器数量进而增加业务的负载能力,应用也不关心这些进程运行在哪台服务器上(Docker 的镜像为容器提供了一致的运行环境),只要前端的负载均衡(Nginx)可以发现它即可,因此我们还需要一种「服务发现」的机制让负载均衡服务能够感知到新容器的加入和已有容器的退出。

分布式的容器调度

在一个公有云的场景中,我们往往需要管理运行在几十台服务器上的几千个容器,物理设备总是可能出现故障的,随着集群规模的增长,出现故障的频率将会越来越高,我们必须能够自动地发现和恢复这些故障,我们将这种程序称为「集群管理器」,它需要关注的问题包括:

  • 容器崩溃:应用进程因错误或内存超限退出,可以简单通过设置 Docker 的重启策略来解决。
  • 容器僵死:负载均衡器应该能够将应用容器无响应的情况通知给集群管理器来重启容器。
  • 服务器崩溃或失联:集群管理需要将崩溃的服务器上的容器移动到其他的服务器并从负载均衡中移除。

在一些计划中的维护任务也需要保证服务不中断:

  • 部署新版本:应逐个启动新版本的容器并加入负载均衡,确认新容器工作正常后再将旧容器从负载均衡中移除并停止。
  • 调度到资源充足的服务器:集群管理器应该能够感知到各个服务器的负载情况,将负载较高的服务器的容器移动到负载较低的服务器。

集群管理器需要决定将容器部署到哪台服务器,需要考虑的因素包括:

  • 服务器实际负载。
  • 有时容器会声明自己需要多少资源,虽然实际并没有占用这么多,但一段时间之后可能会有变化。
  • 将容器分散到不同的服务器以应对单个服务器失效。

集群管理器还应该能够应对自身或所依赖的服务失效的情况,通过反复地重试保证实际运行的容器与计划中的一致。

在集群管理器方面社区已经有了很多成熟的解决方案,例如 Docker Swarm、Kubernetes、Marathon,作为私有云来说都基本够用,但在公有云的场景下经常还是需要自己开发一部分功能来和现有系统(例如计费)整合的。

小结

因为随着需要处理的数据量越来越大,我们必须将系统设计成分布式的,这需要我们将计算资源进行一个抽象,不去考虑有关运行环境的细节问题,所以虚拟化是一个大的趋势。基于 Docker 的容器是虚拟化解决方案中的一种,也是目前受到了非常多关注的一种,这大概是因为内核级别的虚拟化有着非常好的性能,同时 Docker 作为一个开源的产品也有着非常活跃的社区。我今天主要介绍的是我在部署环节对 Docker 的实践,但 Docker 的应用并不止于此,在开发和测试环节同样有 Docker 的身影。

参考链接

给初入门程序员的建议

我们经常看到网络上有很多黑程序员的段子,但那不过是一群居心叵测的人散布的谣言,企图通过给新人们留下负面的印象,进而阻止更多人进入这个行业的方式,来保证不思进取的自己不会被行业淘汰。

解决问题的途径比得到结果更重要

解决问题是一个通用的能力,本应在学生时代习得,若是不擅长解决问题,怕是短时间内很难提升,但在一个具体的领域里,往往「途径」更加重要。

当你在一个论坛或社区提出一个问题时,有些惜字如金的大牛会直接丢给你一个链接,不会多留下哪怕半个字,不要小看一个链接,它可能比直接告诉你答案更有价值。

当你进入一个新的领域,遇到问题时往往不知道应该去哪寻找答案,这时候你会想如果有一个网站,列出了作为新手可能遇到的一切问题该多好。但世上没有这么好的事情,往往这些问题和解答分散在不同的网站上 —— 从这个链接就可以点过去。

不要相信一句话就可以描述的真理

网络上的大牛经常提出提出一些简洁有力的口号,例如「抽象可以解决计算机领域的一切问题」、「好的代码不需要注释去解释」、「动态类型语言才能提高编码效率」、「PHP 是世界上最好的编程语言」等等。

并不是说这些观点是错误的,但它们就像「苹果总是会落到地上」这种简单的理论一样,描述的情况是片面的,而非普适的。有些结论是前辈们花了大量的时间和精力探索出来的,但光知道一个结论对你的编程是没有太多指导意义的,更多的细节隐藏在得出这个结论的过程中。

所以如果提出这些观点的人没有深入介绍、你也不打算自行了解,索性不如忘掉这些话。

深入了解你使用的工具

在编程的过程中,我们需要借助大量的工具来完成版本控制、调试、重构、构建和部署等工作。包括你的编辑器(IDE)和操作系统都是必不可少的工具,选择一组好用的工具,并且不断地学习和配置它们,这样才能逐渐提高工作效率。

每个人在选择工具的过程中都会掺杂大量的个人喜好,但我建议大家在选择工具时考虑下面几个因素:是否是免费软件或开源软件、是否有公司在维护、是否有大量用户和活跃的社区、是否支持插件或拓展、是否支持多种平台。

写出可以运行的代码只是最基本的要求

当一个程序可以运行起来了,不要高兴得太早,这只是一个开始。例如你是否考虑到了各种边界情况;当程序收到非预期的输入会发生什么;所依赖的外部服务出现异常会怎样,发生错误时是否能从日志中还原出现场;如果程序处理的数据量或运行时间提高几个数量级会发生什么;构建、测试和部署过程是否做到了自动化;代码是否为将来的修改做好了准备等等。

先精通一种语言,再广泛涉猎

很多新手会各种编程语言搞得头晕目眩,不知道先从哪个学起,索性不如左右开弓,同时学习。

一旦你这样做了就会发现很难将同时学习的两种语言的知识区分开,因为它们实在太像了。但如果你先精通一门语言,了解了它每个语法的工作方式之后再学习其他语言就很轻松了,因为你对已掌握的语言已经足够了解,不会和新语言混淆。而且你会不由自主地用已掌握的语言去和新语言比较,更容易发现它们之间的差异,发现各自语法的内在逻辑。

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2025-02-10.