<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>JavaEye论坛精彩帖子</title>
    <description>JavaEye论坛精彩帖子 - Java编程，Ruby编程，微软.net，AJAX，敏捷软件开发，综合软件技术</description>
    <link>http://www.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>我怎么就觉得rails适合做大型应用</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/232415" style="color:red;">http://www.javaeye.com/topic/232415</a>&nbsp;
          发表时间: 2008年08月25日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 12pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><span style="font-size: 10.5pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><span style="font-family: 宋体;"><span style="font-size: 10.5pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><span style="font-family: 宋体;"><span style="font-size: 10.5pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><span style="font-family: 宋体;"><span style="font-size: 10.5pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; text-align: justify;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">之前读了不少文章，说rails不大适合做大型的互联网应用或者企业应用，但通过实际的使用rails，越发的发现rails做大型应用是个不错的选择。&nbsp;说rails不适合做大型应用无非瞄准了rails的2个软肋，一个是ruby的性能，一个是后期的可维护性。</span><span style="font-size: 10.5pt; font-family: 'Arial'; mso-spacerun: 'yes';">&nbsp;</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;</span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;</span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">先谈谈可维护性吧，可维护性最大的问题是需求的改变，简单的说，取决于项目结束后，客户要求你变更程度的大小与多寡，这更多的是项目管理的范畴，具体到语言的层面，其实意义不大，我们可以想想，一个后期维护的问题放到rails难解决，那么放到java、php&hellip;&hellip;里面就简单了？真要比个优劣的话，我倒是觉得rails更胜一筹，rails本身就是一套良好实践的集合，你按它的规范做，会少走不少弯路，与其说rails是框架级代码的复用，不如说是良好设计和经验的复用。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;咱好好谈谈性能吧，由于rails是个全栈式MVC框架，各个组件之间的搭配都是经过优化的，而采用SSH，需要自行协调各个组件之间的协同工作，稍有不慎，肯定会带来性能上的问题，我想各位看客也知道那个意思，我这里就有个例子，一个用SSH开发的社区网站，速度极其的慢，采用ruby&nbsp;on&nbsp;rails&nbsp;改版后，速度明显提升很多，当然这可能也和开发者的水平有关，我也懒的去研究为什么当初采用SSH时性能会出现瓶颈，仅仅这个例子，让我知道一个一般的程序员用rails开发出东西未见得比用SSH的东西性能要低。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;当然，上面的例子可能并不具有普遍性，所以说服力也不够。那么总所周知的是，做一个大型应用的杀手锏是&ldquo;分&rdquo;，当年的j2ee也是这种理念，尽可能的分，但遗憾的是j2ee分的效果并不太好，或许是过于复杂了，我所知道的java项目大都跑在一台服务器上。当然也是有很多大型java项目还是分布式的，那么既然大家都跑在多台廉价的服务器上，单纯的比单台服务器的速度其实意义并不大，在一个可伸缩的架构中，资源的消耗应该随负载线性上升，负载可由用户流量、数据量等测量。如果说性能衡量的是每一工作单元所需的资源消耗，可伸缩性则是衡量当工作单元的数量或尺寸增加时，资源消耗的变化情况。换句话说，可伸缩性是整个价格-性能曲线的形状，而不是曲线上某一点的取值。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;所以问题归到了架构上面来了，而对于目前或者未来的应用架构，最合理的方式是把一个大型应用拆成许多合理的单元，而内置了REST支持机制的rails将抢占了未来的先机，当然可能这种机制尚不完善，但它的方向我认为是正确的。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">那么我对rails的"分"的方案有以下几种思路：</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;在应用程序的之间水平切分，一个系统拆成各自独立的系统拼接而成，每个独立的系统的后台将做服务器级别的集群，举个例子，校内最近开发的爱听网就是用ruby&nbsp;on&nbsp;rails&nbsp;开发的，它将是个独立的系统，会作为一个频道拼到现有校内的菜单上，这种方式不错，但相互过于独立，数据共享是个问题。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;在应用程序的内部水平切分，这种粒度要小一点，做相册的负责图片，做音乐的负责音乐，做博客的负责博客，用标准的负载均衡服务器来路由进入的流量。所有应用服务器都是均等的，而且任何服务器都不会维持事务性的状态，因此负载均衡可以选择自己的应用服务器。如果需要更多处理能力，只需要简单地增加新的应用服务器。貌似豆瓣是这种模式。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;针对具体资源的切分，这种方法是把所有的服务抽象成粒度更小的资源，分布在各个服务器上，在主服务器上通过REST调用展现出来，这样各个服务节点相互独立，不会因为某一节点造成性能上的瓶颈，当然我也不是随便说说，目前准备用这种方式构建一个社会化网络，就目前的感觉---良好。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;SOA，相关的功能部分应该合在一起，不相关的功能部分应该分割开来&mdash;&mdash;不管你是否把它叫做SOA，功能分解还是工程秘诀。而且，不相关的功能之间耦合程度越松散，就越能灵活地独立伸缩其中的一部分。我对SOA理解不深，这里有一段访谈倒是蛮有说服力,</span></p>
</div>
</font></font></font></font></font></font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></font></font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></span><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></span>
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-family: 宋体;"><span style="font-size: 10.5pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;"><span style="font-family: 宋体;">写道<font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">
<div class="Section0" style="layout-grid: 15.6pt none;">
<div class="quote_div">Engine Yard公司的首席技术官Tom Mornini表示，单机百万线应用的时代已经结束，面向服务架构(SOA)是这一时代的终结者。该公司提供Ruby and Rails主机服务器。 <br />　　他在最近的采访中说&ldquo;我认为使用大型程序的年代已经结束了&rdquo;&ldquo;有些程序看起来很大，但是随着时间的推移，它们将最终成为许多小程序的结合体。&rdquo; <br />　　通过为全球市场的业务提供灵活性，SOA的可组合性改变了应用开发比赛。在全球市场中，商业机会不是一成不变的。 <br />　　Mornini说&ldquo;我实在看不出任何其他方式可以满足存取数据，改变流体的需求，以便在企业内外跟上时代的步伐。&rdquo;&ldquo;这就是为什么未来能解决所有问题的单机百万线应用在这一点上仅仅是个遗迹。&rdquo; <br />　　Mornini认为，这不再是SOA是传统应用开发选择的问题，而是除了SOA以外，我们没有其它的选择。 <br />　　他说&ldquo;这些大型程序很难管理和维护，很难想像单机应用会成为未来发展的方向&rdquo;。 <br />　　Engine Yard公司的首席技术官认为带有REST的Ruby on Rails是为SOA建立新一代的服务和应用的一种方法。与Java不同，Java是在SOA应用开发时代前开发的项目，他注意到，Ruby on Rails 和REST怀抱SOA为理念向世人提供了一个前所未有的方法。 <br />　　Mornini说&ldquo;拥有一个服从该框架的牢固而又深厚的面向服务架构就是Rails的秘诀&rdquo;该架构的开发商认为（它的SOA功能）是该平台的一大优势。 <br />　　他认为Ruby on Rails非常适合SOA开发。新发布的Rail 2.0令该框架更容易为SOA应用以及旧数据存取所接受。他承认，原有的Rails框架与旧数据存取关系并不是十分融洽。今年推出的新模型已经超过了前者。 <br />　　他说，例如，Rails组提供的代码增加了许多新的功能，通过以服务的形式将旧数据曝光，使得在SOA应用中访问旧数据变得更为简便。 <br />　　Engine Yard公司的首席技术官说 &ldquo;由于遵循了售后服务书籍和网络视频记录的规程，Rails令开发商使用RESTful数据变得更为简单&rdquo;。 <br />　　他说，&ldquo;如果你遵循RESTful Rails的标准过程，在系统外用Rails编写了一个程序，就会自动得到该程序展示的一个建立在XML-over-HTTP基础之上的API。 <br />　　但是如果要使其运转，"继续使用 Rails"很重要。Mornini说这就是Rails遵循既定规程的妙招。 <br /></div>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</p>
</div>
</font></font></font></font></span><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';"><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></font></span><font face="宋体"><font face="'Times New Roman'" style="font-size: 10.5pt; mso-spacerun: 'yes';">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></font></span><font face="宋体">
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</font></span></p>
</div>
<div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;">&nbsp;</p>
</div>
</span></span></span></span></span></p>
</div>
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/232415#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 25 Aug 2008 13:24:35 +0800</pubDate>
        <link>http://www.javaeye.com/topic/232415</link>
        <guid>http://www.javaeye.com/topic/232415</guid>
      </item>
      <item>
        <title>我们不是在做技术决策，我们在玩</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/222159" style="color:red;">http://www.javaeye.com/topic/222159</a>&nbsp;
          发表时间: 2008年07月31日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p class="0" style="margin-top: 5pt; text-align: justify;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp;&nbsp;在这里我不想一味地去抱怨对公司管理的不满，只想和大家一起分享下我们在做技术决策时遇到的问题。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">遇到的很多情况是，公司领导是以前在大公司做过什么CTO之类的人，有了自己的一些资本和人脉积累后，自己跳出来开个小公司，这类人在技术上有着自己独特的见解和十分强的自信心。然后开始招兵买马，那么是什么兵什么马呢？据我了解招的大多数是应届生或不足一年工作经验的。之后项目来了，于是开始带领大家做项目，那么这里存在一个采用什么技术进行开发的问题，如果我是老板我一定会选最NiuBility的技术，你想啊，既然我的人拼不过别人，那么我在技术的先进性一定要比别人NiuBility，再加上自己伟大的创意，这样才有和别人竞争的余地。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">事实上我们目前也是这么干的，昨天j2ee，今天SOA，明天restful，后天&hellip;&hellip;，为了超越别人。于是一帮本来就没有多少的经验的人在各种技术之间疲于奔命。就举个实例吧，最近就有这么一个项目，为了做的比别的同类产品NiuBility，打算用flex做前端，丫的，多酷啊，加上javaeyer一起看好，难道有错？在项目启动会上老板首先把这个东西鼓吹一番，什么跨平台、未来趋势、AIR&hellip;&hellip;，之后大家开始讨论这个东西，当时只有我保留意见，其他人一致通过。现在不想去讨论这个东西有多么NiuBility，关键是目前我们有几个人会这个东西，nobody！于是边学边干，据说pureMVC好，于是就基于pureMVC做，做着做着，我越发的感觉这个这个世界太疯狂了，连AS都还没弄清楚怎么回事的人就开始玩转pureMVC了，尽管我没有参与这个项目。结果是一片混乱，工期开始肆意的延长，老板急了，那加工加点吧，赶制出来的东西粗糙的不得了，一堆问题也不知道怎么办，界面不好看为什么不去修改flex的皮肤？由于不熟练怕耽误时间。性能跟不上为什么不去找瓶颈去优化？因为不会。出错了为什么不能很快解决？因为没有调试经验。和后台交互为什么那么费劲？还是因为不熟。&nbsp;&nbsp;&nbsp;</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">那么你可能奇怪，为什么不早点发现这个技术瓶颈问题从而解决呢？那就要话说在项目决策的时候，经过老板这么一鼓吹，大家都被吹晕了，因为这个东西大家都没接触过，也不好反对，再说确实也拿不出一手的经验去证明这个东西不适宜我们，谁能反驳他？他说好那就好喽。而且还有一点，你要是反对使用这个东西你就会被贴上保守、不自信的标签，老板嘴上不说，心里多少会这么想的，我就由于就没投赞成票，老板几次和我说(做不满意状)，叫偶多了解点这方面的东西。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">那么技术决策该怎么做呢？我觉得很简单，根据员工的特长去选，扬长避短，其实这个道理是蛮简单的，但是这里有个矛盾的地方，就是有些公司为了节省人力成本还是愿意接收大量的应届生或者刚入门者，但又想提高公司的技术水准，所以在做项目的时候大家基本是边学边做，这无疑把企业培训的风险和成本加到项目开发过程中了，不可否认的是技术的提高是个积累的过程&nbsp;，拔苗助长只会让项目死的更快，所以也就让老板感觉，他在工作，我们却在玩。折腾了那么多的NiuBility的技术，结果我们</span></span><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';">j2ee<span style="font-family: 宋体;">了吗？我们soa了吗？我们restful了吗？我只感觉我们在玩！</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">那么为什么不去聘请O6Z这样的大佬来带领大家一边拍脑袋，一边干活，多帅啊，很遗憾，我们供不起这样的大佛，你还别不信，在用人上，有些老板有时就会把一块钱看的和月亮一样大，谁要价低，包装看得过得去，就买谁。这里人力资源可能就是T1说的服务期货，没有服务器、路由器等这样的现货来的实在，来的让人心里感到踏实。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">总之我认为，老板有什么样的枪，就去打什么样的鸟，否则结局很可能就是，老板来收我们的烂摊子，而我们只得另投明主了。</span></span><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;</span></p>
