存档

‘软件设计’ 分类的存档

疯狂的sql查询,随时可能引发性能问题

2010年1月26日 admin 没有评论

今天review了群发sms的一段代码,差点没吓死我。 代码如下

for (MtnPlanTosendlist mtnPlanTosendlist:tempList){
           int memberSendTodayCount = getToDayLimit(defaultBo,getMemberSentDal(mtnPlanTosendlist));
           int memberSendMonthCount = getMonthLimit(defaultBo,getMemberSentDal(mtnPlanTosendlist));
           if ((memberSendTodayCount < smsTodayLismitMember) && memberSendMonthCount < smsMonthLimitMember){
              insertList.add(mtnPlanTosendlist);
           }
}

public static int getToDayLimit(BaseBo defaultBo, MtnSmsLog mtnSmsLog) {
       int count = (Integer) defaultBo.get(mtnSmsLog, COUNT_TODAY_EXT)
              .getDefaultModel();
       return count;
}

public static int getMonthLimit(BaseBo defaultBo, MtnSmsLog mtnSmsLog) {
       int count = (Integer) defaultBo.get(mtnSmsLog, COUNT_MONTH_EXT)
              .getDefaultModel();
       return count;
}

这段代码看的出问题吗? For 循环类名套了2个count 查询。如果前面的那个for循环有100个的话, 执行完这个代码就需要200行的sql(远程调用),悲惨呀!

开发人员切莫要把数据库操作当做local方法一样的使用。一则,db操作本质上也是远程访问,远程调用很浪费时间,特别有大量的远程访问的时候;二则,数据库资源有限,大量的数据库访问会导致数据库load的升高,从而影响程序的性能。所以。劲量要减少对数据库的访问,例如通过batch进行操作,或者batch的load到内存中,进行处理。另外,如果count的数量数据行太多的话就需要考虑中间结果,例如做一张中间表来记录当前月、当天的数量,这样也可以有效的减少数据库的访问。

分类: 软件设计 标签:

对产品化和平台化的思考

2010年1月17日 admin 1 条评论

2009年4月加入公司以后做的第一个项目是给公司的事业部A做个类似Google Calendar的时间、任务管理平台,使事业部A能够在这个平台上开展一项具体的业务,并加以管理和统计。所以公共Calendar服务差别最大的一点是我们的Calendar需要实现对事业部A或者未来其他事业部的“应需而变”。当时做第一版设计的时候就提出了“抽象出一个通用的任务制定、整理、展现的平台。这个平台可以方面的加以扩展以满足多种业务对任务管理的需求”这样一个目标,可在经过之后的若干次的业务和功能的扩展以后,我发现这个产品离产品化和平台化越来越远,直到最后成为了事业部A的专属产品。最近的一段时间内对这个产品如果实现产品化和平台化做了进一步的思考,这篇文章就是这些思考的汇总。

2009年4月的第一版设计——基础设计的失败

当时在设计的时候,有两个设计重点。其中一个是“任务”这个概念,当时主要的精力放在了对任务的抽象上,所以针对任务这个对象设计了开始时间、结束时间、重要度、标题,还包括了任务的创建、完成、推迟等行为。当时考虑如果通用的任务概念无法满足某个事业部的需求时就为这个事业部定制“任务”这个概念,这种设计的具体表现体现在当时设计了一张Task表并预留了许多字段,并且自欺欺人地认为这些字段能够支持未来业务平台的扩展。

剩下的另一个设计重点是如何为这个业务部门定制Calendar的展现样式。业务部门的需求是要根据这个事业部的需求定制一个Calendar的每日单元格。当时对Calendar控件提出了“能够支持日历、月历、周历等多种操作模式的变换以取得类似Outlook中日历功能的效果”,“Calendar容器能够支持多种Widget的Plug in。有需要使用Calendar功能的业务可以根据自身业务需求定制Widget,并能方便地跟Calendar容器整合在一起。”。Calendar控件在之后得到了基本的实现,但应需而变的Widget方案并没有能够实现。

当时就是带着这两个巨大的问号,我们完成了这个功能的第一版。

2009年12月的第三版功能升级——平台化的终结

2009年12月还是这个事业部提出了很多“管理”方面的需求,这些需求使得这个事业部成为了这个产品的“惟一”主人。

