qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

接口测试从零开始5_教你如何使用ibatis

     摘要: 1、创建pojo类   public classPingJia   2、创建Dao接口   publicinterfaceIPingJiaDao   3、使用ibatis编写实现类   1)通过pom依赖引入ibatis包 <dependency>      <groupId>com.ibatis&...  阅读全文

posted @ 2011-12-13 15:50 顺其自然EVO 阅读(547) | 评论 (0)编辑 收藏

Patrick Copeland:Google如何进行测试 之二

 在对Patrick Copeland的采访的第二部分,我们主要讨论了管理一个全球开发团队的挑战;食物刺激对程序员的作用;好的测试工程师和超nb测试工程师之间的区别;以及为何某些公司始终无法提供靠谱的软件。

  问:管理一个跨越一打以上国家的团队有什么挑战?在这样的团队中管理人员、流程、产品有什么样的困难?还有,你一般几点睡觉?

  答:“确保对人员的掌控”<谈到这个时他脸上挂着邪恶的微笑>不是Google的解决方案。实际上,我们的团队结构在业界是非典型的。首先,我们是扁平化架构,即使是Google新雇员,和最高管理层之间的层级也只有几步而已。还有就是公司内的那些半自治人员和团队。在这个非典型体系中,让管理者控制一切显然不太合适。与之相反,我更愿意让nb的人组成团队然后让他们自己管理自己。我的重心会放在帮助团队提高效率上。大体上来说,我们评价Google管理者的指标就是他们让聪明人完成任务的能力。而且我们认为,大部分人一旦拥有超过15个下属,就会陷入混乱,投入到真正的管理上的时间就会减少。

  确保我们向同一方向努力的神器是——OKRs,这个东东是董事会成员John Doerr在2000年带入Google的。John强调由公司级目标派生部门目标的重要性;与之对应的,每个员工的OKRs应该支持团队和整个公司范围的目标。在2000年第一季度,我们发布了第一个公司级别的OKRs,其中包括“每秒800万次搜索”以及“为公司寻找CEO”。自那时起,我们已经取得了长足的进步。

  问:我们注意到Google给雇员提供了大量独特友好的工作环境。对于你们的工程师,这些手段是否依然有效呢?或者(如同我们在uTest做的那样)你们正准备把工程师拷在桌子上,然后他们每写一行代码你们扔一个小食品以示奖励?不开玩笑了,真正的问题是,如何保证开放的环境下工程师依然可以做出出色的软件?

  答:我们有开放的文化,工程师有足够的自由去探索他们感兴趣的领域。我们有一个“20%时间段”,在这些时间中我们鼓励工程师去探索他们主要业务之外的领域。

  (好的)文化会衍生出好的产品吗?我们这么认为,但是这二者之间的关系很难判定。有些人会倾向于使用那些“产品化度量”:代码行数、签入次数等等。我们(非正式的)阻止这种做法,因为类似这种度量会造成一些不可预知的问题:例如人们会使用一些“技巧”,以在系统中获得“所谓的成功”。(除了带给最终客户的创新)我们衡量绩效的最重要方式是每季度一次的同事评审。这种系统会强化你对团队工作的认识,确保你在同事中拥有影响力,并建筑尊重。这种评判是主观的,可能看上去很难操作,但是对于塑造个体价值功效显著。

  另外,扔食物在我们那里必然没效果,因为在Google员工可以免费享用各种美食。

  问:好测试工程师和卓越的测试工程师之间有什么区别?

  答:好的测试工程师是可以训练出来的。基础的要求有:计算机基础知识,对于应用领域的认识,对于客户用例的强力理解,客户角度的视野,对于度量的把握以及对开发流程的关注。

  卓越的测试工程师则意味着传说中的10%,他们非常罕见和稀有。不是每一个人都可以成为卓越的人。从个人经验角度看,卓越的开发工程师并不一定能成为卓越的测试工程师,但是卓越的测试工程师(拥有很强的设计能力)则可以成为卓越的开发者。这与一种心态和激情有关。从超过100次面试得来的经验看,我认为,卓越来自于:1)一种发现问题的特殊潜质;2)被潜质指引去测试和发现问题的激情。换句话说,他们喜欢测试并且善于测试。他们也常常感觉到测试中的挑战,大于等于开发中的挑战,并为之感到特爽。一个大牛测试工程师,他们有测试的基因,正确的态度,他们总是很容易的找到一份工作。他们就像金子一样的宝贵。

  问:您认为,导致我们无法在带着(交付日期,优先级,竞争)镣铐下制作出高质量的产品的最大原因是什么?换句话说,为什么不是每一家公司每一次都可以发布世界级的产品?

  答:我经历的每个产品都有不同的故事。每个故事的情节那就是有一句说一句了。某些产品我们几乎可以掌控全部(例如技术选择)。而另外一些产品中我们只能控制一部分。有些因素则完全不受我们的掌控(例如竞争对手想咋整)。

  有些公司试图开发一些规范的流程。这些看上去冠冕堂皇的流程都说自己可以:提高效率,消灭不确定性,维护质量,如此等等。这些重口味开发流程在造大灰机的时候灰常有用——这也确实被一些nb飞机制造商证明了。不过在程序猿们看来,这种流程可能过于繁杂,会破坏他们创造软件的心情。相反的,木有流程的流程则可能导致你的开发无法被复制。你需要在大流程和没留成之间做出平衡。

  让我们把灰机制造和软件开发过程放在一起比一比。在飞行条件具备和飞行员有经验的情况下,制造灰机的核心就剩下了平衡必要条件:超重或推力不足在一些情况下就会导致灾难。同样的,对于团队,产品和流程这些虚拟因素也是如此。例如,在项目后期猛招工程师是没法提供升力的。再比如,采用一套新流程可能会给团队带来一时的新推力,但是也可能在中长期破坏团队的创新能力。

  敏捷开发的流行说明程序猿们需要更好的平衡和创造性。我们的软件质量也确实提升并且过程可控了,我们必须鼓励创新。我们需要鼓励那些给客户带来价值的或者是解决了困难问题的奇思妙想。话句话说,我们要保持团队在天上自由的灰啊灰。

posted @ 2011-12-13 15:42 顺其自然EVO 阅读(192) | 评论 (0)编辑 收藏

