程序之路上的一些感悟

好久没有写东西了,转眼工作近7年了,趁着有激情,写一写这些年的工作感悟吧。

中心思想,还是围绕着如何走好程序之路,如何积累,如何提高,总结总结,少走弯路,站在前人的肩膀上,越走越远。

一项工作做的越久,就越能感受到工作的平凡。这话是什么意思呢,就是说,刚开始工作靠的是激情、速度,但越到后面,靠的越是方向、耐力。还在坚持的、剩下的人越来越少了。很多人上了年纪就转行了,剩下的人中,一部分也会因为成家了,有小孩了,很多心思和重心会放在孩子身上,追求工作和生活的平衡。所以也是“剩”者为王。就算是工作进取心较强的人,也会由于没有同龄的对手,只剩下自己和自己较劲了,所以,从主客观因素来看,工作是寂寞的,是平凡。

工作的越久,会发现技能、技巧的成分越少。刚毕业时,总能感受到某项新技术的震撼,特别牛。但久了会发现,真正牛逼的是深度,而不是某项技术。技术日新月异,XML之后又有JSON,Java之后又有grovvy, go, nodejs, swift,VMware之后又有docker。HTML5, Java8, Java9,等等。总之,新技术从来没停止过出现。如果只着眼于学习一种牛逼的技术,那么很可能会失望,等技术过气之后发现很无奈。相反,技术深度却永远不会过气,比如高并发架构,高可用,灾备,性能调优,等等,是每个技术都离开的话题。不同技术的差别,只不过不同场景的差别,简单来说就是适合干的事情不同,有牺牲性能提高可扩展性的,有牺牲性能提高开发速度的,但也有只注重性能的场景,根据业务需要,选择不同的技术,有条件可以做搭配,发挥各种技术的长处,扬长避短。

什么都会,其实就是什么也不会,精力被分摊了,或者说,如果你真有一样拿手的,你很难做到,也没必要做到什么都会。在差一个数量级的人的眼里才可能觉得有些人什么都会,其实不是的,人总要从自己身边的事情,工作需要,缘分,先接触到技术做起,而且先要有一门看家本领,所以掌握程度总会有较大差异。

其实就算是开发,也会有分化。有项目做的多的,客户需求,行业知识掌握的多的专家。有专攻通用技术框架,对时间复杂度、算法敏感的技术大牛。个人感觉,程序写多了,其实就是思维习惯的改变,越来越懂如何用程序将现实表达起来,也就是常说的业务建模。不管是行业专家,还是技术大牛,其实也有很多共性,就是持续学习。搞开发就像搞建筑,偏实践,动手能力。所以所谓的牛逼的技术,也许核心就是科研人员的一篇论文,懂实践的人用程序实现了。将精力花在自己擅长的环节,是一种聪明。

设计比编程重要,方向比设计重要,一切目的都是积累。如果谁能用技术将人类的智慧管理起来,每个人都干不同的事,最后大家又能无冲突的汇集起来,那人类就无敌了。其实编程没有难事,一切觉的难都是因为没有想明白,等想明白了也就不急着去做了,反而觉得浪费时间,想明白就是设计。然而技术之路能走多远,兴趣、身体健康状况、寿命长短、心态、正义等等都起决定作用,资源调配的合理才能走的远,有时有必要可以牺牲速度,这就是方向。最后看毕生成就的高度,或者技术的伟大程度就是看积累的总量,所以,哪怕一点点进步,但是坚实的,方向是向前的,都应该为自己庆贺!

洋洋洒洒,又写了这么多。也是最近1、2年来的所见、所思、所想。既然想走的远,总要迈出第一步。希望我的文字,能帮助到别人,值了。

我是如何坚持地走着技术道路的

PS:经过一段时间的蛰伏,动荡,迎来了短期的心态的平静。趁热打铁,离梦想更进一步。

随想:写代码如同写诗,他是思想的结晶,寄托着你想表达的情感。

我想说,喜爱很容易,坚持不容易。你可能因为兴趣,喜欢上某一件事,你想去做它。但世上少有不需要坚持就能做好的事情,总会有瓶颈,让你感到付出和回报不成比例。或者你尽力了,但效果不理想,很多时候,这种时候的这种想法,会成为我们放弃坚持的理由。

