推荐一本计算机基础的科普书:《穿越计算机的迷雾》

小时候经常想,计算器是如何将两个数字加起来的?难道计算器保存了所有的个位数相加的表吗?为什么能将数字显示出来?为什么只能做简单的操作,不能做混合运算(后来知道了一种叫“科学计算器”的东西)?

后来学到了计算机内部使用二进制表示的,但是小时候那种困惑依然没有解决。这个二进制到底是如何应用的?内存里面是有很多点记录了0和1吗?那这些点有多小呢,才能保存那么多数据?这些数据在计算机内部又是怎么取出来的,怎么使用的?

直到同事给我推荐了《穿越计算机的迷雾》这本书,这些困扰我十几年的问题才渐渐明了。当然,如果大学的时候选修计算机组成原理好好上一下的话,可能也早就明白了。对计算机感兴趣,或者对于计算机到底是如何工作还有疑问的同学,非常推荐读一读这本书。这本书语言非常通俗,虽然有些地方需要仔细思考才能跟上,但是作者已经是非常努力的将这些难的东西写明白了。有些地方可能有些啰嗦,像《鸟哥的Linux私房菜》一样,但是也可以当做是幽默吧。

计算机的发明是一个非常漫长的过程,这段历史中有很多伟大的科学家的努力。首先人类先是发现了电,然后发现了电流的磁效应,法拉第又发现了磁能生电,接着开启了用电磁来传递信息的漫长探索之路,摩斯密码,电报,电话因此诞生了。

让计算机做加法这一个简单的操作,也经历了漫长的探索过程。这又和古老的逻辑学分不开,有了逻辑上的真值表和推论,才有可能用二进制表示逻辑问题,将数字逻辑转换成逻辑电路。通过一番精彩的推导,就可以制造出一个电路复杂的全加器。到这里我也对二进制有了一个更深刻的理解:计算机的计算方式和人类是有本质的不同的,人类可以理解复杂的事情,可以处理十进制。计算机天生就是处理很简单的东西,然后使用很简单的东西组合出来很复杂的东西。比如说,计算机当然也可以用十进制,不同的电压(比如0~10v表示1,11~20v表示2……)来表示。但是这样的问题是,我们要设计电路表示1+1=2,1+2=3等等。如果用二进制,只有很有限的几种情况:1-1 1-0 0-0。这种枚举起来简单,大大的简化了电路的设计。

这基本就是 CPU 的原型了:能做简单的计算。借着触发器问世,多个触发器组成的电路可以保存中间结果,支持读写。为了读取这些触发器保存的值,又出现了地址译码器,这样无论读写哪个地址都可以很快,也很简单。这就叫做随机读写(RAM)。

这些基本就是现代计算机的老底了。接下来就是我们熟悉的汇编指令,网络内容等。可以说,电子计算机的发明并不像电灯泡、轮子这种东西一样,经过多次尝试或者从仿生获得的灵感就可以一下子发明出来,电子计算机是无数人经过不断的探索才一步一步走到今天这种形态。我常常会想,现在的生活多么幸福啊,可以跳过那段没有网络,没有现在这种性能的计算机的“黑暗时代”。可是今天的计算机依然在发展,人工智能,区块链这些新技术层出不穷,说不定后人也觉得我们今天的生活是“黑暗的时代”呢。

 

 

部署 Django 项目背后的原理:为什么需要 Nginx 和 Gunicron这些东西?

相信用过 Django 的同学一定会被 “Very easy to setup” 惊艳到。只要一行命令,就可以在 admin 界面看到一个完整的登陆注册。但是到了部署的时候,你一定会被网上复杂的部署教程搞的头晕,为啥本地开发这么简单,到了服务器却需要又是 Nginx,又是 uWSGI 这种东西呢?

摘自The Full Stack Python Guide to Deployments 一书

本文试图解释这些程序在一个 Web 服务中扮演的角色,为什么部署 Python web程序需要它们。本文不介绍如何部署 Django 应用等,这一类教程网上有很多,读者可自行搜索。其中 Nginx 和 Apache 算是一类的,可以替换。Gunicorn 和 uWSGI 的角色是类似的。同理最后端的 Web 框架是一类的,比如 Django 和 Flask。本文的内容替换以上任意一个应该也适用。

如果你读了网上任意一篇教程,你应该知道一个完整的部署应该类似这样:

最后后面的 App 我们知道,只要 runserver 就可以访问了,但是 Django 的文档明确说明:

DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests. (And that’s how it’s gonna stay. We’re in the business of making Web frameworks, not Web servers, so improving this server to be able to handle a production environment is outside the scope of Django.)

