酷 壳 – CoolShell https://coolshell.cn 享受编程和技术所带来的快乐 - Coding Your Ambition Mon, 22 Jul 2019 01:24:52 +0000 zh-CN hourly 1 https://wordpress.org/?v=5.1.1 50年前的登月程序和程序员有多硬核 https://coolshell.cn/articles/19612.html https://coolshell.cn/articles/19612.html#comments Sun, 21 Jul 2019 11:00:30 +0000 https://coolshell.cn/?p=19612 Read More Read More

]]>
2019年7月20日,是有纪念意义的一天,这天不是因为广大网民帮周杰伦在新浪微博上的超话刷到第一,而是阿波罗登月的50周年的纪念日。早在几年前,在Github上放出了当成Apollo飞船使用的源代码(当然是汇编的),但完全不明白为什么这几天会有一些中国的小朋友到这个github的issue里用灌水……,人类历史上这么伟大的一件事,为什么不借这个机会学习一下呢?下面是一些阿波罗登月与程序员相关的小故事,顺着这些东西,你可以把你的周末和精力用得更有价值。

首先,要说的是Apollo 11导航的源代码,这些代码的设计负责人叫Margaret Heafield Hamilton ,是一个女程序员,专业是数学和哲学,1960年得到一个MIT麻省理工大学的临时的软件开发职位,负责在PDP-1和LGP-30上运行天气预报的软件(注:在计算机历史上,PDP系统机器被称为Hack文化的重要推手,PDP-11推了Unix操作系统,而Unix操作系统则是黑客文化的重要产品。参看《Unix传奇》)。然后,她又为美国空军编写探测知敌方飞行的软件,之后,于1965年的时候,她加入了MIT仪器实验室,并成为了这个实验室的主管,这个实验实就是Apollo计划的一部分,她负责编写全新的月球登录的导航软件,以及后来该软件在其他项目中的各个版本。

上图是Hamilton站在她和她的麻省理工团队为阿波罗项目制作的导航软件源代码旁边,在Github上的开源的代码 – Apollo-11 (2016年开源)。我们可以看到,有两个重要的目录,一个目录叫“Comanche055”,一个目录叫“Luminary099”,前者是指挥舱用的(英文为 Command Module )后者为登月舱用的(英文为 Lunar Module),这里需要说明一下的是,指挥舱是把登录舱推到月球上,在返回的时候,登录舱是被抛弃掉的,而返回到地球的是指挥舱。如果你想看这两份源代码的纸版,你可以访问这两个链接:Comanche 55 AGC Program ListingLuminary 99 REv.1 AGC Program Listing。其中的55 和 90 是各自的build 版本号。

我们细看一下,这些文件的日期是,1969年7月14日,而Apollo 11登月的日期是1969年7月16日起程,7月19日经过月球背面,7月20日下午8点登月。代码写好,两天后就直接上生产,然后就登月,还是导航代码,这代码写的的健壮性得有多强。

如果你仔细比较一下这两个目录中的文件,你会发现有些文件是一样的,不但文件名一样,而且内容也一样。这说明这两个模块中的一些东西是相似的。

这些代码应该是很难读了,因为当时写这些代码的时候,C语言都没有被发明,所以基本上来说都是汇编代码了,而且还可以发现,这些代码的源文件全是以agc后缀结尾的, 看来这还不是我们平时所了解的汇编,所谓的AGC代表了运行这些代码的计算机 – Apollo Guideance Computer 。沿着这个Wikipedia的链接,你可以看到AGC这个电脑的指令是什么样的,看懂那几条指令后,这些源代码也就能读懂了。当然,因为是写成汇编的,所以,读起来还是要费点神的。不过,其中有一个文件是 LUNAR_LANDING_GUIDANCE_EQUATIONS.agc 你会不会很好奇想去看看?

打开源文件,你还可以看到每个文件都有很多很多的注释,非常友好,但是也有一些注释比较有趣。这里有一组短视频带你读这些代码 – Exploring the Apollo Guidance Computer(AGC) Code,一供10个小视频,每个2分钟左右,如果你英文听边还行(我觉得很容易听懂),可以看看,了解一下AGC的工作方式,挺有趣意思的。

当时的AGC有32公斤,主频只有2MHz,2K的RAM,36K的ROM。嗯,当年就是这么一个小玩意,把人送上了月球,今天,一个聊天程序就占内存几GB……

下面是AGC在Apollo 1指挥舱里的样子(图片截自上面的视频),这个高质量的3D扫描来自 Simithsonian 3D: Apollo 11 Command Module (我觉得美国人干这些事干就是很漂亮啊,这种高清的3D扫描太牛了,如果你仔细看,这个舱里还有宇航员在舱壁上的手写)

这个AGC的操作界面又叫DSKY – Display 和 Keyboard的缩写,下图是一个 AGC 模拟器,其官方主页在 https://www.ibiblio.org/apollo/源代码在 Github/VirtualAGC。在这个界面上我们可以看到:下面的键盘上左边有两个键,一个是动词Verb一个是名词Noun,Verb指定操作类型,Noun指定要由Verb命令修改的数据。右边的显示器下面有三个5位的数字,这三个数值显示表示航天器姿态的矢量,以及所需速度变化的显示矢量。是的,当年的导航就靠这三个数字和里面的程序了。

 

如果你想了解AGC更多的细节,你可以看看 这篇 AGC for Dummies。这篇文章讲述了AGC这个嵌入式系统的背景和操作指令。一份详细的AGC 汇编语言手册可以让你了解更多的细节。

另外,我在Youtube上找到了一个讲当时Apollo电脑的纪录片 – Navigation Computer,太有趣了。比如:21分51秒开始讲存储用的 Rope Memory 绕线内存,Hamilton 也出来讲了一下在这种内存上编程,画面切到一个人用个比较长的金属针在一个像主板一样的东西上,左右穿梭,就像刺绣一样,但是绣的不是图案,而是程序……太硬核了,真正的通过“硬编织”的方式来写程序。

看完上面这个纪录篇,我是非常之惊叹,惊叹于50年前的工程能力,惊叹于50年前这些人面对技术的的一丝不苟,对技术的尊重和严谨的这种精神和方法,一点都不比较今天差。

不过,最牛的还不是这个,我在Hamilton的Wikipedia词条上找到了他说的一个事件—— 当年Apollo登陆雷达开关放在了错误的位置,导致AGC收到了不少错误的信号。结果就是AGC既得执行着陆必须的计算,又要接受这些占用其15%时间的额外数据。但是AGC的程序居然可以用高优先级的任务打断低优先级的任务,于是,AGC自动剔除了低级别的任务以保证了重要的任务完成。Hamilton 原话说—— 如果当时的程序不能识别错误并从错误中恢复,我怀疑阿波罗不能成功登月。if the computer hadn’t recognized this problem and taken recovery action, I doubt if Apollo 11 would have been the successful moon landing it was。

看到这里,你有没有觉得——“这个女程序员的一小步,是整个人类的一大步”?

Hamilton 的牛逼之外还在于,她是第一个将“软件工程”提出来的人,在MIT,她想让软件开发就像其它工程一样,有相应的工程纪律,给于相关的尊重,于是她创造了Software Engineering这个词。2018年,IEEE在纪念软件工程50周年的时候,他们把 Hamilton 请过去讲了一个叫 What the Errors Tell Us 的主题。她绝对可以称得上是程序员的Pioneer。

三年前,Apollo的源代码被开源时候,Twitter有个叫 Lin Clark 的人发了一条推:“我妈50年前的代码被放到Github上了”,虽然,她不是 Hamilton 的女儿,但她妈妈也是Apolload其中一个程序员,现在Lin Clark同样也是一个程序员,目前在 Mozilla工作,Staff Engineer,专长 WebAssembly, Rust, 和 JavaScript ,也是个非常厉害的程序,Youtube上各种演讲,也是一个跟他妈妈一样牛的人。

当她在Twitter上这么自豪地发了一条这样的推后,我不知道各位有什么想法?想不想你的后代在未来也会这样自豪的发条微博?

 

最后,尤其是想对那些到Apollo源代码的issue里发spam垃圾信息的人说一下,你看看人家,再看看你们自己,你们是不是想让你们的孩子在登月100周年纪念的时候说——50年前我爹那个傻叉在Apollo的github的issue列表时写了些垃圾,还以为自己多机灵?!

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19612.html/feed 28
如何超过大多数人 https://coolshell.cn/articles/19464.html https://coolshell.cn/articles/19464.html#comments Sat, 22 Jun 2019 05:47:57 +0000 https://coolshell.cn/?p=19464 Read More Read More

]]>
当你看到这篇文章的标题,你一定对这篇文章产生了巨大的兴趣,因为你的潜意识在告诉你,这是一本人生的“武林秘籍”,而且还是左耳朵写的,一定有干货满满,只要读完,一定可以练就神功并找到超过大多数人的快车道和捷径……然而…… 当你看到我这样开篇时,你一定会觉得我马上就要有个转折,告诉你这是不可能的,一切都需要付出和努力……然而,你错了,这篇文章还真就是一篇“秘籍”,只要你把这些“秘籍”用起来,你就一定可以超过大多数人。而且,这篇文章只有我这个“人生导师”可以写得好。毕竟,我的生命过到了十六进制2B的年纪,踏入这个社会已超过20年,舍我其谁呢?!

P.S. 这篇文章借鉴于《如何写出无法维护的代码》一文的风格……嘿嘿

相关技巧和最佳实践

要超过别人其实还是比较简单的,尤其在今天的中国,更是简单。因为,你只看看中国的互联网,你就会发现,他们基本上全部都是在消费大众,让大众变得更为地愚蠢和傻瓜。所以,在今天的中国,你基本上不用做什么,只需要不使用中国互联网,你就很自然地超过大多数人了。当然,如果你还想跟他们彻底拉开,甩他们几个身位,把别人打到底层,下面的这些“技巧”你要多多了解一下。

在信息获取上,你要不断地向大众鼓吹下面的这些事:

  • 让大家都用百度搜索引擎查找信息,订阅微信公众号或是到知乎上学习知识……要做到这一步,你就需要把“百度一下”挂在嘴边,然后要经常在群或朋友圈中转发微信公众号的文章,并且转发知乎里的各种“如何看待……”这样的文章,让他们爱上八卦,爱上转发,爱上碎片。
  • 让大家到微博或是知识星球上粉一些大咖,密切关注他们的言论和动向……是的,告诉大家,大咖的任何想法一言一行都可以在微博、朋友圈或是知识星球上获得,让大家相信,你的成长和大咖的见闻和闲扯非常有关系,你跟牛人在一个圈子里你也会变牛。
  • 把今日头条和抖音这样的APP推荐给大家……你只需要让你有朋友成功地安装这两个APP,他们就会花大量的时间在上面,而不能自拔,要让他们安装其实还是很容易的,你要不信你就装一个试玩一会看看(嘿嘿嘿)。
  • 让大家热爱八卦,八卦并不一定是明星的八卦,还可以是你身边的人,比如,公司的同事,自己的同学,职场见闻,社会热点,争议话题,……这些东西总有一些东西会让人心态有很多微妙的变化,甚至花大量的时间去搜索和阅读大量的观点,以及花大量时间与人辩论争论,这个过程会让人上瘾,让人欲罢不能,然而这些事却和自己没有半毛钱关系。你要做的事就是转发其中一些SB或是很极端的观点,造成大家的一睦讨论后,就早早离场……
  • 利用爱国主义,让大家觉得不用学英文,不要出国,不要翻墙,咱们已经是强国了……这点其实还是很容易做到的,因为学习是比较逆人性的,所以,只要你鼓吹那些英文无用论,出国活得更惨,国家和民族都变得很强大,就算自己过得很底层,也有大国人民的感觉。

然后,在知识学习和技能训练上,让他们不得要领并产生幻觉

  • 让他们混淆认识和知识,以为开阔认知就是学习,让他们有学习和成长的幻觉……
  • 培养他们要学会使用碎片时间学习。等他们习惯利用碎片时间吃快餐后,他们就会失去精读一本书的耐性……
  • 不断地给他们各种各样“有价值的学习资料”,让他们抓不住重点,成为一个微信公众号或电子书“收藏家”……
  • 让他们看一些枯燥无味的基础知识和硬核知识,这样让他们只会用“死记硬背”的方式来学习,甚至直接让他们失去信心,直接放弃……
  • 玩具手枪是易用的,重武器是难以操控的,多给他们一些玩具,这样他们就会对玩具玩地得心应手,觉得玩玩具就是自己的专业……
  • 让他们喜欢直接得到答案的工作和学习方式,成为一个伸手党,从此学习再也不思考……
  • 告诉他们东西做出来就好了,不要追求做漂亮,做优雅,这样他们就会慢慢地变成劳动密集型……
  • 让他们觉得自己已经很努力了,剩下的就是运气,并说服他们去‘及时行乐’,然后再也找不到高阶和高效率学习的感觉……
  • 让他们觉得“读完书”、“读过书”就行了,不需要对书中的东西进行思考,进行总结,或是实践,只要囫囵吞枣尽快读完就等同于学好了……

最后,在认知和格局上,彻底打垮他们,让他们变成韭菜。

  • 让他们不要看到大的形势,只看到眼前的一亩三分地,做好一个井底之蛙。其实这很简单,比如,你不要让他们看到整个计算机互联网技术改变人类社会的趋势,你要多让他看到,从事这一行业的人有多苦逼,然后再说一下其它行业或职业有多好……
  • 宣扬一夜暴富以及快速挣钱的案例,最好让他们进入“赌博类”或是“传销类”的地方,比如:股市、数字货币……要让他们相信各种财富神话,相信他们就是那个幸运儿,他们也可以成为巴菲特,可以成为马云……
  • 告诉他们,一些看上去很难的事都是有捷径的,比如:21天就能学会机器学习,用区块链就能颠覆以及重构整个世界等等……
  • 多跟他们讲一些小人物的励志的故事,这样让他们相信,不需要学习高级知识,不需要掌握高级技能,只需要用低等的知识和低级的技能,再加上持续不断拼命重复现有的工作,终有一天就会成功……
  • 多让他们跟别人比较,人比人不会气死人,但是会让人变得浮躁,变得心急,变得焦虑,当一个人没有办法控制自己的情绪,没有办法让自己静下心来,人会失去耐性和坚持,开始好大喜欢功,开始装逼,开始歪门邪道剑走偏锋……
  • 让他们到体制内的一些非常稳定的地方工作,这样他们拥有不思进取、怕承担责任、害怕犯错、喜欢偷懒、得过且过的素质……
  • 让他们到体制外的那些喜欢拼命喜欢加班的地方工作,告诉他们爱拼才会赢,努力加班是一种福报,青春就是用来拼的,让他们喜欢上使蛮力的感觉……
  • 告诉他们你的行业太累太辛苦,干不到30岁。让他们早点转行,不要耽误人生和青春……
  • 当他们要做决定的时候,一定要让他们更多的关注自己会失去的东西,而不是会得到的东西。培养他们患得患失心态,让他们认识不到事物真正的价值,失去判断能力……(比如:让他们觉得跟对人拍领导的马屁忠于公司比自我的成长更有价值)
  • 告诉他们,你现有的技能和知识不用更新,就能过好一辈子,新出来的东西没有生命力的……这样他们就会像我们再也不学习的父辈一样很快就会被时代所抛弃……
  • 每个人都喜欢在一些自己做不到的事上找理由,这种能力不教就会,比如,事情太多没有时间,因为工作上没有用到,等等,你要做的就是帮他们为他们做不到的事找各种非常合理的理由,比如:没事的,一切都是最好的安排;你得不到的那个事没什么意思;你没有面好主要原因是那个面试官问的问题都是可以上网查得到的知识,而不没有问到你真正的能力上;这些东西学了不用很快会忘了,等有了环境再学也不迟……

最后友情提示一下,上述的这些“最佳实践”你要小心,是所谓,贩毒的人从来不吸毒,开赌场的人从来不赌博!所以,你要小心别自己也掉进去了!这就是“欲练神功,必先自宫”的道理。

相关原理和思维模型

对于上面的这些技巧还有很多很多,你自己也可以发明或是找到很多。所以,我来讲讲这其中的一些原理。

一般来说,超过别人一般来说就是两个维度:

  1. 在认知、知识和技能上。这是一个人赖以立足社会的能力(参看《程序员的荒谬之言还是至理名言?》和《21天教你学会C++》)
  2. 在领导力上。所谓领导力就是你跑在别人前面,你得要有比别人更好的能力更高的标准(参看《技术人员发展之路》)

首先,我们要明白,人的技能是从认识开始,然后通过学校、培训或是书本把“零碎的认知”转换成“系统的知识”,而有要把知识转换成技能,就需要训练和实践,这样才能完成从:认识 -> 知识 -> 技能 的转换。这个转换过程是需要耗费很多时间和精力的,而且其中还需要有强大的学习能力和动手能力,这条路径上有很多的“关卡”,每道关卡都会过滤掉一大部分人。比如:对于一些比较枯燥的硬核知识来说,90%的人基本上就倒下来,不是因为他们没有智商,而是他们没有耐心。

认知

要在认知上超过别人,就要在下面几个方面上做足功夫:

1)信息渠道。试想如果别人的信息源没有你的好,那么,这些看不见信息源的人,只能接触得到二手信息甚至三手信息,只能获得被别人解读过的信息,这些信息被三传两递后必定会有错误和失真,甚至会被传递信息的中间人hack其中的信息(也就是“中间人攻击”),而这些找不出信息源的人,只能“被人喂养”,于是,他们最终会被困在信息的底层,永世不得翻身。(比如:学习C语言,放着原作者K&R的不用,硬要用错误百出谭浩强的书,能有什么好呢?)

2)信息质量。信息质量主要表现在两个方面,一个是信息中的燥音,另一个是信息中的质量等级,我们都知道,在大数据处理中有一句名言,叫 garbage in garbage out,你天天看的都是垃圾,你的思想和认识也只有垃圾。所以,如果你的信息质量并不好的话,你的认知也不会好,而且你还要花大量的时间来进行有价值信息的挖掘和处理。

3)信息密度。优质的信息,密度一般都很大,因为这种信息会逼着你去干这么几件事,a)搜索并学习其关联的知识,b)沉思和反省,c)亲手去推理、验证和实践……一般来说,经验性的文章会比知识性的文章会更有这样的功效。比如,类似于像 Effiective C++/Java,设计模式,Unix编程艺术,算法导论等等这样的书就是属于这种密度很大的书,而像Netflix的官方blogAWS CTO的blog等等地方也会经常有一些这样的文章。

知识

要在知识上超过别人,你就需要在下面几个方面上做足功夫:

1)知识树(图)。任何知识,只在点上学习不够的,需要在面上学习,这叫系统地学习,这需要我们去总结并归纳知识树或知识图,一个知识面会有多个知识板块组成,一个板块又有各种知识点,一个知识点会导出另外的知识点,各种知识点又会交叉和依赖起来,学习就是要系统地学习整个知识树(图)。而我们都知道,对于一棵树来说,“根基”是非常重要的,所以,学好基础知识也是非常重要的,对于一个陌生的地方,有一份地图是非常重要的,没有地图的你只会乱窜,只会迷路、练路、走冤枉路!

2)知识缘由。任何知识都是有缘由的,了解一个知识的来龙去脉和前世今生,会让你对这个知识有非常强的掌握,而不再只是靠记忆去学习。靠记忆去学习是一件非常糟糕的事。而对于一些操作性的知识(不需要了解由来的),我把其叫操作知识,就像一些函数库一样,这样的知识只要学会查文档就好了。能够知其然,知其所以然的人自然会比识知识到表皮的人段位要高很多。

3)方法套路。学习不是为了找到答案,而是找到方法。就像数学一样,你学的是方法,是解题思路,是套路,会用方程式解题的和不会用方程式解题的在解题效率上不可比较,而在微积分面前,其它的解题方法都变成了渣渣。你可以看到,掌握高级方法的人比别人的优势有多大,学习的目的就是为了掌握更为高级的方法和解题思路

技能

要在技能上超过别人,你就需要在下面几个方面做足功夫:

1)精益求精。如果你想拥有专业的技能,你要做不仅仅是拼命地重复一遍又一遍的训练,而是在每一次重复训练时你都要找到更好的方法,总结经验,让新的一遍能够更好,更漂亮,更有效率,否则,用相同的方法重复,那你只不过在搬砖罢了。

2)让自己犯错。犯错是有利于成长的,这是因为出错会让人反思,反思更好的方法,反思更完美的方案,总结教训,寻求更好更完美的过程,是技能升级的最好的方式。尤其是当你在出错后,被人鄙视,被人嘲笑后,你会有更大的动力提升自己,这样的动力才是进步的源动力。当然,千万不要同一个错误重复地犯!

3)找高手切磋。下过棋,打个球的人都知道,你要想提升自己的技艺,你必需找高手切磋,在和高手切磋的过程中你会感受到高手的技能和方法,有时候你会情不自禁地哇地一下,我靠,还可以这么玩!

领导力

最后一个是领导力,要有领导力或是影响力这个事并不容易,这跟你的野心有多大,好胜心有多强 ,你愿意付出多少很有关系,因为一个人的领导力跟他的标准很有关系,因为有领导力的人的标准比绝大多数人都要高。

1)识别自己的特长和天赋。首先,每个人DNA都可能或多或少都会有一些比大多数人NB的东西(当然,也可能没有),如果你有了,那么在你过去的人生中就一定会表现出来了,就是那种大家遇到这个事会来请教你的寻求你帮助的现象。那种,别人要非常努力,而且毫不费劲的事。一旦你有了这样的特长或天赋,那你就要大力地扩大你的领先优势,千万不要进到那些会限制你优势的地方。你是一条鱼,你就一定要把别人拉到水里来玩,绝对不要去陆地上跟别人拼,不断地在自己的特长和天赋上扩大自己的领先优势,彻底一骑绝尘。

2)识别自己的兴趣和事业。没有天赋也没有问题,还有兴趣点,都说兴趣是最好的老师,当年,Linus就是在学校里对minx着迷了,于是整出个Linux来,这就是兴趣驱动出的东西,一般来说,兴趣驱动的事总是会比那些被动驱动的更好。但是,这里我想说明一下什么叫“真∙兴趣”,真正的兴趣不是那种三天热度的东西,而是那种,你愿意为之付出一辈子的事,是那种无论有多大困难有多难受你都要死磕的事,这才是“真∙兴趣”,这也就是你的“野心”和“好胜心”所在,其实上升到了你的事业。相信我,绝大多数人只有职业而没有事业的。

3)建立高级的习惯和方法。没有天赋没有野心,也还是可以跟别人拼习惯拼方法的,只要你有一些比较好的习惯和方法,那么你一样可以超过大多数人。对此,在习惯上你要做到比较大多数人更自律,更有计划性,更有目标性,比如,每年学习一门新的语言或技术,并可以参与相关的顶级开源项目,每个月训练一个类算法,掌握一种算法,每周阅读一篇英文论文,并把阅读笔记整理出来……自律的是非常可怕的。除此之外,你还需要在方法上超过别人,你需要满世界的找各种高级的方法,其中包括,思考的方法,学习的方法、时间管理的方法、沟通的方法这类软实力的,还有,解决问题的方法(trouble shooting 和 problem solving),设计的方法,工程的方法,代码的方法等等硬实力的,一开始照猫画虎,时间长了就可能会自己发明或推导新的方法。

4)勤奋努力执着坚持。如果上面三件事你都没有也没有能力,那还有最后一件事了,那就是勤奋努力了,就是所谓的“一万小时定律”了(参看《21天教你学会C++》中的十年学编程一节),我见过很多不聪明的人,悟性也不够(比如我就是一个),别人学一个东西,一个月就好了,而我需要1年甚至更长,但是很多东西都是死的,只要肯花时间就有一天你会搞懂的,耐不住我坚持十年二十年,聪明的人发明个飞机飞过去了,笨一点的人愚公移山也过得去,因为更多的人是懒人,我不用拼过聪明人,我只用拼过那些懒人就好了。

好了,就这么多,如果哪天你变得消极和不自信,你要来读读我的这篇文章,子曰:温故而知新。

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19464.html/feed 71
HTTP API 认证授权术 https://coolshell.cn/articles/19395.html https://coolshell.cn/articles/19395.html#comments Thu, 09 May 2019 13:37:29 +0000 https://coolshell.cn/?p=19395 Read More Read More

]]>
我们知道,HTTP是无状态的,所以,当我们需要获得用户是否在登录的状态时,我们需要检查用户的登录状态,一般来说,用户的登录成功后,服务器会发一个登录凭证(又被叫作Token),就像你去访问某个公司,在前台被认证过合法后,这个公司的前台会给你的一个访客卡一样,之后,你在这个公司内去到哪都用这个访客卡来开门,而不再校验你是哪一个人。在计算机的世界里,这个登录凭证的相关数据会放在两种地方,一个地方在用户端,以Cookie的方式(一般不会放在浏览器的Local Storage,因为这很容易出现登录凭证被XSS攻击),另一个地方是放在服务器端,又叫Session的方式(SessonID存于Cookie)。

但是,这个世界还是比较复杂的,除了用户访问,还有用户委托的第三方的应用,还有企业和企业间的调用,这里,我想把业内常用的一些 API认证技术相对系统地总结归纳一下,这样可以让大家更为全面的了解这些技术。注意,这是一篇长文!

本篇文章会覆盖如下技术:

  • HTTP Basic
  • Digest Access
  • App Secret Key + HMAC
  • JWT – JSON Web Tokens
  • OAuth 1.0 – 3 legged & 2 legged
  • OAuth 2.0 – Authentication Code & Client Credential

HTTP Basic

HTTP Basic 是一个非常传统的API认证技术,也是一个比较简单的技术。这个技术也就是使用 usernamepassword 来进行登录。整个过程被定义在了 RFC 2617 中,也被描述在了 Wikipedia: Basic Access Authentication 词条中,同时也可以参看 MDN HTTP Authentication