辩证的去思考,容易做的事情,大家都容易,喜悦感来的快,但也走得快。难的东西,大家都难,喜悦感来的慢,来的艰难,来的痛苦,但到来的时候,喜悦更强烈,更喜悦。想想高考,当年是如何挑灯夜战的,是如何坚持每天凌晨2点睡觉的,考完试是如何不悔的,拿到大学通知书是如何喜悦。你可能不够聪明,但更可能不够努力。压抑的越久,快乐到来时来的越强烈。

技术道路或多或少有相似的地方,走的人很多,坚持的人很少。我是如何坚持的?答案很简单,爱好。答案很虚伪,有人说了,好羡慕有人有这种爱好。这种爱好,不是天然的,是一种思辨,一种选择。眼观放长远,收入预期放低些,保持爱好的精神饱满体力放松,就像散步一样,想要走的远,千万不能跑,要健康的走。切记,不要急于求成。保持住这种状态,便会有源源不断的兴趣,源源不断的动力,一直坚持下去。

其实在选择之前,会问为什么,为什么走技术道路。技术是我进入社会的切入点,技术工作充满神秘,有一种敬畏。三观很正,具有常说的一些优点,比如工匠精神,钻研精神,和气,温柔。这样的人,建设出来的祖国,世界,也会充满温柔,关怀,充满爱。

我是本科毕业已经做了6年的技术,对事情初具自己的想法,看法。也具有一定独当一面的实力。其实,技术的职业道路可以说见仁见智,衡量的尺度很多种,不必追求唯一。始终有所收获即可,保持学习,保持成长,是最重要的。被面试过,也面试过别人,被鄙视过,也被抬举过,拒绝过别人。这些都没什么,最重要的是你自己对自己的看法。

其实一路走过来,也并不平坦。第一份工作在国内软件公司100强,写过C++,写过Java,也写过Flex,很杂,可以说公司需要你用什么,就学什么。好在日子过的扎实,项目经验极其丰富,短短4年做的项目、产品不下10个。调侃自己是:没待过互联网,但有些项目加班却加出了互联网的感觉,呵呵。1星期保持通宵状态,为了项目上线,我都挑了一根大梁,成功了。那次经历对我鼓励很大,启发也很大。当时很累,后来成为了宝贵的财富,值了!第二份工作也是现在,在一家刚进世界500强的外企。一切高大上了起来。因为前一段工作积累很扎实,所以第二份工作上手很快,适应很快。挑战都和技术无关了,反而是一些语言、工作方式上面的。研究的技术也前沿了许多,获取新知识的手段和渠道也宽了很多狠毒。夸张点说简直是做梦都会笑,呵呵。工作职能的安排上,也专注了很多,专业了很多,基本上只需要研究专属领域的技术了,不会再有像之前那样大的跨度了。目前一直专注在Java、云方面的技术了。

回头看来,只有坚持着,才会不断收割之前老天欠你的财富,只有眼光放长远,你才能走的更远。像我的经历的坎坷体现在,毕业错过了某大公司,刚毕业做的东西太杂哪一样都时间不够去学好,好不容易精通了Flex,Flex却死了。曾几何时也一度羡慕过混着互联网的同学,感觉身上个个带着光环。保持学习,不放弃,你会了,这事就成了。后来,又精通回去了Java。其实回顾以往工作中,有80%以上知识或技能是自学的,知识还好,其实最重要的是,养成了快速学习的习惯。后来发现,学的快其实才是我的核心竞争力。

当有了一定基础之后,回过头来,再看世界,再看问题,眼光和着重点又不同了。我不再用某一种语言,或者技术去思考,比如Java,之前有一个阶段,我会认为掌握某一种框架是能力,框架能做的事情,我再去做。但今日又有不同,更多是对问题的一种抽象,定义。也是所谓的建模。宏观上,实体定义好,关系定义好,微观上,流程定义好,策略定义好,最后统一考量安全性,高可用性,维护下,扩展性等方面。因为Java是一种最方便实现的语言,它有完善的作用域类型,足以抽象表达出各种业务场景。配合框架实现狠多基础架构的工作,你只需要定义好架构,接口,只需要实现业务逻辑就好。基础框架越完备,业务逻辑就可以越纯粹。

技术一直在发展,推陈出新,比如,时至今日,又热起来的微服务,使用Docker+Spring Boot去实现Java语言的系统重构,相应基础架构管理的Zookeeper+Kafka等技术。所以,仍然要跟上脚步,保持学习。

如果说重复是程序员的魔咒,那么只有不断学习,不断进步,才能始终保持做不同的事,以传帮带的精神,也不断有新人接过你用已掌握的旧的知识做的重复的事情,对于新人也是新的知识,也是新的挑战,这样就是一个健康的生态环境。