即 Django 做的事情只是一个框架,不会去关心一些安全问题、HTTP 的性能问题等。所以我们需要一个专业的 HTTP 服务器。这就出现了 Nginx 或 Apache。那么如何将 HTTP 服务器和我们的应用连接起来呢?动态网站问世的时候,就出现了 CGI 协议。注意这是一个协议,定义了HTTP 服务器如何通过后端的应用获取动态内容。可以简单的理解成 HTTP 服务器通过 CGI 协议调用后端应用吧!WSGI 可以理解成 Python 的 CGI。uWSGI 和 Gunicorn 是这种 WSGI 的一些实现。通过像 uWSGI 和 Gunicorn 的实现,Nginx 这样的通用 web 服务器就可以和 uWSGI 来沟通了。那 Django 呢?上文说到,Django 是一个 Framework,而不是一个 Web Server,Django 只是帮你去写 Web 程序的代码,真正运行的其实是 uWSGI 进程 (而并没有一个 django 进程)。就出现了上面提到的三层的部署。

但是,为什么我们还需要 Nginx 呢?这些 WSGI 程序本身不是也提供访 HTTP 问吗?

是的,uWSGI 和 Gunicorn 本身就是一个便携的 web 服务器了,但是我们一般还是在它们前面档一个更加专业的 HTTP 服务器。Nginx 作为一个经过更长时间验证的 HTTP 服务器来说,它有很多 uWSGI 没有支持的 feature。比如:

  1. 处理静态资源更加优秀,E-Tag 的设置,Gzip 压缩等
  2. 处理网络连接,降低网络负载。例如 reuqest_buffering ,假如部署一个 uWSGI 程序,如果有慢的请求存在,uWSGI 必须等待整个 HTTP 请求发过来之后才开始处理请求。但是如果前置 Nginx,那么 Nginx 会帮你收到整个 HTTP 请求之后才交给 uWSGI 处理,也就是说,uWSGI获得的永远会是完整的 HTTP 请求,不会占用一个线程在等待。(为什么慢请求不会占用 Nginx 一个线程但是会占用 uWSGI 的一个线程,这个是因为 Nginx 是基于 epoll 的异步模型,而 uWSGI 是同步模型,这里面要详细了解的话,就要去搜索和阅读很多东西了)
  3. 甚至缓存动态的内容,例如将博客的首页缓存 5 分钟
  4. 作为一个负载均衡的前置,这样每一层的实例数量可以横向扩展
  5. Nginx 本身是作为一个服务存在的,几乎在任何 Linux 版本上安装之后都可以用 initd 启动,就像 MySQL 那样。uWSGI 一般是需要自己将其配置成服务的。(虽然前置了 Nginx 依然是需要配置 uWSGI)
  6. ……

此外,这样分开的好处还是得,到达 uWSGI 和 Gunicorn 的请求的情况变得简单了很多,Nginx 处理了一层,将过滤和处理之后的请求交给 uWSGI 或 Gunicorn。这使得这些 WSGI 程序的实现简单了一些,简化了开发的工作。专业的事情交给专业的人去做。

当然,并不是所有的项目都需要这么复杂的部署,有一个可选的是将 WSGI 程序嫁接到 HTTP 服务器上,比如 Apache httpd + mod_wsgi, Nginx + mod_uwsgi 等。

参考资料:

  1. 一份比较不错的部署指南
  2. Why do I need Nginx and something like Gunicorn?
  3. Why do I need nginx when I have uWSGI
 

辅助Django开发的一些隐藏资源(文档)

距离上次使用 Django 还是一年前了,最近又回到 Django 上来,慢慢读它的文档,发现了1年之前马马虎虎使用 Django 的时候没有发现的好东西。

Admin可以自动生成文档

大家都知道 Django 可以自动根据你的 Model 生成 admin,但是我还是第一次知道这个 admin 可以生成文档呢。效果如下,可以展示项目的 App 中可以用的 Tags,Model 等。

Admin自动生成的文档首页

对我来说,比较有用的是可以展示某个 Model 的所有可以用的属性,这样就不用翻代码了,一目了然。尤其是对于没有 Model 之间的关系的 related_name ,有一些没有自己定义,即使自己定义了可能也比较乱。通过这个文档来看,这个 Model 上面的可调用的东西就很清晰了。

安装方法:

  1. 将 django.contrib.admindocs 添加到 INSTALLED_APPS
  2. 在 urls.py 中添加  path('admin/doc/', include('django.contrib.admindocs.urls'))
  3. 安装 http://docutils.sourceforge.net/ (pip install docutils)

