paulwong

#

Tengine

简介

Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站
的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如
淘宝网天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、
易用的Web平台。

从2011年12月开始,Tengine成为一个开源项目,Tengine团队在积极地开发和维护
着它。Tengine团队的核心成员来自于淘宝搜狗等互联网企业。Tengine是社区合作
的成果,我们欢迎大家参与其中,贡献自己的力量。

特性

动态

邮件列表

posted @ 2015-11-06 12:56 paulwong 阅读(451) | 评论 (0)编辑 收藏

SPRING IO

Spring起初只专注ioc和aop,现在已发展成一个庞大体系。比如security、mvc等。
如此一来,不同模块或者与外部进行集成时,依赖处理就需要各自对应版本号。
比如,较新spring与较老的quartz,它们集成就会遇到问题,给搭建和升级带来不便。

因此Spring IO Platform应运而生,只要项目中引入了它,外部集成时依赖关系无需版本号

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId> 
</dependency>

Spring IO Platform只是一个pom文件,记录了spring与其他开源项目对应的版本。
省去了版本号,也就省去了处理依赖时的问题,因为Spring IO Platform中有最优的版本。

posted @ 2015-10-30 14:05 paulwong 阅读(483) | 评论 (0)编辑 收藏

一篇不错的Docker入门介绍

看到这标题你可能会想,网上不是已经有很多Docker的入门介绍了么?同一个主题被讲过很多次,还有没有必要必要再讲?可是,我体内这混杂的的高傲与固执虽然让人厌恶,但却让我广受大家的欢迎,这也让我觉得我应该来一发(篇),哈哈。 

举一个我遇到的场景,ELK三剑客,即Elasticsearch、Logstash和Kibana。我可以选择把它们直接安装在我的MacBook上,这是我主力开发机,但是在上面已经装好一个Elasticsearch了。我不想破坏现在已有的环境。要解决这个问题,用2015年时下最热门的解决方案就是Docker了。如果过去一年关于Docker的各种热闹你都错过了,那么请继续往下看。 

Docker做的事情就是将的软件隔离起来,让它们即使出了问题也不会互相影响。这并不是什么横空出世的新思想。你很可能会说内核控制的进程不就这样玩么?每一个进程都有自己的内存空间,并且在一个进程自身看来,内存空间与所处在的计算机的内存空间是一样的。然而内核欺骗了进程,在背后将内存地址重新映射到了真实的内存空间中。想想今天高速运转处理器,任何地方见到的系统都能同时运行多个进程。今天的文明社会比人类历史任何一个时间点制造的谎言数量级都要很多的量级。 

扯远了,Docker将进程的隔离模型的进行了扩展,让隔离性变得更强。Docker是在Linux内核的基础上打造的一系列工具。整个文件系统被抽象了,网络被虚拟化了,其它进程被隐藏了,并且从理论上,不可能逃脱容器去对在一个机器上的其他进程搞破坏。实际中,每个人对于怎么才能逃脱容器,至少去收集一点运行容器的机器的相关信息,持开放的态度。跟虚拟机比较起来,容器的隔离性还是较弱。 

 

上面箭头:提升单机性能;下面箭头:提升隔离性

但换个角度看,进程比容器性能更好,容器性能比虚拟机性能更佳。原因很简单,隔离性更高,在每一个上下文中就需要运行更多的东西,从而拖慢速度。选择一个隔离性的过程,实际就是决定你对要运行进程的信任有多少的过程 - 它会不会去干扰其他的进程?如运行的进程都是自己的亲儿子,那你对他们会有一个很高的信任度,对他们用最少的隔离,运行在一个进程中就行了。如果是SAP,那么你很可能需要尽可能高的隔离性:将电脑装在封存在箱子里,绑在火箭上发射到月球。 

Docker另一个很好的特性是,容器可以作为一个整体交付。他们不会像虚拟机那么臃肿。这大大的提高了部署的简易度。在这个微服务流行的世界里,你可以很容易将你的服务捆在一起,用镜像来发布。你甚至可以将build的结果指定成一个Docker镜像。 

Docker将会怎样改变软件开发和部署的过程仍然有待观察。尽管我觉得Docker是一种带有破坏性的技术,但影响还在几年之后才会到来。虽然我会认为Docker会让很多系统管理员丢掉工作,但是实际上Docker却会改变他们的工作。每个人现在都需要一点变革,赶上时代的脚步。 

又扯远了,说说OSX上的Docker。 

细心的你可能注意到,我之前说Docker是运行在Linux内核之上的。然而OSX没有Linux内核,那怎么运行Docker呢。为了解决这个问题,我们需要用虚拟机来运行Docker。我们可以用一个叫boot2docker的工具来做这件事情,但是最近被docker-machine取代了。 

我的机上有一个比较老的Docker,但是我觉得想试试Docker Compose,因为我运行着很多的服务。Docker Compose能让很多的容器协作起来运行一个整体的环境。为了遵循保证隔离服务的宗旨,每一个服务都运行在单独的容器中。因而,一个典型的web应用中,可以把web服务器运行在一个容器里面,数据库运行在另外一个容器里面,然后这些容器可以放在同一个机器上。 

我从Docker官网上下载了安装包,并且跟着安装指南http://docs.docker.com/mac/step_one/安装。装好Docker后,我让docker-machine在Virtual Box上创建了新的虚拟机。 

 

一起看起来很顺利,然后启动随处可见的hello-world镜像。 

很惊讶这个镜像的并不完全,完全没有发现任何一个地方有“hello world”的输出。然而好在,不是每一个Docker镜像都实现地这般草率。这个hello world的例子比较无聊,看看能不能找到更加有意思的。我们想从容器中服务一个页面,我打算使用Nginx,已经有一个现成的Nginx的容器了,因此我创建了个新的Dockerfile。Dockerfile包含了一系列如何指导Docker从一系列镜像中创建出一个容器的指令。这里提到的容器包含以下内容: 
Java代码 
  1. FROM nginx  
  2. COPY *.html /usr/share/nginx/html/  

第一行设置了我们容器的基础镜像。第二行将本地的带有html后缀的文件拷贝到Nginx容器中WEB服务器的目录里。为了使用这个Dockerfile文件,我们需要创建一个Docker的镜像: 
Java代码 
  1. /tmp/nginx$ docker build -t nginx_test .  
  2. Sending build context to Docker daemon 3.072 kB  
  3. Step 0 : FROM nginx  
  4. latest: Pulling from library/nginx  
  5. 843e2bded498: Pull complete  
  6. 8c00acfb0175: Pull complete  
  7. 426ac73b867e: Pull complete  
  8. d6c6bbd63f57: Pull complete  
  9. 4ac684e3f295: Pull complete  
  10. 91391bd3c4d3: Pull complete  
  11. b4587525ed53: Pull complete  
  12. 0240288f5187: Pull complete  
  13. 28c109ec1572: Pull complete  
  14. 063d51552dac: Pull complete  
  15. d8a70839d961: Pull complete  
  16. ceab60537ad2: Pull complete  
  17. Digest: sha256:9d0768452fe8f43c23292d24ec0fbd0ce06c98f776a084623d62ee12c4b7d58c  
  18. Status: Downloaded newer image for nginx:latest  
  19. ---> ceab60537ad2  
  20. Step 1 : COPY *.html /usr/share/nginx/html/  
  21. ---> ce25a968717f  
  22. Removing intermediate container c45b9eb73bc7  
  23. Successfully built ce25a968717f   