2010年1月——新的思考

对于一个大型的企业应用需求往往总是来自于某个较小单位,并逐步的扩展到更大的范围,而另一方面软件设计往往力求能最大程度的支持未来业务的扩展,也就是想实现产品化、平台化的目标,当有新的需求需要实现的时候,能够付出最小的成本。这两者之间某种程度上存在着很大的矛盾,要解决这种矛盾,我目前的思考是要依赖以下几点内容

  • 分析需求,识别出需求涉及的领域。如果没有对需求进行领域的划分,或者划分是错误的,结果只有一个——产品化和平台化的终结。
  • 在领域划分的基础上,规划每个领域的基础、高阶服务。每个领域可能会有若干个层次的服务:基础、高阶。以Calendar领域为例,基础服务可能就有任务的管理(创建、删除),以日历、月历、周历等多种操作模式的变换。高阶的服务可能包括时间冲突的提醒,任务的提醒等等。
  • 将具体的需求通过,某个领域的服务,或者多个领域的服务组合来加以实现。组合式的服务基于几个领域的基础服务构成一个新的服务。每个领域的基础服务要力求“最强大化”,否则整个平台的扩展就会受到很大的限制。

image

还是以那个事业部A的需求为例。

  • 我们识别出需求涉及Calendar和A事业部两个领域。
  • Calendar领域提供的基础服务包括任务、时间的管理、Calendar页面的展现,高阶服务包括任务提醒等等。A事业部领域包括了具体任务的制定业务、任务执行的监督、任务执行状态在Calendar页面的展现、任务执行的具体内容和后续跟踪等等。
  • 通过将Calendar领域的任务管理服务和A事业部领域的具体任务制定服务组合在一起实现A事业部任务安排的需求。将Calendar领域的任务管理服务和A事业部领域的结果反馈服务组合在一起完成A事业部的结果反馈需求。
  • 当后续B事业部想要在Calendar平台上开展自己的业务的时候,由于Calendar领域的服务没有夹杂任何其他领域的内容并且实现了功能的最大化能够很有效的被B事业部加以重用。

 

总结

  • 分析需求,识别出需求涉及的领域。对领域间的隔离态度上是“决不妥协的”。
  • 每个领域的服务要追求最大化、最强大化。
  • 具体的需求通过,某个领域的服务,或者多个领域的服务组合来加以实现。
分类: 软件设计 标签:

SOA实践指南读书笔记

2010年1月16日 admin 没有评论

最近读完了《SOA实践指南——分布式系统设计的艺术 》这本书,以下是这次的读书笔记。

什么是SOA  

在计算科学中,术语“面向服务的架构”(SOA)表达了一种软件架构的概念,它定义为使用服务来满足满足软件用户的需求。在SOA环境中,网络上的节点以独立服务的形式将自己的资源开发给网络上其他参与者,其他参与者按一种标准的方式使用资源。大多数对于SOA的定义都提到了实现SOA时使用Web Services(即使用SOAP或REST)。然而,可以使用任何基于服务的技术实现SOA。

……

与传统点对点架构不同,各种SOA都由松耦合、高度可以互操作的应用服务构成。这些服务基于某种格式定义进行互操作,该定义独立于底层平台和编程语言(例如,WSDL)。接口定义封装(隐藏)了供应商和语言相关的实现。SOA独立于开发技术(比如JAVA和.NET)。由此,软件组件将具备非常高的重用性,因为接口按与标准兼容方式定义。

基于SOA的逻辑架构模型

image

无状态服务为什么更好

    首先,对于无状态服务来说,在服务层做到负载均衡和失效备援相当简单。ESB仅需实现能使用下一个最容易获得的服务实例即可。它甚至可以是其他消费进程以前使用过的线程。这意味着如果一个系统失败了,其他系统可以轻易地接手,继续工作。如果服务层的通量不够高,你可以将服务实例的数量翻倍,从而是通量翻倍。这意味着解决方案能线性伸缩,并且,如果一个服务实例意外死亡,其他服务实例能投入进来。

而有状态服务的缺点是,它们绑定在会话上。这样一来,知道消费者会话结束(或者连接失效)之前,为它们分配的资源都绑定在一个特定的消费会话上。除此以外,你必须能让ESB把连续的服务调用路由到同样的服务实例上。