评审技术在高质量软件开发中的应用分析(下)

评审技术在高质量软件开发中的应用分析(上)

  三、评审在高质量软件开发的实际应用

  3.1 高质量软件开发项目介绍

  高质量软件,如电信软件、金融证券类软件等,有较严格的要求:可用性要求非常高,并且不会因为系统维护和扩展而带来运营中断;支持使用现有管理工具和标准进行远程管理;能够提供更出色的性能以及运营在高可用性集群上的能力,减少任何单点的软硬件失效现象。五个九(99.999%)意味着一个系统的宕机时间一年不超过526秒。因此高质量软件项目是一种对可用性、可靠性、稳定性要求非常高的软件项目,要求软件能够每周7*24工作。

  因此高质量软件开发一般采用严格的软件开发过程,明确定义每个阶段的质量目标和要求,严格项目软件开发过程控制。

  我们在多个高质量软件开发项目中成功地采用了评审技术,并发挥了巨大的作用。从这些项目的实际开发过程中,我们针对于规模从30人月——300人月,代码行数从5万行——30万行的运营支撑系统项目制定了项目评审流程和相关要求。

  3.2 软件过程定义

  软件过程主要分为项目立项阶段、需求分析阶段、设计阶段、编码实现阶段、测试阶段(包括集成测试、系统测试和用户验收测试)、实施阶段和维护阶段,项目管理工作横贯于所有的阶段。详细流程见流程图。

  在软件过程中,我们定义了以下角色:

  1)客户

  2)销售人员

  3)项目经理

  4)系统分析员

  5)系统架构师

  6)开发工程师

  7)质量工程师

  8)技术支持人员

  在规划质量体系时,我们参考PMBOK对项目质量管理的要求,将项目质量管理过程设计为三个阶段:

  1)质量规划——确定质量活动的流程和标准,如软件过程定义,质量达标定义等;

  2)实施质量保证——编写相应的测试计划、执行测试和评审活动;

  3)实施质量控制——监控质量保证活动的结果,判断是否达标,如不达标,则采取相应的风险防范措施。

  3.3 软件评审过程及标准定义

  我们在整体软件过程中明确定义了需要软件评审的过程及实施的方法。

  3.3.1 采用审查的过程

  采用最严格最系统的评审方法——审查的软件过程有:

  1)《软件需求规格说明书》的评审

  2)《概要设计说明书》的评审

  3)《详细设计说明书》的评审

 4)代码评审

  5)《单元测试计划》的评审

  6)《集成测试计划》的评审

  7)《系统测试计划》的评审

  对于文档评审以文档页数为基数,要求每页发现的缺陷数有一个目标值,并规定了上下限的范围。对于代码评审以代码行数为基数,要求每千行代码发现的缺陷数有一个目标值,并规定了上下限的范围。这个审查的缺陷数标准如下表。

  软件过程审查的质量目标

  质量目标                                                                   目标        下限          上限

  SRS文档Review缺陷发现密度(个/页)              0.80        0.50         1.10

  HLD文档Review缺陷发现密度(个/页)              0.70        0.50         0.90

  LLD文档Review缺陷发现密度(个/页)              0.43        0.22         0.64

  代码检视缺陷发现密度(个/KLOC                   10.62       7.43         13.81

  单元测试计划Review缺陷发现密度(个/页)     0.43        0.22         0.64

  集成测试计划Review缺陷发现密度(个/页)     0.70        0.50         0.90

  系统测试计划Review缺陷发现密度(个/页)     0.80        0.50         1.10

  如果发现的缺陷密度低于或高于质量目标范围,则我们需要分析其原因,然后根据原因进行返工或相应处理流程。这要和实际情况相结合,具体情况具体分析:如某个开发工程师的水平和素质非常高,他的代码一般很少出错,这样他的代码检视缺陷密度低是属于正常的;而另外一个工程师水平一般,但发现的缺陷密度也很低,但原因是属于检视的过程不严格,大家没有时间来进行严格的评审,则此时需要重新进行检视。

  3.3.2 采用小组评审的过程

  采用小组评审的软件过程主要包括对客户需求的评审、项目计划的评审和维护计划的评审。

  对客户需求的评审参加人员有项目经理、系统分析员、系统架构师、质量部主管等。

  项目计划的评审主要由项目经理、系统分析员、系统架构师、质量部主管和部门经理等参加,对人力资源、进度和质量管控等进行评审。

  维护计划由项目经理、技术支持人员、质量部主管和客户服务人员参加,对人力资源、管控流程等进行评审。

  3.3.3 采用走查评审的过程

  需求分析过程中,系统分析员、系统架构师相互之间的走查;

  设计过程中,系统分析员、系统架构师相互之间的走查;

  在进入维护阶段时,作者需和维护人员进行走查,让维护人员能够维护作者的工作产品。

  3.3.4 采用桌查的过程

  采用桌查的过程主要集中在代码提交阶段,主要是经验丰富的开发人员对提交的代码进行检查,合格的产品才会提交到CVS上面。

  具体实施方法为:开发经验较少(8年以下开发经验)的开发人员在提交代码时,请经验丰富(10年以上开发经验)的开发人员进行桌查,没有明显问题后才允许提交;经验丰富的开发人员提交代码时也需另一名经验丰富的开发人员桌查后方可提交。

  3.3.5 采用临时评审的过程

  代码编写阶段开发工程师之间的临时评审;

  其他开发阶段,开发人员相互之间的临时评审。

  3.3.6 采用结队编程的过程

  针对于需求不明确、采用迭代增量开发过程的小规模项目,采用极限编程时,建议采用结队编程,但一般不做强制规定。

  我们实际采用极限编程思想进行了两个项目(一个内部项目和一个外部项目)的开发,实际上取得了非常好的效果。开发人员实际产出值达到了5000行代码/人月以上。当然这些项目不太大,同时文档编写比较简单。

  3.4 审查流程定义

  我们规定了审查流程的定义,其他评审技术只是采用了其中的流程和管理思想。

  3.5 软件评审效果分析

  我们强化了软件评审技术后,在实际过程中取得了非常好的效果。以一个网络流量分析的项目为实例,在第一期没有严格实施软件评审技术,而第二期严格实施了软件评审技术,其中审查数据如下表。

  评审过程数据及质量分析实例

  文件/模块 计算基数(页数/KLOC 致命 严重 一般 提示 总和 标准(目标/下限-上限) 比例 达标与否

  SRS 42 1 1 29 10 31 0.8 / 0.5~1.1 0.738 OK
  STP 58 22 15 12 37 0.8 / 0.5~1.1 0.638 OK
  HLD 34 4 15 29 19 0.7 / 0.5~0.9 0.559 OK
  LLD 205 11 59 29 70 0.43 / 0.22~0.64 0.341 OK
  UTP 217 15 80 15 95 0.43 / 0.22~0.64 0.438 OK
  CodeReview 50 7 372 151 379 10.62 / 7.43~13.8 7.580 OK
  SITP 50 6 98 112 30 216 5.65/3.86~8.44 4.320 OK

  产生的效果为:

  1)产出量:单位开发人员的产出量由950行代码/人月(全流程)增长到1320行代码/人月(全流程),增长量为38.9%。关键原因在于大在减少了项目后期返工的工作量。考虑由于项目熟悉和学习曲线等的原因,实际的产出增长量应该超过20%

  2)产品质量(遗留缺陷密度):我们从软件系统的遗留缺陷率来分析系统的质量情况。在半年的维护时间内,第一期代码行为4万行,严重缺陷有5个,一般缺陷有32个,严重缺陷发现密度为0.125个缺陷/千行代码,总遗留缺陷发现密度为0.925个缺陷/千行代码;第二期代码行数为5万行,严重缺陷有1个(属于客户需求问题引发的设计缺陷),一般缺陷有15个,严重缺陷发现密度为0.02个缺陷/千行代码,总遗留缺陷发现密度为0.32个缺陷/千行代码。因此严重缺陷发现密度改进了84%,一般缺陷发现密度改进了65.4%

  3)客户满意度:第一期客户严重不满意,称我们在做玩具,满意度只有22%;第二期客户满意度大幅上升,称我们是专业人士,非常敬业,为他们所钦佩,满意度达到了91%。因此满意度提高了314%

  最主要的是,我们采用了软件评审技术,规范了软件开发过程的标准,并积累了实际的软件开发过程数据,为后面的项目管理和过程控制提供了宝贵的软件过程财富。

  四、总结和展望

  在实际工作中,我们以前掌握的过程数据不多,基本上一步一步摸索着前进,对于过程数据也在不断的收集及整理中。对于评审技术而言,其中最重要的一点是采用多种实际工具和手段,如针对每个过程进行评审的《缺陷检查表》等,我们也在逐步地完善这套体系和过程数据,从而为我们的过程改进不断提供支持。