最后,再回归主题,说我是如何坚持地走着技术道路的。我想,就是用一种愉快的开放的心态吧,想着要人别人快乐,这样别人最后也会让你快乐。对未知的世界保持好奇,新鲜。保持学习,保持进步。以这样的一种方式去“爱好”。就像写这篇blog的出发点一样,把一种正能量的心态,成果,经验知识分享给更多人。

小谈关于WADL,ALPS和OData

标题中列举了3个,都实现了RESTFul接口描述的技术,觉得很有趣,想放在一起比较一下。

WADL,是我遇到的第一个,能把REST接口,描述清楚的语法。它是在学习Jersey时,遇到的一个技术,已经是在成为JavaEE标准的roadmap中了。可以参见:https://wadl.java.net。它是基于XML格式,类似WSDL的概念,模仿过来的。

OData,是遇到的第二个,是在工作中遇到的,一片新的天地,绝对强大的新天地。它是基于JSON格式的描述语言,叫做metadata。它不仅规定了DataType,而且还规定了operation类型,更加严格,也更加规范。当然,也缺乏灵活性。

ALPS,是今天才看到的,Spring Data Rest的内容,http://docs.spring.io/spring-data/rest/docs/current/reference/html/#metadata,也有规范作为依靠,https://tools.ietf.org/html/rfc6906。刚刚接触,不太了解,大概浏览了一下,也是基于JSON格式的描述语法。

WADL更像是WSDL的REST版本,是一种补充。OData更像是一种规范的ATOM的Web Resource,有点像Web层的DAO。ALPS更像一种API,类似与REST的Java Doc。

总的来说,语义的自我表述,是一种趋势。引申一步讲,规范,描述比技术,功能或实现,更加重要。我们更希望一个一目了然,一下子知道干什么的工具。其实,也解决了一个程序员的大问题,就是功能和描述不一致。总之,这总,利用技术手段,增强的一致性,才是大势所趋吧。呵呵。

今天学了点SAP EP系统Web Dynpro工程的迁移

        这两天在本地出差,一个客户系统升级代码迁移过程出了点问题,请了位我司的专家来指导,我跟着来学习。避免遗忘,仅此记录一下。

        迁移是有指导手册的,说的也比较详细了,但现实情况往往更复杂,一般人照着弄,很容易出错。怎么说呢,虽然感觉我不会出那些个错,但毕竟我不是当局者(迷),而是旁观者(清)。归类错误,可分为以下几类:

  1. 没有一个好的例子做参照,不能做对比试验。对于一个陌生的且庞杂的系统,通常应该先基于一个运行良好的基础版本进行改动,而且改动幅度不能太大,一点点的改,遇到错误了,一点点回退,试也能试出来,改了哪导致不行的。该客户的情况是。。。代码管理出了问题,升级前的代码就不work,更何况升级后呢。

  2. 别一下子猜2步。发现客户的开发特别逗,喜欢猜,并且基于自己猜测的结果为前提,又开始第二步猜测,然而第二步猜测往往诡异而奇怪,比如,我看到错误提示日志里面我的模块名的前缀和我定义的不一样,我看到工程属性这个地方有显示,和日志里面的一样,感觉是改这里,但是!这里为什么编辑不了呢?好奇怪。。。我也觉得好奇怪,怎么能往这个方向猜呢。。。最后问题解决了,真正原因是有些更基础的基础包没有迁移。。。

  3. 没有理清原理就开始干,干一点,算一点。怎么说呢,就算我没用过SAP的产品,我用过Java,至少知道jar包之间是有依赖关系的,最底层的包要先部署,要先搞对。否则先搞上层的没有意义。Java里面有maven或gradle,专门解决这个问题。

        下面说说技术相关的经验:

  1. 越是奇怪的问题,导因往往越简单,简单到甚至是可能某个文件没有访问权限。。。

  2. 有顺序,或者依赖关系的事情,千万要按照步骤来,否则出了错都没意义,比如迁移过程,有些东西就要先做,有些后做,别存在侥幸心理,认为只是换了个顺序。

  3. 站在程序的角度想想,就好比让你做这个迁移工具,每一步需要知道什么信息,你是否提供了?否则不可能无中生有,巧妇难为无米之炊。

        综上,其实迁移这个过程可以这么理解,由于JDK版本升级了,以及部分产品接口模块重构了,所以导致旧的代码直接用不了,所以需要迁移,并且有些改动,已经不是工具能自动化完成了。然而Web Dynpro不仅仅是源码,而且还有生成的代码,所以要这么干:

        先把生成的代码清理掉,然后导入到新的工作空间,IDE会自动识别出旧版本的工程,并且提供了迁移按钮,有初始化向导,自动完成迁移,主要是工程配置文件的变更,先搬进新的工程,然后在新的工程下,代码会有错误,首先就是该JDK版本,其次按照指导手册里面的对应关系表,把新旧类改成新类名,等没错了,然后再编译生成新的代码,最后再部署到新系统。完成。

        我喜欢程序的思维,因为程序的思维缜密,有逻辑,而且不会偷懒,不会骗人,让人觉得放心,踏实。