然而,要注意的是,有状态服务仍然可以线性伸缩。使用一种“粘性”路由的政策,根据可用的资源,你在第一个服务调用被执行时找到你的服务实例。这一来,对服务实例数量的翻倍就又能将可能的会话数量翻倍了。然而,此时不具备服务资源可以在不同消费者之间共享的优点。并且,如果一个服务实例意外死亡,会话状态就丢失了。

同时也要注意,有状态服务可以有失效备援机制(并且仍然可以线性伸缩)。此时,每个服务器不但返回它自己的会话ID,还返回失效备援会话的ID。只要这个失效备援会话不在一个中央服务器上,而是在邻近的服务器上(并且,每个服务器的邻近服务器各不相同),这样就不会造成瓶颈。

等幂性

等幂性是服务的一种可能的属性。在服务的上下文环境中,它意味着对同一服务调用的多次递交/处理不会引发问题。假设你有一个向银行中增加钱的服务。如果一个消费者调用这个服务(例如,为了处理银行转账)并且没有得到应答,消费者不会知道服务的请求还是服务的应答失败了。例如下图中,服务可能在请求处理之前或之后丢失。

image

在后一种情况下,从供应者的角度来看,服务成功了,指定数量的钱被增加到了银行账户上。在前一种情况下,服务没有影响(即没有增加钱)。

当服务的消费者没有得到应答时,它不知道服务是否成功了。为求确保,它可能重试服务调用,再次发出同样的请求。结果是,达到供应者的服务请求可能有一次或两次。如果服务是幂等的,那么,服务请求到达多少次都没有关系:同一服务调用的多次请求只产生一次的效果。

刚刚描述的银行服务不是幂等的,因为,它依赖于第一次服务调用失败发生在何处,客户的余额可能最终被增加了两次,而不是消费者想要的一次。为了避免这样的问题,通常说来,只要有可能就是服务幂等是个好主意。

所有的读取服务都是幂等的,因为一个服务供应者无论多频繁地执行返回数据的请求都没有关系。然而,要注意,一旦做了类似每个请求都写一个备忘录项这种事,读取服务就变成了写入服务。如果这个备忘录和业务有某种关联,这就可能成为问题(例如,对同一样的事项,最终可能有多个备忘录项)。

写入服务可能是幂等的,也可能不是。当一个服务调用的影响依赖于后端现有的状态时,写入服务不是幂等的。举个例子,在前面的例子中,银行账户的最终余额依赖与最初的余额。如果开始的余额是500美元,服务调用增加100美元,结果将是600美元。如果因为最初的应答丢失了,另一个请求到达并且得到处理,最后的余额将是700美元。

一个幂等的写入服务的例子是,一个通过发送所有地址来设定客户地址的服务。供应者处理多少次请求都没有关系,结果将是一样的。

注意:你总是可以避免让服务不幂等。例如,我们可以修改银行服务语义,发送新值而不是应该增加的值,从而使服务幂等。这就是说,不是调用:

addToBalance(100);

我们可以调用:

setBalance(600);

这个调用可以反复地发送和处理,余额的结果都会是600。当然,其他服务也能在相同的账户中增加或减少资金,这就带来了问题。这就带来了问题。你如何才能知道最终的余额应该是多少呢?正如你看到的,从业务的角度来看,让服务幂等可能变得复杂。

除此以外,从业务角度来看,有的服务内生就是不幂等的。所有创建某些东西的服务都没有办法和一个状态去比较(除非你知道创建新资源的工厂的状态)。例如,如果你有一个创建银行账户的服务,你就是在创建账户,仅此而已。

如果从业务的角度来看,引入幂等性是个难题的话,那么,你需要一些对幂等性的技术支持。实现幂等性的普通方法相当简单,消费者发送数据的方式必须能让供应者看的出两个技术性的请求其实是同一个。为此,消费者可以随着每次新请求发送一个唯一的ID。如果消费者没有得到应答,它的每次重试都是同样的ID。下图是对此的图解。

image

在处理每个进入的请求前,供应者应将请求及相关联的唯一ID存储到一个中央数据库中。如果供应者收到一个ID已经在存储库中的请求,它就知道该请求已经被处理过。于是,供应者就不会再处理它,而是向消费者重新发送最初的应答(应答在第一次被送出去之前,也保存下来)。如果第二次请求到达是,服务实现正处于运行中,则供应者可以回复一个应答,说服务调用在运行中,或者供应者可以等到第二次请求处理完毕以后,再次把应答发送出去。