相关链接:

评审技术在高质量软件开发中的应用分析(上)

 

posted @ 2011-12-13 15:37 顺其自然EVO 阅读(304) | 评论 (0)编辑 收藏

NoSQL生态系统

  要想了解NoSQL,必须先了解现有的这些工具,去理解那些引导它们开拓出新的存储领域的设计思路。

  NoSQL 其名

  在给 NoSQL 下定义之前,我们先来试着从它的名字上做一下解读。顾名思义,NoSQL 系统的数据操作接口应该是非 SQL 类型的。但在 NoSQL 社区,NoSQL 被赋予了更具有包容性的含义,其意为 Not Only SQL,即 NoSQL 提供了一种与传统关系型数据库不同的存储模式,这为开发者提供了关系型数据库之外的另一种选择。

  NoSQL 的启示

  NoSQL 运动受到了很多相关研究论文的启示,在所有资料中,最核心的有两个:Google 的 BigTable 论文和 Amazon 的 Dynamo 论文。

  特性概述

  NoSQL 系统舍弃了一些 SQL 标准中的功能,取而代之的是一些简单灵活的功能。NoSQL 的构建思想就是尽量简化数据操作,尽量让操作的执行效率可预估。当你去考查一个 NoSQL 系统时,下面的几点是值得注意的。

  数据模型及操作模型:你的应用层数据模型是行、对象还是文档型的呢?这个系统是否能支持你进行一些统计工作呢?

  可靠性:当你更新数据时,新的数据是否立刻写到持久化存储中去了?新的数据是否同步到多台机器上了?

  扩展性:你的数据量有多大,单机是否能容下?你的读写量需求单机是否能支持?

  分区策略:考虑到对扩展性、可用性或者持久性的要求,你是否需要一份数据被存在多台机器上?你是否需要知道或者说你能否知道数据在哪台机器上?

  一致性:你的数据是否被复制到了多台机器上?这些不同节点的数据如何保证一致性?

  事务机制:业务是否需要 ACID 事务机制?

  单机性能:如果你打算持久化的将数据存在磁盘上,哪种数据结构能满足你的需求(你的需求是读多还是写多)?写操作是否会成为磁盘瓶颈?

  负载可评估:对于一个读多写少的应用,诸如响应用户请求的网络应用,我们总会花很多精力来关注负载情况。你可能需要进行数据规模的监控,对多个用户的数据进行汇总统计。你的应用场景是否需要这样的功能呢?

  NoSQL 数据模型及操作模型

  数据库的数据模型指的是数据在数据库中的组织方式,数据库的操作模型指的是存取这些数据的方式。通常数据模型包括关系模型、键值模型以及各种图 结构模型。操作语言可能包括 SQL、键值查询及 MapReduce 等。NoSQL 通常结合了多种数据模型和操作模型,提供不一样的架构方式。

  基于Key值存储的NoSQL数据模型

  在键值型系统中,复杂的联合查询以及满足多个条件的数据查询操作就不那么容易实现了,需要换一种思维来建立和使用键名。比如要获取部门号为 20 的所有员工的信息,应用层可以先获取 Key 为 employee_departments:20的这个列表,然后再循环地拿这个列表中的 ID 通过获取 employee:ID 得到所有员工的信息。

  Key-Value 存储

  Key-Value 存储可以说是最简单的 NoSQL 存储,每个 Key 值对应一个任意的数据值。对 NoSQL 系统来说,这个任意的数据值是什么,它并不关心。比如在员工信念数据库里,employee:30这个 Key 对应的可能就是一段包含员工所有信息的二进制数据。这个二进制的格式可能是 Protocol Buffer、Thrift 或者 Avro 都无所谓。

  Key-结构化数据存储

  Key-结构化数据存储的典型代表是 Redis,Redis 将 Key-Value 存储的 Value 变成了结构化的数据类型。Value 的类型包括数字、字符串、列表、集合以及有序集合。除了 set/get/delete 操作以为,Redis 还提供了很多针对以上数据类型的特殊操作,比如针对数字可以执行增、减操作,对 list 可以执行 push/pop 操作,通过提供这种针对单个 Value 进行的特定类型的操作,Redis 可以说实现了功能与性能的平衡。