Docker build命令开始将拉取已经构建好的Nginx容器。然后将我们的文件拷贝到容器里面,并且显示容器的hash值,这让它们很容易辨认。要运行这个容器我们可以运行: 
Java代码 
  1. /tmp/nginx$ docker run --name simple_html -d -p 3001:80 -p 3002:443 nginx_test  

这条命令让Docker运行nginxtest的容器,并且取名为simple_html。-d选项是为了让Docker在后台运行这条命令,并且最终-p选项是为了转发端口,这里需要将本地的3001端口映射到容器的80端口 - 即正常的web服务器端口。现在我们可以连接到web服务上了。如果我们打开chrome,访问localhost:3001就会看到: 

 

居然不行,问题在于Docker没有意识到自己运行在虚拟机的环境里面,因此我们需要将vm的端口映射到我们本地机器上: 
Java代码 
  1. Docker container:80 -> vm host:3001 -> OSX:3001  

这个从虚拟机管理器里面可以轻松的搞定: 

 

现在我们可以看到页面了: 

 

这就是我们放在容器中的文件。好极了!现在我准备好尝试更复杂一点的容器了。 

小贴士: 

我注意到在虚拟机里面同时并行的运行Docker会整个让系统hang住。我怀疑同时跑两个虚拟工具可能让某个地方卡住产生了冲突的结果。我相信docker-machine的并行的支持正在在积极的解决中,0.5版本可能会看到。直到这之前,你可以参考:http://kb.parallels.com/en/123356并且看看:https://github.com/Parallels/docker-machine中对docker-machine的fork版本。 

原文链接:Yet another intro to docker (翻译:钟最龙 校对:宋喻) 

译文来自:DockOne.io

posted @ 2015-10-30 12:49 paulwong 阅读(468) | 评论 (0)编辑 收藏

糖果CMS,一个像糖果一样的CMS 糖果CMS

一个JAVA的内容管理系统
http://www.oschina.net/p/tg-cms

posted @ 2015-10-27 18:24 paulwong 阅读(500) | 评论 (0)编辑 收藏

REDIS监控

http://git.oschina.net/hellovivi/RedisFlag

posted @ 2015-10-19 12:39 paulwong 阅读(489) | 评论 (1)编辑 收藏

深度技术揭秘,支付宝,财付通,到底每天都是怎样工作的?