作为一项优化,消费者能和请求一起发送一个标志,这样供应者就知道此请求是否是一个重试。这样能做到改善性能,因为如果调用没有被标记为重试的华话,供应者就不必验证请求是否已经在存储库中了。

分类: 软件设计, 阅读 标签:

Code Review的一点感悟

2010年1月10日 admin 1 条评论

这两天review同学的代码,除了代码本身的质量(注释、命名、函数过长等等)以外,还能感觉到代码中那种heavy(沉重)的感觉,于是用借着这篇博文把这些感觉整理出来。

要索取资源而不是自己去寻找资源(也就是依赖注入笛米特法)。

例如我们的代码有一个文件的下载器要实现,文件下载器除了要处理要知道一个下载文件的源路径以外,要需要两个参数,其中一个是下载完成以后将文件保存到的目录,还有一个是下载失败的重试次数。在同学的实现中是由下载器这个类实现这两个参数的获取逻辑。代码看到这里就就让我对文件下载器的实现有一种沉重的感觉,这种感觉就是这两个参数的获取逻辑对整个文件下载器可重用性的破坏。这种实现就会导致整个文件文件下载器只能将文件输出到某个特定的目录,导致这个文件下载器对这个特定配置(不管配置数据是保存数据库中还是配置文件)的依赖,也就是说下次要重用这个文件下载器(例如文件下载器的使用场景发生变化,或是有新的场景要对文件进行下载)还必需要提供给这配置项保存的资源,如果是配置项是保存到数据库中的,就必须要移植相应的表结构和DAO访问层。这种实现会让当前文件下载器被重用的几率只有“零”。所以在文件下载器的设计上要贯彻“索取资源而不是自己去寻找资源(也就是依赖注入笛米特法)的原则”,也就是让文件下载器的消费者在构造文件下载器的时候提供文件下载器所需依赖的参数,这样让文件下载器具备100%的可重用性。至于说文件下载器的调用方本身就是负责某个具体业务的实现,本身的重用需求就比较小,依赖特定的上下文也是合理的。

分类: 软件设计 标签:

转载:笛米特法则详解(the Law of Demeter or Principle of Least Knowledge)

2010年1月10日 admin 没有评论

原文地址:笛米特法则详解(the Law of Demeter or Principle of Least Knowledge)

The Law of Demeter和 Principle of Least Knowledge讲的都是一回事,是说一个软件实体要尽可能的只与和它最近的实体进行通讯。通常被表述为:talk only to your immediate friends ( 只和离你最近的朋友进行交互)。  “talk”其实就是对象间方法的调用。这条规则表明了对象间方法调用的原则:

(1) 调用对象本身的方法;

(2) 调用通过参数传入的对象的方法;

(3) 在方法中创建的对象的方法;

(4) 所包含对象的方法。

上面的4点看起来有点别扭,下面通过一个具体的例子,就可以对上述4条guideline有进一步感性的认识:

 demeter_code

下面对start()方法中的语句进行分析:

第10行-key.turns():符合上述的第(2)条,key对象是通过参数传入start()方法的。

第13行-engine.start():符合上述的第(4)条,engine对象是包含在Car的对象之中的。

第14行-UpdateDashboardDisplay():符合上述的第(1)条,UpdateDashboardDisplay()方法是Car对像自身的方法。

第15行-doors.lock():符合上述的第(3)条,doors对象是在start()方法中创建的对象。

接下来看一个违反Principle of Least Knowledge的例子:

1 public float getTemp() {
2   Thermometer thermometer = station.getThermometer();
3   return thermometer.getTemperature();
4 }

上面的方法中station对象是immediate friends。但是上面的代码却从station对象中返回了一个Thermometer对象,然后调用了thermometer对象的getTemperature()方法,违反了Principle of Least Knowledge

下面对上面的方法作出符合Principle of Least Knowledge的改进:

1 public float getTemp() {
2  return station. getTemperature();
3 }

我们在Station类中添加一个方法getTemperature()。这个方法将调用Station类中含有的Thermometer对象的getTemperature()。这样getTemp()方法就只知道Station对象而不知道Thermometer对象。

