如何将新技术快速应用到现有系统中

最近对于编程的思考尤其多,是不是到了一个新level。。。可以打高级怪了:) 最近看了一本书,叫做Google工作整理术。书中主要以作者的个人经验为核心,介绍了一套适合现代社会的信息整理方法。书中有一个例子很有意思。有一项科学试验,试验目标是一帮程序员,试验要求让各位程序员做自己不擅长的开发工作。这项研究发现,大多数程序员会根据已有经验来完成这项本不熟悉的任务。程序员们这时思想都很乱,他们会以试错的方式,将自己已有的经验套用在这项新任务中,以此达到完成的目的。然而现实是,多数时候,以往经验对于新技术的学习可能很有限。 如果对于一项新技术你能很好的把握,快速上手,那你之前一定填过很多坑。开发就是这样的,不断积累和探索,关键还是方法。 我在回顾以前,我是怎么使用新东西的时候,我发现我当时的所作所为,也是按照以往的经验进行不断的试错,这种方式十分低效,原因在于违背了大脑的运行机制。说白了就是,这东西你不会,你怎么试都是错。所以我就会在网上去搜索相关条目,按照别人的做法一步一步的做,大部分情况时不能一次到位的,其实也是一个不断试错的过程。但这种方法稍微正确一点,在于至少别人这么用过。总之,这种方式类似于复制代码,你的目的仅仅是让它跑起来。但是一问起内部运行机制就蒙圈了。 所以我现如今的方式是,学习新技术的架构,了解它的出发点,怎么设计实现,为什么这么实现,如果让我实现,我会怎么实现?而了解出发点最好地方,我觉得不是直接读源码,而是通篇阅读官方文档。 为什么不直接读源码? 国内的博客作者很多都会写一些xxx源码分析,而国外程序员很少写这种东西。更多的是写那种"xxx under the hood",或者是"How does xxx work internally?"。给我的感觉是,从宏观上把握一项技术比单独的看一段代码更有意义。程序员的学习准则应该是0或1,也就是会与不会,没有大概、差不多、可能。。。所以使用一项技术就应该尽量的学习这项技术,只是知道怎么配置,怎么让它跑起来是不够的,还要知道这东西为啥这么设计,它通过什么方式实现了这样的操作。如果每次学习新技术都把人家的代码看一遍的话,我觉得像spring这种东西简直就是无穷无尽的。所以相比较阅读源码,我认为阅读官方文档更重要。在知道这东西做了什么事之后,再去看代码会清晰很多,因为你是知道这样做的目的是什么的。比如像spring下面的各个子项目,文档不要太多好吗。很多人认为通读文档太浪费时间,等到读完文档了,交付日期早都过了。但是文档中介绍内部工作机制的,绝对不能错过,这是学习的绝佳机会,也许你可能一辈子都不会自己写一套权限验证系统,但是学习spring security的内部工作机制一样会对你的工作产生积极的影响。久而久之,当你看过足够多的技术实现后,你就会触类旁通。当有一天,真要你实现一个复杂的系统的时候,那些你看过的技术实现一个个都会浮现在眼前。

September 19, 2016 · 1 min · magicalne

编程思考