为了可以更好地解释支付结算系统对账过程,我们先把业务从头到尾串起来描述一下场景,帮助大家理解:一个可能得不能再可能的场景,请大家深刻理解里面每个角色做了什么,获取了哪些信息:  某日阳光灿烂,支付宝用户小明在淘宝上看中了暖脚器一只,价格100元。犹豫再三后小明使用支付宝网银完成了支付,支付宝显示支付成功,淘宝卖家通知他已发货,最近几日注意查收。
  小明:持卡人,消费者,淘宝和支付宝的注册会员,完成了支付动作,自己的银行账户资金减少,交易成功。
  银行:收单银行,接受来自支付宝的名为“支付宝BBB”的100元订单,并引导持卡人小明支付成功,扣除小明银行卡账户余额后返回给支付宝成功通知,并告诉支付宝这笔交易在银行流水号为“银行CCC”
  支付宝:支付公司,接收到淘宝发来的订单号为“淘宝AAA”的商户订单号,并形成支付系统唯一流水号:“支付宝BBB”发往银行系统。然后获得银行回复的支付成功信息,顺带银行流水号“银行CCC”
  淘宝:我们支付公司称淘宝这类电商为商户,是支付系统的客户。淘宝向支付系统发送了一笔交易收单请求,金额为100,订单号为:“淘宝AAA”,支付系统最后反馈给淘宝这笔支付流水号为“支付BBB”
  以上流程貌似大家都达到了预期效果,但实际上仍然还有一个问题:
  对支付公司(支付宝)而言,虽然银行通知了它支付成功,但资金实际还要T+1后结算到它银行账户,所以目前只是一个信息流,资金流还没过来。
  Tips:插一句话,对支付系统内部账务来说,由于资金没有能够实时到账,所以此时小明的这笔100元交易资金并没有直接记入到系统资产类科目下的“银行存款”科目中,而是挂在“应收账款”或者“待清算科目”中。大白话就是,这100元虽然答应给我了,我也记下来了,但还没收到,我挂在那里。
  对商户(淘宝)而言,虽然支付公司通知了它支付成功,他也发货了,但资金按照合同也是T+1到账。如果不对账确认一下,恐怕也会不安。
  倒是对消费者(小明)而言:反正钱付了,你们也显示成功了,等暖脚器呀等暖脚器~
  基于支付公司和商户的困惑,我们的支付结算系统需要进行两件事情:一是资金渠道对账,通称对银行帐;二是商户对账,俗称对客户帐。对客户帐又分为对公客户和对私客户,通常对公客户会对对账文件格式、对账周期、系统对接方案有特殊需求,而对私客户也就是我们一般的消费者只需要可以后台查询交易记录和支付历史流水就可以了。
  我们先聊银行资金渠道对账,由于支付公司的资金真正落地在商业银行,所以资金渠道的对账显得尤为重要。
  在一个银行会计日结束后,银行系统会先进行自己内部扎帐,完成无误后进行数据的清分和资金的结算,将支付公司当日应入账的资金结算到支付公司账户中。于此同时,目前多数银行已经支持直接系统对接的方式发送对账文件。于是,在某日临晨4点,支付宝系统收到来自银行发来的前一会计日对账文件。根据数据格式解析正确后和前日支付宝的所有交易数据进行匹配,理想情况是一一匹配成功无误,然后将这些交易的对账状态勾对为“已对账”。
  Tips:此时,对账完成的交易,会将该笔资金从“应收账款”或者“待清算账款”科目中移动到“银行存款”科目中,以示该交易真正资金到账。
  以上太理想了,都那么理想就不要对账了。所以通常都会出一些差错,那么我总结了一下常见的差错情况:
  1.支付时提交到银行后没有反馈,但对账时该交易状态为支付成功
  这种情况很正常,因为我们在信息传输过程中,难免会出现掉包和信息不通畅。消费者在银行端完成了支付行为,银行的通知信息却被堵塞了,如此支付公司也不知道结果,商户也不知道结果。如果信息一直没法通知到支付公司这边,那么这条支付结果就只能在日终对账文件中体现了。这时支付公司系统需要对这笔交易进行补单操作,将交易置为成功并完成记账规则,有必要还要通知到商户。
  此时的小明:估计急的跳起来了……付了钱怎么不给说支付成功呢!坑爹!
  TIPS:通常银行系统会开放一个支付结果查询接口。支付公司会对提交到银行但没有回复结果的交易进行间隔查询,以确保支付结果信息的实时传达。所以以上情况出现的概率已经很少了。
  2.我方支付系统记录银行反馈支付成功,金额为100,银行对账金额不为100
  这种情况已经不太常见了,差错不管是长款和短款都不是我们想要的结果。通常双方系统通讯都是可作为纠纷凭证的,如果银行在支付结果返回时确认是100元,对账时金额不一致,可以要求银行进行协调处理。而这笔账在支付系统中通常也会做对应的挂账处理,直到纠纷解决。
  3.我方支付系统记录银行反馈支付成功,但对账文件中压根不存在
  这种情况也经常见到,因为跨交易会计日的系统时间不同,所以会出现我们认为交易是23点59分完成的,银行认为是第二天凌晨0点1分完成。对于这种情况我们通常会继续挂账,直到再下一日的对账文件送达后进行对账匹配。
  如果这笔交易一直没有找到,那么就和第二种情况一样,是一种短款,需要和银行追究。
  以上情况针对的是一家银行资金渠道所作的流程,实际情况中,支付公司会在不同银行开立不同银行账户,用以收单结算(成本会降低),所以真实情况极有可能是:
  临晨1点,工行对账文件丢过来(支行A)
  临晨1点01分,工行又丢一个文件过来(支行B)
  临晨1点15分,农行对账文件丢过来
  。 。 。
  临晨5点,兴业银行文件丢过来
  。。。
  不管什么时候,中国银行都必须通过我方业务员下载对账文件再上传的方式进行对账,所以系统接收到中行文件一般都是早上9点05分……
  对系统来说,每天都要处理大量并发的对账数据,如果在交易高峰时段进行,会引起客户交互的延迟和交易的失败,这是万万行不得的所以通常支付公司不会用那么傻的方式处理数据,而是在一个会计日结束后,通常也是临晨时段,将前一日交易增量备份到专用对账服务器中,在物理隔绝环境下进行统一的对账行为,杜绝硬件资源的抢占。
  以上是银行资金渠道入款的对账,出款基本原理一致,不过出款渠道在实际业务过程中还有一些特别之处,由于大家不是要建设系统,我就不赘述了。
  谈完了资金渠道的对账,我们再来看看对客户帐。
  前面提到了,由于资金落在银行,所以对支付公司来说,对银行帐非常重要。那么同理,由于资金落在支付公司,所以对商户来说,对支付公司账也一样重要。能否提供高品质甚至定制化的服务,是目前支付公司走差异化路线的一个主要竞争点。
  之前说过,银行与支付公司之间的通讯都是可以作为纠纷凭证的。原理是对支付报文的关键信息进行密钥加签+md5处理,以确保往来报文“不可篡改,不可抵赖”。
  同理,支付公司和商户之间也会有类似机制保证报文的可追溯性,由此我们可以知道,一旦我方支付系统通知商户支付结果,那么我们就要为此承担责任。由此我们再推断一个结论:
  即便某支付订单银行方面出错导致资金未能到账,一旦我们支付系统通知商户该笔交易成功,那么根据协议该结算的资金还是要结算给这个商户。自然,我们回去追究银行的问题,把账款追回。
  没经过排版的小知识点---------------------------------------------------
  一、对支付系统而言,最基本的对账功能是供商户在其后台查询下载某一时间段内的支付数据文件,以供商户自己进行对账。
  这个功能应该是个支付公司就有,如果没有就别混了。
  二、对大多数支付系统而言,目前已经可以做到对账文件的主动投送功能。
  这个功能方便了商户系统和支付系统的对接,商户的结算人员无须登录支付平台后台下载文件进行对账,省去了人工操作的麻烦和风险。
  对大型支付系统而言,商户如果跨时间区域很大,反复查询该区域内的数据并下载,会对服务器造成比较大的压力。各位看官别笑,觉得查个数据怎么就有压力了。实际上为了这个查询,我最早就职的一家支付公司重新优化了所有SQL语句,并且因为查询压力过大服务器瘫痪过好几次。
  现在比较主流的做法是把商户短期内查询过、或者经常要查询的数据做缓存。实在不行就干脆实时备份,两分钟同步一次数据到专用数据库供商户查询,以避免硬件资源占用。甚至……大多数支付公司都会对查询范围跨度和历史事件进行限制,比如最多只能查一个月跨度内,不超过24个月前的数据……以避免服务嗝屁。
  对账这块大致就这样了,再往细的说就说不完了,
  风险控制,在各行各业都尤其重要。
  金融即风险,控制好风险,才有利润。
  虽然第三方支付严格意义上说并非属于金融行业,但由于涉及资金的清分和结算,业务主体又是资金的收付,所以风险控制一样非常重要。
  对支付公司来说,风控主要分为合规政策风控以及交易风控两种。
  前者主要针对特定业务开展,特定产品形态进行法规层面的风险规避,通常由公司法务和风控部门一起合作进行。例如,一家公司要开展第三方支付业务,现在要获得由人民银行颁发的“支付业务许可证”。遵守中国对于金融管制的所有条规,帮助人行监控洗钱行为……这些法规合规风险,虽然条条框框,甚至显得文绉绉,但如果没人解读没人公关,业务都会无法开展。
  当然,这块也不是本题所关注的重点,提问者关注的,应当是业务进行过程中的交易风控环节。
  现在随着各支付公司风险控制意识的加强,风控系统渐渐被重视起来。除了上述提到的合规风控相关功能,风控系统最讲技术含量,最讲业务水平,最考究数据分析的业务就是交易风控环节。
  对一笔支付交易而言,在它发生之前、发生过程中及发生过程后,都会被风控系统严密监控,以确保支付及客户资产安全。而所有的所有秘密,都归结到一个词头上:规则。风控系统是一系列规则的集合,任何再智能的风控方案,都绕不开规则二字。
  我们先看看,哪些情况是交易风控需要监控处理的:
  1.钓鱼网站
  什么是钓鱼呢?
  用我的说法,就是利用技术手段蒙蔽消费者,当消费者想付款给A的时候,替换掉A的支付页面,将钱付给B,以达成非法占用资金的目的。
  还有更低级的,直接就是发小广告,里面带一个类似http://tiaobao.com的网址,打开后和淘宝页面一摸一样,上当客户直接付款给假冒网站主。
  第一种情况风控系统是可以通过规则进行简单判定的,虽然有误杀,但不会多。
  通常使用的规则是判断提交订单的IP地址和银行实际支付完成的IP地址是否一致,如果不一致,则判断为钓鱼网站风险交易,进入待确认订单。
  但第二种情况,亲爹亲娘了,支付公司真的没办法。当然遇到客户投诉后可能会事后补救,但交易是无法阻止了。
  2.盗卡组织利用盗卡进行交易
  大家都知道,信用卡信息是不能随便公布给别人的,国内大多信用卡虽然都设置了密码,但银行仍然会开放无磁无密支付接口给到商户进行快速支付之用。
  所谓无磁无密,就是不需要磁道信息,不需要密码就可以进行支付的通道。只需要获取到客户的CVV,有效期,卡号三个核心信息,而这三个信息是在卡上直接有的,所以大家不要随便把卡交给别人哦~
  碰到类似的这种交易,风控系统就不会像钓鱼网站这样简单判断了。
  过去我们所有的历史交易,都会存库,不仅会存支付相关信息,更会利用网页上的控件(对,恶心的activex或者目前用的比较多的flash控件)抓取支付者的硬件信息,存储在数据库中。
  当一笔交易信息带着能够搜集到的硬件信息一同提交给风控系统时,风控系统会进行多种规则判定。
  例如:当天该卡是否交易超过3次
  当天该IP是否交易超过3次
  该主机CPU的序列号是否在黑名单之列
  等等等等,一批规则跑完后,风控系统会给该交易进行加权评分,标示其风险系数,然后根据评分给出处理结果。
  通过硬件信息采集以及历史交易记录回溯,我们可以建立很多交易风控规则来进行监控。所以规则样式的好坏,规则系数的调整,就是非常重要的用以区别风控系统档次高低的标准。
  例如,我听说著名的风控厂商RED,有一个“神经网络”机制,灰常牛逼。
   
  其中有一个规则我到现在还记忆犹新:
  某人早上八点在加利福尼亚进行了信用卡支付,到了下午一点,他在东亚某小国家发起了信用卡支付申请。系统判断两者距离过长,不是短短5小时内能够到达的,故判定交易无效,支付请求拒绝。
  规则非常重要,当然,数据也一样重要。我们不仅需要从自己的历史记录中整合数据,同时还要联合卡组织、银行、风控机构,购买他们的数据和风控服务用来增加自己的风控实力。
  SO,风控是一个不断积累数据、分析数据、运营数据、积累数据的循环过程。
  好的风控规则和参数,需要经过无数次的规则修改和调整,是一个漫长的过程。
  不知道大家做互联网,有没有利用GA做过AB测试,同样的,风控系统也需要反复地做类似AB测试的实验,以保证理论和实际的匹配。
  最后给大家说一个小小的概念:
  所谓风控,是指风险控制,不是风险杜绝。
  风控的目标一定不是把所有风险全部杜绝。
  合理的风控,目标一定是:利润最大化,而不是风险最小化
  过于严格的风控规则,反而会伤害公司利益(看看销售和风控经常打架就知道了)
  不光是交易的风控,我们日常制定规则,法规,公司流程,也一定要秉着这个出发点进行规划。