其技术原理如下:

  1. usernamepassword 做成  username:password 的样子(用冒号分隔)
  2. 进行Base64编码。Base64("username:password") 得到一个字符串(如:把 haoel:coolshell 进行base64 后可以得到 aGFvZW86Y29vbHNoZWxsCg
  3. aGFvZW86Y29vbHNoZWxsCg放到HTTP头中 Authorization 字段中,形成 Authorization: Basic aGFvZW86Y29vbHNoZWxsCg,然后发送到服务端。
  4. 服务端如果没有在头里看到认证字段,则返回401错,以及一个个WWW-Authenticate: Basic Realm='HelloWorld' 之类的头要求客户端进行认证。之后如果没有认证通过,则返回一个401错。如果服务端认证通过,那么会返回200。

我们可以看到,使用Base64的目的无非就是为了把一些特殊的字符给搞掉,这样就可以放在HTTP协议里传输了。而这种方式的问题最大的问题就是把用户名和口令放在网络上传,所以,一般要配合TLS/SSL的安全加密方式来使用。我们可以看到 JIRA Cloud 的API认证支持HTTP Basic 这样的方式。

但我们还是要知道,这种把用户名和密码同时放在公网上传输的方式有点不太好,因为Base64不是加密协议,而是编码协议,所以就算是有HTTPS作为安全保护,给人的感觉还是不放心。

Digest Access

中文称“HTTP 摘要认证”,最初被定义在了 RFC 2069 文档中(后来被 RFC 2617 引入了一系列安全增强的选项;“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数)。

其基本思路是,请求方把用户名口令和域做一个MD5 –  MD5(username:realm:password) 然后传给服务器,这样就不会在网上传用户名和口令了,但是,因为用户名和口令基本不会变,所以,这个MD5的字符串也是比较固定的,因此,这个认证过程在其中加入了两个事,一个是 nonce 另一个是 qop

  • 首先,调用方发起一个普通的HTTP请求。比如:GET /coolshell/admin/ HTTP/1.1
  • 服务端自然不能认证能过,服务端返回401错误,并且在HTTP头里的 WWW-Authenticate 包含如下信息:
 WWW-Authenticate: Digest realm="testrealm@host.com",
                        qop="auth,auth-int",
                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                        opaque="5ccc069c403ebaf9f0171e9517f40e41"
  • 其中的 nonce 为服务器端生成的随机数,然后,客户端做 HASH1=MD5(MD5(username:realm:password):nonce:cnonce) ,其中的 cnonce 为客户端生成的随机数,这样就可以使得整个MD5的结果是不一样的。
  • 如果 qop 中包含了 auth ,那么还得做  HASH2=MD5(method:digestURI) 其中的 method 就是HTTP的请求方法(GET/POST…),digestURI 是请求的URL。
  • 如果 qop 中包含了 auth-init ,那么,得做  HASH2=MD5(method:digestURI:MD5(entityBody)) 其中的 entityBody 就是HTTP请求的整个数据体。
  • 然后,得到 response = MD5(HASH1:nonce:nonceCount:cnonce:qop:HASH2) 如果没有 qopresponse = MD5(HA1:nonce:HA2)
  • 最后,我们的客户端对服务端发起如下请求—— 注意HTTP头的 Authorization: Digest ...
GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username="Mufasa",
                     realm="testrealm@host.com",
                     nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                     uri="%2Fcoolshell%2Fadmin",
                     qop=auth,
                     nc=00000001,
                     cnonce="0a4f113b",
                     response="6629fae49393a05397450978507c4ef1",
                     opaque="5ccc069c403ebaf9f0171e9517f40e41"

维基百科上的 Wikipedia: Digest access authentication 词条非常详细地描述了这个细节。

摘要认证这个方式会比之前的方式要好一些,因为没有在网上传递用户的密码,而只是把密码的MD5传送过去,相对会比较安全,而且,其并不需要是否TLS/SSL的安全链接。但是,别看这个算法这么复杂,最后你可以发现,整个过程其实关键是用户的password,这个password如果不够得杂,其实是可以被暴力破解的,而且,整个过程是非常容易受到中间人攻击——比如一个中间人告诉客户端需要的 Basic 的认证方式 或是 老旧签名认证方式(RFC2069)。

App Secret Key + HMAC

先说HMAC技术,这个东西来自于MAC – Message Authentication Code,是一种用于给消息签名的技术,也就是说,我们怕消息在传递的过程中被人修改,所以,我们需要用对消息进行一个MAC算法,得到一个摘要字串,然后,接收方得到消息后,进行同样的计算,然后比较这个MAC字符串,如果一致,则表明没有被修改过(整个过程参看下图)。而HMAC – Hash-based Authenticsation Code,指的是利用Hash技术完成这一工作,比如:SHA-256算法。

 

(图片来自 Wikipedia – MAC 词条

我们再来说App ID,这个东西跟验证没有关系,只是用来区分,是谁来调用API的,就像我们每个人的身份证一样,只是用来标注不同的人,不是用来做身份认证的。与前面的不同之处是,这里,我们需要用App ID 来映射一个用于加密的密钥,这样一来,我们就可以在服务器端进行相关的管理,我们可以生成若干个密钥对(AppID, AppSecret),并可以有更细粒度的操作权限管理。

把AppID和HMAC用于API认证,目前来说,玩得最好最专业的应该是AWS了,我们可以通过S3的API请求签名文档看到AWS是怎么玩的。整个过程还是非常复杂的,可以通过下面的图片流程看个大概。基本上来说,分成如下几个步骤:

  1. 把HTTP的请求(方法、URI、查询字串、头、签名头,body)打个包叫 CanonicalRequest,作个SHA-256的签名,然后再做一个base16的编码
  2. 把上面的这个签名和签名算法 AWS4-HMAC-SHA256、时间戳、Scop,再打一个包,叫 StringToSign
  3. 准备签名,用 AWSSecretAccessKey来对日期签一个 DataKey,再用 DataKey 对要操作的Region签一个 DataRegionKey ,再对相关的服务签一个DataRegionServiceKey ,最后得到 SigningKey.
  4. 用第三步的 SigningKey来对第二步的 StringToSign 签名。

 

最后,发出HTTP Request时,在HTTP头的 Authorization字段中放入如下的信息:

Authorization: AWS4-HMAC-SHA256 
               Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, 
               SignedHeaders=content-type;host;x-amz-date, 
               Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7

 

其中的  AKIDEXAMPLE 是 AWS Access Key ID, 也就是所谓的 AppID,服务器端会根据这个AppID来查相关的 Secret Access Key,然后再验证签名。如果,你对这个过程有点没看懂的话,你可以读一读这篇文章——《Amazon S3 Rest API with curl》这篇文章里有好些代码,代码应该是最有细节也是最准确的了。

这种认证的方式好处在于,AppID和AppSecretKey,是由服务器的系统开出的,所以,是可以被管理的,AWS的IAM就是相关的管理,其管理了用户、权限和其对应的AppID和AppSecretKey。但是不好的地方在于,这个东西没有标准 ,所以,各家的实现很不一致。比如: Acquia 的 HMAC微信的签名算法 (这里,我们需要说明一下,微信的API没有遵循HTTP协议的标准,把认证信息放在HTTP 头的 Authorization 里,而是放在body里)

JWT – JSON Web Tokens

JWT是一个比较标准的认证解决方案,这个技术在Java圈里应该用的是非常普遍的。JWT签名也是一种MAC(Message Authentication Code)的方法。JWT的签名流程一般是下面这个样子:

  1. 用户使用用户名和口令到认证服务器上请求认证。
  2. 认证服务器验证用户名和口令后,以服务器端生成JWT Token,这个token的生成过程如下:
    • 认证服务器还会生成一个 Secret Key(密钥)
    • 对JWT Header和 JWT Payload分别求Base64。在Payload可能包括了用户的抽象ID和的过期时间。
    • 用密钥对JWT签名 HMAC-SHA256(SecertKey, Base64UrlEncode(JWT-Header)+'.'+Base64UrlEncode(JWT-Payload));
  3. 然后把 base64(header).base64(payload).signature 作为 JWT token返回客户端。
  4. 客户端使用JWT Token向应用服务器发送相关的请求。这个JWT Token就像一个临时用户权证一样。

当应用服务器收到请求后:

  1. 应用服务会检查 JWT  Token,确认签名是正确的。
  2. 然而,因为只有认证服务器有这个用户的Secret Key(密钥),所以,应用服务器得把JWT Token传给认证服务器。
  3. 认证服务器通过JWT Payload 解出用户的抽象ID,然后通过抽象ID查到登录时生成的Secret Key,然后再来检查一下签名。
  4. 认证服务器检查通过后,应用服务就可以认为这是合法请求了。

我们可以看以,上面的这个过程,是在认证服务器上为用户动态生成 Secret Key的,应用服务在验签的时候,需要到认证服务器上去签,这个过程增加了一些网络调用,所以,JWT除了支持HMAC-SHA256的算法外,还支持RSA的非对称加密的算法。

使用RSA非对称算法,在认证服务器这边放一个私钥,在应用服务器那边放一个公钥,认证服务器使用私钥加密,应用服务器使用公钥解密,这样一来,就不需要应用服务器向认证服务器请求了,但是,RSA是一个很慢的算法,所以,虽然你省了网络调用,但是却费了CPU,尤其是Header和Payload比较长的时候。所以,一种比较好的玩法是,如果我们把header 和 payload简单地做SHA256,这会很快,然后,我们用RSA加密这个SHA256出来的字符串,这样一来,RSA算法就比较快了,而我们也做到了使用RSA签名的目的。

最后,我们只需要使用一个机制在认证服务器和应用服务器之间定期地换一下公钥私钥对就好了。

这里强烈建议全文阅读 Anglar 大学的 《JSW:The Complete Guide to JSON Web Tokens

OAuth 1.0

OAuth也是一个API认证的协议,这个协议最初在2006年由Twitter的工程师在开发OpenID实现的时候和社交书签网站Ma.gnolia时发现,没有一种好的委托授权协议,后来在2007年成立了一个OAuth小组,知道这个消息后,Google员工也加入进来,并完善有善了这个协议,在2007年底发布草案,过一年后,在2008年将OAuth放进了IETF作进一步的标准化工作,最后在2010年4月,正式发布OAuth 1.0,即:RFC 5849 (这个RFC比起TCP的那些来说读起来还是很轻松的),不过,如果你想了解其前身的草案,可以读一下 OAuth Core 1.0 Revision A ,我在下面做个大概的描述。

根据RFC 5849,可以看到 OAuth 的出现,目的是为了,用户为了想使用一个第三方的网络打印服务来打印他在某网站上的照片,但是,用户不想把自己的用户名和口令交给那个第三方的网络打印服务,但又想让那个第三方的网络打印服务来访问自己的照片,为了解决这个授权的问题,OAuth这个协议就出来了。

  • 这个协议有三个角色:
    • User(照片所有者-用户)
    • Consumer(第三方照片打印服务)
    • Service Provider(照片存储服务)
  • 这个协义有三个阶段:
    • Consumer获取Request Token
    • Service Provider 认证用户并授权Consumer
    • Consumer获取Access Token调用API访问用户的照片

整个授权过程是这样的:

  1. Consumer(第三方照片打印服务)需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret
  2. 当 User 访问 Consumer 时,Consumer 向 Service Provide 发起请求请求Request Token (需要对HTTP请求签名)
  3. Service Provide 验明 Consumer 是注册过的第三方服务商后,返回 Request Token(oauth_token)和 Request Token Secret (oauth_token_secret
  4. Consumer 收到 Request Token 后,使用HTTP GET 请求把 User 切到 Service Provide 的认证页上(其中带上Request Token),让用户输入他的用户和口令。
  5. Service Provider 认证 User 成功后,跳回 Consumer,并返回 Request Token (oauth_token)和 Verification Code(oauth_verifier
  6. 接下来就是签名请求,用Request Token 和 Verification Code 换取 Access Token (oauth_token)和 Access Token Secret (oauth_token_secret)
  7. 最后使用Access Token 访问用户授权访问的资源。

下图附上一个Yahoo!的流程图可以看到整个过程的相关细节。

因为上面这个流程有三方:User,Consumer 和 Service Provide,所以,又叫 3-legged flow,三脚流程。OAuth 1.0 也有不需要用户参与的,只有Consumer 和 Service Provider 的, 也就是 2-legged flow 两脚流程,其中省掉了用户认证的事。整个过程如下所示:

  1. Consumer(第三方照片打印服务)需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret
  2. Consumer 向 Service Provide 发起请求请求Request Token (需要对HTTP请求签名)
  3. Service Provide 验明 Consumer 是注册过的第三方服务商后,返回 Request Token(oauth_token)和 Request Token Secret (oauth_token_secret
  4. Consumer 收到 Request Token 后,直接换取 Access Token (oauth_token)和 Access Token Secret (oauth_token_secret)
  5. 最后使用Access Token 访问用户授权访问的资源。

最后,再来说一说OAuth中的签名。

  • 我们可以看到,有两个密钥,一个是Consumer注册Service Provider时由Provider颁发的 Consumer Secret,另一个是 Token Secret。
  • 签名密钥就是由这两具密钥拼接而成的,其中用 &作连接符。假设 Consumer Secret 为 j49sk3j29djd 而 Token Secret 为dh893hdasih9那个,签名密钥为:j49sk3j29djd&dh893hdasih9
  • 在请求Request/Access Token的时候需要对整个HTTP请求进行签名(使用HMAC-SHA1和HMAC-RSA1签名算法),请求头中需要包括一些OAuth需要的字段,如:
    • Consumer Key : 也就是所谓的AppID
    • Token: Request Token 或 Access Token
    • Signature Method :签名算法比如:HMAC-SHA1
    • Timestamp:过期时间
    • Nonce:随机字符串
    • Call Back:回调URL

下图是整个签名的示意图:

图片还是比较直观的,我就不多解释了。

OAuth 2.0

在前面,我们可以看到,从Digest Access, 到AppID+HMAC,再到JWT,再到OAuth 1.0,这些个API认证都是要向Client发一个密钥(或是用密码)然后用HASH或是RSA来签HTTP的请求,这其中有个主要的原因是,以前的HTTP是明文传输,所以,在传输过程中很容易被篡改,于是才搞出来一套的安全签名机制,所以,这些个认证的玩法是可以在HTTP明文协议下玩的。

这种使用签名方式大家可以看到是比较复杂的,所以,对于开发者来说,也是很不友好的,在组织签名的那些HTTP报文的时候,各种,URLEncode和Base64,还要对Query的参数进行排序,然后有的方法还要层层签名,非常容易出错,另外,这种认证的安全粒度比较粗,授权也比较单一,对于有终端用户参与的移动端来说也有点不够。所以,在2012年的时候,OAuth 2.0 的 RFC 6749 正式放出。

OAuth 2.0依赖于TLS/SSL的链路加密技术(HTTPS),完全放弃了签名的方式,认证服务器再也不返回什么 token secret 的密钥了,所以,OAuth 2.0是完全不同于1.0 的,也是不兼容的。目前,Facebook 的 Graph API 只支持OAuth 2.0协议,Google 和 Microsoft Azure 也支持Auth 2.0,国内的微信和支付宝也支持使用OAuth 2.0。

下面,我们来重点看一下OAuth 2.0的两个主要的Flow:

  • 一个是Authorization Code Flow, 这个是 3 legged 的
  • 一个是Client Credential Flow,这个是 2 legged 的。
Authorization Code Flow

Authorization Code 是最常使用的OAuth 2.0的授权许可类型,它适用于用户给第三方应用授权访问自己信息的场景。这个Flow也是OAuth 2.0四个Flow中我个人觉得最完整的一个Flow,其流程图如下所示。

 

下面是对这个流程的一个细节上的解释:

1)当用户(Resource Owner)访问第三方应用(Client)的时候,第三方应用会把用户带到认证服务器(Authorization Server)上去,主要请求的是 /authorize API,其中的请求方式如下所示。

https://login.authorization-server.com/authorize?
        client_id=6731de76-14a6-49ae-97bc-6eba6914391e
        &response_type=code
        &redirect_uri=http%3A%2F%2Fexample-client.com%2Fcallback%2F
        &scope=read
        &state=xcoiv98CoolShell3kch

其中:

    • client_id为第三方应用的App ID
    • response_type=code为告诉认证服务器,我要走Authorization Code Flow。
    • redirect_uri意思是我跳转回第三方应用的URL
    • scope意是相关的权限
    • state 是一个随机的字符串,主要用于防CSRF攻击。

2)当Authorization Server收到这个URL请求后,其会通过 client_id来检查 redirect_uriscope是否合法,如果合法,则弹出一个页面,让用户授权(如果用户没有登录,则先让用户登录,登录完成后,出现授权访问页面)。

3)当用户授权同意访问以后,Authorization Server 会跳转回 Client ,并以其中加入一个 Authorization Code。 如下所示:

https://example-client.com/callback?
        code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
        &state=xcoiv98CoolShell3kch

我们可以看到,

    • 请流动的链接是第 1)步中的 redirect_uri
    • 其中的 state 的值也和第 1)步的 state一样。

4)接下来,Client 就可以使用 Authorization Code 获得 Access Token。其需要向 Authorization Server 发出如下请求。

POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
&grant_type=code
&redirect_uri=https%3A%2F%2Fexample-client.com%2Fcallback%2F
&client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&client_secret=JqQX2PNo9bpM0uEihUPzyrh

5)如果没什么问题,Authorization 会返回如下信息。

{
  "access_token": "iJKV1QiLCJhbGciOiJSUzI1NiI",
  "refresh_token": "1KaPlrEqdFSBzjqfTGAMxZGU",
  "token_type": "bearer",
  "expires": 3600,
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciO.eyJhdWQiOiIyZDRkM..."
}

其中,

    • access_token就是访问请求令牌了
    • refresh_token用于刷新 access_token
    • id_token 是JWT的token,其中一般会包含用户的OpenID

6)接下来就是用 Access Token 请求用户的资源了。

GET /v1/user/pictures
Host: https://example.resource.com

Authorization: Bearer iJKV1QiLCJhbGciOiJSUzI1NiI

 

 Client Credential Flow

Client Credential 是一个简化版的API认证,主要是用于认证服务器到服务器的调用,也就是没有用户参与的的认证流程。下面是相关的流程图。

这个过程非常简单,本质上就是Client用自己的 client_idclient_secret向Authorization Server 要一个 Access Token,然后使用Access Token访问相关的资源。

请求示例

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=czZCaGRSa3F0Mzpn
&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

返回示例

{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}

这里,容我多扯一句,微信公从平台的开发文档中,使用了OAuth 2.0 的 Client Credentials的方式(参看文档“微信公众号获取access token”),我截了个图如下所谓。我们可以看到,微信公众号使用的是GET方式的请求,把AppID和AppSecret放在了URL中,虽然这也符合OAuth 2.0,但是并不好,因为大多数网关代理会把整个URI请求记到日志中。我们只要脑补一下腾讯的网关的Access Log,里面的日志一定会有很多的各个用户的AppID和AppSecret……

 

小结

讲了这么多,我们来小结一下(下面的小结可能会有点散)

两个概念和三个术语
  • 区分两个概念:Authentication(认证) 和 Authorization (授权),前者是证明请求者是身份,就像身份证一样,后者是为了获得权限。身份是区别于别人的证明,而权限是证明自己的特权。Authentication为了证明操作的这个人就是他本人,需要提供密码、短信验证码,甚至人脸识别。Authorization 则是不需要在所有的请求都需要验人,是在经过Authorization后得到一个Token,这就是Authorization。就像护照和签证一样。
  • 区分三个概念:编码Base64Encode、签名HMAC、加密RSA。编码是为了更的传输,等同于明文,签名是为了信息不能被篡改,加密是为了不让别人看到是什么信息。
明白一些初衷
  • 使用复杂地HMAC哈希签名方式主要是应对当年没有TLS/SSL加密链路的情况。
  • JWT把 uid 放在 Token中目的是为了去掉状态,但不能让用户修改,所以需要签名。
  • OAuth 1.0区分了两个事,一个是第三方的Client,一个是真正的用户,其先拿Request Token,再换Access Token的方法主要是为了把第三方应用和用户区分开来。
  • 用户的Password是用户自己设置的,复杂度不可控,服务端颁发的Serect会很复杂,但主要目的是为了容易管理,可以随时注销掉。
  • OAuth 协议有比所有认证协议有更为灵活完善的配置,如果使用AppID/AppSecret签名的方式,又需要做到可以有不同的权限和可以随时注销,那么你得开发一个像AWS的IAM这样的账号和密钥对管理的系统。
相关的注意事项
  • 无论是哪种方式,我们都应该遵循HTTP的规范,把认证信息放在 Authorization HTTP 头中。
  • 不要使用GET的方式在URL中放入secret之类的东西,因为很多proxy或gateway的软件会把整个URL记在Access Log文件中。
  • 密钥Secret相当于Password,但他是用来加密的,最好不要在网络上传输,如果要传输,最好使用TLS/SSL的安全链路。
  • HMAC中无论是MD5还是SHA1/SHA2,其计算都是非常快的,RSA的非对称加密是比较耗CPU的,尤其是要加密的字符串很长的时候。
  • 最好不要在程序中hard code 你的 Secret,因为在github上有很多黑客的软件在监视各种Secret,千万小心!这类的东西应该放在你的配置系统或是部署系统中,在程序启动时设置在配置文件或是环境变量中。
  • 使用AppID/AppSecret,还是使用OAuth1.0a,还是OAuth2.0,还是使用JWT,我个人建议使用TLS/SSL下的OAuth 2.0。
  • 密钥是需要被管理的,管理就是可以新增可以撤销,可以设置账户和相关的权限。最好密钥是可以被自动更换的。
  • 认证授权服务器(Authorization Server)和应用服务器(App Server)最好分开。

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19395.html/feed 37
StackOverflow 2019 程序员调查 https://coolshell.cn/articles/19307.html https://coolshell.cn/articles/19307.html#comments Sun, 21 Apr 2019 04:29:13 +0000 https://coolshell.cn/?p=19307 Read More Read More

]]>
前些天,StackOverflow 发布了 2019年的年度程序员调查,这个调查报查有90000名程序员参与,这份调度报告平均花了20分钟,可见,这份报告有很多的问题,也是很详细的。这份报告有一些地方,让我有了一些思考。

首先,我们先来看一下之份报告的 Key Results:

  • Python 成为了过去一年中成长最快的语言,把Java挤到了第二位,排在后面的是Rust语言。
  • 有半数以上的被访者在是在16岁写下自己的第一行代码。
  • DevOps Specialists 和 Site Reliability Engineers 是程序员中最有经验,技术最牛,薪资最好的职位。(这对应于国内的——系统架构师)
  • 在几个头部的程序员大国中,中国的程序员最乐观的,他们相信在今天出生的人会有比他们父母更好的人生。对于欧洲的程序员来说,比较法国和德国的程序员,他们对未来并不太乐观。
  • 对于最影响程序员生产力的事,不同的程序员有不同的想法。

第一部分,Developer Profile

在第一部分中,我们可以看到,中国程序员参与这个调查的并不多,程序员主要集中在美国、欧洲、印度这三个地方。所以,这份报告更偏国际上一些。这对于我们中国程序员也有很大的帮助,因为一方面可以看到世界发展的趋势,另一方面也可以了解我们和世界有什么不一样。

对于技术职业来说,整个世界的程序员开始趋于全栈和后端,有51.9%的人是全栈,50%的人是后端,32.8%的人是前端……在这些人中,很多程序员都选了多项,中位数是3项,最常见是前端、后端和全栈全选的。然后,接下来是选两项的,选两项目的包括:数据库管理员和系统管理员,DevOps Specialist 和 Site Reliablility Engineer, 学术研究者和科学家,设计师和前端工程师。

从这些数据中我们可以看见:前后端的界限越来越不明显,设计师和前端的界限也开始模糊。这应该说明,工具和框架的成熟,让后端程序员和设计师也可以进入到前端工程师的领域,或是前端工程师开始进入后端和设计的领域。总之,复合型人才越来越越成为主流,而前后端也趋于一个相互融合的态势。

在接下来的图表中,我们可以看到有80%以上的人是把编程当成自己的爱好(包括相关的女性)。

真是应了那句话——“Programmers who don’t code in their spare time for fun will never become as good as those that do”,是的,如果你对编程没有感到一种快乐,没有在你空闲的时候去以一种的兴趣爱好方式去面对,那么,无论是编程,还是运动,还是去旅游,都不会有太多成效的。

在接下来的编程经验上,有两组如下的数据:

学习编程的年限 编程的年限

我们可以看到无论是学习还是编程,随着时间的拉长,其人数占比越来越少。

下面我们再来看一个年龄图:

调查报告从20岁开始每隔5年划分一个年龄段,我们不难发现从25-29岁开始每个年龄段都比前一个年龄段人数急剧减少大约30-50%,比如25-29年龄段占总人数27.6%,而30-34则只有19.3%。以此类推,到60岁以上,就只剩1%。可以看出5年是大多数程序员的转型周期。这是合理的,因为5年时间足够一个人积累足够的经验技能为职业转型做准备。

我们也可以看到50岁以上的程序员只有4.2%,大约是参与调查人员的300多人,如果这些人20岁左右参加工作,那么说明他们在1990左右就开始写代码,事实上那个时间点别说是程序员了,连电脑用户都不多。电脑和互联网真正暴发的时间还是在1995年 – 2000年之间,不过,那个时间点程序员的总体人数也不多,而行业越来越火才会导致大量的人进入到这个行业中,这个转换过程基本上去需要3-5年,也就是从2000年后才开始有大量的人拥入程序员这个行业,程序员的人数在过去30年间也是呈增涨态势的,所以,我个人认为,所谓的“众多老程序员”的比例会被2005年以后大量拥入程序员行业的年青人所“稀释”。所以,上图的比例不能完全说明程序员是个青春饭

但是,我们还是要正视老牌资深的程序员越来越少的这个事实,在这份报告第三部分中说了一些和程序员职业生涯相关的调查,如下:

  • 在被问到有多少人对自己的职业满意的时。有40%的人觉得很满意,而有34.3%的人觉得一般满意,有10%的人说不清,还有15%的人是不满意的。可以看到有不少人是对这个职业生涯是有想法的。
  • 在被问到有多少人想转管理而可以挣得更多时。有30%的人是说想转的,有51%的人是明确不转的,还有20%的人是说不知道。可见,想转管理的人最多可能会有一半的人。
  • 在被问到有多少人想转管理时。有1/3的人是明确不想转的,而有1/4的人是明确是想转,而有36%的人则是不说,观望中。可见,的确是有很多想想转管理的。

我们可以看到,程序员中并不是所有的人都是可以坚持这么长时间的,这也挺正常的,对很大一部分人来说,对这个职业是有或多或少的不满意的,也有一部分人可能会随着技术的更新被淘汰,还有另外很大一部分人是想转管理的。所以,能够长时间地跟上形势长时间地喜欢写代码,并且对程序员这个的职业长期满意,不想转管理的,的确是为随时年龄的越大也越来越少

但我们完全可以看出来,程序员的主力军在20-40岁这个区间,而30岁左右的程序员是年富力强(经验和能力都很好)的黄金时间

老程序员在国外似乎不会存在多大的问题,但在国内会有一些问题,所以,对于像我一样喜欢写代码、打算长久做程序员的兄弟,这里分享一些相关的经验。

  1. 持续高效地学习。软件行业的新技术层出不穷,旧的技术淘汰很快,所以我们更要多多学习基础技术和原理,那些都是很难改变的,并且基础扎实了后,学习新的技术也才会更快速。其间我们也不要乱学新技术,我们要关注那些有潜力的技术,也就看准了再学(参看酷壳的《Go语言、Docker和新技术》)。注意,而是跟上大时代已经比较不容易,引领时代的人还是少数,所以,还是要更为高效地学习。
  2. 积极面对他人的不解。 很多时候,总是会有人说:“到了你这个年纪怎么还在做程序员?”,这句话感觉就是对程序员这个职业的一种羞辱,社会的价值观感觉容不下大龄程序员。这个时候,我一般会跟他们解释到,我40来岁了,我觉得自己的状态还很好,工作完成没什么问题,偶尔加班到凌晨也行,新知识和技术我学起来不比年轻人慢,我在这个年纪有的经验比他们都多,而且,我这个年纪还在写代码,说明我真的喜欢这个事,像我这样的人能够长时间坚持做一个职业的人这个世界已经不多了,你们应该珍惜……
  3. 找到自己的定位。我们需要做好职业规划、财务和心理方面的准备。40岁的程序员,所能竞争的一定是自己的认识和经验,所以,40岁以后如果你还是很喜欢这一行业,你的社会阅历和经历以及对这个社会的理解,可以让你做一些有创新的事,除此之外,你还可以做一个教练、老师、咨询、专家……,用你的经验和能力帮助下一代和一些中小型的公司,这不但是他们的刚需,同时也会让重新焕发的。

第二部分,技术

首先,在这部分,主要是了解一些技术,这部分的技术可以给于程序员们一些指导。

最流行的语言 最热门的语言

我们可以看到,

  • Javascript/HTML/CSS是很多人都会用到的,后面的是SQL,这个也没什么问题,无论前后端的人,或多或少都会要用到的,这些技术感觉已经成为了基础必会的技术了,就像数中的加减乘除一样。
  • Python/Java/Shell 是后端开发主流语言的前三强,Python在今年超过了Java。这里让我比较好奇的是居然还有很多人用Shell,这估计跟运维有关,所以,Python的热可能也是通过运维和大数据相关。
  • 流行语言后,第二梯队的是 C# / PHP / C++ / TypeScript / C ,接下来的是: Ruby / Go / Swift / Kotlin /WebAssembly / Rust… 。但在最被程序员喜欢的编程语言中:Rust / Python / TypeScript / Koltin / WebAssembly / Swift / Go… 都是排在前几名的。程序语言每隔一段时间就会整出一些新的语言来,我们一定要明白新出来的东西主要是为了解决什么样的问题,不然很容易迷失。
  • 在后面还有一个编程语言的薪资图,我们可以看到,在上面被提过的这些个编程语言中,Go语言的薪资是最高的(这可能是因为Go语言写关键的系统级的中件间——因为Go语言正在成为云计算的第一编程语言),然后是Scala、Ruby、WebAssembly、Rust、Erlang、Shell、Python、Typescript……