想写这篇文章好多天了,一直没得空,今天趁机补上。 事出有因,上周的时候,公司里一个前端项目在自动部署之后就挂了。很神奇,这个项目使用webpack-dev-server作为代理服务器,而我那天只是提交了一个代理路径的修改,从那以后这个url就不能正常被代理转发了,就算改回去也不行。但是我本地环境确是好的…首先我思考可能是devops那边做了配置切换,因为之前是出现过这种事的。但经过多次确认之后发现不是devops的问题…最后我把目光放在了package.json上。最终发现了问题。由于devops在使用docker部署的时候,会每次拉一份最新代码,然后copy过去,重新npm install。而package.json中,我是这么写的 “webpack-dev-server”: “^1.11.0” 。这里的**^**表示,选取小版本中最新版本。恰巧两周前webpack-dev-server发布了新版本,而其中涉及代理转发的部分没有向前兼容,也就是一个巨大的bug… 在知道问题之后,我开始尝试寻找解决办法,github上有人针对这个bug展开了很多探讨,但是并没有合适的workaround。我发现webpack-dev-server中的proxy是实现,依赖于另外一个proxy实现,而且这个proxy在实现过程中又引入了另外一个开源项目…我发现要实现这个代理的功能,我要克服重重障碍,仅仅因为源代码中在开发新版本时,没有考虑向前兼容。值得肯定的是,在原来的版本中,实现代理的功能十分轻松有效。 最近看了很多wangyin的文章,基本都刷过一遍。我现在对于编程的理解仿佛又深了一层。我以前一直认为编程是个手艺活,仿佛旧时代的匠人。甭管孬好,有身手艺在手,至少饿不着。我爷爷就是个手艺人、我爸也算半个。所以,一直以来我都把编程当作一项手艺活,认为自己是个匠人,但从来没想过成为艺术家。看了wangyin的文章,我开始相信编程其实是一项艺术。当你真正了解计算机是如何运行、网络如何传输、程序如何编译,你写的代码不再是简简单单的代码,你看到的不是某种语言的语法,而是真相。 软件是要被使用的 拿webpack-dev-server v1.15.0的这次bug说吧,尽管我可以在此基础上自己实现一个版本,但是我需要花费更多的精力,并且要独自维护。我并不是说这个bug有多么严重,我只是从这件事中得到了启发。有些程序员把代码写的极其复杂,难以理解,用到了很多有的没的新奇技术。他完全没有考虑调用者的感受。而且没有维护代码的意识,这是极其可怕的。交付可用软件是很重要的,以前我也干过那种不通知调用方直接改数据结构的下流事,实在惭愧… 对于新技术的评判和使用有待商榷。我也喜欢用新技术,但是不希望过度工程,把这项新技术强加到这个架构体系上去而不管是否合适,那就是耍流氓。我觉得如果对于新技术的使用存在一个平衡点,那就是简历驱动开发vs系统架构需要。需求永远走在前面。 代码是给人看的 以前我相信好代码需要注释,烂代码更需要注释。现在我不想信了。代码不像自然语言,代码有自己的表达特点,好的代码是有比自然语言更强的表达能力。所以要写能让人看得懂的代码,而不是依赖注释解释这些代码做了些什么。注释的作用应该放在,这段代码当初为什么要这么写。为将来接手的同事提供一个良好的上下文,而不是挖一个大大的坑。 代码不是一蹴而就的 很多程序员都知道这个道理,但是真正意识到这是一个问题的人却很少。我身边的程序员就是这样,他们写完代码测试通过了就大事告吉了,仿佛这段代码已经是完美了,直到下次出现bug之前事不会再看这段代码了。一蹴而就的代码往往经不起推敲,不用说完美,就连能不能正常工作都不敢保证。好的代码一定是自我批判、改造、再批判、再改造的过程。有些程序员会说能程序工作就已经很不容易了。但是我想说,如果仅仅是让程序能够工作,公司招个高职、专科毕业的一样能让它工作,为什么花那么多钱招你来呢? 测试驱动不了开发 我以前认为开发完毕时,你只完成了一半的工作量。剩下的一半时间你需要测试程序的准确性。这里有一个自欺欺人的地方,你只能找到能够被检测出来的bug,而不能找到出没被检测出来的bug。我以前一直认为单元测试很重要,诚然,它有它存在的道理。但是对于一个新项目,或者说一个新需求,整个业务需求在不断变化,你不知道这个功能做到最后是什么样子的,但是我每次开发完了必须要自欺欺人的写单元测试。然后过了两天不到,这个接口需要或多或少的修改,你就要跟着把单元测试也跟着改掉。从此以后,你会发现你不仅需要维护这个接口,还要维护这个什么都做不了的单元测试。我开始相信单元测试这种东西是用在那些业务不会频繁变动的代码上。而对于频繁变动的代码,单元测试只会增加你的维护成本。 之前看到很多人鼓吹测试驱动开发,他们强调的观点是当你开发一个功能时,先写好单元测试,然后再写代码。或者至少想好怎么进行单元测试,然后再着手写代码,因为不能被单元测试的代码是有问题的,所以需要时刻关注是否能够写出可以被单元测试的代码。但我现在我很好奇他们所在的公司是个啥公司。。。我也想去这样写了代码就不会再更改的公司。 知其然,不如知其所以然 我在日常编程的工作生活中,尽量学习内部实现原理,尽管大多数时候我会忘记。。。但是有一些我却一辈子都不会忘。编程是一件十分有趣的工作,它之所以有趣,我认为是计算机给我带来的种种未知。我到现在不相信,在这世界少有人能精通计算机的方方面面,能把各种底层的实现细节说的一清二楚。如果真有这号人,我觉得他现在一定很无聊:) 学习ArrayList和LinkedList的区别可能是过于基础的,ConcurrentHashMap为何能够支持并发又不损耗性能可能也不难,jvm为什么要把内存分堆和栈,函数式编程到底有啥好,什么是cps,什么是coroutine,java中是否可以实现cps和coroutine,在命令行中启动一个spring boot项目的时候都发生了什么…等等这一切,不就是编程最有意思的地方吗。

