码迷,mamicode.com
首页 > 其他好文 > 详细

性能分析与调优的原理

时间:2015-09-23 16:53:45      阅读:196      评论:0      收藏:0      [点我收藏+]

标签:

最近一直纠结性能分析与调优如何下手,先从硬件开始,还是先从代码或数据库。从操作系统(CPU调度,内存管理,进程调度,磁盘I/O)、网络、协议(HTTP, TCP/IP ),还是从应用程序代码,数据库调优,中间件配置等方面入手。

  单一个中间件又分web中间件(apache 、IIS),应用中间件(tomcat 、weblogic 、webSphere )等,虽然都是中间件,每一样拎出来往深了学都不是一朝一夕之功。但调优对于每一项的要求又不仅仅是“知道”或“会使用”这么简单。起码要达到“如何更好的使用”。

  常看到性能测试书中说,性能测试不单单是性能测试工程师一个人的事儿。需要DBA 、开发人员、运维人员的配合完成。但是在不少情况下性能测试是由性能测试人员独立完成的,退一步就算由其它人员的协助,了解系统架构的的各个模块对于自身的提高也有很大帮助,同进也更能得到别人的尊重。

  再说性能调优之前,我们有必要再提一下进行测试的目的,或者我们进行性能测试的初衷是什么?

  能力验证:验证某系统在一定条件具有什么样的能力。

  能力规划:如何使系统达到我们要求的性能能力。

  应用程序诊断:比如内存泄漏,通过功能测试很难发现,但通过性能测试却很容易发现。

  性能调优:满足用户需求,进一步进行系统分析找出瓶颈,优化瓶颈,提高系统整体性能。

  技术分享

  一般系统的瓶颈                                                                                          

  性能测试调优需要先发现瓶颈,那么系统一般会存在哪些瓶颈:

  硬件上的性能瓶颈

  一般指的是CPU、内存、磁盘I/O 方面的问题,分为服务器硬件瓶颈、网络瓶颈(对局域网可以不考虑)、服务器操作系统瓶颈(参数配置)、中间件瓶颈(参数配置、数据库、web服务器等)、应用瓶颈(SQL 语句、数据库设计、业务逻辑、算法等)。

  应用软件上的性能瓶颈

  一般指的是应用服务器、web 服务器等应用软件,还包括数据库系统。

  例如:中间件weblogic 平台上配置的JDBC连接池的参数设置不合理,造成的瓶颈。

  应用程序上的性能瓶颈

  一般指的是开发人员新开发出来的应用程序。

  例如,程序架构规划不合理,程序本身设计有问题(串行处理、请求的处理线程不够),造成系统在大量用户方位时性能低下而造成的瓶颈。

  操作系统上的性能瓶颈

  一般指的是windows、UNIX、Linux等操作系统。

  例如,在进行性能测试,出现物理内存不足时,虚拟内存设置也不合理,虚拟内存的交换效率就会大大降低,从而导致行为的响应时间大大增加,这时认为操作系统上出现性能瓶颈。

  网络设备上的性能瓶颈

  一般指的是防火墙、动态负载均衡器、交换机等设备。

  例如,在动态负载均衡器上设置了动态分发负载的机制,当发现某个应用服务器上的硬件资源已经到达极限时,动态负载均衡器将后续的交易请求发送到其他负载较轻的应用服务器上。在测试时发现,动态负载均衡器没有起到相应的作用,这时可以认为网络瓶颈。

  性能测试出现的原因及其定位十分复杂,这里只是简单介绍常见的几种瓶颈类型和特征,而性能测试所需要做的就是根据各种情况因素综合考虑,然后协助开发人员\DBA\运维人员一起定位性能瓶颈。