Key-文档存储

  Key-文档存储的代表有 CouchDB、MongoDB 和 Riak。这种存储结构下 Key-Value 的 Value 是结构化的文档,通常这些文档是被转换成 JSON 或者类似于 JSON 的结构进行存储。文档可以存储列表,键值对以及层次结构复杂的文档。

  BigTable 的列簇式存储

  HBase 和 Cassandra 的数据模型都借鉴自 Google 的 BigTable。这种数据模型的特点是列式存储,每一行数据的各项被存储在不同的列中(这些列的集合称作列簇)。而每一列中每一个数据都包含一个时间戳 属性,这样列中的同一个数据项的多个版本都能保存下来。

  列式存储可以这样理解:将行 ID、列簇号,列号以及时间戳一起,组成一个 Key,然后将 Value 按 Key 的顺序进行存储。Key 值的结构化使这种数据结构能够实现一些特别的功能,最常用的就是将一个数据的多个版本存成时间戳不同的几个值,这样就能方便地保存历史数据。这种结构也能 天然地进行高效的松散列数据(在很多行中并没有某列的数据)存储。当然,对于那些很少有某一行有 NULL 值的列,由于每一个数据必须包含列标识,这又会造成空间的浪费。

  图结构存储

  图结构存储是 NoSQL 的另一种存储实现。其指导思想是:数据并非对等的,关系型的存储或者键值对的存储,可能都不是最好的存储方式。图结构是计算机科学的基础结构之一,Neo4j 和 HyperGraphDB 是当前最流行的图结构数据库。

  复杂查询

  在 NoSQL 存储系统中,有很多比键值查找更复杂的操作。比如 MongoDB 可以在任意数据行上建立索引,可以使用 Javascript 语法设定复杂的查询条件。BigTable 型的系统通常支持对单独某一行的数据进行遍历,允许对单列的数据进行按特定条件的筛选。CouchDB 允许你创建同一份数据的多个视图,通过运行 MapReduce 任务来实现一些更为复杂的查询或者更新操作。很多 NoSQL 系统都支持与 Hadoop 或者其他 MapReduce 框架结合来进行一些大规模数据分析工作。

  事务机制

  与关系型数据库不同的是,NoSQL 系统通常注重性能和扩展性,而非事务机制。传统的 SQL 数据库的事务通常都是支持 ACID 的强事务机制。ACID 的支持使得应用者能够很清楚他们当前的数据状态。对很多 NoSQL 系统来说,对性能的考虑远在 ACID 的保证之上。通常 NoSQL 系统仅提供行级别的原子性保证,也就是说同时对同一个 Key 下的数据进行的两个操作,在实际执行时是会串行的,保证了每一个 Key-Value 对不会被破坏。

  Schema-free 的存储

  还有一个很多 NoSQL 的共同点,就是它通常并没有强制的数据结构约束。即使是在文档型存储或者列式存储上,也不会要求某一个数据列在每一行数据上都必须存在。

  数据可靠性

  最理想的状态是,数据库会把所有写操作立刻写到持久化存储的设备,同时复制多个副本到不同地理位置的不同节点上,以防止数据丢失。但这种对数据安全性的要求对性能是有影响的,所以不同的 NoSQL 系统在自身性能的考虑下,在数据安全上采取了不太一样的策略。

  单机可靠性

  单机可靠性理解起来非常简单,它的定义是写操作不会由于机器重启或者断电而丢失。通常单机可靠性的保证是通过把数据写到磁盘来完成的,而这通常会造成磁盘I/O成为整个系统的瓶颈。下面我们谈谈一些在单机可靠性的保证下提高性能的方法。

  控制fsync的调用频率

  Redis 提供了几种对 fsync 调用频率的控制方法。应用开发者可以配置 Redis 在每次更新操作后都执行一次 fsync,这样会比较安全,当然也就比较慢。Redis 也可以设置成N秒种调用一次 fsync,这样性能会更好一点。但这样的后果就是一旦出现故障,最多可能导致N秒内的数据丢失。而对一些可靠性要求不太高的场合(比如仅仅把 Redis 当 Cache 用的时候),应用开发者甚至可以直接关掉 fsync 的调用:让操作系统来决定什么时候需要把数据 flush 到磁盘(译者注:这只是 Redis append only file 的机制,Redis 是可以关闭 aof 日志的,另外,Redis 本身支持将内存中数据 dump 成 rdb 文件的机制,和上面说的不是一回事)。

 使用日志型的数据结构

  Cassandra、HBase、Redis 和 Riak 都会把写操作顺序的写入到一个日志文件中。相对于存储系统中的其他数据结构,上面说到的日志文件可以频繁地进行 fsync 操作,这样就把对磁盘的随机写变成顺序写了。

  通过合并写操作提高吞吐性能

  Cassandra 有一个机制,它会把一小段时间内的几个并发的写操作放在一起进行一次 fsync 调用,这种做法叫 group commit。

  多机可靠性

  由于硬件层面有时会造成无法恢复的损坏,单机可靠性的保证在这时就鞭长莫及了。对于一些重要数据,跨机器做备份保存是必备的安全措施。一些 NoSQL 系统提供了多机可靠性的支持。

  Redis 采用传统的主从数据同步的方式。

  MongoDB 提供了一种叫 Replica Sets 高可用架构。

  Riak、Cassandra 和 Voldemort 提供了一些更灵活的可配置策略,并提供一个可配置的参数N,代表每一个数据会被备份的份数。为了应对整个数据中心出现故障的情况,需要实现跨数据中心的多机备份功能。

  横向扩展带来性能提升

  横向扩展的目标是达到线性的效果,即如果你增加一倍的机器,那么负载能力应该也能相应的增加一倍。其主要需要解决的问题是如何让数据在多台机器间分布,这里面涉及到分片技术。

  分片的意思,就是没有任何一台机器可以处理所有写请求,也没有任何一台机器可以处理对所有数据的读请求。下面我们将会对 hash 分片和范围分片两种分片方式进行描述。

  如非必要,请勿分片

  分片会导致系统复杂程度大增,所以,如果没有必要,请不要使用分片。普通情况下,我们可以使用读写分离和构建缓存的方式来缓解我们的数据读压力。但如果写操作达到单点无法承担的程度,那我们可能就真的需要进行分片了。

  通过协调器进行数据分片

  一种分片策略是通过引入一个中间代理层来实现,该代理层记录数据在各个节点的分布状况,所有读写请求都通过代理层来做路由。比如与 CouchDB 的两个项目:Lounge 和 BigCouch。类似的,Twitter 自己也实现了一个叫 Gizzard 的协调器,可以实现数据分片和备份功能。

  一致性 hash 环算法

  一致性 hash 是一种被广泛应用的技术,其最早在一个叫 distributed hash tables(DHTs)的系统中进行使用。那些类 Dynamo 的应用,比如 Cassandra、Voldemort 和 Riak,基本上都使用了一致性 hash 环算法。

  如图 1 所示,一致性 hash 环算法有一个 hash 函数H,所有存储数据的节点和数据本身都可以通过这个函数算出一个 hash 值,作为自己在下面环上的位置。然后每个节点会负责存储其 hash 值到下一个节点间的所有数据的存储。这样使得即使节点数变化了,大部分数据并不需要进行迁移。