September 6, 2016 · 1 min · magicalne

How to write a Java cache

最近在看Java concurrency in practice 真是一本好书哇。原本以为并发方面的书都会写的云里雾里的,没想到这本书是如此的大道至简。在书的第5章结束的地方,作者引入了一个问题:如何开发支持高并发的cache?我觉得很有意思,一直以来,cache在开发过程中时如此的不可缺少,一切的性能提升几乎都跟cache有关。 那么如何开发一个属于自己的高xx缓存系统呢? 书中例子使用了ConcurrentHashMap来保存cache,以此实现了高并发。另外所保存的value使用FutureTask类型。这导致cache不能移除长久没有使用的对象,或者更新cache过的值。 我在 jache 这个项目中会一步一步的解决这写问题,最后写出一个真正的cache:)

August 23, 2016 · 1 min · magicalne

How to remove elements in a collection

前不久在微信公众号中看到一篇技术文章,内容是讨论“java中如何删除一个集合的多个元素”。 文章并没有把这个问题挖深,它的结论是,如果直接使用ArrayList,然后用forEach两层循环遍历,会报ConcurrentModificationException,尽管测试代码时单线程。原因是java的forEach使用iterator实现的循环遍历,而iterator在遍历的过程中是不允许删除元素的。也不能用传统for循环,因为使用for循环删除元素的过程中,数组下标有变动。 以上是文章的结论,现在呢,到底“删除集合的的多个元素”哪家强? 结论: 最简单的就是removeAll() 可以使用CopyOnWriteArrayList 我的测试代码: public class RemoveElementsInList { public void remove1(List<Integer> all, List<Integer> sub) { for (int i = 0; i < all.size(); i ++) { for (int j = 0; j < sub.size(); j ++) { if (all.get(i).equals(sub.get(j))) { all.remove(i); } } } System.out.println(all); } public static void main(String[] args) { List<Integer> sub = new ArrayList<>(Arrays.asList(1,2,3)); List<Integer> all = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7)); all.removeAll(sub); List<Integer> sub1 = new CopyOnWriteArrayList<>(sub); List<Integer> all1 = new CopyOnWriteArrayList<>(all); RemoveElementsInList removeElementsInList = new RemoveElementsInList(); removeElementsInList....

August 7, 2016 · 2 min · magicalne

Some dark side in Java -- Synchronize on Boolean