一般性能调优步骤                                                                                      

  一般性能问题调优的步骤:

  步骤一:确定问题

  应用程序代码:在通常情况下,很多程序的性能问题都是写出来的,因此对于发现瓶颈的模块,应该首先检查一下代码。

  数据库配置:经常引起整个系统运行缓慢,一些诸如oracle 的大型数据库都是需要DBA进行正确的参数调整才能投产的。

  操作系统配置:不合理就可能引起系统瓶颈。

  硬件设置:硬盘速度、内存大小等都是容易引起瓶颈的原因,因此这些都是分析的重点。

  网络:网络负载过重导致网络冲突和网络延迟。

  步骤二:确定问题

  当确定了问题之后,我们要明确这个问题影响的是响应时间吞吐量,还是其他问题?是多数用户还是少数用户遇到了问题?如果是少数用户,这几个用户与其它用户的操作有什么不用?系统资源监控的结果是否正常?CPU的使用是否到达极限?I/O 情况如何?问题是否集中在某一类模块中? 是客户端还是服务器出现问题? 系统硬件配置是否够用?实际负载是否超过了系统的负载能力? 是否未对系统进行优化?

  通过这些分析及一些与系统相关的问题,可以对系统瓶颈有更深入的了解,进而分析出真正的原因。

  步骤三: 确定调整目标和解决方案

  得高系统吞吐理,缩短响应时间,更好地支持并发。

  步骤四:测试解决方案

  对通过解决方案调优后的系统进行基准测试。(基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试)

  步骤五:分析调优结果

  系统调优是否达到或者超出了预定目标?系统是整体性能得到了改善,还是以系统某部分性能来解决其他问题。调优是否可以结束了。

  最后,如果达到了预期目标,调优工作就基本可以结束了。

  下面算是一个技巧,如面试官问到一个性能问题假设,我不知道性能问题出在哪儿时,可以按照这个思路回答^_^

  • 查找瓶颈时按以下顺序,由易到难。

  服务器硬件瓶颈---〉网络瓶颈(对局域网,可以不考虑)---〉服务器操作系统瓶颈(参数配置)---〉中间件瓶颈(参数配置,数据库,web服务器等)---〉应用瓶颈(SQL语句、数据库设计、业务逻辑、算法等)

  注:以上过程并不是每个分析中都需要的,要根据测试目的和要求来确定分析的深度。对一些要求低的,我们分析到应用系统在将来大的负载压力(并发用户数、数据量)下,系统的硬件瓶颈在哪儿就够了。

  • 分段排除法 很有效

  性能测试调优应该注意的要点:


  要点1: 在应用系统的设计开发过程中,应始终把性能放在考虑的范围内。    
  要点2: 确定清晰明确的性能目标是关键。    
  要点3: 必须保证调优后的程序运行正确。    
  要点4: 系统的性能更大程度上取决于良好的设计,调优技巧只是一个辅助手段。    
  要点5: 调优过程是迭代渐进的过程,每一次调优的结果都要反馈到后续的代码开发中去。    
  要点6: 性能调优不能以牺牲代码的可读性和可维护性为代码。

  本文只介绍了一些性能调优的要关注的东西以及性能调优的一般要点。并没有具体说如何对系统的每个部件进行调优,如何要细说也不是一两书能说清的,对知识面的要求也非常高,是我目前的能力无法触摸的。

  这里做个总结:

  《性能测试知多少》系列基本完结,虽然时间拉得比较长,但我没有把它给太监。虽然内容都在空谈性能测试理论知识,但我认为这些东西对于你从事性能测试工作必不可少。当然,我在“ jmeter基础 ” 与“ loadrunner 技巧 ” 中讲解两个性能测试工具的使用。

  如果我的这些文章对于想了解和学习性能的同学带来一丝的帮助,我将非常开心。我不是高手,只是和你一起热爱测试技术的初学者,只是比较喜欢总结;也时常为前途迷茫,但我知道只要断去学习,路就在前方。我后面会整理性能调优的相关文章。

 

 

 

 

 

 

动态的 Web 应用程序能够存储大量信息,让用户能够通过熟悉的界面立即访问这些信息。但是,随着应用程序越来越受欢迎,可能会发现对请求的响应速度没有以前那么快了。 开发人员应该了解 Web 应用程序处理 Web 请求的方式,知道在 Web 应用程序开发中可以做什么,不能做什么,这有助于减少日后的麻烦。

静态的 Web 请求(比如图 1 所示的请求)很容易理解。客户机连接服务器(通常通过 TCP 端口 80),使用 HTTP 协议发出一个简单的请求。


图 1. 客户机通过 HTTP 请求静态的文件
技术分享

服务器解析这个请求,把它映射到文件系统上的一个文件。然后,服务器向客户机发送一些描述有效负载(比如网页或图像)的响应头,最后向客户机发送文件。