总结:笛米特法则告诉我们要尽量只和离自己最近的对象进行交互。离自己最近的对象包括:自身包含的对象,方法中创建的对象,通过参数传进的对象,还有自己本身。

参考资料

Breaking the Law of Demeter is Like Looking for a Needle in the Haystack

分类: 设计模式, 软件设计 标签:

谈设计的学习和特定技术学习的平衡和取舍

2010年1月3日 admin 3 条评论

有位大三还在公司实习的同学问了我一个问题,说他在加入公司之前通过网上下载一些Hibernate、Spring之类的视频来学些一些主流的Java技术和框架,通过这种方式和内容的学习,他的作品得到了老师的认可,并成为了公司的实习生。可在到了部门以后,发现我特别强调面向对象设计、设计模式的运用、UML以及文档。可就他自己的感觉而言,他对这些内容的学习不怎么感冒,我当时没有直接回答这个问题,今天整理了一下我的思路,说不上是一个回答,只是谈一下我的感觉吧。

故事的起点其实并不久远,就在我加盟现在的公司,并在9月份开始接手了一个产品线的开发工作。至此以后,我面对了很多的难题:

  • 产品的规划、维护做的很差,看不出模块化的设计,很多模块混成一团,理不清减还乱。这种“人造”的复杂,再加上缺乏文档,开发人员的陆续离开,系统的理解、维护出现很大的真空。
  • 产品的升级、扩展、改造难度大。
  • 产品设计、编码质量很差。这种情况存在在部门负责的多个系统中,不只是我们的产品线有这样的问题。这一点给我提了一个大大的问号?甚至是让我产生了离开公司的想法,其中的纠结是很难向外人说明白的。
  • 团队绩效低下,新的团队成员很难摸清楚现有的业务逻辑、长期依赖原有的开发人员开展工作,要很长的时间才能成为某个模块的owner,至于说整个产品线的,更加困难。
  • 产品质量差,新增加一个功能往往会影响几个历史功能,每次有新的大小版本要发布,都提心吊胆。这一点不仅影响到了开发团队,连测试部门很增加了很多的压力。
  • 工作压力大,对工作的满意度差。由于上面的几点严重影响了工作的效率,导致我和我的团队加班越来越频繁,满意度、幸福感随之下降。

与此相反的是我和我的团队所负责的产品线,需要支持一个年收入30亿的公司的使用,有10几20个业务部门在该产品线上开展核心的非核心的业务,每年有上百个的功能改进需求,有4-5次的产品版本升级,以上种种任务都需要一个7人左右的开发团队来支撑。如此突出的矛盾,我如何解决呢?我把强化设计视为解决我和我团队诸多问题的方案之一。坦白的说,我之前的工作经历中,我和我之前的公司、同事都没有对相关的内容有特别的关注,正是客观的环境教育了我,使得我把近期把技术重点放到了相关的内容上来。

在这段时间,我特别要求我和我的开发团队做好产品线的设计工作,强烈地要求我的团队重视设计工作、强化设计能力、完善文档、做好产品模块化的设计。也直接地在具体的工作中对团队成员的面向对象设计、设计模式、UML等方面的知识有提出了一定的要求,这些内容或许给他们有了不少的压力,他们多数是刚刚走出校园的学生或从未对相关的内容进行系统的学习。

最后回到那位同学问我的问题,具体的技术和设计如何取舍。首先,我不认为两者存在什么冲突和矛盾,如果说要有什么冲突和矛盾,也是由于一定时间内人的学习精力和时间有限,所以很难在两者直接加以平衡。我对技术的整理可以以下面这张图来理解。黄色图框的面向对象、设计模式之类的技术都是那些超越了具体语言、框架、平台的内容,而蓝色图框中的都是针对的某些语言或某种平台。但大家不要错误地认为两者之间是“井水不犯河水”的,其实两者之间存在着紧密的联系,以Java语言为例,Web框架总是离不开MVC模式的运用,Spring最核心的功能——DI(依赖注入)也可以在很多讲述软件架构的书中找到说明和分析。以我个人的学习经验而言,很多蓝色图框中的技术、框架的学习,最后都在要通过对黄色图框中相应内容的学习,找到彻底的答案。例如,WebWork中的Interceptor是整个Webwork框架的核心概念和开发重点。如果只是去学习Webwork,甚至是看Webwork的源代码都很难理解在Webwork中为什么要有Interceptor这种概念,能给整个Web框架带来什么样的好处,这些问题的都可以在Interceptor的相关设计模式中找到答案。