<p class="0"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp; 欢迎拍砖，如有雷同，纯属巧合</span></span></p>
<!--EndFragment-->
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/222159#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 31 Jul 2008 12:58:32 +0800</pubDate>
        <link>http://www.javaeye.com/topic/222159</link>
        <guid>http://www.javaeye.com/topic/222159</guid>
      </item>
      <item>
        <title>Why OO sucks</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://hideto.javaeye.com">hideto</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/230760" style="color:red;">http://www.javaeye.com/topic/230760</a>&nbsp;
          发表时间: 2008年08月20日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          为什么OO很恶心<br /><br />原文： <a href="http://www.sics.se/~joe/bluetail/vol1/v1_oo.html" target="_blank">http://www.sics.se/~joe/bluetail/vol1/v1_oo.html</a><br />作者：Joe Armstrong<br /><br />当我第一次知道OOP的概念时，我非常疑惑，但是不知道为啥——它仅仅在感觉上“不对”。<br />在OOP问世之后变得粉流行（稍后解释为什么），而批评OOP就像“在教堂里咒骂”。<br />OO成为了每个受尊敬的语言必须具备的一部分。<br /><br />而当Erlang变得越来越流行时，我们经常问一个问题“Erlang是OO的吗？”<br />当然正确的答案是“当然不是”——但是我们没有大肆宣扬——我们只是换了种精心设计的说法，Erlang是某种OO但不是真的是。<br /><br />这时我想起在法国巴黎时IBM的老板在7th IEEE逻辑编程大会上的演讲。<br />IBM prolog添加了许多OO扩展，当人们问起时他说：“我们的客户想要OO的prolog，所以我们构建了OO的prolog”<br /><br />我想到了“多么简单，没有良心的疑虑，没有灵魂的搜索，没有‘这是正确的事情’的问题。。。”<br /><br /><strong>为什么OO很恶心</strong><br /><br />我对OOP的反对原则源自一些基本的概念，我将概述其中一些反对意见。<br /><br /><strong>反对之一——数据类型和方法应该绑定在一起</strong><br />对象将方法和数据结构绑定在一起成为不可分割的单元。我认为这是基本的错误，因为方法和数据结构属于完全不同的世界。为啥哩？<br /><br />1，方法做事情。它们是输入和输出。输入和输出的是方法所改变的数据结构。<br />在大部分编程语言里，方法由命令式语句顺序构建：“做这件事然后那件事。。。”<br />理解方法首先得理解做事情的顺序（在懒惰函数编程语言和逻辑语言中这个限制被放宽了）<br />2，数据结构是结构。它们不做任何事情。它们本质上是声明。“理解”数据结构比“理解”方法简单多了。<br /><br />方法作为黑盒子来转换输入和输出。如果我理解输入和输出，这样我就理解了方法。这并不意味着我可以写这个方法。<br /><br />方法通常理解为在一个计算系统里用来将数据结构T1转换为数据结构T2的东西。<br /><br /><strong>既然方法和数据结构是完全不同类型的动物，那么将它们锁在一个笼子里就是完全错误的。</strong><br /><br /><strong>2，反对之二——任何东西都必须为对象</strong><br />考虑“时间”。在OO语言里“时间”也必须是对象。但是在非OO语言里一个“时间”是一个数据结构的实例。<br />例如，在Erlang里有许多不同类型的时间，它们可以使用类型声明来明确指定：<br /><pre name="code" class="java">