在 上面的场景中可能出现几个瓶颈。如果请求的变化很大,导致无法有效地使用操作系统的磁盘缓存,那么服务器的磁盘会很忙,到了某种程度之后,就会减慢整个过 程。如果为客户机提供数据的网络通道饱和了,就会影响所有客户机。但是,除了这些状况之外,“接收请求,发送文件” 过程还是相当高效的。

通 过做一些假设,可以大致体会静态服务器的性能。假设一个请求的服务时间是 10ms(主要受到磁头寻道时间的限制),那么大约每秒 100 个请求就会使磁盘接近饱和(10msec/request / 1 second = 100 requests/second)。如果要发送 10K 的文档,就会产生大约 8mbit/sec 的 Web 通信流(100 requests/second * 10 KBytes/request * 8bits/byte)。如果可以从内存缓存中获取文件,就可以降低平均服务时间,因此增加服务器每秒能够处理的连接数。如果您有磁盘服务时间或平均请求 延时的真实数据,可以把它们放进上面的算式,从而计算出更准确的性能估计值。

既然服务器的处理容量是平均请求服务时间的倒数,那么如果服务时间加倍,服务器的处理容量(每秒处理的连接数)就会减半。请记住这一点,下面看看动态应用程序的情况。

动态应用程序的流程依赖于应用程序的具体情况,但是一般情况下与图 2 相似。


图 2. 客户机通过 HTTP 请求动态页面
技术分享

与前一个示例中的客户机一样,图 2 中的客户机首先发出一个请求。静态请求和动态请求之间实际上没什么差异(有时候 .php 或 .cgi 等扩展名可能意味着动态请求,但是它们可能引起误解)。如何处理请求是由 Web 服务器决定的。

图 2 中,请求被发送到一个应用服务器,比如运行一个 Java™ 应用程序的 Solaris 系统。应用服务器执行一些处理,然后向数据库查询更多的信息。得到这些信息之后,应用服务器生成一个 HTML 页面,这个页面由 Web 服务器转发给客户机。因此,这个请求的服务时间是几个部分的总和。如果数据库访问花费 7ms,应用服务器花费 13ms,Web 服务器花费 5ms,那么网页的服务时间就是 25ms。根据前面介绍的倒数规则,各个组件的容量分别是每秒 142、77 和 200 个请求。因此,瓶颈是应用服务器,它使这个系统每秒只能处理 77 个连接;超过这个数量之后,Web 服务器被迫等待,连接开始排队。

但 是,一定要注意一点:因为系统每秒只能分派 77 个连接,而一个连接需要的处理时间是 25ms,所以并非每个应用程序用户的请求都能够在 25ms 内得到处理。每个组件每次只能处理一个连接,所以在高峰负载下,请求不得不等待 CPU 时间。在上面的示例中,考虑到排队时间和 25ms 的处理时间,平均请求服务时间最终会超过 1.1 秒。关于解决这些排队问题的更多信息,请参见 参考资料

通过这些示例可以得出以下结论:

  • 在用户发出请求和获得最终页面之间的步骤越多,整个过程就越慢,系统容量就越低。

  • 随着页面请求速率的增加,这种效应会越来越显著。

  • 在项目开始时做出的体系结构决策也会影响站点处理负载的能力。

本文的其余部分将深入讨论这些问题。

用于动态站点的 N 层体系结构

应用程序(包括 Web 应用程序)的体系结构常常按照层来描述。静态站点可以被看作只有一层 —— Web 服务器。如果用 Web 服务器运行某种脚本语言(比如 PHP),从而连接数据库,那么这可以看作两层。前一节中的示例有三层,即前端 Web 服务器、应用服务器和数据库。

一 个软件也可能由多层组成,这取决于您谈话的对象。例如,PHP 脚本可能使用一个模板引擎把业务逻辑与表示分隔开,它可以被看作单独的两层。Java 应用程序可能通过 Java servlet 执行表示任务,servlet 通过与 Enterprise Java Bean (EJB) 通信执行业务逻辑,EJB 通过连接数据库获取更多信息。因此,换一个角度来看,三层体系结构可能是另一副样子,尤其是在涉及不同的工具集时。

常见的体系结构

尽管应用程序的体系结构各不相同,但是有一些常见的体系结构趋势。在一般情况下,应用程序需要四个功能层:

  • 客户机层

  • 表示层

  • 业务逻辑层

  • 数据层