posted @ 2015-09-09 23:09 paulwong 阅读(877) | 评论 (0)编辑 收藏

Reactor模式详解

前记

第一次听到Reactor模式是三年前的某个晚上,一个室友突然跑过来问我什么是Reactor模式?我上网查了一下,很多人都是给出NIO中的 Selector的例子,而且就是NIO里Selector多路复用模型,只是给它起了一个比较fancy的名字而已,虽然它引入了EventLoop概 念,这对我来说是新的概念,但是代码实现却是一样的,因而我并没有很在意这个模式。然而最近开始读Netty源码,而Reactor模式是很多介绍Netty的文章中被大肆宣传的模式,因而我再次问自己,什么是Reactor模式?本文就是对这个问题关于我的一些理解和尝试着来解答。

什么是Reactor模式

要回答这个问题,首先当然是求助Google或Wikipedia,其中Wikipedia上说:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。从这个描述中,我们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。如果用图来表达:

从结构上,这有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并没有Queue来做缓冲,每当一个Event输入到Service Handler之后,该Service Handler会主动的根据不同的Event类型将其分发给对应的Request Handler来处理。

更学术的,这篇文章(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events)上说:“The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。这段描述和Wikipedia上的描述类似,有多个输入源,有多个不同的EventHandler(RequestHandler)来处理不同的请求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注册到Initiation Dispatcher中,然后Initiation Dispatcher根据输入的Event分发给注册的EventHandler;然而Initiation Dispatcher并不监听Event的到来,这个工作交给Synchronous Event Demultiplexer来处理。

Reactor模式结构

在解决了什么是Reactor模式后,我们来看看Reactor模式是由什么模块构成。图是一种比较简洁形象的表现方式,因而先上一张图来表达各个模块的名称和他们之间的关系:

Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel可以是CONNECT事件,对SocketChannel可以是READ、WRITE、CLOSE事件等。
Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,可以调用Selector的selectedKeys()方法获取Set<SelectionKey>,一个SelectionKey表达一个有事件发生的Channel以及该Channel上的事件类型。上图的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是对的,那内部实现应该是select()方法在事件到来后会先设置Handle的状态,然后返回。不了解内部实现机制,因而保留原图。
Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。
Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。
Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。

Reactor模式模块之间的交互

简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:

1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而建立Handle到EventHandler的映射(Map)。
3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
5. 在EventHandler的handle_events()方法中还可以向InitiationDispatcher中注册新的Eventhandler,比如对AcceptorEventHandler来,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中。

Reactor模式实现

Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式,这个Logging Server的实现完全遵循这里对Reactor描述,因而放在这里以做参考。Logging Server中的Reactor模式实现分两个部分:Client连接到Logging Server和Client向Logging Server写Log。因而对它的描述分成这两个步骤。
Client连接到Logging Server

1. Logging Server注册LoggingAcceptor到InitiationDispatcher。
2. Logging Server调用InitiationDispatcher的handle_events()方法启动。
3. InitiationDispatcher内部调用select()方法(Synchronous Event Demultiplexer),阻塞等待Client连接。
4. Client连接到Logging Server。
5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的连接到来。 
6. LoggingAcceptor调用accept方法accept这个新连接。
7. LoggingAcceptor创建新的LoggingHandler。
8. 新的LoggingHandler注册到InitiationDispatcher中(同时也注册到Synchonous Event Demultiplexer中),等待Client发起写log请求。
Client向Logging Server写Log

1. Client发送log到Logging server。
2. InitiationDispatcher监测到相应的Handle中有事件发生,返回阻塞等待,根据返回的Handle找到LoggingHandler,并回调LoggingHandler中的handle_event()方法。
3. LoggingHandler中的handle_event()方法中读取Handle中的log信息。
4. 将接收到的log写入到日志文件、数据库等设备中。
3.4步骤循环直到当前日志处理完成。
5. 返回到InitiationDispatcher等待下一次日志写请求。

Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有对Reactor模式的C++的实现版本,多年不用C++,因而略过。 

Java NIO对Reactor的实现

在Java的NIO中,对Reactor模式有无缝的支持,即使用Selector类封装了操作系统提供的Synchronous Event Demultiplexer功能。这个Doug Lea已经在Scalable IO In Java中有非常深入的解释了,因而不再赘述,另外这篇文章对Doug Lea的Scalable IO In Java有一些简单解释,至少它的代码格式比Doug Lea的PPT要整洁一些。