我们知道,java中的同步关键字是synchronized。它有两种用法: 修饰方法 修饰代码块 修饰代码块需要给定一个在其上进行同步的对象,最理想的情况是对当前的对象同步: synchronized(this) {...} 但往往我们并不是想要直接的给当前对象加锁,而是创造一个单一的、跟对象无关的临界区。 然后就有了这样的代码: private Boolean aBoolean = ... synchronized(aboolean) {...} 这个问题其实不是很明显, 原因是自动装箱和JVM对于常量的管理导致在一个jvm下,同时有且仅有两个Boolean实例:true/false 尽管我可以 new Boolean(true) 但是,我们知道,拜自动装箱所赐,这都是行不通的。 这篇文章的作者有一个例子 private Boolean isOn = false; private String statusMessage = "I'm off"; public void doSomeStuffAndToggleTheThing(){ // Do some stuff synchronized(isOn){ if(!isOn){ isOn = true; statusMessage = "I'm on"; // Do everything else to turn the thing on } else { isOn = false; statusMessage = "I'm off"; // Do everything else to turn the thing off } } } 作者给出了解释...

July 30, 2016 · 1 min · magicalne

Develop with reactjs -- part 0

入坑 组里躺着一个裸体的规则引擎,已经一年多了,木有UI,程序员用起来都费劲的那种…年前用nodejs写了一个CMD工具去CRUD规则,然并卵。一个多月前感觉已经有必要开发一套UI了,否则项目无法推动,新增修改规则全靠我人力搬运,实在手累,然后就有了这篇文章… 技术选型 这个前端项目很明显,就是那种“一个人的项目”。一开始就想好了,既然一个人的项目,那一定要写的爽!技术必须用最新的。然而毕竟是前端项目,虽然对外号称本人是准全栈工程师,然而前端这种博大精深的东西怎么是我这中渣渣掌握的了得…特别是我这种懒人,不想写HTML和CSS:( 选型的时候,前端框架基本没跑——reactjs无疑,好奇好久了,终于可以一探究竟了!可是webpack好难学,不会。不会webpack,就不会自己搭项目框架,不会搭框架,就没法写代码…不过好在github上已经有很多boilerplate了。这里我使用了mxstbr/react-boilerplate 这里不得不说一下,作者是来自于“offline-first foundation”,听名字也懂得~但是由于我的项目不需要offline,所以这个东西对于我来说是累赘,而且还引起了一个bug,这个bug是在我用react-router,实现不跳转刷新页面时出现的,除非使用强制刷新,否则子页面是无法刷新出来的,希望是我使用不当吧,反正我是把offline删掉了。 框架中使用了redux来替代flux,所以redux也没跑了。虽然当时还不懂啥是redux… 使用webpack作为打包工具。 不喜欢写前端的最大原因就是不想学习CSS的实现机制…也不喜欢写CSS。但是对material ui很是喜欢…自己肯定没能力写出那么漂亮的组件。So,随便在网上搜了搜,我的天,material ui实现的现成框架不要太多。想想我也就是半年没写前端,竟然出了那么多东西…于是我就挑选了一套实现material ui的react component——material-ui。可以先说结论,值得使用,99%的体验都很棒,但是框架自己也有很多bug,看你怎么取舍了,大都不那么致命,也都有hack的办法。比较恶心的是,文档过于先进,代码没实现的功能在文档中是实现了的…严重怀疑写文档的和写代码是不是一批人…当时我用的时候还是v0.14.X,没俩月就出v0.15了。更新还是很快的。 由于项目中需要写代码,所以需要一个编辑器,那就必须要使用大名鼎鼎的Ace!真的很强大!本身由于Ace不是react component,如果直接使用还要自己写wrapper,像我这么懒的人怎么可能会写wrapper呢…而且我也不会写啊…于是,我又在github上找到了react-ace,算是ace的功能子集吧,但是已经够我的项目用了。使用过程中由于我没有仔细阅读文档,进而写出了一个bug,以为是react-ace的bug,然后去社区提问,结果没多久就有热心人指出了我的使用问题。社区还是很活跃的,值得信赖。 So,大体上就是这样的,技术栈:reactjs,redux,webpack,material-ui,ace。其中还使用了es6,所以还涉及babel。后面再说~

June 1, 2016 · 1 min · magicalne

Checkstyle and findbugs

Developers try to write code as good as possible. But we need some tools to validate. Checkstyle and findbugs are two great tools to check our code. There are three ways to use Checkstyle in your development. Install plugin in your IDE maybe the easiest way to use. Although you can use the default config file to check the code, I highly recommend you to write a config file for you and your team....

January 21, 2016 · 2 min · magicalne

年终总结-2015

好久没有更新了,这个年终总结更是拖了好久。接下来就总结一下2015年的技术成长吧。 总的来说感觉成长是很有限的,感觉现在所在的公司技术层面并不会特别突出,业务推进也比较缓慢,不过好在有时间学习新知识,这也算是一种折中。2015年12月末来到dr实习,当时参与开发一套借款端的怀旧系统…美名其曰为开发,世为修bug和写bug。这个系统主要开发语言是java,实用了十分怀旧的技术,可能由于CEO是Orale出来的缘故,整个技术栈都是Oracle的破东西,服务器用的是OC4J,ormapping框架用的toplink,都是不为人知,甚至不被oracle维护的东西…不过整个组内氛围十分融洽,而且有幸由当是的leader带我。环顾整个公司,有leader带人的情况也就发生在我身上了。 每当忙完一段时间,我的leader会让我做一些探索性的工作。比如说看看系统的源码,当时发现老人在实现发送短信的功能上,有一个非常有意思的地方,我想可以理解为oracle抗压(可见系统有多老),他/她在实现异步发送短信的逻辑上,采用将待发的手机号码先存储到oracle db中,在从oracle读取,整个异步看似很蠢,但是所有压力都放在oracle上,不知道这是不是10年前oracle抗压的典型做法?还有一次,leader让我给系统配一个log,当时的log框架用的五花八门的,一点不统一。于是我就配置了一份log4j,并阅读了它的源码。 后来由于团队需要,开始转做前端,并开始使用node和angularjs。通过node钻进了异步的世界,进入了高效i/o的世界,最有意思的莫过于函数式编程。虽然从来没有在工作中写过curry或是trampoline,不过确实开了眼界。另外现在的前端开发,跟我之前了解到的一点也不一样,jquery虽然很强大,但仿佛是上世纪的产物,angularjs、reactjs以及emberjs真是很强大的产物。在dr的前端开发中我们使用angularjs作为mvc框架,gulp作为build工具,使用less简化css的组织结构。其实当时的想法是,前端开发和后端开发很像。而且前端开发的处理逻辑的地方更多。不过后来就发现,前端对于我来说是一个深坑。我一直在尝试走全栈开发的路线,然而一个全栈前端是需要懂设计的…是在下输了…:( 有相当长的一段时间,我曾认为javascript是一个比java强好多的语言,毕竟js可以run在好多地方,而且单说i/o效率是比java强的(node)。不过后来回头重新写java时,还是认为java的积淀更深一些。 学习node最有意思的地方就是async i/o, so fun!在学习的过程中尽量学习node内部如何实现callback,针对异步的场景,还根同事讨论过,将问题扩展到所有的异步场景,包括linux的epoll、windows的IOCP。所有的高效异步都是同步的轮询。 最近参与开发针对线上借款业务的风控系统,名为risk engine。里面包含一个子项目叫rules engine。也就是一个DSL。这个东西陆陆续续做了几个月了,以后应该找个时间单独拿出来说的。

January 10, 2016 · 1 min · magicalne

layout: post title: Java中的引用类型 date: 2017-02-08 tag: java reference 最近在写一个Java实现的cache,就是一个guava cache的轮子。 之前没有刻意研究guava cache,这几天重温guava源码的时候,发现guava cache中继承了SoftReference, WeakReference, PhantomReference,里面还有实现了的queue,这些reference会被enqueue,好奇心因此产生。 看到上述代码的时候才发现自己不知道如何在java中使用这些reference,于是研究了一番。大家经常在面试、笔试中碰到的reference的问题大都是问:“Java中有几种reference?分别是什么?”但是很少问它们究竟都是用来干什么的?如何使用? java.lang.ref上有详细的解释,但是不是很通俗。由于我是想自己写一个cache,而SoftReference非常适合开发cache程序,我就特别关注这个问题。总的来说,这些reference都是跟垃圾回收器相关,会针对不同的reference类型,产生不同的回收策略。 Soft references are for implementing memory-sensitive caches, weak references are for implementing canonicalizing mappings that do not prevent their keys (or values) from being reclaimed, and phantom references are for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism. 为什么说SoftReference适合做cache?因为当你为一个普通对象创建一个SoftReverence,即使当这个对象不再拥有任何其他对象的强连接,垃圾收集器也可能不会立即将SoftReference回收,而是等到剩余内存达到阀值了,不得不进行垃圾回收的时候才将SoftReference引用的对象回收。因此,在构建cache程序时,这里就可以使用SoftReference构成多级cache架构。第一层是你的cache数据结构,可能是一个自己实现的Map,第二层是SoftReference。 对比SoftReference、WeakReference和PhantomReference ReferenceQueue<Foo> fooQueue = new ReferenceQueue<>(); Foo foo = new Foo(); Reference<Foo> reference = new WeakReference<>(foo, fooQueue); foo = null; System....

2 min · magicalne

title: 一次性能优化之旅 layout: post date: 2017-02-21 tag: GC tuning java 近期对我所开发的规则引擎进行了性能调优,过程很好玩! 起因 起因是,之前有对接的同事询问我规则引擎的tps大约是多少。之前也没有压测过,当场也说不出来,回头我拿自己的mbp测了一下,tps=~44。后来我就跟对面的测试哥说了一下这件事,测试哥找了台机器压测了一顿,tps大约也是44的样子。由于生产环境没有出现瓶颈的征兆,我就本打算不管这事了。幸亏测试哥经验丰富。。。顺便又观察了一下GC,才发现了一个天大的秘密! 现象 我们的规则引擎在执行过程中会频繁的进行full GC。且full GC并不是有效的,就是说GC过后内存没有被回收,而且停顿时间明显,系统响应性越来越差,慢慢死掉。。。典型的雪崩。 第一次尝试 当时看代码发现了有两处实现的有问题,构成了两个朝生西死的大对象。干掉之后,压测结果好看了一些。但依然没有解决频繁full GC且GC无效的问题。 第二次尝试 由于我使用了guava cache,然后就思考会不会跟cache有关呢?重新看guava cache源码的时候,发现自己配制cache的地方是有问题的。然后更改了配置项,压测后结果又好了一点,但是仍然没解决问题。 第三次尝试 我开始思考,是不是这些大对象直接进入老年代,而且full GC之后依然存在的原因,是因为这些对象真的没有被使用完? 我们用的是tomcat,tomcat会对每一个connection建立一个线程,这些都是要消耗大量内存的。如果full GC的时候这些请求没有被处理完(因为处理慢),那这些线程也自然无法释放。但是jetty默认却是单线程处理connection的,如果connection可以每次不被分配一个线程,那么自然也就不需要消耗那么多内存了。而且,我当时认为,测试的时候只有一个tcp连接,这个connection应该被重用的。 结果是,jetty无效。后来跟测试哥沟通才发现,测试哥发请求的时候,真的是并发的。即每次使用不同的端口号发请求,这样就导致虽然是两台机器建立tcp连接,但是由于发出端的端口号不同,所建立的connection也是不同的,不能复用,因此jetty并不能解决这个并发场景下的连接问题。 第四次尝试 我开始思考,如果根本没有对象进入老年代呢?这可是规则引擎啊,用完就走的系统,我们当时测试使用的请求,确实没有啥朝生西死的大对象。那到底是什么在触发full GC呢? 首先我尝试调大heap大小,压测效果提升明显,但是仍然频繁full GC。 换G1!屌起了!tps稳定在200,且响应分布均匀,没有timeout,基本没有full GC,minor GC十分高效。配合设置停顿时间为200ms,整个系统焕然一新。 结论 到这里,原因都清楚了。规则引擎使用Java 8,我们默认GC算法使用CMS。CMS的minor GC最大的问题就是不能处理framentation,因为CMS没有compact的操作。在高并发的执行规则的时候,会创建很多小对象,minor GC过后会回收这部分对象,但是由于没有compact,heap上有很多碎片。导致CMS必须依赖full GC来触发compact操作。然而full GC的停顿时间长,导致系统响应性降低,当有后续请求进来时,系统会越来越慢,进而发生雪崩现象。 G1确实是这个场景下最合适的垃圾回收策略。G1可以更好的利用大内存,并有效降低停顿时间。而且在进行minor GC的时候就会触发compact操作,解决了碎片化的问题。从而保证了系统响应性,提高了吞吐量。 目前看来,本地单机压测的结果一致看好G1,还未发现其他问题,找个生产环境再压压看吧。 GC调优还是很好玩的,我本地使用两个工具用于观测GC,一个是VisualVM,另一个是jstat。貌似jconsole也不错。这次调优之旅不仅让我用实践的方式理解了GC,还让我更好的理解业务。处理输入输出的那部分代码看来需要重构,否则如果输入输出太大,直接进入老年代就不好玩了。

1 min · magicalne

title: Avro——从入门到放弃 layout: post date: 2017-06-18 tag: java avro 近期尝试寻找一个支持异步RPC比较好的整体解决方案。我希望能尽量简单,不需要一上来就考虑分布式。异步调用支持的足够完整。其实我最熟悉的还是tomcat,但是servlet在支持异步上面有先天劣势,尽管tomcat从开发到调优都已十分方便且成熟,但是在异步调用这里,肯定是不如netty的。不是tomcat实现的不好,而是受限于servlet标准。 所以就想找一个好的替代品。以前就知道有Avro这号东西的,对标的其他开源项目有protocol buffer和thrift。具体就不介绍了,简单说来就是,Avro更像是序列化数据结构这个领域的终结品。诞生于Hadoop,目的在于成为Hadoop中序列化数据结构的基础实现,替代protobuf和thrift。 于是呼,我就写了个demo project,但是到最后也没有完成…Avro也不是啥新项目,也存在有些日子了,实在想不清为什么文档会那么差。整个文档就没有涉及异步rpc的事情!我是手动翻阅maillist和jira tickets才找到的。然后写出来的demo中,居然不能异步调用。尽管在客户端确实是调用的异步方法,但是到server这边居然还是走的同步的实现-。- 我在网上也确实看到有一个人遇到了相同的问题,各种尝试均以失败告终。 文档真的太重要了。也许Avro存在的目的就是为了服务于Hadoop,反正RPC的功能我是放弃了,不过序列化数据结构我还是会用的。确实是个好东西。

1 min · magicalne

title: 如何为自己的DSL写parser layout: post date: 2017-06-18 tag: java DSL parser parboiled 什么是DSL? DSL——Domain specific language,领域语言。 DSL在规则引擎中有广泛的应用,但其实不单单是规则引擎,很多地方都有DSL的身影。比如最简单的JSON,SQL,properties配置文件,yml配置文件,thrift、protobuf、avro的IDL,dockerfile等等,这些都是DSL。DSL的存在可以大大降低系统的复杂性,当然前提是DSL并不复杂。DSL绝对不能等同于编程语言,但要求近似等同于人类语言。 设计DSL的语法结构 在之前开发的系统中,其中一个模块就是一个规则引擎,规则引擎有一个workflow的功能。workflow就是一套事先定义好的flows,执行过程中会因为中间结果的不同而走向不同的flow。在开发workflow的时候,我就设计了一个DSL。 DSL的内容很简单: namespace some.project init: ruleSet = [checkAge.groovy, checkCompany.groovy,...] mode = normal pass -> blacklist reject -> return step blacklist: ruleSet = [...] 首先,是一个namespace的定义;然后必须定义一个init block,init中定义了将要执行的ruleSet,执行ruleSet的时候会根据mode的值选择具体的执行方式,如顺序执行、并行执行、快速失败等等。mode的默认值是normmal,所以mode可以省略。对于ruleSet的执行结果返回三种可能:pass,reject和undefine。对于各个结果可以设定下一步的执行路径,路径可以是直接返回,或是一个step name。默认是直接返回,即return。step可以有0或多个。 这样,我们的DSL的语法结构就出来了。现在我们需要一个parser。 什么是Parser? Parser一段程序,可以从上到下扫一遍DSL文件,parse出DSL的结构信息。Parser的写法就那么几种,原理简单,并不复杂,复杂的地方是,如果我写的DSL出问题了,Parser是否可以及时发现,并在正确的地方抛出异常。 这里我选用了parboiled来写parser。真的非常好用。parboiled不像antlr那类的生成器,parboiled是一个框架,开发者需要自己用java或scala开发parser。parboiled基于PEG(Parsing expression grammars)。 简单解释PEG PEG还是很好理解的,可以理解为对字符串parse的过程进行拆分并组装。假设我要实现一个四则运算的计算器,就是给一个公式,公式中包含了因子和操作符,操作符包含了加减乘除和左右小括号,因子就是简单的数字。一个公式可能是这样的:1+2,也可能是这样的:1+2*3+4/2,还可能是这样的:(1+2)*(4-3)。那如何用PEG描述呢? Expr ← Sum Sum ← Product (('+' / '-') Product)* Product ← Value (('*' / '/') Value)* Value ← [0-9]+ / '(' Expr ')' 翻译成中文就是:我们要求表达式(Expr)的值,首先求Sum;Sum是一个Product,以及0或多个加/减Product(注意这里的0或多个修饰的是:加/减和product);而Product是一个Value,以及0个或多个乘除Value;Value或者是由多个0-9的简单数字组成的整数,或者是一个表达式Expr。...

1 min · magicalne

layout: post title: 谨慎在生产环境使用thrift server date: 2017-08-06 tags: java thrift 在生产环境引入thrift server一周之后,被逼无奈,使用grpc+protobif替换掉了thrift。 使用thrift IDL的场景很多,特别是大厂们,不同语言的异构系统,可以通过thrift无缝衔接。但是进程间如何通信呢?RPC框架的选择真的少啊。我不想直接上dubbo,太重了,这只是一个简单的gateway。由于一开始调研的时候发现了一个有趣的事情,导致我一上来就放弃了grpc。protobuf很早之前就开源了,但是grpc是在近几年才开源的,然而grpc在google内部其实是和protobuf同年在内部release的。我对于google的半截子工程产生了深深的怀疑…然后就相信了thrift,也没有仔细看thrift的论文。 thrift server的问题 其实也不能算问题吧。thrift server底层是c++实现的,设计之初就是应用于响应时间短的快请求。也就压根没有对慢请求有任何的优化,而且client与server的通信机制也十分简单,很多异常情况也没有考虑实现。 这就导致,你的请求不能慢,你必须转换成快请求。那thrift server是否实现了我们习以为常的异步接口呢?答案是:Yes, and no! thrift client和server都有nonblocking的实现,但是这和我们基于java,特别是netty的nonblocking不太一样。关于异步实现的解释基本上很难在网上找到了,很多文章都在错误的演示async client的使用方式。当然你也不能在官方文档中找到,只能通过论文的设计的原理一探究竟… 当你实现了一个async client rpc方法,当你尝试使用Future包装并发送请求时,你会惊讶的发现,你只能以阻塞的方式的调用,而不能以非阻塞的方式调用,也就是不能异步的调用。 你需要: Object result = future.get(); 当你尝试使用CompletableFuture包装,并apply到另一个线程处理的时候,你会惊讶的发现,请求根本发不出去的!你必须以阻塞的方式进行调用。 thrift server期待的nonblocking是使用oneway修饰的rpc方法。如果你的rpc不需要返回值,可以设置为oneway,并且使用async client发送oneway rpc请求。也就是说,只有onway修饰的方法才能使用async client,而oneway修饰的方法意味着没有返回值。thrift希望快请求再快一点。这也就是为什么async client发送异步请求时,结果跟我们期待的如此不同。 另外一个严重的问题是:thrift client没有pool。这是很严重的。网上有一些介绍,如何使用commons-pool实现的例子,但都太简单了,是不能在生产环境使用的。最严重的一个问题是,当server shutdown重启之后,pool中的连接应该已经断掉了,如果你没有实现检测远程连接是否断开的逻辑,pool中的连接是不能访问server的,你会得到一片片的异常。究其原因,是因为tcp头上的id对不上号导致的。当然还有没有其他问题,我就不得而知了。 这一波必须跪舔protobuf+grpc。特别是grpc,底层还是使用了netty,client会帮你维护心跳,有pool,简单且足够用。

1 min · magicalne