在 Web 应用程序中,客户机层由 Web 浏览器处理。浏览器显示 HTML 并执行 Javascript(以及 Java applet、ActiveX 或 Flash applet),从而向用户显示信息和收集用户信息。表示层是从服务器到客户机的接口,它负责控制输出的格式,让输出可以在客户机上显示。业务逻辑层实施 业务规则(比如计算和工作流),从而驱动应用程序。最后,数据访问层是持久化的数据存储,比如数据库或文件存储。

大多数应用程序需要所有这四层的功能,尽管它们可能不需要明显完整地实现这些层。

另一种流行的体系结构是 Model-View-Controller,这是一种用于分隔应用程序组件的模式。在 MVC 模式中,模型封装业务逻辑层,并与框架一起封装数据层。视图负责处理发送给客户机的数据表示。控制器的作用是控制应用程序流程。

层的容量扩展

扩 展 Web 应用程序的容量意味着让它能够处理更多的通信流。容量扩展的一个方面是如何根据需求部署硬件。另一个方面是应用程序如何响应新的硬件环境。从概念上说,在 出现性能问题时,往往首先想到使用功能更强的服务器;但是应用程序本身很可能造成其他瓶颈。把应用程序划分为一系列层有助于收缩问题的范围,可以简化容量 扩展。

现在先不考虑应用程序瓶颈。扩展应用程序的硬件通常有两种方式:水平扩展垂直扩展。水平扩展意味着在一层中添加更多的服务器。在前面的示例中,应用服务器的瓶颈把请求速率限制在每秒 77 个请求,通过添加第二个应用服务器并在两个服务器之间共享负载,可能可以解决此问题。这会把理论容量提高到每秒 154 个请求,瓶颈位置就会转到数据库。

另一方面,垂直扩展意味着使用功能更强的计算机。可以使用功能更强的计算机运行应用服务器的两个实例,或者更快地处理请求。

初 看上去,您可能会完全排除垂直扩展方式,因为购买多台小型计算机通常比不断购买更高级的服务器便宜。但是,在许多情况下,垂直扩展是更好的方法。如果您有 通过逻辑分区 (LPAR) 支持硬件分区的 IBM® Power® 服务器,就可以把空闲的容量添加到应用服务器层。

应用程序的需求也可能促使您选择垂直扩展。在一台服务器上很容易通过共享内存段共享用户的会话状态。如果使用两台服务器,就需要通过其他方式共享状态,比如数据库。数据库访问比内存访问慢,所以两台服务器的处理速度达不到一台服务器的两倍。

数据库是另一个常常适合使用垂直扩展的场合。让数据集跨越不同的服务器需要在应用程序层做大量工作,比如跨两个数据库联结列并确保数据是一致的。使用更强大的数据库服务器要容易得多,而且不需要通过重新构建应用程序来支持分散的数据。

把 Web 应用程序建模为队列

根据前面对应用程序体系结构的讨论可以看出,Web 请求会通过多个阶段,每个阶段花费一定的执行时间。请求排队通过每个步骤,完成一个步骤之后,再排队进入下一个步骤。每个步骤很像人们在商店里排队结帐的情况。

可以把 Web 应用程序建模为一系列步骤(称为 “队列”)。应用程序的每个组件都是一个队列。建模为一系列队列的典型 WebSphere 应用程序如图 3 所示。


图 3. 建模为排队网络的 WebSphere® 应用程序
技术分享

图 3 显示请求等待 Web 服务器处理它们,然后等待 Web 容器,依此类推。如果进入某个队列的请求速率超过了此队列处理请求的速率,请求就会聚集起来。当出现请求聚集时,服务时间是不可预测的,用户会察觉到浏览器会话延迟。图 3 中的队列代表最糟糕的情况,因为 Web 服务器可以自己处理一些请求,即不需要访问数据库。

队 列在 UNIX® 环境中很常见。当应用程序发出磁盘请求的速率快于磁盘返回数据的速率时,操作系统会让磁盘请求排队,还可能调整请求的次序以降低寻道时间。另一个队列是运 行队列,其中包含等待运行的进程的有序列表。应用程序会等待轮到它们使用某些有限的资源(比如 CPU)。

