this
Python 之禅:
1 | >>> import this |
Python 之禅:
1 | >>> import this |
当我们看到一段代码时,最先注意到的,不是代码有几层循环,用了什么模式,而是变量与注释,因为它们是代码里最接近自然语言的东西,最容易被大脑消化、理解。
Python 支持灵活的动态解包语法,只要用星号表达式(*variables)作为变量名,它便会贪婪地捕获多个值对象,并将捕获到的内容作为列表赋值给 variables:
1 | >>> data = ['ethan', 'apple', 'orange', 'banana', 100] |
在 Python 交互式命令行里,_ 变量还有一层特殊含义——默认保存我们输入的上个表达式的返回值:
1 | >>> 'foo'.upper() |
剖析 Linux 容器的核心实现原理:
在开始实践之前,你需要准备一台 Linux 机器,并安装 Docker。这一次,我要用 Docker 部署一个用 Python 编写的 Web 应用。这个应用的代码部分(app.py)非常简单:
1 | from flask import Flask |
一个容器,实际上是一个由 Linux Namespace、Linux Cgroups 和 rootfs 三种技术构建出来的进程的隔离环境。从这个结构中我们不难看出,一个正在运行的 Linux 容器,其实可以被“一分为二”地看待:
更进一步地说,作为一名开发者,我并不关心容器运行时的差异。因为,在整个“开发 -> 测试 -> 发布”的流程中,真正承载着容器信息进行传递的,是容器镜像,而不是容器运行时。这个重要假设,正是容器技术圈在 Docker 项目成功后不久,就迅速走向了“容器编排”这个“上层建筑”的主要原因:作为一家云服务商或者基础设施提供商,我只要能够将用户提交的 Docker 镜像以容器的方式运行起来,就能成为这个非常热闹的容器生态图上的一个承载点,从而将整个容器技术栈上的价值,沉淀在我的这个节点上。更重要的是,只要从我这个承载点向 Docker 镜像制作者和使用者方向回溯,整条路径上的各个服务节点,比如 CI/CD、监控、安全、网络、存储等等,都有我可以发挥和盈利的余地。这个逻辑,正是所有云计算提供商如此热衷于容器技术的重要原因:通过容器镜像,它们可以和潜在用户(即,开发者)直接关联起来。
最初,公共服务平台提供的是,基于某个开源 RPC 框架的 RPC 格式的接口。在上线一段时间后,我们发现这个开源 RPC 框架的 Bug 很多,多次因为框架本身的 Bug,导致整个公共服务平台的接口不可用,但又因为团队成员对框架源码不熟悉,并且框架的代码质量本身也不高,排查、修复起来花费了很长时间,影响面非常大。所以,我们评估下来,觉着这个框架的可靠性不够,维护成本、二次开发成本都太高,最终决定替换掉它。对于引入新的框架,我们的要求是成熟、简单,并且与我们现有的技术栈(Spring)相吻合。这样,即便出了问题,我们也能利用之前积累的知识、经验来快速解决。所以,我们决定直接使用 Spring 框架来提供 RESTful 格式的远程接口。
把 RPC 接口替换成 RESTful 接口,除了需要修改公共服务平台的代码之外,调用方的接口调用代码也要做相应的修改。除此之外,对于公共服务平台的代码,尽管我们只是改动接口暴露方式,对业务代码基本上没有改动,但是,我们也并不能保证就完全不出问题。所以,为了保险起见,我们希望灰度替换掉老的 RPC 服务,而不是一刀切,在某个时间点上,让所有的调用方一下子都变成调用新的 RESTful 接口。因为替换的过程是灰度的,所以老的 RPC 服务不能下线,同时还要部署另外一套新的 RESTful 服务。我们先让业务不是很重要、流量不大的某个调用方,替换成调用新的 RESTful 接口。经过这个调用方一段时间的验证之后,如果新的 RESTful 接口没有问题,我们再逐步让其他调用方,替换成调用新的 RESTful 接口。
但是,如果万一中途出现问题,我们就需要将调用方的代码回滚,再重新部署,这就会导致调用方一段时间内服务不可用。而且,如果新的代码还包含调用方自身新的业务代码,简单通过 Git 回滚代码重新部署,会导致新的业务代码也被回滚。所以,为了避免这种情况的发生,我们就得手动将调用新的 RESTful 接口的代码删除,再改回为调用老的 RPC 接口;除此之外,为了不影响调用方本身业务的开发进度,调用方基于回滚之后的老代码,来做新功能开发,那替换成新的 RESTful 接口的那部分代码,要想再重新 merge 回去就比较难了,有可能会出现代码冲突,需要再重新开发。