需要指出的是,不同这里使用InitiationDispatcher来管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandler,因而不需要注册EventHandler这个步骤,或者设置Attachment就是这里的注册。而且在这篇文章中,Doug Lea从单线程的Reactor、Acceptor、Handler实现这个模式出发;演化为将Handler中的处理逻辑多线程化,实现类似Proactor模式,此时所有的IO操作还是单线程的,因而再演化出一个Main Reactor来处理CONNECT事件(Acceptor),而多个Sub Reactor来处理READ、WRITE等事件(Handler),这些Sub Reactor可以分别再自己的线程中执行,从而IO操作也多线程化。这个最后一个模型正是Netty中使用的模型。并且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相应的描述。

EventHandler接口定义

对EventHandler的定义有两种设计思路:single-method设计和multi-method设计:
A single-method interface:它将Event封装成一个Event Object,EventHandler只定义一个handle_event(Event event)方法。这种设计的好处是有利于扩展,可以后来方便的添加新的Event类型,然而在子类的实现中,需要判断不同的Event类型而再次扩展成 不同的处理方法,从这个角度上来说,它又不利于扩展。另外在Netty3的使用过程中,由于它不停的创建ChannelEvent类,因而会引起GC的不稳定。
A multi-method interface:这种设计是将不同的Event类型在 EventHandler中定义相应的方法。这种设计就是Netty4中使用的策略,其中一个目的是避免ChannelEvent创建引起的GC不稳定, 另外一个好处是它可以避免在EventHandler实现时判断不同的Event类型而有不同的实现,然而这种设计会给扩展新的Event类型时带来非常 大的麻烦,因为它需要该接口。

关于Netty4对Netty3的改进可以参考这里
ChannelHandler with no event objectIn 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.

4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() andhandleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

为什么使用Reactor模式

归功与Netty和Java NIO对Reactor的宣传,本文慕名而学习的Reactor模式,因而已经默认Reactor具有非常优秀的性能,然而慕名归慕名,到这里,我还是要不得不问自己Reactor模式的好处在哪里?即为什么要使用这个Reactor模式?在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是这么说的:
Reactor Pattern优点

Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method functionality. The application-independent mechanisms become reusable components that know how to demultiplex events and dispatch the appropriate hook methods defined byEvent Handlers. In contrast, the application-specific functionality in a hook method knows how to perform a particular type of service.

Improve modularity, reusability, and configurability of event-driven applications: The pattern decouples application functionality into separate classes. For instance, there are two separate classes in the logging server: one for establishing connections and another for receiving and processing logging records. This decoupling enables the reuse of the connection establishment class for different types of connection-oriented services (such as file transfer, remote login, and video-on-demand). Therefore, modifying or extending the functionality of the logging server only affects the implementation of the logging handler class.

Improves application portability: The Initiation Dispatcher’s interface can be reused independently of the OS system calls that perform event demultiplexing. These system calls detect and report the occurrence of one or more events that may occur simultaneously on multiple sources of events. Common sources of events may in- clude I/O handles, timers, and synchronization objects. On UNIX platforms, the event demultiplexing system calls are calledselect and poll [1]. In the Win32 API [16], the WaitForMultipleObjects system call performs event demultiplexing.

Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at the level of event demultiplexing and dispatching within a process or thread. Serialization at the Initiation Dispatcher level often eliminates the need for more complicated synchronization or locking within an application process.

这些貌似是很多模式的共性:解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制等,因而并不能很好的说明什么,特别是它鼓吹的对性能的提升,这里并没有体现出来。当然在这篇文章的开头有描述过另一种直观的实现:Thread-Per-Connection,即传统的实现,提到了这个传统实现的以下问题:
Thread Per Connection缺点

Efficiency: Threading may lead to poor performance due to context switching, synchronization, and data movement [2];

Programming simplicity: Threading may require complex concurrency control schemes;

Portability: Threading is not available on all OS platforms. 对于性能,它其实就是第一点关于Efficiency的描述,即线程的切换、同步、数据的移动会引起性能问题。也就是说从性能的角度上,它最大的提升就是减少了性能的使用,即不需要每个Client对应一个线程。我的理解,其他业务逻辑处理很多时候也会用到相同的线程,IO读写操作相对CPU的操作还是要慢很多,即使Reactor机制中每次读写已经能保证非阻塞读写,这里可以减少一些线程的使用,但是这减少的线程使用对性能有那么大的影响吗?答案貌似是肯定的,这篇论文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)对随着线程的增长带来性能降低做了一个统计:

在这个统计中,每个线程从磁盘中读8KB数据,每个线程读同一个文件,因而数据本身是缓存在操作系统内部的,即减少IO的影响;所有线程是事先分配的,不会有线程启动的影响;所有任务在测试内部产生,因而不会有网络的影响。该统计数据运行环境:Linux 2.2.14,2GB内存,4-way 500MHz Pentium III。从图中可以看出,随着线程的增长,吞吐量在线程数为8个左右的时候开始线性下降,并且到64个以后而迅速下降,其相应事件也在线程达到256个后指数上升。即1+1<2,因为线程切换、同步、数据移动会有性能损失,线程数增加到一定数量时,这种性能影响效果会更加明显。

对于这点,还可以参考C10K Problem,用以描述同时有10K个Client发起连接的问题,到2010年的时候已经出现10M Problem了。

当然也有人说:Threads are expensive are no longer valid.在不久的将来可能又会发生不同的变化,或者这个变化正在、已经发生着?没有做过比较仔细的测试,因而不敢随便断言什么,然而本人观点,即使线程变的影响并没有以前那么大,使用Reactor模式,甚至时SEDA模式来减少线程的使用,再加上其他解耦、模块化、提升复用性等优点,还是值得使用的。

Reactor模式的缺点

Reactor模式的缺点貌似也是显而易见的:
1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
2. Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
3. Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。

参考

Reactor Pattern WikiPedia
Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
Scalable IO In Java
C10K Problem WikiPedia

posted @ 2015-09-08 12:28 paulwong 阅读(718) | 评论 (0)编辑 收藏

Netty3架构解析

前记

很早以前就有读Netty源码的打算了,然而第一次尝试的时候从Netty4开始,一直抓不到核心的框架流程,后来因为其他事情忙着就放下了。这次趁着休假重新捡起这个硬骨头,因为Netty3现在还在被很多项目使用,因而这次决定先从Netty3入手,瞬间发现Netty3的代码比Netty4中规中矩的多,很多概念在代码本身中都有清晰的表达,所以半天就把整个框架的骨架搞清楚了。再读Netty4对Netty3的改进总结,回去读Netty4的源码,反而觉得轻松了,一种豁然开朗的感觉。

记得去年读Jetty源码的时候,因为代码太庞大,并且自己的HTTP Server的了解太少,因而只能自底向上的一个一个模块的叠加,直到最后把所以的模块连接在一起而看清它的真正核心骨架。现在读源码,开始习惯先把骨架理清,然后延伸到不同的器官、血肉而看清整个人体。