因此,队列调优是一种平衡的艺术。队列太小,就会在仍然有富余容量的情况下拒绝用户。队列太大,就会试图为过多的用户提供服务,导致性能很差。

导 致情况更复杂的另一个因素是,这些排队位置并不是无成本的。保留排队位置会导致内存开销,对于应用服务器,这会与正在处理请求的线程争用内存。因此,在一 般情况下,在应用服务器上排队并不是好方法。推荐的方法是在应用服务器之前(比如在 Web 服务器上)排队。这意味着 Web 服务器要保持与 Web 客户机的连接,并在应用服务器空闲时发出请求。应用服务器只需处理它能够及时派发的请求。

IBM 的文档中推荐了 Web 应用程序布局方法和各种队列的调优方法。但是注意,IBM 建议应该避免在 WebSphere 中排队。这意味着应该把发送给 WebSphere 应用服务器的请求速率控制在能够立即处理的范围内。Web 服务器(或 Web 服务器前面的代理服务器)应该限制过多的连接,让它们等待处理。这确保负载比较重的应用服务器队列能够把时间花在为有限的请求提供服务上,而不是试图同时 为所有请求提供服务。

针对开发人员的提示

作为开发人员,应该按照一些一般原则提高应用程序的可伸缩性。这些原则可以应用于大多数 Web 应用程序。

度量设施

应 用程序应该以某种方式向收集系统提供度量值(即使收集系统仅仅是日志文件)。这些度量值包括访问应用程序中某个函数的频率或处理一个请求花费的时间等。这 并不会使应用程序运行得更快,但是有助于了解应用程序为什么会变慢以及代码的哪些部分花费的时间最长。了解什么时候调用某些函数,这有助于把在系统上观察 到的现象(比如 CPU 忙或磁盘活动量高)与应用程序中的活动(比如上传图像)联系起来。

能够了解站点上发生的情况,这是扩展站点容量的关键。您认为不够优化的代码部分可能不会造成问题。只有通过适当的度量,才能发现真正的瓶颈。

会话

Web 在本质上是无状态的。用户发出的每个请求都独立于以前的请求。但是,应用程序常常是有状态的。用户必须登录应用程序以证明自己的身份,在访问站点期间可能 要维护购物车的状态,还可能要填写供以后使用的个人信息。跟踪会话是一种成本很高的操作,尤其是在涉及多个服务器的情况下。

在单 一服务器上运行的 Web 应用程序可以把会话信息放在内存中,在服务器上运行的任何 Web 应用程序实例都可以访问共享内存。常常会给用户分配一个标志,这个标志标识内存中的会话。考虑一下在涉及第二个应用服务器时会发生什么。如果用户的第一个 请求发送给一个服务器,第二个请求发送给另一个服务器,那么会存在两个单独的会话,它们并不相同。

此问题的常用解决方案是,把会话存储在数据库而不是内存中。这种方法导致的问题是,对于每个请求,需要增加数据库读操作,还可能涉及数据库写操作。每个 Web 应用服务器都需要这个数据库。

一个解决方案是,只在需要会话的地方使用会话。应用程序并不为每个请求装载会话,而是只在需要会话时装载会话。这会减少对后端数据库的请求数量。

另 一个方法是加密会话数据并把它发送回客户机,这样就不需要在本地存储会话。在用户的 cookie 中能够存储的数据量是有限的,但是 RFC 2109 规定客户机应该能够为每个域名存储至少 20 个 cookie,每个 cookie 至少可以保存 4K 字节的数据。

如果发现用数据库存储的会话是性能瓶颈,而且无法消除它们,那么应该考虑把它们分散到单独的数据库,甚至是多个数据库。例如,可以在一个数据库中存储偶数的会话 ID,在另一个数据库中存储奇数的会话 ID。

缓存

与其他部分相比,应用程序的某些部分会更频繁地修改数据。新闻网站可能每个月只修改顶级分类列表一次。因此,对于每个请求都通过查询数据库获取最新的分类列表是很浪费的。同样,包含新闻稿的页面在其整个生命周期中可能只修改一两次,所以不需要为每个请求重新生成它。

缓存意味着把处理成本很高的请求的结果存储起来,供以后使用。可以缓存分类列表或整个页面。