-deftype day() = 1..31.
-deftype month() = 1..12.
-deftype year() = int().
-deftype hour() = 1..24.
-deftype minute() = 1..60.
-deftype second() = 1..60.
-deftype abstime() = {abstime, year(), month(), day(), hour(), min(), sec()}.
-deftype hms() = {hms, hour(), min(), sec()}.
...
</pre><br />注意这些定义不属于任何特殊的对象。它们很普遍，并且数据结构表示的时间可以被系统中的任何方法处理。<br /><br />没有相关联的方法。<br /><br /><strong>反对之三——在一个OOP语言里数据类型定义散布到任意位置</strong><br />在OOP语言里数据类型定义属于对象。<br />这样我就不能在一个地方找到所有的数据类型定义。<br />在Erlang或者C里我可以在一个单独的include文件或数据字典里定义我所有的数据类型。<br />在一个OOP语言里我不能——数据类型定义散布到任意位置。<br /><br />让我举一个例子。假设我想定义一个通用的数据结构。通用数据类型是一个数据类型，它在系统中的任意位置出现。<br /><br />lisp程序员知道，拥有一个较小数量的通用数据类型和在它上面的大量的小方法会更好。<br /><br />通用数据类型就比如linked list，或者一个array或者一个hash table或者更高级的对象如time或者date或者filename。<br /><br />在一个OOP语言里我不得不选择一些base对象来在里面定义通用的数据结构，所有其他想使用这些数据结构的对象必须继承该对象。<br />假设现在我想创建一些“time”对象，那么它应该属于哪个对象呢。。。<br /><br /><strong>反对之四——对象拥有私有状态</strong><br />状态是所有罪恶的根源。特别是有副作用的方法应该避免。<br /><br />在编程语言里状态是令人讨厌的，而真实世界里状态却千奇百怪的存在着。<br />我对我的银行账户的状态很感兴趣，当我从我的账户存钱或取钱时我希望我的银行账户状态成功更新。<br /><br />既然状态在真实世界里存在，那么编程语言应该提供什么能力来处理状态呢？<br /><br />1，OOP语言说“将状态隐藏”。状态仅仅通过访问方法来隐藏和可见。<br />2，传统编程语言（C，Pascal）说状态的可见度由语言的scope规则来决定。<br />3，纯声明式语言说没有状态。系统的全局状态转移到方法里然后从方法里出来。<br />类似于monad(函数式编程语言)和DCG(逻辑语言)等机制被用来隐藏状态，这样它们可以像“有没有状态无所谓”一样来编程，但是对系统状态的完全访问是必需的。<br /><br />OOP语言所选择的“隐藏状态”可能是最坏的选择。<br />它们不是将状态显示出来并寻找减少状态的坏处的方式，而是将状态隐藏起来。<br /><br /><strong>为什么OO粉流行？</strong><br /><br />1，原因1——它被认为很容易学<br />2，原因2——它被认为让代码更易重用<br />3，原因3——它被大肆宣传<br />4，原因4——它创建了一个新的软件工业<br /><br />我看不到原因1和原因2的证据。原因看起来像是技术背后的驱动力。<br />如果一个编程语言技术如此之差，然后它创建了一个新的工业来解决它自己本身的问题，则它会成为想从中牟利的人的好工具。<br /><br />这就是OOP背后真正的驱动力。<br /><br />以及一篇回复：<a href="http://konryd.blogspot.com/2007/08/why-why-oo-sucks-sucks.html" target="_blank">Why "Why OO Sucks" Sucks </a>
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/230760#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 20 Aug 2008 16:53:35 +0800</pubDate>
        <link>http://www.javaeye.com/topic/230760</link>
        <guid>http://www.javaeye.com/topic/230760</guid>
      </item>
      <item>
        <title>关于两个世界体系的对话</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://trustno1.javaeye.com">Trustno1</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/231515" style="color:red;">http://www.javaeye.com/topic/231515</a>&nbsp;
          发表时间: 2008年08月22日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这一段是我还在写的文章中的一部分.<br />=========================================<br />.....<br />习惯经验的强大惯性，源自于背景的长期稳定性。软件体系的快速变革，让我们忽视了硬件体系的长期稳定。这种稳定性使得很多习惯经验变成了不言自明的信条。大多数的软件设计方法的革新只不过是用旧石斧打造出来新石斧。在C中我们使用getc,putc来进行IO,在Java中无非是变成了System.in.read(),System.out.print ()。为什么IO必定是这种形式呢?这是因为我们长期使用着同一种计算机。我们知道PC/Mac这样的计算机中CPU与IO设备进行通信，需要通过各种总线。下面这张图演示了CPU与IO设备之间通信的基本过程.<br /><br /><img src=" http://www.javaeye.com/upload/attachment/35779/78c89cc6-93be-386c-8086-a48b5a8510ec.jpg" />以C语言为代表的传统的IO,实际上是单CPU上单任务工作模式的投影。在单台计算机上, 传统计算机体系结构决定了CPU处于控制者和决策者的地位。换而言之,我们历来习惯于以CPU的视角来考虑程序的IO逻辑.程序员是将自己假设为CPU. 程序员关心的IO设施只是一个黑盒.我们只需要往IO发送一个请求，然后等待请求回来进行运算,完全不关心这一来一回之间到底发生了什么过程.<br />但是当我们打开黑盒,观察CPU与IO的通信过程的时候, IO Monad就从幕后走向了台前。以总线的角度看,CPU和外设是等同的，都只是一个具有运算能力和输入输出端口的黑盒.总线正如Bind/>>=函数一样不关心这些黑盒子里如何运算的，它只关心从这个黑盒拿数据出来放入那个黑盒. 从整个计算机的体系结构看，传统的IO观念只不过是IO Monad的一个局部化形态。<br />  IO Monad实则上在一些接近操作系统底层的软件中，经常扮演者数据总线这种核心角色。比如说Unix/linux shell的管道命令就是彻头彻尾的IO Monad. cat,命令是return/Unit函数,|管道符就是bind/>>=函数。例如:cat sample.txt|grep "High"|wc –l .cat 将sample.txt的文件内容包装成stdout,|管道符将stdout的内容传给grep 命令查询所有单词位High的行，查询的结果又被转化为stdout,再通过|管道符传送给wc命令进行行数统计。微软最新的Shell取名为Monad,其言下之意恐怕无需赘述了.<br />   不仅如此,IO Monad在结构化程序语言的最初演化的阶段也残留了一些踪迹.很多古老的Pascal程序，都保留了在程序首部书写Input Outpu参数的习惯.<br />program  fac_n(input,output);<br />　　var<br />　　n:integer;<br />　　function fac(n:integer):integer;<br />　　begin<br />　　if n=1 then fac:=1<br />　　else fac:=n*fac(n-1);<br />　　end;<br />这就是把程序的主体看成IO Monad上的一个计算函数。所有的Pascal函数可以通过操作系统的IO设施串联起来.当然随着程序语言往CPU中心化的演化,这些痕迹就逐渐消失了. <br />Monad仅仅是一个数学框架，并没有提供什么新的软件技术。在Monad 诞生之前，程序员们就已经在广泛的使用类似的软件设计方法。但是这些设计方法本身却无法回答我们一系列的追问:Exception,IO,Pipeline,为什么这些毫不相关的领域中都会出现相同的数学结构?为什么在不同的领域内呈现的Monad 特性完全不同，有的多有的少有的甚至完全消失?这些相同的结构的背后又预示着什么?以及,我们为什么要问这么多为什么?这些问题的答案对我们的编程实践又有什么意义?<br /><br />                              (2)关于两个世界体系的对话<br />并行计算,是一群顽皮的孩童,充满着生机勃勃的活力，却又不失恶作剧式的顽皮。他们仿佛是一座活跃的“火山”。他们身上惊人的能量只有正确的引导下，才能完全的绽放出来而不致埋没；他们身上顽皮的天性只有在严格的教导中，才不会变成脱缰的野马而无法驾驭。正如儿童的教育首先需要深入他们的内心世界，我们也必须深入并行计算的本质，去探究这些孩子内心中的陌生而又神奇的世界。<br /><br />在这群孩子们中间，并发是与我们最为的亲密也是最让我们头疼的一位。如何对付并发?目前存在两个截然对立的答案,Thread Model Vs Event Driven.对这两个模型，在编程实践的层面上我们都经过了严格的探讨，也有很多人通过各种途径去尝试调和其中的分歧。但是Which is a bad idea?是一个至今争论不休的问题。我认为这不是仅靠分析他们各自优劣就能解决的问题，而是必须解释清楚为什么会产生这两种截然不同的方案。知其然，必先知其所以然。只有回到问题的源头，才能以更宽广的视野去选择道路。<br /><br />在我们介绍IO Monad的部分时，曾经略带提过计算机体系结构决定了程序员以CPU为思考中心的习惯。这个计算机模型，就是统治我们现在大多数计算机内部构造的冯诺依曼模型。<br />在计算机中存在两种不同的流，一种称为控制流(Control Flow),另外一种则是数据流(Data Flow)。计算机要进行工作必须要通过其中一种进行驱动。冯•诺依曼机的核心工作方式是控制流(指令流)驱动。即按照指令的执行序列，依次读取指令，然后根据指令所含的控制信息，调用数据进行处理。冯•诺依曼机为了控制指令序列的执行顺序，设置一个程序(指令) 计数器PC(Program Counter)，让它存放当前指令所在的存储单元的地址。如果程序现在是顺序执行的，每取出一条指令后PC内容加l，指示下一条指令该从何处取得。如果 程序将转移到某处，就将转移的目标地址送入PC，以便按新地址读取后继指令。<br />Program Counter是一个递增的自然数。我们知道，自然数集具有严格的顺序性,1&lt;2&lt;3,这种特性的集合在数学上被称为偏序集。如果我们在两个偏序集中找到一个函数 <img src="http://www.javaeye.com/upload/attachment/35781/4745e71c-0167-351c-b99a-8465909a44ca.jpg" /> 那么我们称f为保序的连续映射.<br />在冯诺依曼机中,正是通过时钟发生器与PC,PC与内存地址之间的连续映射把CPU时间的顺序性映射到了代码的顺序性。比如X86 CPU大致就可以分为下面几个时钟周期.(FC为取指令周期,SC为源操作数周期,DC为目的操作数周期,EC执行周期,IC中断相应周期,DMA,DMA传送周期) <br /><img src="http://www.javaeye.com/upload/attachment/35777/64055853-f462-3a7a-b8fc-38a23e0f2741.jpg" /><br /><br />冯诺依曼机的数据流是由控制流驱动，比如X86Cpu中，数据是在CPU取指以后在SC周期读取的.这点在大多数人看来是不言自明无足轻重的。但是他们却涉及到了传统编程语言中最为本质的问题。Fortran之父John Backus在它图林奖领奖演说&lt;Can Programming Be Liberated from the von Neumann Style?>中, 毫不讳言的道出了这一点:<br /><br /><div class="quote_title">引用</div><div class="quote_div">” Von Neumann  programming languages use variables to imitate the computer's storage cells; control statements elaborate its jump and test instructions; and assignment statements imitate its fetching, storing.……The primary statement in that world is the assignment statement itself. All the other statements of the language exist in order to make it possible to perform a computation that must be based on this primitive construct: the assignment statement.”<br />“冯诺依曼型的语言,使用变量来模仿计算机的存储单元;控制语句来描述跳转和测试指令;赋值语句模仿数据的获取，存储….这个世界最核心的语句就是赋值语句本身。所有其它语句的存在，只是为了能使那些根植于赋值语句的计算结构可以正确地运行。”</div><br /><br /> <br />这回答了我们一个问题,为什么在传统语言中IO的行为是与Monad截然对立的? 赋值语句本身是一种CPU内部的隐式IO操作。因此传统IO函数与赋值语句是可复合的，它的确让我们的程序看上去自然舒适，而不像IO Monad那样别扭。但是这种暂时的自然舒适，给我们带来了无尽的麻烦。我们能够放心的让计算机运算1+1是因为有图林机和lambda演作为保障。隐式的赋值语句将IO混入了计算后,会发生什么呢?显然这是这两个模型无法回答，而又需要我们深入探究的问题。作为结构化语言的发明人John Backus在同一片演说里，反复强调了对赋值语句所带来的困扰<br /><div class="quote_title">引用</div><div class="quote_div"><br /> ” Moreover, the assignment statement splits programming into two worlds.The first world comprises the right sides of assignment statements. This is an orderly world of expressions, a world that has useful algebraic properties……It is the world in which most useful computation takes place.<br /> The second world of conventional programming languages is the world of statements.…...This world of statements is a disorderly one, with few useful mathematical properties. Structured programming can be seen as a modest effort to introduce some order into this chaotic world, but it accomplishes little in attacking the fundamental problems created by the word-at-a-time von Neumann style of programming, with its primitive use of loops, subscripts, and branching flow of control.”<br />更重要的是，赋值语句将程序割裂为两个世界。第一个世界是赋值语句右边的世界。这是一个有序的表达式世界，这个世界有许多有用的代数特性…….。最有用的计算都发生在这里。第二个世界是语句的世界，这是一个无序的世界，在那里找不到什么有用的数学特性。结构化编程一定层度上为这个混乱的世界带来一些秩序，但是它那些原始的循环,子函数,分支流程技术从未击中过冯诺依曼型语言的本质问题---一次一条指令的控制流模式”</div><br /><br />John Backus的演讲，已经过去31年了，他老人家也已经驾鹤西去。相比于Backus的年代，我们在编程实践上已经有非常丰富的手段来应付这个混乱的世界，结构化，面向对象，面向方面,动态语言。在这些方法看来，赋值语句与计算是等价的、同质的、混合使用是天经地义无需置疑的；IO只是局部领域内的专有问题(比如网络通信)而不是全局性的问题。但是另一方面理论研究者们遵循John Backus观点,用数学理论构建IO世界的内在秩序。这些数学理论的推演告诉我们这样一个结果: 在冯诺依曼型语言中IO问题不是局部的而是全局性的。IO具有并发性，而计算是非并发的，这两种操作需要分别处理。当程序中引入越来越多的并行/并发背景的时候，John Backus的远见卓识就越显示出他深邃的洞察力。<br /><br />并发占主流的程序里,IO的并发性意味着单台计算机面对的将不是唯一的时钟。比如两台以TCP连接的服务器,他们时钟速度未必是等同的，即便是等同的我们也无法忽略时钟信号传递的延时。当两个时序不一样的时钟共同驱动一份代码的时候，冯诺依曼机的根本性难题也随之而来。冯诺依曼机的程序指令是按照CPU的时序顺序执行的，并发多任务程序也不例外。偏序集的有一个特性叫做传递性,1&lt;2,2&lt;4,那么1&lt;4.这种传递性。这使得自然数集的若干个子集的并集同样存在偏序性. 在冯诺依曼机上,任何与顺序有关的问题，最为简易的方式就是依靠偏序的传递性,从底层自低向上地一级一级传递CPU的时钟序列。Thread Model模型之所以易于开发，也正是因为操作系统在底层控制了时间片的分配，使得每一个Thread的时钟序列和代码顺序保持一致。换而言之Thread Model是一个放大了的冯诺依曼机，要在这个模型中处理并发，我们必须面一个问题——时钟校准。<br />时钟校准恐怕是这个世界最本质的问题之一，它直接导致了上世纪最为诡异的理论——相对论的诞生。从物理学上讲，信号传递是时钟校准的唯一办法。在计算机中也不能例外，冯诺依曼机要校准不同的时钟序列，就必须解决如何获取外来信号的问题。控制流驱动模式决定了冯诺依曼机不允许外界的信号直接驱动指令执行。CPU只能通过本地时钟触发控制流，周期性发起状态查询。CPU在某个周期向其他时钟源发送信号，在收到远端时钟的反馈信号后计算得出本地代码序列上的同步点,然后移动PC指针指向同步点。无论是早期CPU的轮询模式还是现在广泛采用的中断方式，其基本的校准模式并没有改变,只不过查询对象由最初的IO设备演变为中断寄存器。<br /><br />基于信号查询的校准方式，让冯诺依曼机在处理并发问题上就像是带着镣铐跳舞。控制流驱动首先无可避免地导致了锁机制的产生。John Backus 告诉我们赋值语句其实是一种IO,它意味着赋值运算符两边数据的单向的数据流动。但是冯诺依曼机的查询式时钟校准机制，必然意味着一次双向的数据流动。如果以两条赋值指令来驱动一次双向数据交换，那么势必就要同步两条指令的先后执行顺序。但是要同步指令的执行顺序，又必须先校准时序。于是这里我们陷入了类似于相对论的逻辑困境。两个惯性系中要校准时钟必须首先知道光速的传播速度，而要知道光速的传播速度必须首先校准时钟。爱因斯坦为了避免这种逻辑无穷倒退，引入了速度不变的光信号。类似地在冯诺依曼机下我们必须强迫一次双向的数据交换在一个指令中瞬时的完成,比如X86下的test&set, exchange等等。这些指令的使用就导致了Mutex, Semaphore, Critical Section各种五花八门的锁结构。随着锁机制的引入，锁竞争，死锁，粒度控制，各种并发性的麻烦滚滚而来。然而我们的麻烦还远没有结束。<br />控制流驱动还会导致冯诺依曼综合症中的 “Memory Wall”效应(另外两个效应是”Energy Wall“和“Education Wall“)。因为每一次指令的执行都必须伴随着计算数据地址，状态地址，指令地址等等额外的数据流动开销。随着指令数量的增加Memory Wall会越来越厚，最终会阻塞控制流的运行,使其而无法及时响应IO信号。Context Switch就是最为著名的Memeory Wall.在外界的IO信号未到达之前，Thread必须不间断地定期执行信号查询代码。每次查询完毕让出CPU后就要执行复杂的数据流动(比如Register save/restore,Cache refill等)。为了避免这种开销,现在很多语言采用消耗更低的Green thread的方案. 这些方案可以一定程度上降低消耗但是无法完全根除。在并发性达一定数量级后即便是Erlang这样的语言也无法忽视“Memory Wall”的存在。因为Thread 模型的困难不是来自于局部实现上的疥癣之痒，而是冯诺依曼模型所带来无法根治的顽疾。<br /><br />在冯诺依曼机统治计算机业的长达60多年,它带来的Education Wall使得很多人特别是战斗在开发第一线的程序员们很难想象有非冯诺依曼结构的存在。其实令人更加难以想象的是，在冯诺依曼这位教皇统治现代计算机工业之前,绝大多数的电子计算机是并行计算的。有一个传说流传甚广——冯诺依曼制造了第一台计算机ENIAC。其实冯诺依曼本人未曾参与ENIAC的制造,那篇奠定冯诺依曼机结构的101页报告也是在ENIAC运行了一年后才发表的，更重要的是ENIAC是一台并行计算机。冯诺依曼对计算机的兴趣,来自于曼哈顿工程中大量的数值计算。1944年冯诺依曼在火车站上偶遇了ENIAC总设计师戈尔斯坦后才参观了当时ENIAC的研制小组。当时冯诺依曼发现，<br /><div class="quote_title">引用</div><div class="quote_div">“the machines' abilities for parallel operations made programming significantly more complicated. This taught him to focus on single-instruction code where parallel handling of operands was guaranteed not to occur”<br />“这些机器的并行操作的能力使得程序编制极为复杂。这一事实让冯诺依曼更多的关注于顺序指令代码，企图以此来保证并行操作决不会出现。”<br />&lt;John von Neumann and the Origins of Modern Computing> William Aspray 1990.</div><br /><br />冯诺依曼其实不只一次地在阴沟里翻过船。相对于量子力学的可加性假设，反对Backus设计Fortran语言来说，冯诺依曼综合症还算不上是一种失误。因为即便在我们这个软硬件高度发达的年代里,并行计算仍然是一个飘荡的幽灵。可以想象,在那个电子管和打孔机的年代里,并行计算的难度恐怕是连这位能心算无穷级数的天才级数学大师都望而生畏的东西。但是无论如何，冯诺依曼的设计的确为后世的计算机工业带上了一个难以解套的紧箍咒。今日无数天才程序员为之殚精竭虑的并行计算，其实是一个非常幽默的问题——如何在一台原本设计用来杜绝并行计算的机器上进行并行编程？<br /><br />由于冯诺依曼机在并行计算上的困难是本质性的，很难在它之上做零碎的修改来彻底治愈冯诺依曼综合症。我们迫切需要适合并行计算的计算机模型。计算机科学家们发现,运算之间的时序性其实只取决于运算之间结果依赖性。比如说这样一个计算(2+3)*(4+6)。假设我们有两个CPU,显然两个加法可以同时进行.他们的开始执行时依赖于两个参数的输入时间，乘法的开始执行时间依赖于最后一个加法的完成时间。<br /><img src="http://www.javaeye.com/upload/attachment/35775/5d5ddefd-e1cd-3b65-b358-b2dedc6aa766.jpg" /><br />这种计算机模型称为数据流机。在这种计算机上，首先需要依靠编译器分析程序中的数据依赖性。与冯诺依曼结构为每一个内存单元表识一个变量名不同，编译器为每一个运算上的依赖节点标记一个唯一的Tag.比如上面这个运算,我们可以为所有的Input标记Tag(Input1….Input4),为乘法运算所依赖的两个加法运算标记两个Tag(Add1,Add2)。编译后的运算指令和Tag一起被合并到一种叫instruction storage的包。比如2+3就变成了(Input1,Input2,+,Add1).当程序运行时这些数据包就会加载到一种叫做CAM的内存中。CAM(Content-addressable memory)与我们普通使用的线性编址随机访问的RAM(Random Access memory)不同。在RAM中我们给出一个地址，它返回这个地址的存储的data word。而CAM是给它一个data word,它会搜索地址空间给出所有存储这个data-word的地址。当一个instruction storage的所有tag处于到达状态时比如(2,3,+,Add1)，(4,6,+,Add2),就会把运算数据和操作指令打包成instruction token交给运算单元进行并行运算.一旦运算完成,运算单元会将运算结果和输出Tag打成一个data token数据包发送给CAM.CAM就搜索所有依赖于此Tag的instruction storage搜索出来，将对应的tag标记为到达状态比如(5,Add2,*,Output)。<br /><br />在DataFlow机上整个计算过程由数据流驱动控制流。每一个指令有一系列类似黑盒的Input Output以及相关的运算操作组成。程序的运算顺序依赖于数据的输入顺序，而不依赖于系统的时钟序列。数据也不会像冯诺依曼机一样长久的存储于内存单元当中，而变成在instruction storage之间传递的数据消息。这种模式在应对针对外部的并发信号时，程序无需进行毫无必要的轮询操作，而是在信号到达的即刻立即响应处理数据。没有了控制流带来的Memory wall,也不需要引入锁机制。那个阴魂不散的赋值运算符所带来的混乱也消失的无影无踪。<br />很多程序员在学习Monad模型时,对它那种抽象违反直觉的模型产生本能的反感，认为Monad只是一种纯粹为了形式化而形式化的奇技淫巧。这种现象并不奇怪，正所谓不识庐山真面目，只缘身在此山中。我们如果在冯诺依曼机的顺序执行的背景下去考察Monad,看到的势必是并发世界在顺序模型上的扭曲投影。而在DataFlow模型下，Monad模型自然而然地回归到了并发形态。我提到过一个IO Monad 的例子<br />getline().DO<br />(X=>X.ToUpper().IO()).DO<br />(X=>putline(X));<br />我们曾把Monad比作联邦快递，现在来看这个联邦快递所作的工作就是在快递数据。每一个被Bind/>>=复合的函数都是一个可以并行计算的instruction storage，而Bind/>>=则是描述了运算与运算之间的依赖性和数据流的走向。IO Monad的各个部件在数据流驱动模型中一一对应必不可少的。现在我们再回过头去关注一下Unix Pipe和早期的Pascal程序,恐怕就不会对他们具有Monad结构而感到奇怪了。Unix发明人Dennis M. Ritchie在&lt;The Evolution of the Unix Time-sharing System>中说道:<br /><br /><div class="quote_title">引用</div><div class="quote_div">“Of course, the fundamental idea was by no means new; the pipeline is merely a specific form of coroutine.”<br /> “当然，管道的基本概念没有什么新意;它只是一种特别形式的协程”</div><br /> <br />Monad结构之所以会在PipeLine的层面上发扬光大,完全是因为pipeline所需要面对的是一群并发的进程。在Ritchie的同一片文章里,我们还能看到早期Unix系统中地的并发性问题是依靠管道,消息队列等操作系统机制来完成的。也就是说早期的Unix系统本身就是一个使用Monad结构来构造的并发系统。传统程序语言只是设计用来编写这个并发系统上的非并发的顺序型代码。随着技术的发展,当操作系统层面上的工具无法应付越来越复杂的并发问题时,我们才想起往传统编程语言中增加并发性的支持。<br /><br />Monad模型在并发问题上展现出来的强大能力实非偶然。整个DataFlow的计算过程类似一棵表达式展开树。在更加复杂的例子中，比如引入递归的情况下,DataFlow就会形成一张网或者说图。在DataFlow诞生以来，计算机科学家们对DataFlow网结构特性进行了大量的研究。比如Thomas Hildebrandt在1998年工作，给出了一个基于traced monoidial category的模型.Tarmo Uustalu and Varmo Vene,2005年在此基础上直接给出了一个基于Co-Monad的DataFlow interpreter. 随着研究的继续深入，人们甚至发现并发机制可能更多的与代数几何的拓扑性质有关。比如并发实体之间的状态组合会随并发数量的增加而导致状态爆炸(state space explosion problem).状态爆炸意味着我们无法通过机械的手段来控制和检验并发状态的变换和转移，只能依靠人力来检查。现在学者普遍认为现状态空间之间极有可能存在代数几何中的同伦变换，如果我们能够找到各个状态空间之间的同伦变换，那么我们就无需遍历所有的状态。（<a href="http://www.di.ens.fr/~goubault/index1.html" target="_blank">&lt;A historical note on ``Geometry and Concurrency''></a><br /><br />现在大多数研究者相信, 顺序型问题与并发并行是两个完全不同的领域。前者可能具有更多的代数性质，而后者则是一个完全几何化的问题。这些前沿的研究方向，不是本文的重点。不过我们值得注意的是Monad./Co-Monad是代数几何基础理论---Topos的核心方法。我们可以这样猜测，Monad之所以与DataFlow模型兼容，极有可能是因为它具有与并发系统相一致的几何性质。我们还可以更进一步地猜测，<br />Eugenio Moggi论文中用Monad所描述的6种计算可能都是具有并发性的。比如Exception就是一个并发性的问题。这一点在Erlang中体现的最为自然，Erlang中Exception演变为进程与进程之间的通信消息，Erlang不在需要一层层的try_catch防卫代码。一个进程中的代码出现异常自己死亡，在死亡之前给link进程发送一个消息由link进程决定如何处理异常。<br />如果这些数学理论的猜测都能得到证实的话，我们可能面临的是一个编程观念上的极为重要的转折点。在顺序型领域种种软件结构的代数性质问题，在未来都需要在并行/并发的几何结构上进行重新探讨。这一点也与代数几何的种种结果不谋而合，在代数几何下几乎全部交换代数定理都有明显的几何意义。当然这些问题是我们无法深究的还是需要留给数学家们去头疼，不过这些前沿理论给我们的编程实践点亮前进的灯塔——“The world is parallel “。Joe Armstrong的这句箴言应该成为未来软件设计的首要准则。<br /><br />DataFlow虽然在并行/并发中有着巨大的优势,但是至今没有什么成功的商业型的应用。这里有着众多原因，首先是硬件设备的制造，与现在动不动几个G的RAM来相比，大容量的CAM非常难以制造。所以CAM现在只是用在一些特殊的设备上，比如网络交换机和路由器基本依靠CAM来实现地址查找。其次是软件设计如何去兼顾硬件的限制，指令与数据的依赖性分析需要对程序进行切分，但是切分到什么样的颗粒度则完全依赖于硬件的限制。如果切分的太细那么硬件之间的通信网络就无法承受大量的数据流。最后是如何能够充分的兼容冯诺依曼机上的软件。这些都是DataFlow模型在工程实践方面尚不成熟的领域。<br />但是DataFlow模型上有相当多的成熟技术已经广泛应用于我们日常的软硬件里。在指令级层面，比如X86CPU处理器从Cache预取了一批指令后，通过数据流分析(data flow analysis)分析找出没有互相依赖的并发执行的指令，送到几个独立的执行单元进行乱序执行(Out-of-order execution),最后依靠Cache恢复指令序列。这就是一个缩减化的DataFlow模型。在高级语言层面,许多编译器都能对源代码做数据依赖分析从而进行代码重排优化，将可并行的指令尽量靠近排列，以便CPU一次能够尽量取到更多的并行指令。<br />在软件结构的层面上,最直接的案例就是Event Driven。原始的同步/异步IO仍然是一种轮询信号的做法,唯一的不同在于同步IO是Thread内部轮询，异步IO是程序员手工编写轮询编码。为了进一步提高并发性能,现在主流的操作系统都提供了触发式的IO,比如windows下的完成端口,Linux下的EPOLL/KQueue.这些Event Driven的模型是一个放大了的DataFlow模型。它将多个台机器上的独立的运算过程，看成是一个DataFlow整体计算。程序员将事件对应的代码片断，注册在内存中的一张Hash表或者线性表里。只有当外部IO的信号到达的那一刻程序才会查询事件注册表找到对应的程序片断开始执行。Event Driven相比于Thread的优势是显而易见的，没有Context Switch，外部IO事件几乎可以得到实时地响应，没有锁机制和内存共享，程序的鲁棒性和可扩展性大大提到。但是它的缺点也是难以回避的。我们毕竟还是在冯诺依曼机器上编写代码,我们的编码仍然要受到Program Counter的限制。与Thread自底向上同步时钟序列的模式不同。Event Driven无法依靠偏序集的传递性自动地将外部IO时序与代码之间的顺序映射起来。这也就意味着它必须以一种自顶向下地方式手工维护两者时序的映射关系。Event Driven难以编写和维护。原本按照本地时钟顺序自然编写的代码，需要程序员手工切分成若干块，以及手工维护复杂的状态机来驱动代码块切换时所产生的数据流动。<br /><br />鱼与熊掌如何兼得，就成为了程序员们在处理并发问题上最为头痛的问题.融合Thread和Event Driven，历来有两种不同方向的解决方案。第一种比较主流的方案，是以冯诺依曼机的角度出发，以Thread设施兼容Event Driven，比如Fiber, co-routine, cooperative-thread等等。这一方案的好处在于，实施简单无需在原先的Thread代码上做太多的更动。缺点在于，第一无法消除冯诺依曼机的Memory Wall, 是一个用空间复杂度换取时间复杂度的方案。 像Fiber这样的解决方案以Stack switch替代Context Switch. 为每一个Fiber保留Stack受到了内存的限制，在C/C++这样大量使用Stack 变量的语言里需要为每一个Fiber预留1M左右的Stack.第二，我们将在后文看到这种方案虽然能够解决并发问题，但是无法解决并行问题。在多核系统上，多个Event Loop之间的任务调度是一个巨大的难题。<br />第二种方案是以DataFlow机的角度出发,用Continuation Monad & Lazy Evaluation将Event Driven的代码片断组合出Thread顺序语义。这一工作是，最早是由Koen Claessen在1999年做出的。2007年由Peng Li, Simon Marlow, Simon Peyton Jones 在Haskell上给出了第一个实现。
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/231515#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 22 Aug 2008 11:48:57 +0800</pubDate>
        <link>http://www.javaeye.com/topic/231515</link>
        <guid>http://www.javaeye.com/topic/231515</guid>
      </item>
      <item>
        <title>人，是人，真的是人---走出软件作坊：三五个人十来条枪 如何成为开发正规军（四）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://david-lv.javaeye.com">david_lv</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/231239" style="color:red;">http://www.javaeye.com/topic/231239</a>&nbsp;
          发表时间: 2008年08月21日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          写了《三五个人十来条枪 如何走出软件作坊成为开发正规军》（一）、（二）、（三）后，每篇都点击上万跟贴评论无数。<br /><br />  有网友评论我之前的几篇博文：分析的不错，方案似乎也很能解决问题！不过必须满足一个潜条件：一定要找到非常合适人。现实中，就连最基本的程序员，找个合格的也不容易（聪明伶俐的养不住、经验丰富的养不起、迟钝呆傻的没法要、碰上心术不正的还够你喝一水壶的）<br /><br />  还有网友评论：楼主所说的很多方法，都是假设了客户还不错、对项目的重视程度、习惯于正规化的程度都还过得去，而楼上有些朋友的质疑则是指出这些资源不一定满足的情况；<br /><br />  但是跟贴最多的评论就是：现实问题描述的很精确，但解决方案不现实，太理想化，老板根本不可能给你人。如果真的发慈悲心，也是给你一个新人让你哭死。你想主导项目，省省吧，死了你的心吧，一切都是老板说了算。而且，你敢和客户说个不字，看来你是不想要你的饭碗了。还是乖乖敲好你的代码，多干活，多跟客户搞好关系。高手做啥都是高手，低能再培养再有方法管理他也是低能。你这样研究，只能吃饱饭瞎想瞎扯蛋，有你这工夫，早就把项目做好了。<br /><br />  种种评论来看，一切的根源都是人，是人。大家都觉得我的方法要想实施，必须老板支持，员工也是高素质的，客户也是高素质的。而三者要想凑到一起具备，根本不可能。所以我的方法算是理想的痴人说梦。<br /><br />  能支持的老板从哪里来？高素质的员工从哪里来？高素质的客户从哪里来？好像一切都是运气而来。好像我们就有高薪能聘得起高素质的员工。好像我们的产品面对的就是高素质的客户。<br /><br />  但我回顾了自己10多年的从业经验和管理经验，我并没有发现这个规律。我并非供职国际巨头公司，也不是国内知名企业，只是信息化行业内略有名气而已。手下很少出现名牌大学的员工，也很少能达到所谓的高薪（我自认自己还没有在马云、史玉柱、牛根生、柳传志这样大胸怀大眼光的企业家手下任过职，我们所从事的行业信息化也不是暴利的行业，大家也都知道管理软件没啥暴利，定制化修改、实施、咨询、培训、支持占去了很多成本。），我们的客户也是各种各样的人都有，从挖煤暴发户的私营老板到死气沉沉勾心斗角的国企，我们的客户千奇百怪。<br /><br />  在这样的环境下，我能把方法用起来，我和许多网友也交流过，最重要的是我认可了以下观点，这就是一个职业经理人和老板的关系：<br /><br />  1老板都是疑人也用用人也疑。用人不疑，疑人不用，我不奢望。<br /><br />  2再劳苦功高，我也只是职业经理人，我不拥有这个企业的哪怕1%所有权，所以我做好职业经理人本分，老板的归老板，职业经理人的归职业经理人。职业经理人的职责范围的，老板权力范围的，不要超越，也不要动歪脑筋。即使公司大部分的收入都是你研发的产品带来的。<br /><br />  3计划不计划一件事情，执行不执行一件事情。一定要以老板利益为目的。老板不赚钱，一切好事一切好想法都会被老板推翻，老板就是老板。老板赚钱赚的眉开眼笑，其他的事情就好办的多。这是很多职业经理人居然都认识不到的，他们总抱怨老板限制太死，什么资源也不给，干活还贼累。根源就出在这里了。想实现你的想法，必须在实现了老板想法和目的的前提下才能做。所以我的方法能实现，多靠此。<br /><br />  4而且我的方法不是为了我自己有什么好处，我的每一个方法也都不是为了正规化装修门面图好看。我的方法都是为了解决实际问题，为了老板赚钱更快更省成本更容易，员工更省力，客户更满意，而且每个方法都是在本企业能力和成本范围内能执行落地的解决方案。这样的解决方案，哪个老板会不支持呢。但很奇怪的是，很多研发部主管都忽略这个重点，老板在想利益，他在想技术。两人说不到一个目的去，互相不理解互相不支持互相埋怨，久而久之互相猜疑互相提防互相留一手。其实技术就是个手段，赚钱是目的，双方一起绑定去赚钱，怎么合法的赚更多钱怎么来。如果研发主管能脚踏实地的从本企业的能力和困境和现状去思考改进方法执行落地，而不是抱怨这样的环境没法实现想法消极怠工或心想跳槽，我想很多心结就都打开了。<br /><br />  只有和老板具备了这样的距离和关系，我的方法才好实施下去。所以，很多人觉得太理想化，就是和老板没有找到自己的位置。<br /><br />  但是，即使有这样的基础，要实现我所述的方法，也需要其他的环境支持。<br /><br />  我个人是这么看的：<br /><br />  1好的氛围，才会引入、留住好的人（乱世强盗多就是这个理）。<br /><br />  2好的人，才会有好的制度，并且保持好这个制度（制度是人定的）。<br /><br />  3好的人和好的制度，才会遇到好的客户（有句老话，夜路走多了总会遇见鬼。有些人老想着邪门事，最后也被邪人玩。近朱者赤近墨者黑，什么人总遇到什么人，就这个道理）。好客户就会产生好的结果。<br /><br />  所以，好的人才，好的客户都不是运气来的，而是来自你自己。你就是控制源头的人。<br /><br />  如何制造好的氛围，我讲讲我的职业经理人管理人的一些心得：<br /><br />  1师傅制。这里没有总监，没有经理，只有师傅，老师。总监，经理，会让员工产生隔阂，距离，权力争斗。每一个人总有一个师傅。每一个新人进来，都要指定一位合适的师傅。尤其是新人，更要短期内注意看时候合适，不合适就要更换合适的师傅。什么问题都可以问师傅，从技术到公司制度到公司新闻公司历史到职业发展规划到个人生活问题。团队的凝聚力，配合性，归属感，责任感，很多问题都被人的感情消化了。<br /><br />  2朝九晚五，禁止加班。其实大部分程序员也是不喜欢加班的（不过有些程序员是光棍，也是漂在北京，反正也是一个人，于是就喜欢呆在公司上网玩游戏看小说看电影吹空调，美其名曰加班。还有一类老板喜欢看表面功夫，谁加班就喜欢谁，于是大家都装做很忙都要加班）。因为加班不给钱。不给钱，还加班，天长日久就觉得自己很亏，心里不平衡，各种心思就都有了。其实也没有多大的事。我的老板一开始对我的不加班也是心存戒心，但是每次交给他的结果比加班的部门做的都好都快，他也就默许了。<br /><br />  3良好的办公环境和良好的个人形象。我们看到美女就兴奋的口吐莲花，我们看到阳光溪水草地我们就心情舒畅。当然，我们看自己，别人看我们，都是一个道理。心情好，工作才能好。一个满桌狼藉充满烟味饭味脚丫子味有人在冥思苦想解决问题有人在打游戏有人在放朋克音乐有人在骂有人在打闹嬉笑有人把脚放到桌子上的办公环境，我看谁都会逃离。<br /><br />  4以更快更省成本更容易完成任务为目标，以赚更多钱为目标，以提高产品质量产品价值产品售价为目标，鼓励员工进行自我岗位上的改进创新，我经常给与交流和指导，一旦有效，进行精神或物质的奖励或职位提拔或工资晋级。<br /><br />  好的氛围有了，就需要有好的人才。以下是我引入好的人才的几个心得：<br /><br />  1人的年龄和工作经验拉开距离。年年招，时时招。不断看人，试人，滤人，培养人，形成层次感有阶梯有接力的员工组织，绵绵不断前赴后继，不会出现人才地震、集体疲劳、小团伙争斗。避免不同高低职位上全是80-84年的人。下属还在窝里斗互相不服（很多员工不看对方能力，就看对方的工资和年龄。凭啥你就是我师傅？），那么客户逼你，老板压你，其他部门利益冲突你，下属还闹你，你这个孤独人算是失道者寡助也。<br /><br />  2人的技术能力高低先放一边。首先要过EQ关。有些中小型企业没有HR经理，一般考察EQ，都是老板把关。如果你现在招人没有老板把关，那么必须先考察人的EQ，再考察他的技术能力。我最怕有些羡慕科学管理的管理者照搬什么EQ测试问卷或什么团队游戏来评测。我的评测方法仍然是不讲道理，要讲经历。没有工作经历，至少有学习经历和生活经历吧。一个人的情感、压力、正义感、真诚感、领悟力、心细观察力、思路整理总结能力、关注全面平衡能力、执着力，都能看的出来。<br /><br />  招聘程序员也得看这些。我曾遇到一个程序员，思维混乱所以代码也混乱，思考也不全面，程序到处都是漏洞，思路也不自我整理总结，无法举一反三，给他讲了多遍的需求他都无法自己重述，一有了问题很急躁说搞不定了，一看还是很简单的问题，把错误提示原模原样输入到百度中查百度就能搜到好多，你说这样的程序员算技术合格吗？<br /><br />  其实，试用期的三个月就是主要看他的EQ和他的技术能力、理解学习成长能力，而不是片面只看他的现状技术能力。一个不愿意学习钻研，没有方法钻研快速学习理解，推一下动一下，或者怎么说都理解不了的，都需要统统辞掉。另外，对于心术不正有仇必报不服管教之类，早就扫出门外。一个讲究吃穿用享受或者满口脏话习惯毛病一堆或者不孝顺父母或者满口介词的人坚决不能要。<br /><br />  3专业发展，流程协作。如果不专业化，老板有什么活就分配什么活，时间短了还认为自己是在学习更多知识在锻炼。时间长了就会觉得自己就像个混子，干什么都干过，但什么都拿不起来。出去应聘啥职位，是应聘开发呢，项目经理呢，实施呢，支持呢，销售呢。啥都做过，但啥都没做专，都了解个皮毛，真要让上手还真给人家拿不下来。心就慌了，觉得自己是个被老板困在手心的小鸟，无法飞出本企业的樊笼，一旦飞出就要饿死没有能力存活。好可怕。难道只能在这家公司耗死了？赶快能逃逃吧，逃到一个正规的专业的公司去。<br /><br />  4下一阶段目标交流制定。交流，我想每个CTO或技术总监或研发经理都会做。交流可以了解员工的困难和心中的疑惑、个人期望、个人专业兴趣的变化、人生观世界观技术观管理观生活观（以调整自己以后和该员工如何交流、如何讲解工作、如何鼓励、如何布置任务、如何考察等等）。交流也可以让员工多了解自己是怎么想的。双方在日常很多事情的分歧和误解就会消除，心会往一处想，劲会往一处使。但是，交流也不仅仅实现这些目标。更重要的是，交流，主要为了能给该员工制定一个切实可行的、某段时间段内可达到的、他也喜欢也愿意努力的、也会他未来职业发展很有好处的职业目标。没有目标的工作，虽然他很努力，但是他容易迷失方向。如果他又是一个不能很有悟性很有规划的人，他的工作就会形成做一天和尚撞一天钟。撞钟撞的不错，但没什么更高层次的提高。天长日久，就会木然，倦怠，不思进取，思想守旧，遇到新问题无法突破。所以，我会根据双方的交流，和员工一些协商一个下一阶段的职业发展目标，并且时常指导调整他的做事方法和思考方法，给他讲解一些我过去的工作经验和我的感受，鼓励指导他们有计划有目标的走的更高更专业。这是很多研发部门主管没有做的一点。<br /><br />  最后有几句话和大家分享一下：<br /><br />  1毛主席说：社会主义就是打土豪分田地（不是资本论这样的天人天书），要天天讲，时时讲，到处讲，要团部建设到连队。所以，借用毛主席的方针，咱们的团队精神建设也得这样。天长日久，就形成了文化精神，就形成了习惯。<br /><br />  2习惯决定性格，性格决定命运，细节决定成败
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/231239#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 21 Aug 2008 16:35:04 +0800</pubDate>
        <link>http://www.javaeye.com/topic/231239</link>
        <guid>http://www.javaeye.com/topic/231239</guid>
      </item>
      <item>
        <title>OGNL &amp; ValueStack 入门</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxb8901.javaeye.com">jxb8901</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/223612" style="color:red;">http://www.javaeye.com/topic/223612</a>&nbsp;
          发表时间: 2008年08月03日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          h1. 一个例子<br />请看下面的需求，假设有如下用户对象模型：<br /><pre name="code" class="java">