程序员排错的第六感|老鸟谈经验

    作为一个4年工作经验的程序员,记录与分享下自己的排错经验。

    场景1:

    经常,我们会被派去研究一个新的问题,也许是刚入职不久,师傅让你自己先研究,也许是工作了一段时间,由于工作需要,要研究新的东西。我们常常觉得心里很没有底,感觉好像到处是吭,随便一个小怪就会把自己搞残血,不一会儿,就放弃了。。。

    尤其是刚入职的人来说,貌似被分配了一个很简单的任务,貌似自己研究要花费很久的时间,貌似导师很轻松就能搞定。。。越想自己越弱小,好像一直要给导师暗示出,看,导师你应该给花时间耐心的教我,不要让我自己搞,我搞不定,不然将要花很久。

    大部分时候,导师不会一板一眼的教自己,一副很拽的样子,只告诉你原理,原理往往是那种高大上,也听不懂的,往往不明觉厉东西。然后心里略带委屈的继续默默研究。

    我想说,我经历过新人,也经历过导师,我当新人的时候,是那种拽拽的新人,当导师时是那种弱弱的导师。下面分享我的经验。

    我作为新人,如何做到拽?拽不代表不负责任,不尊重导师,不代表很嚣张的样子,不听话的样子,恰恰相反,对待导师,一定要珍惜和他交流的机会,不轻易交流,一旦交流必有准备,必有自己的见解,必须对自己有提高,不要把导师的问题浪费在小怪身上,以至于大怪到没技能了或CD中。

    首先要听导师分配的任务,要相信,在入门阶段,你不会发现JVM的Bug,不会发现C++编译器的Bug,不会发现操作系统的Bug,唯一的Bug绝对来自你自己的代码,现象越奇怪的问题原因往往越简单,不要认为分号、大小写、申请了释放不释放,等等代码规则是很随意的。如果一个应该很简单的问题Try了半天没试出来,你就要分析下前进的方向了,是不是用错东西了?是不是没有访问权限?(新人的访问权限最低,最容易被坑)

    巧用百度验证,如果你遇到的问题,百度上很少,别人很少遇到,90%就说明你的用法不对,工具用错地方了。等你写多代码了会发现,自己的浅显,越来越不会怀疑环境,怀疑像JDK这种东西。当然,话无绝对,1000分之1的概率你给JDK提了一个Bug,在刚出道的时候成为标准的贡献者,高手在民间!(本人在第三年的时候才去研究开源框架代码的Bug,也是出于项目中出现了性能问题,不得不)

    时间就是财富,导师给你时间去研究,好好利用这个时间培养学习能力,天塌了有导师顶着呢,怕啥!一般公司不会让啥都不会的人上客户那里的,作死行为。。。

    写程序,解决问题前,先不要急着下手,先把思路,解题步骤写下来,用笔和纸就行了,一般写程序就像做生物的对比实验,好多东西都是试出来的,然后才理解的。总结出来的。(得承认,我不是那种思维特敏捷的,给我个再简单的问题,我都要花费分析的过程,但历史证明,我是效率最高的)

    和导师,闻道有先后,术业有专攻,长于己的地方就是好老师,不必尽信师。无私的教自己,要感恩。

    场景2

    比如电脑坏了,或者太慢了,很影响你工作效率,你想好好写代码,piu piu的写,电脑半分钟卡2个字出来,光标一直在转圈圈。。。你决定修电脑!拆开电脑看看,是不是灰多了?线坏了?这是发现自己没有螺丝刀!你准备去买螺丝刀,发现住的地方太高大上了附件没有卖的!只得在京东上买,真要买的时候发现京东账号和密码忘记了!你不记得记在那个邮箱了,要用电脑在邮箱里面搜。。。祸不单行,就这么纠结。比如再遇见买回来螺丝刀发现不够用,还有5角型的,还要再买。。。等等,最后发现灰清了,线插紧了还是慢。。。不得不送修。。。再比如小店还修不好,还要去苹果天才吧修。。。还要预约?怎么样,有点感觉了吧,初衷只是想好好写代码而已,却不得已绕这么大一圈。

    给程序排错的时候也是这样的,在领域经验较少,周围没有人可问的情况下,如何解决问题呢?

    解决这种问题要首先需要的是耐心,其次有这些建议:

    1. 条件允许,2个人一起找错,一个人改,一个人看,我c*o,效率超高,尤其在有个爱喷你的队友陪伴的情况下,更是如此。2个人不是用来扯淡的,因为这种问题最重要的是把我主线,不能扯的太远,要随时可以收敛,1个人研究,既想分支研究的专注,又想随时收敛,简直了,我个人认为是矛盾的,起码这种状态我坚持不了很久。想起了一些务虚的话,往往一些务虚的领导爱说,类似既要xx又要xx,N手抓,N手都要硬(N>3,声明:N=2的时候很合理)。什么好的都要做到,要求员工什么都要会,什么都要精,简直能量不守恒。

    2. 大多数情况,条件不用允许。。。那就多费点时间,做好记录工作,慢慢细心的试,同样,做对比实验,逐一排除问题。要有足够的耐心,搭建单元测试环境。往往搭建环境的时间,要远大于试一次的时间。这么做的目的是为了更快的重复问题。以及真正的理解问题。从根上解决。至于实验怎么设计,这个就不用说了,排除法么。如果B这句话有了,没问题,没有就有问题,说明B是少不了的。

    3. 结果导向解决问题。这点最难做到,因为度不好衡量。我见过不少这样的程序员,想法是好的,但是效率很慢。为何,他习惯弄懂了80%再下手,我觉得研究无人涉及的领域还好,说的过去,如果是有别人会的时候,就没必要了,Java核心卷1、2那么厚呢,深入了解MFC那么厚呢,不可能等你把书看完一遍再来写Java,再来写C++,纯理论的学习人会忘记。找到正确可执行的代码,先认为他是对的,先解决主要矛盾,如算法问题,流程逻辑问题,把边边角角的先忽略,比如工具的使用,配置项的配置,能编、能跑、能打印日志就Ok。界面什么的都不美观都先放放,把接口先定好。

    回归主题,扯得有点远,说说第六感。第六感就是遇到那种很基础,而且很少人遇到的问题时,你会发现8成是自己方向错了,或者某个工具用错了,至少这应该是件很简单的事。尽管前路都没走过,全是新的,但是你仿佛知道该如何选择,或者如何判断自己走错了,耐心与时间,你将走出荆棘,前路一片辉煌。