在考虑缓存时,问自己一个问题:“这些信息必须是最新的吗?” 如果不是这样,就可以考虑使用缓存。在新闻最初出现时,能够及时改变新闻稿可能很重要;但是在以后,每分钟检查一次修改并通过缓存提供页面,就足够了。

一种补充方法是,当底层数据改变时,让缓存的数据项失效。如果修改了新闻稿,在保存它时可以删除缓存的版本。对于下一个请求,由于没有缓存的版本,所以会生成新的数据项。

在使用缓存时,必须注意在缓存项过期或被删除时发生的情况。如果有许多请求在请求缓存项,那么在缓存项过期时,会为许多用户重新生成缓存项。为了解决这个问题,可以只为第一个请求重新生成缓存,而其他用户使用过时的版本,直到新的缓存项可用为止。

memcached 是一种流行的分布式内存缓存系统,在 UNIX 环境中部署的许多应用程序都使用它。服务器运行 memcache 守护进程的实例,这些进程分配一块可以通过一种简单的网络协议访问的 RAM。希望在 memcache 中存储或获取数据的应用程序首先对键进行散列计算,这告诉它们应该使用 memcache 池中的哪个服务器。然后,通过连接这个服务器检查或存储数据,这比磁盘或数据库访问快得多。

在寻找应该缓存的数据时,还应该考虑是否确实需要直接提供这些信息。需要在每个页面上显示用户的购物车吗?只显示总金额怎么样?或者只显示一个简单的链接 “view the contents of your cart”。

Edge-Side Includes (ESI) 是一种标记语言,可以用它把网页划分为单独的可缓存的实体。应用程序负责生成包含 ESI 标记的 HTML 文档,还负责生成组件。Web 应用程序前面的代理缓存根据各个部分重新组装最终的文档,负责缓存一些组件并为其他组件发出请求。清单 1 给出一个 ESI 文档示例。


清单 1. ESI 示例

 

<html>
<head>
</head>
<body>
<p>This is static content</p>
<esi:include src="/stories/123" />
<p>The line above just told the proxy to request /stories/123 and insert 
 it in the middle of the page </p>
</body>
</html>

 

 

尽管这个示例非常简单,但是 清单 1 说明了如何把两个文档拼接在一起,这两个文档有自己的缓存规则。

异步处理

还 有一个问题与 “这些信息必须是最新的吗?” 相关:“必须在处理完请求时更新这些信息吗?” 在许多情况下,可以获取用户提交的数据并把处理延后几秒,而不需要在处理信息时让用户一直等待装载页面。这称为异步处理。一种常用方法是,让应用程序把数 据发送给一个消息队列,比如 IBM WebSphere MQ,等待到资源可用时处理数据。这样就可以立即把一个页面返回给用户,尽管数据处理的结果还是未知的。

请考虑一个电子商务应用 程序,用户会在这个程序中提交订单。立即返回信用卡检验结果可能是很重要的,但是不需要让订单系统马上确认订单的所有内容都是有效的。可以把订单放进一个 队列中等待处理,这可能会在几秒内发生。如果发生了错误,可以通过电子邮件通知用户,如果用户仍然在网站上,甚至可以把错误通知插入他的会话。另一个示例 是报告。不需要让用户一直等待生成报告,而是可以返回 “please check the reports page in a few minutes” 消息,同时在另一台服务器上异步地生成报告。

结束语

应用程序常常采用分层方式编写。表示逻辑与业务逻辑分隔开,业务逻辑又与持久化存储分隔开。这种方式可以提高代码的可维护性,但是也会导致一些开销。在扩展应用程序的容量时,应该了解数据在分层环境中的流动并寻找出现瓶颈的位置。

缓存和异步处理等技术可以重用以前的结果或把工作转移到另一台计算机上,从而降低应用程序的工作负载。在应用程序中提供度量设施,有助于及时了解 “热点”。

应用服务器环境的工作方式与排队网络很相似,一定要仔细地管理队列的大小,确保一层不会对另一层施加过大的压力。IBM 建议尽可能在应用服务器之前排队,比如在外部 Web 服务器或代理服务器上。

仅仅靠投入更多的硬件,很少能够有效地扩展应用程序的容量。常常需要综合应用这些技术,才能让新的硬件发挥作用。

 

性能分析与调优的原理

标签:

原文地址:http://www.cnblogs.com/qmfsun/p/4829981.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!