public interface User {
    public String getName();
    public Date getRegisterDate();
    public Customer getCustomer();
}
public interface Customer {
    public String getId();
    public String getName();
    public boolean isVip();
}
public interface EntCustomer extends Customer {
    public String getTrustId(); // 组织机构代码证号
}
</pre><br />对于给定的用户jack，且该用户所属客户是企业客户，那么我们如何获取该用户的姓名？如何获取用户所属客户的名称？如何判断该用户所属客户是否是VIP客户？如何取jack所属企业的组织机构代码证号？<br /><br />* 采用java代码的方式，我们可以用如下的API调用得到所需信息：<br /><pre name="code" class="java">
jack.getName();
jack.getCustomer().getName();
jack.getCustomer().isVip();
((EntCustomer)jack.getCustomer()).getTrustId();
</pre><br /><br />* 但我们现在在讲述OGNL，因此通过采用OGNL，我们可以用如下方式取得我们所需要的信息：<br /><pre name="code" class="java">
jack.name
jack.customer.name
jack.vip
jack.customer.trustId
</pre><br />由此我们可以看到OGNL的表达方式与java表达方式有以下几点不同：<br />** 不需关注对象类型，不需进行类型转换<br />** 表达方式更简短和直观<br /><br />OGNL表达式最大的优点就是：*简单* 和 *直观*，你不这样认为吗？ 如果你觉得上面的表达式还不够简单和直观，那我们再来看：<br /><pre name="code" class="java">name</pre><br />这也是一个OGNL表达式，也就是取姓名！简单吗？至少足够直观了吧:)<br /><br />h1. 基本概念<br />我们前面看到了OGNL的一个最简单的例子，事实上OGNL确实很简单，如果能理解上面那个例子的用法，那么我们就掌握了OGNL的80%的用法了。<br />上面的例子虽然简单，但其中却含有OGNL的两个最基本的概念：*表达式(expression)* 和 *上下文(context)*，我们先看*表达式*。<br /><br />h3. 表达式<br />OGNL就是表达式！它能让我们用简洁直观的语法表达我们的想法，如同上面的例子一般。简洁直观就是表达式的最大优点！我们知道表达式总是有一个结果，也就是说表达式总是会求值出一个结果，这个结果可能是一个字符串（如名称、组织机构代码证号等），或者是一个布尔值（如是否是VIP客户等），至于这个结果要怎么使用，那就是我们自己来决定的了。<br /><br />h3. 上下文（context）<br />表达式的概念，我相信很好理解，但什么是上下文（context）？简单来说上下文就是环境，表达式求值的环境！还是不理解吗？我们来看一个例子：<br />还是上面最后那个例子：<pre name="code" class="java">name</pre> 细心的你是否会问，这个表达式要取谁的姓名呢？OK，很好！这就是环境，"谁"就存在于环境之中，也就是存在上下文之中。对于不同的环境/上下文，相同的表达式会有不同的结果！而环境/上下文的实质是什么呢？就是一组带名称的对象集合。<br /><div class="quote_title">引用</div><div class="quote_div">思考：表达环境或上下文这个概念的最好的数据结构是什么？</div><br /><br />h3. OGNL上下文概念详解<br />我们前面说上下文就是一组名称-对象对的集合，如下图所示就是一个简单的上下文：<br /><pre name="code" class="java">
user ---> User(name:"jack", ...)
request ---> HttpServletRequest(header: ...)
</pre><br />那么在上面的环境中，我们可以有如下的OGNL表达式：<br /><pre name="code" class="java">
#user.name // 取用户的姓名
#user.age // 取用户年龄
#user.birthday // 取用户生日
#user.customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br />请注意上面表达式中的"#user"和"#request"的用法，"#"表示访问环境/上下文中的对象。<br /><br />现在可以很方便地访问环境中的对象了，那么如果你比较懒惰的话（记住：在程序员群体，懒惰是褒义词！），你是否觉得访问用户的姓名，年龄，生日，等等其它属性如果全部要使用"#user"来访问会不会太麻烦了呢？OK，ONGL的设计者早就考虑了这个问题，我们可以指定user为环境中的特权对象，访问该对象可以不需要使用#user的方式，如下所示代码与上面的完全等价，当然，前提是要预先指定user为特权对象：<br /><pre name="code" class="java">
name // 取用户的姓名
age // 取用户年龄
birthday // 取用户生日
customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br /><br />我们上面所说的"特权对象"在OGNL中称为"根对象"(root)<br /><br />h3. 小结<br />综上所述，理解OGNL表达式的关键是理解其上下文的概念，因为OGNL的上下文概念中引入了"根对象"的概念，所以初学者往往会在这里迷失方向。<br /><br /><div class="quote_title">引用</div><div class="quote_div"><br />OGNL的中文全称是对象图导航语言，也就是说OGNL是一门语言，如同java是一门语言一样。你是否会认为OGNL的作者太夸张了，竟敢把表达式谎称为语言？不，OGNL的语法确实非常简洁，OGNL的代码（我没有说表达式，因为代码是和语法相匹配的词语）通常不会换行，这意味着我们不可能把OGNL的代码写得很长，但是，这并不意味着OGNL的表达能力很弱。事实上，OGNL的语法设计非常简洁，但其功能却相当强大，如果你有兴趣，可以深入阅读OGNL参考手册的集合与lambda章节。<br /></div><br /><br />慢着，事情还未至此结束！struts2对OGNL中的上下文的概念又定义了新的含义，且听我慢慢道来！<br /><br />h3. struts2中的OGNL上下文<br />struts2对OGNL上下文的概念又做了进一步扩充，在struts2中，OGNL上下文通常如下所示：<br /><pre name="code" class="java">
|
                     |--request
                     |
                     |--application
                     |
       context map---|--OgnlValueStack(root) [ user, action, OgnlUtil, ... ]
                     |
                     |--session
                     |
                     |--attr
                     |
                     |--parameters