图 1 一致性 hash 环算法的 hash 函数

连续范围分区

  使用连续范围分区的方法进行数据分片,需要我们保存一份映射关系表,标明哪一段 Key 值对应存在哪台机器上。与一致性 hash 类似,连续范围分区会把 Key 值按连续的范围分段,每段数据会被指定保存在某个节点上,然后会被冗余备份到其他节点。

  BigTable 的处理方式

  Google BigTable 论文中描述了一种范围分区方式,它将数据切分成一个个的 tablet 数据块。每个 tablet 保存一定数量的键值对。然后存储在 Tablet 服务器上。tablet 块的大小会保持在一定范围,太大的块会分裂成两个,太小的块又会合并成一个。BigTable 通过一个叫 Chubby 的模块来实现节点状态检测。类似的在 Hadoop 中有一个叫 ZooKeeper 的工具实现此功能。

  一致性

  上面讲到了通过将数据冗余存储到不同的节点来保证数据安全和减轻负载,下面我们来看看这样做引发的一个问题:保证数据在多个节点间的一致性是非常困难的。在多个点间保持数据的一致性的问题,也就是本章的主题。下面我们首先来看一下在著名的 CAP 理论。

  一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。

  可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。

  分区容忍性(P):集群中的某些节点在无法联系后,集群整体是否还能继续进行服务。

  而 CAP 理论就是说在分布式存储系统中,最多只能实现上面的两点。再加之当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。结果就是我们只能在一致性和可用性之间进行权衡,没有 NoSQL 系统能同时保证这三点。

  对一致性的保证,通常有强一致性和弱一致性的选择,而在弱一致性里,又以最终一致性的实现较为普遍。

  如果我们采用 NRW 的设定,N为数据需要备份的份数,R为读操作需要读到的不同节点上的数据份数,W为写操作需要成功写到不同节点的数据份数,那么当R+W>N时,既 是强一致性的保证,当R+W

  写在最后的话

  目前 NoSQL 系统来处在它的萌芽期,我们上面讨论到的很多 NoSQL 系统,它们的架构、设计和接口可能都会改变。本章的目的,不在于让你了解这些 NoSQL 系统目前是如何工作的,而在于让你理解这些系统之所以这样实现的原因。NoSQL 系统把更多的设计工作留给了应用开发工作者来做。理解上面这些组件的架构,不仅能让你写出下一个 NoSQL 系统,更让你对现有系统应用得更好。



posted @ 2011-12-13 15:28 顺其自然EVO 阅读(141) | 评论 (0)编辑 收藏

JAVA学习:成员内部类基本概念及用法

简单的说,内部类就是将一个类的定义放到另一个类的定义内部。内部类分为:成员内部类、局部内部类、静态内部类、匿名内部类。

  成员内部类:作为外部类的一个成员存在,与外部类的属性、方法并列。

  优点:一方面,内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使声明为private,但是对于处于其内部的内部类还是可见的。)另一方面,可以内部类定义在外部类不可访问的属性。这样就在外部类中实现了比外部类private还要小的额访问权限。

  注意:

  内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。

  对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后出现Outer.class 和 Outer$Inner.class 两个类

  当Outer是一个private类时,外部类对于其外部访问是私有的,所以就无法建立外部类对象,进而也无法建立内部类对象。

  局部内部类

  在方法中第一的内部类称为局部内部类。

  与局部变量类似,在局部内部类前不加修饰符public和private,其范围为定义它的代码块

  注意:

  在类外不可直接生产局部内部类(保证局部内部类对外是不可见的)。

  要想使用局部内部类时需要生产对象,对象调用方法,在方法中才能调用局部内部类。

  通过内部类和接口达到一个强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性。

  静态内部类

  静态内部类可以使用public,protected,private修饰

  静态内部类中可以定义静态和非静态的成员

  注意:

  一个静态内部类不需要一个外部类的成员:只是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成

  这实际上静态内部类成为了一个顶级类。

  静态内部类不可用private来进行定义。

  当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。

  匿名内部类

  匿名内部类就是没有名字的内部类。

  注意:

  匿名内部类不能有构造函数

  匿名内部类不能定义任何静态成员、方法和类

  匿名内部类不能是public、protected、private、static

  只能创建匿名内部类的一个实例

  一个匿名内部类用其隐含实现一个接口或实现一个类。

  因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

posted @ 2011-12-13 15:26 顺其自然EVO 阅读(303) | 评论 (0)编辑 收藏