本文从Reactor模式在Netty3中的应用,引出Netty3的整体架构以及控制流程;然而除了Reactor模式,Netty3还在ChannelPipeline中使用了Intercepting Filter模式,这个模式也在Servlet的Filter中成功使用,因而本文还会从Intercepting Filter模式出发详细介绍ChannelPipeline的设计理念。本文假设读者已经对Netty有一定的了解,因而不会包含过多入门介绍,以及帮Netty做宣传的文字。

Netty3中的Reactor模式

Reactor模式在Netty中应用非常成功,因而它也是在Netty中受大肆宣传的模式,关于Reactor模式可以详细参考本人的另一篇文章《Reactor模式详解》,对Reactor模式的实现是Netty3的基本骨架,因而本小节会详细介绍Reactor模式如何应用Netty3中。

如果读《Reactor模式详解》,我们知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler构成,在Java的实现版本中,Channel对应Handle,Selector对应Synchronous Event Demultiplexer,并且Netty3还使用了两层Reactor:Main Reactor用于处理Client的连接请求,Sub Reactor用于处理和Client连接后的读写请求(关于这个概念还可以参考Doug Lea的这篇PPT:Scalable IO In Java)。所以我们先要解决Netty3中使用什么类实现所有的上述模块并把他们联系在一起的,以NIO实现方式为例:

模式是一种抽象,但是在实现中,经常会因为语言特性、框架和性能需要而做一些改变,因而Netty3对Reactor模式的实现有一套自己的设计:

1. ChannelEvent:Reactor是基于事件编程的,因而在Netty3中使用ChannelEvent抽象的表达Netty3内部可以产生的各种事件,所有这些事件对象在Channels帮助类中产生,并且由它将事件推入到ChannelPipeline中,ChannelPipeline构建ChannelHandler管道,ChannelEvent流经这个管道实现所有的业务逻辑处理。ChannelEvent对应的事件有:ChannelStateEvent表示Channel状态的变化事件,而如果当前Channel存在Parent Channel,则该事件还会传递到Parent Channel的ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS等,该事件可以在各种不同实现的Channel、ChannelSink中产生;MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事件,它由NioWorker、需要对Message做进一步处理的ChannelHandler产生;WriteCompletionEvent表示写完成而触发的事件,它由NioWorker产生;ExceptionEvent表示在处理过程中出现的Exception,它可以发生在各个构件中,如Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEvent由IdleStateHandler触发,这也是一个ChannelEvent可以无缝扩展的例子。注:在Netty4后,已经没有ChannelEvent类,所有不同事件都用对应方法表达,这也意味这ChannelEvent不可扩展,Netty4采用在ChannelInboundHandler中加入userEventTriggered()方法来实现这种扩展,具体可以参考这里

2. ChannelHandler:在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一个标记接口,它有两个子接口:ChannelDownstreamHandler和ChannelUpstreamHandler,其中ChannelDownstreamHandler表示从用户应用程序流向Netty3内部直到向Socket写数据的管道,在Netty4中改名为ChannelOutboundHandler;ChannelUpstreamHandler表示数据从Socket进入Netty3内部向用户应用程序做数据处理的管道,在Netty4中改名为ChannelInboundHandler。

3. ChannelPipeline:用于管理ChannelHandler的管道,每个Channel一个ChannelPipeline实例,可以运行过程中动态的向这个管道中添加、删除ChannelHandler(由于实现的限制,在最末端的ChannelHandler向后添加或删除ChannelHandler不一定在当前执行流程中起效,参考这里)。ChannelPipeline内部维护一个ChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现,具体可以参考这里,这个模式的实现在后一节中还是详细介绍。

4. NioSelector:Netty3使用NioSelector来存放Selector(Synchronous Event Demultiplexer),每个新产生的NIO Channel都向这个Selector注册自己以让这个Selector监听这个NIO Channel中发生的事件,当事件发生时,调用帮助类Channels中的方法生成ChannelEvent实例,将该事件发送到这个Netty Channel对应的ChannelPipeline中,而交给各级ChannelHandler处理。其中在向Selector注册NIO Channel时,Netty Channel实例以Attachment的形式传入,该Netty Channel在其内部的NIO Channel事件发生时,会以Attachment的形式存在于SelectionKey中,因而每个事件可以直接从这个Attachment中获取相关链的Netty Channel,并从Netty Channel中获取与之相关联的ChannelPipeline,这个实现和Doug Lea的Scalable IO In Java一模一样。另外Netty3还采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor设计,其中NioSelector的两个实现:Boss即为Main Reactor,NioWorker为Sub Reactor。Boss用来处理新连接加入的事件,NioWorker用来处理各个连接对Socket的读写事件,其中Boss通过NioWorkerPool获取NioWorker实例,Netty3模式使用RoundRobin方式放回NioWorker实例。更形象一点的,可以通过Scalable IO In Java的这张图表达:


若与Ractor模式对应,NioSelector中包含了Synchronous Event Demultiplexer,而ChannelPipeline中管理着所有EventHandler,因而NioSelector和ChannelPipeline共同构成了Initiation Dispatcher。

5. ChannelSink:在ChannelHandler处理完成所有逻辑需要向客户端写响应数据时,一般会调用Netty Channel中的write方法,然而在这个write方法实现中,它不是直接向其内部的Socket写数据,而是交给Channels帮助类,内部创建DownstreamMessageEvent,反向从ChannelPipeline的管道中流过去,直到第一个ChannelHandler处理完毕,最后交给ChannelSink处理,以避免阻塞写而影响程序的吞吐量。ChannelSink将这个MessageEvent提交给Netty Channel中的writeBufferQueue,最后NioWorker会等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这就是上图的send是从SubReactor直接出发的原因。

6. Channel:Netty有自己的Channel抽象,它是一个资源的容器,包含了所有一个连接涉及到的所有资源的饮用,如封装NIO Channel、ChannelPipeline、Boss、NioWorkerPool等。另外它还提供了向内部NIO Channel写响应数据的接口write、连接/绑定到某个地址的connect/bind接口等,个人感觉虽然对Channel本身来说,因为它封装了NIO Channel,因而这些接口定义在这里是合理的,但是如果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例就可以得到和它相关的基本所有资源,因而这种write、connect、bind动作不应该再由它负责,而是应该由其他类来负责,比如在Netty4中就在ChannelHandlerContext添加了write方法,虽然netty4并没有删除Channel中的write接口。

Netty3中的Intercepting Filter模式

如果说Reactor模式是Netty3的骨架,那么Intercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实现,它是Netty3具有良好性能的基础,而Intercepting Filter模式则是ChannelHandler组合实现一个应用程序逻辑的基础,只有很好的理解了这个模式才能使用好Netty,甚至能得心应手。