</pre><br />我们可以使用"#requet"访问HttpServletRequest对象, "#session"访问HttpSession对象，但请注意"根对象"是什么？是ValueStack!<br />那么ValueStack是什么？值栈。也就是一组对象的堆栈。也就是说，在struts2中，根对象不是我们通常的一个对象，而是一组对象。我们可以push新的对象到值栈中，也可以弹出值栈的栈顶对象。如上图所示，假设我们将user对象push到值栈中，那么如下的表达式将与之前我们见过的表达式一样，具有相同的结果：<br /><pre name="code" class="java">
name // 取用户的姓名
age // 取用户年龄
birthday // 取用户生日
customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br />也就是说，我们使用name这个表达式的时候，ONGL会取"根对象"的name属性，但现在根对象是ValueStack！那么访问ValueStack的name属性意味着什么呢？这意味着: ValueStack会先查看栈顶元素是否有name属性，如果有就返回该属性值，否则取出栈顶下的元素，继续查看，直到栈底为止。<br /><br />以上就是OGNL表达式的核心概念，你理解了吗？下一步，你需要了进一步了解OGNL的语法，以发掘其更强大的功能！
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/223612#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 03 Aug 2008 12:53:45 +0800</pubDate>
        <link>http://www.javaeye.com/topic/223612</link>
        <guid>http://www.javaeye.com/topic/223612</guid>
      </item>
      <item>
        <title>潜入memcached server</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ahuaxuan.javaeye.com">ahuaxuan</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/225692" style="color:red;">http://www.javaeye.com/topic/225692</a>&nbsp;
          发表时间: 2008年08月08日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          /**<br /><br />*作者：张荣华<br /><br />*日期：2008-08-08<br /><br />**/ <br /><br />Memcached，人所皆知的remote distribute cache（不知道的可以javaeye一下下，或者google一下下，或者baidu一下下，但是鉴于baidu的排名商业味道太浓（从最近得某某事件可以看出），所以还是建议javaeye一下下），使用起来也非常的简单，它被用在了很多网站上面，几乎很少有大型的网站不会使用memcached。<br /><br />	曾经我也看过很多剖析memcached内部机制的文章，有一点收获，但是看过之后又忘记了，而且没有什么深刻的概念，但是最近我遇到一个问题，这个问题迫使我重新来认识memcache，下面我阐述一下我遇到的问题<br /><br />	问题：我有几千万的数据，这些数据会经常被用到，目前来看，它必须要放到memcached中，以保证访问速度，但是我的memcached中数据经常会有丢失，而业务需求是memcached中的数据是不能丢失的。我的数据丢失的时候，memcached server的内存才使用到60%，也就是还有40%内存被严重的浪费掉了。但不是所有的应用都是这样，其他应用内存浪费的就比较少。为什么内存才使用到60%的时候LRU就执行了呢（之所以确定是LRU执行是因为我发现我的数据丢失的总是前面放进去的，而且这个过程中，这些数据都没有被访问，比如第一次访问的时候，只能访问第1000w条，而第300w条或者之前的数据都已经丢失了，从日志里看，第300w条肯定是放进去了）。<br /><br />	带着这些疑问，我开始重新审视memcached这个产品，首先从它的内存模型开始：我们知道c++里分配内存有两种方式，预先分配和动态分配，显然，预先分配内存会使程序比较快，但是它的缺点是不能有效利用内存，而动态分配可以有效利用内存，但是会使程序运行效率下降，memcached的内存分配就是基于以上原理，显然为了获得更快的速度，有时候我们不得不以空间换时间。<br /><br />	也就是说memcached会预先分配内存，对了，memcached分配内存方式称之为allocator，首先，这里有3个概念：<br />1 slab<br />2 page<br />3 chunk<br />解释一下，一般来说一个memcahced进程会预先将自己划分为若干个slab，每个slab下又有若干个page，每个page下又有多个chunk，如果我们把这3个咚咚看作是object得话，这是两个一对多得关系。再一般来说，slab得数量是有限得，几个，十几个，或者几十个，这个跟进程配置得内存有关。而每个slab下得page默认情况是1m，也就是说如果一个slab占用100m得内存得话，那么默认情况下这个slab所拥有得page得个数就是100，而chunk就是我们得数据存放得最终地方。<br /><br />举一个例子，我启动一个memcached进程，占用内存100m，再打开telnet，telnet localhost 11211，连接上memcache之后，输入stats  slabs，回车，出现如下数据：<br /><pre name="code" class="java">STAT 1:chunk_size 80
STAT 1:chunks_per_page 13107
STAT 1:total_pages 1
STAT 1:total_chunks 13107
STAT 1:used_chunks 13107
STAT 1:free_chunks 0
STAT 1:free_chunks_end 13107
STAT 2:chunk_size 100
STAT 2:chunks_per_page 10485
STAT 2:total_pages 1
STAT 2:total_chunks 10485
STAT 2:used_chunks 10485
STAT 2:free_chunks 0
STAT 2:free_chunks_end 10485
STAT 3:chunk_size 128
STAT 3:chunks_per_page 8192
STAT 3:total_pages 1
STAT 3:total_chunks 8192
STAT 3:used_chunks 8192
STAT 3:free_chunks 0
STAT 3:free_chunks_end 8192</pre><br /><br />以上就是前3个slab得详细信息<br />chunk_size表示数据存放块得大小，chunks_per_page表示一个内存页page中拥有得chunk得数量，total_pages表示每个slab下page得个数。total_chunks表示这个slab下chunk得总数（＝total_pages * chunks_per_page），used_chunks表示该slab下已经使用得chunk得数量，free_chunks表示该slab下还可以使用得chunks数量。<br /><br />从上面得示例slab 1一共有1m得内存空间，而且现在已经被用完了，slab2也有1m得内存空间，也被用完了，slab3得情况依然如此。 而且从这3个slab中chunk得size可以看出来，第一个chunk为80b，第二个是100b，第3个是128b，基本上后一个是前一个得1.25倍，但是这个增长情况我们是可以控制得，我们可以通过在启动时得进程参数 –f来修改这个值，比如说 –f 1.1表示这个增长因子为1.1，那么第一个slab中得chunk为80b得话，第二个slab中得chunk应该是80*1.1左右。<br /><br />解释了这么多也该可以看出来我遇到得问题得原因了，如果还看不出来，那我再补充关键的一句：memcached中新的value过来存放的地址是该value的大小决定的，value总是会被选择存放到chunk与其最接近的一个slab中，比如上面的例子，如果我的value是80b，那么我这所有的value总是会被存放到1号slab中，而1号slab中的free_chunks已经是0了，怎么办呢，如果你在启动memcached的时候没有追加-M（禁止LRU，这种情况下内存不够时会out of memory），那么memcached会把这个slab中最近最少被使用的chunk中的数据清掉，然后放上最新的数据。这就解释了为什么我的内存还有40%的时候LRU就执行了，因为我的其他slab中的chunk_size都远大于我的value，所以我的value根本不会放到那几个slab中，而只会放到和我的value最接近的chunk所在的slab中(而这些slab早就满了，郁闷了)。这就导致了我的数据被不停的覆盖，后者覆盖前者。<br /><br />问题找到了，解决方案还是没有找到，因为我的数据必须要求命中率时100%，我只能通过调整slab的增长因子和page的大小来尽量来使命中率接近100%，但是并不能100%保证命中率是100%（这话怎么读起来这么别扭呢，自我检讨一下自己的语文水平），如果您说，这种方案不行啊，因为我的memcached server不能停啊，不要紧还有另外一个方法，就是memcached-tool，执行move命令，如：move 3 1，代表把3号slab中的一个内存页移动到1号slab中，有人问了，这有什么用呢，比如说我的20号slab的利用率非常低，但是page却又很多，比如200，那么就是200m，而2好slab经常发生LRU，明显page不够，我就可以move 20 2，把20号slab的一个内存页移动到2号slab上，这样就能更加有效的利用内存了（有人说了，一次只移动一个page，多麻烦啊？ahuaxuan说，还是写个脚本，循环一下吧）。<br /><br />	有人说不行啊，我的memcache中的数据不能丢失啊，ok，试试新浪的memcachedb吧，虽然我没有用过，但是建议大家可以试试，它也使利用memcache协议和berkeleyDB做的（写到这里，我不得不佩服danga了，我觉得它最大的贡献不是memcache server本身，而是memcache协议），据说它被用在新浪的不少应用上，包括新浪的博客。<br /><br />	补充，stats slab命令可以查看memcached中slab的情况，而stats命令可以查看你的memcached的一些健康情况，比如说命中率之类的，示例如下：<br /><pre name="code" class="java">STAT pid 2232
STAT uptime 1348
STAT time 1218120955
STAT version 1.2.1
STAT pointer_size 32
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 1
STAT total_connections 3
STAT connection_structures 2
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT bytes_read 26
STAT bytes_written 16655
STAT limit_maxbytes 104857600</pre><br />从上面的数据可以看到这个memcached进程的命中率很好，get_misses低达0个，怎么回事啊，因为这个进程使我刚启动的，我只用telnet连了一下，所以curr_connections为1，而total_items为0，因为我没有放数据进去，get_hits为0，因为我没有调用get方法，最后的结果就是misses当然为0，哇哦，换句话说命中率就是100%，又yy了。<br /><br />该到总结的时候了，从这篇文章里我们可以得到以下几个结论：<br />结论一，memcached得LRU不是全局的，而是针对slab的，可以说是区域性的。<br />结论二，要提高memcached的命中率，预估我们的value大小并且适当的调整内存页大小和增长因子是必须的。<br />结论三，带着问题找答案理解的要比随便看看的效果好得多。<br /><br />Ok,晚了，睡了。<br /><br /><br />注明：由于ahuaxuan水平有限，文中不妥之处还望不吝指正，谢谢。
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/225692#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 08 Aug 2008 09:57:40 +0800</pubDate>
        <link>http://www.javaeye.com/topic/225692</link>
        <guid>http://www.javaeye.com/topic/225692</guid>
      </item>
      <item>
        <title>关键字:查询,事务,粒度</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ahuaxuan.javaeye.com">ahuaxuan</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/231670" style="color:red;">http://www.javaeye.com/topic/231670</a>&nbsp;
          发表时间: 2008年08月22日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="font-size: medium">/** <br /><br />*作者：张荣华 <br /><br />*日期：2008-08-22 <br /><br />**/<br /><br />在那遥远的过去,俺曾经写过一篇关于事务的文章,原文地址见: <a href="http://ahuaxuan.javaeye.com/blog/95124" target="_blank">http://ahuaxuan.javaeye.com/blog/95124</a>.文章大意是这样的:在spring+hibernate的场景下,readonly的事务会有特别的优化.因为readonly的事务在提交的时候不会flush 一级缓存中的几个队列(包括,更新队列,插入队列等).看了那篇文章的同学会以为:ok,只读的时候我只要readonly就行了. 不过那篇文章中我并没有考虑到所有的场景,所有再写一篇文章,算是对整个概念(查询操作是否需要事务)的一个完善.<br /><br />	还是老路子,我先描述一下我遇到的一个项目的问题:<br />1 代码逻辑,下面是一段伪代码<br />	<pre name="code" class="java">/**
	 * 该方法上加事务,传播途径为required
	 * @param params
	 * @return
	 */
	public List&lt;object> getObject(Map&lt;String, String> params)	 {
		//先从memcached中取得数据
		List&lt;object> o1 = memcachedClient.getFromCache(params);
		
		
		if (o1 == null) {
			ol = ObjectDao.getfromDB(params);
			memcachedClient.putToCache(params, o1);
		}
		
		return o1;
	}</pre><br /><br />这段代码逻辑非常简单,先从memcached中取数据,取不到就从db取,然后再放到memcached中,无可挑剔(不过要注意我的注释,这个方法上加了required的事务)<br /><br />就是这段代码,放到tomcat中,我的同事william测出来的结果是,单线程请求(tomcat的thread pool中在同一时间只有一个处理请求的线程被调用):ab  -c 1 –n 1000 http://xxxx.xxx.xxx/xxx<br />结果是每秒中只能处理20个请求,  我的天啊, 20个,改多线程呢,: ab  -c 100 –n 1000 http://xxxx.xxx.xxx/xxx, 一百个线程请求1000次,晕倒,还是每秒只能处理20多个请求.<br /><br />估计有些人会觉得很奇怪,这么简单的逻辑怎么会这么慢,我但是也很奇怪,不过突然大脑中一个概念闪过”连接池”(难道这就是传说中的灵感),打开配置一看,果然,连接池配置的连接数只有20.<br /><br />“每秒钟处理20个请求,20个connection,加了事务”<br />“每秒钟处理20个请求,20个connection,加了事务”<br />“每秒钟处理20个请求,20个connection,加了事务”<br /><br />多想了两遍之后(快分裂了),答案出来了:<br /><br />在getObject方法上加上事务之后,所有的调用都会新建事务对象,然后放到当前线程中,而新建该事务对象的基础是connection,同时这个connection也会被保存在当前线程中,这样造成的结果是只有等到拥有connection的请求退出事务之后,connection才能重新回到线程池,换句话说,getObject方法是依赖于connection的,getObject能够被调用的次数取决于线程池中线程的数量<br /><br />	于是把线程池开到100,同样运行ab,结果果然好了很多,现在每秒能够处理的请求达到了120+,connection的数量变成原来的5倍,每秒处理的请求数也变成了原来的5倍.<br /><br />	看上去为这个get方法配置事务导致了该方法依赖于db connection是真正的原因,<br />从我们的逻辑上看该方法确实是不需要事务,但是由于我们的习惯,就顺利成章的给配置了一个,但是这个小小的配置确带来了巨大的影响.<br /><br />	不过有时候,我们以为找到了所有的问题,但是往往有更深层次的问题隐藏其中,比如说多次查询不加事务可能产生幻读的情况(不过如果你的应用对幻读的要求不高的话也没有什么问题).<br />	<br />	那么也许我们可以这样结论,只要查询的操作在高性能需求的场景下千万不要加事务,即使是readonly的也不行,其他场景加上吧,不加可能会有些问题,比如说前面提到的幻读(这次我的结论好像底气不足,因为我还没有对它有过特别彻底的研究).<br /><br />	结论下来了,我们看看如何优化,其实在我举的这个场景中,是既可以保证事务,又能提高效率的,就是缩小事务的粒度,如果我在ObjectDao.getfromDB(params);加上事务的话,依赖于connection的只有ObjectDao.getfromDB(params);这个方法了,而这个方法只有o1 == null的时候才会被调用,绝大多数情况下它是不会被调用的.这样既一定程度上保证了事务又提高的程序的速度.<br /><br />	那么我们还可以下一个结论,就是<span style="color: darkred">某些场景下缩小我们事务的粒度能够很大程度提高程序的性能</span>.<br /><br /><br /><br />注明：由于ahuaxuan水平有限，文中不妥之处还望不吝指正，谢谢。<br /><br /><br /><br /></span>
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/231670#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 22 Aug 2008 17:05:19 +0800</pubDate>
        <link>http://www.javaeye.com/topic/231670</link>
        <guid>http://www.javaeye.com/topic/231670</guid>
      </item>
      <item>
        <title>HDFS用户指南(翻译）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/228132" style="color:red;">http://www.javaeye.com/topic/228132</a>&nbsp;
          发表时间: 2008年08月14日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>
    
    
HDFS用户指南<br id="o3qg" />
原文地址：http://hadoop.apache.org/core/docs/current/hdfs_user_guide.html<br id="jh7z" />
译者：dennis zhuang(killme2008@gmail.com),有错误请指正，多谢。<br id="o3qg0" />
</p>
<h2 class="h3" id="o3qg1">目的<br id="o3qg2" />
</h2>
<div class="section" id="o3qg3">
<p id="o3qg4">本文档可以作为使用Hadoop分布式文件系统用户的起点，无论是将HDFS应用在一个Hadoop集群中还是作为一个单独的分布式文件系统使用。HDFS被设计成可以马上在许多环境中工作起来，那么一些HDFS的运行知识肯定能大大地帮助你对一个集群做配置改进和诊断。<br id="bz6u" />
 </p>
</div>
<p>
    
<a name="N1001B" id="o3qg6"></a>
<a name="Overview" id="o3qg7"></a>
</p>
<h2 class="h3" id="o3qg8">概览<br id="l_0z" />
  </h2>
<div class="section" id="o3qg9">
<p id="o3qg10">HDFS是Hadoop应用的主要分布式存储。一个HDFS集群由一个管理文件系统元数据的NameNode，和存储实际
数据的一些Datanode组成。HDFS的架构在这里有详细描述。这个用户指南主要提供给需要跟HDFS集群打交道的用户或者管理员。HDFS架构文章
中的图描绘了Namenode、Datanode和客户端们之间的基本交互。本质上，客户端与Namenode通讯获取或者修改文件的元数据，与
Datanode进行实际的IO操作。<br id="l_0z0" />
 </p>
<p id="l_0z1">下面的列表应该是大多数用户关心的HDFS突出特点。斜体字的术语将在后面详细描述。</p>
<p id="fvr_">1）Hadoop，包括HDFS，非常适合廉价机器上的分布式存储和分布式处理。它是容错的、可伸缩的，并且非常易于扩展。并且，以简单性和适用性著称的Map-Reduce是Hadoop不可或缺的组成部分。</p>
<p id="ag1q">2）HDFS的默认配置适合于大多数安装的应用。通常情况下，只有在一个非常大规模的集群上才需要修改默认配置。</p>
<p id="d26i">3）HDFS是用java编写的，支持大多数平台。</p>
<p id="queu">4）支持shell命令行风格的HDFS目录交互。</p>
<p id="ufzm">5）Namenode和Datanode都内建了web服务器，可以方便地查看集群的状态</p>
<p id="jafv">6）HDFS经常性地实现新的特性和改进，下面是HDFS中的一些有用特性的子集：</p>
<p id="c.lr">&nbsp;&nbsp; <em id="v3zz">文件许可和授权</em>
</p>
<p id="c.lr0">&nbsp;&nbsp; <em id="v3zz0">Rack awareness</em>
:当调度任务和分配存储的时候将节点的物理位置考虑进去。</p>
<p id="cbpn">&nbsp;&nbsp; <em id="v3zz1">Safemode(安全模式）</em>
：用于维护的一个管理状态</p>
<p id="tej-">&nbsp;&nbsp; <em id="v3zz2">fsck</em>
： 诊断文件系统的一个工具，用来查找丢失的文件或者block</p>
<p id="z_lu">&nbsp;&nbsp; <em id="v3zz3">Rebalancer</em>
:当数据在Datanode间没有均匀分布的时候，用于重新平衡集群的工具</p>
<p id="p1gp">&nbsp;&nbsp; <em id="v3zz4">升级和回滚</em>
：当Hadoop软件升级，在升级遇到不可预期的问题的时候，可以回滚到HDFS升级前的状态</p>
<p id="fzza">&nbsp;&nbsp; <em id="v3zz5">二级Namenode</em>
：帮助Namenode维持包含了HDFS修改的日志的文件（edits日志文件,下文谈到）大小在限制范围内。<br id="ubzs" />
</p>
</div>
<p> 
<a name="N10083" id="o3qg41"></a>
<a name="Pre-requisites" id="o3qg42"></a>
</p>
<h2 class="h3" id="o3qg43">前提条件<br id="px_d" />
  </h2>
<div class="section" id="o3qg44">
<p id="o3qg45">下面的文档描述了一个Hadoop集群的安装和设置：<br id="px_d0" />
</p>
<ul id="o3qg46">
<li id="o3qg47">
 		
<a href="http://hadoop.apache.org/core/docs/current/quickstart.html" id="o3qg48">Hadoop Quickstart</a>
，给初次使用用户 <br id="bb4w" />
</li>
<li id="o3qg49">
 		
<a href="http://hadoop.apache.org/core/docs/current/cluster_setup.html" id="o3qg50">Hadoop Cluster Setup</a>
 大规模、分布式集群<br id="p8d9" />
</li>
</ul>
<p id="o3qg51"><br id="tegj" />
</p>
<p id="tegj0">本文档的剩余部分假设你已经搭设并运行了一个至少拥有一个Datanode的HDFS。基于本文档的目的，Namenode和Datanode可以运行在同一台机器上。<br id="tegj1" />
</p>
</div>
<p> 
<a name="N100A1" id="o3qg52"></a>
<a name="Web+Interface" id="o3qg53"></a>
</p>
<h2 class="h3" id="o3qg54"> Web接口 </h2>
<div class="section" id="o3qg55">
<p id="o3qg56">Namenode和Datanode分别跑了一个内置的web服务器，来展现集群当前状态的一些基本信息。在默认配置
下，Namenode的首页地址是http://namenode:50070（namenode就是Namenode节点所在机器IP或者名称）。这个
页面列出了集群中的所有datanode以及集群的基本统计。web接口同样可以用于浏览文件系统（点击Namenode首页上的&ldquo;Browse
the file system&quot;链接）。<br id="lb5-" />
 	</p>
</div>
<p> 
<a name="N100AE" id="o3qg58"></a>
<a name="Shell+Commands" id="o3qg59"></a>
</p>
<h2 class="h3" id="o3qg60">Shell命令<br id="wep4" />
</h2>
<div class="section" id="o3qg61">
<p id="o3qg62">Hadoop包括了多种shell风格的命令，用于跟HDFS或者Hadoop支持的其他文件系统交互。命令
bin/hadoop fs -help 可以列出Hadoop shell支持的命令。更进一步，bin/hadoop fs -help
command
可以展现特定命令command的帮助细节。这些命令支持一般文件系统的操作，例如拷贝文件、修改文件权限等。同时也支持了部分HDFS特有的命令，例如
修改文件的replication因子。<br id="wep40" />
      </p>
<a name="N100BD" id="o3qg65"></a>
<a name="DFSAdmin+Command" id="o3qg66"></a>
<h3 class="h4" id="o3qg67"> DFSAdmin命令 </h3>
<p id="o3qg68">
   	
<span class="codefrag" id="o3qg69">'bin/hadoop dfsadmin'</span>
命令支持一些HDFS管理功能的操作。'bin/hadoop dfsadmin -help'可以列出所有当前支持的命令。例如：</p>
<ul id="o3qg71">
<li id="o3qg72">
   	    
<span class="codefrag" id="o3qg73">-report</span>
   	    : 报告HDFS的基本统计信息。部分信息同时展现在Namenode的web首页上。&nbsp;
   	</li>
<li id="o3qg74">
   		
<span class="codefrag" id="o3qg75">-safemode</span>
   		: 尽管通常并不需要，管理员还是可以通过手工操作进入或者离开safemode状态
   	</li>
<li id="o3qg77">
   		
<span class="codefrag" id="o3qg78">-finalizeUpgrade</span>
   		: 移除上一次升级时集群所做的备份。
   	</li>
</ul>
</div>
<p> 
<a name="N100E6" id="o3qg79"></a>
<a name="Secondary+Namenode" id="o3qg80"></a>
</p>
<h2 class="h3" id="o3qg81"> 二级Namenode </h2>
<div class="section" id="o3qg82">
<p id="o3qg83">Namenode将对文件系统的修改存储在一个原生文件系统文件中（名为edits的文件）。当Namenode启动的时
候，它从映像文件（fsimage)读取HDFS的状态，然后将edits日志文件中的修改作用在此内存状态上，接着将得到的新的HDFS状态写回
fsimage，后续的正常操作开始于一个空的edits日志文件。由于Namenode仅仅在启动的时候将fsimage和edits合并，因此在一个
大的集群上经过一定时间操作后，edits文件将会非常大。由此带来的一个副作用就是下次Namenode的重新启动将花费很长时间。二级
Namenode就是为了解决这个问题，它会周期性地合并fsimage和edits日志文件，并且将edits日志文件的大小保持在限制范围内。通常它
会跑在另一个机器上，因为它的内存要求跟主namenode一样。二级Namenode可以通过'bin/start-dfs.sh'启动在conf
/masters配置文件里配置的节点上。<br id="s4h2" />
     </p>
</div>
<p> 
<a name="N1010B" id="o3qg93"></a>
<a name="Rebalancer" id="o3qg94"></a>
</p>
<h2 class="h3" id="o3qg95"> Rebalancer </h2>
<div class="section" id="o3qg96">
<p id="o3qg97">HDFS的数据可能不会总是在Datanode之间分布得很一致。一个常见的原因是往现有的集群中加入了新的Datanode。当分配block的时候，Namenode依据几个参数来决定哪个datanode来接受这些block。一些需要考虑的因素如下：</p>
<p id="i-5q">1）一个block的副本存放在正在写该block的节点上</p>
<p id="fj63">2）需要将一个block的副本扩展到其他机架上，防止因为整个机架故障导致的数据丢失。</p>
<p id="ik23">3）副本之一通常放在同一个机架的另一个节点上，减少跨机架的网络IO</p>
<p id="dswx">4）将HDFS数据均匀一致地分布在集群中的datanode上。</p>
<p id="dswx0">&nbsp;&nbsp;&nbsp; 基于这些相互竞争的因素，数据可能不会在Datanode之间扩展得一致。HDFS给管理员提供了一个工具，用来分析block的分配情况和在datanode之间重新平衡数据。这个功能暂未实现，它的描述可以在这个&nbsp;<a href="http://issues.apache.org/jira/secure/attachment/12368261/RebalanceDesign6.pdf" id="o3qg105">PDF</a>
文档中看到，记录编号<a href="http://issues.apache.org/jira/browse/HADOOP-1652" id="o3qg106">HADOOP-1652</a>
.</p>
</div>
<p> 
<a name="N10132" id="o3qg107"></a>
<a name="Rack+Awareness" id="o3qg108"></a>
</p>
<h2 class="h3" id="o3qg109"> Rack Awareness </h2>
<div class="section" id="o3qg110">
<p id="o3qg111">典型的大规模Hadoop集群是部署在数个机架上的，那么显然同一个机架内的节点间的网络通讯比之不同机架间节点间的网
络通讯更可取。另外，Namenode会尝试将block的副本分布在数个机架中以提高容错性。Hadoop让集群管理员来决定某个节点从属于哪个机架，
通过配置变量dfs.network.script来实现。当这个脚本有配置的时候，每个节点都运行该脚本来决定它的rackid。默认安装假设所有的节
点从属于同一个机架。这个特性和配置进一步的阐述在这个<a href="http://issues.apache.org/jira/secure/attachment/12345251/Rack_aware_HDFS_proposal.pdf" id="o3qg116">PDF</a>
文档，编号为 
      <a href="http://issues.apache.org/jira/browse/HADOOP-692" id="o3qg117">HADOOP-692</a>
。
      </p>
</div>
<p> 
<a name="N10150" id="o3qg118"></a>
<a name="Safemode" id="o3qg119"></a>
</p>
<h2 class="h3" id="o3qg120"> Safemod(安全模式） </h2>
<div class="section" id="o3qg121">
<p id="o3qg122">当Namenode启动的时候，它从fsimage和edits日志两个文件中加载文件系统的状态。然后等待
datanode报告他们的block信息，以便防止Namenode在确认block副本是否足够前过早地开始复制block。这段时间的
Namenode就是处于所谓safemode状态。处于safemode的Namenode也是HDFS集群的只读模型，此时不允许任何对文件系统或者
block的修改。正常情况下，Namenode会在开始后自动退出safemode。如果有需要，HDFS可以通过'bin/hadoop
dfsadmin
-safemode'命令显式地进入safemode状态。Namenode的web首页显示当前的safemode是否打开。更详细的描述和配置可以参
考<a href="http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/dfs/NameNode.html#setSafeMode%28org.apache.hadoop.dfs.FSConstants.SafeModeAction%29" id="o3qg128"><span class="codefrag" id="o3qg129">setSafeMode()</span>
</a>
方法的JavaDoc。</p>
<p id="y.-h">译
注：详细介绍下safemode的配置参数，在safemode状态，Namenode会等待所有的datanode报告他们自己的block信息，看看
所有的block的副本是否达到最低要求的数目，这个数目可以通过dfs.replication.min参数配置，默认是1,也就是至少要求有一个副
本。当报告合格的Datanode的数目达到一定百分比，Namenode才会离开safemode状态。这个百分比也是可配置的，通过
dfs.safemode.<tt id="v23v">threshold.pct参数，默认是0.999f(也就是要求99.9%的Datanode
合格）。Namenode在合格的datanode数目达到要求的时候，并不是马上离开safemode状态，会有一个扩展时间，让剩余的
datanode来报告block信息，这个扩展时间默认是30秒，可以通过</tt>
<tt id="zwjf">dfs.safemode.extension参数配置，单位是毫秒。</tt>
      </p>
</div>
<p> 
<a name="N1016E" id="o3qg130"></a>
<a name="Fsck" id="o3qg131"></a>
</p>
<h2 class="h3" id="o3qg132"> Fsck </h2>
<div class="section" id="o3qg133">
<p id="o3qg134">HDFS提供了fsck命令用来检测各种各样的不一致性。fsck被设计用来报告各种文件的问题，例如某个文件丢失的
block，block的副本数目是否低于设置等。不同于传统的一般原生文件系统的fsck命令，hdfs的fsck命令并不修正所检测到的错误。通常情
况下，Namenode会自动修正大多数可以被修复的错误，HDFS的fsck不是Hadoop shel的命令，可以通过'bin/hadoop
fsck'执行，可以运行在整个文件系统上或者一个文件子集上。<br id="as3m" />
    
      </p>
</div>
<p> 
<a name="N1017E" id="o3qg137"></a>
<a name="Upgrade+and+Rollback" id="o3qg138"></a>
</p>
<h2 class="h3" id="o3qg139"> 升级和回滚 </h2>
<div class="section" id="o3qg140">
<p id="o3qg141">当升级某个集群的Hadoop的时候，正如任何软件的升级一样，可能会引入新的bug或者不兼容的修改导致现有的应用出
现过去没有发现的问题。在所有重要的HDFS安装应用中，是不允许出现因丢失任何数据需要从零开始重启HDFS的情况。HDFS允许管理员恢复到
Hadoop的早期版本，并且将集群的状态回滚到升级前。HDFS的升级细节请参考 <a href="http://wiki.apache.org/hadoop/Hadoop%20Upgrade" id="o3qg143">upgrade wiki</a>
。HDFS在任何时间只能有一个备份，因此在升级前，管理员需要通过'bin/hadoop dfsadmin -finalizeUpgrade'命令移除现有的备份。下面简要描述了典型的升级过程：</p>
<p id="ctbn">1）在升级Hadoop前，如果已经存在备份，需要先结束（finalize)它。可以通过'dfsadmin -upgradeProgress status'命令查询集群是否需要执行finalize</p>
<p id="kpd3">2)停止集群，分发部署新版本的Hadoop</p>
<p id="ur8l">3）执行新版本的hadoop，通过添加 -upgrade 选项，例如/bin/start-dfs.sh -upgrade</p>
<p id="gmxl">4)大多数情况下，集群在升级后可以正常运行。一旦新的HDFS在运行若干天的操作后没有出现问题，那么就可以结束(finalize)这次升级。请注意，在升级前删除的文件并不释放在datanode上的实际磁盘空间,直到集群被结束（finalize)升级前。</p>
<p id="xsll">5）如果有需要回到老版本的Hadoop，那么可以：</p>
<p id="xsll0">&nbsp;&nbsp; a)停止集群，分发部署老版本的Hadoop</p>
<p id="xsll1">&nbsp;&nbsp; b)通过rollback选项启动集群，例如bin/start-dfs.sh -rollback<br id="ctbn0" />
      </p>
<a name="N101BF" id="o3qg160"></a>
<a name="File+Permissions+and+Security" id="o3qg161"></a>
</div>
<h2 class="h3" id="o3qg162"> 文件许可和安全</h2>
<div class="section" id="o3qg163">
<p id="o3qg164">文件许可的设计与其他平台(如linux)
的文件系统类似。在当前实现，安全被限制在简单的文件许可上。启动Namenode的用户被作为HDFS的超级用户。HDFS的未来版本将支持网络验证，
例如Kerberos方案（译注：MIT开发的一个验证系统）的用户验证以及数据传输的加密。更详细的讨论参考<a href="http://hadoop.apache.org/core/docs/current/hdfs_permissions_guide.html" id="o3qg166"><em id="o3qg167">Permissions User and Administrator Guide</em>
</a>
。           
      </p>
</div>
<p> 
<a name="N101D1" id="o3qg168"></a>
<a name="Scalability" id="o3qg169"></a>
</p>
<h2 class="h3" id="o3qg170">伸缩性<br id="m-bp" />
  </h2>
<div class="section" id="o3qg171">
<p id="o3qg172">Hadoop正运行在成千上万个节点的集群上。      <a href="http://wiki.apache.org/hadoop/PoweredBy" id="o3qg173">PoweredBy Hadoop</a>
列
出了一些部署Hadoop在大规模集群上的组织和机构。HDFS在每个集群上只有一个Namenode节点，Namenode节点上可用内存是当前伸缩性
的主要限制。在非常大规模的集群上，增加HDFS中存储的文件的平均大小，将可以帮助提高集群的大小而不用增加Namenode的内存需求。默认的配置可
能不适合非常大规模的集群应用。<a href="http://wiki.apache.org/hadoop/FAQ" id="o3qg174">Hadoop FAQ</a>
页列出了对于大规模Hadoop集群的配置改进建议。
      </p>
