但是现在很多团队在自己的摸索中,已经发现了这12条其中的一部分。然后我也跟很多朋友聊过这个话题,如果是来自大公司的话,他们很多会认为这12条应该是日常应该做的事情。如果来自创业公司或者小团队来说,这12条理念会让开发速度变慢。但其实这12条理念其实可以非常好的对抗software erosion。
它的本意是随着时间的流逝被消磨或者腐蚀掉了。前面几个星期前写的代码,现在已经看不太懂,或者说公司有一个系统,你现在有一个新的需求,你要满足这个需求,就是要满足这12条例子最快地实现这个需求。如果现在运行得很好的一个产品,可能一两年之后操作系统更新了,底层的依赖已经变了,这时你重启系统,发现已经不能运动了,不知道怎么回事。这样的版本因为系统资源不够了,必须要进行清理。我们讲一下Heroku这个公司,有很厉害的iOS开锁的作者。这家公司并不是非常成功的,因为它在10年被收购的时候,我看了一下它线上只有大概一万个运用,他们的现金流应该是比较有限的。但是他们有非常强大的工程能力,他们还是在10年的时候被两亿美金收购掉了。大家可能都回用git去管理代码,平时部署会很麻烦,但是你只要用git push就可以完成代码的版本了。
我只要fork一下就可以了,如果我的生活环境有什么变化,我可以直接fork一份。我们为什么不直接用Heroku去开发呢?因为Heroku比所有的EPS都要贵一点,还有一个最大的问题,实际上是很强的问题,部分AT是被墙掉的,所以没有办法。其实Heroku有很多的开源可以去用,但是我们发现这些克隆的版本都有很多的限制,我们并不是一定要造一个Heroku出来,去做我们的开发。我只需要在Heroku这个创始人他们在创建Heroku的这个过程中,学习会了这套理念,把这套理念用到我们的开发中就可以了。这边是具体的12条,根据我们的实践下来,就是这12条。
我们并不需要全部都去应用出来。但是其中的部分,其实还是挺有价值的。第一条实际上它的原文讲的是每一个代码都要用独立的代码仓库去管理,到现在就是microservices的架构。我们知道Twitter每次公共的API调用都产生超过100次的内部API调用。
有利于开发团队进行协作、降低部署成本,异常快速回滚。这个就是我们用到的内部的一些私有的仓库,遵守这条发作的话会有一些好处,比如说一份代码我只要修改一下定制就可以了。如果说一个系统没有办法用一份代码存储的话,说明它必须切分成多个代码仓库。这边我们用到三个比较基础的东西,这个模型层,实际上是私有的Ruby的代码库。
它封装了一些对数据库的操作,它是独立成一个代码仓库,我们可以通过环境变量注入只读的数据库,这样可以确保它是很干净的。还有一个只写的对外提供APT的接口。在这里把它分成不同的子系统,我们可以用X-Rate-Limit。这样的好处在于不管是客户端的问题还是内部的其他子系统的问题,或者说是来自外界的恶意攻击,我们都可以有办法把攻击降到最低。第二点跟刚刚一开始提到的,如果你需要系统迁移或者系统升级,这种情况下怎么样保证系统很容易的进行迁移。需要对依赖做声明。这边的话是Gemfile的示例,这个文件会具体的申明用到哪一个库的数据。你可以保证用到的都是一模一样的版本。这样还是有点抽象,这里举个例子,Sidekg和系统默认的版本不一致,如果我用Bundle exec,将来万一他们是不兼容的,或者代码迁移到其他系统,这个系统默认的库就是不兼容的。
当然目前特别流行的docker是可以很容易地满足这个需求,今天就不讲这个事情。还有第三点的话,我们提倡把配置放到Unix的环境变量中。一开始是放在代码常量里面,但是代码常量有个不好的地方,你要修改的话要来回地改动代码。这样会影响你在代码仓库的一个情况。如果你在比如说上线前的版本和上线后的版本配置不一样的话,你要在代码仓库维持两个分支,肯定这样是不合理的。还是举个例子,如果是ruby的话他有一个比较推崇的.ENB。
第四点和第三点关系很大的,我们对外部系统的依赖,比如说我们要访问七牛的API,我们建议放在稳定变量,包括其他的子系统。刚刚我们提到了就是我们的模型层,我们会有对外暴露的两个接口,这两个接口会注入进来。这边有个简单的测试,你永远不应该把不应该提交的代码仓库提交出去。这边是个简单的示例,这个脚本是很简单的执行环境命令。如果我们用dot enb的话,不管用什么语音都是很容易实现的。但是它对整个系统的稳定性的帮助是非常大的。
第五点的话其实对于我们实践下来,其实感觉并不是特别的重要,大家看一下就好。
第六是这样的,就是应用应该作为没有状态的进程去运行。除了和持久化相关的服务,其他服务都是应该无状态的。这么做有什么不好的地方?你没有办法确保同一个客户一样的进程保存在里面,而且下一次这个客户的请求也会到其他的机器上。
我们刚刚提到了可以通过环境变量的注入依赖,我们提倡把每一个独立的应用绑定,就是内嵌HTTP的库。他们直接对外绑定端口,对外提供服务,而不是依赖外部的服务容器运行。这个绑定的端口它未必以HTTP的协议进行运行。
并且我们刚刚提到以这样的方式去申明的话,这些服务就可以作为其他的服务的依赖。高层次的API就可以基于低层次的API执行了。以这样的一种方式去做的话,接下去有非常好的应用,就是说我们在最前端可以用layer awore tcp proxy进行。如果它get某一个路径的话,我们可以转化到对应的m point上面。还有类似的做法,以类似的这种TCP的代理方式,我们可以很简单地复制一份流量。我如果要进行系统的性能测试,我肯定不能在正常环境下测,但是我如果部署两份一模一样的流量,这样子一是可以做性能的压力测试,另外在线如果线上有什么bug的话,新系统如果要上线,我一样可以用这样的方式测试。相同的流量下来,它会有什么样的反应。这种方式在GitHub的应用,它不可能允许DNS的这种方式随机给你指定某一个机器去响应你的请求。因为每个推的,每个往上面push的代码都是跟个人相关的。但是以这种方式去做的话,就可以捕捉到Git协议里面的一段,然后我们可以抓出你的用户名,可以定向到对应的机器上去。这样可以做到很容易的扩容。
第八点,扩容的方式我们提倡是以多进程的模型进行扩容。
虽然是以多进程的模式扩容的话,并不代表每个进程是单进程的。因为我们刚刚提到了,我们提倡服务都是无状态的。每个服务都是无状态,扩容就很容易。可以把同样的进程部署到不同的服务器上。后面两点实际上讲述多进程的模式,我们提供的服务就不应该把自己变成系统的daemon。常见的把一个程序绑在后台的一种方式,但这几种方式其实都不太好的。第二个是把代码放在阿帕奇的容器里面运行,这样两份代码互相会有冲突,你使用阿帕奇这样的容器。另外的话像ruby的unicon,这样子也是不够好的。都会遇到很多奇怪的问题,如果进程挂掉了怎么办?进程异常退出,会飘到其他的PID上面。如果我们没有做好好的环境隔离的话,会有不纯净的环境,额外的环境变量会对系统造成异常的影响。这边还是以Ruby为例子,我们以procfile示例,对外提供FTTP的服务,这两个脚本随时会因为各种原因退出,如果以这样的方式导出到upstyle。
第九点是快速启动,优雅关闭。
第十点的话,我们还是希望开发、测试、部署的环境要尽量接近。
很多公司因为分工很明确,代码是开发写的,生产环境和运行是运维的事情。作为负责开发,要从需求的正确性到往后,整个代码的正确性都是应该负责的。第二条机器太慢的情况已经比较少见了,所以我们尽量保证开发的环境和运行的环境是一样的。持续集成很重要,其实很多人,特别是小团队可能会觉得持续集成,搞测试是一件很烦的事情。其实不是的,你要很简易的搭一个不断地跑测试的服务器是很重要的。它会往这个服务发一个HTTP的包,不管成功、失败会发出来一份文件。整个环境的搭载是很容易的,大概一两个小时就可以搭起来了。我一直是写Ruby的,对于创业公司来说这样可能会有点过了,因为很多时候你们不知道会做成什么样子。这种情况回归测试是很重要的,遇到一个bug,写一个测试能够重现它。下一次这个测试会直接告诉你怎样做的,这样也是回到刚刚的一个主题,将来这个代码不可维护了。任何的改动破坏到原来的东西,测试都会跑不过。第十点,我觉得是特别重要的一点。如果每个系统都往文件里面写的话,每一个日志都会分摊在不同的地方,一旦出了问题,你都不知道该去看哪里的日志了。
据我个人得经验,服务器满了,都是日志把磁盘写满了。理想的情况下,应该是把所有的日志直接标准输出,再从印象到服务器上,这是理想的状况。
所有的日志统一分析和处理。我一个朋友他跟我讲说这12点其实对他们公司来说都是很正常的做法,但是小团队的话如果搭这样一套环境比较困难,我们现在目前还没有搭自己的日志服务,现在用的是一个国外的paper trail的服务。
这样做的另外的好处就在于我可以做日志报警,如果出现这样的一个情况的话,我必须收到一封邮件。不管里面多少个子系统,任何一个子系统出现问题,我都可以收到一封邮件。好的,谢谢大家,今天的分享就到这里。
PS:开发者最佳实践日·第8期-互联网产品从设计到上线 北京站
报名地址:http://qiniu-8.eventdove.com