首次尝试测试驱动开发的体会

 测试驱动开发的定义

  测试驱动开发(Test-driven development)是现代计算机软件开发方法的一种。利用测试来驱动软件程序的设计和实现。测试驱动开始流行于20世纪90年代。测试驱动开发是极限编程中倡导的程序开发方法,方法主要是先写测试程序,然后再编码使其通过测试。测试驱动开发的目的是取得快速反馈并使用“illustrate the main line”方法来构建程序。

  测试驱动开发的比喻。开发可以从两个方面去看待:实现的功能和质量。测试驱动开发更像两顶帽子思考法的开发方式,先戴上实现功能的帽子,在测试的辅助下,快速实现正确的功能;再戴上重构的帽子,在测试的保护下,通过去除冗余和重复的代码,提高代码重用性,实现对质量的改进。可见测试在测试驱动开发中确实属于核心地位,贯穿了开发的始终。

  最近在学习《重构》,里面强调,实施重构的一个前提是,必须建立一个可靠的测试环境。里面还提到了测试驱动开发,以及测试驱动开发的好处。正好最近在开发一个小的项目,所以便在这个项目中尝试了一下测试驱动开发,感觉不错,这里分享一下我的体会。

  我开发使用的是C++,我使用的测试框架是Google的c++开源测试框架gtest。另外Google也有一个mock的开源框架gmock,可以和gtest一起使用。至于gtest的使用,可以参照我以前转载的一个系列文章玩转Google单元测试框架gtest系列

  首先谈一下测试框架gtest。总体感觉gtest使用起来还算比较方便的,而且他们能够自动运行所有的测试用例,这一点非常重要。在《玩转Google单元测试框架gtest系列》这个系列的文章中,没有看到测试框架对private方法测试的描述。后来我看了一下官方网站的说明,它是支持的。另外,这个测试框架的一个不好的地方就是,它主要是通过检查函数返回值来判断测试成功或失败的,对于返回值为void的方法,要判断执行成功与失败,则非常的不方便,有的时候,根本无法判断。不过从总体上来看,这个测试框架还是比较不错的。

  其次那,谈一下测试驱动开发带来的好处

  1、提高软件质量。毋容置疑,有单元测试保障的软件产品质量肯定要比没有经过单元测试的软件产品质量要好。

  2、开发速度。测试驱动开发主张在编码之前首先编写测试代码,这种做法会降低编码的速度。但是在整个软件开发过程中,编码所占用的时间是很少的,我们大部分的时间在调试,定位bug,修改bug。测试驱动对开发速度的提升在于,它可以减少后者的时间,从而在整体上提升开发速度。

  3、重构。构建一个可靠的测试环境,可以让我们大胆的进行重构,而不用担心会引入潜伏下来的bug。

  4、增量开发。使用测试驱动开发,我们可以实现软件产品的增量开发,而不用担心增量开发会对原有的功能造成破坏。

  5、促进优良的设计:低耦合,高内聚。测试驱动开发一个非常重要的好处就是它可以促进优良的设计。糟糕的设计,一般情况会非常难以进行测试。测试驱动有助于促进低耦合,高内聚的设计,因为这样的设计才会易于测试。

  6、真正实现面向接口编程。

  7、避免过度设计。当你构建好测试用例后,也就提供了一个完成设计的目标:让所有的测试用例测试通过。这样可以避免过度设计。

  虽然测试驱动开发有这么多的好处,但是要在研发人员的推行这个开发方式,面临的挑战还是非常大的,毕竟它带来的是一种全新的开发思路和模式。不过我相信,只要开发人员乐于去尝试这种开发方式,他一定会喜欢上它的。

posted @ 2011-12-12 14:34 顺其自然EVO 阅读(271) | 评论 (0)编辑 收藏

单元测试实践的主要问题与解决(2)

二、单元测试实践的主要问题

  单元测试有个特点:测试简单独立的代码很容易,但要在实际工作中做好单元测试却很困难。

  根据我们的经验,企业在实施单元测试时,通常会面对四大问题——

  ● 不愿做:程序员没有单元测试习惯。

  ● 没时间:编写测试代码需要耗费大量的时间,项目的周期可能不允许。

  ● 做不了:代码具有较高的耦合性,使单元测试难以进行。

  ● 做不好:测试效果不能令人满意。我们通常会以覆盖率来衡量测试效果,但要实现高标准的测试覆盖很困难。

  三、解决思路和方法

  如何解决上述问题呢?接下来,谈谈一些思路和方法,使用的工具是Visual Unit。Visual Unit,简称VU,是可视化的C/C++单元测试工具。

  3.1 如何解决“不愿做”和“没时间”

  对于“不愿做”,我们采用的对策是可视化,这个可视化,是指程序行为可视,后面我会用案例来演示;对于“没时间”,采用的对策是自动化,通过自动生成测试代码、自动打桩等功能,让测试的时间成本最小化。这两者结合起来,就是ETDD开发模式。

  那么,ETDD是什么呢?

  首先来介绍一下TDD,TDD就是测试驱动开发,这个大家可能听得比较多了。ETDD就是Easy TDD,即:易行版的TDD。ETDD具有以下一些特点:

  ● 可视化,在开发过程中,程序行为可视。

  ● 自动化,除了测试数据需要人工设定外,其他基本上都自动完成。

  ● 现实化,不一定要测试所有代码,在开始阶段,可以只测试功能逻辑复杂的20%代码。

  下面,我用一个案例,讲解一下ETDD的过程:

  假如我要编写一个函数,它的功能是删除字符串左边的空格。

  先写好函数的框架,能通过编译就行。在编写代码前,程序员必须要做的一件事情,是想清楚代码的功能。如果我们想的时候,顺手把它记录下来,就可以让代码的功能更清晰、更明确。

  我们现在来记录代码的功能。这里的记录,不是文字形式的宠统说明,而是数据形式的精确定义,也就是用输入和输出的方式来记录。

  首先,记录最基本的功能,也就是最基本、最常见的输入和输出。输入一个左边有空格的字符串,输出是删除左边空格后的字符串,返回值跟参数的输出是一样的。

 