beyong_language

所以请我们的同学根据自己的情况、从事的工作内容在黄色图框和蓝色图框间做好平衡。其实我们每天的工作就像镜子一样可以反映出我们的缺陷,引导我们加以改进,如果我们能更主动地来加以调整,我们每个人的学习成长经历会更有效率和充满乐趣。最后,附上一个“木桶原理”的示意图,供大家玩味。

分类: 学习, 软件设计 标签:

Daniel-Journey Weekly Dose-2009/12/26

2009年12月27日 admin 没有评论

Java

Memory overhead of Java HashMap compared to ArrayList

Top 5 IntelliJ IDEA Performance Tips

Windows

How to check which application is using which port

Separated Interface

Database

Top 20+ MySQL Best Practices

Programming

Buffer和cache的区别是什么?

最关键的区别其实在于,buffer主要作用是在于减少实际的I/O操作次数,即,将多次操作尽量合并成一次的成批操作,通常其中的数据在操作完成之后,buffer不会被继续使用;而cache的主要作用在于更好地利用局部性原理,减少不必要的I/O,避免代价昂贵(例如,速度很慢)的I/O操作。

Buffer 更多的(场景)是减小写操作的冲击,而 Cache 主要用于减小读 I/O 的重复开销。不过很多时候二者都混淆得面目不清

Architecture & Design

提高架构质量的10个观点

1. 架构是创意的表现,架构来自创意,创意是假设(Hypothesis);

2. 假设需要检验,以需求检验创意;

3. 创意根源于固有文化,设计是文化与技术相遇的地方;

4. 从文化感悟体悟序(Order)之美,追求建立软件的美之序;

5. 以序容易(包容改变),美之序能包容繁杂多变,创造无尽繁荣等等。

Reading

Top 5 Essays You Should Read

6 Books Every Programmer Should Own

分类: 学习, 软件设计 标签:

Separated Interface分离接口模式

2009年12月27日 admin 没有评论

在一个包定义接口,而在另一个与这个包分离的包中实现这个接口。

需要对两个系统之间进行解藕时,可以使用Separated Interface。当并不提倡对每个类都使用Separated Interface。保持接口和实现的分离需要一些额外的工作,只有当你希望打破这种依赖关系的使用才使用它,或者同一个接口有多个实现才使用。当然,也可以把接口和实现放在一起,当需要分离的时候才进行重构。

参考资料

Separated Interface

Patterns of Enterprise Application Architecture 476页

分类: 设计模式, 软件设计 标签:

Daniel-Journey Weekly Dose-2009/12/06

2009年12月6日 admin 没有评论
分类: 学习, 软件设计 标签:

Daniel-Journey Weekly Dose-2009/11/29

2009年11月29日 admin 没有评论

Java

Spring 的优秀工具类盘点,第 1 部分: 文件资源操作和 Web 相关工具类

Spring 的优秀工具类盘点,第 2 部分: 特殊字符转义和方法入参检测工具类

Thoughts on Java logging and SLF4J

Getting Started with Terracotta

性能测试项目总结之内存泄露和内存溢出

Terrocotta – 基于JVM的Java应用集群解决方案

面向对象设计

Applying Strategy Pattern Instead of Using Switch Statements

What we gain by using Strategy Pattern?
  • The code is easier to read. I don’t need to go and read (or
    search) an “endless” switch statement to understand each
    aspect of the code.
  • The code is more maintainable. I only need to go to the
    relevant class that implement the algorithm in order to change it
    or refactor it when needed.
  • It is easier to add more algorithms. I only need to add more classes
    to the pile of algorithms and that is it. Doing so helps us to imply
    the Open / Closed Principle because in switch statement we are going 
    to have to change our code (add another case statement) in opposed
    to Strategy which we add another algorithm class.
  • Strategy Pattern is more testable.

数据库

Top 20+ MySQL Best Practices

其他

Transfer Obejcts vs Business Objects – Which Approach Works Best

分类: 学习, 软件设计 标签: