系统架构-性能

本文探讨了系统架构随业务增长的扩展策略,包括应用切割、负载均衡、缓存及数据库管理等方面的技术要点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      

      本文牵扯的面积可能会比较泛,或者说比较大,在这个层面很多人也有自己的见解,所以我这也仅仅是抛砖引玉,结合前面讲述的一些基础技术,从思想中阐述更为深入的架构思想基础,因为最好的架构思想是架构师结合实际情况思考出来最适合的架构,这里仅仅说明下一些常用的原理和思想,主要包含的内容有(内容很泛,所以都是简单阐述入门知识,具体后续深入探讨):

1、app切分集群组扩展

2、app集群组负载均衡

3、Memcached原理

4、db cache应用

5、db存储类型以及存储cache说明

6、存储条带思想

7、数据库集群

8、数据库分布式存储

9、数据库容灾备份以及监控

10、nosql思想

11、无锁分析

 

 

 

 

1、app切分集群组扩展

 

 

     应用系统架构随着外部并发量的增加,必然导致的是app应用的压力逐渐增加,并且绝大部分app对于线程的分配能力都是有限的,但是app应用在扩展上是非常容易的,最基础的就是一种应用的垂直切割,其次是水平切割。

 

     在了解app切割原理的基础上先来了解一个其他的概念就是,就是Internate的路由器如何路由你请求的一个URL,你发送的URL并不知道路由器发送到哪里去了,最终路由到远端的服务器(互联网本身就是一个云计算为基础的平台),到了远程后,它如何找到自己的应用呢,以及应用下具体的服务内容呢?就是通过URL后面部分的标识符号。也就是在云的最终技术也是各自管理各自的内容,而云之所谓称之为云是因为你无须关心你发送的URL是如何路由到远端的服务器的,又是如何通过哪些路由器返回回来的。这里不深入讨论云的概念,回到主题上是说远端的服务器每一个目标都有自己处理的对象,或者说不同的路径下或者不同的端口下都会有自己的处理服务。所以app系统切割的基本思想就是路径。

 

     当一个系统业务十分复杂,应用并发量很大的情况下,必然导致的一个步骤就是业务分解,将一个大的系统拆分为多个小系统,不论是软件本身设计的可扩展性还是软件性能扩展性都会有很大的帮助,比如在各个子系统之间他们的业务的复杂性以及并发量都会有很大的区别,我们可以将这些小系统拆分到不同的集群节点上去,这些集群节点可以是由同一个主机发布出来的不同端口或者URL,或者是不同的主机发布出来的内容,并且三者可以根据实际情况调整使得成本、软件扩展性、性能扩展性达到较好的程度,总之将一个大系统拆分为多个小系统是第一个需要做的,也就是app应用拆分,这种并不难,但是拆分的依据一定要把控好,而且还有一个总体架构,不然软件最终会做的五花八门;在很多的应用下都会使用,只是在这种拆分,拆分后除上述的总体设计要做好外,还需要主意的一点就是系统通信问题,子系统拆分后应该是高内聚的,但是免不了需呀通信,否则就根本不算是一个系统的,而且即使不是同一个系统也有可能因为服务需求而需要去通信,所以在通信上需要多下功夫,在不同服务器以及语言之间通信最麻烦的事情就是字符集,也是需要主意的,不过不是本文的重点。

 

     在上面拆分完成后,当某一个子系统的并发量非常大的时候,我就需要单独对某一个子系统进行拆分了,这种没的选,一般不太可能通过URL来控制(除非申请不同的VIP或者在同一个主机上用不同的虚拟目录来做,不过是不是有点挫),这种一般是通过在不同的服务端口(也称为运行节点,在同一个主机上多个节点肯定是不同的端口的),或者发布到不同的主机上来完成;这部分拆分app应用不会受到太大的限制;这个地方需要主意的是,当你在app内部做静态内存时,就无法做到当一个机器的内存修改后同时修改到其他内存中,除非你自己写程序要么定时刷新要么相互之间传送数据,但是这两种都会付出巨大的成本,如何解决呢,我们后面会说到的Memcached就是解决的方法。

 

     上述两者完成后,新的问题出现了,就是子系统之间的通信,他们不再是单机对单机的通信,而是集群组对集群组的通信,所以子系统中间件必然有一些非一对一的通信机制就出现了,以及中途产生的同步通信和异步通信等机制,如IBM MQ、EJB、Webservice、HttpClient、HttpInvokie、RPC、Socket甚至于借助于数据库或者文件作为中间层等等都是通信机制的基础,应用非常特殊的公司会自己写自己的通信机制。

 

     有了上述的集群,URL可以通过网络路由到具体的服务器,但是服务器下每一个集群节点不可能都去申请一个URL吧,而且客户也不会自己知道我第一次用URL1,下一次用URL2、再下一次用URL3来给你服务器做负载均衡吧,客户只知道用一个URL访问你,而且那么多的IP在互联网上也会占用非常大的资源,所以很多时候,一个大网站后台可能数万的主机,前端暴露的IP可能只有几个,这个可以成为VIP,他们之间有一个绑定关系,由这个VIP来负责域名的帮顶,而VIP一般会绑定在一个负载均衡器上面,由负载均衡器根据实际请求内容负载到具体的主机上面去,下面第二章就是我们要写的负载均衡基本原理。

 

2、app集群组负载均衡

 

      所谓负载均衡就是负载均衡了,呵呵,也就是不让某太机器单独忙,也不让某台机器太闲,将请求进行分发,这就是负载均衡器设计的初衷了。

 

      随着发展的变化,负载均衡器需要承担更大的作用

     第一个需要做的就是请求解析,也就是很多不同的应可能由一个负载均衡器来完成;

     进一步,同一个应用发布的不同的节点或者不同的端口,负载均衡器可以识别出来并达到分发负载,将并发负载到很多不同的节点上去运行;

     再进一步,某个客户端请求第一次访问了某个节点后,当session未失效时,应当做到继续访问同一台主机,这样保证客户在多次交互中session内容是一致的,至少不会导致重新登陆等现象;

     再进一步,在节点失败是,负载均衡器应当识别出来,并可以将访问切换到其他主机,在有必要的情况下需要做session复制。

 

     负载均衡最基本的需要做到以上几点内容才算负载均衡。

     负载均衡器一般需要的内容是全局的,但是它并不关注与细节,所以它主要做的事情是全局资源定位,监控,负载均衡,切换动作;一般会有一个单独的管理节点和单独的分发节点,但是每一门负载均衡的机制在设计层面都会有很大的区别,所以无需一概而论。

    

     因为负载均衡器在所有应用的最前端,所以我们非常关注于它的性能,有很多基于高级语言编写的负载均衡器,甚至于你可以直接通过你的JSP、ASP、PHP等等做一个简单的控制跳转上的负载均衡,但是他们的性能就很低了,扩展性受到明显的限制,Linux内核才是负载均衡器的王道,终极方案,要深入研究和负载均衡的方案,请大家多多参详Linux内核。

 

     目前市面上非常常用的负载均衡器是apache,它本身也可以作为WEB服务器来应用(它的一些模块就可以直接用于php),另外weblogic自带的proxy+domain+managed模式也是一种负载均衡方法,不过我做过几个版本效果不理想,主要原因还是主要是因为实现的基础是高级语言吧;而apache虽然性能不错,而且大家广受喜爱的一种东西,不过随着互联网并发量的上升,apache在很多极为高并发的系统中仍然受到扩展性的限制,于是乎ngnix出现了,它是目前高并发网站应用中最广泛或者说在大网站中用得最多的负载均衡器,国内的大网站基本都有它的影子,它是俄罗斯一位工程师编写,而且是免费的,性能极高,占用资源极少,并且支持cache以及代理,很多客户端访问的机制都可以配置化,安装和使用都非常简单(要深入研究就没那么简单),而且故障率非常低,为什么那么好,因为它的基础就是unux内核,没有别的,当然写代码一定要写得很好才行;当然国内并非没有这样的人才存在,而且要看公司是否给这类人才一个机会去完成这样一个东西,因为自己写的可能会更加适合于自己,其实国内也有很多对Unix内核研究很深入的顶尖高手。

 

3、Memcached原理

      这一章本身就想在数据库后面说明的,不过由于只是简单介绍,而且后面应该几乎都是技术,所以就这这里说明了。

 

      一般应用程序除了处理业务逻辑和一定的计算后,就是访问数据库,做数据库的存、取、事务操作,在OLAP会有更多的是在数据库端的计算,OLAP不是本文的重点,因为OLAP不会涉及并发量的问题,所以更多偏重于OLTP,而且是极高并发的系统。

 

     当前端app并发达到一定程度,即将考虑的问题就是数据库的压力,数据库面对的更多的数据,虽然它在各方面做了非常大的优化,不过它毕竟是存大量锁机制和磁盘读写操作,来保证数据一致性和安全性,而这两者往往是影响性能的关键指标,但是我们很多时候又不得不用数据库,因为他可以提供给我们的东西实在是太多了。

 

     在互联网应用中有个几乎所有网站都会拥有的一个共同特征那就是读取次数非常多,而写的次数相对比例较少(并不代表没有写操作),此时人们在设计上第一个想法是让数据库来完成主备或者镜像方式上的读写分离,不过始终会与数据库交互,而且扩展上会受到非常大的限制,在极高并发下,人们又对应用做了对页面输出html的方式,但是最终发现在实施过程中会受到很多限制(尤其是ajax交互),虽然有很多软件可以支持,此时很多人想到将数据载入到内存中,按照某种方式刷新内存即可,不过我们上面已经讨论,在集群下它很难做到每个被切割开的节点他们之间的静态内存是一致的,所以Memcached出现了。

 

 

    Memcached我看网上写它是分布式的,这个大家最好不要乱理解,因为从基本的设计上讲,它只是将app和静态内存分开了,而并非真正意义上做到分布式(真正意义上的分布式应当自动将多个Memcached节点的访问如同访问一个节点一样简单),而一般Memcached的访问方式还是通过程序去控制的,而多个不同节点划分,也是通过人为的完成的,你可以认为你访问的Memcached是数据库一样的东西,因为它的访问方式类似于数据库,但是它如果命中肯定比访问数据库要快很多,而且可以大量减少读的压力,因为一个大网站百分之八九十以上的压力来源于读;一个好的Memcached设计会使得读命中率达到95%以上,而其成本只需要大内存,并具有极大的扩展性;根据实际系统的场景讲Memcached划分数据的方法指定,当命中是获取,当修改时先修改数据库,然后让对应的cached失效即可;主意解决如果它挂掉会产生什么问题,它的基础原理是一种Key-Value方式,但是通用的东西往往不是性能最佳的东西,所以你在有必要的情况下可以适当做下修改,淘宝网的tair开源技术就是一套自己完成的分布式缓存技术,也是很不错的选择。

 

 

4、db cache应用

      上述已经描述到数据库访问会有大量的磁盘操作,这里我们说下Oracle是如何缓解这些问题的,以至于它一直在数据库领域处于行业界得老大哥形象出现。

 

      它首先由一个SGA的全局区域,内部的其他区域已经在前面的文章中说明,中间对于数据层面,最重要的就是databuffer了,这个databuffer是采用基于LRU算法为基础的方式来完成的所以只要有足够大的内存,在读远大于写的情况下命中率也会非常高(其实oracle做写操作也是写内存的,即使你commit命令oracle也不会做磁盘写,但是它会写日志,当达到一定并发量日志写也是不可估量的,而且脏块列表也会非常频繁的被刷新到磁盘上,也很容易出现瓶颈),data buffer这也是db cache最为核心的部分,当然还有些其他区域也有一定的cache思想。

 

     一般来说,对于极为高并发的系统,数据库的cached逐渐受到限制,虽然oracle rac可以非常高效的扩展,但是其限制最多是64节点的整列结构,而且在这个过程中并非没有锁,尤其是在集群下的全局锁机制,这个开销也是很大的,但是我们很多时候访问很多数据并非需要锁,也就是这些数据是在这段时间内我们确定不会被修改或者说根本不会被修改甚至于说修改了一个简单脏数据的延迟读也是无所谓的,这类数据我们没有必要让他来和其他需要做绝对一致性的事情套在一起,该做事务的被阻塞了,可以间接做事务的也被阻塞了,所以在更多的层面我们希望的是app端做好cache,才是更好的方案,通常app的性能会占用整个系统性能指标的50%以上,而有20%在于数据库端,另外还有设计方案、存储等等其他的,以及SQL了。

 

     在app设计cached后,数据库更多的是做修改,读显得更加少,所以在app设计cached后,数据库端的内存可以保留,也可以节约一些出来也可以。

5、db存储类型以及存储cache说明

     存储就是指最终数据存放的位置,有些地方也叫做整列(因为很多时候它是多个磁盘通过RAID完成的),存储一般会有低端存储、中端存储、高端存储。

 

    存储设备中最挫的就是本地硬盘了,一般都可以不认为他是独立的存储设备;但是最终你会发现它在是最好的,呵呵,在分布式的架构上,我们更加愿意选择廉价的成本设备,并自己架构主机来完成使得性能达到更高的程度;比如在一种顺序写非常多、随机读非常多的场景下,我们就更加愿意选择SSD硬盘来做存储,因为它的总体设计就非常适合这种情况。

 

    低端存储一般只有一个控制器,坏掉全部坏掉,没有任何存储cached,存磁盘操作。

 

     中端存储一般有2个控制器,可以做均衡负载,而且可以冗余保护,坏掉一个性能会降低50%,并且有一定的cache设备,有些时候也会分读cache和写cache,IBM DS 8000属于一种中端存储,不过它自称是高端存储设备,外部一般说他是伪高端设备。

 

    高端存储,多个控制器相互冗余,坏掉一两个性能影响较小,具体影响要看存储成本和具体需求;EMC高端存储就是非常流行的选择,DMX3中还有一种读cache镜像和写cache镜像,在某些应用下性能更加提升;不过高端存储的成本极高,在必要的环境下才会使用,绝大部分企业会使用中端存储设备。

 

    存储成本并非和性能或者说高可用性完全成正比,尤其是本身高可用性很好的情况下;所以在选择存储的时候再考虑当前应用下需要考虑的就是成本,主要是:数据存储容量、电费、网路带宽;以及一个存储在多少年后报废等一起计算。

 

    存储的基本考量标准也是系统性能重点指标:IOPS、QPS、TPS、带宽容量、单个请求响应时间。

 

    这些目前不做深入探讨,以后我们再说(因为涉及内容非常多,而且和磁盘管理方式有关系,如下面的条带就会对其影响),只做下简单介绍:

 

    IOPS:磁盘阵列上每秒相应IO次数,这个IO次数不分读写,但是一般是OLTP系统中的小IO,一般用2K、4K这种来做测试(所以主意你在设计OLTP系统的数据库block时为什么要小,因为提取一条数据并不想用多次IO,而oracle提取数据的单位是block,MySQL和sqlserver是页);一般单个硬盘的IOPS会根据设计有关系,一个15k rpm 的IOPS一般是150个,但是并非绝对,可能会管理方式以及每个IO的大小有关系。

 

    QPS和TPS是对IOPS的一个分解,其实本身没这个概念,不过可以做这个来看出一个系统的读写比例以及让系统以后如何设计来更好的工作。这两个分别代表的是每秒的查询次数、事务次数;可以通过一些内部SQL抓取等方法来实现。

 

   IO带宽:当上述内容完成后,就需要考虑带宽了,当你的IOPS可以上去后,但是带宽上不去就悲剧了,那刚才的15k rpm来说,一般带宽是13M/s,这里单位注意是字节(B),这里假设有120块磁盘,那么也就是1560M/s,此时就需要通信上做一些支持,也就是要支持1G多的流量,需要光纤带宽8Gb(这里是网络上的大小,也就是二进制大小),那么最少使用4块2Gb的光纤卡;这种考虑基本在OLAP中比较多,而在OLTP系统中IO都是小IO,带宽按照小IO的大小乘以IOPS已经足够。

 

   响应速度:这个因素就多了,除了上述的IOPS以及吞吐量以外,还和存储cache有关系,甚至于和锁都有关系,总之响应速度算是一个最终结果,影响因数上面每一种都会有,具体需要根据实际系统来协调,一般来说一个IO如果存磁盘操作最少需要10ms甚至于更多,而如果在cache中命中可能2ms左右就响应了,也就是从单个IO来说,cache命中比正常磁盘操作要快5倍,而平均IO一般保持在10ms是比较良好的,很多时候非cache的情况下平均IO一般会达到20ms以上

 

6、存储条带思想

    大家不要被这个词汇所吓到,它就是RAID0的另一种说法,其实RAID有很多种,从RAID0~RAID7每一种都有自己的特征所在,而且还有组合的,企业常用的有:RAID 10、RAID5、RAID3这几种,本文不对磁盘阵列做详细阐述,而只是通过条带给带出来一些思想。

 

   RAID0,也就是条带,它的思想源于负载均衡,和散列存储,最终在磁盘上的统一实现,并将其作为磁盘组为中心,给外部调用,而无需关心磁盘的内部细节。

 

   它按照一定的数据顺序,将数据分布逐个分布在多个磁盘上,所以看起来就像“条带”一样,同时不论在读还是写的过程中,它都将IO负载到了不同的磁盘上,使得IO的总体性能几乎可以与磁盘数成正比,极大提高IO性能。

 

    但是RAID0本身没有保护,也就是当磁盘坏掉,数据就丢了,找不回来,所以后来出现各种各样的RAID,根据不同的情况每一种RAID都会有自己的方式来处理,实现补充程度的冗余,还是那句话,发展到一定的冗余度将会导致成本直线上升,但是并不一定会带来收益的直线上升;RAID10就是通过50%冗余完成,也就是一对一冗余完成,同一个整列下所有的数据坏掉也可以找回来,除非两块磁盘是相互冗余的磁盘同时坏掉;而RAID5属于从RAID3、RAID4做一些算法改进和性能提升上来的,其原理都是奇、偶校验码原则,数据分布式按照条带思想,冗余N+1块磁盘,数据随机存放在N块磁盘上,剩余一块做校验位,相对减少磁头同步粒度,其中任意一块磁盘坏掉,均可恢复,但同一个RAID5阵列同时坏掉2块不行。

 

    顺便提及下,ORACLE个只疯狗什么东西都想独霸,他的ASM就是拿出来和RAID竞争的,它的管理可以基于裸机,更加优于基于操作系统层的调用,而在裸设备的管理上又会有很多新的讲究。

7、数据库集群

     数据库集群上,最初是通过一种操作系统机制HA完成,但是它在数据库层面存在很多缺陷,相对管理数据库来说还存在很多专业上的个性化,所以ORACLE在10g推出了ORACLE RAC(其实是9i,但是9i的集群做得很烂,所以可以认为是10g才有的);另外10g之前的集群需要第三方的cluster软件完成,10g后就有了oracle自己的CRS软件,并且是免费的,可以到官方下载。

 

    数据库集群除正常的app拥有的(load banlance)负载均衡、(failover)失败切换,还有很多机制在内,包含主从关系、切换机制、以及分布式计算(网格计算(Grid)在ORACLE RAC中是一种最简单的实现方法,真正的网格计算是指在实际的网格环境下去管理网格下多个应用的数据库包括集群,他们是同一的,甚至于你无须关心网格下集群组之间的关系,就能非常清晰得去做操作了),这里的网格计算是指在一些大的统计下,在配置数据库参数时,将相应的INSTANCE参数设置为集群分组,并开启并行,在做一些大操作时就会实现多实例配合完成,也是通过心跳完成的。

 

   数据库集群的负载均衡一般是通过app端完成,这部分可能是client端的TNS配置(此时前提是通过cluster完成使用同一个service_name对应多个SID),或者类似TNS配置在链接数据库的URL中,它内部一个重要参数就是LOAD_BALANCE等等,它可以设置为:(yes、on、true是等价的,不区分大小写,即开启负载均衡),相反,设置为(no、off、false)则为取消负载均衡,此时按照配置的远程主机IP或者域名的顺序逐个访问到一个可用的即可,此时一般会导致一台机器忙一台机器闲的情况,不过另一台机器如果只是用来做备机器,当一台挂掉后切换过去也是可以的,一般用RAC我们也会将该参数开启。

 

   failover就是将数据库的SQL切换到另一个机器上,但是事务会被回滚,具体是否切换或者如何切换要看其它参数配置,首先FAILOVER参数和上面参数的参数值一样都是那样设置,当设置为开启状态就会进行失败切换,否则这个连接池的请求就会失败;而其它几个参数一般是在开启状态下有默认值的,自己也可以设置的哦,在FAILOVER_MODE配置中很多:

   首先是TYPE参数的配置中一般有:session(失败时候,所有内容被中止,已经操作的事务被回滚,创建新的session到另一个可用实例上)、select(设置为该参数和上面差不多,不过切换时,开始被操作的事务虽然被回滚,但是如果是select语句不会被中断,会继续执行),none(不做任何操作,直接回滚,也不接管,用于测试,客户端会直接报错)

   其次METHOD参数,这个参数一般是有:basic(在发生失败时候再在另一个实例上创建session回话节点)、preconnect(预先设立回话节点,有一定开销,但是切换速度很快速,在主从模式下推荐)而RETRIES分别代表重试次数(默认5)、DELAY代表每次重试时间片信息(默认1秒)、BACKUP(备份节点的网路服务名)

 

   集群RAC由于设计更加专业于数据库应用,所以他比起HA更加适用于数据库,也是众多企业的选择,它配合data guard(有些是extend rac是包含了这两种功能)来完成备份,也有oracle的一直以来的终极备份方案rman来完成,不过前者更加偏重于容灾,还有些关于复制以及迁移等功能不是本文重点,不便多提及。

 

   ORACLE RAC和相关的东西都是烧钱的东西,价格不菲,对各项硬件要求非常高,所以注意成本预算,如高速网络以及各个INSTANCE连接共享存储阵列的SAN交换机一般需要多个来冗余,心跳的交换机也需要冗余等等。

 

   ORACLE RAC依赖于一个共享存储,做相应INSTANCE和数据库级别的管理,这也是数据库和实例的区别了,那么它的瓶颈就在后端了,所以后端很多时候会选择高端存储来完成;另外它还有很多全局资源管理使得它的很多发展在这些瓶颈上出现问题,如它的节点一般最多支持64节点,而随着节点数量的增加,成本会直线上升,至于性能是否能直线上升呢,你应该可以考虑下当前的各种瓶颈在哪里,也需要和实际情况结合才好说。

8、数据库容灾备份以及监控

     接下来一个系统设计应该如何?需要做的就是容灾以及监控运行状况是否良好,对于app端一般不需要容灾,只需要监控,而其一般是通过监控内存、CPU、磁盘使用量(主要是日志和本地缓存文件);如果监控系统做得不好,那么我想很多DBA晚上睡不着(至于夜间做生产变更这类可以通过其他的自动化程序完成),系统的发展也会受到限制,我们需要一个伸缩性很强的系统就必然会走这一步。

 

     而数据库容灾现在又很多方案,上面已经说了,现在比较多的就是使用dataguard备份到一个或多个备份机器上,dataguard上有多种配置机制,来实现各种常用的要求,关于磁盘管理可以使用ASM来管理,数据库也可以负责制过去,也可以异步通过程序度过去,也可以通过触发器+dblink过去等等都可以实现。关键看实际需求。

 

     数据库的监控,这个oracle也提供了系列的监控软件(Statspace、AWR、logmgr等等系列),不过很多时候我们需要更加精确的参数需要自己去编码,否则就还是需要自己去查询很多自己做报表什么的,而且很不直观;长期需要监控的除了常用的IOPS、TPS、QPS以外,还需要关心很多如latch征用、sql parser(硬解析和软解析的各方面指标)、cache命中率、锁等待、内存指标变化、CPU指标变化、索引、磁盘碎片等等都需要得到全方位的监控

 

    数据库的管理应当自动化,首先从监控下手,完全自动化管理和资源调配方面是一个理想,不过半自动化也是很容易的,就是在有问题或者在一定情况下有某种方式的通知,如短信息吧。这样DBA就不用成天盯着监控或者后台的某个字典表一直看了。

9、CDN思想基础

    后面几个章节不是本文重点,简单阐述下即可,在高可用性网站设计中,即使前端应用增加了Memcached这类东西,不过始终不能很好的效果,要达到极佳的效果,因为很多时候跨网段的开销是非常大的,经过的路由器越多开销越大;其次很多时候,不愿意因为大文件输出(如视频下载)导致应用服务器宕机的事情,这是没有必要的,因为应用服务器更多关心的应该是业务处理。

 

    CDN的出现就是为了解决这个问题,也就是网站加速器,他需要运营商的配合(具体细节请自己查阅资料),在很多地方建立站点,它需要做的事情就是托管DNS,通常DNS是解析域名成IP并访问对应IP内容,而CDN做了一层重写,就是通过域名解析得到的是一个CNAME,它按照提供CNAME会按照最短路径找到对应的CDN网点,接受数据,客户端的数据接受更加快速,并且可以实现冗余保护,另外它只是缓存在这里,可以认为是本地的一个私服,也就是需要跨网段的流量都切换到本地了,这里做一个极端的假设,就是跨网段的开销是2,本网段拖数据是1,有100个请求时,跨网段需要200的开销,而本地网段就只需要101个开销。

 

    大文件下载,是通过缓存到本地的私服上,如视频下载就很多时候这段时间大家看的都是热播电影,就可以通过CDN来进行网站加速。

10、nosql思想

     根据上面的描述,我们很多时候就不想做到百分百的数据安全,或者一致性吧,比如做一个网站的留言板,数据有一点偏差也无所谓,而且数据库的sql parser一般是很慢的,很容易达到极限,所以nosql的诞生就出现了,现在很多开源的nosql平台,它也是现有云存储的基础,apache的Hadoop以及谷歌的mapreduce后来做了一个Percolator,还有RedisMongoDB等等,其实所谓nosql基础的原理就是没有sql,就想刚才说的Memcache一样,只是它有存储以及根据设计不同,会有一些会存在一些锁机制,并且只是面向对象;有基于行存储的、有基于列存储的他们是根据实际应用场景设计的一种类似于数据库的东西,它具有极高的扩展性和伸缩性,因为控制完全在于你本身的架构和设计,也是我们一直所崇尚的:最好的东西肯定是最优秀的人根据实际的场景所架构出来的。

 

      不论是哪一门,nosql它首先抛开的是sql parser的一种,但是它没有了SQL的支持,在一些复杂操作上显得比较困难(这些就要看具体场景和nosql的设计了);我们在结合上述几种技术的基础上如何不将Cached、nosql、RDBMS、app几个结合起来,向后端移动,实现app调用完全无需关心很多调用的细节,那么这就是真正的云存储了,因为是在分布式存储基础上以及cache管理的基础上实现了对应用的透明调用。

 

     如何设计待以后专门有文章来阐述,今天只是一个开头而已。

11、无锁分析

    通过上面的文章内容,我们在很多时候很多不必要的信息没有必要使用RDBMS一样的锁和同步等等动作,所以所谓真正意义上的无锁或者几乎无锁,就是将很多内容抽象出来利用间接的方法来实现。

 

    一般来说降低锁的粒度有以下几种方法:

     a.使用hash、range、位图对数据进行提前分布,让其分框,根据实际情况而定,如果一个框只有一个线程在处理那么就几乎可以算是无锁了。

     b.在一些特殊必要的应用中,使用特殊的方法来控制,变通的方法来控制,如队列中的对头和队尾算法,如果只有一个生产者和一个消费者可以让他们在一个定长数组下跑圈圈即可,后者永远追不上前者,而多生产者多消费者模式又该如何呢?比如多个线程做push操作,那么你只需要在多个线程以当前队头下标开始位置分配到不同的下标,几个线程就可以无锁操作了,那么如何分配到不同的下标呢?用Java的volatile,你可以认为它是锁的,不过它非常轻量级的锁,只是在对使用volatile变量修改和读取过程中强制从从新内存中获取,而不是寄存器,所以在计数器使用中,多个线程去同时修改这个变量并获取到的值都是不同的;pop也是如此,这些有一定的应用场景,栈也可以用变通的手段得到解决。

     c.还有一些通过版本号码、向量复制、脏块列表等等思想来实现,都有一些应用场景和方法;以及java提供的乐观锁机制(适用于非常多线程调用同一段代码,而不是循环非常多次去调用同一段代码)。

 

     还有很多其他的知识可以借鉴,曾经看到过非常复杂的图形算法,而且是多维度的,太复杂了,所以这里就不说明了。

 

     根据上述N多知识可以看出很多知识都是相通的,无非就是分解、根据实际情况命中与解锁,让更快的地方替换最慢的地方,让复杂的管理变得更加简单。

 

     另一种无锁是一种变通的手段,就是单线程写操作了,也就是完全无锁的一种机制,其实你会觉得它很慢,经过测试发现,如果你的操作全是或者基本是OLTP中的小IO单个线程的写已经可以达到非常快速度,这种非常适合于写不多,但读非常多的系统,也就是读写分离,写全部在内存中完成,但是需要写日志,读是从多个散列主机上获取,但是也会从这个内存中获取相应数据,内存中为最新修改后得数据列,他们之间会在对应字段上以内存为主进行返回,这个机器只要内存足够大(现在稍微好点的PC SERVER几十G的内存非常容易),就可以承受非常大的修改,这个数据只需要在业务量较小的时候合并到静态数据中即可;那么当业务进行扩大,单线程无法承受的时候应该如何呢?内存也写不下了,那么此时又需要对其进行切割分离了,在业务和逻辑表上做一定的标识符号,类似于上述说到的volatile一样的东西,而写操作也可以类似于读操作一样的分层,这就越来越像Memcache+app+RDBMS这种结构了,只是它在Memcached有日志记录和恢复,并对于应用来说透明化了这种分布式的调用,它将整个体系向后端移动和抽象出来使得app的编程更加简单和方便,也就是app无需关心数据的具体位置在哪里,以及写到哪里去了,缓存在哪里,他们如何同步的,这就逐步可以认为是云存储和计算了,另外其精巧的设计不得不说是非常优秀的。

 

       一个web应用绝大部分请求的整个过程:client发出请求->server开始响应并创建请求对象及反馈对象->如果没有用户对象就创建session信息->调用业务代码->业务代码分层组织数据->调用数据(从某个远程或数据库或文件等)->开始组织输出数据->反馈数据开始通过模板引擎进行渲染->渲染完成未静态文件向客户端进行输出->待客户端接收完成结束掉请求对象(这种请求针对短连接,长连接有所区别)。

就从前端说起吧,说下一下几个内容:

1、线程数量

2、内容输出

3、线程上下文切换

4、内存

 

1.首先说下线程数量,线程数量很多人认为在配置服务器的线程数量时认为越多越好,各大网站上很多人也给出了自己的测试数据,也有人说了每个CPU配置多少线程为合适(比如有人说过每个CPU给25个线程较为合适),但是没有一个明确的为什么,其实这个要和CPU本身的运行效率和上来说明,并非一概而论的,也需要考虑每个请求所持有的CPU开销大小以及其处于非Running状态的时间来说明,线程配置得过多,其实往往会形成CPU的征用调度问题,要比较恰当将CPU用满才是性能的最佳状态(说到线程就不得不说下CPU,因为线程就是消耗CPU的,其本身持有的内存片段非常小,前面文章已经说明了它的内存使用情况,所以我们主要是讨论它与CPU之间的关系)。

首先内存到CPU的延迟在几十纳秒,虽然CPU内部的三级缓存比这个更加小,但是几乎对于我们所能识别的时间来讲可以被忽略;另外内存与CPU之间的带宽也是以最少几百M每秒的速度通信,所以对于内存与CPU交互数据的时间开销对于常规的高并发小请求的应用客户忽略掉,我们只计算本身的计算延迟开销以及非计算的等待开销,这些都一般会用毫秒来计算,相互之间是用10e6的级别来衡量,所以前者可以忽略,我们可以认为处于running的时间就是CPU实际执行的时间,因为这种短暂的时间也很难监控出来到底用了多久。

那么首先可以将线程的运行状态划分为两大类,就是:运行与等待,我们不考虑被释放的情况,因为线程池一般不会释放线程,至于等待有很多种,我们都认为它是等待就可以了;为什么是这两种呢,这两种正好对应了CPU是否在被使用,running状态的线程就在持有CPU的占用,等待的就处于没有使用CPU。

再明确一个概念,一个常规的web请求,后台对应一个线程对它的请求进行处理,同一个线程在同一个时间片上只能请求一个CPU为他进行处理,也就是说我们可以认为它不论请求过多少次CPU、不论请求了多少个CPU,只要这些CPU的型号是一样的,我们就可以认为它是请求的一个CPU(注意这里的CPU不包含多个core的情况,因为多个core的CPU只能说明这个CPU的处理速度可以接近于多个CPU的速度,而真正对线程的请求来讲,它认为这是一个CPU,在主板上也是一个插槽,所以计算CPU的时候不考虑多核心)。

最后明确线程在什么情况下会发生等待,比如读取数据库时,数据库尚未反馈内容之前,该线程是不会占用CPU的,只会处理等待;类似的是向客户端输出、线程为了去持有锁的等待一系列的情况。

此时一个线程过来如果一个线程毫无等待(这种情况不存在,只是一种假设),不论它处理多久,处理时间长度多长,此时如果只有一个CPU,那么这个应用服务器只需要一个1个线程就足以支撑,因为线程没有等待,那么CPU就没有停止运行,1个线程处理完这个请求后,接着就处理下一个请求,CPU一直是满的,也几乎没有太大的征用,此时1个线程就是最佳的,如果是多个同型号的CPU,那么就是CPU数量的线程是最佳的;不过这个例子比较极端,在很多类似的情况下,大家喜欢用CPU+1或CPU-1来完成对类似情况的线程设置,为了保证一些特殊情况的发生。

那么考虑下实际的情况,如果有等待,这个等待不是锁等待的(因为锁等待有瓶颈,瓶颈在于CPU的个数对于他们无效),应该如何考虑呢?我们此时来考虑下这个等待的时间长度应该如何去考虑,假如等待的时间长度为100ms,而运行的时间长度为10ms,那么在等待的这100ms中,就可以有另外10个线程进来,对CPU进行占用,也就是说对于单个CPU来说,11个线程就可以占满整个CPU的使用,如果是多个CPU当然在理论上可以乘以CPU的个数,这里再次强调,这里的CPU个数是物理的,而不算多核,多核在这里的意义比如以前一个CPU处理一个线程需要30ms,现在采用4个core,只需要处理10ms了,在这里体现了速度,所以计算是不要用它来计算。

那么对于锁等待呢?这个有点麻烦了,因为这个和模块有关系,这里也只能说明某个有锁等待的模块要达到最佳状态的访问效率可以配置的线程数,首先要明确锁等待已经没有CPU个数的概念,不论多少个CPU,只要运行到这段代码,他们就是一个CPU,不然锁就没有存在的意义了;另外,假如访问是非常密集的,那么当某个线程持有锁并访问的时候,其他没有得到的运行到这个位置都会处于等待,我们将一个模块的所有有锁等待的时间集中在一起,只有当前一个线程将具有锁的这段代码运行完成后,下一个线程才可以继续运行,所以它其他地方都没有瓶颈,或者说其他地方理论配置的线程数都会很高,唯独遇到这个地方就会很慢,假如一个线程从运行代码时长为20ms,等待事件为100ms,锁等待为20ms,此时假如该线程没有受到任何等待就是140ms即可运行完成,而当多个线程同时并发到这里的时候,后续每个线程将会等待20*N的时间长度,当有7个线程的时候,恰好排满运行的队列,也就是当又7个线程访问这个模块的时候,理论上刚好达到每个线程顺序执行而且成流水线状态,但是这里不能乘以CPU的个数了,为什么,你懂的。

 

2.内容输出,其实内容输出有很多种方法,在Java方面,你可以自己编写OutputStream或者PrintWriter去输出,也可以用渲染模板去渲染输出,渲染的模板也有很多,最常见的就是JSP模板来渲染,也有velocity等各种各样的渲染模板,当然对于页面来讲只能用渲染模板去做,不过异步请求你可以选择,在选择时要对应选择才能将效果做得比较好。

说到这里不得不说下velocity这个东西,也就是经常看到的vm的文件,这种文件和JSP一样都是渲染模板的方法,只是语法格式有所区别,velocity是新出来的东西,很多人认为新的东西肯定很好,其实velocity是渲染效率很低的,在内容较小的输出上对性能进行压力测试,其单位时间内所能承受的访问量,比JSP渲染模板要低好几倍,不过对较大的数据输出和JSP差不多,也就是页面输出使用velocity无所谓的,而且效果比JSP要好,但是类似ajax交互中的小数据输出建议不要使用vm模板引擎,使用JSP模板引擎甚至于直接输出是最佳的方式。

说到这里JSP模板引擎在输出时是会被预先编译为java的class文件,VM是解释执行的,所以小文件两者性能差距很大,当遇到大数据输出时,其实大部分时间在输出文件的过程中,解释时间几乎就可以被忽略掉了。

那么JSP输出小文件是不是最快的呢?未必,JSP的输出其实是将JSP页面的内容组成字符串,最终使用PrintWriter流取完成,中间跳转交互其实还是蛮多的,而且有部分容器在组装字符串的时候竟然用+,这个让我很是郁闷啊,所以很多时候小数据的输出,我还是喜欢自己写,经过测试得到的结果是使用OutputStream的性能将会比PrintWriter高一些,(至于高多少,大家可以自己用工具或写代码测试下就知道了,这里可能单个处理速度几乎看不出区别,要并发访问看下平均每秒能处理的请求数就会有区别了),字符集方面,在获取要输出内容的时候,指定byte的字符集,如:String.getByte(“字符集”),一般这类输出也不会有表头,只需要和接收方或者叫浏览器一致就可以了(有些接收方可能是请求方);其实OutputStream比PrintWriter快速的原因很简单,在底层运行和传输的过程中,始终采用二进制流来完成,即使是字符也需要转换成byte格式,在转换前,它需要去寻找很多的字符集关系,最终定位到应该如何去转换,内部代码看过一下就明白,内部的方法调用非常多,一层套一层,相应占用的CPU开销也会升高。

总结起来说,如果你有vm模板引擎,在页面请求时建议使用vm模板引擎来做,因为代码要规范一些,而且也很好用;另外如果在简单的ajax请求,返回数据较小的情况下,建议使用OutputStream直接输出,这个输出可以放在你的BaseAction的中,对实现类中是透明的,实现类只需要将处理的反馈结果数据放在一个地方,由父类完成统一的输出即可,此处将Ajax类的调用可以独立一个父亲类出来,这样继承后就不用关心细节了。

输出中文件和大数据将是一个问题,对于文件来说,尤其是大文件,在前面文章已经说明,输出时压缩只能节省服务器输出时和客户端的流量,从而提高下载速度,但是绝对不会提高服务器端的性能,因为服务器端是通过消耗CPU去做动作,而且压缩的这个过程是需要时间的,这种只会降低速度,而绝对不会提高;那么大文件的方法就是一种是将大文件提前压缩好存放,如果实在太大,需要考虑采用断点传送,并将文件分解。

对大数据来讲,和文件类似,不过数据可能对我们要好处理一点,需要控制访问频率甚至于直接在超过访问频率下拒绝访问请求,每次请求的量也需要控制,如果对特殊大的数据量,建议采用异步方式输出到文件并压缩后,再由客户端下载,这样不论是客户端还是服务器端都是有好处的。

 

3、线程上下文切换,对于线程的上下文切换,在一般的系统中基本遇不到,不过一些特殊应用会遇到,比如刚才的异步导出的功能,请求的线程只是将事情提交上去,但是不是由它去下载,而是由其他线程再去处理这个问题,处理完成后再回写某个状态即可;在javaNIO中是非常的多,NIO是一种高性能服务器的解决方案,在有限的线程资源情况下,对极高并发的小请求,并存在很多推拉数据的情况下是很有效的,最大的要求就是服务器要有较好的连接支撑能力,NIO细节不用多说,理解上就是异步IO,把事情交给异步的一个线程去做,但是它也未必马上做,它做完再反馈,这段时间交给你的这个线程不是等待而是去做其他的事情,充分利用线程的资源,处理完反馈结果的线程也未必是开始请求的线程,几个来来回回是有很多的开销的,总体其实效率上未必有单个请求好,但是对服务器的性能发挥是非常有效的。

线程之间的开销大小也要看具体应用情况以及配置情况决定,此时将任务和线程没有做一个一对一的绑定,而是放一堆事情在队列中,处理线程也有很多,谁有时间处理谁就处理它,每个线程都做自己这一类的事情,甚至于将一些内容交给远程去做,交互后就不管了,结果反馈的时候,这边再由一个线程去处理结果请求即可。

在整个过程中会涉及到一次或多次的线程切换,这个过程中的开销在某些时候也是不小的,关键还是要看应用场景,不能一概而论。

4、内存,最后还是内存,其实这里我就不想多说了,因为前面几篇文章说得太多了,不论是理论上还是实现上,以及经验上都说了非常多,不过可以说明的一点就是内存的问题绝大部分来源于代码,而代码有很大一部分可能性来源于工程的程序员编写或者框架,第三方包的内存问题相对较少,一般被开源出来的包内存溢出的可能性不大,但是不排除有写得比较烂的代码;二方包呢,一般指代公司内部人员封装的包,如果在经过很多项目的验证可以比较放心使用,要绝对放心的话还是需要看看源码才行,至于JVM本身的BUG一般不要找到这个上面来,虽然也有这种可能性,不过这种问题除了升级JVM外也没有太多的办法,修改它的源码的可能性不大,除非你真的太厉害了(这里在内存上一般是指C或C++语言的源码,java部分的基础类包这些代码如果真的有问题,还是比较容易修改的,但还是不建议自己刻意去修改,除非你能肯定有你更好的解决方案而且是稳定有效的);在编写代码的时候将那些可以提前做的事情做了(比如这个事情以后会反复做,重复做,而且都是一样的,那么可以提前做一次,以后就不用做了),那些逻辑是可以省掉的,最后是如果你的应用很特殊是不是更好的解决方案和算法来完成。

 

总结下,从今天提到的系统设计的角度来说,影响QPS的最关键的东西就是模板渲染,它会占据请求的很大一部分时间,而且这个东西可以做非常大的改进,比如:压缩空白字符、重复对象的简化和模板化、大数据和重复信息的CSS化、尽量将输出转化为网络可以直接接受的内容;而其次就是如何配置线程,配置得太少,CPU的开销一直处于一种比较闲的状态,而配置得太多,CPU的征用情况比较严重,没有建议值,只要最适合应用场景的值,不过你的代码如果没有太多的同步,线程最少应该设置为CPU的格式+1或-1个;上下文切换对常规应用一般不要使用,对特殊的应用要注意中间的切换开销应该如何降低;文件输出上讲提前做的压缩提前做掉,注意控制访问频率和单次输出量;最后内存上多多注意代码,配置上只需要控制好常规的几个参数,其余的在没有特殊情况不要修改默认的配置。

 

扩展,那么关于一个系统的架构中是不是就这么一点就完了呢,当然不是,这应该说说出了一个常见的OLTP系统的一些常见的性能指标,但是还有很内容,比如:缓存、宕机类异常处理、session切换、IO、数据库、分布式、集群等都是这方面的关键内容,尤其是IO也是当今系统中性能瓶颈的最主要原因之一;在后续的文章中会逐步说明一些相关的解决方案。

 

下面说下OOM的常见情况(本文基于jdk 1.6系列版本来编写,其余的版本未必完全适用):

 

第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出:

那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了:

Java.lang.OutOfMemoryError: ......Java heap space.....

也就是当你看到heap相关的时候就肯定是堆栈溢出了,此时如果代码没有问题的情况下,适当调整-Xmx和-Xms是可以避免的,不过一定是代码没有问题的前提,为什么会溢出呢,要么代码有问题,要么访问量太多并且每个访问的时间太长或者数据太多,导致数据释放不掉,因为垃圾回收器是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,自然不会去回收了;主意这个溢出之前,可能系统会提前先报错关键字为:

java.lang.OutOfMemoryError:GC over head limit exceeded

这种情况是当系统处于高频的GC状态,而且回收的效果依然不佳的情况,就会开始报这个错误,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致,但是java heap space的内存溢出有可能提前不会报这个错误,也就是可能内存就直接不够导致,而不是高频GC.

 

第二类内存溢出,PermGen的溢出,或者PermGen 满了的提示,你会看到这样的关键字:

关键信息为:

java.lang.OutOfMemoryError: PermGen space

原因:系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法,导致常量池的膨胀,虽然JDK 1.5以后可以通过设置对永久带进行回收,但是我们希望的是这个地方是不做GC的,它够用就行,所以一般情况下今年少做类似的操作,所以在面对这种情况常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。

 

第三类内存溢出:在使用ByteBuffer中的allocateDirect()的时候会用到,很多javaNIO的框架中被封装为其他的方法

溢出关键字:

java.lang.OutOfMemoryError: Direct buffer memory
如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,如果常规的应用程序你要将一个文件的内容输出到客户端需要通过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),然后再输出到直接内存由操作系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存可以直接由应用程序自己控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,所以要注意了哦。

如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize

 

第四类内存溢出错误:

溢出关键字:

java.lang.StackOverflowError  

这个参数直接说明一个内容,就是-Xss太小了,我们申请很多局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4以前默认是256K,1.5以后是1M,如果报这个错,只能说明-Xss设置得太小,当然有些厂商的JVM不是这个参数,本文仅仅针对Hotspot VM而已;不过在有必要的情况下可以对系统做一些优化,使得-Xss的值是可用的。

 

第五类内存溢出错误:

溢出关键字:

java.lang.OutOfMemoryError: unable to create new native thread 

上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了,说明了原因,如何去修改,不用我多说,你懂的。

 

第六类内存溢出:

溢出关键字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

这类错误一般是由于地址空间不够而导致。 

 

六大类常见溢出已经说明JVM中99%的溢出情况,要逃出这些溢出情况非常困难,除非一些很怪异的故障问题会发生,比如由于物理内存的硬件问题,导致了code cache的错误(在由byte code转换为native code的过程中出现,但是概率极低),这种情况内存 会被直接crash掉,类似还有swap的频繁交互在部分系统中会导致系统直接被crash掉,OS地址空间不够的话,系统根本无法启动,呵呵;JNI的滥用也会导致一些本地内存无法释放的问题,所以尽量避开JNI;socket连接数据打开过多的socket也会报类似:IOException: Too many open files等错误信息。

 

JNI就不用多说了,尽量少用,除非你的代码太牛B了,我无话可说,呵呵,这种内存如果没有在被调用的语言内部将内存释放掉(如C语言),那么在进程结束前这些内存永远释放不掉,解决办法只有一个就是将进程kill掉。

 

另外GC本身是需要内存空间的,因为在运算和中间数据转换过程中都需要有内存,所以你要保证GC的时候有足够的内存哦,如果没有的话GC的过程将会非常的缓慢。

 

顺便这里就提及一些新的CMS GC的内容和策略(有点乱,每次写都很乱,但是能看多少看多少吧):

首先我再写一次一前博客中的已经写过的内容,就是很多参数没啥建议值,建议值是自己在现场根据实际情况科学计算和测试得到的综合效果,建议值没有绝对好的,而且默认值很多也是有问题的,因为不同的版本和厂商都有很大的区别,默认值没有永久都是一样的,就像-Xss参数的变化一样,要看到你当前的java程序heap的大致情况可以这样看看(以下参数是随便设置的,并不是什么默认值):

$sudo jmap -heap `pgrep java`
Attaching to process ID 4280, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 1073741824 (1024.0MB)
   NewSize          = 134217728 (128.0MB)
   MaxNewSize       = 134217728 (128.0MB)
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 134217728 (128.0MB)
   MaxPermSize      = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 85721088 (81.75MB)
   used     = 22481312 (21.439849853515625MB)
   free     = 63239776 (60.310150146484375MB)
   26.22611602876529% used
From Space:
   capacity = 24051712 (22.9375MB)
   used     = 478488 (0.45632171630859375MB)
   free     = 23573224 (22.481178283691406MB)
   1.9894134770946867% used
To Space:
   capacity = 24248320 (23.125MB)
   used     = 0 (0.0MB)
   free     = 24248320 (23.125MB)
   0.0% used
PS Old Generation
   capacity = 939524096 (896.0MB)
   used     = 16343864 (15.586723327636719MB)
   free     = 923180232 (880.4132766723633MB)
   1.7395896571023124% used
PS Perm Generation
   capacity = 134217728 (128.0MB)
   used     = 48021344 (45.796722412109375MB)
   free     = 86196384 (82.20327758789062MB)
   35.77868938446045% used

 

付:sudo是需要拿到管理员权限,如果你的系统权限很大那么就不需要了,最后的grep java那个内容如果不对,可以直接通过jps或者ps命令将和java相关的进程号直接写进去,如:java -map 4280,这个参数其实完全可以通过jstat工具来替代,而且看到的效果更加好,这个参数在线上应用中,尽量少用(尤其是高并发的应用中),可能会触发JVM的bug,导致应用挂起;在jvm 1.6u14后可以编写任意一段程序,然后在运行程序的时候,增加参数为:-XX:+PrintFlagsFinal来输出当前JVM中运行时的参数值,或者通过jinfo来查看,jinfo是非常强大的工具,可以对部分参数进行动态修改,当然内存相关的东西是不能修改的,只能增加一些不是很相关的参数,有关JVM的工具使用,后续文章中如果有机会我们再来探讨,不是本文的重点;补充:关于参数的默认值对不同的JVM版本、不同的厂商、运行于不同的环境(一般和位数有关系)默认值会有区别。

 

OK,再说下反复的一句,没有必要的话就不要乱设置参数,参数不是拿来玩的,默认的参数对于这门JDK都是有好处的,关键是否适合你的应用场景,一般来讲你常规的只需要设置以下几个参数就可以了:

-server 表示为服务器端,会提供很多服务器端默认的配置,如并行回收,而服务器上一般这个参数都是默认的,所以都是可以省掉,与之对应的还有一个-client参数,一般在64位机器上,JVM是默认启动-server参数,也就是默认启动并行GC的,但是是ParallelGC而不是ParallelOldGC,两者算法不同(后面会简单说明下),而比较特殊的是windows 32位上默认是-client,这两个的区别不仅仅是默认的参数不一样,在jdk包下的jre包下一般会包含client和server包,下面分别对应启动的动态链接库,而真正看到的java、javac等相关命令指示一个启动导向,它只是根据命令找到对应的JVM并传入jvm中进行启动,也就是看到的java.exe这些文件并不是jvm;说了这么多,最终总结一下就是,-server和-client就是完全不同的两套VM,一个用于桌面应用,一个用于服务器的。

-Xmx 为Heap区域的最大值

-Xms 为Heap区域的初始值,线上环境需要与-Xmx设置为一致,否则capacity的值会来回飘动,飘得你心旷神怡,你懂的。

-Xss(或-ss) 这个其实也是可以默认的,如果你真的觉得有设置的必要,你就改下吧,1.5以后是1M的默认大小(指一个线程的native空间),如果代码不多,可以设置小点来让系统可以接受更大的内存。注意,还有一个参数是-XX:ThreadStackSize,这两个参数在设置的过程中如果都设置是有冲突的,一般按照JVM常理来说,谁设置在后面,就以谁为主,但是最后发现如果是在1.6以上的版本,-Xss设置在后面的确都是以-Xss为主,但是要是-XX:ThreadStackSize设置在后面,主线程还是为-Xss为主,而其它线程以-XX:ThreadStackSize为主,主线程做了一个特殊判定处理;单独设置都是以本身为主,-Xss不设置也不会采用其默认值,除非两个都不设置会采用-Xss的默认值。另外这个参数针对于hotspot的vm,在IBM的jvm中,还有一个参数为-Xoss,主要原因是IBM在对栈的处理上有操作数栈和方法栈等各种不同的栈种类,而hotspot不管是什么栈都放在一个私有的线程内部的,不区分是什么栈,所以只需要设置一个参数,而IBM的J9不是这样的;有关栈上的细节,后续我们有机会专门写文章来说明。

 

-XX:PermSize-XX:MaxPermSize两个包含了class的装载的位置,或者说是方法区(但不是本地方法区),在Hotspot默认情况下为64M,主意全世界的JVM只有hostpot的VM才有Perm的区域,或者说只有hotspot才有对用户可以设置的这块区域,其他的JVM都没有,其实并不是没有这块区域,而是这块区域没有让用户来设置,其实这块区域本身也不应该让用户来设置,我们也没有一个明确的说法这块空间必须要设置多大,都是拍脑袋设置一个数字,如果发布到线上看下如果用得比较多,就再多点,如果用的少,就减少点,而这块区域和性能关键没有多大关系,只要能装下就OK,并且时不时会因为Perm不够而导致Full GC,所以交给开发者来调节这个参数不知道是怎么想的;所以Oracle将在新一代JVM中将这个区域彻底删掉,也就是对用户透明,G1的如果真正稳定起来,以后JVM的启动参数将会非常简单,而且理论上管理再大的内存也是没有问题的,其实G1(garbage first,一种基于region的垃圾收集回收器)已经在hotspot中开始有所试用,不过目前效果不好,还不如CMS呢,所以只是试用,G1已经作为ORACLE对JVM研发的最高重点,CMS自现在最高版本后也不再有新功能(可以修改bug),该项目已经进行5年,尚未发布正式版,CMS是四五年前发布的正式版,但是是最近一两年才开始稳定,而G1的复杂性将会远远超越CMS,所以要真正使用上G1还有待考察,全世界目前只有IBM J9真正实现了G1论文中提到的思想(论文于05年左右发表),IBM已经将J9应用于websphere中,但是并不代表这是全世界最好的jvm,全世界最好的jvm是Azul(无停顿垃圾回收算法和一个零开销的诊断/监控工具),几乎可以说这个jvm是没有暂停的,在全世界很多顶尖级的公司使用,不过价格非常贵,不能直接使用,目前这个jvm的主导者在研究JRockit,而目前hotspot和JRockit都是Oracle的,所以他们可能会合并,所以我们应该对JVM的性能充满信心。

 

也就是说你常用的情况下只需要设置4个参数就OK了,除非你的应用有些特殊,否则不要乱改,那么来看看一些其他情况的参数吧:

 

先来看个不大常用的,就是大家都知道JVM新的对象应该说几乎百分百的在Eden里面,除非Eden真的装不下,我们不考虑这种变态的问题,因为线上环境Eden区域都是不小的,来降低GC的次数以及全局 GC的概率;而JVM习惯将内存按照较为连续的位置进行分配,这样使得有足够的内存可以被分配,减少碎片,那么对于内存最后一个位置必然就有大量的征用问题,JVM在高一点的版本里面提出了为每个线程分配一些私有的区域来做来解决这个问题,而1.5后的版本还可以动态管理这些区域,那么如何自己设置和查看这些区域呢,看下英文全称为:Thread Local Allocation Buffer,简称就是:TLAB,即内存本地的持有的buffer,设置参数有:

-XX:+UseTLAB                          启用这种机制的意思
-XX:TLABSize=<size in kb>    设置大小,也就是本地线程中的私有区域大小(只有这个区域放不下才会到Eden中去申请)。
-XX:+ResizeTLAB                     是否启动动态修改

这几个参数在多CPU下非常有用。

-XX:+PrintTLAB                        可以输出TLAB的内容。

 

下面再闲扯些其它的参数:

 

如果你需要对Yong区域进行并行回收应该如何修改呢?在jdk1.5以后可以使用参数:

-XX:+UseParNewGC

注意: 与它冲突的参数是:-XX:+UseParallelOldGC-XX:+UseSerialGC,如果需要用这个参数,又想让整个区域是并行回收的,那么就使用-XX:+UseConcMarkSweepGC参数来配合,其实这个参数在使用了CMS后,默认就会启动该参数,也就是这个参数在CMS GC下是无需设置的,后面会提及到这些参数。

 

 

默认服务器上的对Full并行GC策略为(这个时候Yong空间回收的时候启动PSYong算法,也是并行回收的):

-XX:+UseParallelGC

另外,在jdk1.5后出现一个新的参数如下,这个对Yong的回收算法和上面一样,对Old区域会有所区别,上面对Old回收的过程中会做一个全局的Compact,也就是全局的压缩操作,而下面的算法是局部压缩,为什么要局部压缩呢?是因为JVM发现每次压缩后再逻辑上数据都在Old区域的左边位置,申请的时候从左向右申请,那么生命力越长的对象就一般是靠左的,所以它认为左边的对象就是生命力很强,而且较为密集的,所以它针对这种情况进行部分密集,但是这两种算法mark阶段都是会暂停的,而且存活的对象越多活着的越多;而ParallelOldGC会进行部分压缩算法(主意一点,最原始的copy算法是不需要经过mark阶段,因为只需要找到一个或活着的就只需要做拷贝就可以,而Yong区域借用了Copy算法,只是唯一的区别就是传统的copy算法是采用两个相同大小的内存来拷贝,浪费空间为50%,所以分代的目标就是想要实现很多优势所在,认为新生代85%以上的对象都应该是死掉的,所以S0和S1一般并不是很大),该算法为jdk 1.5以后对于绝大部分应用的最佳选择。

-XX:+UseParallelOldGC

 

-XX:ParallelGCThread=12:并行回收的线程数,最好根据实际情况而定,因为线程多往往存在征用调度和上下文切换的开销;而且也并非CPU越多线程数也可以设置越大,一般设置为12就再增加用处也不大,主要是算法本身内部的征用会导致其线程的极限就是这样。

 

设置Yong区域大小:

-Xmn  Yong区域的初始值和最大值一样大

-XX:NewSize-XX:MaxNewSize如果设置以为一样大就是和-Xmn,在JRockit中会动态变化这些参数,根据实际情况有可能会变化出两个Yong区域,或者没有Yong区域,有些时候会生出来一个半长命对象区域;这里除了这几个参数外,还有一个参数是NewRatio是设置Old/Yong的倍数的,这几个参数都是有冲突的,服务器端建议是设置-Xmn就可以了,如果几个参数全部都有设置,-Xmn和-XX:NewSize与-XX:MaxNewSize将是谁设置在后面,以谁的为准,而-XX:NewSize -XX:MaxNewSize与-XX:NewRatio时,那么参数设置的结果可能会以下这样的(jdk 1.4.1后):

min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))

-XX:NewRatio为Old区域为Yong的多少倍,间接设置Yong的大小,1.6中如果使用此参数,则默认会在适当时候被动态调整,具体请看下面参数UseAdaptiveSizepollcy 的说明。

三个参数不要同时设置,因为都是设置Yong的大小的。

 

-XX:SurvivorRatio:该参数为Eden与两个求助空间之一的比例,注意Yong的大小等价于Eden + S0 + S1,S0和S1的大小是等价的,这个参数为Eden与其中一个S区域的大小比例,如参数为8,那么Eden就占用Yong的80%,而S0和S1分别占用10%。

以前的老版本有一个参数为:-XX:InitialSurivivorRatio,如果不做任何设置,就会以这个参数为准,这个参数的默认值就是8,不过这个参数并不是Eden/Survivor的大小,而是Yong/Survivor,所以所以默认值8,代表每一个S区域的空间大小为Yong区域的12.5%而不是10%。另外顺便提及一下,每次大家看到GC日志的时候,GC日志中的每个区域的最大值,其中Yong的空间最大值,始终比设置的Yong空间的大小要小一点,大概是小12.5%左右,那是因为每次可用空间为Eden加上一个Survivor区域的大小,而不是整个Yong的大小,因为可用空间每次最多是这样大,两个Survivor区域始终有一块是空的,所以不会加上两个来计算。

 

-XX:MaxTenuringThreshold=15:在正常情况下,新申请的对象在Yong区域发生多少次GC后就会被移动到Old(非正常就是S0或S1放不下或者不太可能出现的Eden都放不下的对象),这个参数一般不会超过16(因为计数器从0开始计数,所以设置为15的时候相当于生命周期为16)。

要查看现在的这个值的具体情况,可以使用参数:-XX:+PrintTenuringDistribution

  

通过上面的jmap应该可以看出我的机器上的MinHeapFreeRatio和MaxHeapFreeRatio分别为40个70,也就是大家经常说的在GC后剩余空间小于40%时capacity开始增大,而大于70%时减小,由于我们不希望让它移动,所以这两个参数几乎没有意义,如果你需要设置就设置参数为:

-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70

 

JDK 1.6后有一个动态调节板块的,当然如果你的每一个板块都是设置固定值,这个参数也没有用,不过如果是非固定的,建议还是不要动态调整,默认是开启的,建议将其关掉,参数为:

-XX:+UseAdaptiveSizepollcy 建议使用-XX:-UseAdaptiveSizepollcy关掉,为什么当你的参数设置了NewRatio、Survivor、MaxTenuringThreshold这几个参数如果在启动了动态更新情况下,是无效的,当然如果你设置-Xmn是有效的,但是如果设置的比例的话,初始化可能会按照你的参数去运行,不过运行过程中会通过一定的算法动态修改,监控中你可能会发现这些参数会发生改变,甚至于S0和S1的大小不一样。

如果启动了这个参数,又想要跟踪变化,那么就使用参数:-XX:+PrintAdaptiveSizePolicy

 

上面已经提到,javaNIO中通过Direct内存来提高性能,这个区域的大小默认是64M,在适当的场景可以设置大一些。

-XX:MaxDirectMemorySize

 

一个不太常用的参数:

-XX:+ScavengeBeforeFullGC 默认是开启状态,在full GC前先进行minor GC。

 

对于java堆中如果要设置大页内存,可以通过设置参数:

付:此参数必须在操作系统的内核支持的基础上,需要在OS级别做操作为:

echo 1024 > /proc/sys/vm/nr_hugepages

echo 2147483647 > /proc/sys/kernel/shmmax

-XX:+UseLargePages

-XX:LargePageSizeInBytes

此时整个JVM都将在这块内存中,否则全部不在这块内存中。 

 

javaIO的临时目录设置

-Djava.io.tmpdir

jstack会去寻找/tmp/hsperfdata_admin下去寻找与进程号相同的文件,32位机器上是没有问题的,64为机器的是有BUG的,在jdk 1.6u23版本中已经修复了这个bug,如果你遇到这个问题,就需要升级JDK了。

 

还记得上次说的平均晋升大小吗,在并行GC时,如果平均晋升大小大于old剩余空间,则发生full GC,那么当小于剩余空间时,也就是平均晋升小于剩余空间,但是剩余空间小于eden + 一个survivor的空间时,此时就依赖于参数:

-XX:-HandlePromotionFailure

启动该参数时,上述情况成立就发生minor gc(YGC),大于则发生full gc(major gc)。

 

一般默认直接分配的对象如果大于Eden的一半就会直接晋升到old区域,但是也可以通过参数来指定:

-XX:PretenureSizeThreshold=2m  我个人不建议使用这个参数

也就是当申请对象大于这个值就会晋升到old区域。

 

传说中GC时间的限制,一个是通过比例限制,一个是通过最大暂停时间限制,但是GC时间能限制么,呵呵,在增量中貌似可以限制,不过不能限制住GC总体的时间,所以这个参数也不是那么关键。

-XX:GCTimeRatio=

-XX:MaxGCPauseMillis

-XX:GCTimeLimit

要看到真正暂停的时间就一个是看GCDetail的日志,另一个是设置参数看:

-XX:+PrintGCApplicationStoppedTime 

 

有些人,有些人就是喜欢在代码里面里头写System.gc(),耍酷,这个不是测试程序是线上业务,这样将会导致N多的问题,不多说了,你应该懂的,不懂的话看下书吧,而RMI是很不听话的一个鸟玩意,EJB的框架也是基于RMI写的,RMI为什么不听话呢,就是它自己在里面非要搞个System.gc(),哎,为了放置频繁的做,频繁的做,你就将这个命令的执行禁用掉吧,当然程序不用改,不然那些EJB都跑步起来了,呵呵:

-XX:+DisableExplicitGC 默认是没有禁用掉,写成+就是禁用掉的了,但是有些时候在使用allocateDirect的时候,很多时候还真需要System.gc来强制回收这块资源。

 

内存溢出时导出溢出的错误信息:
-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/xieyu/logs/   这个参数指定导出时的路径,不然导出的路径就是虚拟机的目标位置,不好找了,默认的文件名是:java_pid<进程号>.hprof,这个文件可以类似使用jmap -dump:file=....,format=b <pid>来dump类似的内容,文件后缀都是hprof,然后下载mat工具进行分析即可(不过内存有多大dump文件就多大,而本地分析的时候内存也需要那么大,所以很多时候下载到本地都无法启动是很正常的),后续文章有机会我们来说明这些工具,另外jmap -dump参数也不要经常用,会导致应用挂起哦;另外此参数只会在第一次输出OOM的时候才会进行堆的dump操作(java heap的溢出是可以继续运行再运行的程序的,至于web应用是否服务要看应用服务器自身如何处理,而c heap区域的溢出就根本没有dump的机会,因为直接就宕机了,目前系统无法看到c heap的大小以及内部变化,要看大小只能间接通过看JVM进程的内存大小(top或类似参数),这个大小一般会大于heap+perm的大小,多余的部分基本就可以认为是c heap的大小了,而看内部变化呢只有google perftools可以达到这个目的),如果内存过大这个dump操作将会非常长,所以hotspot如果以后想管理大内存,这块必须有新的办法出来。

最后,用dump出来的文件,通过mat分析出来的结果往往有些时候难以直接确定到底哪里有问题,可以看到的维度大概有:那个类使用的内存最多,以及每一个线程使用的内存,以及线程内部每一个调用的类和方法所使用的内存,但是很多时候无法判定到底是程序什么地方调用了这个类或者方法,因为这里只能看到最终消耗内存的类,但是不知道谁使用了它,一个办法是扫描代码,但是太笨重,而且如果是jar包中调用了就不好弄了,另一种方法是写agent,那么就需要相应的配合了,但是有一个非常好的工具就是btrace工具(jdk 1.7貌似还不支持),可以跟踪到某个类的某个方法被那些类中的方法调用过,那这个问题就好说了,只要知道开销内存的是哪一个类,就能知道谁调用过它,OK,关于btrace的不是本文重点,网上都有,后续文章有机会再探讨,
原理:
No performance impact during runtime(无性能影响)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M内存就dump出512M的空间大小)
JVM is “dead” during dumping(死掉时dump)
Restarting JVM during this dump will cause unusable .hprof file(重启导致文件不可用) 

 

注明的NUMA架构,在JVM中开始支持,当然也需要CPU和OS的支持才可以,需要设置参数为:

-XX:+UseNUMA  必须在并行GC的基础上才有的

老年代无法分配区域的最大等待时间为(默认值为0,但是也不要去动它):

-XX:GCExpandToAllocateDelayMillis 

让JVM中所有的set和get方法转换为本地代码:

-XX:+UseFastAccessorMethods

以时间戳输出Heap的利用率

-XX:+PrintHeapUsageOverTime 

 在64bit的OS上面(其实一般达不到57位左右),由于指针会放大为8个byte,所以会导致空间使用增加,当然,如果内存够大,就没有问题,但是如果升级到64bit系统后,只是想让内存达到4G或者8G,那么就完全可以通过很多指针压缩为4byte就OK了,所以在提供以下参数(本参数于jdk 1.6u23后使用,并自动开启,所以也不需要你设置,知道就OK):

-XX:+UseCompressedOops   请注意:这个参数默认在64bit的环境下默认启动,但是如果JVM的内存达到32G后,这个参数就会默认为不启动,因为32G内存后,压缩就没有多大必要了,要管理那么大的内存指针也需要很大的宽度了。

后台JIT编译优化启动

-XX:+BackgroundCompilation

如果你要输出GC的日志以及时间戳,相关的参数有:

-XX:+PrintGCDetails  输出GC的日志详情,包含了时间戳

-XX:+PrintGCTimeStamps 输出GC的时间戳信息,按照启动JVM后相对时间的每次GC的相对秒值(毫秒在小数点后面),也就是每次GC相对启动JVM启动了多少秒后发生了这次GC

-XX:+PrintGCDateStamps输出GC的时间信息,会按照系统格式的日期输出每次GC的时间

-XX:+PrintGCTaskTimeStamps输出任务的时间戳信息,这个细节上比较复杂,后续有文章来探讨。

-XX:-TraceClassLoading  跟踪类的装载

-XX:-TraceClassUnloading 跟踪类的卸载

-XX:+PrintHeapAtGC  输出GC后各个堆板块的大小。

将常量信息GC信息输出到日志文件:

-Xloggc:/home/xieyu/logs/gc.log

 

 

现在面对大内存比较流行是是CMS GC(最少1.5才支持),首先明白CMS的全称是什么,不是传统意义上的内容管理系统(Content Management System)哈,第一次我也没看懂,它的全称是:Concurrent Mark Sweep,三个单词分别代表并发、标记、清扫(主意这里没有compact操作,其实CMS GC的确没有compact操作),也就是在程序运行的同时进行标记和清扫工作,至于它的原理前面有提及过,只是有不同的厂商在上面做了一些特殊的优化,比如一些厂商在标记根节点的过程中,标记完当前的根,那么这个根下面的内容就不会被暂停恢复运行了,而移动过程中,通过读屏障来看这个内存是不是发生移动,如果在移动稍微停一下,移动过去后再使用,hotspot还没这么厉害,暂停时间还是挺长的,只是相对其他的GC策略在面对大内存来讲是不错的选择。

 

下面看一些CMS的策略(并发GC总时间会比常规的并行GC长,因为它是在运行时去做GC,很多资源征用都会影响其GC的效率,而总体的暂停时间会短暂很多很多,其并行线程数默认为:(上面设置的并行线程数 + 3)/ 4

 

付:CMS是目前Hotspot管理大内存最好的JVM,如果是常规的JVM,最佳选择为ParallelOldGC,如果必须要以响应时间为准,则选择CMS,不过CMS有两个隐藏的隐患:

1、CMS GC虽然是并发且并行运行的GC,但是初始化的时候如果采用默认值92%JVM 1.5的白皮书上描述为68%其实是错误的,1.6是正确的),就很容易出现问题,因为CMS GC仅仅针对Old区域,Yong区域使用ParNew算法,也就是Old的CMS回收和Yong的回收可以同时进行,也就是回收过程中Yong有可能会晋升对象Old,并且业务也可以同时运行,所以92%基本开始启动CMS GC很有可能old的内存就不够用了,当内存不够用的时候,就启动Full GC,并且这个Full GC是串行的,所以如果弄的不好,CMS会比并行GC更加慢,为什么要启用串行是因为CMS GC、并行GC、串行GC的继承关系决定的,简单说就是它没办法去调用并行GC的代码,细节说后续有文章来细节说明),建议这个值设置为70%左右吧,不过具体时间还是自己决定。

2、CMS GC另一个大的隐患,其实不看也差不多应该清楚,看名字就知道,就是不会做Compact操作,它最恶心的地方也在这里,所以上面才说一般的应用都不使用它,它只有内存垃圾非常多,多得无法分配晋升的空间的时候才会出现一次compact,但是这个是Full GC,也就是上面的串行,很恐怖的,所以内存不是很大的,不要考虑使用它,而且它的算法十分复杂。

 

还有一些小的隐患是:和应用一起征用CPU(不过这个不是大问题,增加CPU即可)、整个运行过程中时间比并行GC长(这个也不是大问题,因为我们更加关心暂停时间而不是运行时间,因为暂停会影响非常多的业务)。

启动CMS为全局GC方法(注意这个参数也不能上面的并行GC进行混淆,Yong默认是并行的,上面已经说过

-XX:+UseConcMarkSweepGC

在并发GC下启动增量模式,只能在CMS GC下这个参数才有效。

-XX:+CMSIncrementalMode

启动自动调节duty cycle,即在CMS GC中发生的时间比率设置,也就是说这段时间内最大允许发生多长时间的GC工作是可以调整的。

-XX:+CMSIncrementalPacing

在上面这个参数设定后可以分别设置以下两个参数(参数设置的比率,范围为0-100):

-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10

增量GC上还有一个保护因子(CMSIncrementalSafetyFactor),不太常用;CMSIncrementalOffset提供增量GC连续时间比率的设置;CMSExpAvgFactor为增量并发的GC增加权重计算。

-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset= 
-XX:CMSExpAvgFactor=

 

是否启动并行CMS GC(默认也是开启的)

-XX:+CMSParallelRemarkEnabled

要单独对CMS GC设置并行线程数就设置(默认也不需要设置):

-XX:ParallelCMSThreads

 

对PernGen进行垃圾回收:

JDK 1.5在CMS GC基础上需要设置参数(也就是前提是CMS GC才有):

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

1.6以后的版本无需设置:-XX:+CMSPermGenSweepingEnabled,注意,其实一直以来Full GC都会触发对Perm的回收过程,CMS GC需要有一些特殊照顾,虽然VM会对这块区域回收,但是Perm回收的条件几乎不太可能实现,首先需要这个类的classloader必须死掉,才可以将该classloader下所有的class干掉,也就是要么全部死掉,要么全部活着;另外,这个classloader下的class没有任何object在使用,这个也太苛刻了吧,因为常规的对象申请都是通过系统默认的,应用服务器也有自己默认的classloader,要让它死掉可能性不大,如果这都死掉了,系统也应该快挂了。

 

CMS GC因为是在程序运行时进行GC,不会暂停,所以不能等到不够用的时候才去开启GC,官方说法是他们的默认值是68%,但是可惜的是文档写错了,经过很多测试和源码验证这个参数应该是在92%的时候被启动,虽然还有8%的空间,但是还是很可怜了,当CMS发现内存实在不够的时候又回到常规的并行GC,所以很多人在没有设置这个参数的时候发现CMS GC并没有神马优势嘛,和并行GC一个鸟样子甚至于更加慢,所以这个时候需要设置参数(这个参数在上面已经说过,启动CMS一定要设置这个参数):

-XX:CMSInitiatingOccupancyFraction=70

这样保证Old的内存在使用到70%的时候,就开始启动CMS了;如果你真的想看看默认值,那么就使用参数:-XX:+PrintCMSInitiationStatistics 这个变量只有JDK 1.6可以使用 1.5不可以,查看实际值-XX:+PrintCMSStatistics;另外,还可以设置参数-XX:CMSInitiatingPermOccupancyFraction来设置Perm空间达到多少时启动CMS GC,不过意义不大。

JDK 1.6以后有些时候启动CMS GC是根据计算代价进行启动,也就是不一定按照你指定的参数来设置的,如果你不想让它按照所谓的成本来计算GC的话,那么你就使用一个参数:-XX:+UseCMSInitiatingOccupancyOnly,默认是false,它就只会按照你设置的比率来启动CMS GC了。如果你的程序中有System.gc以及设置了ExplicitGCInvokesConcurrent在jdk 1.6中,这种情况使用NIO是有可能产生问题的。

 

启动CMS GC的compation操作,也就是发生多少次后做一次全局的compaction:

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:发生多少次CMS Full GC,这个参数最好不要设置,因为要做compaction的话,也就是真正的Full GC是串行的,非常慢,让它自己去决定什么时候需要做compaction。

 

-XX:CMSMaxAbortablePrecleanTime=5000 设置preclean步骤的超时时间,单位为毫秒,preclean为cms gc其中一个步骤,关于cms gc步骤比较多,本文就不细节探讨了。

 

并行GC在mark阶段,可能会同时发生minor GC,old区域也可能发生改变,于是并发GC会对发生了改变的内容进行remark操作,这个触发的条件是:

-XX:CMSScheduleRemarkEdenSizeThreshold

-XX:CMSScheduleRemarkEdenPenetration

即Eden区域多大的时候开始触发,和eden使用量超过百分比多少的时候触发,前者默认是2M,后者默认是50%。 

但是如果长期不做remark导致old做不了,可以设置超时,这个超时默认是5秒,可以通过参数:

-XX:CMSMaxAbortablePrecleanTime

-XX:+ExplicitGCInvokesConcurrent 在显示发生GC的时候,允许进行并行GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 几乎和上面一样,只不过多一个对Perm区域的回收而已。

 

补充:

其实JVM还有很多的版本,很多的厂商,与其优化的原则,随便举两个例子hotspot在GC中做的一些优化(这里不说代码的编译时优化或运行时优化):

Eden申请的空间对象由Old区域的某个对象的一个属性指向(也就是Old区域的这个空间不回收,Eden这块就没有必要考虑回收),所以Hotspot在CPU写上面,做了一个屏障,当发生赋值语句的时候(对内存来讲赋值就是一种写操作),如果发现是一个新的对象由Old指向Eden,那么就会将这个对象记录在一个卡片机里面,这个卡片机是有很多512字节的卡片组成,当在YGC过程中,就基本不会去移动或者管理这块对象(付:这种卡片机会在CMS GC的算法中使用,不过和这个卡片不是放在同一个地方的,也是CMS GC的关键,对于CMS GC的算法细节描述,后续文章我们单独说明)。

Old区域对于一些比较大的对象,JVM就不会去管理个对象,也就是compact过程中不会去移动这块对象的区域等等吧。

 

以上大部分参数为hotspot的自带关于性能的参数,参考版本为JDK 1.5和1.6的版本,很多为个人经验说明,不足以说明所有问题,如果有问题,欢迎探讨;另外,JDK的参数是不是就只有这些呢,肯定并不是,我知道的也不止这些,但是有些觉得没必要说出来的参数和一些数学运算的参数我就不想给出来了,比如像禁用掉GC的参数有神马意义,我们的服务器要是把这个禁用掉干个屁啊,呵呵,做测试还可以用这玩玩,让它不做GC直接溢出;还有一些什么计算因子啥的,还有很多复杂的数学运算规则,要是把这个配置明白了,就太那个了,而且一般情况下也没那个必要,JDK到现在的配置参数多达上500个以上,要知道完的话慢慢看吧,不过意义不大,而且要知道默认值最靠谱的是看源码而不是看文档,官方文档也只能保证绝大部是正确的,不能保证所有的是正确的。

 

 

本文最后追加在jdk 1.6u 24后通过上面说明的-XX:+PrintFlagsFinal输出的参数以及默认值(还是那句话,在不同的平台上是不一样的),输出的参数如下,可以看看JVM的参数是相当的多,参数如此之多,你只需要掌握关键即可,参数还有很多有冲突的,不要纠结于每一个参数的细节:

$java -XX:+PrintFlagsFinal

uintx AdaptivePermSizeWeight               = 20               {product}
uintx AdaptiveSizeDecrementScaleFactor     = 4                {product}
uintx AdaptiveSizeMajorGCDecayTimeScale    = 10               {product}
uintx AdaptiveSizePausePolicy              = 0                {product}
uintx AdaptiveSizePolicyCollectionCostMargin  = 50               {product}
uintx AdaptiveSizePolicyInitializingSteps  = 20               {product}
uintx AdaptiveSizePolicyOutputInterval     = 0                {product}
uintx AdaptiveSizePolicyWeight             = 10               {product}
uintx AdaptiveSizeThroughPutPolicy         = 0                {product}
uintx AdaptiveTimeWeight                   = 25               {product}
 bool AdjustConcurrency                    = false            {product}
 bool AggressiveOpts                       = false            {product}
 intx AliasLevel                           = 3                {product}
 intx AllocatePrefetchDistance             = -1               {product}
 intx AllocatePrefetchInstr                = 0                {product}
 intx AllocatePrefetchLines                = 1                {product}
 intx AllocatePrefetchStepSize             = 16               {product}
 intx AllocatePrefetchStyle                = 1                {product}
 bool AllowJNIEnvProxy                     = false            {product}
 bool AllowParallelDefineClass             = false            {product}
 bool AllowUserSignalHandlers              = false            {product}
 bool AlwaysActAsServerClassMachine        = false            {product}
 bool AlwaysCompileLoopMethods             = false            {product}
 intx AlwaysInflate                        = 0                {product}
 bool AlwaysLockClassLoader                = false            {product}
 bool AlwaysPreTouch                       = false            {product}
 bool AlwaysRestoreFPU                     = false            {product}
 bool AlwaysTenure                         = false            {product}
 bool AnonymousClasses                     = false            {product}
 bool AssertOnSuspendWaitFailure           = false            {product}
 intx Atomics                              = 0                {product}
uintx AutoGCSelectPauseMillis              = 5000             {product}
 intx BCEATraceLevel                       = 0                {product}
 intx BackEdgeThreshold                    = 100000           {pd product}
 bool BackgroundCompilation                = true             {pd product}
uintx BaseFootPrintEstimate                = 268435456        {product}
 intx BiasedLockingBulkRebiasThreshold     = 20               {product}
 intx BiasedLockingBulkRevokeThreshold     = 40               {product}
 intx BiasedLockingDecayTime               = 25000            {product}
 intx BiasedLockingStartupDelay            = 4000             {product}
 bool BindGCTaskThreadsToCPUs              = false            {product}
 bool BlockOffsetArrayUseUnallocatedBlock  = false            {product}
 bool BytecodeVerificationLocal            = false            {product}
 bool BytecodeVerificationRemote           = true             {product}
 intx CICompilerCount                      = 1                {product}
 bool CICompilerCountPerCPU                = false            {product}
 bool CITime                               = false            {product}
 bool CMSAbortSemantics                    = false            {product}
uintx CMSAbortablePrecleanMinWorkPerIteration  = 100              {product}
 intx CMSAbortablePrecleanWaitMillis       = 100              {product}
uintx CMSBitMapYieldQuantum                = 10485760         {product}
uintx CMSBootstrapOccupancy                = 50               {product}
 bool CMSClassUnloadingEnabled             = false            {product}
uintx CMSClassUnloadingMaxInterval         = 0                {product}
 bool CMSCleanOnEnter                      = true             {product}
 bool CMSCompactWhenClearAllSoftRefs       = true             {product}
uintx CMSConcMarkMultiple                  = 32               {product}
 bool CMSConcurrentMTEnabled               = true             {product}
uintx CMSCoordinatorYieldSleepCount        = 10               {product}
 bool CMSDumpAtPromotionFailure            = false            {product}
uintx CMSExpAvgFactor                      = 50               {product}
 bool CMSExtrapolateSweep                  = false            {product}
uintx CMSFullGCsBeforeCompaction           = 0                {product}
uintx CMSIncrementalDutyCycle              = 10               {product}
uintx CMSIncrementalDutyCycleMin           = 0                {product}
 bool CMSIncrementalMode                   = false            {product}
uintx CMSIncrementalOffset                 = 0                {product}
 bool CMSIncrementalPacing                 = true             {product}
uintx CMSIncrementalSafetyFactor           = 10               {product}
uintx CMSIndexedFreeListReplenish          = 4                {product}
 intx CMSInitiatingOccupancyFraction       = -1               {product}
 intx CMSInitiatingPermOccupancyFraction   = -1               {product}
 intx CMSIsTooFullPercentage               = 98               {product}
double CMSLargeCoalSurplusPercent           =  {product}
double CMSLargeSplitSurplusPercent          =  {product}
 bool CMSLoopWarn                          = false            {product}
uintx CMSMaxAbortablePrecleanLoops         = 0                {product}
 intx CMSMaxAbortablePrecleanTime          = 5000             {product}
uintx CMSOldPLABMax                        = 1024             {product}
uintx CMSOldPLABMin                        = 16               {product}
uintx CMSOldPLABNumRefills                 = 4                {product}
uintx CMSOldPLABReactivityCeiling          = 10               {product}
uintx CMSOldPLABReactivityFactor           = 2                {product}
 bool CMSOldPLABResizeQuicker              = false            {product}
uintx CMSOldPLABToleranceFactor            = 4                {product}
 bool CMSPLABRecordAlways                  = true             {product}
uintx CMSParPromoteBlocksToClaim           = 16               {product}
 bool CMSParallelRemarkEnabled             = true             {product}
 bool CMSParallelSurvivorRemarkEnabled     = true             {product}
 bool CMSPermGenPrecleaningEnabled         = true             {product}
uintx CMSPrecleanDenominator               = 3                {product}
uintx CMSPrecleanIter                      = 3                {product}
uintx CMSPrecleanNumerator                 = 2                {product}
 bool CMSPrecleanRefLists1                 = true             {product}
 bool CMSPrecleanRefLists2                 = false            {product}
 bool CMSPrecleanSurvivors1                = false            {product}
 bool CMSPrecleanSurvivors2                = true             {product}
uintx CMSPrecleanThreshold                 = 1000             {product}
 bool CMSPrecleaningEnabled                = true             {product}
 bool CMSPrintChunksInDump                 = false            {product}
 bool CMSPrintObjectsInDump                = false            {product}
uintx CMSRemarkVerifyVariant               = 1                {product}
 bool CMSReplenishIntermediate             = true             {product}
uintx CMSRescanMultiple                    = 32               {product}
uintx CMSRevisitStackSize                  = 1048576          {product}
uintx CMSSamplingGrain                     = 16384            {product}
 bool CMSScavengeBeforeRemark              = false            {product}
uintx CMSScheduleRemarkEdenPenetration     = 50               {product}
uintx CMSScheduleRemarkEdenSizeThreshold   = 2097152          {product}
uintx CMSScheduleRemarkSamplingRatio       = 5                {product}
double CMSSmallCoalSurplusPercent           =  {product}
double CMSSmallSplitSurplusPercent          =  {product}
 bool CMSSplitIndexedFreeListBlocks        = true             {product}
 intx CMSTriggerPermRatio                  = 80               {product}
 intx CMSTriggerRatio                      = 80               {product}
 bool CMSUseOldDefaults                    = false            {product}
 intx CMSWaitDuration                      = 2000             {product}
uintx CMSWorkQueueDrainThreshold           = 10               {product}
 bool CMSYield                             = true             {product}
uintx CMSYieldSleepCount                   = 0                {product}
 intx CMSYoungGenPerWorker                 = 16777216         {product}
uintx CMS_FLSPadding                       = 1                {product}
uintx CMS_FLSWeight                        = 75               {product}
uintx CMS_SweepPadding                     = 1                {product}
uintx CMS_SweepTimerThresholdMillis        = 10               {product}
uintx CMS_SweepWeight                      = 75               {product}
 bool CheckJNICalls                        = false            {product}
 bool ClassUnloading                       = true             {product}
 intx ClearFPUAtPark                       = 0                {product}
 bool ClipInlining                         = true             {product}
uintx CodeCacheExpansionSize               = 32768            {pd product}
uintx CodeCacheFlushingMinimumFreeSpace    = 1536000          {product}
uintx CodeCacheMinimumFreeSpace            = 512000           {product}
 bool CollectGen0First                     = false            {product}
 bool CompactFields                        = true             {product}
 intx CompilationPolicyChoice              = 0                {product}
 intx CompilationRepeat                    = 0                {C1 product}
ccstrlist CompileCommand                       =                  {product}
ccstr CompileCommandFile                   =  {product}
ccstrlist CompileOnly                          =                  {product}
 intx CompileThreshold                     = 1500             {pd product}
 bool CompilerThreadHintNoPreempt          = true             {product}
 intx CompilerThreadPriority               = -1               {product}
 intx CompilerThreadStackSize              = 0                {pd product}
uintx ConcGCThreads                        = 0                {product}
 bool ConvertSleepToYield                  = true             {pd product}
 bool ConvertYieldToSleep                  = false            {product}
 bool DTraceAllocProbes                    = false            {product}
 bool DTraceMethodProbes                   = false            {product}
 bool DTraceMonitorProbes                  = false            {product}
uintx DefaultMaxRAMFraction                = 4                {product}
 intx DefaultThreadPriority                = -1               {product}
 intx DeferPollingPageLoopCount            = -1               {product}
 intx DeferThrSuspendLoopCount             = 4000             {product}
 bool DeoptimizeRandom                     = false            {product}
 bool DisableAttachMechanism               = false            {product}
 bool DisableExplicitGC                    = false            {product}
 bool DisplayVMOutputToStderr              = false            {product}
 bool DisplayVMOutputToStdout              = false            {product}
 bool DontCompileHugeMethods               = true             {product}
 bool DontYieldALot                        = false            {pd product}
 bool DumpSharedSpaces                     = false            {product}
 bool EagerXrunInit                        = false            {product}
 intx EmitSync                             = 0                {product}
uintx ErgoHeapSizeLimit                    = 0                {product}
ccstr ErrorFile                            =  {product}
 bool EstimateArgEscape                    = true             {product}
 intx EventLogLength                       = 2000             {product}
 bool ExplicitGCInvokesConcurrent          = false            {product}
 bool ExplicitGCInvokesConcurrentAndUnloadsClasses  = false            {produ
 bool ExtendedDTraceProbes                 = false            {product}
 bool FLSAlwaysCoalesceLarge               = false            {product}
uintx FLSCoalescePolicy                    = 2                {product}
double FLSLargestBlockCoalesceProximity     =  {product}
 bool FailOverToOldVerifier                = true             {product}
 bool FastTLABRefill                       = true             {product}
 intx FenceInstruction                     = 0                {product}
 intx FieldsAllocationStyle                = 1                {product}
 bool FilterSpuriousWakeups                = true             {product}
 bool ForceFullGCJVMTIEpilogues            = false            {product}
 bool ForceNUMA                            = false            {product}
 bool ForceSharedSpaces                    = false            {product}
 bool ForceTimeHighResolution              = false            {product}
 intx FreqInlineSize                       = 325              {pd product}
 intx G1ConcRefinementGreenZone            = 0                {product}
 intx G1ConcRefinementRedZone              = 0                {product}
 intx G1ConcRefinementServiceIntervalMillis  = 300              {product}
uintx G1ConcRefinementThreads              = 0                {product}
 intx G1ConcRefinementThresholdStep        = 0                {product}
 intx G1ConcRefinementYellowZone           = 0                {product}
 intx G1ConfidencePercent                  = 50               {product}
uintx G1HeapRegionSize                     = 0                {product}
 intx G1MarkRegionStackSize                = 1048576          {product}
 intx G1RSetRegionEntries                  = 0                {product}
uintx G1RSetScanBlockSize                  = 64               {product}
 intx G1RSetSparseRegionEntries            = 0                {product}
 intx G1RSetUpdatingPauseTimePercent       = 10               {product}
 intx G1ReservePercent                     = 10               {product}
 intx G1SATBBufferSize                     = 1024             {product}
 intx G1UpdateBufferSize                   = 256              {product}
 bool G1UseAdaptiveConcRefinement          = true             {product}
 bool G1UseFixedWindowMMUTracker           = false            {product}
uintx GCDrainStackTargetSize               = 64               {product}
uintx GCHeapFreeLimit                      = 2                {product}
 bool GCLockerInvokesConcurrent            = false            {product}
 bool GCOverheadReporting                  = false            {product}
 intx GCOverheadReportingPeriodMS          = 100              {product}
 intx GCPauseIntervalMillis                = 500              {product}
uintx GCTaskTimeStampEntries               = 200              {product}
uintx GCTimeLimit                          = 98               {product}
uintx GCTimeRatio                          = 99               {product}
ccstr HPILibPath                           =  {product}
 bool HandlePromotionFailure               = true             {product}
uintx HeapBaseMinAddress                   = 2147483648       {pd product}
 bool HeapDumpAfterFullGC                  = false            {manageable}
 bool HeapDumpBeforeFullGC                 = false            {manageable}
 bool HeapDumpOnOutOfMemoryError           = false            {manageable}
ccstr HeapDumpPath                         =  {manageable}
uintx HeapFirstMaximumCompactionCount      = 3                {product}
uintx HeapMaximumCompactionInterval        = 20               {product}
 bool IgnoreUnrecognizedVMOptions          = false            {product}
uintx InitialCodeCacheSize                 = 163840           {pd product}
uintx InitialHeapSize                     := 16777216         {product}
uintx InitialRAMFraction                   = 64               {product}
uintx InitialSurvivorRatio                 = 8                {product}
 intx InitialTenuringThreshold             = 7                {product}
uintx InitiatingHeapOccupancyPercent       = 45               {product}
 bool Inline                               = true             {product}
 intx InlineSmallCode                      = 1000             {pd product}
 intx InterpreterProfilePercentage         = 33               {product}
 bool JNIDetachReleasesMonitors            = true             {product}
 bool JavaMonitorsInStackTrace             = true             {product}
 intx JavaPriority10_To_OSPriority         = -1               {product}
 intx JavaPriority1_To_OSPriority          = -1               {product}
 intx JavaPriority2_To_OSPriority          = -1               {product}
 intx JavaPriority3_To_OSPriority          = -1               {product}
 intx JavaPriority4_To_OSPriority          = -1               {product}
 intx JavaPriority5_To_OSPriority          = -1               {product}
 intx JavaPriority6_To_OSPriority          = -1               {product}
 intx JavaPriority7_To_OSPriority          = -1               {product}
 intx JavaPriority8_To_OSPriority          = -1               {product}
 intx JavaPriority9_To_OSPriority          = -1               {product}
 bool LIRFillDelaySlots                    = false            {C1 pd product}
uintx LargePageHeapSizeThreshold           = 134217728        {product}
uintx LargePageSizeInBytes                 = 0                {product}
 bool LazyBootClassLoader                  = true             {product}
 bool ManagementServer                     = false            {product}
uintx MarkStackSize                        = 32768            {product}
uintx MarkStackSizeMax                     = 4194304          {product}
 intx MarkSweepAlwaysCompactCount          = 4                {product}
uintx MarkSweepDeadRatio                   = 5                {product}
 intx MaxBCEAEstimateLevel                 = 5                {product}
 intx MaxBCEAEstimateSize                  = 150              {product}
 intx MaxDirectMemorySize                  = -1               {product}
 bool MaxFDLimit                           = true             {product}
uintx MaxGCMinorPauseMillis                = 4294967295       {product}
uintx MaxGCPauseMillis                     = 4294967295       {product}
uintx MaxHeapFreeRatio                     = 70               {product}
uintx MaxHeapSize                         := 268435456        {product}
 intx MaxInlineLevel                       = 9                {product}
 intx MaxInlineSize                        = 35               {product}
 intx MaxJavaStackTraceDepth               = 1024             {product}
uintx MaxLiveObjectEvacuationRatio         = 100              {product}
uintx MaxNewSize                           = 4294967295       {product}
uintx MaxPermHeapExpansion                 = 4194304          {product}
uintx MaxPermSize                          = 67108864         {pd product}
uint64_t MaxRAM                               = 1073741824       {pd product}
uintx MaxRAMFraction                       = 4                {product}
 intx MaxRecursiveInlineLevel              = 1                {product}
 intx MaxTenuringThreshold                 = 15               {product}
 intx MaxTrivialSize                       = 6                {product}
 bool MethodFlushing                       = true             {product}
 intx MinCodeCacheFlushingInterval         = 30               {product}
uintx MinHeapDeltaBytes                    = 131072           {product}
uintx MinHeapFreeRatio                     = 40               {product}
 intx MinInliningThreshold                 = 250              {product}
uintx MinPermHeapExpansion                 = 262144           {product}
uintx MinRAMFraction                       = 2                {product}
uintx MinSurvivorRatio                     = 3                {product}
uintx MinTLABSize                          = 2048             {product}
 intx MonitorBound                         = 0                {product}
 bool MonitorInUseLists                    = false            {product}
 bool MustCallLoadClassInternal            = false            {product}
 intx NUMAChunkResizeWeight                = 20               {product}
 intx NUMAPageScanRate                     = 256              {product}
 intx NUMASpaceResizeRate                  = 1073741824       {product}
 bool NUMAStats                            = false            {product}
 intx NativeMonitorFlags                   = 0                {product}
 intx NativeMonitorSpinLimit               = 20               {product}
 intx NativeMonitorTimeout                 = -1               {product}
 bool NeedsDeoptSuspend                    = false            {pd product}
 bool NeverActAsServerClassMachine         = true             {pd product}
 bool NeverTenure                          = false            {product}
 intx NewRatio                             = 2                {product}
uintx NewSize                              = 1048576          {product}
uintx NewSizeThreadIncrease                = 4096             {pd product}
 intx NmethodSweepFraction                 = 4                {product}
uintx OldPLABSize                          = 1024             {product}
uintx OldPLABWeight                        = 50               {product}
uintx OldSize                              = 4194304          {product}
 bool OmitStackTraceInFastThrow            = true             {product}
ccstrlist OnError                              =                  {product}
ccstrlist OnOutOfMemoryError                   =                  {product}
 intx OnStackReplacePercentage             = 933              {pd product}
uintx PLABWeight                           = 75               {product}
 bool PSChunkLargeArrays                   = true             {product}
 intx ParGCArrayScanChunk                  = 50               {product}
uintx ParGCDesiredObjsFromOverflowList     = 20               {product}
 bool ParGCTrimOverflow                    = true             {product}
 bool ParGCUseLocalOverflow                = false            {product}
 intx ParallelGCBufferWastePct             = 10               {product}
 bool ParallelGCRetainPLAB                 = true             {product}
uintx ParallelGCThreads                    = 0                {product}
 bool ParallelGCVerbose                    = false            {product}
uintx ParallelOldDeadWoodLimiterMean       = 50               {product}
uintx ParallelOldDeadWoodLimiterStdDev     = 80               {product}
 bool ParallelRefProcBalancingEnabled      = true             {product}
 bool ParallelRefProcEnabled               = false            {product}
uintx PausePadding                         = 1                {product}
 intx PerBytecodeRecompilationCutoff       = 200              {product}
 intx PerBytecodeTrapLimit                 = 4                {product}
 intx PerMethodRecompilationCutoff         = 400              {product}
 intx PerMethodTrapLimit                   = 100              {product}
 bool PerfAllowAtExitRegistration          = false            {product}
 bool PerfBypassFileSystemCheck            = false            {product}
 intx PerfDataMemorySize                   = 32768            {product}
 intx PerfDataSamplingInterval             = 50               {product}
ccstr PerfDataSaveFile                     =  {product}
 bool PerfDataSaveToFile                   = false            {product}
 bool PerfDisableSharedMem                 = false            {product}
 intx PerfMaxStringConstLength             = 1024             {product}
uintx PermGenPadding                       = 3                {product}
uintx PermMarkSweepDeadRatio               = 20               {product}
uintx PermSize                             = 12582912         {pd product}
 bool PostSpinYield                        = true             {product}
 intx PreBlockSpin                         = 10               {product}
 intx PreInflateSpin                       = 10               {pd product}
 bool PreSpinYield                         = false            {product}
 bool PreferInterpreterNativeStubs         = false            {pd product}
 intx PrefetchCopyIntervalInBytes          = -1               {product}
 intx PrefetchFieldsAhead                  = -1               {product}
 intx PrefetchScanIntervalInBytes          = -1               {product}
 bool PreserveAllAnnotations               = false            {product}
uintx PreserveMarkStackSize                = 1024             {product}
uintx PretenureSizeThreshold               = 0                {product}
 bool PrintAdaptiveSizePolicy              = false            {product}
 bool PrintCMSInitiationStatistics         = false            {product}
 intx PrintCMSStatistics                   = 0                {product}
 bool PrintClassHistogram                  = false            {manageable}
 bool PrintClassHistogramAfterFullGC       = false            {manageable}
 bool PrintClassHistogramBeforeFullGC      = false            {manageable}
 bool PrintCommandLineFlags                = false            {product}
 bool PrintCompilation                     = false            {product}
 bool PrintConcurrentLocks                 = false            {manageable}
 intx PrintFLSCensus                       = 0                {product}
 intx PrintFLSStatistics                   = 0                {product}
 bool PrintFlagsFinal                     := true             {product}
 bool PrintFlagsInitial                    = false            {product}
 bool PrintGC                              = false            {manageable}
 bool PrintGCApplicationConcurrentTime     = false            {product}
 bool PrintGCApplicationStoppedTime        = false            {product}
 bool PrintGCDateStamps                    = false            {manageable}
 bool PrintGCDetails                       = false            {manageable}
 bool PrintGCTaskTimeStamps                = false            {product}
 bool PrintGCTimeStamps                    = false            {manageable}
 bool PrintHeapAtGC                        = false            {product rw}
 bool PrintHeapAtGCExtended                = false            {product rw}
 bool PrintHeapAtSIGBREAK                  = true             {product}
 bool PrintJNIGCStalls                     = false            {product}
 bool PrintJNIResolving                    = false            {product}
 bool PrintOldPLAB                         = false            {product}
 bool PrintPLAB                            = false            {product}
 bool PrintParallelOldGCPhaseTimes         = false            {product}
 bool PrintPromotionFailure                = false            {product}
 bool PrintReferenceGC                     = false            {product}
 bool PrintRevisitStats                    = false            {product}
 bool PrintSafepointStatistics             = false            {product}
 intx PrintSafepointStatisticsCount        = 300              {product}
 intx PrintSafepointStatisticsTimeout      = -1               {product}
 bool PrintSharedSpaces                    = false            {product}
 bool PrintTLAB                            = false            {product}
 bool PrintTenuringDistribution            = false            {product}
 bool PrintVMOptions                       = false            {product}
 bool PrintVMQWaitTime                     = false            {product}
uintx ProcessDistributionStride            = 4                {product}
 bool ProfileInterpreter                   = false            {pd product}
 bool ProfileIntervals                     = false            {product}
 intx ProfileIntervalsTicks                = 100              {product}
 intx ProfileMaturityPercentage            = 20               {product}
 bool ProfileVM                            = false            {product}
 bool ProfilerPrintByteCodeStatistics      = false            {product}
 bool ProfilerRecordPC                     = false            {product}
uintx PromotedPadding                      = 3                {product}
 intx QueuedAllocationWarningCount         = 0                {product}
 bool RangeCheckElimination                = true             {product}
 intx ReadPrefetchInstr                    = 0                {product}
 intx ReadSpinIterations                   = 100              {product}
 bool ReduceSignalUsage                    = false            {product}
 intx RefDiscoveryPolicy                   = 0                {product}
 bool ReflectionWrapResolutionErrors       = true             {product}
 bool RegisterFinalizersAtInit             = true             {product}
 bool RelaxAccessControlCheck              = false            {product}
 bool RequireSharedSpaces                  = false            {product}
uintx ReservedCodeCacheSize                = 33554432         {pd product}
 bool ResizeOldPLAB                        = true             {product}
 bool ResizePLAB                           = true             {product}
 bool ResizeTLAB                           = true             {pd product}
 bool RestoreMXCSROnJNICalls               = false            {product}
 bool RewriteBytecodes                     = false            {pd product}
 bool RewriteFrequentPairs                 = false            {pd product}
 intx SafepointPollOffset                  = 256              {C1 pd product}
 intx SafepointSpinBeforeYield             = 2000             {product}
 bool SafepointTimeout                     = false            {product}
 intx SafepointTimeoutDelay                = 10000            {product}
 bool ScavengeBeforeFullGC                 = true             {product}
 intx SelfDestructTimer                    = 0                {product}
uintx SharedDummyBlockSize                 = 536870912        {product}
uintx SharedMiscCodeSize                   = 4194304          {product}
uintx SharedMiscDataSize                   = 4194304          {product}
uintx SharedReadOnlySize                   = 10485760         {product}
uintx SharedReadWriteSize                  = 12582912         {product}
 bool ShowMessageBoxOnError                = false            {product}
 intx SoftRefLRUPolicyMSPerMB              = 1000             {product}
 bool SplitIfBlocks                        = true             {product}
 intx StackRedPages                        = 1                {pd product}
 intx StackShadowPages                     = 3                {pd product}
 bool StackTraceInThrowable                = true             {product}
 intx StackYellowPages                     = 2                {pd product}
 bool StartAttachListener                  = false            {product}
 intx StarvationMonitorInterval            = 200              {product}
 bool StressLdcRewrite                     = false            {product}
 bool StressTieredRuntime                  = false            {product}
 bool SuppressFatalErrorMessage            = false            {product}
uintx SurvivorPadding                      = 3                {product}
 intx SurvivorRatio                        = 8                {product}
 intx SuspendRetryCount                    = 50               {product}
 intx SuspendRetryDelay                    = 5                {product}
 intx SyncFlags                            = 0                {product}
ccstr SyncKnobs                            =  {product}
 intx SyncVerbose                          = 0                {product}
uintx TLABAllocationWeight                 = 35               {product}
uintx TLABRefillWasteFraction              = 64               {product}
uintx TLABSize                             = 0                {product}
 bool TLABStats                            = true             {product}
uintx TLABWasteIncrement                   = 4                {product}
uintx TLABWasteTargetPercent               = 1                {product}
 bool TaggedStackInterpreter               = false            {product}
 intx TargetPLABWastePct                   = 10               {product}
 intx TargetSurvivorRatio                  = 50               {product}
uintx TenuredGenerationSizeIncrement       = 20               {product}
uintx TenuredGenerationSizeSupplement      = 80               {product}
uintx TenuredGenerationSizeSupplementDecay  = 2                {product}
 intx ThreadPriorityPolicy                 = 0                {product}
 bool ThreadPriorityVerbose                = false            {product}
uintx ThreadSafetyMargin                   = 52428800         {product}
 intx ThreadStackSize                      = 0                {pd product}
uintx ThresholdTolerance                   = 10               {product}
 intx Tier1BytecodeLimit                   = 10               {product}
 bool Tier1OptimizeVirtualCallProfiling    = true             {C1 product}
 bool Tier1ProfileBranches                 = true             {C1 product}
 bool Tier1ProfileCalls                    = true             {C1 product}
 bool Tier1ProfileCheckcasts               = true             {C1 product}
 bool Tier1ProfileInlinedCalls             = true             {C1 product}
 bool Tier1ProfileVirtualCalls             = true             {C1 product}
 bool Tier1UpdateMethodData                = false            {product}
 intx Tier2BackEdgeThreshold               = 100000           {pd product}
 intx Tier2CompileThreshold                = 1500             {pd product}
 intx Tier3BackEdgeThreshold               = 100000           {pd product}
 intx Tier3CompileThreshold                = 2500             {pd product}
 intx Tier4BackEdgeThreshold               = 100000           {pd product}
 intx Tier4CompileThreshold                = 4500             {pd product}
 bool TieredCompilation                    = false            {pd product}
 bool TimeLinearScan                       = false            {C1 product}
 bool TraceBiasedLocking                   = false            {product}
 bool TraceClassLoading                    = false            {product rw}
 bool TraceClassLoadingPreorder            = false            {product}
 bool TraceClassResolution                 = false            {product}
 bool TraceClassUnloading                  = false            {product rw}
 bool TraceGen0Time                        = false            {product}
 bool TraceGen1Time                        = false            {product}
ccstr TraceJVMTI                           =  {product}
 bool TraceLoaderConstraints               = false            {product rw}
 bool TraceMonitorInflation                = false            {product}
 bool TraceParallelOldGCTasks              = false            {product}
 intx TraceRedefineClasses                 = 0                {product}
 bool TraceSafepointCleanupTime            = false            {product}
 bool TraceSuspendWaitFailures             = false            {product}
 intx TypeProfileMajorReceiverPercent      = 90               {product}
 intx TypeProfileWidth                     = 2                {product}
 intx UnguardOnExecutionViolation          = 0                {product}
 bool Use486InstrsOnly                     = false            {product}
 bool UseAdaptiveGCBoundary                = false            {product}
 bool UseAdaptiveGenerationSizePolicyAtMajorCollection  = true             {p
 bool UseAdaptiveGenerationSizePolicyAtMinorCollection  = true             {p
 bool UseAdaptiveNUMAChunkSizing           = true             {product}
 bool UseAdaptiveSizeDecayMajorGCCost      = true             {product}
 bool UseAdaptiveSizePolicy                = true             {product}
 bool UseAdaptiveSizePolicyFootprintGoal   = true             {product}
 bool UseAdaptiveSizePolicyWithSystemGC    = false            {product}
 bool UseAddressNop                        = false            {product}
 bool UseAltSigs                           = false            {product}
 bool UseAutoGCSelectPolicy                = false            {product}
 bool UseBiasedLocking                     = true             {product}
 bool UseBoundThreads                      = true             {product}
 bool UseCMSBestFit                        = true             {product}
 bool UseCMSCollectionPassing              = true             {product}
 bool UseCMSCompactAtFullCollection        = true             {product}
 bool UseCMSInitiatingOccupancyOnly        = false            {product}
 bool UseCodeCacheFlushing                 = false            {product}
 bool UseCompiler                          = true             {product}
 bool UseCompilerSafepoints                = true             {product}
 bool UseConcMarkSweepGC                   = false            {product}
 bool UseCountLeadingZerosInstruction      = false            {product}
 bool UseCounterDecay                      = true             {product}
 bool UseDepthFirstScavengeOrder           = true             {product}
 bool UseFastAccessorMethods               = true             {product}
 bool UseFastEmptyMethods                  = true             {product}
 bool UseFastJNIAccessors                  = true             {product}
 bool UseG1GC                              = false            {product}
 bool UseGCOverheadLimit                   = true             {product}
 bool UseGCTaskAffinity                    = false            {product}
 bool UseHeavyMonitors                     = false            {product}
 bool UseInlineCaches                      = true             {product}
 bool UseInterpreter                       = true             {product}
 bool UseLWPSynchronization                = true             {product}
 bool UseLargePages                        = false            {pd product}
 bool UseLargePagesIndividualAllocation   := false            {pd product}
 bool UseLoopCounter                       = true             {product}
 bool UseMaximumCompactionOnSystemGC       = true             {product}
 bool UseMembar                            = false            {product}
 bool UseNUMA                              = false            {product}
 bool UseNewFeature1                       = false            {C1 product}
 bool UseNewFeature2                       = false            {C1 product}
 bool UseNewFeature3                       = false            {C1 product}
 bool UseNewFeature4                       = false            {C1 product}
 bool UseNewLongLShift                     = false            {product}
 bool UseNiagaraInstrs                     = false            {product}
 bool UseOSErrorReporting                  = false            {pd product}
 bool UseOnStackReplacement                = true             {pd product}
 bool UsePSAdaptiveSurvivorSizePolicy      = true             {product}
 bool UseParNewGC                          = false            {product}
 bool UseParallelDensePrefixUpdate         = true             {product}
 bool UseParallelGC                        = false            {product}
 bool UseParallelOldGC                     = false            {product}
 bool UseParallelOldGCCompacting           = true             {product}
 bool UseParallelOldGCDensePrefix          = true             {product}
 bool UsePerfData                          = true             {product}
 bool UsePopCountInstruction               = false            {product}
 intx UseSSE                               = 99               {product}
 bool UseSSE42Intrinsics                   = false            {product}
 bool UseSerialGC                          = false            {product}
 bool UseSharedSpaces                      = true             {product}
 bool UseSignalChaining                    = true             {product}
 bool UseSpinning                          = false            {product}
 bool UseSplitVerifier                     = true             {product}
 bool UseStoreImmI16                       = true             {product}
 bool UseStringCache                       = false            {product}
 bool UseTLAB                              = true             {pd product}
 bool UseThreadPriorities                  = true             {pd product}
 bool UseTypeProfile                       = true             {product}
 bool UseUTCFileTimestamp                  = true             {product}
 bool UseUnalignedLoadStores               = false            {product}
 bool UseVMInterruptibleIO                 = true             {product}
 bool UseVectoredExceptions                = false            {pd product}
 bool UseXMMForArrayCopy                   = false            {product}
 bool UseXmmI2D                            = false            {product}
 bool UseXmmI2F                            = false            {product}
 bool UseXmmLoadAndClearUpper              = true             {product}
 bool UseXmmRegToRegMoveAll                = false            {product}
 bool VMThreadHintNoPreempt                = false            {product}
 intx VMThreadPriority                     = -1               {product}
 intx VMThreadStackSize                    = 0                {pd product}
 intx ValueMapInitialSize                  = 11               {C1 product}
 intx ValueMapMaxLoopSize                  = 8                {C1 product}
 bool VerifyMergedCPBytecodes              = true             {product}
 intx WorkAroundNPTLTimedWaitHang          = 1                {product}
uintx YoungGenerationSizeIncrement         = 20               {product}
uintx YoungGenerationSizeSupplement        = 80               {product}
uintx YoungGenerationSizeSupplementDecay   = 8                {product}
uintx YoungPLABSize                        = 4096             {product}
 bool ZeroTLAB                             = false            {product}
 intx hashCode                             = 0                {product}

 

系统为什么拆分?

系统做大了,并发量无法扛得住,如何做?

业务做复杂了,单个应用中不能个性化,如何做?

模块和逻辑对各类资源开销非常特殊,如何做?

。。。。。。

拆分、拆分、再拆分。

由 全世界用一个系统表达全世界所有的企业和公司的业务开始,注定系统做大后必然拆分的走向,也就是一个大力士无法完成成千上万群众所能做到的一件大事,高集 成度的硬件和软件解决方案,为传统企业提供较为完善的解决方案,并在这种程度上是可以节约成本,高端机和高端存储的解决方案,当达到一个成本的交叉点后,随着数据量以及并发量的不断上升,其解决方案的成本也会随之直线上涨。

 

如何拆分?拆分后有什么后果,是其中一个问题?

 

首先我们看看应用一般是如何拆分的:

1、应用一般的企业内部都是按照业务增长方式比较多,所以随着业务的增加,将系统进行拆分的是比较多的,也就是将一个较大的系统拆分为多个小的系统。

 

2、在一些企业中,不愿意将系统拆分为小系统(原因后面说明),而是将所有的内容部署在一起,依赖于集群分发到多个节点上去做负载均衡,这样来完成一种切割,前序两种也就是应用系统级别的纵横向切割。

 

3、 独立工具、模块、服务的独立化和集群化,基于SOA服务的企业级应用,很多模块经过抽象后,并非子系统,而是一个独立的服务系统,不参与业务,只参与一个技术级别的功能服务,如MQ、JMS、MemCached等,我们经常也管这一类叫做中间件,也就是平台没有提供自己来做或第三方提供的中间件(当然中间 件也包含应用服务器)。

 

4、数据库拆分,数据库拆分是也是因为压力上升,以及存储容量的需求,最终在成本上认为拆分是必然的走势;数据库拆分有多重规则存在。

 

5、由于上述各类拆分导致的运维的困难,在数以万计的计算机集群下,如何动态资源分配和拆分以及抛开分布式的内部细节来编程,如何自动化运维系统就是大型计算机集群下需要考虑的问题-云存储与云计算

 

我们拆分中面临哪些问题?(这些内容在后面的文章中说明,本文不再阐述)

1、负载均衡器的问题。

2、不同系统之间的通信问题。

3、数据写入和查找的问题。

4、跨数据库事务问题。

5、跨数据库序列问题。

6、不同应用的本地缓存问题。

7、系统之间的直接依赖和间接依赖问题。

8、独立模块面临的单点问题。

9、各类批量分组、切换、扩展的问题。

10、统一监控和恢复问题。

 

本 文我们暂时不讨论关于云存储方面的问题,先引入话题,不过每项技术的产生都是为了解决某些特定的问题而存在,所以云也并非万能的,后面的文章我们会介绍一 些基于纯Java开发的Hadoop相关架构和模块(如:MapReduce、HbaseHive、HDFS、Pig等子系统,说明当今海量信息的互联 网中大象的崛起)。

 

1、系统按照业务拆分

首先看下企业中拆分为小系统的过程中的过程和遇到的问题,在大多数企业中,选择高端企业的解决方案,因为一台两台小型机一般的企业都没有问题,除非是做的项目的确太小了,这类系统的访问量大概每天保持在几十万左右高的有一百多万的,不过为什么要拆分,一般有以下原因

   a.随着业务的发展,模块之间的耦合性越来越强

   b.开发人员越来越多,相互之间代码版本也难以管理

   c.系统启动加载PermGen的时间也会很长并且需要很大的PermGen,更加重要的原因是JVM在CMSGC出来之前管理大内存是有问题的

   d.尤其是发生Full GC时候在大内存的JVM上暂停时间是相当的长,长得让人无法接受.

   e.在单个机器上硬件厂商做得集成度越高,算法就越来越复杂,尤其是CPU的个数始终有限,这样就导致的是单位时间内处理的请求数也就受到限制,拆分水平扩展是非常容易的.

   f.一个大系统由多个开发商完成,多个开发商都有自己的主打产品,为自己节约成本,将各个产品以集成的方法完成一个大系统的业务过程。

  等等原因。

 

那 么系统拆分这样的系统拆分有什么技巧吗,可以说原因就算是技巧,也就是在什么时候再拆分,一般系统我们能不拆分就不拆分,因为拆分有有很多麻烦要去面对, 面临的第一个困难就是以前一个工程内部的系统,相互之间的调用就可以直接调用到,现在很麻烦,要两边来做接口,接口还得联调,联调是一件比较恶心的事情,尤其是两个厂商之间来联调。

 

所以拆分应当具有的最基本条件是高内聚、 低耦合的条件,也就是说,这个系统和外部系统的调用模块对于整个系统的模块来讲是比较少的,而不是大部分模块都是在和外部系统交互,除了专门用于处理系统交互的系统外,这样的拆分设计是肯定不合理的,因为通信的代价远远大于本地JVM的代价。

 

开 发人员越来越多,从最初的一个人,几个人,到几十人,几百人甚至上千人,在一个工程中来写代码是很恐怖的事情,谁改了没法查出来,无法定位,很乱,所以拆 分在一定程度上可以将版本控制的粒度细化一下,但是并不代表拆分后就没有版本问题;随着产品不断模块化和抽象化,在大多数的应用中,独立的子系统就会成为一个独立的行业产品,可以基于配置模式的适用于大部分的地区工厂或者企业的应用,也可以通过一个顶层版本分发出来的多个地区化个性化版本(可能有两层结 构);也就是在节约大部分共享劳动力的基础上如何做到个性化平台,这也是行业软件中非常盛行的,不过这样将绝大部分程序员控制在一个小匣子里面了,几乎没有发挥的空间。

 

上面也说了,系统可能由几十人、几百人甚至于上千人去 写,如果大家都写一个工程,代码量可想而知,系统初始化需要加载代码段到PermGen,这块空间将不可预知的大小发展,并且随着业务的复杂性,需要的引 入的第三方技术越来越多,第三发包的class同样会占用PermGen的空间,不用多说,这块空间的大小是不可预知的。

 

当 发生Full GC的时候,遍历整个内存,在没有CMS GC出来之前,或者现在G 1的出现,Full GC对于几十G上百G的大内存是一件非常痛苦的事情,延迟时间可以打到十几秒甚至于上百秒(这里在16个4 core的CPU使用了并行GC,时间是应用暂停时间),这是不可以接受的,虽然CMS GC已经可以在较短的暂停时间内回收掉大内存(只是暂停时间减少,但是回收时间可能会更加长),不过在它目前解决的主要问题是这个,同时由于大内存部署逻辑节点的个数减少,使得负载均衡器的负载目标成倍减少,这样可以让同样的负载均衡器支撑起更加庞大的后台访问集群;不过大部分早期的系统还没有看到CMS GC的诞生,更加没有想到G1会出现(其实早在N多年前,论文就出来了,只是一直没有实现而已),所以一直都还是在沿用比较老的拆分方法,不过拆分始终是 有它的好处的,不仅仅是因为GC的问题,在传统企业中一般的负载均衡器也足以支撑,不会面临更大的问题。

 

对 于集成度较高的,通过芯片等方式来完成高性能的服务方法,对于传统软件来讲是非常好的,因为通过硬件完成的,一般情况下比软件完成的速度要快(所谓通过硬 件完成除了通过集成电路增加各类特殊指令外,还有就是基于芯片或底层语言实现使之效能更高而且封装操作),不过遇到的问题就是随着集成度的高度集中,算法越来越复杂,导致了内部的诸多冲突,水平扩展性受到了严重的限制,所以几乎没有多少算法的拆分,是一个必然的发展趋势。

 

多 个开发商完成了一个自己的系统,开发商为了产品化系统,并且由于系统的复杂性,以及提升开发商在行业内部的积淀,所以就需要不断完善产品,不断版本化,以 及本地化的不断改善;这个目的是好的,不过一定要做好版本的事情,以及一个大型的行业软件的顶层架构以及继承关系,否则不如不做,部分软件厂商可能只考虑到前者,也就是产品化,不过代码顶层架构几乎没有,只有业务架构,产品化和本地化代码更加是随心所欲,软件五花八门,就像贴补丁一样,谁要做本地就加一个 else if,甚至于有直接对地区判定的硬代码,很无语的做法,我个人认为这样做不如直接拿一个模板来改出来一个系统,就不要做什么版本,因为这样的版本的代码是越来越烂,面对这种代码唯一的办法就是重构,如果不想重构就永远下去吧,不面临改变终究会被淘汰掉;这种情况也面临在系统底层版本升级上,包括JDK的升 级,如果只是考虑到成本和风险的话应该说真的永远都无法升级,没有做不到的升级,关键是否愿意去做,越晚去升级,所带来的成本代价是越高的,类似国际上有多少大型软件的底层版本也是在不断的升级中,而上层的代码由于繁杂而不断的重构,虽然说不一定要时时刻刻重构,这样程序员也会很累,并且也体现不出他们的 价值,因为成天就是改代码,但是该重构就应该要去重构。

负载均衡,首先负载均衡可以是硬件也可以是软件,硬件在一定程度上支撑不上去的时候就要考虑通过软件的负载均衡来解决了(硬件一般情况下比软件要快速,但是它本身设计的复杂性导致了在一定场景下不如软件的扩展性好),系统在拆分后不论是分布到各个机器上还是在一个机器上虚拟出来多个节点都是需要,将其负载均衡的,按照URL和端口+虚拟目录名称进行负载,负载均衡器需要 知道这些内容,也需要知道那个session访问了那台机器,中间负载均衡器会采用一些特殊的算法来解决一些问题,这里简单介绍到这里,在下一篇文章中会 介绍下负载均衡的大致原理和作用。

负载均衡器并不简单承担这个问题,在负载均衡器中一般还会有很多算法存在,如负载均衡器比较经典的算法就是一致性hash算法,或者轮训检测;而在有限的线程下,为了得到更大的连接访问,异步IO的访问策略应运而生,著名的Nginx到目前为止都是全世界大型互联网前端负载均衡的设计蓝图的标准,其QPS极限情况可以打到30000-50000左右,内部还存在各种模式来支持不同的情况(NAT、DR、RUN),当然还有很多类似的负载均衡设备(设计上有些差别)。

2、系统水平拆分:系统水平拆分即同一个子系统,或者整个系统部署在多个node上,这些node可以是多个主机或同一个主机上的多个软件节点;但是这些节点目前来讲即使应用拆分得再细,在分布式系统上的这种低端机器也不可能扛得住高并发的访问,一般这类低端服务器代码调解得较好等各种情况下,服务器的QPS一般都是保持在200以内的(这是以16个CPU来处理,一个请求在80ms内处理完成请求分派,业务处理和数据请求,反馈结果等过程已经是非常快速的了,很多时候一个SQL都会超过这个时间),当然单用几个字节去做压力测试,反馈几个字节,并且中间几乎没有IO方面的额请求(如数据库、文件、远程方法调用等),那么这个QPS你可能会上千,甚至于在好的机器上可以上万也有可能。

也就是系统真正运行的时候,前端的用的并发量都是有限的,而且很多时候代码不好的时候,一般应用的QPS还会更低;面对高并发,在这种情况下,我们唯一可以做的就是加机器,也就是水平扩展,它的分发也是依赖于负载均衡设备,加机器的过程就好比是工厂里面的请很多工人来做同一件事情一样,相对来讲第一种拆分就是请不同的人来做不同的事情,不要让一个人从头做到尾部,那样会搞得很累,而且对人的要求也很高。

这种拆分没有什么太高的要求,只要负载均衡设备可以支撑就可以,为了让负载均衡可以支撑更大的压力,那么就尽量让节点数量减少,那么就希望在同一台实体机器上尽量一个节点(通过对实体机器进行虚拟化可以在某些情况下节约成本,并将物理机本身的性能发挥到一个极限,并可以将一个比较好一点的机器分摊给多个访问量较低的系统,不过虚拟化本身也会产生很多开销,在这些方面需要综合权衡一下好坏),可惜目前来讲Oracle的Hotspot VM还不足以支撑大型的非常好的实时系统(我们很多时候不得不在同一个大内存机器上部署多个小的JVM节点),尤其面对几种场景显得力不从心:

1、大内存的管理(包括GC、内存分析、跟踪等都没有完善的体系和解决方案)。

2、做实时应用不适合,实时应用的延迟一般是毫秒级别(如2ms响应,最慢也不能有十多毫秒的响应,当然这种不包含IO操作,只是做一些内存处理和反馈,并且数据量不大),而java在正常情况下,如果一旦发生GC,即使并行GC,而且仅仅只针对Yong空间做GC,也需要一段延迟(在一个16CPU的机器上,配置了并行GC,发生YGC的时候(Yong的大小大概为330M左右),延迟大概为10ms-15ms左右,发生Full GC的时候(Heap大小为1.5G),延迟大概为30ms-40ms左右),如果是更大的内存,就更加蛮了,因为回收的时间很多时候取决于内存的大小,增加一倍的内存,并不代表回收时间只增加一倍,因为随着内存的增加,回收过程中产生的开销和冲突也变化,所以内存增加一倍,时间不一定只增加一倍,曾经在96G的JVM内存上,采用16CPU进行全局GC,大概需要3分多钟,也就是说这3分多钟外部是无法访问的,在实时应用面前这就是垃圾。

3、做缓存不适合,分代垃圾回收考虑的是绝大部分对象都应该死掉,而Old会采用全局GC,即使是CMS也会有各种问题;很多时候我们在缓存的时候,数据初始化就会装载进去,而很少甚至于不用去做GC,至少可以说99%的内存是不需要考虑GC的;而且做缓存的服务器内存都是大内存,也就是没有地方让自己来操控可存放不做GC的内容,但是程序员发现这部分内容占据了绝大部分内存而自己却无法控制它。

4、目前不支持半长命对象,也就是要么是长命鬼、要么是短命鬼,但是很多非常规应用中,有很多半长命对象,采用不同的算法,会提高更好的性能,如一些page cache数据,在内存中启用LRU策略,这些队列的数据,不能说他们的寿命很长,也不能说他们的寿命很短,但是LRU本身在使用的过程中,不想受到类似Yong和Old之间的这种晋升策略,因为放在Eden中觉得命太短,来回倒腾,有很多还是会到Old中(具体有多少进入old要看应用场景),进入old它又并不是什么太长命的东西,随时可能就挂掉了,真是无奈啊。

其实还有很多JVM不方便去做的服务器方面的特殊应用,不过随着JVM的发展已经比以前有了很大的飞跃,而且越来越多的硬件厂商和学术界的顶尖高手在为java的发展而努力,所以我很期待java能解决掉这些问题。

3、 独立工具、模块、服务的独立化和集群化

其实这种拆分和第一种拆分有相似之处,几乎可以算是一样的拆分模式,不过说到工具化、模块化、服务化、集群化,这种属于更为专业的拆分,第一种拆分的依据是系统各项压力上来,为考虑扩展性,而不得不拆分系统,而将很多高内聚、低耦合的系统拆分出来,也就是模块成为了子系统。

而这种拆分是一种技术独立性拆分,将很多较为复杂,不好解决的技术以及工具特征独立出来,虚拟化为一种服务模式,为外部提供服务,你可以将它理解为一个传统的子系统,不过它是属于很多系统里面都需要的一个公共子系统,而前者仅仅一般只为自己的兄弟模块提供相应的服务以及一些自己的对外用户服务;比如:将邮件系统独立、短信系统独立就是为很多应用服务,大家都可以使用,将通信技术独立、将分布式缓存独立、将配置管理独立、将版本管理独立就是属于技术上的独立进而逐步个性化成为一种服务。

第一种和这种拆分方法没有明显的区别,可以说这种拆分的思想是受第一种拆分的影响或者说基于它来做的,它的目的是以一个个体或者集群为外部提供一种公共服务;当一个企业或者一个大的互联网公司,将这些公共服务开放出来后,形成一种全局的数据、技术的服务平台。

4、数据库拆分

这个话题扯得有点大了,因为数据库拆分这个拆分方法的确太多,本文也不能完全说明数据库的拆分方法,只是概要性的提及一些内容。

首先,前端有压力,数据库自然也有,而且数据库压力肯定比前端压力会更多(当然前端可以采用很多缓存技术来环节数据库的压力),数据库的复杂性比前端要更多,因为它是数据的核心,需要对数据库的安全、一致性等做保障,在很多处理上它都是采用磁盘IO操作,而普通的sata盘是很烂的,sas盘可能会稍微好一些,这些盘上做几个KB的IOPS测试,一般只能达到180的IOPS就很不错了,当然根据磁盘本身的尺寸和转速会有所区别;在早期的技术上,我们大部分的都是采用高端存储,如:EMC、IBM这类公司就是专门做高端存储的,其IOPS可以达到万级别,其实其原理也是在普通存储级别上做了很多类似多存储控制器、镜像、cache等技术来解决很多问题,但是其价格非常昂贵,小型机+EMC的解决方案相信是诸多企业的绝佳解决方案,因为根本不用担心性能、稳定性和存储空间,但是在数据量达到非常大的时候,他们也会显得力不从心,此时在这种解决方案下也不得不去拆分,拆分过程中出现的问题就需要技术人员来解决,付出的成本将是指数级的上升,而不是平衡上升的;SSD的出现虽然颠覆了传统的磁盘存取效率(主要是随机存取效率,顺序读写优势并不大),不过目前还有很多问题存在,最近Intel也称其发生过丢失数据的问题,而且SSD目前的成本非常高,不过我们可以看到它的来临是传统磁盘开始被取代的标志。

好,OK,随着磁盘性能提高,但是容量还是和以前差不多,而且更加贵,所以就当前来讲我们绝大部分还是用传统磁盘来解决,在这种一块磁盘做一百多的IOPS的情况下(注意一个SQL并不代表只做一次IO,具体做多少次IO和实际的应用场景、实际的优化方案、以及SQL的写法所决定;而一个业务请求也一般会做多个SQL操作),我们如何提高数据库的性能呢?和上面一样,在很多时候我们先选择的是小机+高端存储的解决方案;但是随着复杂性的增加,成本开始补课预测,所以为了接触这种耦合性,我们需要一种高可扩展的分布式技术来解决,在多个分布式的机器上来解决这些问题。

首先,这种可以认为是一种分区技术在分布式上的一种实现,也就是将原有分区的技术应用在多台机器上,按照一种规则拆分到多台计算机上,如果跨机器查找也就是原来的跨分区查找,显然性能不如在单个机器上查找快速,所以如何设计分区成为一个性能关键,而不是仅仅为了拆分而拆分;另外拆分之前要有预算,计算所需要的TPS、QPS等负载情况,拆分到多少个机器上可以承受得起这样的访问量,一般最少需要预留一半的余量才可以预防突发性事件,如果需要未来几年都不受到拆分上的干扰,那么就可以预留更多;因为这种数据库拆分的代价是很高的。

在早期的数据库拆分中,有主备读写分离,ORACLE RAC多实例运算,不过面对越来越庞大的系统,他们都显得力不从心了,当然读写分离还是可以和现有的人工拆分所兼容,人工拆分主要是为了得到更好的水平扩展。

首先我们来看看传统应用中的range分区,如果用在分布式上,就是放在多个主机上的多个库上的多个表,这种用于自动增长列或时间上比较多,如刚开始可以将1-1亿的数据一般可以用多久,选择多少个机器来做,每台机器可以存放多少数据,而这种拆分Insert操作始终落在最后一台机器的最后一个表的最后一个block上,而且在刚开始使用的时候,后面所有的机器的所有的表都是空的,没有任何用处,显得非常的浪费,也就是没有数据的机器一直都是闲着的;这个问题比较好解决,你可以用一个无穷大来代表最后一台机器,当觉得应该加机器的时候,再将最后前一台机器的上限控制住,不过前一个问题是没办法搞定的,所以这种方法是用在insert压力并不是很大的情况,每秒要是有几千个insert这样做肯定是不行的,其余的update、delete等如果有最近访问热点,那么最后一台机器也必将成为热点访问区域,一般最近访问的都是热点,不过这种思路最容易让人接受,而且最容易做出来。

那么在大部分应用中我们为了考虑负载较为均衡,所以我们选择hash算法,但是绝对不是一致性hash算法,因为一致性hash在扩展时会导致数据不一致,一些数学模型可以解决,但是非常复杂而且也会存在数据版本的问题;hash算法最简单的就是求模,如将一个表拆分为100个表,那么按照绝大部分情况按照某个编号去求模得到的是0-99之间的数据,这一百个表编号为0-99,分别对应存储即可,无论是自动增长还是非自动增长也不太可能落在同一个表上面去;而对于某些热点用户,如果按照用户拆分,这些热点用户的访问就会被反复访问到同一个表,比如类似微博这种东西,也许某个热门人物的他的好友个数就会非常多,可能会导致某个表非常大,所以为了缓解这种问题,我们会再做二次hash;而对于一些非数字类的数据,我们一般会采取对其前几个字符的ascii或者hash值等取出来做操作,具体看实际情况而定;那么hash算法就是完美的吗?当然不是,它最痛苦的就是拆分,一旦拆分将会面临各种问题,应用要重启,配置要修改,数据要迁移;虽然用了一些手段来解决,但是这些手段一般都是需要提前预估出来的,比如hash算法一般都是2的次方拆分法则,因为数据库都会有备库,而且很多时候会有多个备库,所以如果做2倍数拆分的时候,可以直接将一个备份库上的数据拿上来用,如原来拆分规则为100,现在变成200,按照100求模=1的机器的主库数据和200求模等于1的都还在这个上面,只是有一部分和200求模会变成101(理论上可以认为是一半),备库也是这样,所以在乘以2以后这个备库变成主库后,数据是完全可以路由到的,剩余的工作就只需要将原有主库上和200求模等于101的数据删掉,以将这部分空间节约出来,而原有备库替换成的主库上和200求模等于1的数据删掉,删掉的这个过程是痛苦的,时间长,资源多,全是IO操作,而且还有各种锁,性能影响极大;试图想到我可以直接把他干掉,几乎不影响性能就除非需要删掉的数据是一个独立的逻辑单位,在同一个表上能想到的就是分区,也就是它如果是一个分区你就可以直接把他很快的drop掉或truncate掉,这种必须要有提前的预案才可以,否则这些都是空想;所以这种基于hash的拆分一般不要随便拆分,代价是很大的,因为这个上面的每个节点都需要做切割,甚至于只有一个备库的需要做迁移,要尽量少做,压力来了也得做,所以需要预估未来几年几乎不太可能做这样的事情,这个估算是根据业务的发展速度和趋势来决定的。

剩下是一种很常规但是很少用到的拆分,就是基于位图的拆分,也就是认为的讲某个字段(这个字段的值是可以被列举的),某些值放在某个放在某个表里面,也就是表的个数是被定义好的,拆分的个数收到值的个数的限制,除非和其他字段再进行二次组合;虽然它本身用途不多,但是如果以range或hash作为前提它也有可能是有用途的。

上面阐述了几种基本的拆分方法,都有各自有优缺点,为了更好的解决问题,我们考虑得失,会考虑使用他们进行组合,组合的方法根据实际情况而定,如我们在一些数字列上,既想考虑扩展性,又想考虑负载均衡,那么在可接收的条件下,那么我们将range-hash或hash-range,至于是那一种要看具体情况,我们简单说下range-hash,它在做range的时候,每个hash值就面对多个主机目标,在这部分主机目标内部做相应的hash负载均衡,如果出现热点,在这个range内部做二次拆分,其他的range是不需要的,如果负载较低,可以合并一些数据,range拆分的条件只是负责某个数据段的数据太多,较为均衡的分布数据,多个range如果以后不是怎么用了,可以将多个range的数据进行再次合并(这个代价相对较大,因为每个range下面的hash规则可以是不一样的,但是如果只要2的多少次方来完成这个动作,就不会出现太大的问题);而面对字符串的数据,或者不是自动增长类的数据,range没有办法,因为范围不可预知,虽然可以通过ascii来取,我们的范围也可以用正无穷和负无穷来代表,但是我们无法保证数据的均衡的,所以建议还是先做hash,而在拆分的过程中,为了使得应用不停需要设置一个版本号,也就是拆分过程中,的时间戳标记,所有在这个时间点以后的数据都在新的分布式规则中,老的数据读取的时候在老的规则中,然后可以迁移数据,但是迁移过程中性能是很低的,迁移完成后就将中间规则去掉就完成了整个的拆分过程,这个拆分过程就不局限于必须是2倍拆分了。

有关组合条件有很多,可以根据自己的应用场景去选取不同的组合方法,使得它的性能最佳,尽量少出现跨库跨表的操作,如果是按照非拆分字段进行查询,要么做二级拆分,要么就是做索引表,索引表也可以是拆分的表,也就是先查索引表然后再从索引表得到的主表的分表字段去找主表内容(但是由于索引表的结构完全又开发人员自己定义,所以索引表的维护完全是程序来控制,一致性需要开发人员来保证)。

如上,拆分解决了很多问题,也带来了很多新问题,如维护成本极度上升,需要大量外围软件来支持,否则发生任何问题将无从下手;其二,开发人员要编写很多的代码来处理路由规则信息和分布式的一致性数据的问题;切分和数据库切换过程中,一次要切换一大堆机器,应用重启时间很长;动态扩展要实现就需要非常复杂的代价。

为了解决第一个问题,公司需要较好的基层架构的人员,来编写很多外围的类似分布式一致性监控、问题跟踪处理等工具软件,并且这些软件要可持续的,否则经常换成本永远无法控制,只要基层做好了,以后这些成本就会越来越少了,或者这些成本在同等的业务水平下会越来越少。

为了解决第二个问题,让开发来编写路由等信息肯定是不合理的,一个是很多开发人员水平有限,数据是业务关键,路由更加保护这数据存储在哪里,所以要是代码写得不好就死定了;于是我们需要独立中间件,这个中间件可以只是保留在应用中的一个算法,也可以是一个独立的服务模式,服务模式为了保证其不是单点问题以及访问压力过大,需要优化的是提供服务应当是一个集群,而所有访问它的应用系统应当做一定算法的本地缓存来处理;这又回到我们上一章节说的应用系统的拆分了。

为了解决第三个问题,切换要让应用不知道,那么就要让应用感觉不到IP、PORT、库名称的变化,那么就必须将这些东西抽象出来,抽象为如上所示的独立服务模式或本地配置,当一个统一的配置被修改后,将会通知相关的应用系统进行修改,并一致性将多套机器全部一次性切换完成。

为了解决第四个问题,我们想了很多拆分的动态扩展性,但是算法十分复杂,就增加第二个中间件的复杂性,并且面临各种风险,所以传统RDBMS的拆分再次受到水平扩展的限制,人为介入太多,主要原因就是先独立做数据库,再做上层管理,是一个从下到上的过程,也就是有问题贴补丁的过程,而并非从一个站在高处把控一切的思想;于是为了解决更多的特殊的问题,如数据量超级大,而且增量也很多,读的访问非常多的情况,nosql这种低耦合的拆分技术出现了,也是云计算来临的基础(现在云计算这个词汇彻底被用乱了,尤其是中国,太过分了,这么好歌东西,在国内就被到处使用,用着当招牌,对此我表示无语),关于这部分不是本文的重点,最后一章节会简单提及一些bigtable的思路和原理,以及其开源版本的实现HBase的大概的架构模式。

Nosql技术概述:

谷歌是一家伟大的互联网公司,其引领着互联网时代的发展,bigtable的经典一直在世界各大互联网公司所效仿,后来还有多个升级版本,但是大家还是喜欢叫他bigtable;谷歌取名字很奇怪,就是直截了当,bigtable就是大表,什么样的表的是大表,每个几十亿、几百亿、几千亿什么的,不是大表,谷歌的架构可以承受万亿级别的数量,它的MapReduce也是一个非常简单的名词,也就是先做Map处理(也就是将需要分析的目标中将需要分析的有效数据提取出来并组织为K-V结构),而Reduce就负责将这些K-V数据进行处理;Apache也是一家伟大的公司,开源社区的大拿,在java界更加孕育了非常多的经典,它的Hadoop架构就是仿照谷歌的架构来完成的,这套架构完全是java编写的,这个公司也有自己的特点,就是很多名字都是动物的名字,hadoop号称就是大象的崛起,呵呵,这个hadoop架构里头有什么:pig、zookeeper就是什么猪、公园什么的意思,整个就是动物园,他们不需要多么玄的名字,就是纯属喜欢什么就用用什么,甚至于某些食物的名字或某个亲人的名字。

Hadoop虽然还不可以和谷歌的架构抗衡(基本差一个到两个数量级),但是对于绝大部分的应用是绝对没有问题,在分布式运算上,它的MapReduce架构可以支撑4000台(在雅虎)的机器同时进行分布式运算;对于线上应用就是其子模块的HBase架构,全世界最大的是960台(也是在雅虎)。

HBase算是nosql中的一种,每一种nosql都是为了解决某些特殊的问题而存在,因为前面看到分布式的拆分方法有很多种,nosql也不可能解决所有的问题,HBase总体来讲需要配合的子系统有多个Region Server、Zookeeper、Master、HDFS来配合,其中HDFS为存储引擎,所有和hadoop相关的内容不论是不是HBase都基本是存在这个上面的,少部分内容是存储在本地文件(如MapReduce中Map后的中间结果可能会用本地文件来存储,因为用完后中间数据就没有用了,没有必要放在HDFS上面);Zookeeper就是公园,管理这些动物,哪里出事或者门票都是它说了算,在这里如果谁宕机了(那个Region server宕机了),它要知道,然后告诉Master(管理者)去处理这些问题,Master宕机了就会启动备用的Master,客户端要请求首先就就是从Zookeeper请求,Zookeeper从Master哪里得到数据分布信息后,反馈给客户端,客户端会缓存主分布信息,然后开始访问,如果访问不到就再次请求Zookeeper,所以Zookeeper必须是多台机器才能保证稳定性。

也就是客户端最终访问的Region Server(其实这里可以看得出来它是基于范围,但是这个范围绝对不是基于某个自动增长列什么的,而是基于数据的字节码匹配,可以放中文、数字什么的都可以,但是放进去前都需要转换为二进制,所以转换的过程完全是业务层自己定义的),这个东西你就可以理解为一个JVM节点,数据cache在内存的是memstore,内部存储很多storefile,以hfile为单位,对文件进行读写;memstore一般都是64M两个之间来回写,一个写满就flush,另一个也在写,如果另一个写满了,这个flush还未完成,就要锁住两个部分。

Region Server负责和HDFS通信,其另一个需要做的就是HLog的编写,Hlog一般是数据实时写的,但是也可以不是实时写的;HBase数据的版本个数等方面都是可以设置的,并且可以保证单个操作的一致性;Master在初始化的时候,就会从HDFS上去获取字典Meta信息,所以这些内容都是存储在HDFS上的。

OK,这部分并不是本文的重点,本文重点在于拆分,这里携带阐述了下HBase,但是它也有很多问题,相信问题最多的就是他是用java做的,对于后端的实时应用一旦发生GC就有很多的问题,尤其是我们前面也简单说了下GC对于这种半长命的鬼东西是很有问题的;其次是虽然ZK可以发现宕机,但是时间很长,这个心跳时间设置太短可能会是一种假死,心跳时间太长就宕机很多也不会被人发现,总之还有很多问题,但是在JVM进步的同时,我们相信这些问题都可以得到解决

 

:以业务考虑数据库确实是,拆分的规则引擎本身就是以业务为立足点,业务上也会做相应的改造来完成这样的事情,在互联网公司通常是与业务配合做这个事情,使用最为频繁的是hash,而离散规则是考虑数据库本身的性能容量。在目前这些规则逐步开始自动化,对使用者逐步开始隔离,业务规则理解分布式规则,根据规则自己的业务特征来选择划分方式。

读大于写在大部分场景适用,我们有很多历史库,基本只有写,一年下来没几个读请求(用户只有翻历史账的时候会用下),这就是根据业务嘛,我拿评论这个案例出来说也是说业务背景。另外,读写比不大的情况下,还算不上读多写少,一般是超过好几倍以上才算的能算得上读多写少,例如大部分前端网站的读写比一般可以达到10~18倍。

评论这个案例的写大不大是看规模,还有类似聊天的一些信息,在阿里里面的这种规模还是很恐怖的,具体数字我倒不能说,只能说比较变态。我可没说啥场景,一直可是说的都是拆分的做法而已,而且是一种算法上的参考,我可没有下什么结论怎么做是正确的,业务应用本身拆分是另一个维度的事情,在前面也有提到过。换句话说,如果没遇到的一些并发场景,也不需要怎么拆分,除非是一些业务上解耦合,如果需要拆分肯定是遇到一些并发的问题,在这边有大概上万个应用的数据库应用拆分规则,业务特征各有奇葩之事,但总归是在这里面绕圈子,很多业界其它公司的拆分也差不多是拿这一套过去自己改的。传统行业去年提到过拆分,也想这么拆,只是很麻烦,为啥不想多说,因为我以前也干传统行业的,很清楚里头的规则。

你说自己不要一概而论,但确先下结论是说对数据库很小(峰值不峰值也得看应用本身是干啥的,有的应用从来就没闲过),这不是自相矛盾吗,对数据库小不小自然是根据场景来说,你自己首先下定论说下,怎么还说不要一概而论呢,我虽然嘴巴上没说不要一概而论,但话语之中一直就没有下过某个说法肯定要怎么做,往往你在说必须怎么做才是对的,怎么做才是错的,还有我可不止考虑自己的场景,我在公司某个关键时间专门给各个应用干这个事情,外部很多认识的朋友的场景和做法也大概清楚咋回事(当然不是说多权威,只是说不是瞎扯出来的东西而已),而这是从技术角度去分析一个场景,因为在互联网公司技术会作为产品最终自动化给人为来选择。对于读远远大于写,的看数据库抗不扛得住,扛不住除优化外还有其它的方案而定。

 

回复xieyuooo:我觉得还是得以业务为核心来考虑数据库,而不是只看数据库去考虑数据库。
写的需求永远小于读,这是毋庸置疑的。
评论什么的,只是写接近读,然后读有缓存,对数据库来说就变成写大于读了。但是这种情况下,写的IOPS也不会很高,只有特殊时候会产生很高的峰值,这个峰值可能会超出平时好几倍,甚至更多。
也就是说,从现实情况来看,折腾了那么多,只是在峰值的时候有效果。
而且,还有一点很重要。在峰值的时候,产生高IOPS的写,其实只会针对某些特定的数据,这些数据对于整个数据库来说,占比是很小的。完全可以用别的方法去应对峰值,而不是一概而论。
这只是一个思路,我也正在闲暇时间进行完善。

 

回复xiceblue:首先,拆分的方法与读写比确实有关系,读写比是另一个看待拆分的方式,很多读写比很高的,会把很多读剥离到数据库以外的组件去做,单纯数据库的拆分,读写分离的效果与数据库本身的性能有关系,这本身是从数据库管理者的角度在思考这个问题。
IO有读也有写,很多场景看来是读远远大于写,但并不代表所有的场景都是这样的,读的话缓存命中还不会产生过多IO,而写是最终肯定会产生的,至于比重依然会看场景,所以极端可能是你只考虑自己的场景,我并没有说必须要怎么做,虽然这个帖子是几年前写的,当时看法比较片面这个是真的。
不过说是不是这么玩的,我可以说的是,在目前国内的大型互联网公司里面的大部分业务在数据库这个位置基本都这么玩的,只是在其中会变通出很多细节出来,至少到目前位置基本都是。
这个拆分已经不是单纯的数据库级别,而是有中间件配合来完成。我提到的片面是指内容比较泛泛而谈,没有谈及其中的细节问题和详细场景。而Insert较多的业务确实是存在的,而且不少,例如评价、搜藏、日志等,在这些选择上,会考虑其它的存储,而并非RDBMS,提到Range情况,它的弱点就是最后一个库的最后一个位置,自然会去评估最后一个机器的负载情况,在HBase的时候,我们依然会考虑各种hash头部规则将数据离散到不同的服务器上去。

 

拆分不是这么玩的吧,太极端了。
数据库最大的压力是在读上,而不是写。因为写可以简单的拆分后解决,而造成的就是读的复杂。另外,对于写来说IOPS一般是不会很高的,主要是读的情况IOPS会很高。所以个人认为数据库拆分应该围绕读来思考。

 

回复zkq1989:其实拆分也不是随便拆的,我这篇文章写了有几年了,当时写得给人感觉有点通用化。

其实拆分一个要有:业务背景,一个要有未来预期。所谓业务背景就是你的场景为啥要拆分,这就牵涉到目的。

拆分的目无非2个:一个是为了系统的耦合小,可以做自己的事情可以自己优化,局部化,一个是为了让性能提升整体扩展性。
不过拆分也会带来很多问题,就是以前的代码调用,都需要通过网络来完成,需要考虑更多的技术细节。
其实你要说用什么技术实现,其实就是基本的网络交互,细化下去就是网络的交互方式,从使用上来讲,例如有Socket、HttpInvoker、RMI、EJB等,如果从技术上,通常说协议、同步异步、阻塞非阻塞、长连接短连接等。

拆分本身是很自由的,没有什么拆分是绝对合理和不合理的,有一些通用的拆分方案,也只是60%常见场景的抽象,最为关键的是利用拆分的思想,合理地去做出一些拆分。

啥为合理,就回到预期,你预期未来的系统是什么样子的,如果从拆分的角度来讲,就是服务化的板块足够强,自己内部的事情大部分可以内部解决,只有牵涉到其它的服务才会去调用其它的模块。例如你看到的图形中,产品、会员、订单、支付,如果在小规模使用的时候,做在一起也无所谓,只要将其模块化就好,大规模使用的时候,业务复杂性开始增强,业务上会相互依赖、发布相互依赖、宕机也相互依赖,各自对系统的压力评估不好做,拆分后业务相对单一。通过网络交互去完成,其实本身也有开销,只不过扩展性会更好。因为每个模块拆分为子系统后可以更好的评估自己的性能趋势(因为代码相对更加单一),可以更好的去做局部化优化,可以更好地去做本模块的业务扩展和服务优化,它可以一用一组集群去单独提供这样的服务,就像协作式分工一样的道理。
其实如果规模做得足够大,这里面也有可能还会继续拆分的,只是继续拆分可能又会把系统变得更加复杂,关键看是否有必要。总之拆分是自定义的,目的是清晰、业务扩展性、性能扩展,技术是网络,思想是高内聚、低耦合。

转载于:https://my.oschina.net/u/2822116/blog/889101

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值