【转】理解HTTP幂等性

原文地址:http://www.cnblogs.com/weidagang2046/archive/2011/06/04/2063696.html

这篇文章原文写的非常好,推荐在原文中浏览

原文如下:

Todd.log – a place to keep my thoughts on programming 理解HTTP幂等性

基于HTTP协议的Web API是时下最为流行的一种分布式服务提供方式。无论是在大型互联网应用还是企业级架构中,我们都见到了越来越多的SOA或RESTful的Web API。为什么Web API如此流行呢?我认为很大程度上应归功于简单有效的HTTP协议。HTTP协议是一种分布式的面向资源的网络应用层协议,无论是服务器端提供Web服务,还是客户端消费Web服务都非常简单。再加上浏览器、Javascript、AJAX、JSON以及HTML5等技术和工具的发展,互联网应用架构设计表现出了从传统的PHP、JSP、ASP.NET等服务器端动态网页向Web API + RIA(富互联网应用)过渡的趋势。Web API专注于提供业务服务,RIA专注于用户界面和交互设计,从此两个领域的分工更加明晰。在这种趋势下,Web API设计将成为服务器端程序员的必修课。然而,正如简单的Java语言并不意味着高质量的Java程序,简单的HTTP协议也不意味着高质量的Web API。要想设计出高质量的Web API,还需要深入理解分布式系统及HTTP协议的特性。

幂等性定义

本文所要探讨的正是HTTP协议涉及到的一种重要性质:幂等性(Idempotence)。在HTTP/1.1规范中幂等性的定义是:

Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. 从定义上看,HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。幂等性属于语义范畴,正如编译器只能帮助检查语法错误一样,HTTP规范也没有办法通过消息格式等语法手段来定义它,这可能是它不太受到重视的原因之一。但实际上,幂等性是分布式系统设计中十分重要的概念,而HTTP的分布式本质也决定了它在HTTP中具有重要地位。