</div>
<p> 
<a name="N101E3" id="o3qg175"></a>
<a name="Related+Documentation" id="o3qg176"></a>
</p>
<h2 class="h3" id="o3qg177">关联文档<br id="n8q_" />
 </h2>
<div class="section" id="o3qg178">
<p id="o3qg179">&nbsp;本用户指南可作为使用HDFS很好的一个起点，在本文档持续改进的同时，有一些非常有价值的关于Hadoop和HDFS的文档资料可供参考。下列资料可作为进一步探索的起点：<br id="ugay" />
</p>
<ul id="o3qg180">
<li id="o3qg181">
        
<a href="http://hadoop.apache.org/" id="o3qg182">Hadoop Home Page</a>
        : Hadoop一切的起始页。
      </li>
<li id="o3qg183">
        
<a href="http://wiki.apache.org/hadoop/FrontPage" id="o3qg184">Hadoop Wiki</a>
        :由社区维护的wiki文档。</li>
<li id="o3qg185"> 
<a href="http://wiki.apache.org/hadoop/FAQ" id="o3qg186">FAQ</a>
 from Hadoop Wiki.
      </li>
<li id="o3qg187">
        Hadoop <a href="http://hadoop.apache.org/core/docs/current/api/" id="o3qg188">
          JavaDoc API</a>
.
      </li>
<li id="o3qg189">
        Hadoop User Mailing List : 
        <a href="mailto:core-user@hadoop.apache.org" id="o3qg190">core-user[at]hadoop.apache.org</a>
.
      </li>
<li id="o3qg191">
         浏览<span class="codefrag" id="o3qg192">conf/hadoop-default.xml</span>
文件，它包括了当前可用配置变量的概括介绍。
      </li>
</ul>
</div>
<p><br id="o3qg193" />
</p>
          <br/>
          <span style="color:red;">
            <a href="http://raylinn.javaeye.com/topic/228132#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 14 Aug 2008 20:27:56 +0800</pubDate>
        <link>http://www.javaeye.com/topic/228132</link>
        <guid>http://www.javaeye.com/topic/228132</guid>
      </item>
      <item>
        <title>结合Maven2进行J2EE项目构建</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://orpheus.javaeye.com">orpheus</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/230265" style="color:red;">http://www.javaeye.com/topic/230265</a>&nbsp;
          发表时间: 2008年08月19日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <strong>一.背景</strong><br />Maven2 的基本原理很简单，采用远程仓库和本地仓库以及  pom（project object model）.xml  ，将  pom.xml  中定义的  jar  文件从远程仓库下载到本地仓库，各个应用使用同一个本地仓库的  jar  ，同一个版本的  jar  只需下载一次，而且避免每个应用都去拷贝  jar  。如图  1  。同时它采用了现在流行的插件体系架构，只保留最小的核心，其余功能都通过插件的形式提供，所以在执行  maven  任务时，才会自动下载需要的插件。这个特性也为客户系统的升级带来的很大的方便，客户每次升级的时候可以使用maven的远程部署功能自动下载最新的系统组件（jar），并重新打包部署，很大程度的减少的系统升级的工作量。<br />理解Maven的原理，可以参考 Pear ――ＰＨＰ扩展与应用库（ the PHP Extension and Application Repository ），其原理非常类似，都有一个官方库，都是微内核，通过网络将需要的文件下载到本地，通过官方仓库将相应的类库进行统一管理。<br />     Maven2的基本安装方法网上很多，就到<a href="http://maven.apache.org" target="_blank">http://maven.apache.org</a>下载一个最新版，解压后即可，如果需要在命令行运行，还需要设置一些环境变量，网上的资料很多，这里就不多说了。总之，安装成功后当你在命令行下执行maven -version后正确显示当前maven的版本即可。<br />     我们在项目中结合maven的进行开发的主要思路：<br />   1.建立支持Maven2的开发框架，框架中结合了一些项目功能和工具类，并且此框架本身是一个eclipse工程，支持使用eclipse IDE的开发，并通过CVS可进行团队协作。<br />   2.在Maven2的pom.xml中制定开发框架的依赖包，并建立依赖包的团队管理本地服务器，使团队中的包依赖得到统一管理。<br />   3.每日下班后，在构建服务器上每日从cvs上下载各个团队开发人员的代码，统一进行集成构建和测试。由于是每日构建，所以发现的bug可及时反馈给开发人员进行修正，避免了一般开发过程中的bug长时间遗留的情况。<br /><br /><strong>二.实施过程</strong><br /><br />为了实现上述思路，我们分几步实施：<br /><br /><strong>1.首先需要构建一个系统的开发框架</strong>，<br />    我们有两种方式构建，<br />    其一是从零开始构建全新的框架，进入commond line，cd 到一个目录 ，执行<br /> <br /> <br /><pre name="code" class="java">mvn archetype:create -DgroupId=com.mycompany.app -DartifactId=my-webapp -DarchetypeArtifactId=maven-archetype-webapp</pre><br /><br />执行完毕后接下来cd 到项目目录my-webapp下，执行<br /><pre name="code" class="java">mvn package
mvn eclipse:eclipse
</pre><br /><br />之后，打开eclipse，到其目录下导入项目，并手动编辑pom.xml文件，设定指定的jar包，比如加入一个jwebunit的jar包，我们需要在pom中添加一段：<br /><pre name="code" class="java">&lt;dependency>
            &lt;groupId>jwebunit&lt;/groupId>
            &lt;artifactId>jwebunit&lt;/artifactId>
            &lt;version>1.2&lt;/version>
            &lt;scope>test&lt;/scope>
            &lt;exclusions>
                &lt;exclusion>
                    &lt;groupId>rhino&lt;/groupId>
                    &lt;artifactId>js&lt;/artifactId>
                &lt;/exclusion>
            &lt;/exclusions>
        &lt;/dependency></pre><br /><br /><br />其中指定了包的名称，版本，使用的范围域等，pom.xml设置方式网上也是一堆一堆的，具体的可以自己搜搜。同时我们也可以使用maven2在 eclipse中的插件进行编辑，很方便，就不用记住那些该死的标签了。插件下载地址 <a href="http://m2eclipse.codehaus.org" target="_blank">http://m2eclipse.codehaus.org</a> /update，将这个url填入到eclipse的Help-》Software Updates->find&install中新建一个插件下载地址的对话框中即可下载。<br />这种方式是完全自定义一个全新的工程后再进行框架搭建，比较累，尤其是添加依赖包的时候，需要根据自己的项目需要一个一个添加，很烦人，所以我们使用的第二个方法就直接找了一个现成的，到 Appfus 的网站<a href="http://appfuse.org/" target="_blank">http://appfuse.org/</a> 根据项目需要下载了一个项目框架作为原型，我们使用的是appfuse-light-webwork-ibatis- 1.8.2（webwork2.26,spring2.0,ibatis2.0），如果你使用的是其他的的技术，如 struts2，hibernate....直接到网站上下载一个相应的框架即可。appfuse框架使用maven2作为基本构建工具，其中自带的 pom.xml也替开发人员写好了，中所定义的依赖包可满足一般的开发需要，如需要自己指定的包，那么直接在其pom.xml中添加即可。要将这个框架作为eclipse工程使用，需要在解压后的框架目录下执行：<br /><br /><pre name="code" class="java">mvn eclipse:eclipse -DdownloadSources=true</pre><br /><br /><br />这个命令会将工程将框架转换为eclipse工程，并从远程下载jar包到本地仓库（window下是(C:\Documents and Settings\${username}\.m2\repository），之后执行：<br /><br /><pre name="code" class="java">mvn -Declipse.workspace=&lt;path-to-eclipse-workspace> eclipse:add-maven-repo</pre><br /><br /><br />其中path-to-eclipse-workspace是本机的eclipse的worksapce的路径。执行后maven会