更多安装信息可以参考 Django文档

Classy Class-Based Views网站

去年写了一篇文章介绍 Django 的 Class-based-View,但是 Django 的 View 看起来比较杂乱,也比较多(好像一旦用上了多继承,就会搞得这样)。想要一个 View 或 Mixin 的时候,经常怀疑自己选择的这个是不是最合适的。

http://ccbv.co.uk 这个网站可以清楚地展示 Django(可以选择 Django 的版本) 所有可以用的 View。每个 View 的详情页展示了这个 View 的属性和可以用的方法(如果用 dir的话,你可能会被显示出的信息吓到)。并且可以查看方法的文档和源码,也可以跳转到 Django 在 Github 的源代码。

还能画出继承关系的类图,详见恨晚。

 

《罪与罚》摘录

去年去参加了 WEPLAY 游戏展,会展上《寂静岭》的音乐制作人山冈晃先生接受采访的时候说,《寂静岭》音乐的灵感来自于一本俄罗斯小说《罪与罚》,所以我决定去读一下这本书。

花了半年时间才读完这本书,读完之后发现,这个情节和游戏中的剧情有很微妙的关系。《罪与罚》中主线讲述的是大学生罗佳迫于生活的穷困,内心又有着伟大的思想,认为杀死一个蝼蚁一般的老太婆,获得短期内的钱来供自己这段时间的发展,对人类社会来说是有益的,就像拿破仑一样。然而,内心是有这样的想法,但是实践起来却畏手畏脚,最终鬼使神差践行了自己的计划,良心却备受谴责。经历了很长时间的煎熬,最后去警察局自首。我认为罗佳的悲剧来自于年纪轻轻却不甘于平庸,急切地想有非常舒适的环境去发展自己的矛盾。现实中的自己无法满足自己对自己的期望,从而对自己产生了恨,表现地对自己的妹妹和妈妈冷漠,对自己的处境也漠不关心。

除了主人公罗佳,书中还描述了很多人的悲剧。无法自制经常跑去饮酒的小官吏,硬生生断送的自己的前程,还有原本幸福的家庭;命途多舛的官吏妻子,被逼卖淫的女儿;被世俗嘲笑,被富婆包养的斯维德里盖洛夫,即使放弃了一切也换不到杜尼娅的芳心,最终自杀;对儿子百般溺爱,精神失常的罗佳的妈妈。

这本书所描述的悲剧如此突出,我觉得很大部分原因是作者陀思妥耶夫斯基喜欢让人物的感情剧烈变化,文章有打断的独白和对话,用了很多语气词,惊叹词,使得这些人物的台词感染力非常强,很容易代入。

读完这本书,也没有什么很特别的想法,就是感受到了这么多悲剧。生者还是好好活着,多考虑一下别人,小心翼翼的,生活太容易破碎了。

以下是我在 Kindle 上划出的一些摘录。

 

这种理论把芸芸众生人为地分为“非凡的人”和“平凡的人”两类:“一类是低级的人(平凡的人),也就是说,可以称之为仅仅是繁殖同类的材料;另一类是真正意义上的人,也就是具有天赋和才干,能在自己所处的社会提出新见解的人。”

个人究竟该以何种方式与途径发展自己的个性呢?

但他犯罪后良心不安,被他蔑视的道德规范暗暗地惩罚着他的灵魂,他噩梦连连,疑神疑鬼,惶惶不可终日,几次打算到警察分局去投案自首。这样,这部小说所写的真正惩罚就是道德与良心的惩罚,它所描绘的是发生在主人公内心深处的道德和心理斗争。

“那是由于没有别的人可找,也没有别的路可走啊!不是吗,任何人总得至少有一条路可走啊。因为有时候一个人必须有条路可走!

此外,要了解任何一个人,都必须一步一步地细心观察,才不致产生错误和偏见,否则,以后纠正错误和消除成见就十分困难了。

啊。当然,即使只吃黑面包只喝白开水,她也不会出卖自己的灵魂,也不会因为耽于舒适而放弃精神方面的自由;

啊,必要时我们会压制我们的道德情感,并且把自由、宁静,甚至良心,所有的一切,都拿到旧货市场上去拍卖。连生命也毫不顾惜!只要我们热爱的人能够幸福。

一种新的、无法克服的感觉以每分钟逐渐增强之势控制了他:这就是对劈面相逢的、环绕四周的一切都怀着某种无比强烈的、几乎是生理性的反感,一种持续不断的、怒气冲冲的、恨之入骨的反感。他憎恶劈面相逢的一切人,——憎恶他们的面孔、步伐、举止。假如有谁来与他攀谈,他简直要啐他一脸唾沫,可能还会咬他一口……