分布式事务 vs 幂等设计

为什么需要幂等性呢?我们先从一个例子说起,假设有一个从账户取钱的远程API(可以是HTTP的,也可以不是),我们暂时用类函数的方式记为:

bool withdraw(account_id, amount) withdraw的语义是从account_id对应的账户中扣除amount数额的钱;如果扣除成功则返回true,账户余额减少amount;如果扣除失败则返回false,账户余额不变。值得注意的是:和本地环境相比,我们不能轻易假设分布式环境的可靠性。一种典型的情况是withdraw请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被掉丢了,导致客户端无法得知处理结果。如果是在网页上,一些不恰当的设计可能会使用户认为上一次操作失败了,然后刷新页面,这就导致了withdraw被调用两次,账户也被多扣了一次钱。如图1所示:

non-idempotent

图1

这个问题的解决方案一是采用分布式事务,通过引入支持分布式事务的中间件来保证withdraw功能的事务性。分布式事务的优点是对于调用者很简单,复杂性都交给了中间件来管理。缺点则是一方面架构太重量级,容易被绑在特定的中间件上,不利于异构系统的集成;另一方面分布式事务虽然能保证事务的ACID性质,而但却无法提供性能和可用性的保证。

另一种更轻量级的解决方案是幂等设计。我们可以通过一些技巧把withdraw变成幂等的,比如:

int create_ticket() bool idempotent_withdraw(ticket_id, account_id, amount) create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。

基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:1.调用create_ticket()获取ticket_id;2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响可以忽略,加上idempotent_withdraw是幂等的,所以任何一步由于网络等原因失败或超时,客户端都可以重试,直到获得结果。如图2所示:

idempotent

图2

和分布式事务相比,幂等设计的优势在于它的轻量级,容易适应异构环境,以及性能和可用性方面。在某些性能要求比较高的应用,幂等设计往往是唯一的选择。

HTTP的幂等性

HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是RESTful的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定;另一种是SOA的,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议。本文所讨论的HTTP幂等性主要针对RESTful风格的,不过正如上一节所看到的那样,幂等性并不属于特定的协议,它是分布式系统的一种特性;所以,不论是SOA还是RESTful的Web API设计都应该考虑幂等性。下面将介绍HTTP GET、DELETE、PUT、POST四种主要方法的语义和幂等性。

HTTP GET方法用于获取资源,不应有副作用,所以是幂等的。比如:GET http://www.bank.com/account/123456,不会改变资源的状态,不论调用一次还是N次都没有副作用。请注意,这里强调的是一次和N次具有相同的副作用,而不是每次GET的结果相同。GET http://www.news.com/latest-news这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。

HTTP DELETE方法用于删除资源,有副作用,但它应该满足幂等性。比如:DELETE http://www.forum.com/article/4231,调用一次和N次对系统产生的副作用是相同的,即删掉id为4231的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。

比较容易混淆的是HTTP POST和PUT。POST和PUT的区别容易被简单地误认为“POST表示创建资源,PUT表示更新资源”;而实际上,二者均可用于创建资源,更为本质的差别是在幂等性方面。在HTTP规范中对POST和PUT是这样定义的:

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line …… If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. POST所对应的URI并非创建的资源本身,而是资源的接收者。比如:POST http://www.forum.com/articles的语义是在http://www.forum.com/articles下创建一篇帖子,HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。而PUT所对应的URI是要创建或更新的资源本身。比如:PUT http://www.forum/articles/4231的语义是创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性。

在介绍了几种操作的语义和幂等性之后,我们来看看如何通过Web API的形式实现前面所提到的取款功能。很简单,用POST /tickets来实现create_ticket;用PUT /accounts/account_id/ticket_id&amount=xxx来实现idempotent_withdraw。值得注意的是严格来讲amount参数不应该作为URI的一部分,真正的URI应该是/accounts/account_id/ticket_id,而amount应该放在请求的body中。这种模式可以应用于很多场合,比如:论坛网站中防止意外的重复发帖。

总结

上面简单介绍了幂等性的概念,用幂等设计取代分布式事务的方法,以及HTTP主要方法的语义和幂等性特征。其实,如果要追根溯源,幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同,有兴趣的读者可以从Wikipedia上进一步了解。

参考

RFC 2616, Hypertext Transfer Protocol — HTTP/1.1, Method Definitions The Importance of Idempotence Stackoverflow – PUT vs POST in REST