通过这些个信息,我们可以看出主流技术、有潜力的技术,传统过气技术,以及相关薪资,对我们在选择编程语言上有一定的启示。

在后面,我们可以看到:

  • 在 Web 开发框架上,主流使用还是 jQuery, React.js,Angular.js 为最前面的三个前端开发框架。而被程序员所喜欢的则是 React.js,Vue.js,Express, Spring,程序员非常不喜欢 Drupal,jQuery,Ruby on Rails 和Angular.js……
  • 在其它开发框架/库/工具上,主流是Node.js、.NET、Pandas、Unity 3D、Tensorflow、Ansible、Cordova、Xamarin……而程序员比较喜欢的是.NET、Torch/PyTorch、Flutter、Pandas、Tensorflow、Node.js …
  • 在操作系统上,主流使用Linux、Windows、Docker、Android、AWS……,而程序员最喜欢的是Linux、Docker、Kubernetes、Raspberry Pi、AWS、MacOS、iOS……
  • 在数据库上,MySQL、PostgreSQL、MSSQL、SQLite、MongoDB、Redis、Elasticsearch是比较主流的,而程序员非常喜欢的是,Redis、PostgreSQL、Elasticsearch、Firebase、MongoDB……,程序员比较讨厌的是 Couchbase、Oracle、Cassandra、MySQL。

从这些个图表中,我们可以看到主流和有潜力的技术是什么,我们可以看到 Windows 的技术并没有过时,感觉似乎都有可能会卷土重来,但是,开源的技术来势凶凶,正在吞食整个软件业,不容小觑,Docker/Kubernetes无论是在主流应用上还是被程序员的喜好上都是非常猛的,而云平台的AWS开始成为标准平台技术……

接下来的开发工具中,我们可以看到:

  • Visual Studio Code 成为了最流行的开发工具。让我没有想到的是跟在后面的是 Notepad++(好久没用这个工具了,我得找回来用用了),而IntelliJ、Vim、Sublime Text排以后面。 Eclipse 和 Atom 动力不足,Emacs 开始变得小众了。
  • 程序员主要的开发平台还是Windows占了近1/2, MacOS和Linux随后,各占1/4。
  • 有38%的人使用容器技术做开发,30%的人使用容器做测试,在生产线上使用容器的有26%

看样子编程开发工具还是Visual Studio 和 IntelliJ的天下,MacOS/Linux正在抢Windows的开发市场

接下来,StackOverflow给了一个技术圈的图

从上面这个图中,我们可以看以技术的几圈子:

  • Microsoft圈 – Windows、.NET、ASP.NET、C#、Azure、SQL Server
  • Java圈 – Java、Spring
  • 手机圈 – Android、 iOS、Kotlin、Swift、Firebase
  • 前端圈 – Javascript、React.js、Angular.js、PHP
  • 大数据圈 – Python、TensorFlow、Torch/PyTorch
  • 基础平台圈 – Linux、Shell、Vim、Docker、Kubernetes、Elasticsearch、Redis……
  • 其它圈子 – C/C++/汇编圈子、Ruby圈子、Hadoop/Spark圈子、……

看到谁的圈子大了吧,圈子大的并不代表技术实力强或是有前途,不过可以代表在那个圈子相关的关联技术,一方面,可以给你一些相关的参考,另一方面,整体可以让你看到全部的目前比较主流的技术。

第三部份 工作

在第三部份工作中,我们可以看到如下的一些数据:

  • 有3/4的程序员是全职的,10%左右的程序员是自由职业,6%左右的程序员是失业的,这个比例在北美、印度和欧洲都差不多。
  • 有1/3的人在过去一年内换过工作,1/4的人在过去1-2年间换过工作,1/3的人在2-4年换过工作。
  • 程序员找工作时,影响程序员的几个主要因素是:技术(编程语言、框架和使用的技术)、办公环境和公司文化、灵活的时间和安排、更专业的机会、远程工作……
  • 影响程序员工作的几大因素是:有干扰的工作环境、开会、要干一些和开发无关的事、人手不够、管理不够、工具不够、通勤时间……
  • 对于工程质量,有近70%的人有Code Review,而30%的则没有;有60%多的人有Unit Test,而不到40%的没有……

从工作中我们可以看到,程序员还是比较关心技术和公司文化的,换工作也是这个职业很正常的特性,他们并不喜欢被打扰,希望有足够的时间,而对于工程质量还是很有追求的。

最后用一张程序员的“每周工作时间” 来结束本文!

祝大家快乐!

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19307.html/feed 12
“努力就会成功” https://coolshell.cn/articles/19271.html https://coolshell.cn/articles/19271.html#comments Wed, 17 Apr 2019 01:12:27 +0000 https://coolshell.cn/?p=19271 Read More Read More

]]>
那一年,我加入了某知名公司的某知名部门,在办公室中,我看到了到处都挂着——“努力就会成功”的条幅,这个部门中大多数员工的邮件签名都会有“努力就会成功”,我感到一种热血沸腾的气氛,这是我在多年工作来都没有感受到的,当时挺高兴地能和这样一群人工作,也没多想。直到有一天,我看到这些高级的软件工程师们把自己关在又挤又吵的会议室中,拼命地加班,真是拼命,周一到周日,每天早上10点到凌晨3点甚至凌晨5点,连国庆节都来上班,就在这样的环境和状态下,连续干了三个多月……上线前,QA找到了1000多个bug(你没看错,就是一千多个),最后这个项目用了1年多的时间来返工,本来一个6-8个月的项目,团队被打了鸡血想在3个月内完成,最终却花了近两年的时间来返工……(要知道,我以前在外国公司工作,外国老板看到团队在长时间加班会感到焦虑的,因为加班通常代表着有不好的事情正在发生……)

所以对此,我是有点看不懂的,看不懂的是,为什么这么一群聪明的人,放着明亮宽敞的办公桌不用,硬要挤在一个又窄又小又吵又热的小空间里工作,而且要这么透支地写那么重要的很关键的系统级的代码……这就好像,一架在一个小作坊里被人加班加点赶工出来的飞机,谁敢坐啊?!老实说,这群工程师真是很优秀的工程师,他们完全是可以做得更好的……但是却做出了如此蹩脚和糟糕的系统……他们说,这样坐在一起可以做到快速沟通,然而,我觉得这恰恰是一种没有章法的表现。

也是在这家公司,在这个项目烂尾一年前,公司感到了危机,CEO号召全体996,举全公司之力从董事长到下面基层员工对抗外部所谓的威胁,有的部门为了表现,甚至997,然而,在一年后,做出了一个烂得不能再烂的软件,最终以失败告终,很多人包括CEO也因此下课……

这是最让我看不懂的一个事了,为什么这么如此成功的公司的高级管理层会做出这样的事情,而且还制定这样的政策……把这么优秀的员工以及公司大把把数以亿计的钞票投入到这种错误的路线上来,而且还拼命地加班…… 他们脑子里在想什么呢?难道他们真的以为,有足够多的钱,足够多的人,然后拼命加班,就能打败对手吗?……

你喜欢这句话吗?

“努力就会成功”,“加班就会有成就”,“勤劳就会致富”……是这样吗?仔细思考一些,这些话存在严重的逻辑问题,我们在高中的时候学过“充分条件”,“必要条件”和“充要条件”!“努力就会成功”这句话,把“努力”说成了“成功”的充要条件,这不就是错的吗?努力只是成功的必要条件之一。你在错误的方向或是格局很小的方向上努力,能有用么?你努力地要饭,你努力地当搬运工,你努力地打骚扰电话销卖保险…… 在错误和小格局的方向上努力,你还觉得努力还有用吗?

但是很多人是很喜欢“努力就会成功”这句话,这类人也很喜欢看很多小人物通过自己的努力变成成功人士的励志的故事,为什么这种故事会被很多人喜欢甚至感动。因为这很符合大众的心理诉求,这种诉求其实就是一种只要使力只要拼命了就可以成功的心理诉求,因为这类人基本上都是能力有限,不知道怎么提升自己的人,当他们看到只要拼命使力就可以成功的观点时,他们就会有共鸣,就会感到,不用学习那些晦涩难懂高级的知识,不用掌握和练习哪些高级技能,自己只需要在低级的事情上拼命和努力,加更多的班和干更多活,自己就会像电影中的那些小人物一样,总有一天会成功的……

“努力就会成功,勤劳就会致富”,不但符合那些低级管理者的利益诉求,同样符合那些能力不足不愿意学习和成长的人的诉求。因为,他们混淆了行动与进展,忙碌与多产,他们以为能靠蛮力可以弥补思维上的惰性,靠拼命可以弥补能力上的不足……

喜欢或认同这句话的人基本是能力上有问题的人,这类适合做劳动密集型的事。不信你可以试试看,当一件事的难度超过一定程度的时候,那些聪明的人会找到更省力的方法,而能力上有问题的,还是在那使蛮力。

我成长的过程

回想我的过去,我在2001年那年被外包到了某银行做开发,标准的9/10/6,封闭开发,就是用C语言在AIX系统里堆一些银行的交易逻辑,老实说,这个过程并没有让我学到什么东西,也没有什么成长,我每天想的就是我要离开这个地方,所以,我在晚上10点以后开始看书学习到11点半,并使用工作环境动手实践书上的代码,一年后,我精读了《TCP/IP详解》《Windows核心编程》《Java编程思想》等书。然后,我找到一份外企业的工作,月薪一下翻了三倍。

在外企不加班,但是当时的外企压力也很大,对代码的质量要求的也很高,来的第二个月,就因为代码写的太差,差点被开掉,所以,为了能够达到更高的标准,我自然也是很努力的,在周末甚至黄金周节假日我哪里都不去,我就去公司,但我不是在公司上班,因为我没有自己的电脑,所以,我只能蹭公司的电脑,这导致办公楼的管理人员经常打电话给我让我帮他在周末的时候管理物业…… 在这家公司是我成长最快的时候,然而,并不是因为我的努力,而是因为有很多比我牛逼的人在Code Review上给我大量的帮助,在项目上帮助我,我的努力学习虽然也有作用,但更多的是高手对我的帮助

再回想一下我以前在职场上的很多关键点,不是因为我加班了,而是因为在某些关键问题上,我跳出来解决了其它人都解决不了的问题,我解决了一个网络通信莫名其妙的断掉的问题,我把性能优化了很多倍,我解决了一个不能重现的一个困扰团队3个星期的问题(其实就是大家没有认真读文档),我在入职一个公司的第一天里就为这个公司解决了一个历史遗留问题……在Platform,我每周解决了bug数是全公司的其它人的总和还要多(从不加班),在路透,我带团队优化的系统的性能是全球所有研发中心最高的,在亚马逊,两周打通美国和德国的订单和商品列表系统……我也有失败的时候,而我失败的时候,总是因为我搞不定事,即便是加班拼命努力也无济于事!是的,我的职业生涯的成长,最根本的不是你有多努力,有多勤奋,而是你能搞定很多人搞不定的事!

你不信你可以看看你们公司那些不用加班,就算什么也不干,公司也要花钱养的技术人员,他们的成功一定不是努力和加班加出来的,你会发现这些人拼的不是谁干的多,而是谁解决的问题更有难

我加班996的时候,从来都不是我成长最快的时候,而我和一群牛人在解决难题的时才是我成长最快的时候。

Work Smart

2015年因为父亲病危要动手术,所以我不能工作在家照顾父亲。于是我就成为了一个自由职业者,帮很多公司解决一些技术问题,好多都是高并发和系统稳定性的问题,有一些是分布式架构的运维的问题,还有一些是工程管理和企业文化问题……有一些小公司的单体架构在业务上一推广就宕机了,于是把我叫过去,我在生产线上直接re-arch,用一些非常规的手段,1-2天就把性能救过来了…… 还有就是解决一些点状的技术问题,还帮用户做一些design/code review……,有70%工作是真正的按劳取酬,也就是先把问题解决了再谈要收多少钱,那段时间我出卖的不是我的劳动力,而是我的技能,所以,反而比打工挣得多多了,而且还比较轻闲……

有时候,我还调侃到,你在大公司里一天写上万行代码,拼命地加班,你信不信,我只用写几百行代码就挣得比你多?同样是一个简单的 for-loop 语句,有人写的就值1万元一行,而你写的则一文不值。关键不在于谁写的代码多,关键在于我们解决了什么样的问题。你千万不要以为只要付你足够的钱,你就可以996,让你干什么都可以,然而当你自己把自己当成劳动力的时候,你也就只是一个像牲口一样的行事了!

这就好像算法一样,你那个O(n^2)的递归穷举算法,再怎么样也干不过我的O(n)的动态规划的算法。

现在我拿了投资在创业,一开始帮助各大企业建高并发高可用云化架构的公司,现在还给企业提供金融和营销能力,我跟客户谈业务的时候,基本不是因为我有多加班多努力地做方案,而是我能一针见血地指出用户的问题,帮用户解决问题。我在很多地方都见到阿里、蚂蚁、华为、HP……,一个小创业公司跟他们竞争真的很难,但我知道,要能竞争过这些大公司,这根本就不是能够通过加班996或是拼命努力就能搞定的,我必需要使用更好的方式,所以,除了更好地站在用户的立场,能够给用户制定更符合用户的技术方案之外,我必需做到我的技术方案不比这些大公司的差,而这一点,完全不是加班、努力或是勤奋能出来的,这是需要靠自己的经验、学习能力、归纳思考、和与更多牛人交流才出的来的……当我给某银行CIO介绍完我的分布式系统的方案后,CIO给我微微鞠躬说:“过去一两年,我听过几乎所有国内外产商跟我讲的分布式的方案,你的是我听过的最好的方案!谢谢你!”,当我给某省电信行业公司讲了一下DevOps的方案后,老总对我说:“你们真的是做事的人!”,当用户来问我:“你们的API网关是怎么写的?为什么运行的这么稳定?”……这些话都是让我很心里很暖的话……当然,我也有被骂的时候,也有失败的时候,但基本上来说,我无法通过努力工作改善我思维的不足……

我们学计算机当程序员最大的福气不是可以到大公司里加班和996,而是我们生活在了第三次工业革命的信息化时代,这才是最大的福气,所以,我们应该努力地提升自己,而不是把自己当劳动力一样的卖了!在这样的一个时代,你要做的不是通过加班和拼命来跪着挣钱,而是通过技能来躺着挣钱……

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19271.html/feed 82
打造高效的工作环境 – Shell 篇 https://coolshell.cn/articles/19219.html https://coolshell.cn/articles/19219.html#comments Sun, 17 Mar 2019 05:53:01 +0000 https://coolshell.cn/?p=19219 Read More Read More

]]>

注:本文由雷俊(Javaer/Emacser)和我一起编辑,所以文章版权归雷俊与我共同所有,转载者必需注明出处和我们两位作者。原文最早发于酷壳微信公众号,后来我又做了一些修改,再发到博客这边。

程序员是一个很懒的群体,总想着能够让代码为自己干活,他们不断地把工作生活中的一些事情用代码自动化了,从而让整个社会的效率运作地越来越高。所以,程序员在准备去优化这个世界的时候,都会先要优化自己的工作环境,是所谓“工欲善其事,必先利其器”。

我们每个程序员都应该打造一套让自己更为高效的工作环境。那怕就是让你少输入一次命令,少按一次键,少在鼠标和键盘间切换一次,都会让程序员的工作变得更为的高效。所以,程序员一般需要一台性能比较好,不会因为开了太多的网页或程序就卡得不行的电脑,还要配备多个显示器,一个显示器写代码,一个查文档,一个测试运行结果,而不必在各种窗口来来回回的切换……在大量的窗口间切换经常会迷路,而且也容易出错(分不清线上或测试环境)……

除了硬件上的装备,软件上也是能够提升程序员生产力的地方,在软件层面提升程序员生产力的东西有一个很重要的事就是命令行和脚本,使用鼠标和图形界面则会大大降低程序员的生产力。酷壳以前也写过一些,如《你可能不知道的Shell》和《 应该知道的Linux技巧》,但是Unix/Linux Shell就是一个大宝库,怎么写也写不完,不然,怎么会有“Where is the Shell, there is a way”。

命令行

在不同的操作系统下,都有着很不错的命令行工具,比如 Mac 下的 Iterm2,Linux 下的原生命令行,如果你是在 Windows 下工作,问题也不大,因为 Windows 下现在有了 WSL。WSL 提供了一个由微软开发的Linux兼容的内核接口(不包含Linux内核代码),然后可以在其上运行GNU用户空间,例如 Ubuntu,openSUSE,SUSE Linux Enterprise Server,Debian和Kali Linux。这样的用户空间可能包含 Bash shell 和命令语言,使用本机 GNU/Linux 命令行工具(sed,awk 等),编程语言解释器(Ruby,Python 等),甚至是图形应用程序(使用主机端的X窗口系统)。

使用命令行可以完成所有日常的操作,新建文件夹(mkdir)、新建文件(touch)、移动(mv)、复制(cp)、删除(rm)等等。而且使用 Linux/Unix 命令行最好的方式是可以用 awksedgrepxargsfindsort 等等这样的命令,然后用管道把其串起来,就可以完成一个你想要的功能,尤其是一些简单的数据统计功能。这是Linux命令行不可比拟的优势。比如:

  • 查看连接你服务器 top10 用户端的 IP 地址:

netstat -nat | awk '{print $5}' | awk -F ':' '{print $1}' | sort | uniq -c | sort -rn | head -n 10

  • 查看一下你最常用的10个命令:

cat .bash_history | sort | uniq -c | sort -rn | head -n 10 (or cat .zhistory | sort | uniq -c | sort -rn | head -n 10

(注:awk 和 sed 是两大神器,所以,我以前的也有两篇文章来介绍它们——《awk简明教程》和《sed简明教程》,你可以前往一读)

在命令行中使用 alias 可以将使用频率很高命令或者比较复杂的命令合并成一个命令,或者修改原生的命令。

下面这几个命令,可能是你天天都在敲的。所以,你应该设置成 alias 来提高效率

alias nis="npm install --save "
alias svim='sudo vim'
alias mkcd='foo(){ mkdir -p "$1"; cd "$1" }; foo '
alias install='sudo apt get install'
alias update='sudo apt-get update; sudo apt-get upgrade'
alias ..="cd .."
alias ...="cd ..; cd .."
alias www='python -m SimpleHTTPServer 8000'
alias sock5='ssh -D 8080 -q -C -N -f user@your.server'

你还可以参考如下的一些文章,看看别人是怎么用好 alias 的

命令行中除了原生的命令之外,还有很多可以提升使用体验的工具。下面罗列一些很不错的命令,把原生的命令增强地很厉害:

  • fasd 增强了 cd 命令 。
  • bat 增强了 cat 命令 。如果你想要有语法高亮的 cat,可以试试 ccat 命令。
  • exa 增强了 ls 命令,如果你需要在很多目录上浏览各种文件 ,ranger 命令可以比 cd 和 cat 更有效率,甚至可以在你的终端预览图片。
  • fd 是一个比 find 更简单更快的命令,他还会自动地忽略掉一些你配置在 .gitignore 中的文件,以及 .git 下的文件。
  • fzf 会是一个很好用的文件搜索神器,其主要是搜索当前目录以下的文件,还可以使用 fzf --preview 'cat {}'边搜索文件边浏览内容。
  • grep 是一个上古神器,然而,ackag 和 rg 是更好的grep,和上面的 fd一样,在递归目录匹配的时候,会使用你配置在 .gitignore 中的规则。
  • rm 是一个危险的命令,尤其是各种 rm -rf …,所以,trash 是一个更好的删除命令。
  • man 命令是好读文档的命令,但是man的文档有时候太长了,所以,你可以试试 tldr 命令,把文档上的一些示例整出来给你看。
  • 如果你想要一个图示化的ping,你可以试试 prettyping 。
  • 如果你想搜索以前打过的命令,不要再用 Ctrl +R 了,你可以使用加强版的 hstr  。
  • htop  是 top 的一个加强版。然而,还有很多的各式各样的top,比如:用于看IO负载的 iotop,网络负载的 iftop, 以及把这些top都集成在一起的 atop
  • ncdu  比 du 好用多了用。另一个选择是 nnn
  • 如果你想把你的命令行操作建录制成一个 SVG 动图,那么你可以尝试使用 asciinema 和 svg-trem 。
  • httpie 是一个可以用来替代 curlwget 的 http 客户端,httpie 支持 json 和语法高亮,可以使用简单的语法进行 http 访问: http -v github.com
  • tmux 在需要经常登录远程服务器工作的时候会很有用,可以保持远程登录的会话,还可以在一个窗口中查看多个 shell 的状态。
  • Taskbook 是可以完全在命令行中使用的任务管理器 ,支持 ToDo 管理,还可以为每个任务加上优先级。
  • sshrc 是个神器,在你登录远程服务器的时候也能使用本机的 shell 的 rc 文件中的配置。
  • goaccess  这个是一个轻量级的分析统计日志文件的工具,主要是分析各种各样的 access log。

关于这些增加命令,主要是参考自下面的这些文章

  1. 10 Tools To Power Up Your Command Line
  2. 5 More Tools To Power Up Your Command Line (Part 2 Of Series)
  3. Power Up Your Command Line, Part 3
  4. Power Up Your Command Line
  5. Hacker Tools

Shell 和脚本

shell 是可以与计算机进行高效交互的文本接口。shell 提供了一套交互式的编程语言(脚本),shell的种类很多,比如 shbashzsh 等。

shell 的生命力很强,在各种高级编程语言大行其道的今天,很多的任务依然离不开 shell。比如可以使用 shell 来执行一些编译任务,或者做一些批处理任务,初始化数据、打包程序等等。

现在比较流行的是 zsh + oh-my-zsh + zsh-autosuggestions 的组合,你也可以试试看。其中 zsh 和 oh-my-zsh 算是常规操作了,但是 zsh-autosuggestions 特别有用,可以超级快速的帮你补全你输入过的命令,让命令行的操作更加高效。

另外,fish 也是另外一个牛逼的shell,比如:命令行自动完成(根据历史记录),命令行命令高亮,当你要输入命令行参数的时候,自动提示有哪些参数…… fish在很多地方也是用起来很爽的。和上面的 oh-my-zsh 有点不分伯仲了。

你也许会说,用 Python 脚本或 PHP 来写脚本会比 Shell 更好更没有 bug,但我要申辩一下:

  • 其一,如果你有一天要维护线上机器的时候,或是到了银行用户的系统(与外网完全隔离,而且服务器上没有安装 Python/PHP 或是他们的的高级库,那么,你只有 Shell 可以用了)。
  • 其二,而且,如果要跟命令行交互很多的话,Shell 是不二之选,试想一下,如果你要去 100 台远程的机器上查access.log 日志中有没有某个错误,完成这个工作你是用 PHP/Python 写脚本快还是用 Shell 写脚本快呢?

所以,我们还要学会只使用传统的grep/awk/sed等等这些POSIX的原生的系统默认安装的命令

当然,要写好一个脚本并不容易,下面有一些小模板供你参考:

处理命令行参数的一个样例

while [ "$1" != "" ]; do
    case $1 in
        -s  )   shift	
		SERVER=$1 ;;  
        -d  )   shift
		DATE=$1 ;;
	--paramter|p ) shift
		PARAMETER=$1;;
        -h|help  )   usage # function call
                exit ;;
        * )     usage # All other parameters
                exit 1
    esac
    shift
done 

命令行菜单的一个样例

#!/bin/bash
# Bash Menu Script Example

PS3='Please enter your choice: '
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do
    case $opt in
        "Option 1")
            echo "you chose choice 1"
            ;;
        "Option 2")
            echo "you chose choice 2"
            ;;
        "Option 3")
            echo "you chose choice $REPLY which is $opt"
            ;;
        "Quit")
            break
            ;;
        *) echo "invalid option $REPLY";;
    esac
done

颜色定义,你可以使用 echo -e "${Blu}blue ${Red}red ${RCol}etc...." 进行有颜色文本的输出

RCol='\e[0m'    # Text Reset

# Regular           Bold                Underline           High Intensity      BoldHigh Intens     Background          High Intensity Backgrounds
Bla='\e[0;30m';     BBla='\e[1;30m';    UBla='\e[4;30m';    IBla='\e[0;90m';    BIBla='\e[1;90m';   On_Bla='\e[40m';    On_IBla='\e[0;100m';
Red='\e[0;31m';     BRed='\e[1;31m';    URed='\e[4;31m';    IRed='\e[0;91m';    BIRed='\e[1;91m';   On_Red='\e[41m';    On_IRed='\e[0;101m';
Gre='\e[0;32m';     BGre='\e[1;32m';    UGre='\e[4;32m';    IGre='\e[0;92m';    BIGre='\e[1;92m';   On_Gre='\e[42m';    On_IGre='\e[0;102m';
Yel='\e[0;33m';     BYel='\e[1;33m';    UYel='\e[4;33m';    IYel='\e[0;93m';    BIYel='\e[1;93m';   On_Yel='\e[43m';    On_IYel='\e[0;103m';
Blu='\e[0;34m';     BBlu='\e[1;34m';    UBlu='\e[4;34m';    IBlu='\e[0;94m';    BIBlu='\e[1;94m';   On_Blu='\e[44m';    On_IBlu='\e[0;104m';
Pur='\e[0;35m';     BPur='\e[1;35m';    UPur='\e[4;35m';    IPur='\e[0;95m';    BIPur='\e[1;95m';   On_Pur='\e[45m';    On_IPur='\e[0;105m';
Cya='\e[0;36m';     BCya='\e[1;36m';    UCya='\e[4;36m';    ICya='\e[0;96m';    BICya='\e[1;96m';   On_Cya='\e[46m';    On_ICya='\e[0;106m';
Whi='\e[0;37m';     BWhi='\e[1;37m';    UWhi='\e[4;37m';    IWhi='\e[0;97m';    BIWhi='\e[1;97m';   On_Whi='\e[47m';    On_IWhi='\e[0;107m';

取当前运行脚本绝对路径的示例:(注:Linux下可以用 dirname $(readlink -f $0) )

FILE="$0"
while [[ -h ${FILE} ]]; do
    FILE="`readlink "${FILE}"`"
done
pushd "`dirname "${FILE}"`" > /dev/null
DIR=`pwd -P`
popd > /dev/null

如何在远程服务器运行一个本地脚本

#无参数
ssh user@server 'bash -s' < local.script.sh

#有参数
ssh user@server ARG1="arg1" ARG2="arg2" 'bash -s' < local_script.sh

如何检查一个命令是否存在,用 which 吗?最好不要用,因为很多操作系统的 which 命令没有设置退出状态码,这样你不知道是否是有那个命令。所以,你应该使用下面的方式。

# POSIX 兼容:
command -v [the_command]

# bash 环境:
hash [the_command]
type [the_command]

# 示例:
gnudate() {
    if hash gdate 2> /dev/null; then
        gdate "$@"
    else
        date "$@"
    fi
}