然后,记录详细的功能。例如,左边没有空格的,全是空格的,还有空字符串。

  把每种输入的正确输出也记录一下。完成了这个工作后,代码的功能就完全定义下来了。

  现在,我们开始编写代码。我的编码思路是这样的:分为两步,第一步计算左边的空格数量;第二步,将非空格的字符向左移动,覆盖掉左边的空格。

  以下几行代码,计算左边的空格,现在编译一下。CTRL+F7。如果编译通过,测试就会自动运行。

  我们可以看到,输入是什么,执行了哪些代码,产生了什么输出。这里,黑色的是当前输入下所执行的代码,未执行的话会显示为红色。这里全是黑色,表示当前输入下执行了全部代码。如果我们想看一下计算左边空格的结果对不对,这是内部的数据,要指定位置后才会打印出来。按ESC键回到开发环境。

 用这种语法可以输出内部数据,适合于程序员开发过程中使用。复杂类型也可以用同样的语法输出。

  另一种输出内部数据的语法是,在左边的代码窗口,在要输出的位置点击一下,右键菜单选择“输出内部数据”,这样填一下就行了。这种方式不会修改产品代码,适合于测试员使用。

  再次执行后,可以看到,左边的空格的数量是4,这是对的,那我们可以继续编写。

  (待续)


posted @ 2011-12-12 14:30 顺其自然EVO 阅读(153) | 评论 (0)编辑 收藏

测试人员如何做到更好的沟通

 软件项目从起头到竣事,始终都贯穿着频繁的沟通。可是一个很普遍的现象就是沟通成本往往会远远超出了预期,从而大大降低了工作的效率。

  熟悉沟通成本

  沟通是必需的,可是沟通存在“巨大”成本。这个成本表现在:

  1、沟通无法实现100%的信息传递,因为信息失踪导致的成本。

  2、沟通自己存在的时间和空间成本。

  3、因为一次沟通不到位,导致发生后续多次沟通。

  可是这些沟通是否通顺,有很多角色配合抉择,好比PRD,开发,测试

  那从测试人员的角色来说,如何才能降低沟通成本呢?

  1、选择正确的沟通路子

  选择正确的沟通路子对于确保完成沟通方针起到很是主要的浸染。在软件项目开发中,存在各种各样的沟通。可能因为沟通的对象分歧,也可能因为沟通的内容分歧,我们可能需要选择分歧沟通途径。最有用的是face to face的沟通,尤其是在需求评审阶段。有些争议复杂的用文字容易呈现歧义的问题,面对面以及电话沟通往往是最直接最有用最清楚的。

  2、使表述的内容易于理解

  沟通的坚苦往往在于无法把想要讲述的内容以一种对方容易理解的方式呈现给对方。作为测试人员, bug的描述必然要清楚,主题要简明简要,场景规范要描述清楚,好比测试帐号,数据,以及重现的bug。因为,有些争议小的bug开发可能经由过程看主题就已经可以定位了,不需要在看繁琐的程序。那场景规范描述的是否清楚直接影响到测试人员和开发之间的沟通成本。

  3、沟通技巧

  先礼后“兵”,测试和开发的沟通在整个项目过程中都是很主要的一个环节。作为测试人员必然要在明晰自己的立场(保障项目质量和用户需求)的同时,注重和开发同窗沟通的质量。先礼后“兵”很主要,有问题先要很好的沟通,需要的时候可以从他们的立场出发去追求打破,不要破损和开发之间的友好关系,可是在问题得不到解决,或者会直接影响到项目的进度及质量的时候,也要坚定的向上一级追求辅佐,让更有讲话权的人来作出沟通和确定。

posted @ 2011-12-12 14:17 顺其自然EVO 阅读(181) | 评论 (0)编辑 收藏

如何有效实现软件的需求管理(7)

如何有效实现软件的需求管理(7)

 【本篇为《如何有效实现软件的需求管理》第七篇,(第一篇第二篇第三篇第四篇第五篇第六篇)】

  版本控制:

  在我们公司的实际需求管理中,需求的版本控制用的地方非常多,比如

  第一,因为一个需求从获取到最终能拿去开发,中间也需要经历非常多次的改动。既然有改动,就肯定也会出现类似写代码一样,这次写错了,想看看上次的这类情况,所以还是需要能看到不同的版本。

  第二,有时候,一个需求改了N次,到最后想比较各个版本看看,来得出一个最终版本。

  第三,还有种情况,改了很久,突然发现前面有一次不错,所以想回滚到原来的设计。

  第四,当经常有变更的时候,开发和测试就需要获取最新的设计文档,这时候版本控制总是能让他们马上获取最新的版本。

  第五,有些项目我们会使用软件基线(Baseline),而基线也是版本控制的一部分。

  在DevSpec中,版本控制我们用到的功能就是版本功能与基线功能,基本上能达到我们的预期。


 可跟踪性

  可跟踪性的强大来源于数据记录与数据挖掘的强大,我们现在用的DevSpec中,对任何操作,任何数据变化,几乎都会记录下来,谁在什么时间做了什么事情都能一目了然,这样的好处也是显而易见的,简单而言,一方面,这些数据记下来,以后万一出现任何“纠纷”都能有理可依了;另一方面,当然也能知道谁真的在干事,奖励起来也有了依据;还有一方面,完整的数据记录给报表提供了最真实的依据,能让你最正确的分析过去,处理现在和面对未来。

  需求管理的五点要求基本上讲完了,可以说这些要求能很好的实现的话,基本上你们的需求管理水平已经是不错了,这也是产品能够成功的前提条件了。

  当然,好的产品最终还是需要好的设计的,管理只是能增加产品成功的可能性,但是没有好的设计产品绝对是不能成功的。

  不过今天咱们是在说管理,所以还是回到管理上来说吧,我们公司内部曾经做过评估,发现用了DevSuite系统以后,整个实际效率提高了80%,产品的质量水平提高了200%,员工的积极性也有明显的上升。显而易见,用了管理工具以后,效果还是很明显的。

  所以要有效地实现需求管理,关键是要把需求管理所要涉及到管理点管好,由于现代软件的规模已经无法再用纯手工/半手工的方式来管理需求了,所以采用一个好的工具无疑是一个好的解决方法。

  (全文完)

posted @ 2011-12-12 14:07 顺其自然EVO 阅读(151) | 评论 (0)编辑 收藏