关于Intercepting Filter模式的详细介绍可以参考这里,本节主要介绍Netty3中对Intercepting Filter模式的实现,其实就是DefaultChannelPipeline对Intercepting Filter模式的实现。在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandler,同时它在Netty3中也起到桥梁的作用,即它是连接Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipeline,它使用一个ChannelHandler的双向链表来存储,以DefaultChannelPipelineContext作为节点:

public interface ChannelHandlerContext {
    Channel getChannel();
    ChannelPipeline getPipeline();
    String getName();
    ChannelHandler getHandler();
    boolean canHandleUpstream();
    boolean canHandleDownstream();
    void sendUpstream(ChannelEvent e);
    void sendDownstream(ChannelEvent e);
    Object getAttachment();
    void setAttachment(Object attachment);
}

private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
    volatile DefaultChannelHandlerContext next;
    volatile DefaultChannelHandlerContext prev;
    private final String name;
    private final ChannelHandler handler;
    private final boolean canHandleUpstream;
    private final boolean canHandleDownstream;
    private volatile Object attachment;
..
}

在DefaultChannelPipeline中,它存储了和当前ChannelPipeline相关联的Channel、ChannelSink以及ChannelHandler链表的head、tail,所有ChannelEvent通过sendUpstream、sendDownstream为入口流经整个链表:

public class DefaultChannelPipeline implements ChannelPipeline {
    private volatile Channel channel;
    private volatile ChannelSink sink;
    private volatile DefaultChannelHandlerContext head;
    private volatile DefaultChannelHandlerContext tail;

    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
        if (head == null) {
            return;
        }
        sendUpstream(head, e);
    }

    void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        try {
            ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
        } catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    }

    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
        if (tail == null) {
            try {
                getSink().eventSunk(this, e);
                return;
            } catch (Throwable t) {
                notifyHandlerException(e, t);
                return;
            }
        }
        sendDownstream(tail, e);
    }

    void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        if (e instanceof UpstreamMessageEvent) {
            throw new IllegalArgumentException("cannot send an upstream event to downstream");
        }
        try {
            ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
        } catch (Throwable t) {
            e.getFuture().setFailure(t);
            notifyHandlerException(e, t);
        }
    }

对Upstream事件,向后找到所有实现了ChannelUpstreamHandler接口的ChannelHandler组成链(
getActualUpstreamContext()),而对Downstream事件,向前找到所有实现了ChannelDownstreamHandler接口的ChannelHandler组成链(getActualDownstreamContext()):

    private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
        if (ctx == null) {
            return null;
        }
        DefaultChannelHandlerContext realCtx = ctx;
        while (!realCtx.canHandleUpstream()) {
            realCtx = realCtx.next;
            if (realCtx == null) {
                return null;
            }
        }
        return realCtx;
    }
    private DefaultChannelHandlerContext getActualDownstreamContext(DefaultChannelHandlerContext ctx) {
        if (ctx == null) {
            return null;
        }
        DefaultChannelHandlerContext realCtx = ctx;
        while (!realCtx.canHandleDownstream()) {
            realCtx = realCtx.prev;
            if (realCtx == null) {
                return null;
            }
        }
        return realCtx;
    }

在实际实现ChannelUpstreamHandler或ChannelDownstreamHandler时,调用 ChannelHandlerContext中的sendUpstream或sendDownstream方法将控制流程交给下一个 ChannelUpstreamHandler或下一个ChannelDownstreamHandler,或调用Channel中的write方法发送 响应消息。

public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
    public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        // handle current logic, use Channel to write response if needed.
        
// ctx.getChannel().write(message);
        ctx.sendUpstream(e);
    }
}

public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
    public void handleDownstream(
            ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        // handle current logic
        ctx.sendDownstream(e);
    }
}

当ChannelHandler向ChannelPipelineContext发送事件时,其内部从当前ChannelPipelineContext节点出发找到下一个ChannelUpstreamHandler或ChannelDownstreamHandler实例,并向其发送ChannelEvent,对于Downstream链,如果到达链尾,则将ChannelEvent发送给ChannelSink:

public void sendDownstream(ChannelEvent e) {
    DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
    if (prev == null) {
        try {
            getSink().eventSunk(DefaultChannelPipeline.this, e);
        } catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    } else {
        DefaultChannelPipeline.this.sendDownstream(prev, e);
    }
}

public void sendUpstream(ChannelEvent e) {
    DefaultChannelHandlerContext next = getActualUpstreamContext(this.next);
    if (next != null) {
        DefaultChannelPipeline.this.sendUpstream(next, e);
    }
}

正是因为这个实现,如果在一个末尾的ChannelUpstreamHandler中先移除自己,在向末尾添加一个新的ChannelUpstreamHandler,它是无效的,因为它的next已经在调用前就固定设置为null了。


ChannelPipeline作为ChannelHandler的容器,它还提供了各种增、删、改ChannelHandler链表中的方法,而且如果某个ChannelHandler还实现了LifeCycleAwareChannelHandler,则该ChannelHandler在被添加进ChannelPipeline或从中删除时都会得到同志:

public interface LifeCycleAwareChannelHandler extends ChannelHandler {
    void beforeAdd(ChannelHandlerContext ctx) throws Exception;
    void afterAdd(ChannelHandlerContext ctx) throws Exception;
    void beforeRemove(ChannelHandlerContext ctx) throws Exception;
    void afterRemove(ChannelHandlerContext ctx) throws Exception;
}

public interface ChannelPipeline {
    void addFirst(String name, ChannelHandler handler);
    void addLast(String name, ChannelHandler handler);
    void addBefore(String baseName, String name, ChannelHandler handler);
    void addAfter(String baseName, String name, ChannelHandler handler);
    void remove(ChannelHandler handler);
    ChannelHandler remove(String name);
    <T extends ChannelHandler> T remove(Class<T> handlerType);
    ChannelHandler removeFirst();
    ChannelHandler removeLast();
    void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
    ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
    <T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName, ChannelHandler newHandler);
    ChannelHandler getFirst();
    ChannelHandler getLast();
    ChannelHandler get(String name);
    <T extends ChannelHandler> T get(Class<T> handlerType);
    ChannelHandlerContext getContext(ChannelHandler handler);
    ChannelHandlerContext getContext(String name);
    ChannelHandlerContext getContext(Class<? extends ChannelHandler> handlerType);
    void sendUpstream(ChannelEvent e);
    void sendDownstream(ChannelEvent e);
    ChannelFuture execute(Runnable task);
    Channel getChannel();
    ChannelSink getSink();
    void attach(Channel channel, ChannelSink sink);
    boolean isAttached();
    List<String> getNames();
    Map<String, ChannelHandler> toMap();
}

在DefaultChannelPipeline的ChannelHandler链条的处理流程为:


http://www.blogjava.net/DLevin/archive/2015/09/04/427031.html


参考:

《Netty主页》
《Netty源码解读(四)Netty与Reactor模式》
《Netty代码分析》
Scalable IO In Java
Intercepting Filter Pattern

posted @ 2015-09-08 11:11 paulwong 阅读(546) | 评论 (0)编辑 收藏

最新版BOOTSTRAP 4.0教学