科学却告诉我们:首先你应该只爱自己,因为世界上的一切都以个人利益为基础。你只爱你自己,那么你就会把自己的事情办妥,你的长上衣也就会完整如一。经济学的真理进一步告诉我们,社会上办得好的私人事业越多,也就是说完整如一的长上衣越多,社会的基础就越牢固,公共事业也就会办得越发兴旺。

“您的那个讲师在莫斯科受审时被问到为何伪造有奖债券时,答道:‘大家都千方百计致富,所以我也想快速发财。’原话我记不太清了,但意思就是不劳而获,尽快地大发横财!大家都习惯于坐享其成,以别人的思想为思想,吃别人的现成饭。哈,伟大的时刻来临了,每个人都露出了自己的本性,都在看用什么法子发财……”

他只知道一点:“这一切必须就在今天结束,一次性地结束,立即结束;否则他决不回家,因为他不愿意如此活着。”如何结束?用什么法子结束?对此他毫不知晓,甚至不愿加以考虑。他驱走了这个念头,因为这个念头让他苦恼不已。他只是感觉到并且知道,一切总归都必须改变,这样变或者那样变,“不管怎样变都行”,他怀着天不怕地不怕、无可动摇的自信和决心反复喃喃着这句话。

“没什么可锁的人是幸福的,对吗?”他笑盈盈地对索尼娅说。

比方说,哪怕是人类的立法者和规章制度的创立者,从远古时代,直至后来的莱喀古士[17]、梭伦[18]、穆罕默德[19]、拿破仑等等,无一例外,都是罪犯,唯一的原因在于,他们在制定新法规的同时,也就破坏了世所公认的、神圣不可侵犯的、代代相传的古老法规,而且,当然啰,他们也不会面对流血而停步不前,只要流血(有时流的完全是无辜的、为维护古代的法规而英勇献身者的鲜血)能帮助他们成功。尤其令人注目的是,这些人类的恩人和规章制度的创立者,大多数都是血流成河的特别可怕的屠夫。

第一类人永远是现在的主人,第二类人则是未来的主人。第一类人保存这世界,增殖人口;第二类人则推动世界向前发展,并引导它奔向目的地。无论是第一类人还是第二类人,都有完全相同的生存权利。

“有良心的人一旦意识到自己的错误,就会深感痛苦。这也是对他的一种苦役之外的惩罚。”

追求进步,并跻身于“我们的年轻的一代”的行列——完全是出于一时的青春激情。他是那些数不胜数、各式各样的言行庸俗、思想幼稚、志大才疏而又刚愎自用者之一,这类人对于最流行的时髦思想必定是顷刻间便趋之若鹜,紧紧依附,为的是立即把它庸俗化,并在一瞬间把他们有时竭诚效劳的一切漫画化。

也许,在这件事上起决定作用的是那种特殊的穷人的自尊心。就是因为这种自尊心,千千万万的穷人每逢日常生活中人人必须遵守的某些社会习俗时,都会倾箱倒箧地把自己节衣缩食积攒起来的一点点钱全都花光,只是为了证明自己“毫不逊色于别人”,以及不让那些人对他们横加“指责”。

你们还不知道,还不知道,这是怎样的一颗心灵,这是怎样的一个姑娘!她竟会偷钱,她!如果你们需要的话,她为了济人之难会脱下自己身上的最后一件衣服,光着脚去把它卖掉,再把钱送给你们,瞧啊她就是这样一个姑娘!因为我的孩子啼饥号寒,她甚至去领了黄色执照,她完全是为了我们才去出卖自己的呀!……哎哟,死鬼,死鬼呀!哎哟,死鬼,死鬼呀!你看见了吗?你看见了吗?这就是为你办的丧后酬客宴!上帝啊!你们倒是保护保护她呀,究竟为什么光是站着呢!罗季昂·罗曼诺维奇!您为什么不出来主持正义啊?难道连您都不相信她?你们连她的一个小指头都抵不上,你们所有的人,所有的人,所有的人,所有的人!上帝啊!最后只好请您保护她了!”

你瞧:你已经知道,我母亲几乎是一无所有。妹妹侥幸受了些教育,也注定只能东奔西跑当家庭教师。她们把一切希望都寄托在我的身上。我已经上了大学,但在大学里我无法维持自己的生活,迫不得已只好暂时退学。假如我就这样勉强拖下去,那么十年二十年之后(如果天从人愿,情况好转),我仍然有希望当上一名教师,或者成为一个官吏,可以拿到一千卢布的年薪……(他似乎是倒背如流)然而到那个时候,母亲早已因为操劳和愁苦而憔悴不堪了,我还是不能使她感到安慰,而妹妹……唉,妹妹的情况也许更糟!……难道竟然可以一辈子对这一切都漠不关心,置若罔闻,把母亲忘记到九霄云外,让妹妹低首下心地,譬如说吧,任人侮辱?为了什么?是不是为了在埋葬她们以后,挣钱去养活其他的人——妻子和孩子,而以后又一文不名地让他们活在世上餐风饮露?唔……唔,于是我下定决心要占有老太婆的钱,把它们用做我最近几年的费用,不再使母亲担忧受苦,用这些钱维持我大学的生活,实现我大学毕业后最初的一些计划——大刀阔斧、不遗余力地彻底改变这一切,为自己开创一个崭新的前程,踏上一条独立自主的新路……唔……唔,我要说的就全在这里了……唔,当然喽,我杀死了这个老太婆,——我做了一件坏事……唉,不说了!”

一百只兔子永远拼不成一匹马,一百个疑点永远拼不成一个证据,有句英国谚语不是这样说的吗?不过这仅仅是一种理智的说法,可是一时冲动起来的感情是难以控制的。

其实,要对某些人做出公正的评判,就必须预先放弃一些先入的偏见,改变那种对待我们周围的人和事的习惯态度。

最后,我采取了一个征服女人的心的最高明、最有效的绝招,这个绝招永远都不会使人失望,对任何人都绝对管用,无一例外。这个绝招是众所周知的,它就是阿谀奉承。世界上最难的事是开诚相见,而最容易的事是阿谀奉承。开诚相见,只要有百分之一的虚假,那么马上就会出现不和谐,而麻烦就会随之而来。至于阿谀奉承,哪怕从头至尾都是虚假,但还是令人感到高兴,听起来不会不舒服;哪怕觉得肉麻,但还是觉得舒服。不管阿谀奉承是如何肉麻,其中肯定至少有一半使人觉得真实。

“罪行?什么罪行?”他突然出人意外地狂叫,“我杀死的是一只可恶的、有害的虱子,一个对任何人都没有益处的放高利贷的老太婆,一个穷人的吸血鬼,杀了她可以赎四十桩罪行,这也算是犯罪吗?我可不认为这是罪行,也没有想去赎罪。为什么大家都指着我说:‘犯罪,犯罪!’直到现在我才明白,我的胆怯是非常愚蠢的,现在我已决定去受这种不应该受到的耻辱!我这样做,只是由于自己的卑鄙和无能,也许还由于这个……波尔菲里建议的好处!……”

但是他对自己进行了严格的审判,他那变得冷酷无情的良心,在他以往的行为中没有发现任何特别严重的罪过,除了人人都免不了的一般的失误以外。

 

Gitlab自动部署方案(Systemd+nc)

Gitlab 的 CI 还是比较有好的,只要配置好一些 Shared Runner,所有的项目都可以共享这些 Runner 来跑 CI 了,这样比较节省资源,这些 Runner 勤勤恳恳地跑完了一个就去跑下一个。

但是 CD 自动部署稍稍有些麻烦,Gitlab 这边能做到的就是跑完所有的测试之后发送一个 Curl 通知,告诉 Build 所有的测试通过了,可以开始部署了。.gitlab-ci.yml 文件类似下面:

这样 Runner 在所有的 test stage 执行完了之后,就会执行 deploy 这个 stage。

最后 deploy 这个地方只写了一个 curl ,那么接收 Gitlab 的通知,然后执行部署这个步骤就有些麻烦了。可能需要一个 HTTP 服务监听端口,在网上找了一些方案,有用 PHP 写的脚本,也有用 Python 写的,但是这样就有了更多的依赖要维护了。

找了一圈,发现 netcat 这个小巧的工具就能实现监听端口->响应请求->执行脚本部署的需求。

下面的命令可以一直监听 9999 端口,有请求的话就响应 echo 的内容。然后结束命令。

配合 && 连接起来,就可以做到通过 Curl 触发部署操作了。

通过 systemd,可以将这个脚本管理起来,让它永远重启,这样一次部署之后,马上就可以重新监听,等待下一次部署命令。注意要添加 StartLimitInterval ,限制一下执行的频率。最终的 systemd service 如下:

这样就可以实现每次向 master push 代码,自动测试成功并且马上推送到测试环境中。 update.sh 脚本的最后可以加一个 Curl 命令向钉钉或者 slack 发送提醒。