Java的NIO以及线程并发

 一、NIO的出现

  NIO是JDK1.4里面才出现的东东,他给大家带来的最大好处是异步socket。其它file,pipe暂时就不多谈了。

  在JDK1.4出现之前,如果你需要编写一个Java服务器,为了实现异步操作,你必须为每个连接请求生成一个Java线程,当连接请求很多时,线程的调度,上下文切换,所付出的代价是非常昂贵,而且由于Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,因此传统的Java服务器编程架构是低效的且代价贵,dl大侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,但是相比之与C、C++写出来的服务器,java服务器在性能要求很高的情况下,基本上没有什么竞争力,甚至是入围的权利的都没有。

  二、异步socket的实现

  NIO出现后,好像让java的程序员有了杨眉吐气的机会,怎么个吐气法,当时大家是个什么感受,俺是不知道,因为当时俺不搞java,对java的认识有限。

  NIO是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去做你的事情,没事件时你大可以节约大把时间去做其它任何事情。而且NIO的主线程only one,不像传统的模型,需要N个线程去,也减轻了JVM的工作量,使得JVM处理任务时显得更加高效。

  刚开始接触NIO时,被N层的Channel架构、网上铺天盖地的好评给镇住了,想想也应当是个很成熟的产品了,网上资料这么多,抄一抄Jetty、Tomcat以及其它一些牛B的源代码,基本上就能搞定了,此时没有想到大家受同步的影响这么深,也没有想到连最基本的异步概念都没有搞清楚就去写代码,搞出一堆的问题来(这是后话,后面再说)。

  现在研究了NIO以后,发现NIO实际上在Java中做的工作是很简单,就是将事件进行收集和分发,我们结合一个经典的调用例子来说明这个问题,我就不从NIO的基本使用说起了,大家可以查其它的资料,网上一大把。

  当Channel注册至Selector以后,我们的最经典的调用方法,是这样子的。

  • while(somecondition) 
  •         int n = selector.select(TIMEOUT); 
  •         if(n == 0continue
  •         for (Iterator iter = selector.selectedKeys().iterator(); iter.hasNext();) 
  •         { 
  •         if (key.isAcceptable()) 
  •             doAcceptable(key); 
  •         if (key.isConnectable()) 
  •             doConnectable(key); 
  •         if (key.isValid() && key.isReadable()) 
  •             doReadable(key); 
  •         if (key.isValid() && key.isWritable()) 
  •             doWritable(key); 
  •       iter.remove(); 
  •     } 
  • }
  •   这只是个小例子啊,什么异常我就懒得抓了。

      nio中取得事件通知,就是在selector的select事件中完成的,在selector事件时有一个线程,这个线程具体的处理简单点说就是:向操作系统询问,selector中注册的Channel&&SelectionKey的偶对各种事件是否有发生,如果有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。程序员发现这个值>0,表示有事件发生,马上迭代selectedKeys中的SelectionKey,根据Key中的表示的事件,来做相应的处理。

     实际上,这段说明表明了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)全部交给操作系统自己去完成,异步的核心Selector,不过是将这些调度收集、分发而已。因为操作系统的socket、线程调度再咋D也比你JVM中要强,效率也高。

      而且就算jvm做的和操作系统一样好,性能一样高(当然这是不现实的),使用异步socket你至少也节约了一半的系统消耗,想想假定操作系统本身也是使用线程来维护N个socket连接,在传统的java编程中,你还必须为这些socket还多起一个java线程,那至少是2N个线程,现在只需要N+1。在高并发的情况下,你自己去想吧。

      懂了这个道理,异步socket也就好写了,也不会搞得思路混乱了。

      三、异步Socket中应当注意的事情

      1、读

      异步socket最基本的理念就是事件通知,前面也说了,有事件通知你了,你才该做你应当做的事情。在异步socket中当注册了一个OP_READ事件后,你就等着Selector通知你吧,如果没有通知你,你在家睡大觉都行。

      在这里,我们有人出现的错误就是受同步的影响,自己去主动读,而且还搞出了多线程,如果仔细考虑一下,就不会出现这个问题了。同步socket中,调用read方法读取IO中的数据时,通常情况下如果没有数据read方法会阻塞,且是同步的,所以当多个线程同时访问时,read方法是线程安全的。

      而在异步下就不同,异步是不会阻塞的,有什么就返回什么,你主动去读,只要有数据,你就可以拿走,在多线程的情况下,也许你是想让第一个线程读取,but此时来数据时正好是线程2读到了,那线程2就高高兴兴的拿去,而线程1还在苦苦等待,这样导致数据混乱不说,如果后面再也不来数据了,线程1就是死循环啦。

      2、写

      在异步socket中,写是唯一一个主动点的操作,但是也不能直接去写Channel,而是应当先把自身注册为OP_WRITABLE,这时Selector就会发现你的存在,并把给发一个write事件,你这时后就可以写了,不过这时候有个小小的技巧,就是你执行写操作之前,请取消掉你的写注册,否则你的cpu肯定是100%。

      3、等待

      在传统的服务器编程中,由于对于每个请求都是产生的一个线程,因此你在你每个请求线程中wait也好,sleep也好,不会影响别人。但是异步不同,他的主线程只有一个,基本上每个处理都是线性的,也就是说处理完第一个,然后才能处理第二个,因此nio是一个极好的处理短连接的架构。

      我们现在出现的问题是,有人受同步的影响,没有搞清异步是如何处理,竟然在方法处理中用上sleep,而且一等还是3秒,这意味着什么,3秒才能处理一个请求,My god,我要一个3秒才能处理一个请求的服务器干嘛啊,还是60年代啊

      如果出现这样的需要等待的情况,应当另起一个线程(推荐使用线程池)去完成这个“长”时间的任务,或者将其它交给一个消息队列,通过发消息的方式将给别人去完成也行,客户端能等,你服务器怎么也能等呢?写出这样的代码,基本上一个服务器也就废了。

    posted @ 2011-12-12 13:52 顺其自然EVO 阅读(230) | 评论 (0)编辑 收藏

    仅列出标题
    共394页: First 上一页 348 349 350 351 352 353 354 355 356 下一页 Last 
    <2024年11月>
    272829303112
    3456789
    10111213141516
    17181920212223
    24252627282930
    1234567

    导航

    统计

    常用链接

    留言簿(55)

    随笔分类

    随笔档案

    文章分类

    文章档案

    搜索

    最新评论

    阅读排行榜

    评论排行榜