http://wiki.jikexueyuan.com/project/bootstrap4/

posted @ 2015-09-02 10:56 paulwong 阅读(517) | 评论 (0)编辑 收藏

张左峰的歪理邪说 之 Redmine 1.4.X 史上最全插件方案

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://salivaxiu.blog.51cto.com/330680/1613510

前面有一个0.9.X的插件推荐列表,但是太老了,更新一下!

 

PS.很多插件是我自己汉化,甚至付费的,并且进行小幅修改,所以仅供参考。。。。。

 

PPS.话说,搞Redmine这帮人,真是一群疯子,更新太快了。。。。。就不敢更新慢点么。。。。。。

 

Advanced roadmap & milestones plugin  0.7.0  高级路线图,里程碑,不解释
Author box  0.0.3  这个可以在Wiki侧边栏显示一个你的头像和信息,搞个人崇拜用的
CRM plugin 2.3.3-pro CRM插件,很好用,也很专业,付费的
Due Date Reminder plugin  0.2.1 超期提醒插件
Issue Hot Buttons Plugin  0.4.5 超级有用的插件,快速自定义功能按钮,很方便更新问题,尤其是一两项的时候
Redmine Add Subversion Links  0.0.5 为版本库增加直接的SVN链接
Redmine Assets plugin  0.0.1 软资产管理,各位亲们,如果你有几万个问题,里卖包含图片文档啥的,你要找一个特痛苦吧?这个插件帮你解决
Redmine Banner plugin 0.0.6 公告。。。超级实用。。。可以针对全局,也可以针对项目,发出公告
Redmine Better Gantt Chart plugin 0.6.5 更好的甘特图。。的确更好
Redmine CKEditor plugin 0.0.6 超级棒的Textile插件,替换Redmine那个简单的文本编辑器
Redmine Code Review plugin 0.4.8 代码评审,不解释
Redmine Default Version plugin 0.0.2 默认版本,给新建问题设置一个默认版本
Digest plugin 0.2.0 一个后台发送项目活动信息的插件,做日报,或者什么汇报用的DMSF 1.2.3 网页版的SVN,文档管家,支持HTML5,超级好用,但是界面稍显臃肿
Redmine Doodles plugin 0.5.1 投票,不解释
Redmine Glossary Plugin 0.7.0 名词解释插件,知识管理一部分
Redmine Good Job plugin 0.1.1 Goodjob....耍帅用的
Issue Importer 1.0 导入插件,超级实用,如果你有多个工作系统,切换来切换去,离不开这玩意,但是不建议新手使用,批量出错,特痛苦。
Redmine Information Plugin 0.2.5 这个也挺有用的,把系统的一些信息开放出来,比如宏,让大家参考
Redmine Attach Screenshot plugin 0.4.2 附件截图,好似用了Java
Redmine Issue Checklist plugin 1.0.3 Checklist,这个还解释么
Redmine Issue Extensions plugin 0.1.0 问题扩展插件,官方都用的,必须用啊
Redmine Issue Templates plugin 0.0.2.1 这是一个比较好用的插件,问题模板,尤其是一些重复性高,但是需要对过程管理的,配合
ChecklistKnowledgebase 1.0.0 知识库。。。我觉得也很实用的,不过有个BUG,需要你在数据库指定关联一下项目
List duplicate views plugin 0.0.5 好似可以检测到插件冲突。。。我就遇到过
Redmine Local Avatars plugin 0.1.1 本地头像,哈哈,我们断网的
Redmine Logs plugin 0.0.3 可以再后台直接看日志,不用登陆到服务器了
Redmine (Monitoring & Controlling | Monitoramento & Controle) 0.1.1 图形化的数据分析插件,装B用的(当然也有实际意义,宏观数据)
My Roadmaps plugin 0.1.12 我的路线图。。。。其实没啥用
Redmine News Balloon plugin 0.0.1 如果有新闻,会弹出一个气泡,提醒你
Niko-niko Calendar plugin 1.1.2 我最最最最喜欢的插件,感觉跟每天微博一样,挺有意思的
Redmine plugin views revisions plugin 0.0.1 忘了。。。。。
Preview attached files and attributes column plugin 0.1.7 附件的那个缩略图
Private Wiki 0.1.1 私有维基,Redmine最大的问题,在于权限管理不够细分,多这么一个插件,就相当增加一项特殊权限
Reorder links arbitrary 0.0.7 这个对管理员设置有帮助,可以快速设置排序
Smart issues sort plugin 0.3.1 智能排序,对经常用父任务的人,帮助很大
My Page Blocks plugin 1.2 (20120610) 自定义我的工作台,很好使!
Redmine Wiki Extensions plugin 0.4.1 维基扩展,不解释,官方都用
Wiki sidebar toc plugin 0.0.1 维基侧边栏吧?想不起来了
Redmine Wiki table of contents plugin 0.0.3 维基快速位置排序
Issues XLS export 0.2.1  问题列表XLS导出

 

我还没用的,但是考虑装上试试的列表,都是1.4.X可以用的

 

用户属性 http://www.redmine.org/plugins/userprofile
会议室预定 http://www.redmine.org/plugins/mmqb
费用、分票 http://www.redmine.org/plugins/invoices       需付费,可以考虑
免费发票 http://www.redmine.org/plugins/haltr
我的技能 http://www.redmine.org/plugins/redmine_coderwall
甘特图日期 http://www.redmine.org/plugins/redmine_gantt_with_date
登陆后跳转 http://www.redmine.org/plugins/landing_page
Wiki加密 http://www.redmine.org/plugins/redmine_wikicipher     可以考虑
Email过滤 http://www.redmine.org/plugins/redmine_email_notification_content_filter  可以考虑
另一个截图 http://www.redmine.org/plugins/javasript_screenshot
项目侧边栏 http://www.redmine.org/plugins/sidebar       可以考虑
匿名观察 http://www.redmine.org/plugins/anonymous-watchers     可以考虑
你的意思 http://www.redmine.org/plugins/didyoumean      可以考虑
隐身模式 http://www.redmine.org/plugins/redmine_stealth      可以考虑
发行说明 http://www.redmine.org/plugins/redmine_release_notes

论坛主题 http://www.redmine.org/plugins/redmine_boards
自定义邮件 http://www.redmine.org/plugins/notify_custom_users
日历假期 http://www.redmine.org/plugins/redmine_multi_calendar
多种上传 http://www.redmine.org/plugins/redmine_multiple_files_upload

Wiki
维基按钮 http://www.redmine.org/plugins/redmine_wiki_files_toolbar

Time
时间联系 http://www.redmine.org/plugins/linked_time_entries

版本库
SCM扩展  http://www.redmine.org/plugins/redmine_scm_extensions
编辑维基 http://www.redmine.org/plugins/redmine_require_wiki_comment

posted @ 2015-08-19 10:26 paulwong 阅读(2236) | 评论 (0)编辑 收藏

仅列出标题
共115页: First 上一页 33 34 35 36 37 38 39 40 41 下一页 Last