然后,如果要写出健壮性更好的脚本,下面是一些相关的技巧:

  • 使用 -e 参数,如:set -e 或是 #!/bin/sh -e,这样设置会让你的脚本出错就会停止运行,这样一来可以防止你的脚本在出错的情况下还在拼拿地干活停不下来。
  • 使用 -u 参数,如: set -eu,这意味着,如果你代码中有变量没有定义,就会退出。
  • 对一些变理,你可以使用默认值。如:${FOO:-'default'}
  • 处理你代码的退出码。这样方便你的脚本跟别的命令行或脚本集成。
  • 尽量不要使用 ; 来执行多个命令,而是使用 &&,这样会在出错的时候停止运行后续的命令。
  • 对于一些字符串变量,使用引号括起,避免其中有空格或是别的什么诡异字符。
  • 如果你的脚有参数,你需要检查脚本运行是否带了你想要的参数,或是,你的脚本可以在没有参数的情况下安全的运行。
  • 为你的脚本设置 -h 和 --help 来显示帮助信息。千万不要把这两个参数用做为的功能。
  • 使用 $() 而不是 “ 来获得命令行的输出,主要原因是易读。
  • 小心不同的平台,尤其是 MacOS 和 Linux 的跨平台。
  • 对于 rm -rf 这样的高危操作,需要检查后面的变量名是否为空,比如:rm -rf $MYDIDR/* 如果 $MYDIR为空,结果是灾难性的。
  • 考虑使用 “find/while” 而不是 “for/find”。如:for F in $(find . -type f) ; do echo $F; done 写成 find . -type f | while read F ; do echo $F ; done 不但可以容忍空格,而且还更快。
  • 防御式编程,在正式执行命令前,把相关的东西都检查好,比如,文件目录有没有存在。

你还可以使用ShellCheck 来帮助你检查你的脚本。

最后推荐一些 Shell 和脚本的参考资料。

各种有意思的命令拼装,一行命令走天涯:

下面是一些脚本集中营,你可以在里面淘到各种牛X的脚本:

甚至写脚本都可以使用框架:

Google的Shell脚本的代码规范:

最后,别忘了几个和shell有关的索引资源:

最后,如果你还有什么别的更好的玩的东西,欢迎在评论区留言,或是到 coolshellx/ariticles @ github 修改本文。

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19219.html/feed 30
谈谈我的“三观” https://coolshell.cn/articles/19085.html https://coolshell.cn/articles/19085.html#comments Tue, 26 Feb 2019 08:02:07 +0000 https://coolshell.cn/?p=19085 Read More Read More

]]>
也许是人到了四十多了,敢写这么大的命题,我也醉了,不过,我还是想把我的想法记录下来,算是对我思考的一个snapshot,给未来的我看看,要么被未来的我打脸,要么打未来我的脸。无论怎么样,我觉得对我自己都很有意义。注意,这篇文章是长篇大论。

三观是世界观、人生观和价值观,

  • 世界观代表你是怎么看这个世界的。是左还是右,是激进还是保守,是理想还是现实,是乐观还是悲观……
  • 人生观代表你要想成为什么样的人。是成为有钱人,还是成为人生的体验者,是成为老师,还是成为行业专家,是成为有思想的人,还是成为有创造力的人……
  • 价值观则是你觉得什么对你来说更重要。是名是利,是过程还是结果,是付出还是索取,是国家还是自己,是家庭还是职业……

人的三观其实是会变的,回顾一下我的过去,我感觉我的三观至少有这么几比较明显的变化,学生时代、刚走上社会的年轻时代,三十岁后的时代,还有现在。估计人都差不多吧……

  • 学生时代的三观更多的是学校给的,用各种标准答案给的,是又红又专的
  • 刚走上社会后发现完全不是这么一回事,但学生时代的三观根深蒂固,三观开始分裂,内心开始挣扎
  • 三十岁后,不如意的事越来越多,对社会越来越了解,有些人屈从现实,有些人不服输继续奋斗,而有些人展露才能开始影响社会,而分裂的三观开始收敛,我属于还在继续奋斗的人。
  • 四十岁时,经历过的事太多,发现留给自己的时间不多,世界太复杂,而还有好多事没做,从而变得与世无争,也变得更为地自我。

面对世界

年轻的时候,抵制过日货,虽然没上过街,但是也激动过,一次是1999南斯拉夫大使馆被炸,一次是2005反日示威,以前,我也是一个爱国愤青。但是后来,有过各种机会出国长时间生活工作,加拿大、英国、美国、日本……随着自己的经历和眼界的开阔,自己的三观自己也随着有了很多的变化,发现有些事并不是自己一开始所认识的那样,而且还是截然相反的。我深深感觉到,要有一个好的世界观,你需要亲身去经历和体会这个世界,而不是听别人说。所以,当我看到身边的人情绪激动地要抵制这个国家,搞死那个民族的时候,我都会建议他去趟那个国家最好在在那个国家呆上一段时间,亲自感受一下。

再后来发现,要抵制的越来越多,小时候的美英帝国主义,然后是日本,再后面是法国、韩国、菲利宾、印度、德国、瑞典、加拿大……从小时候的台独到现在的港独、藏独、疆独……发现再这样下去,基本上来说,自己的人生也不用干别的事了……另外,随着自己的成长,越来越明白,抵制这个抵制那个只不过是幼稚和狭隘的爱国主义,真想强国,想别让他人看得起,就应该把时间和精力放在努力学习放在精益求精上,做出比他们更好的东西来。另外,感觉用对内的爱国主义解决对外的外交问题也有点驴唇不对马嘴,无非也就是转移一下内部的注意力罢了,另外还发现爱国主义还可以成为消费营销手段……不是我不爱国,是我觉得世道变复杂了,我只是一个普通的老百姓,能力有限,请不要赋予我那么大的使命,我只想在我的专业上精进,能力所能及地帮助身边的人,过一个简单纯粹安静友善的生活……

另外,为什么国与国之间硬要比个你高我低,硬要分个高下,硬要争出个输赢,我也不是太理解,世界都已经发展到全球化的阶段了,很多产品早就是你中有我,我中有你的情况了。举个例子,一部手机中的元件,可能来自全世界数十个国家,我们已经说不清楚一部手机是究竟是哪个国家生产的了。即然,整个世界都在以一种合作共赢全球化的姿态下运作,认准自己的位置,拥抱世界,持续向先进国家学习,互惠互利,不好吗?你可能会说,不是我们不想这样,是别人不容我们发展……老实说,大的层面我也感受不到,但就我在的互联网计算机行业方面,我觉得整个世界的开放性越来越好,开源项目空前地繁荣,世界上互联网文化也空前的开放,在计算机和互联网行业,我们享受了太多的开源和开放的红利,人家不开放,我们可能在很多领域还落后数十年。然而现在很多资源我们都访问不了,用个VPN也非法,你说是谁阻碍了发展?我只想能够流畅地访问互联网,让我的工作能够更有效率,然而,我在自己的家里却像做贼一样去学习新知识新技术,随时都有可能被抓进监狱……

随着自己的经历越多,发现这个世界越复杂,也发现自己越渺小,很多国家大事并不是我不关心,是我觉得那根本不是我这个平头老百姓可以操心的事,这个世界有这个世界运作的规律和方法,而还有很多事情超出了我能理解的范围,也超出了我能控制的范围,我关心不关心都一个样,这些大事都不会由我的意志所决定的。而所谓的关心,无非就是喊喊口号,跟人争论一下,试图改变其它老百姓的想法,然而,对事情的本身的帮助却没有多大意义。过上几天,生活照旧,人家该搞你还不是继续搞你,而你自己并不因为做这些事而过得更好。

我对国与国之间的关系的态度是,有礼有节,不卑不亢,对待外国人,有礼貌但也要有节气,既不卑躬屈膝,也不趾高气昂,整体上,我并不觉得我们比国外有多差,但我也不觉得我们比国外有多好,我们还在成长,还需要帮助和协作,四海之内皆兄弟,无论在哪个国家,在老百姓的世界里,哪有那么多矛盾。有机会多出去走走,多结交几个其它民族的朋友,你会觉得,在友善和包容的环境下,你的心情和生活可以更好

我现在更多关心的是和我生活相关的东西,比如:上网、教育、医疗、食品、治安、税务、旅游、收入、物价、个人权益、个人隐私……这些东西对我的影响会更大一些,也更值得关注,可以看到过去的几十年,我们国家已经有了长足的进步,这点也让我让感到很开心和自豪的,在一些地方也不输别人。但是,依然有好些事的仍然没有达到我的预期,而且还很糟糕,这个也要承认。而对,未来的变数谁也不好说,我在这个国度里的安全感似乎还不足够,所以,我还是要继续努力,以便我可以有更多的选项。有选项总比没得选要好。所以,我想尽一切办法,努力让选项多起来,无法改变无法影响,那就只能提高自己有可选择的可能性

面对社会

另外,在网上与别人对一些事或观点的争论,我觉得越来越无聊,以前被怼了,一定要怼回去,现在不会了,视而不见,不是怕了,是因为,网络上的争论在我看来大多数都是些没有章法,逻辑混乱的争论。

  • 很多讨论不是说事,直接就是怼人骂人。随意就给人扣个帽子。
  • 非黑即白的划分,你说这个不是黑的,他们就把你划到白的那边。
  • 飘移观点,复杂化问题。东拉西扯,牵强附会,还扯出其它不相关的事来混淆。
  • 杠精很多,不关心你的整体观点,抓住一个小辫子大作文章。

很明显,与其花时间教育这些人,不如花时间提升自己,让自己变得更优秀,这样就有更高的可能性去接触更聪明更成功更高层次的人。因为,一方面,你改变不了他们,另外,改变他们对你自己也没什么意义,改变自己,提升自己,让自己成长才有意义。时间是宝贵的,那些人根本不值得花时间,应该花时间去结交更有素质更聪明的人,做更有价值的事。

美国总统富兰克林·罗斯福妻子埃莉诺·罗斯福(Eleanor Roosevelt)说过下面的一句话。

Great minds discuss ideas;
Average minds discuss events;
Small minds discuss people

把时间多放在一些想法上,对自己对社会都是有意义的,把时间放在八卦别人,说长到短,你也不可能改善自己的生活,你批评这个批评那个,看不上这个看不起那个,不会让你有成长,也不会提升你的影响力,你的影响力不是你对别人说长道短的能力,而是别人信赖你并希望得到你的帮助的现象。多交一些有想法的朋友,多把自己的想法付诸实践,那怕没有成功,你的人生也会比别人过得有意义。

如果你看过我以前的文章,你会看到一些吐槽性质的文章,而后面就再也没有了。另外,我也不再没有针对具体的某个人做出评价,因为人太复杂的了,经历的越多,你就会发现你很难评价人,与其花时间在评论人和事上,不如把时间花在做一些力所能及的事来改善自己或身边的环境。所以,我建议大家少一些对人的指责和批评,通过对一件事来引发你的思考,想一想有什么可以改善,有什么方法可以做得更好,有哪些是自己可以添砖加瓦的?你会发现,只要你坚持这么做,你个人的提升和对社会的价值会越来越大,而你的影响力也会越来越大

面对人生

现在的我,即不是左派也不是右派,我不喜欢爱国主义,我也不喜欢崇洋媚外,我更多的时候是一个自由派,哪边我都不站,我站我自己。因为,生活在这样的一个时代,能让自己过好都是一些比较奢望的事了。

《教父》里有这样的人生观:第一步要努力实现自我价值,第二步要全力照顾好家人,第三步要尽可能帮助善良的人,第四步为族群发声,第五步为国家争荣誉。事实上作为男人,前两步成功,人生已算得上圆满,做到第三步堪称伟大,而随意颠倒次序的那些人,一般不值得信任。这也是古人的“修身齐家治国平天下”!所以,在你我准备要开始要“平天下”的时候,也得先想想,自己的生活有没有过好了,家人照顾好了么,身边有哪些力所能及的事是可以去改善的……

穷则独善其身,达则兼济天下。提升自己,实现自我,照顾好自己的家人,帮助身边的人。这已经很不错了!

什么样的人干什么样的事,什么样的阶段做什么样的选择,有人的说,选择比努力更重要的,我深以为然,而且,我觉得选择和决定,比努力更难,努力是认准了一个事后不停地发力,而决定要去认准哪个事是自己该坚持努力的,则是令人彷徨和焦虑的(半途而废的人也很多)。面对人生,你每天都在作一个一个的决定,在做一个又一个的选择,有的决定大,有的决定小,你的人生的轨迹就是被这一个一个的决定和选择所走走出来的。

我在24岁放弃了一房子离开银行到小公司的时候,我就知道,人生的选择就是一个翘翘板,你要一头就没有另一头,选择是有代价的,你不选择的代价更大;选择是要冒险的,你不敢冒险的风险更大;选择是需要放弃的,因为无论怎么选你都会要放弃。想想你老了以后,回头一看,好多事情在年轻的时候都不敢做,而你再也没有机会,你就知道不敢选择不敢冒险的代价有多大了。选择就是一种 trade-off,这世上根本不会有什么完美,只要你想做事,你有雄心壮志,你的人生就是一个坑接着一个坑,你所能做的就是找到你喜欢的方向跳坑。

所以, 你要想清楚你要什么,不要什么,而且还不能要得太多,这样你才好做选择。否则,你影响你的因子太多,决定不好做,也做不好。

就像最前面说的一样,你是激进派还是保守派,你是喜欢领导还是喜欢跟从,你是注重长期还是注重短期,你是注重过程还是注重结果……等等,你对这些东西的坚持和守护,成为了你的“三观”,而你的三观则影响着你的选择,而你的选择影响着你的人生。

价值取向

下面是一些大家经常在说,可能也是大多数人关心的问题,就这些问题,我也谈谈我的价值取向。

挣钱。挣钱是一个大家都想做的事,但你得解决一个很核心的问题,那就是为什么别人愿意给你钱?对于挣钱的价值观从我大学毕业到现我就没怎么变过,那就是我更多关注的是怎么提高自己的能力,让自己值那个价钱,让别人愿意付钱。另外一方面,我发现,越是有能力的人,就越不计较一些短期得失,越计较短期得失的人往往都是很平庸的人。有能力的人不会关心自己的年终奖得拿多少,会不会晋升,他们更多的关心自己真正的实力有没有超过更多的人,更多的关注的是自己长远的成长,而不是一时的利益。聪明的人从来不关心眼前的得失,不会关心表面上的东西,他们更多关心的是长期利益,关心长期利益的人一定不是投机者,一定是投资者,投资会把自己的时间精力金钱投资在能让自己成长和提升的地方,那些让自己可以操更大的盘的地方,他们培养自己的领导力和影响力。而投机者在职场上会通过溜须拍马讨好领导,在学习上追求速成,在投资上使用跟随策略,在创业上甚至会不择手段,当风险来临时,投机者是几乎完全没有抗风险能力的,他们所谓的能力只不过因为形势好。

 

技术。对于计算机技术来说,要学的东西实在是太多,我并不害怕要学的东西很多,因为学习能力是一个好的工程师必需具备的事,我不惧怕困难和挑战。我觉得在语言和技术争论谁好谁坏是一种幼稚的表现, 没有完美的技术,Engineering 玩的是 Tradeoff。所以,我对没有完美的技术并不担心,但是我反而担心的是,当我们进入到一些公司后,这些公司会有一些技术上的沉淀也就是针对公司自己的专用技术,比如一些中间件,一些编程框架,lib库什么的。老实说,我比较害怕公司的专用技术,因为一旦失业,我建立在这些专用技术上的技能也会随之瓦解,有时候,我甚至害怕把我的技术建立在某一个平台上,小众的不用说了,大众的我也比较担扰,比如Windows或Unix/Linux上,因为一旦这个平台不流行或是被取代,那么我也会随之淘汰(过去的这20年已经发生过太多这样的事了)。为了应对这样的焦虑,我更愿意花时间在技术的原理和技术的本质上,这导致我需要了解各种各样的技术的设计方法,以及内在原理。所以,当国内的绝大多数程序员们更多的关注架构性能的今天,我则花更多的时间去了解编程范式,代码重构,软件设计,计算机系统原理,领域设计,工程方法……因为只有原理、本质和设计思想才可能让我不会被绑在某个专用技术或平台上,除非,我们人类的计算机这条路没走对。

 

职业。在过去20多年的职业生涯中,我从基层工程师做到管理,很多做技术的人都会转管理,但我却还是扎根技术,就算是在今天,还是会抠很多技术细节,包括写代码。因为我心里觉得,不写代码的人一定是做不好技术管理的,因为做技术管理有人要做技术决定,从不上手技术的人是做不好技术决定的,另一方面,我觉得管理是支持性的工作,不是产出性的工作,大多数的管理者无非是因为组织大了,所以需要管人管事,所以,必然要花大量的时间和精力处理各种问题,甚至办公室政治,然而,如果有一天失业了,大环境变得不好了,一个管理者和一个程序员要出去找工作,程序员会比管理者更能自食其力。所以,我并不觉得管理者这个职业有意思,我还是觉得程序员这个有创造性的职业更有趣。通常来说,管理者的技能力需要到公司和组织里才能展现,而有创造力的技能的人是可以自己独立的能力,所以,我觉得程序员的技能比管理者的技能能让我更稳定更自地活着。所以,我更喜欢“电影工作组”那样的团队和组织形式。

 

打工。对于打工,也就是加入一家公司工作,无论是在一家小公司还是一家大公司工作,都会有好的和不好的,任何公司都有其不完美的地方,这个需要承认。首先第一的肯定是完成公司交给你的任务(但我也不会是傻傻地完成工作,对于一些有问题的任务我也会提出我的看法),然后我会尽我所能在工作找到可以提高效率的地方进行改善。在推动公司/部门/团队在一技术和工程方面进步并不是一件很容易的事,因为进步是需要成本的,有时候,这种成本并不一定是公司和团队愿意接受的,而另外,从客观规律上来说,一件事的进步一定是会有和现状有一些摩擦的。有的人害怕有摩擦而忍了,而我则不是,我觉得与别人的摩擦并不可怕,因为大家的目标都是基本一致的,只是做事的标准和方式不一样,这是可能沟通的,始终是会相互理解的。而如果你没有去推动一个事,我觉得对于公司对于我个人来说,都是一种对人生的浪费,敬业也好,激情也好,其就是体现在你是否愿意冒险去推动一件于公于私都有利的事,而不是成为一个“听话”、“随大流”、“懒政”的人,即耽误了公司也耽误了自己。所以,我更信仰的是《做正确的事情,等着被开除》,这些东西,可参看《我看绩效考核》,以及我在Gitchat上的一些问答

 

创业。前两天,有个小伙来跟我说,说他要离开BAT要去创业公司了,说在那些更自由一些,没有大公司的种种问题。我毫不犹豫地教育了他一下,我说,你选择这个创业公司的动机不对啊,你无非就是在逃避一些东西罢了,你把创业公司当做是一个避风港,这是不对的,创业公司的问题可能会更多,去创业公司的更好的心态是,这个创业公司在干的事业是不是你的事业?说白了,如果你是为了你的事业,为了解决个什么,为了改进个什么,那么,创业是适合你的,也只有在做自己事业的时候,你才能不惧困难,才会勇敢地面对一切那种想找一个安稳的避风港呆着的心态是不会让你平静地,你要知道世界本来就是不平静的,找了自己的归宿和目标才可能让你真正的平静。所以,在我现的创业团队,我不要求大家加班,我也不鸡汤洗脑,对于想要加入的人,我会跟他讲我现在遇到的各种问题以及各种机遇,并一直在让他自己思考,我们在做的事是不是自己的事业诉求?还可不可以更好?每个人都应该为自己的事业为自己的理想去活一次,追逐自己的事业和理想并不容易,需要有很大的付出,而也只有你心底里的那个理想值得这么大的付出……

 

客户。基于上述的价值观,在我现在创业的时候,我在面对客户的时候,也是一样的,我并不会完全的迁就于客户,我的一些银行客户和互联网客户应该体会到我的做的方式了,我并不觉得迁就用户,用户要什么我就应该给什么,用户想听什么,我就说什么,虽然这样可以省着精力,更圆滑,但这都不是我喜欢的,我更愿意鲜明地表达我的观点,并拉着用户跟我一起成长,因为我并不觉得完成客户的项目有成就感,我的成就感来自客户的成长。所以,面对客户有些做得不对有问题有隐患的地方,或是有什么做错的事,我基本上都是直言不讳地说出来,因为我觉得把真实的相法说出来是对客户和对自己最基本的尊重,不管客户最终的选择是什么,我都要把利弊跟客户讲清楚。我并不是在这里装,因为,我也想做一些更高级更有技术含量的事,所以,对于一些还达到的客户,我如果不把他们拉上来,我也对不起自己。

 

在我“不惑之年”形成了这些价值观体系,也许未来还会变,也许还不成熟,总之,我不愿跟大多数人一样,因为大多数人都是随遇而安随大流的,因为这样风险最小,而我想走一条属于自己的路,做真正的自己,就像我24岁从银行里出来时想的那样,我选择对了一个正确的专业(计算机科学),呆在了一个正确的年代(信息化革命),这样的“狗屎运”几百年不遇,如果我还患得患失,那我岂不辜负活在这样一个刺激的时代?!我所要做的就是在这个时代中做有价值的事就好了!这个时代真的是太好了!

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/19085.html/feed 122
记一次Kubernetes/Docker网络排障 https://coolshell.cn/articles/18654.html https://coolshell.cn/articles/18654.html#comments Sat, 08 Dec 2018 03:57:35 +0000 https://coolshell.cn/?p=18654 Read More Read More

]]>
昨天周五晚上,临下班的时候,用户给我们报了一个比较怪异的Kubernetes集群下的网络不能正常访问的问题,让我们帮助查看一下,我们从下午5点半左右一直跟进到晚上十点左右,在远程不能访问用户机器只能远程遥控用户的情况找到了的问题。这个问题比较有意思,我个人觉得其中的调查用到的的命令以及排障的一些方法可以分享一下,所以写下了这篇文章。

问题的症状

用户直接在微信里说,他们发现在Kuberbnetes下的某个pod被重启了几百次甚至上千次,于是开启调查这个pod,发现上面的服务时而能够访问,时而不能访问,也就是有一定概率不能访问,不知道是什么原因。而且并不是所有的pod出问题,而只是特定的一两个pod出了网络访问的问题。用户说这个pod运行着Java程序,为了排除是Java的问题,用户用 docker exec -it 命令直接到容器内启了一个 Python的 SimpleHttpServer来测试发现也是一样的问题。

我们大概知道用户的集群是这样的版本,Kuberbnetes 是1.7,网络用的是flannel的gw模式,Docker版本未知,操作系统CentOS 7.4,直接在物理机上跑docker,物理的配置很高,512GB内存,若干CPU核,上面运行着几百个Docker容器。

问题的排查

问题初查

首先,我们排除了flannel的问题,因为整个集群的网络通信都正常,只有特定的某一两个pod有问题。而用 telnet ip port 的命令手工测试网络连接时有很大的概率出现 connection refused 错误,大约 1/4的概率,而3/4的情况下是可以正常连接的。

当时,我们让用户抓个包看看,然后,用户抓到了有问题的TCP连接是收到了 SYN 后,立即返回了 RST, ACK

我问一下用户这两个IP所在的位置,知道了,10.233.14.129 是 docker010.233.14.145 是容器内的IP。所以,这基本上可以排除了所有和kubernets或是flannel的问题,这就是本地的Docker上的网络的问题。

对于这样被直接 Reset 的情况,在 telnet 上会显示 connection refused 的错误信息,对于我个人的经验,这种 SYN完直接返回 RST, ACK的情况只会有三种情况:

  1.  TCP链接不能建立,不能建立连接的原因基本上是标识一条TCP链接的那五元组不能完成,绝大多数情况都是服务端没有相关的端口号。
  2. TCP链接建错误,有可能是因为修改了一些TCP参数,尤其是那些默认是关闭的参数,因为这些参数会导致TCP协议不完整。
  3. 有防火墙iptables的设置,其中有 REJECT 规则。

因为当时还在开车,在等红灯的时候,我感觉到有点像 NAT 的网络中服务端开启了 tcp_tw_recycle 和 tcp_tw_reuse 的症况(详细参看《TCP的那些事(上)》),所以,让用户查看了一上TCP参数,发现用户一个TCP的参数都没有改,全是默认的,于是我们排除了TCP参数的问题。

然后,我也不觉得容器内还会设置上iptables,而且如果有那就是100%的问题,不会时好时坏。所以,我怀疑容器内的端口号没有侦听上,但是马上又好了,这可能会是应用的问题。于是我让用户那边看一下,应用的日志,并用 kublet describe看一下运行的情况,并把宿主机的 iptables 看一下。

然而,我们发现并没有任何的问题。这时,我们失去了所有的调查线索,感觉不能继续下去了……

重新梳理

这个时候,回到家,大家吃完饭,和用户通了一个电话,把所有的细节再重新梳理了一遍,这个时候,用户提供了一个比较关键的信息—— “抓包这个事,在 docker0 上可以抓到,然而到了容器内抓不到容器返回 RST, ACK ” !然而,根据我的知识,我知道在 docker0 和容器内的 veth 网卡上,中间再也没有什么网络设备了(参看《Docker基础技术:LINUX NAMESPACE(下)》)!

于是这个事把我们逼到了最后一种情况 —— IP地址冲突了!

Linux下看IP地址冲突还不是一件比较简单事的,而在用户的生产环境下没有办法安装一些其它的命令,所以只能用已有的命令,这个时候,我们发现用户的机器上有 arping 于是我们用这个命令来检测有没有冲突的IP地址。使用了下面的命令:

$ arping -D -I docker0 -c 2 10.233.14.145
$ echo $?

根据文档,-D 参数是检测IP地址冲突模式,如果这个命令的退状态是 0 那么就有冲突。结果返回了 1 。而且,我们用 arping IP的时候,没有发现不同的mac地址。 这个时候,似乎问题的线索又断了

因为客户那边还在处理一些别的事情,所以,我们在时断时续的情况下工作,而还一些工作都需要用户完成,所以,进展有点缓慢,但是也给我们一些时间思考问题。

柳暗花明

现在我们知道,IP冲突的可能性是非常大的,但是我们找不出来是和谁的IP冲突了。而且,我们知道只要把这台机器重启一下,问题一定就解决掉了,但是我们觉得这并不是解决问题的方式,因为重启机器可以暂时的解决掉到这个问题,而如果我们不知道这个问题怎么发生的,那么未来这个问题还会再来。而重启线上机器这个成本太高了。

于是,我们的好奇心驱使我们继续调查。我让用户 kubectl delete 其中两个有问题的pod,因为本来就服务不断重启,所以,删掉也没有什么问题。删掉这两个pod后(一个是IP为 10.233.14.145 另一个是 10.233.14.137),我们发现,kubernetes在其它机器上重新启动了这两个服务的新的实例。然而,在问题机器上,这两个IP地址居然还可以ping得通

好了,IP地址冲突的问题可以确认了。因为10.233.14.xxx 这个网段是 docker 的,所以,这个IP地址一定是在这台机器上。所以,我们想看看所有的 network namespace 下的 veth 网卡上的IP。

在这个事上,我们费了点时间,因为对相关的命令也 很熟悉,所以花了点时间Google,以及看相关的man。

  • 首先,我们到 /var/run/netns目录下查看系统的network namespace,发现什么也没有。
  • 然后,我们到 /var/run/docker/netns 目录下查看Docker的namespace,发现有好些。
  • 于是,我们用指定位置的方式查看Docker的network namespace里的IP地址

这里要动用 nsenter 命令,这个命令可以进入到namespace里执行一些命令。比如

$ nsenter --net=/var/run/docker/netns/421bdb2accf1 ifconfig -a

上述的命令,到 var/run/docker/netns/421bdb2accf1 这个network namespace里执行了 ifconfig -a 命令。于是我们可以用下面 命令来遍历所有的network namespace。

$ ls /var/run/docker/netns | xargs -I {} nsenter --net=/var/run/docker/netns/{} ip addr 

然后,我们发现了比较诡异的事情。

  • 10.233.14.145 我们查到了这个IP,说明,docker的namespace下还有这个IP。
  • 10.233.14.137,这个IP没有在docker的network namespace下查到。

有namespace leaking?于是我上网查了一下,发现了一个docker的bug – 在docker remove/stop 一个容器的时候,没有清除相应的network namespace,这个问题被报告到了 Issue#31597 然后被fix在了 PR#31996,并Merge到了 Docker的 17.05版中。而用户的版本是 17.09,应该包含了这个fix。不应该是这个问题,感觉又走不下去了。

不过, 10.233.14.137 这个IP可以ping得通,说明这个IP一定被绑在某个网卡,而且被隐藏到了某个network namespace下。

到这里,要查看所有network namespace,只有最后一条路了,那就是到 /proc/ 目录下,把所有的pid下的 /proc/<pid>/ns 目录给穷举出来。好在这里有一个比较方便的命令可以干这个事 : lsns

于是我写下了如下的命令:

$ lsns -t net | awk ‘{print $4}' | xargs -t -I {} nsenter -t {}&nbsp;-n ip addr | grep -C 4 "10.233.14.137"

解释一下。

  • lsns -t net 列出所有开了network namespace的进程,其第4列是进程PID
  • 把所有开过network namespace的进程PID拿出来,转给 xargs 命令
  • xargs 命令把这些PID 依次传给 nsenter 命令,
    • xargs -t 的意思是会把相关的执行命令打出来,这样我知道是那个PID。
    • xargs -I {}  是声明一个占位符来替换相关的PID

最后,我们发现,虽然在 /var/run/docker/netns 下没有找到 10.233.14.137 ,但是在 lsns 中找到了三个进程,他们都用了10.233.14.137 这个IP(冲突了这么多),而且他们的MAC地址全是一样的!(怪不得arping找不到)。通过ps 命令,可以查到这三个进程,有两个是java的,还有一个是/pause (这个应该是kubernetes的沙盒)。

我们继续乘胜追击,穷追猛打,用pstree命令把整个进程树打出来。发现上述的三个进程的父进程都在多个同样叫 docker-contiane 的进程下!

这明显还是docker的,但是在docker ps 中却找不道相应的容器,什么鬼!快崩溃了……

继续看进程树,发现,这些 docker-contiane 的进程的父进程不在 dockerd 下面,而是在 systemd 这个超级父进程PID 1下,我靠!进而发现了一堆这样的野进程(这种野进程或是僵尸进程对系统是有害的,至少也是会让系统进入亚健康的状态,因为他们还在占着资源)。

docker-contiane 应该是 dockerd 的子进程,被挂到了 pid 1 只有一个原因,那就是父进程“飞”掉了,只能找 pid 1 当养父。这说明,这台机器上出现了比较严重的 dockerd 进程退出的问题,而且是非常规的,因为 systemd 之所以要成为 pid 1,其就是要监管所有进程的子子孙孙,居然也没有管理好,说明是个非常规的问题。(注,关于 systemd,请参看《Linux PID 1 和 Systemd 》,关于父子进程的事,请参看《Unix高级环境编程》一书)

接下来就要看看 systemddockerd 记录的日志了…… (然而日志只有3天的了,这3天dockerd没有任何异常)

总结

通过这个调查,可以总结一下,

1) 对于问题调查,需要比较扎实的基础知识,知道问题的成因和范围。

2)如果走不下去了,要重新梳理一下,回头仔细看一下一些蛛丝马迹,认真推敲每一个细节。

3) 各种诊断工具要比较熟悉,这会让你事半功倍。

4)系统维护和做清洁比较类似,需要经常看看系统中是否有一些僵尸进程或是一些垃圾东西,这些东西要及时清理掉。

最后,多说一下,很多人都说,Docker适合放在物理机内运行,这并不完全对,因为他们只考虑到了性能成本,没有考虑到运维成本,在这样512GB中启动几百个容器的玩法,其实并不好,因为这本质上是个大单体,因为你一理要重启某些关键进程或是机器,你的影响面是巨大的

 

———————— 更新 2018/12/10 —————————

问题原因

这两天在自己的环境下测试了一下,发现,只要是通过 systemctl start/stop docker 这样的命令来启停 Docker, 是可以把所有的进程和资源全部干掉的。这个是没有什么问题的。我唯一能重现用户问题的的操作就是直接 kill -9 <dockerd pid> 但是这个事用户应该不会干。而 Docker 如果有 crash 事件时,Systemd 是可以通过 journalctl -u docker 这样的命令查看相关的系统日志的。

于是,我找用户了解一下他们在Docker在启停时的问题,用户说,他们的执行 systemctl stop docker 这个命令的时候,发现这个命令不响应了,有可能就直接按了 Ctrl +C

这个应该就是导致大量的 docker-containe 进程挂到 PID 1 下的原因了。前面说过,用户的一台物理机上运行着上百个容器,所以,那个进程树也是非常庞大的,我想,停服的时候,系统一定是要遍历所有的docker子进程来一个一个发退出信号的,这个过程可能会非常的长。导致操作员以为命令假死,而直接按了 Ctrl + C ,最后导致很多容器进程并没有终止……

 

其它事宜

有同学问,为什么我在这个文章里写的是 docker-containe 而不是 containd 进程?这是因为被 pstree 给截断了,用 ps 命令可以看全,只是进程名的名字有一个 docker-的前缀。

下面是这两种不同安装包的进程树的差别(其中 sleep 是我用 buybox 镜像启动的)

systemd───dockerd─┬─docker-contained─┬─3*[docker-contained-shim─┬─sleep]
                  │                 │                    └─9*[{docker-containe}]]
                  │                 ├─docker-contained-shim─┬─sleep
                  │                 │                 └─10*[{docker-containe}]
                  │                 └─14*[{docker-contained-shim}]
                  └─17*[{dockerd}]
systemd───dockerd─┬─containerd─┬─3*[containerd-shim─┬─sleep]
                  │            │                 └─9*[{containerd-shim}]
                  │            ├─2*[containerd-shim─┬─sleep]
                  │            │                    └─9*[{containerd-shim}]]
                  │            └─11*[{containerd}]
                  └─10*[{dockerd}]

顺便说一下,自从 Docker 1.11版以后,Docker进程组模型就改成上面这个样子了.

  • dockerd 是 Docker Engine守护进程,直接面向操作用户。dockerd 启动时会启动 containerd 子进程,他们之前通过RPC进行通信。
  • containerddockerdrunc之间的一个中间交流组件。他与 dockerd 的解耦是为了让Docker变得更为的中立,而支持OCI 的标准 。
  • containerd-shim  是用来真正运行的容器的,每启动一个容器都会起一个新的shim进程, 它主要通过指定的三个参数:容器id,boundle目录(containerd的对应某个容器生成的目录,一般位于:/var/run/docker/libcontainerd/containerID), 和运行命令(默认为 runc)来创建一个容器。
  • docker-proxy 你有可能还会在新版本的Docker中见到这个进程,这个进程是用户级的代理路由。只要你用 ps -elf 这样的命令把其命令行打出来,你就可以看到其就是做端口映射的。如果你不想要这个代理的话,你可以在 dockerd 启动命令行参数上加上:  --userland-proxy=false 这个参数。

更多的细节,大家可以自行Google。这里推荐两篇文章:

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18654.html/feed 44
程序员练级攻略(2018) 与我的专栏 https://coolshell.cn/articles/18360.html https://coolshell.cn/articles/18360.html#comments Tue, 29 May 2018 04:38:23 +0000 https://coolshell.cn/?p=18360 Read More Read More

]]>
写极客时间8个月了,我的专栏现在有一定的积累了,今天想自己推荐一下。因为最新的系列《程序员练级攻略(2018)版》正在连载中,而且文章积累量到了我也有比较足的自信向大家推荐我的这个专栏了。推荐就从最新的这一系统的文章开始。

2011年,我在 CoolShell 上发表了 《程序员技术练级攻略》一文,得到了很多人的好评(转载的不算,在我的网站上都有近1000W的访问量了)。并且陆续收到了一些人的反馈,说跟着这篇文章找到了不错的工作。几年过去,也收到了好些邮件和私信,希望我把这篇文章更新一下,因为他们觉得有点落伍了。是的,老实说,抛开这几年技术的更新迭代不说,那篇文章写得也不算特别系统,同时标准也有点低,当时是给一个想要入门的朋友写的,所以,非常有必要从头更新一下《程序员练级攻略》这一主题

目前,我在我极客时间的专栏上更新《程序员练级攻略(2018版)》。升级版的《程序员练级攻略》会比Coolshell上的内容更多,也更专业。这篇文章有【入门篇】、【修养篇】、【专业基础篇】、【软件设计篇】、【高手成长篇】五大篇章,它们会帮助你从零开始,一步步地,系统地,从陌生到熟悉,到理解掌握,从编码到设计再到架构,从码农到程序员再到工程师再到架构师的一步一步进阶,完成从普通到精通到卓越的完美转身……

这篇文章是我写得最累也是最痛苦的文章,原因如下:

  •  学习路径的梳理。这是一份计算编程相关知识地图,也是一份成长和学习路径。所以有太多的推敲了,知识的路径,体,地图……这让我费了很多工夫,感觉像在编写一本教材一样,即不能太高大上,也不能误人子弟。
  • 新旧知识的取舍。另外,因为我的成长经历中很多技术都成了过去时,所以对于新时代的程序员应该学习新的技术,然后,很多基础技术在今天依然管用,所以,在这点上,哪些要那些不要,也花了我很多的工夫。
  • 文章书籍的推荐。为了推荐最好的学习资料和资源,老实说,我几乎翻遍了整个互联网,进行了大量的阅读和比较。这个过程让我也受益非浅。一开始,这篇文章的大小居然在500K左右,太多的信息就是没有信息,所以在信息的筛选上我花费了很多的工夫,删掉了60%的内容。但是,依然很宠大。

总之,你一定会被这篇文章的内容所吓到的,是的,我就是故意这样做的,因为,这本来就没有什么捷径,也不可能速成,很多知识都是硬骨头,你只能一口一口的啃,我故意这样做就是为了让你不要有“速成”的幻想,也可以轻而一举的吓退那些不想用功不想努力的人

但是,我们也要知道《易经》有云:“取法其上,得乎其中,取法其中,得乎其下,取法其下,法不得也”。所以,我这里会给你立个比较高标准,你要努力达到,相信我,就算是达不到,也会比你一开始期望的要高很多……

下面是这份练级攻略的目录,目前只在极客时间上发布,你需要付费阅读(在本文最后有相关的二维码)。

 

那么,除程序员练级攻略外,我还写了哪些内容?下面是迄今为止我所有的文章的目录。你可以在下面看一下相关的目录。这也算是我开收费专栏来8个月给大家的一份答卷吧。我也没有想到,我居然写了这么多的文章,而且对很多人都很有用。

首先是个人成长和经验之谈的东西,在这里的文章还没有完全更新完,未来要更新什么我也不清楚,但是可以呈现出来的内容和方向如下所示,供你参考。对于个人成长中的内容,都是我多年来的心得和体会,从读者的反馈来看是非常不错的,你一定要要阅读的。

分布式系统架构,我一共出了两个系列,一个是分布式系统架构的本质,另一个是设计模式。前者偏概念,后者偏技术。这里旨在让你看到整个分布式系统设计的一个非常系统的蓝图,但是因为在手机端上,不可能写得非常细,所以,会缺失一些细节,这些细节我是故意缺失的,主要是有几方面的原因,

  • 一方面,这是为了阅读的效果,手机上的文章不过长,所以,不能有太多的细节。
  • 另一方面,也是是想留给大家自行学习,而不是一定要我把饭喂到你的嘴里,你才能吃得着。学习不只是为要答案,而是学方法
  • 最后是我的私心,因为我也在创业,所以,技术细节上东西正是我在做的产品,所以,如果你想了解得更细,你需要和我有更商业合作。

 

区块链的技术专栏本来不在我的写作计划中的,但是因为来问我这方面的技术人太多了,所以,就被问了一系列的文章,这里的文章除了一些技术上的科普,同样有有很多我的观点,你不但可以学到技术,还可以了解一些金融知识和相关的逻辑,我个人觉得这篇文章是让你有独立思考的文章。

我的专栏还在继续,接下来还有一个系列的文章——《从技术到管理》,欢迎关注,也欢迎扫码订阅。

最后友情提示一下:在手机上学习并不是最好的学习方式,也不要在我的专栏上进行学习,把我的专栏当成一个你的助手,当成一个向导,当成一个跳板,真正的学习还是要在线下,专心的,系统地、有讨论地、不断实践地学习,这点希望大家切记!

 

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18360.html/feed 56
关于我”极客时间“的专栏 https://coolshell.cn/articles/18246.html https://coolshell.cn/articles/18246.html#comments Tue, 02 Jan 2018 08:56:11 +0000 https://coolshell.cn/?p=18246 Read More Read More

]]>
不少朋友都知道我在“极客时间”上开了一个收费专栏,这个专栏会开设大约一年的时间,一共会发布104篇文章。现在,我在上面以每周两篇文章的频率已发布了27篇文章了,也就是差不多两个半月的时间。新的一年开始了,写专栏这个事对我来说是第一次,在这个过程中有一些感想,所以,我想在这里说一下这些感受和一些相关的故事,算是一个记录,也算是对我专栏的正式介绍,还希望能得到、大家的喜欢和指点。(当然,CoolShell这边还是会持续更新的)

为什么要开设一个收费专栏

首先,说一下,为什么要开这个收费专栏。

老实说,我一开始根本就不想开收费专栏的,是的,完全不想!主要是有两个原因,一方面是我在创业中,我自然是没有太多的时间,另一方面是,我以前在《为什么我不在微信公众号上写文章》也说过,我觉得知识最好的方式是被检索、讨论、引用、整理、补充和更新。所以,收费这种模式,我感觉并不利于很好的传播。但是,我为什么还干了这么一件事?这事还得从2017年6月份开始说起。

这个月,一共有三家技术社区来找我,都是希望我能去他们那边开收费专栏,其中一家就是“极客邦科技”。对于这三家来说,从一开始我就是以婉拒的姿态回应的。而“极客邦科技”来找我的时候和我说,一周写五篇,写一年,一共260篇。我当时心想,“去你的,当我啥呢,你们真以为技术文章好写啊”?然后,他们问我可以写多少,我说,我现在也就一个月一篇的节奏……

然后,就开始了时间漫长的拉锯战。极客邦这边一直从6月份和我谈到9月份,完全就是不达目的不罢休的玩法,其间,每当我说一个问题,他们就会想出一个解,我这边不断地制造不能写下去的问题,他们就不断的给出相应的解。我其实是想让他们知难而退,另外,我也不确定这帮人对于这个事有多上心,因为写技术文章是需要非常认真的态度的,所以,我提出了很多比较苛刻的条件,甚至也很直白的直接拒绝,但是他们完全就跟没有听见似的,不断的想新的方法来让我”上床”(对!就是上床,不是上船)。

  • 我说,我最多一个月写2-3篇。他们和我说,我们看过了,你写的都是长文,都在5000字左右,一篇可以拆成上下篇,这样就有6篇左右了,然后,你每个月再来两篇文章,一篇是推荐一些资料或资源,一篇是回答读者的问题。这样就有8篇了,一周就可以发2篇了。
  • 我说,就算是这样,我也没有时间写,我现在创业中,事多得去了,完全没精力投入。然后,他们说,不用你写,我们来帮你写。你去客户那边,叫上我们,你到大会上做分享,叫上我们,你和别人分享,也叫让我们,我们全程录音,然后帮你你整理。然后每周末的时候来找你,和你聊上2个小时。我们把内容做出来,你再精编一下就好了。而且,我们也会帮你分担创业的精力的,我们极客邦/QCon/ArchSummit会帮你的产品做推广、做市场和BD客户……
  • 我说,就算是这样,我也没时间。他们说,我们还会给你配个编辑,一个不够就配两个,他们会帮你上网查资料,他们都是计算机专业的,一定是懂技术的。不会让你一个人写的。专栏这种事一定是会需要一个小的编辑团队的。

他们还甚至在晚上10点左右跑到我家门口来和我谈。这还没完,我继续刁难他们……

  • 我说,技术文章相当专业,你们来试试看,于是,我给了一篇极其难读的英文论文,还有一篇技术细节非常晦涩的英文文章,我让他们不要翻译,而是读懂后理解完用自己的话,能够让一般人读懂的话写一下。这两篇文章,就算是对于有多年经验的程序员来说,也是很难读的。结果他们一周后,就搞好了,我读了一下,不算特别好,但是对于他们来说,已经很不错了。
  • 我又说,我的文章中会有好些代码,有数学公式,在手机上怎么排版?阅读体验不行吧。你们还要做音频,我的文章中如果有代码,有图片,有数据公式,你让音频时怎么读?这不行吧。他们说,数学公式可以用LaTeX搞,代码排版会努力排好,同时也提供网页端的浏览。音频会这样搞,会让编辑把代码、数学公式、图片理解完后用别外的话说出来。也就是说,文章要有两个版本,一个是阅读的版本,一个是给音频师的版本。

就这样,这几个月的过程中,我心里面有了一些不一样的感觉。

  • 一方面,我觉得这种“不达目的不罢休”的做法让我欣赏。因为我也在创业,创业的过程中有很多难题,也会遇到很多困难和艰辛。而极客邦他们这样的作法我是非常认可,也是非常佩服的,因为,要是换作我,我可能早放弃去寻找其它人了。但是他们没有,他们一直不断地在穷尽一切方法来说服我写专栏。能这样做的人,我觉得这个社会上少之又少,绝大多数人都是畏难和容易退缩的,所以,感觉可以深入交往和合作。
  • 另外一方面,在整个过程中,我问他们,为什么你们要把这个事做得这么“重”?为什么不做得“轻”一点呢?还要录音频,音频对于技术型的文章里面有一堆坑啊,对于技术文章还有很多无法翻译的英文单词,在计算机的世界里,好多英文单词都是造出来的,音频师怎么读?(后来的确也是这样,我的音频师就把J2EE读成了“J二EE”),他们的编辑也不知道怎么读,就上Youtube上找相关视频看老外是怎么发音的,然后标注好。而且,我的文章有时候写得太快,经常会有一些小错误,文字好改,但是还要改语音。对于这些,我都觉得好重啊,结果他们说,就是要做个“重的”,就是要做一个别人达不到的标杆,让竞争对手望而却步!

对于这两点,是让我很赞的。这样的做事精神和态度让我很佩服,是啊,在Amazon里也常说,要不断地提高标准。而且这让我深入思考了一下,一个事如果想要做好,做到极致,就算再简单的事,也会变成复杂,这个世界上可能并不存在“轻模式”,只要你想做好,再“轻”的事都会变“重”。他们的这些做法,让我有了一种同道中人的感觉,人总是会向比自己强的人或是跟自己比较像的人靠近的。我感觉我在创业路上,就是要和这样的人在一起,面对再难的事,都要想尽一切办法解决之,面对再轻的事,都要花心思用重的模式去做好。

而其它两个来找我做同样的事的公司,却没有让我看到他们有这样把事做成的不服输的决心和态度,真是形成了强烈的反差和对比。

于是,我就这样“从”了!这里要点名一下极客邦的两个人——我叫他们作“双蕾”:司巧蕾郭蕾。(池大大也为极客时间付出了好多,因为大家都认识他了,我就弱化他一下了,嘿嘿)

这个专栏主要会写什么样的内容

这是一个收费专栏,一旦收费了,我的压力也大了,因为你要写的内容就一定要能达到可以收费的价值了,不以再像个人博客一样,想写什么就什么。好在我从2003年开始我就在给好多企业做一些商业化的讲座和培训,也给一些公司做过一些商业的咨询和技术方案,包括在过去两年内帮助过一些公司打单。另外,在过去的10年内,我也在技术、职业和成长上帮助过很多年轻人。这些内容,我都没有完整或是具体地写在CoolShell中,所以,我觉得这些内容是可以放在这个收费专栏的。

此外,我在CoolShell上的文章都是不系统的,是碎片式的,还有一些只是知识,还不是认识。而我过去成长的20年,我的经验和知识已经在某些方面形成了比较完整的体系,而且有一些技术也能看到本质上的东西。所以,我觉得这些东西是可以呈现在这个专栏内的,都是非常有商业价值的,一定是可以帮助到大家的。当然,其中的一些东西,不是初级入门的程序员能够看懂的,需要有一定的工作经验和基础知识。

而在我入行的这20年来,我觉得对于一个企业,一个团队,一个个体的程序员来说,有三件事是密不可分,也是相辅相成的,这三件事就是:技术、发展和管理。每个人,每个团队,每个企业,都需要认真地面对技术,不断地挑战新的技术,并且还要非常认真地发展个人和团队,而这些都需要对自我的管理或是对团队和公司的管理才能更高效的达成。

所以,我的专栏会由这三部份构成:

  • 技术。对于技术方面,我不会写太多关于知识点的东西,因为这些知识点大家可以自行Google可以RTFM。我要写就一定是以体系化的,而且要能直达技术的本质。我入行这20年来,我最擅长的是针对各种大规模的系统,所以,我会有2-3个和分布式系统相关的系列文章,然后,我学过也用过好多编程语言,所以,我也会有一系列的关于编程本质的文章,而我对一些基础知识研究的也比较多,所以,还会有一系列的和基础知识相关的文章。当然,其中还会穿插一些其它的技术文章,比如一些热点事件,还有一些经验之谈,包括,我会把我的《程序员技术练级攻略》在这个专栏里重新再写一遍。这些东西一定会让大家有醍醐灌顶的感觉。
  • 成长。在过去这20年中,我感觉得到,很多人都会非常在意自己的成长。所以,我会分享一堆我亲身经历的,也是我自己实验的一定和个人发展相关的文章。比如,像技术变现啊、如何面试、如何选择新的技术、如何学习、如何管理自己的时间、如何管理自己的老板和工作、如何成为一个Leader……这些东西一定会对大家有用。但是,我这里一定不会有速成的东西。一切都是要花时间和精力的。如果你想要速成,你不应该来订阅我的专栏。
  • 管理。这20年,我觉得做好技术工作,得做好技术的管理工作,只有管理好了软件工程和技术团队,技术才能发挥出最大的潜力。大多数的技术都是管理上的问题。所以,我会写上一系列的和管理相关的文章,管理三个要素,团队、项目和管理者自己。所以,我会从这三个方面写一系列包括,人员招聘、绩效考核、提升士气、解决冲突、面对变化、沟通说服、项目管理、任何排期、会议、远程管理……等等一系列的文章。这些东西都是我在外企时,接受到的世界顶级管理培训机构培训内容,我会把我的实践写出来分享给大家。这其中一定少不了亚马逊相关的各种实践。这些东西,我和很多公司和大佬都讲过,到目前为止还没有人不赞的。

现在,我这个专栏写了快三个月了,第一部分和第二部分已经有一些呈现了。我周末和假期的时间也完全都搭进去了 ;-)。后面的文章还在和我的编辑一起在整理和书写中,我感觉这个专栏只收199一年简直是太便宜了,我有点想涨价的冲动了。哈哈。

幕后团队

最后说一下我的专栏编辑——她叫杨爽!以前是CSDN的程序员杂志的编辑,后来去了七牛,现在和我一起做我的这个专栏。她对我的这个专栏上的投入非常大,除了帮助我编辑文章,还要帮音频师标注语气,英文发音,以及音频版的文章,还要深度参与写作,有的文章我只给了一个大纲,甚至只是一个方向,或是一系列的素材,然后都是她来操刀的,比如“推荐阅读”的文章、还有技术领导力的下篇,基本上是由杨爽来出第一版,然后我再上面再做修改和补充。她说,写技术文章真是太累了,尤其是帮你编辑你的分布式系列的文章,我基本都把这些技术都看了个大概了。我调侃到,如果你完全搞懂了,你就不用做编辑了,你可以做技术去了,嗯,而且,可以变成架构师了。

另外,她会深度的编辑我的文章,尤其是每篇文章最后的一些总结或是一些问题都是她写的。在我的一篇答疑的文章中,她自己加入了一个观点——“很多事情能做到什么程度,其实在思想的源头就被决定了,因为它会绝大程度地受到思考问题出发点、思维方式、格局观、价值观等因素的影响”,这个观点被读者当成是我的观点,其实,这是杨爽的观点,当然我也很同意。

所以,我的这个专栏离不开杨爽的付出,我和她一般都是在晚上或是周末沟通,因为平时我的时候都被创业的事给占据了。所以,她也只能适配我的时间,但她真的很努力,我能感觉得到她想把文章的质量不断提高的迫切。

关于专栏的音频师,他叫柴巍,是天津广播电台的主持人,一个89年的小伙子,网上他的个人信息在这里。他跨界来读这些技术文章的确对他来说非常不容易,因为一方面这文章里讲的这些东西他都看不懂,另外,他也不认识我,我脾气和性格他不知道,所以,他读我的文章里,并不能完全准确地把握相关的语气。这就需要杨爽来帮他标注和调整,有些地方,不断地修改,不断地录,大家知道,录音和写文章不一样,文章要修改很简单,语音要修改就非常麻烦,得把上下文全都一并重新再读一篇,这个过程的确难,杨爽在其中也花费了大量的时间和这个小伙子沟通和调整。

在一开始,有播音腔,也被读者吐槽了,他自己后来一直在调整,目前越来越符合咱们的要求。这个小哥是非常努力和有挑战精神的,他在这个过程中,也是非常信守承诺的。去年12月6日,录分布式系统冰与火那篇文章时,他上午有自己的工作,下午要开会,晚上又有单位活动,他还是活动的主持人,他实在是没有时间了。我也和我的编辑说,算了,先发文章,后面再补音频。但是他还是挤时间把音频录出来了,期间,我还不知情地又修改了一下文章,他又配合修改,直到完全改好。打车去参加活动,还好提前20分钟赶到,没有耽误主持活动。

唠唠叨叨写这么多,也没什么干货!算是一份记录吧。也希望大家能够从我的专栏中看到这个团队的确是在用心做事的,是的,能认识这些人,还能一起合作,在我的人生经历上是非常有价值的事了。

希望大家在新的一年里也能遇到这样的人。我们一起加油!

图片来自:电影《速度与激情》——Ride or Die

 

(全文完)

 


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18246.html/feed 67
Go语言、Docker 和新技术 https://coolshell.cn/articles/18190.html https://coolshell.cn/articles/18190.html#comments Mon, 30 Oct 2017 01:24:20 +0000 https://coolshell.cn/?p=18190 Read More Read More

]]>
上个月,作为 Go 语言的三位创始人之一,Unix 老牌黑客罗勃·派克(Rob Pike)在新文章“Go: Ten years and climbing”中,回顾了一下 Go 语言的发展过程。其中提到,Go 语言这十年的迅猛发展大到连他们自己都没有想到,并且还成为了云计算领域中新一代的开发语言。还提到了,中国程序员对 Go 语言的热爱完全超出了他们的想象,甚至他们都不敢相信是真的。

这让我想起,我在 2015 年 5 月份拜访 Docker 公司在湾区的总部时,Docker 负责人也和我表达了相似的感叹:他们完全没有想到居然中国有那么多人喜欢 Docker,而且还有这么多人在为 Docker 做贡献,这让他们感到非常意外。此外,还跟我说,中国是除了美国本土之外的另一个如此喜欢 Docker 技术的国家,在其它国家都没有看到。

的确如他们所说,Go 语言和 Docker 这两种技术已经成为新一代的云计算技术,而且可以看到其发展态势非常迅猛。而中国也成为了像美国一样在强力推动这两种技术的国家。这的确是一件让人感到非常高兴的事,因为中国在跟随时代潮流这件事上已经做得非常不错了。

然而,从 2014-2015 年我在阿里推动 Docker 和 Go 语言的痛苦和失败过程中,以及这许多年来,有很多很多人问我是否要学 Go 语言,是否要学 Docker,Go 和 Docker 是否能用在生产线上,这些问题看来,对于 Go 语言和 Docker 这两种技术,在国内的技术圈中有相当大的一部分人和群体还在执观望或是不信任的态度。

所以,我想写这篇文章,从两个方面来论述一下我的观点和看法。

  • 一个方面,为什么 Go 语言和 Docker 会是新一代的云计算技术。
  • 另一个方面,作为技术人员,我们如何识别什么样的新技术会是未来的趋势。

这两个问题是相辅相成的,所以我会把这两个问题揉在一起谈。

虽然 Go 语言是在 2009 年底开源的,但我是从 2012 年才开始接触和学习 Go 语言的。我只花了一个周末两天的时间就学完了,而且在这两天,我还很快地写出了一个能工作很好的网页爬虫程序,以及一个简单的高并发文件处理服务,用于提取前面抓取的网页的关键内容。这两个程序都很简单,总共才写了不到 500 行代码。

我当时对 Go 语言有几点体会。

第一,语言简单,上手快。Go 语言的语法特性简直是太简单了,简单到你几乎玩不出什么花招,直来直去的,学习曲线很低,上手非常快。

第二,并行和异步编程几乎无痛点。Go 语言的 Goroutine 和 Channel 这两个神器简直就是并发和异步编程的巨大福音。像 C、C++、Java、Python 和 JavaScript 这些语言的并发和异步方式太控制就比较复杂了,而且容易出错,而 Go 解决这个问题非常地优雅和流畅。这对于编程多年受尽并发和异步折磨的我来说,完全就是让我眼前一亮的感觉。

(图片来自 Medium:Why should you learn Go?

第三,Go 语言的 lib 库麻雀虽小五脏俱全。Go 语言的 lib 库中基本上有绝大多数常用的库,虽然有些库还不是很好,但我觉得不是问题,因为我相信在未来的发展中会把这些问题解决掉。

第四,C 语言的理念和 Python 的姿态。C 语言的理念是信任程序员,保持语言的小巧,不屏蔽底层且底层友好,关注语言的执行效率和性能。而 Python 的姿态是用尽量少的代码完成尽量多的事。于是我能够感觉到,Go 语言想要把 C 和 Python 统一起来,这是多棒的一件事啊。

(图片来自 Medium:Why should you learn Go?

所以,即便 Go 语言存在诸多的问题,比如垃圾回收、异常处理、泛型编程等,但相较于上面这几个优势,我认为这些问题都是些小问题。于是就毫不犹豫地入坑了。

当然,一个技术能不能发展起来,关键还要看三点。

  • 有没有一个比较好的社区。像 C、C++、Java、Python 和 JavaScript 的生态圈都是非常丰富和火爆的。尤其是有很多商业机构参与的社区那就更为人气爆棚了,比如 Linux 的社区。
  • 有没有一个工业化的标准。像 C、C++、Java 都是有标准化组织的。尤其是 Java,其在架构上还搞出了像 J2EE 这样的企业级标准。
  • 有没有一个或多个杀手级应用。C、C++ 和 Java 的杀手级应用不用多说了,就算是对于 PHP 这样还不能算是一个好的编程语言来说,因为是 Linux 时代的第一个杀手级解决方案 LAMP 中的关键技术,所以,也发展起来了。

上述的这三点是非常关键的,新的技术只需要占到其中一到两点就已经很不错了,何况有的技术,比如 Java,是三点全占到了,所以,Java 的发展是如此好。当然,除了上面这三点重要的,还有一些其它的影响因素,比如:

  • 学习曲线是否低,上手是否快。这点非常重要,C++ 在这点上越做越不好了。
  • 有没有一个不错的提高开发效率的开发框架。如:Java 的 Spring 框架,C++ 的 STL 等。
  • 是否有一个或多个巨型的技术公司作为后盾。如:Java 和 Linux 后面的 IBM、Sun……
  • 有没有解决软件开发中的痛点。如:Java 解决了 C 和 C++ 的内存管理问题。

用这些标尺来量一下 Go 语言,我们可以清楚地看到:

  • Go 语言容易上手;
  • Go 语言解决了并发编程和写底层应用开发效率的痛点;
  • Go 语言有 Google 这个世界一流的技术公司在后面;
  • Go 语言的杀手级应用是 Docker,而 Docker 的生态圈在这几年完全爆棚了。

所以,Go 语言的未来是不可限量的。当然,我个人觉得,Go 可能会吞食很多 C、C++、Java 的项目。不过,Go 语言所吞食主要的项目应该是中间层的项目,既不是非常底层也不会是业务层。

也就是说,Go 语言不会吞食底层到 C 和 C++ 那个级别的,也不会吞食到高层如 Java 业务层的项目。Go 语言能吞食的一定是 PaaS 上的项目,比如一些消息缓存中间件、服务发现、服务代理、控制系统、Agent、日志收集等等,没有复杂的业务场景,也到不了特别底层(如操作系统)的中间平台层的软件项目或工具。而 C 和 C++ 会被打到更底层,Java 会被打到更上层的业务层。这是我的一个判断。

好了,我们再用上面的标尺来量一下 Go 语言的杀手级应用 Docker,你会发现基本是一样的。

  • Docker 上手很容易。
  • Docker 解决了运维中的环境问题以及服务调度的痛点。
  • Docker 的生态圈中有大公司在后面助力。比如 Google。
  • Docker 产出了工业界标准 OCI。
  • Docker 的社区和生态圈已经出现像 Java 和 Linux 那样的态势。
  • ……

所以,早在 3、4 年前我就觉得 Docker 一定会是未来的技术。虽然当时的坑儿还很多,但是,相对于这些大的因素来说,那些小坑儿都不是问题。只是需要一些时间,这些小坑儿在未来 5-10 年就可以完全被填平了。

同样,我们可以看到 Kubernetes 作为服务和容器调度的关键技术一定会是最后的赢家。这点我在去年初就能够很明显地感觉到了。

关于 Docker 我还想多说几句,这是云计算中 PaaS 的关键技术,虽然,这世上在出现 Docker 之前,几乎所有的要玩公有 PaaS 的公司和产品都玩不起来,比如:Google 的 GAE,国内的各种 XAE,如淘宝的 TAE,新浪的 SAE 等。但我还是想说,PaaS 是一个被世界或是被产业界严重低估的平台。

PaaS 层是承上启下的关键技术,任何一个不重视 PaaS 的公司,其技术架构都不可能让这家公司成长为一个大型的公司。因为 PaaS 层的技术主要能解决下面这些问题。

  • 软件生产线的问题。持续集成和持续发布,以及 DevOps 中的技术必需通过 PaaS。
  • 分布式服务化的问题。分布式服务化的服务高可用、服务编排、服务调度、服务发现、服务路由,以及分布式服务化的支撑技术完全是 PaaS 的菜。
  • 提高服务的可用性 SLA。提高服务可用性 SLA 所需要的分布式、高可用的技术架构和运维工具,也是 PaaS 层提供的。
  • 软件能力的复用。软件工程中的核心就是软件能力的复用,这一点也完美地体现在 PaaS 平台的技术上。

老实说,这些问题的关键程度已经到了能判断一家依托技术的公司的研发能力是否靠谱的程度。没有这些技术,依托技术拓展业务的公司几乎没有可能发展得规模很大。

在后面,我会在“极客时间我的付费专栏里另外写几篇文章详细地讲一下分布式服务化和 PaaS 平台的重要程度。

最后,我还要说一下,为什么要早一点地进入这些新技术,而不是等待这些技术成熟了后再进入。原因有这么几个。

技术的发展过程非常重要。我进入 Go 和 Docker 的技术不能算早,但也不算晚,从 2012 年学习 Go,到 2013 年学习 Docker 到今天,我清楚地看到了这两种技术的生态圈发展过程。让我收获最大的并不是这些技术本身,而是一个技术的变迁和行业的发展。

从中,我看到了非常具体的各种思潮和思路,这些东西比起 Go 和 Docker 来说更有价值。因为,这不但让我重新思考我已掌握的技术以及如何更好地解决已有的问题,而且还让我看到了未来。我不但有了技术优势,而且这些知识还让我的技术生涯多了很多的可能性。

这些关键新技术,可以让你拿到技术的先机。这些对一个需要技术领导力的个人或公司来说都是非常重要的。

一个公司或是个人能够占有技术先机,就会比其它公司或个人有更大的影响力。一旦未来行业需求引爆,那么这个公司或是个人的影响力就会形成一个比较大的护城河,并可以快速地产生经济利益。

近期,在与中国移动、中国电信以及一些股份制银行进行交流的过程中,我已看到通讯行业、金融行业对于 PaaS 平台的理解已经超过了互联网公司,而我近 3 年来在这些技术上的研究让我也从中受益非浅。

所以,Go 语和 Docker 作为 PaaS 平台的关键技术前途是无限的,我很庆幸赶上了这个浪潮,也很庆幸在 3 年前我就看到了这个趋势,现在我也在用这些技术开发相关的技术产品,助力于为高速成长的公司提供这些关键技术。

 

最后注明一下:

这篇文章于上周发布于“极客时间”的我的付费专栏中。极客时间中的付费是我受Geekbang邀请写的一个付费专栏,因为过去10多年给企业有过很多内训,过去2年又给好多企业做过一些咨询工作,所以,我会把一些商业化的内容写在极客时间里,当然,也会有一些我的新文章。关于这个事,我后面我专门开一篇文章说一下。(大家可以到 Apple的App Store上搜极客时间,Android版本等到12月初吧)

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18190.html/feed 48
关于Facebook 的 React 专利许可证 https://coolshell.cn/articles/18140.html https://coolshell.cn/articles/18140.html#comments Tue, 19 Sep 2017 06:08:00 +0000 https://coolshell.cn/?p=18140 Read More Read More

]]>
随着Apache、百度、Wordpress都在和Facebook的React.js以及其专利许可证划清界限,似乎大家又在讨论Facebook的这个BSD+PATENT的许可证问题了。这让我想起了之前在Medium读过的一篇文章——《React, Facebook, and the Revocable Patent License, Why It’s a Paper》,我觉得那篇文章写的不错,而且还是一个会编程的律师写的,所以有必要把这篇文章传播到中文社区这边来。注意,我不会全部翻译,我只是用我的语言来负责搬运内容和观点,我只想通过这篇文章让大家了解一下这个世界以及专利相关的知识,这样可以避免你看到某乎的“怎么看待XXX”这类的问题时人云亦云,能有自己的独立思考和自我判断。;-)

这篇文章的作者叫Dennis Walsh,他自称是亚历桑那和加利福尼亚州的律师,主要针对版权法和专利诉论的法律领域。但是这个律师不一样,他更很喜欢商业和软件多一些。现在他用React/GraphQL/Elixir在写一个汽车代理销售相关的软件,而且已经发布到第2版了。

首先,作者表明,专利法经常被人误解,因为其实充满了各种晦涩难懂的法律术语,所以,作者用个例子来讲述专利的一个原则 —— 专利并不是授于让你制造或开发的权利,而是授予你可以排他的权利。(事实上似乎也是这样,申请专利很多时候都不是为了制作相关的产品,而是为了防止别人使用类似的技术制作相关的产品)

如果有公司X为铅笔申请了专利,而另一家公司Y为把用于铅笔的橡皮擦申请了专利。那么,公司X可以阻止公司Y来生产铅笔,而对带橡皮擦的铅笔没办法,但是公司Y的专利可以让公司X不能生产带有橡皮擦的铅笔。

所以,公司Y的橡皮擦专利又被广泛地叫作“Blocking Patent”。公司Y不能说他发明了铅笔,因为这是公司X的专利,但是,他们可以让公司X无法对铅笔做出某些改进。

于是,因为这种 Blocking Patent 存在,对于开源的公司是不利的,因为根据上面的那个例子来说,开源公司就是公司X,他们做了一个基础的软件,而公司Y在上面做了些改进,并注册成了专利,从而导致开源的公司X无法对它基础开源软件作出被公司Y专利阻止的改进,开源的公司X希望能够自由地使用公司Y的橡皮擦专利,因为毕竟是它发明了铅笔并放弃了铅笔的专利。

于是就出来了“专利反击条款”(Patent Retaliation Clauses)。一般来说有两种专利条款,一种是弱条款,一种是强条款。

Weak Patent Retaliation Clauses – 这种条款声明,如果许可证持有者用某个专利来打击许可证颁布者,那么专利就视为终止。用人话来表达就是,公司X做了一个开源铅笔,而公司Y注册了橡皮檫专利。此时,公司X做了一支带像皮擦的铅笔,而公司Y马上对公司X提起专利侵权诉讼。那么,公司Y就失去了对底层铅笔的专利控制。(正如前面所说的,公司Y的橡皮擦专利因为在起诉公司X的开源铅笔,而失去了对开源铅笔的专利排他权利)

Strong Patent Retailiation Clauses – 这种条款声明比“弱条款”要的更多。具体来说就是,任何专利声明终结许可证,而不管这个专利有没有和你基础的软件有关系。用人话来说就是,公司Y使用他们的热气球专利来起诉公司X,那么公司Y就失去了他们对铅笔的专利限制。

我个人理解起来,这两种条款看上去是防御性质的。

Facebook的React的Patent License如下:

The license granted hereunder will terminate, automatically and without notice,if you (or any of your subsidiaries, corporate affiliates or agents) initiatedirectly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporateaffiliates, (ii) against any party if such Patent Assertion arises in whole orin part from any software, technology, product or service of Facebook or any ofits subsidiaries or corporate affiliates, or (iii) against any party relating to the Software. Notwithstanding the foregoing, if Facebook or any of itssubsidiaries or corporate affiliates files a lawsuit alleging patentinfringement against you in the first instance, and you respond by filing apatent infringement counterclaim in that lawsuit against that party that isunrelated to the Software, the license granted hereunder will not terminateunder section (i) of this paragraph due to such counterclaim.

这些条款中和基础软件没有任何关系,所以,这个条款是“强专利反击条款”

在后面,本文的作者又解解释了,为什么React的“强专利反击条款”就跟没有似的。他在文中针对一些歇斯底里的言论,如:“Facebook不用害怕专利诉讼了,而且他可以随时偷袭你家的专利仓库”,也作出了一些解释来分析这个事。

Contractural Liability – 意思是说,专利方面的东西只会影响专利上的事,而不会影响和专利无关的事,React底层协议是BSD-3许可证还是会被保留。换句话说,React的“强专利反击条款”只生效于专利层面,而不会对非常专利的软件使用产生问题,如果和专利无关,React还是走BSD-3的许可协议。

Copyright Liability – 这个和Contractural Liablitity 一样。作者说,如果有人有特别的案例或是有说服力的论据来说明Facebook的这个条款会作用于非专利的地方,那么,请告诉他。

Patent Liability – 专利的责任和损害是两件事,非专业人士总是会把其搞混。

第一个问题是Liability, 要搞清这个事,得搞清“Patent’s Claims”,而不是这个技术的技术规格说明,技术规格说明和权力主张是两码事。作者说,现在的很多专利都是一些想法,很多投机份子随便一拍脑袋就发明出一个想法,然后就去注册专利了。但是可以被用来法律执行的只有“Patent’s Claims”(专利的权利主张),而不是那些想法。这些权利主张相当相当的晦涩难读,而且是会故意被模糊掉的,因为,当你清楚的定义了你的发明是什么,那么,就可以清楚的定义出来什么不是你的发明。比如:一个铅笔专利权利主张里说,“这一个用石墨和木头组合起来的写字工具”,那么,只要我不用木头和石墨来做组合,而是用塑料来做组合,那么我就不是专利侵权。所以,一般来说,专利主张是会更为通用一些,比如,“这是一个用于涂画表面的装置,其包括:与涂画端相连的握持端”。作者这里给了一个苹果公司的滑动解锁专利的示例。可以感受一下产品规格说明和专利权利主张完全是两码事。

专利这些事,在法律界里是非常非常困难作出评估的。所以,这个社会每年都会给律师们几十亿美金来一遍又一遍地回答这些问题,而且律师还经常回答错了。而对于美国的法律,对于专利诉讼会有一个叫Markman hearing的审前听证会(马克曼听证会),自从1996年美国最高法的“马克曼诉威斯幽仪器公司案”这个听证会就变成了一个惯例,美国联邦法院用这个听证会来向决定专利权利主张的解释,而且,上诉法院还经常性的推翻审判法院的裁决。(对于美国法律来说,一般是法官认证法律,陪审团认定事实,然而,对于专利而言,1996年的那个案件认为专利术语是一个需要法官决定的法律问题,而不是陪审团决定的事实问题。关于马克曼听证会的事,可以参看本文未尾的附录)

所以,要决定Facebook的专利责任,我们需要评估Facebook的专利及其权利主张,而不是技术规格说明。具体来说,要明确Facebook对于React这个底层技术的专利权利主张是什么?但是作者搜了一下,发现什么也没有找到。也就是说,对于USPTO(美国专利商标局)或法院来说,他们没办法对Facebook的这样没有为React申请专利的方式来执行任何和专利的诉讼,也就是说,Facebook的这个React License的条款,美国政府是无法在法律上支持的。

第二个问题是专利损害。就算是Facebook可以评估出来一个合法可执行的专利来保护React,对于专利损害也是很有问题的。作者说他到目前还没有发现一个开源软件被专利侵权的事,就算有这样的案例,也不会是这里说的这个事。作者觉得在这个事上操作起来就是一个笑话。

另外,作者认为,React 专利许可证这个事就是个纸老虎。因为,一方面,这个专利不像电信通讯里的那些专利,你拿不掉。作者认为要从你的代码中把React去掉虽然难,但是也不是什么很难的事,另外,要打这样的专利官司,一般来说,在美国至少要花100-200万美金的费用才能发起诉讼,而要胜诉则需要需要200多万到2000万美金的费用,你觉得你要花多钱才能把React从你的代码库中剔除?肯定比这钱少。

作者还认为,Facebook玩这个事虽然出发点不错,但是感觉并不聪明,从目前的情况看下来,就像他想咬你一口,但却没有牙。

后面,作者还说了一下,转成别的框架会不会有问题?比如:你用Preact/Vue或是你自研的东西?作者说,未必,如果Facebook真的为React注册了专利,比如:React里的组件技术、虚拟DOM渲染技术等等。那么,你用Preact/Vue或是带这样技术的自研的框架,那么,从你使用的第一天就在侵犯Facebook的专利权了。然而,使用React反而不会有这么大的风险,因为Facebook让你免费的用React。作者说,用别的框架的法律风险比用其它替代品的风险更高。

后面,作者也更新了一篇文章 《Using GraphQL? Why Facebook Now Owns You》,意思是,用React可能还好,但是用GraphQL就有问题了。因为找到了GraphQL的专利—— “Graph Query Logic”

后来我查了一下,我发现,React也有个相关的专利—— “Efficient event delegation in browser scripts ”,看上去和虚拟DOM渲染有关。Holy Shit!

好了,用还是不用React我也不知道,总之,这个世界比较复杂,我只是想借这篇文章来学习一下法律上的相关东西,欢迎听到大家的观点。

最后,请允许我调侃一下来结束本文——“不用担心React的许可证问题,因为前端不是一年半就用新的框架重写一次么?”哈哈。

更新:Facebook官方于20017年9月23日在其官方blog上发贴《Relicensing React, Jest, Flow, and Immutable.js》决定取消之前的带专利的许可证。

延伸阅读

马克曼听证会 – Markman Hearing

马克曼听证会的一些背景知识,下面的文字来源于《“马克曼听证”制度的由来及启示

与美国专利诉讼的悠长历史相比,1996年才经美国最高法院确立的“马克曼听证”(Markman Hearing,也称为Claim Construction,即权利要求书的解释)无疑是一项年轻的制度。但由于几乎所有的专利侵权诉讼中都会遇到涉案专利权利要求书的解释这一核心问题,且因“马克曼听证”结果往往清楚地预示了案件结果,经“马克曼听证”获得有利结论的一方一旦据此向法庭提起不审即判的动议,专利侵权诉讼往往可就此快速了结,因此该制度的确立成为美国专利诉讼历史上的一件大事。

“马克曼听证”制度的由来

“马克曼听证”制度确立之前,在专利侵权诉讼中的权利要求书解释,通常交由陪审团在对案件事实进行裁决时一并做出,且并不会在诉讼文件上单独就陪审团这一问题的判断进行记录。1991年,马克曼(Markman)先生因认为其拥有的专利号为RE33054的“干洗衣物贮存及追踪控制装置”专利权被Westview公司所侵犯,遂向宾夕法尼亚州东区联邦地方法院提起了专利侵权诉讼。

该专利是用扫描的方式,将客户的衣物编号扫描后输入电脑中做分类标示,并在衣物干洗过程中追踪衣物位置,干洗完成后自动将衣物放回客户固定的存贮位置。被告的产品则是同时运用扫描器和电脑两种方式,将客户干洗衣物的资料存入电脑并显示费用、日期等相关信息。本案陪审团的裁决认为被告装置构成对原告专利权利的侵犯,但该地方法院认为系争专利与被告装置在功能实施上并不一致,遂推翻陪审团的裁决,判决被告不构成侵权。

马克曼不服,于1995年向联邦上诉法院提起上诉,但其上诉理由仅为联邦地方法院错误地解释了陪审团关于专利权利要求书解释中某个词语的涵义。联邦上诉法院在审理该案时,将案件的核心问题定为两个:一是原告对于请求项解释有无权利请求陪审团裁决;二是联邦地方法院是否正确地解释了“Inventory”一词。该院多数法官经审理后认为,权利要求书范围的解释与确定,属于法律问题而非事实问题,因而属于法院权限,而不应交由陪审团决定,且此前将此问题交由陪审团确定并不妥当。同时,由于认为原告专利与被告装置存在实质功能上的差异,联邦上诉法院亦不认为被告构成专利侵权。少数持不同意见的该院法官主要是质疑这一结论违反了美国第七宪法修正案(即所有根据美国法律进行的普通法诉讼,只要争议金额超过20美元,即有要求陪审团审判的权利)。

马克曼不服,向最高法院提出上诉。1996年4月23日,美国最高法院就马克曼诉Westview器械公司案(Markman v. Westview Instruments, Inc. 517 U.S. 370 (1996))做出终审裁决,裁决认定:权利要求书的解释是联邦地区法院法官应当处理的法律问题,而不是应当由陪审团来认定的事实问题,尽管在解释权利要求书的过程中可能会包含一些对于事实问题的解释,且这样做并不违反第七修正案赋予给陪审团的权利。这一裁决标志着“马克曼听证”制度的正式确立。

“马克曼听证”制度的不足

该案判决是美国专利诉讼史上的一个重大转折。“马克曼听证”成为法官专门用于解释专利权利要求的一个经常性听证程序,用以解决专利侵权诉讼的核心问题。由于该听证并非普遍适用,因此,十几年来,联邦民事诉讼规则并未正式对其有任何规定,而是给予法院绝对的自由裁量权。但是,何时可以进行“马克曼听证”?如何进行?是否有必要进行?类似问题在一定程度上困扰了审理专利侵权案件较多的法院。

2001年,加州北区联邦地区法院率先制定了供本法院使用的专利审判专属规则(Patent Local Rules),其中第四部分即为权利要求书的解释程序(Claim Construction Proceddings),对“马克曼听证”的时间、流程、限制及当事人的义务均进行了规定。此后,各州纷纷效仿。目前,乔治亚州北区联邦法院、得克萨斯州东区联邦法院、得克萨斯州南区联邦法院、宾夕法尼亚州西区联邦法院等都制订了书面的“马克曼听证”程序指南。近年来,不断有新的案例在解释与细化着“马克曼听证”,如2006年的Wilson Sporting Goods Co.诉Hillerich & Bradsby Co.案,2005年的Phillips诉AWH Corp.案,2008年的Howmedica Osteonics Corp.诉Wright Medical Technology, Inc.案,这些司法实践大大拓展与丰富了“马克曼听证”使用的实体和程序规则,使之日渐成为美国专利诉讼中一个复杂、完备的司法程序。以至于竟然有人开发了模拟“马克曼听证”程序,只要你愿意,可以下载并训练,以熟悉和确保有真正的权利要求书解释时不会出现不利于自己的问题。

但是,该听证带来的问题也逐渐受到重视。有人质疑说该程序导致专利诉讼费用增加,因为“马克曼听证”通常会单独进行,且程序复杂,因此导致当事人花费大量的时间与精力,更为重要的是,由于40%至60%的联邦地区法院案件会在联邦巡回上诉法院被推翻,因此,花费巨大的“马克曼听证”似乎价值有限。同时,权利要求书的解释要求是不多不少,忠实于技术发明思想与发明事实,但由于地区法院分散,法官的相关技术知识不十分专业,将权利要求书解释这样的问题交给他们,难免会带来一些无法克服的问题。

“马克曼听证”制度的启示

我国民事诉讼中并无陪审团制度,案件的事实问题与法律问题均由法官审理与确定。在专利侵权诉讼中,对于案件中涉及到的技术问题可以通过专家鉴定等方式解决,但并不因此免除法官审理案件的义务,即法律问题的判断归于法官,事实的法律属性判断仍然归于法官。同时,权利要求书的解释在我国的专利侵权诉讼中并不是一个单独的程序,而是合并在案件审理过程中。因此,仅就我国的司法审判而言,“马克曼听证”制度并无直接的借鉴意义。

但是,对于那些已经走出和正在走出国门的企业来说,了解与掌握这一重要的专利诉讼程序却是极其重要的。通领科技集团的积极尝试充分证明了这一点,而且随着这一程序的不断成熟,美国国际贸易法院(ITC)也开始在审理时适用“马克曼听证”制度。所以,知道“马克曼听证”意味着什么,确保所提交的用于解释权利要求的文件确实充分,学会利用“马克曼听证”,无论是对于破解美国的专利诉讼威胁,还是为未来准备有效的法律武器,无疑都非常重要。(知识产权报 作者 魏玮)

 

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18140.html/feed 28
如何免费的让网站启用HTTPS https://coolshell.cn/articles/18094.html https://coolshell.cn/articles/18094.html#comments Sat, 26 Aug 2017 06:06:17 +0000 https://coolshell.cn/?p=18094 Read More Read More

]]>
今天,我把CoolShell变成https的安全访问了。我承认这件事有点晚了,因为之前的HTTP的问题也有网友告诉我,被国内的电信运营商在访问我的网站时加入了一些弹窗广告。另外,HTTP的网站在搜索引擎中的rank会更低。所以,这事早就应该干了。现在用HTTP访问CoolShell会被得到一个 301 的HTTPS的跳转。下面我分享一下启用HTTPS的过程。

我用的是 Let’s Encrypt这个免费的解决方案。Let’s Encrypt 是一个于2015年推出的数字证书认证机构,将通过旨在消除当前手动创建和安装证书的复杂过程的自动化流程,为安全网站提供免费的SSL/TLS证书。这是由互联网安全研究小组(ISRG – Internet Security Research Group,一个公益组织)提供的服务。主要赞助商包括电子前哨基金会Mozilla基金会Akamai以及Cisco等公司(赞助商列表)。

2015年6月,Let’s Encrypt得到了一个存储在硬件安全模块中的离线的RSA根证书。这个由IdenTrust证书签发机构交叉签名的根证书被用于签署两个证书。其中一个就是用于签发请求的证书,另一个则是保存在本地的证书,这个证书用于在上一个证书出问题时作备份证书之用。因为IdenTrust的CA根证书目前已被预置于主流浏览器中,所以Let’s Encrypt签发的证书可以从项目开始就被识别并接受,甚至当用户的浏览器中没有信任ISRG的根证书时也可以。

以上介绍文字来自 Wikipedia 的 Let’s Encrypt 词条

为你的网站来安装一个证书十分简单,只需要使用电子子前哨基金会EFF的 Certbot,就可以完成。

1)首先,打开 https://certbot.eff.org 网页。

2)在那个机器上图标下面,你需要选择一下你用的 Web 接入软件 和你的 操作系统。比如,我选的,nginx 和 Ubuntu 14.04

3)然后就会跳转到一个安装教程网页。你就照着做一遍就好了。

以Coolshell.cn为例 – Nginx + Ubuntu

首先先安装相应的环境:

$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx

然后,运行如下命令:

$ sudo certbot --nginx

certbot 会自动检查到你的 nginx.conf 下的配置,把你所有的虚拟站点都列出来,然后让你选择需要开启 https 的站点。你就简单的输入列表编号(用空格分开),然后,certbot 就帮你下载证书并更新 nginx.conf 了。

你打开你的 nginx.conf 文件 ,你可以发现你的文件中的 server 配置中可能被做了如下的修改:

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/coolshell.cn/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/coolshell.cn/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot

 # Redirect non-https traffic to https
if ($scheme != "https") {
  return 301 https://$host$request_uri;
} # managed by Certbot

 

这里建议配置 http2,这要求 Nginx 版本要大于 1.9.5。HTTP2 具有更快的 HTTPS 传输性能,非常值得开启(关于性能你可以看一下这篇文章)。需要开启HTTP/2其实很简单,只需要在 nginx.conf 的 listen 443 ssl; 后面加上 http2 就好了。如下所示:

listen 443 ssl http2; # managed by Certbot 
ssl_certificate /etc/letsencrypt/live/coolshell.cn/fullchain.pem; # managed by Certbot 
ssl_certificate_key /etc/letsencrypt/live/coolshell.cn/privkey.pem; # managed by Certbot 
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot

然后,就 nginx -s reload 就好了。

但是,Let’s Encrypt 的证书90天就过期了,所以,你还要设置上自动化的更新脚本,最容易的莫过于使用 crontab 了。使用 crontab -e 命令加入如下的定时作业(每个月都强制更新一下):

0 0 1 * * /usr/bin/certbot renew --force-renewal
5 0 1 * * /usr/sbin/service nginx restart

当然,你也可以每天凌晨1点检查一下:

0 1 * * * certbot renew 

注:crontab 中有六个字段,其含义如下:

  • 第1个字段:分钟 (0-59)
  • 第2个字段:小时 (0-23)
  • 第3个字段:日期 (1-31)
  • 第4个字段:月份 (1-12 [12 代表 December])
  • 第5个字段:一周当中的某天 (0-7 [7 或 0 代表星期天])
  • /path/to/command – 计划执行的脚本或命令的名称

这么方便的同时,我不禁要问,如果是一些恶意的钓鱼网站也让自己的站点变成https的,这个对于一般用来说就有点难以防范了。哎……

当然,在nginx或apache上启用HTTPS后,还没有结束。因为你可能还需要修改一下你的网站,不然你的网站在浏览时会出现各种问题。

启用HTTPS后,你的网页中的所有的使用 http:// 的方式的地方都要改成 https:// 不然你的图片,js, css等非https的连接都会导致浏览器抱怨不安全而被block掉。所以,你还需要修改你的网页中那些 hard code http:// 的地方。

对于我这个使用wordpress的博客系统来说,有这么几个部分需要做修改。

1)首先是 wordpress的 常规设置中的 “WordPress 地址” 和 “站点地址” 需要变更为 https 的方式。

2)然后是文章内的图片等资源的链接需要变更为 https 的方式。对此,你可以使用一个叫 “Search Regex” 插件来批量更新你历史文章里的图片或别的资源的链接。比如:把 http://coolshell.cn 替换成了 https://coolshell.cn

3)如果你像我一样启用了文章缓存(我用的是WP-SuperCache插件),你还要去设置一下 “CDN” 页面中的 “Site URL” 和 “off-site URL” 确保生成出来的静态网页内是用https做资源链接的。

基本上就是这些事。希望大家都来把自己的网站更新成 https 的。

嗯,12306,你什么时候按照这个教程做一下你的证书?

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18094.html/feed 87
API设计原则 – Qt官网的设计实践总结 https://coolshell.cn/articles/18024.html https://coolshell.cn/articles/18024.html#comments Tue, 25 Jul 2017 06:16:30 +0000 http://coolshell.cn/?p=18024 Read More Read More

]]>
(感谢好友 @李鼎 翻译此文)

原文链接:API Design Principles – Qt Wiki
基于Gary的影响力上 Gary Gao 的译文稿:C++的API设计指导

译序

Qt的设计水准在业界很有口碑,一致、易于掌握和强大的API是Qt最著名的优点之一。此文既是Qt官网上的API设计指导准则,也是Qt在API设计上的实践总结。虽然Qt用的是C++,但其中设计原则和思考是具有普适性的(如果你对C++还不精通,可以忽略与C++强相关或是过于细节的部分,仍然可以学习或梳理关于API设计最有价值的内容)。整个篇幅中有很多示例,是关于API设计一篇难得的好文章。

需要注意的是,这篇Wiki有一些内容并不完整,所以,可能会有一些阅读上的问题,我们对此做了一些相关的注释。

PS:翻译中肯定会有不足和不对之处,欢迎评论&交流;另译文源码在GitHub的这个仓库中,可以提交Issue/Fork后提交代码来建议/指正。

API设计原则

一致、易于掌握和强大的API是Qt最著名的优点之一。此文总结了我们在设计Qt风格API的过程中所积累的诀窍(know-how)。其中许多是通用准则;而其他的则更偏向于约定,遵循这些约定主要是为了与已有的API保持一致。

虽然这些准则主要用于对外的API(public API),但在设计对内的API(private API)时也推荐遵循相同的技巧(techniques),作为开发者之间协作的礼仪(courtesy)。

如有兴趣也可以读一下 Jasmin Blanchette 的Little Manual of API Design (PDF) 或是本文的前身 Matthias Ettrich 的Designing Qt-Style C++ APIs

1. 好API的6个特质

API之于程序员就如同图形界面之于普通用户(end-user)。API中的『P』实际上指的是『程序员』(Programmer),而不是『程序』(Program),强调的是API是给程序员使用的这一事实。

在第13期Qt季刊Matthias 的关于API设计的文章中提出了观点:API应该极简(minimal)且完备(complete)、语义清晰简单(have clear and simple semantics)、符合直觉(be intuitive)、易于记忆(be easy to memorize)和引导API使用者写出可读代码(lead to readable code)。

1.1 极简

极简的API是指每个class的public成员尽可能少,public的class也尽可能少。这样的API更易理解、记忆、调试和变更。

1.2 完备

完备的API是指期望有的功能都包含了。这点会和保持API极简有些冲突。如果一个成员函数放在错误的类中,那么这个函数的潜在用户就会找不到,这也是违反完备性的。

1.3 语义清晰简单

就像其他的设计一样,我们应该遵守最少意外原则(the principle of least surprise)。好的API应该可以让常见的事完成的更简单,并有可以完成不常见的事的可能性,但是却不会关注于那些不常见的事。解决的是具体问题;当没有需求时不要过度通用化解决方案。(举个例子,在Qt 3中,QMimeSourceFactory不应命名成QImageLoader并有不一样的API。)

1.4 符合直觉

就像计算机里的其他事物一样,API应该符合直觉。对于什么是符合直觉的什么不符合,不同经验和背景的人会有不同的看法。API符合直觉的测试方法:经验不很丰富的用户不用阅读API文档就能搞懂API,而且程序员不用了解API就能看明白使用API的代码。

1.5 易于记忆

为使API易于记忆,API的命名约定应该具有一致性和精确性。使用易于识别的模式和概念,并且避免用缩写。

1.6 引导API使用者写出可读代码

代码只写一次,却要多次的阅读(还有调试和修改)。写出可读性好的代码有时候要花费更多的时间,但对于产品的整个生命周期来说是节省了时间的。

最后,要记住的是,不同的用户会使用API的不同部分。尽管简单使用单个Qt类的实例应该符合直觉,但如果是要继承一个类,让用户事先看好文档是个合理的要求。

2. 静态多态

相似的类应该有相似的API。在继承(inheritance)合适时可以用继承达到这个效果,即运行时多态。然而多态也发生在设计阶段。例如,如果你用QProgressBar替换QSlider,或是用QString替换QByteArray,你会发现API的相似性使的替换很容易。这即是所谓的『静态多态』(static polymorphism)。

静态多态也使记忆API和编程模式更加容易。因此,一组相关的类有相似的API有时候比每个类都有各自的一套API更好。

一般来说,在Qt中,如果没有足够的理由要使用继承,我们更倾向于用静态多态。这样可以减少Qt public类的个数,也使刚学习Qt的用户在翻看文档时更有方向感。

2.1 好的案例

QDialogButtonBoxQMessageBox,在处理按钮(addButton()setStandardButtons()等等)上有相似的API,不需要继承某个QAbstractButtonBox类。

2.2 差的案例

QTcpSocketQUdpSocket都继承了QAbstractSocket,这两个类的交互行为的模式(mode of interaction)非常不同。似乎没有什么人以通用和有意义的方式用过QAbstractSocket指针(或者  以通用和有意义的方式使用QAbstractSocket指针)。

2.3 值得斟酌的案例

QBoxLayoutQHBoxLayoutQVBoxLayout的父类。好处:可以在工具栏上使用QBoxLayout,调用setOrientation()使其变为水平/垂直。坏处:要多一个类,并且有可能导致用户写出这样没什么意义的代码,((QBoxLayout *)hbox)->setOrientation(Qt::Vertical)

3. 基于属性的API

新的Qt类倾向于用『基于属性(property)的API』,例如:

QTimer timer;
timer.setInterval(1000);
timer.setSingleShot(true);
timer.start();

这里的 属性 是指任何的概念特征(conceptual attribute),是对象状态的一部分 —— 无论它是不是Q_PROPERTY。在说得通的情况下,用户应该可以以任何顺序设置属性,也就是说,属性之间应该是正交的(orthogonal)。例如,上面的代码可以写成:

QTimer timer;
timer.setSingleShot(true);
timer.setInterval(1000);
timer.start();

【译注】:正交性是指改变某个特性而不会影响到其他的特性。《程序员修炼之道》中讲了关于正交性的一个直升飞机坠毁的例子,讲得深入浅出很有画面感。

为了方便,也写成:

timer.start(1000);

类似地,对于QRegExp会是这样的代码:

QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern(".");
regExp.setPatternSyntax(Qt::WildcardSyntax);

为实现这种类型的API,需要借助底层对象的懒创建。例如,对于QRegExp的例子,在不知道模式语法(pattern syntax)的情况下,在setPattern()中去解释"."就为时过早了。

属性之间常常有关联的;在这种情况下,我们必须小心处理。思考下面的问题:当前的风格(style)提供了『默认的图标尺寸』属性 vs. QToolButton的『iconSize』属性:

toolButton->setStyle(otherStyle);
toolButton->iconSize();    // returns the default for otherStyle
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize();    // returns (52, 52)
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize();    // returns (52, 52)

提醒一下,一旦设置了iconSize,设置就会一直保持,即使改变当前的风格。这 很好。但有的时候需要能重置属性。有两种方法:

  1. 传入一个特殊值(如QSize()-1或者Qt::Alignment(0))来表示『重置』
  2. 提供一个明确的重置方法,如resetFoo()unsetFoo()

对于iconSize,使用QSize()(比如 QSize(–1, -1))来表示『重置』就够用了。

在某些情况下,getter方法返回的结果与所设置的值不同。例如,虽然调用了widget->setEnabled(true),但如果它的父widget处于disabled状态,那么widget->isEnabled()仍然返回的是false。这样是OK的,因为一般来说就是我们想要的检查结果(父widget处于disabled状态,里面的子widget也应该变为灰的不响应用户操作,就好像子widget自身处于disabled状态一样;与此同时,因为子widget记得在自己的内心深处是enabled状态的,只是一直等待着它的父widget变为enabled)。当然诸如这些都必须在文档中妥善地说明清楚。

4. C++相关

4.1 值 vs. 对象

4.1.1 指针 vs. 引用

指针(pointer)还是引用(reference)哪个是最好的输出参数(out-parameters)?

void getHsv(int *h, int *s, int *v) const;
void getHsv(int &h, int &s, int &v) const;

大多数C++书籍推荐尽可能使用引用,基于一个普遍的观点:引用比指针『更加安全和优雅』。与此相反,我们在开发Qt时倾向于指针,因为指针让用户代码可读性更好。比较下面例子:

color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);

只有第一行代码清楚表达出hsv参数在函数调用中非常有可能会被修改。

这也就是说,编译器并不喜欢『出参』,所你应该在新的API中避免使用『出参』,而是返回一个结构体,如下所示:

struct Hsv { int hue, saturation, value };
Hsv getHsv() const;

【译注】:函数的『入参』和『出参』的混用会导致 API 接口语义的混乱,所以,使用指针,在调用的时候,实参需要加上“&”,这样在代码阅读的时候,可以看到是一个『出参』,有利于代码阅读。(但是这样做,在函数内就需要判断指针是否为空的情况,因为引用是不需要判断的,所以,这是一种 trade-off)

另外,如果这样的参数过多的话,最好使用一个结构体来把数据打包,一方面,为一组返回值取个名字,另一方面,这样有利用接口的简单。

4.1.2 按常量引用传参 vs. 按值传参

如果类型大于16字节,按常量引用传参。

如果类型有重型的(non-trivial)拷贝构造函数(copy-constructor)或是重型的析构函数(destructor),按常量引用传参以避免执行这些函数。

对于其它的类型通常应该按值传参。

示例:

void setAge(int age);
void setCategory(QChar cat);
void setName(QLatin1String name);

// const-ref is much faster than running copy-constructor and destructor
void setAlarm(const QSharedPointer<Alarm> &alarm);

// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect
// are good examples of other classes you should pass by value.

【译注】:这是传引用和传值的差别了,因为传值会有对像拷贝,传引用则不会。所以,如果对像的构造比较重的话(换句话说,就是对像里的成员变量需要的内存比较大),这就会影响很多性能。所以,为了提高性能,最好是传引用。但是如果传入引用的话,会导致这个对象可能会被改变。所以传入const reference。

4.2 虚函数

在C++中,当类的成员函数声明为virtual,主要是为了通过在子类重载此函数能够定制函数的行为。将函数声明为virtual的目的是为了让对这个函数已有的调用变成执行实际实例的代码路径。对于没有在类外部调用的函数声明成virtual,你应该事先非常慎重地思考过。

// QTextEdit in Qt 3: member functions that have no reason for being virtual
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat *f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursor **c = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }

QTextEdit从Qt 3移植到Qt 4的时候,几乎所有的虚函数都被移除了。有趣的是(但在预料之中),并没有人对此有大的抱怨,为什么?因为Qt 3没用到QTextEdit的多态行为 —— 只有你会;简单地说,没有理由去继承QTextEdit并重写这些函数,除非你自己调用了这些方法。如果在Qt在外部你的应用程序你需要多态,你可以自己添加多态。

【译注】:『多态』的目的只不过是为了实践 —— 『依赖于接口而不是实现』,也就是说,接口是代码抽像的一个非常重要的方式(在Java/Go中都有专门的接口声明语法)。所以,如果没有接口抽像,使用『多态』的意义也就不大了,因为也就没有必要使用『虚函数』了。

4.2.1 避免虚函数

在Qt中,我们有很多理由尽量减少虚函数的数量。每一次对虚函数的调用会在函数调用链路中插入一个未掌控的节点(某种程度上使结果更无法预测),使得bug修复变得更复杂。用户在重写的虚函数中可以做很多疯狂的事:

  • 发送事件
  • 发送信号
  • 重新进入事件循环(例如,通过打开一个模态文件对话框)
  • 删除对象(即触发『delete this』)

还有其他很多原因要避免过度使用虚函数:

  • 添加、移动或是删除虚函数都带来二进制兼容问题(binary compatibility/BC)
  • 重载虚函数并不容易
  • 编译器几乎不能优化或内联(inline)对虚函数的调用
  • 虚函数调用需要查找虚函数表(v-table),这比普通函数调用慢了2到3倍
  • 虚函数使得类很难按值拷贝(尽管也可以按值拷贝,但是非常混乱并且不建议这样做)

经验告诉我们,没有虚函数的类一般bug更少、维护成本也更低。

一般的经验法则是,除非我们以这个类作为工具集提供而且有很多用户来调用某个类的虚函数,否则这个函数九成不应该设计成虚函数。

【译注】:

  1. 使用虚函数时,你需要对编译器的内部行为非常清楚,否则,你会在使用虚函数时,觉得有好些『古怪』的问题发生。比如在创建数组对象的时候。
  2. 在C++中,会有一个基础类,这个基础类中已经实现好了很多功能,然后把其中的一些函数放给子类去修改和实现。这种方法在父类和子类都是一组开发人员维护时没有什么问题,但是如果这是两组开发人员,这就会带来很多问题了,就像Qt这样,子类完全无法控制,全世界的开发人员想干什么就干什么。所以,子类的代码和父类的代码在兼容上就会出现很多很多问题。所以,还是上面所说,其实,虚函数应该声明在接口的语义里(这就是设计模式的两个宗旨——依赖于接口,而不是实现;钟爱于组合,而不是继承。也是为什么Java和Go语言使用interface关键字的原因,C++在多态的语义上非常容易滥用)

4.2.2 虚函数 vs. 拷贝

多态对象(polymorphic objects)和值类型的类(value-type classes)两者很难协作好。

包含虚函数的类必须把析构函数声明为虚函数,以防止父类析构时没有清理子类的数据,导致内存泄漏。

如果要使一个类能够拷贝、赋值或按值比较,往往需要拷贝构造函数、赋值操作符(operator =)和相等操作符(operator ==)。

class CopyClass {
public:
    CopyClass();
    CopyClass(const CopyClass &other);
    ~CopyClass();
    CopyClass &operator =(const CopyClass &other);
    bool operator ==(const CopyClass &other) const;
    bool operator !=(const CopyClass &other) const;

    virtual void setValue(int v);
};

如果继承CopyClass这个类,预料之外的事就已经在代码时酝酿了。一般情况下,如果没有虚成员函数和虚析构函数,就不能创建出可以多态的子类。然而,如果存在虚成员函数和虚析构函数,这突然变成了要有子类去继承的理由,而且开始变得复杂了。起初认为只要简单声明上虚操作符重载函数(virtual operators)。 但其实是走上了一条混乱和毁灭之路(破坏了代码的可读性)。看看下面的这个例子:

class OtherClass {
public:
    const CopyClass &instance() const; // 这个方法返回的是什么?可以赋值什么?
};

(这部份还未完成)

【译注】:因为原文上说,这部份并没有完成,所以,我也没有搞懂原文具体也是想表达什么。不过,就标题而言,原文是想说,在多态的情况下拷贝对象所带来的问题??

4.3 关于const

C++的关键词const表明了内容不会改变或是没有副作用。可以应用于简单的值、指针及指针所指的内容,也可以作为一个特别的属性应用于类的成员函数上,表示成员函数不能修改对象的状态。

然而,const本身并没有提供太大的价值 —— 很多编程语言甚至没有类似const的关键词,但是却并没有因此产生问题。实际上,如果你不用函数重载,并在C++源代码用搜索并删除所有的const,几乎总能编译通过并且正常运行。尽量让使用的const保持实用有效,这点很重要。

让我们看一下在Qt的API设计中与const相关的场景。

4.3.1 输入参数:const指针

有输入指针参数的const成员函数,几乎总是const指针参数。

如果函数声明为const,意味着既没有副作用,也不会改变对象的可见状态。那为什么它需要一个没有const限定的输入参数呢?记住const类型的函数通常被其他const类型的函数调用,接收到的一般都是const指针(只要不主动const_cast,我们推荐尽量避免使用const_cast)

以前:

bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos) const;

QWidget声明了许多非const指针输入参数的const成员函数。注意,这些函数可以修改传入的参数,不能修改对象自己。使用这样的函数常常要借助const_cast转换。如果是const指针输入参数,就可以避免这样的转换了。

之后:

bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos) const;

注意,我们在QGraphicsItem中对此做了修正,但是QWidget要等到Qt 5:

bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point) const;

4.3.2 返回值:const值

调用函数返回的非引用类型的结果,称之为右值(R-value)。

非类(non-class)的右值总是无cv限定类型(cv-unqualified type)。虽然从语法上讲,加上const也可以,但是没什么意义,因为鉴于访问权限这些值是不能改变的。多数现代编译器在编译这样的代码时会提示警告信息。

【译注】:cv-qualified的类型(与cv-unqualified相反)是由const或者volatile或者volatile const限定的类型。详见cv (const and volatile) type qualifiers – C++语言参考

当在类类型(class type)右值上添加const关键字,则禁止访问非const成员函数以及对成员的直接操作。

不加const则没有以上的限制,但几乎没有必要加上const,因为右值对象生存时间(life time)的结束一般在C++清理的时候(通俗的说,下一个分号地方),而对右值对象的修改随着右值对象的生存时间也一起结束了(也就是本条语句的执行完成的时候)。

示例:

struct Foo {
    void setValue(int v) { value = v; }
    int value;
};

Foo foo() {
    return Foo();
}

const Foo cfoo() {
    return Foo();
}

int main() {
    // The following does compile, foo() is non-const R-value which
    // can't be assigned to (this generally requires an L-value)
    // but member access leads to a L-value:
    foo().value = 1; // Ok, but temporary will be thrown away at the end of the full-expression.

    // The following does compile, foo() is non-const R-value which
    // can't be assigned to, but calling (even non-const) member
    // function is fine:
    foo().setValue(1); // Ok, but temporary will be thrown away at the end of the full-expression.

    // The following does _not_compile, foo() is ''const'' R-value
    // with const member which member access can't be assigned to:
    cfoo().value = 1; // Not ok.

    // The following does _not_compile, foo() is ''const'' R-value,
    // one cannot call non-const member functions:
    cfoo().setValue(1); // Not ok
}

【译注】:上述的代码说明,如果返回值不是const的,代码可以顺利编译通过,然而并没有什么卵用,因为那个临时对像马上就被抛弃了。所以,这样的无用的代码最好还是在编译时报个错,以免当时头脑发热想错了,写了一段没用但还以为有用的代码。

4.3.3 返回值:非const的指针还是有const的指针

谈到const函数应该返回非const的指针还是const指针这个话题时,多数人发现在C++中关于『const正确性』(const correctness)在概念上产生了分歧。 问题起源是:const函数本身不能修改对象自身的状态,却可以返回成员的非const指针。返回指针这个简单动作本身既不会影响整个对象的可见状态,当然也不会改变这个函数职责范围内涉及的状态。但是,这却使得程序员可以间接访问并修改对象的状态。

下面的例子演示了通过返回非const指针的const函数绕开const约定(constness)的诸多方式中的一种:

QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const {
    moveBy(10, 10); // doesn't compile!
    window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // compiles!
}

返回const指针的函数正是保护以避免这些(可能是不期望的/没有预料到的)副作用,至少是在一定程度上。但哪个函数你会觉得更想返回const指针,或是不止一个函数?

若采用const正确(const-correct)的方法,每个返回某个成员的指针(或多个指向成员的指针)的const函数必须返回const指针。在实践中,很不幸这样的做法将导致无法使用的API:

QGraphicsScene scene;
// … populate scene

foreach (const QGraphicsItem *item, scene.items()) {
    item->setPos(qrand() % 500, qrand() % 500); // doesn't compile! item is a const pointer
}

QGraphicsScene::items()是一个const函数,顺着思考看起来这个函数只应该返回const指针。

在Qt中,我们几乎只有非const的使用模式。我们选择的是实用路子: 相比滥用非const指针返回类型带来的问题,返回const指针更可能招致过分使用const_cast的问题。

4.3.4 返回值:按值返回 还是 按const引用返回?

若返回的是对象的拷贝,那么返回const引用是更直接的方案; 然而,这样的做法限制了后面想要对这个类的重构(refactor)。 (以d-point的典型做法(idiom)为例,我们可以在任何时候改变Qt类在内存表示(memory representation);但却不能在不破坏二进制兼容性的情况下把改变函数的签名,返回值从const QFoo &变为QFoo。) 基于这个原因,除去对运行速度敏感(speed is critical)而重构不是问题的个别情形(例如,QList::at()),我们一般返回QFoo而不是const QFoo &

【译注】:参看《Effective C++》中条款23:Don’t try to return a reference when you must return an object

4.4.5 const vs. 对象的状态

const正确性(Const correctness)的问题就像C圈子中vi与emacs的讨论,因为这个话题在很多地方都存在分歧(比如基于指针的函数)。

但通用准则是const函数不能改变类的可见状态。『状态』的意思是『自身以及涉及的职责』。这并不是指非const函数能够改变自身的私有成员,也不是指const函数改变不了。而是指函数是活跃的并存在可见的副作用(visible side effects)。const函数一般没有任何可见的副作用,比如:

QSize size = widget->sizeHint(); // const
widget->move(10, 10); // not const

代理(delegate)负责在其它对象上绘制内容。 它的状态包括它的职责,因此包括在哪个对象做绘制这样的状态。 调用它的绘画行为必然会有副作用; 它改变了它绘制所在设备的外观(及其所关联的状态)。鉴于这些,paint()作为const函数并不合理。 进一步说,任何paint()QIconpaint()的视图函数是const函数也不合理。 没有人会从内部的const函数去调用QIcon::paint(),除非他想显式地绕开const这个特性。 如果是这种情况,使用const_cast会更好。

// QAbstractItemDelegate::paint is const
void QAbstractItemDelegate::paint(QPainter **painter, const QStyleOptionViewItem &option, const QModelIndex &index) const

// QGraphicsItem::paint is not const
void QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem option, QWidget *widget)

const关键字并不能按你期望的样子起作用。应该考虑将其移除而不是去重载const/非const函数。

5. API的语义和文档

当传值为-1的参数给函数,函数会是什么行为?有很多类似的问题……

是警告、致命错误还是其它?

API需要的是质量保证。API第一个版本一定是不对的;必须对其进行测试。 以阅读使用API的代码的方式编写用例,且验证这样代码是可读的。

还有其他的验证方法,比如

  • 让别人使用API(看了文档或是先不看文档都可以)
  • 给类写文档(包含类的概述和每个函数)

6. 命名的艺术

命名很可能是API设计中最重要的一个问题。类应该叫什么名字?成员函数应该叫什么名字?

6.1 通用的命名规则

有几个规则对于所有类型的命名都等同适用。第一个,之前已经提到过,不要使用缩写。即使是明显的缩写,比如把previous缩写成prev,从长远来看是回报是负的,因为用户必须要记住缩写词的含义。

如果API本身没有一致性,之后事情自然就会越来越糟;例如,Qt 3 中同时存在activatePreviousWindow()fetchPrev()。恪守『不缩写』规则更容易地创建一致性的API。

另一个时重要但更微妙的准则是在设计类时应该保持子类名称空间的干净。在Qt 3中,此项准则并没有一直遵循。以QToolButton为例对此进行说明。如果调用QToolButton的 name()caption()text()或者textLabel(),你觉得会返回什么?用Qt设计器在QToolButton上自己先试试吧:

  • name属性是继承自QObject,返回内部的对象名称,用于调试和测试。
  • caption属性继承自QWidget,返回窗口标题,对QToolButton来说毫无意义,因为它在创建的时候parent就存在了。
  • text函数继承自QButton,一般用于按钮。当useTextLabel不为true,才用这个属性。
  • textLabel属性在QToolButton内声明,当useTextLabeltrue时显示在按钮上。

为了可读性,在Qt 4中QToolButtonname属性改成了objectNamecaption改成了windowTitle,删除了textLabel属性因为和text属性相同。

当你找不到好的命名时,写文档也是个很好方法:要做的就是尝试为各个条目(item)(如类、方法、枚举值等等)写文档,并用写下的第一句话作为启发。如果找不到一个确切的命名,往往说明这个条目是不该有的。如果所有尝试都失败了,并且你坚信这个概念是合理的,那么就发明一个新名字。像widget、event、focus和buddy这些命名就是在这一步诞生的。

【译注】:写文档是一个非常好的习惯。写文档的过程其实就是在帮你梳理你的编程思路。很多时候,文档写着写着你就会发现要去改代码去了。除了上述的好处多,写文档还有更多的好处。比如,在写文档的过程中,你发现文字描述过于复杂了,这表明着你的代码或逻辑是复杂的,这就倒逼你去重构你的代码。所以 —— 写文档其实就是写代码

6.2 类的命名

识别出类所在的分组,而不是为每个类都去找个完美的命名。例如,所有Qt 4的能感知模型(model-aware)的item view,类后缀都是ViewQListViewQTableViewQTreeView),而相应的基于item(item-based)的类后缀是WidgetQListWidgetQTableWidgetQTreeWidget)。

6.3 枚举类型及其值的命名

声明枚举类型时,需要记住在C++中枚举值在使用时不会带上类型(与Java、C#不同)。下面的例子演示了枚举值命名得过于通用的危害:

namespace Qt
{
    enum Corner { TopLeft, BottomRight, ... };
    enum CaseSensitivity { Insensitive, Sensitive };
    ...
};

tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);

在最后一行,Insensitive是什么意思?命名枚举类型的一个准则是在枚举值中至少重复此枚举类型名中的一个元素:

namespace Qt
{
    enum Corner { TopLeftCorner, BottomRightCorner, ... };
    enum CaseSensitivity { CaseInsensitive, CaseSensitive };
    ...
};

tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);

当对枚举值进行或运算并作为某种标志(flag)时,传统的做法是把或运算的结果保存在int型的值中,但这不是类型安全的。Qt 4提供了一个模板类QFlags,其中的T是枚举类型。为了方便使用,Qt用typedef重新定义了QFlag类型,所以可以用Qt::Alignment代替QFlags

习惯上,枚举类型命名用单数形式(因为它一次只能『持有』一个flag),而持有多个『flag』的类型用复数形式,例如:

enum RectangleEdge { LeftEdge, RightEdge, ... };
typedef QFlags<RectangleEdge> RectangleEdges;

在某些情形下,持有多个『flag』的类型命名用单数形式。对于这种情况,持有的枚举类型名称要求是以Flag为后缀:

enum AlignmentFlag { AlignLeft, AlignTop, ... };
typedef QFlags<AlignmentFlag> Alignment;

6.4 函数和参数的命名

函数命名的第一准则是可以从函数名看出来此函数是否有副作用。在Qt 3中,const函数QString::simplifyWhiteSpace()违反了此准则,因为它返回了一个QString而不是按名称暗示的那样,改变调用它的QString对象。在Qt 4中,此函数重命名为QString::simplified()

虽然参数名不会出现在使用API的代码中,但是它们给程序员提供了重要信息。因为现代的IDE都会在写代码时显示参数名称,所以值得在头文件中给参数起一个恰当的名字并在文档中使用相同的名字。

6.5 布尔类型的getter与setter方法的命名

bool属性的getter和setter方法命名总是很痛苦。getter应该叫做checked()还是isChecked()scrollBarsEnabled()还是areScrollBarEnabled()

Qt 4中,我们套用以下准则为getter命名:

  • 形容词以is为前缀,例子:
    • isChecked()
    • isDown()
    • isEmpty()
    • isMovingEnabled()
  • 然而,修饰名词的形容词没有前缀:
    • scrollBarsEnabled(),而不是areScrollBarsEnabled()
  • 动词没有前缀,也不使用第三人称(-s):
    • acceptDrops(),而不是acceptsDrops()
    • allColumnsShowFocus()
  • 名词一般没有前缀:
    • autoCompletion(),而不是isAutoCompletion()
    • boundaryChecking()
  • 有的时候,没有前缀容易产生误导,这种情况下会加上is前缀:
    • isOpenGLAvailable(),而不是openGL()
    • isDialog(),而不是dialog()
      (一个叫做dialog()的函数,一般会被认为是返回QDialog。)

setter的名字由getter衍生,去掉了前缀后在前面加上了set;例如,setDown()setScrollBarsEnabled()

7. 避免常见陷阱

7.1 简化的陷阱

一个常见的误解是:实现需要写的代码越少,API就设计得越好。应该记住:代码只会写上几次,却要被反复阅读并理解。例如:

QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");

这段代码比下面的读起来要难得多(甚至写起来也更难):

QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");

【译注】:在有IDE的自动提示的支持下,后者写起来非常方便,而前者还需要看相应的文档。

7.2 布尔参数的陷阱

布尔类型的参数总是带来无法阅读的代码。给现有的函数增加一个bool型的参数几乎永远是一种错误的行为。仍以Qt为例,repaint()有一个bool类型的可选参数用于指定背景是否被擦除。可以写出这样的代码:

widget->repaint(false);

初学者很可能是这样理解的,『不要重新绘制!』,能有多少Qt用户真心知道下面3行是什么意思:

widget->repaint();
widget->repaint(true);
widget->repaint(false);

更好的API设计应该是这样的:

widget->repaint();
widget->repaintWithoutErasing();

在Qt 4中,我们通过移除了重新绘制(repaint)而不擦除widget的能力来解决了此问题。Qt 4的双缓冲使这种特性被废弃。

还有更多的例子:

widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_***.c??", false, true);

一个明显的解决方案是bool类型改成枚举类型。我们在Qt 4的QString中就是这么做的。对比效果如下:

str.replace("%USER%", user, false);               // Qt 3
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4

【译注】:关于这个条目可以看看 CoolShell 这篇文章一些展开的讨论: 千万不要把 BOOL 设计成函数参数

8. 案例研究

8.1 QProgressBar

为了展示上文各种准则的实际应用。我们来研究一下Qt 3中QProgressBar的API,并与Qt 4中对应的API作比较。在Qt 3中:

class QProgressBar : public QWidget
{
    ...
public:
    int totalSteps() const;
    int progress() const;

    const QString &progressString() const;
    bool percentageVisible() const;
    void setPercentageVisible(bool);

    void setCenterIndicator(bool on);
    bool centerIndicator() const;

    void setIndicatorFollowsStyle(bool);
    bool indicatorFollowsStyle() const;

public slots:
    void reset();
    virtual void setTotalSteps(int totalSteps);
    virtual void setProgress(int progress);
    void setProgress(int progress, int totalSteps);

protected:
    virtual bool setIndicator(QString &progressStr,
                              int progress,
                              int totalSteps);
    ...
};

该API相当的复杂和不一致;例如,reset()setTotalSteps()setProgress()是紧密联系的,但方法的命名并没明确地表达出来。

改善此API的关键是抓住QProgressBar与Qt 4的QAbstractSpinBox及其子类QSpinBoxQSliderQDail之间的相似性。怎么做?把progresstotalSteps替换为minimummaximumvalue。增加一个valueChanged()消息,再增加一个setRange()函数。

进一步可以观察到progressStringpercentageindicator其实是一回事,即是显示在进度条上的文本。通常这个文本是个百分比,但是可通过setIndicator()设置为任何内容。以下是新的API:

virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;

默认情况下,显示文本是百分比指示器(percentage indicator),通过重写text()方法来定制行为。

Qt 3的setCenterIndicator()setIndicatorFollowsStyle()是两个影响对齐方式的函数。他们可被一个setAlignment()函数代替:

void setAlignment(Qt::Alignment alignment);

如果开发者未调用setAlignment(),那么对齐方式由风格决定。对于基于Motif的风格,文字内容在中间显示;对于其他风格,在右侧显示。

下面是改善后的QProgressBar API:

class QProgressBar : public QWidget
{
    ...
public:
    void setMinimum(int minimum);
    int minimum() const;
    void setMaximum(int maximum);
    int maximum() const;
    void setRange(int minimum, int maximum);
    int value() const;

    virtual QString text() const;
    void setTextVisible(bool visible);
    bool isTextVisible() const;
    Qt::Alignment alignment() const;
    void setAlignment(Qt::Alignment alignment);

public slots:
    void reset();
    void setValue(int value);

signals:
    void valueChanged(int value);
    ...
};

8.2 QAbstractPrintDialog & QAbstractPageSizeDialog

Qt 4.0有2个幽灵类QAbstractPrintDialogQAbstractPageSizeDialog,作为 QPrintDialogQPageSizeDialog类的父类。这2个类完全没有用,因为Qt的API没有是QAbstractPrint-或是-PageSizeDialog指针作为参数并执行操作。通过篡改qdoc(Qt文档),我们虽然把这2个类隐藏起来了,却成了无用抽象类的典型案例。

这不是说, 的抽象是错的,QPrintDialog应该是需要有个工厂或是其它改变的机制 —— 证据就是它声明中的#ifdef QTOPIA_PRINTDIALOG

8.3 QAbstractItemModel

关于模型/视图(model/view)问题的细节在相应的文档中已经说明得很好了,但作为一个重要的总结这里还需要强调一下:抽象类不应该仅是所有可能子类的并集(union)。这样『合并所有』的父类几乎不可能是一个好的方案。QAbstractItemModel就犯了这个错误 —— 它实际上就是个QTreeOfTablesModel,结果导致了错综复杂(complicated)的API,而这样的API要让 所有本来设计还不错的子类 去继承。

仅仅增加抽象是不会自动就把API变得更好的。

8.4 QLayoutIterator & QGLayoutIterator

在Qt 3,创建自定义的布局类需要同时继承QLayoutQGLayoutIterator(命名中的G是指Generic(通用))。QGLayoutIterator子类的实例指针会包装成QLayoutIterator,这样用户可以像和其它的迭代器(iterator)类一样的方式来使用。通过QLayoutIterator可以写出下面这样的代码:

QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
    if (child->widget() == myWidget) {
        it.takeCurrent();
        return;
    }
    ++it;
}

在Qt 4,我们干掉了QGLayoutIterator类(以及用于盒子布局和格子布局的内部子类),转而是让QLayout的子类重写itemAt()takeAt()count()

8.5 QImageSink

Qt 3有一整套类用来把完成增量加载的图片传递给一个动画 —— QImageSource/Sink/QASyncIO/QASyncImageIO。由于这些类之前只是用于启用动画的QLabel,完全过度设计了(overkill)。

从中得到的教训就是:对于那些未来可能的还不明朗的需求,不要过早地增加抽象设计。当需求真的出现时,比起一个复杂的系统,在简单的系统新增需求要容易得多。


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/18024.html/feed 25
Linux PID 1 和 Systemd https://coolshell.cn/articles/17998.html https://coolshell.cn/articles/17998.html#comments Sun, 16 Jul 2017 13:40:55 +0000 http://coolshell.cn/?p=17998 Read More Read More

]]>
要说清 Systemd,得先从Linux操作系统的启动说起。Linux 操作系统的启动首先从 BIOS 开始,然后由 Boot Loader 载入内核,并初始化内核。内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程,也叫根进程。它负责产生其他所有用户进程。所有的进程都会被挂在这个进程下,如果这个进程退出了,那么所有的进程都被 kill 。如果一个子进程的父进程退了,那么这个子进程会被挂到 PID 1 下面。(注:PID 0 是内核的一部分,主要用于内进换页,参看:Process identifier

SysV Init

PID 1 这个进程非常特殊,其主要就任务是把整个操作系统带入可操作的状态。比如:启动 UI – Shell 以便进行人机交互,或者进入 X 图形窗口。传统上,PID 1 和传统的 Unix System V 相兼容的,所以也叫 sysvinit,这是使用得最悠久的 init 实现。Unix System V 于1983年 release。

sysvint 下,有好几个运行模式,又叫 runlevel。比如:常见的 3 级别指定启动到多用户的字符命令行界面,5 级别指定启起到图形界面,0 表示关机,6 表示重启。其配置在 /etc/inittab 文件中。

与此配套的还有 /etc/init.d//etc/rc[X].d,前者存放各种进程的启停脚本(需要按照规范支持 startstop子命令),后者的 X 表示不同的 runlevel 下相应的后台进程服务,如:/etc/rc3.d 是 runlevel=3 的。 里面的文件主要是 link 到  /etc/init.d/ 里的启停脚本。其中也有一定的命名规范:S 或 K 打头的,后面跟一个数字,然后再跟一个自定义的名字,如:S01rsyslogS02ssh。S 表示启动,K表示停止,数字表示执行的顺序。

UpStart

Unix 和 Linux 在 sysvint 运作多年后,大约到了2006年的时候,Linux内核进入2.6时代,Linux有了很多更新。并且,Linux开始进入桌面系统,而桌面系统和服务器系统不一样的是,桌面系统面临频繁重启,而且,用户会非常频繁的使用硬件的热插拔技术。于是,这些新的场景,让 sysvint 受到了很多挑战。

比如,打印机需要CUPS等服务进程,但是如果用户没有打机印,启动这个服务完全是一种浪费,而如果不启动,如果要用打印机了,就无法使用,因为sysvint 没有自动检测的机制,它只能一次性启动所有的服务。另外,还有网络盘挂载的问题。在 /etc/fstab 中,负责硬盘挂载,有时候还有网络硬盘(NFS 或 iSCSI)在其中,但是在桌面机上,有很可能开机的时候是没有网络的, 于是网络硬盘都不可以访问,也无法挂载,这会极大的影响启动速度。sysvinit 采用 netdev 的方式来解决这个问题,也就是说,需要用户自己在 /etc/fstab 中给相应的硬盘配置上 netdev 属性,于是 sysvint 启动时不会挂载它,只有在网络可用后,由专门的 netfs 服务进程来挂载。这种管理方式比较难以管理,也很容易让人掉坑。

所以,Ubuntu 开发人员在评估了当时几个可选的 init 系统后,决定重新设计这个系统,于是,这就是我们后面看到的 upstartupstart 基于事件驱动的机制,把之前的完全串行的同步启动服务的方式改成了由事件驱动的异步的方式。比如:如果有U盘插入,udev 得到通知,upstart 感知到这个事件后触发相应的服务程序,比如挂载文件系统等等。因为使用一个事件驱动的玩法,所以,启动操作系统时,很多不必要的服务可以不用启动,而是等待通知,lazy 启动。而且事件驱动的好处是,可以并行启动服务,他们之间的依赖关系,由相应的事件通知完成。

upstart 有着很不错的设计,其中最重要的两个概念是 Job 和 Event。

Job 有一般的Job,也有service的Job,并且,upstart 管理了整个 Job 的生命周期,比如:Waiting, Starting, pre-Start, Spawned, post-Start, Running, pre-Stop, Stopping, Killed, post-Stop等等,并维护着这个生命周期的状态机。

Event 分成三类,signal, methodhookssignal 就是异步消息,method 是同步阻塞的。hooks 也是同步的,但介于前面两者之间,发出hook事件的进程必须等到事件完成,但不检查是否成功。

但是,upstart 的事件非常复杂,也非常纷乱,各种各样的事件(事件没有归好类)导致有点凌乱。不过因为整个事件驱动的设计比之前的 sysvinit 来说好太多,所以,也深得欢迎。

Systemd

直到2010的有一天,一个在 RedHat工作的工程师 Lennart Poettering 和 Kay Sievers ,开始引入了一个新的 init 系统—— systemd。这是一个非常非常有野心的项目,这个项目几乎改变了所有的东西,systemd 不但想取代已有的 init 系统,而且还想干更多的东西。

Lennart 同意 upstart 干的不错,代码质量很好,基于事件的设计也很好。但是他觉得 upstart 也有问题,其中最大的问题还是不够快,虽然 upstart 用事件可以达到一定的启动并行度,但是,本质上来说,这些事件还是会让启动过程串行在一起。  如:NetworkManager 在等 D-Bus 的启动事件,而 D-Bus 在等 syslog 的启动事件。

Lennart 认为,实现上来说,upstart 其实是在管理一个逻辑上的服务依赖树,但是这个服务依赖树在表现形式上比较简单,你只需要配置——“启动 B好了就启动A”或是“停止了A后就停止B”这样的规则。但是,Lennart 说,这种简单其实是有害的(this simplification is actually detrimental)。他认为,

  • 从一个系统管理的角度出来,他一开始会设定好整个系统启动的服务依赖树,但是这个系统管理员要人肉的把这个本来就非常干净的服务依整树给翻译成计算机看的懂的 Event/Action 形式,而且 Event/Action 这种配置方式是运行时的,所以,你需要运行起来才知道是什么样的。
  • Event逻辑从头到脚到处都是,这个事件扩大了运维的复杂度,还不如之前的 sysvint。 也就是说,当用户配置了 “启动 D-Bus 后请启动 NetworkManager”, 这个 upstart 可以干,但是反过来,如果,用户启动 NetworkManager,我们应该先去启动他的前置依赖 D-Bus,然而你还要配置相应的反向 Event。本来,我只需要配置一条依赖的,结果现在我要配置很多很多情况下的Event。
  • 最后,upstart 里的 Event 的并不标准,很混乱,没有良好的定义。比如:既有,进程启动,运行,停止的事件,也有USB设备插入、可用、拔出的事件,还有文件系统设备being mounted、 mounted 和 umounted 的事件,还有AC电源线连接和断开的事件。你会发现,这进程启停的、USB的、文件系统的、电源线的事件,看上去长得很像, 但是没有被标准化抽像出来掉,因为绝大多数的事件都是三元组:start, condition, stop 。这种概念设计模型并没有在 upstart 中出现。因为 upstart 被设计为单一的事件,而忽略了逻辑依赖。

当然,如果 systemd 只是解决 upstart 的问题,他就改造 upstart 就好了,但是 Lennart 的野心不只是想干个这样的事,他想干的更多。

首先,systemd 清醒的认识到了 init 进程的首要目标是要让用户快速的进入可以操作OS的环境,所以,这个速度一定要快,越快越好,所以,systemd 的设计理念就是两条:

  • To start less.
  • And to start more in parallel.

也就是说,按需启动,能不启动就不启动,如果要启动,能并行启动就并行启动,包括你们之间有依赖,我也并行启动。按需启动还好理解,那么,有依赖关系的并行启动,它是怎么做到的?这里,systemd 借鉴了 MacOS 的 Launchd 的玩法(在Youtube上有一个分享——Launchd: One Program to Rule them All,在苹果的开源网站上也有相关的设计文档——About Daemons and Services

要解决这些依赖性,systemd 需要解决好三种底层依赖—— Socket, D-Bus ,文件系统。

  • Socket依赖。如果服务C依赖于服务S的socket,那么就要先启动S,然后再启动C,因为如果C启动时找不到S的Socket,那么C就会失败。systemd 可以帮你在S还没有启动好的时候,建立一个socket,用来接收所有的C的请求和数据,并缓存之,一旦S全部启动完成,把systemd替换好的这个缓存的数据和Socket描述符替换过去。

 

  • D-Bus依赖D-Bus 全称 Desktop Bus,是一个用来在进程间通信的服务。除了用于用户态进程和内核态进程通信,也用于用户态的进程之前。现在,很多的现在的服务进程都用 D-Bus 而不是Socket来通信。比如:NetworkManager 就是通过 D-Bus 和其它服务进程通讯的,也就是说,如果一个进程需要知道网络的状态,那么就必需要通过 D-Bus 通信。D-Bus 支持 “Bus Activation”的特性。也就是说,A要通过 D-Bus 服务和B通讯,但是B没有启动,那么 D-Bus 可以把B起来,在B启动的过程中,D-Bus 帮你缓存数据。systemd 可以帮你利用好这个特性来并行启动 A 和 B。

 

  • 文件系统依赖。系统启动过程中,文件系统相关的活动是最耗时的,比如挂载文件系统,对文件系统进行磁盘检查(fsck),磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时,系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作。autofs 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作,这是通过内核 automounter 模块的支持而实现的。比如一个 open() 系统调用作用在某个文件系统上的时候,而这个文件系统尚未执行挂载,此时 open() 调用被内核挂起等待,等到挂载完成后,控制权返回给 open() 系统调用,并正常打开文件。这个过程和 autofs 是相似的。

 

下图来自 Lennart 的演讲里的一页PPT,展示了不同 init 系统的启动。

除此之外,systemd 还在启动时管理好了一些下面的事。

用C语言取代传统的脚本式的启动。前面说过,sysvint/etc/rcX.d 下的各种脚本启动。然而这些脚本中需要使用 awk, sed, grep, find, xargs 等等这些操作系统的命令,这些命令需要生成进程,生成进程的开销很大,关键是生成完这些进程后,这个进程就干了点屁大的事就退了。换句话说就是,我操作系统干了那么多事为你拉个进程起来,结果你就把个字串转成小写就退了,把我操作系统当什么了?

在正常的一个 sysvinit 的脚本里,可能会有成百上千个这样的命令。所以,慢死。因此,systemd 全面用 C 语言全部取代了。一般来说,sysvinit 下,操作系统启动完成后,用 echo $$ 可以看到,pid 被分配到了上千的样子,而 systemd 的系统只是上百。

另外,systemd 是真正一个可以管住服务进程的——可以跟踪上服务进程所fork/exec出来的所有进程。

  • 我们知道, 传统 Unix/Linux 的 Daemon 服务进程的最佳实践基本上是这个样子的 (具体过程可参看这篇文章“SysV Daemon”)——
    1. 进程启动时,关闭所有的打开的文件描述符(除了标准描述符0,1,2),然后重置所有的信号处理。
    2. 调用 fork() 创建子进程,在子进程中 setsid(),然后父进程退出(为了后台执行)
    3. 在子进程中,再调用一次 fork(),创建孙子进程,确定没有交互终端。然后子进程退出。
    4. 在孙子进程中,把标准输入标准输出标准错误都连到 /dev/null 上,还要创建 pid 文件,日志文件,处理相关信号 ……
    5. 最后才是真正开始提供服务。

 

  • 在上面的这个过程中,服务进程除了两次 fork 外还会 fork 出很多很多的子进程(比如说一些Web服务进程,会根据用户的请求链接来 fork 子进程),这个进程树是相当难以管理的,因为,一旦父进程退出来了,子进程就会被挂到 PID 1下,所以,基本上来说,你无法通过服务进程自已给定的一个pid文件来找到所有的相关进程(这个对开发者的要求太高了),所以,在传统的方式下用脚本启停服务是相当相当的 Buggy 的,因为无法做对所有的服务生出来的子子孙孙做到监控。

 

  • 为了解决这个问题,upstart 通过变态的 strace 来跟踪进程中的 fork()exec()exit() 等相关的系统调用。这种方法相当笨拙。 systemd 使用了一个非常有意思的玩法来 tracking 服务进程生出来的所有进程,那就是用 cgroup (我在 Docker 的基础技术“cgroup篇”中讲过这个东西)。cgroup主要是用来管理进程组资源配额的事,所以,无论服务如何启动新的子进程,所有的这些相关进程都会同属于一个 cgroup,所以,systemd 只需要简单的去遍历一下相应的 cgroup 的那个虚文件系统目录下的文件,就可以正确的找到所有的相关进程,并将他们一一停止。

 

另外,systemd 简化了整个 daemon 开发的过程:

  • 不需要两次 fork(),只需要实现服务本身的主逻辑就可以了。
  • 不需要 setsid()systemd 会帮你干
  • 不需要维护 pid文件systemd 会帮处理。
  • 不需要管理日志文件或是使用syslog,或是处理HUP的日志reload信号。把日志打到 stderr 上,systemd 帮你管理。
  • 处理 SIGTERM 信号,这个信号就是正确退出当前服务,不要做其他的事。
  • ……

除此之外,systemd 还能——

  • 自动检测启动的服务间有没有环形依赖。
  • 内建 autofs 自动挂载管理功能。
  • 日志服务。systemd 改造了传统的 syslog 的问题,采用二进制格式保存日志,日志索引更快。
  • 快照和恢复。对当前的系统运行的服务集合做快照,并可以恢复。
  • ……

还有好多好多,他接管很多很多东西,于是就让很多人不爽了,因为他在干了很多本不属于 PID 1 的事。

Systemd 争论和八卦

于是 systemd 这个东西成了可能是有史以来口水战最多的一个开源软件了。systemd 饱受各种争议,最大的争议就是他破坏了 Unix 的设计哲学(相关的哲学可以读一下《Unix编程艺术》),干了一个大而全而且相当复杂的东西。当然,Lennart 并不同意这样的说法,他后来又写一篇blog “The Biggest Myths”来解释 systemd 并不是这样的,大家可以前往一读。

这个争议大到什么样子呢?2014 年,Debian Linux 因为想准备使用 systemd 来作为标准的 init 守护进程来替换 sysvinit 。而围绕这个事的争论达到了空前的热度,争论中充满着仇恨,systemd 的支持者和反对者都在互相辱骂,导致当时 Debian 阵营开始分裂。还有人给 Lennart 发了死亡威胁的邮件,用比特币雇凶买杀手,扬言要取他的性命,在Youbute上传了侮辱他的歌曲,在IRC和各种社交渠道上给他发下流和侮辱性的消息。这已经不是争议了,而是一种不折不扣的仇恨!

于是,Lennart 在 Google Plus 上发了贴子,批评整个 Linux 开源社区和 Linus 本人。他大意说,

这个社区太病态了,全是 ass holes,你们不停用各种手段在各种地方用不同的语言和方式来侮辱和漫骂我。我还是一个年轻人,我从来没有经历过这样的场面,但是今天我已经对这种场面很熟悉了。我有时候说话可能不准确,但是我不会像他样那样说出那样的话,我也没有被这些事影响,因为我脸皮够厚,所以,为什么我可以在如何大的反对声面前让 systemd 成功,但是,你们 Linux 社区太可怕了。你们里面的有精神病的人太多了。另外,对于Linus Torvalds,你是这个社区的 Role Model,但可惜你是一个 Bad Role Model,你在社区里的刻薄和侮辱性的言行,基本从一定程度上鼓励了其它人跟你一样,当然,并不只是你一个人的问题,而是在你周围聚集了一群和你一样的这样干的人。送你一句话—— A fish rots from the head down !一条鱼是从头往下腐烂的……

这篇契文很长,喜欢八卦的同学可以前往一读。感受一下 Lennart 当时的心态(我觉得能算上是非常平稳了)。

Linus也在被一媒体问起 systemd 这个事来(参看“Torvalds says he has no strong opinions on systemd”),Linus在采访里说,

我对 systemd 和 Lennart 的贴子没有什么强烈的想法。虽然,传统的 Unix 设计哲学—— “Do one thing and Do it well”,很不错,而且我们大多数人也实践了这么多年,但是这并不代表所有的真实世界。在历史上,也不只有systemd 这么干过。但是,我个人还是 old-fashioned 的人,至少我喜欢文本式的日志,而不是二进制的日志。但是 systemd 没有必要一定要有这样的品味。哦,我说细节了……

今天,systemd 占据了几乎所有的主流的 Linux 发行版的默认配置,包括:Arch Linux、CentOS、CoreOS、Debian、Fedora、Megeia、OpenSUSE、RHEL、SUSE企业版和 Ubuntu。而且,对于 CentOS, CoreOS, Fedora, RHEL, SUSE这些发行版来说,不能没有 systemd。(Ubuntu 还有一个不错的wiki – Systemd for Upstart Users 阐述了如何在两者间切换)

 

其它

还记得在《缓存更新的套路》一文中,我说过,如果你要做好架构,首先你得把计算机体系结构以及很多老古董的基础技术吃透了。因为里面会有很多可以借鉴和相通的东西。那么,你是否从这篇文章里看到了一些有分布式架构相似的东西?

比如:从 sysvinitupstart 再到 systemd,像不像是服务治理?Linux系统下的这些服务进程,是不是很像分布式架构中的微服务?还有那个D-Bus,是不是很像SOA里的ESB?而 init 系统是不是很像一个控制系统?甚至像一个服务编排(Service Orchestration)系统?

分布式系统中的服务之间也有很多依赖,所以,在启动一个架构的时候,如果我们可以做到像 systemd 那样并行启动的话,那么是不是就像是一个微服务的玩法了?

嗯,你会发现,技术上的很多东西是相通的,也是互相有对方的影子,所以,其实技术并不多。关键是我们学在了表面还是看到了本质。

 

延伸阅读

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
]]>
https://coolshell.cn/articles/17998.html/feed 45