本文牵扯的面积可能会比较泛,或者说比较大,在这个层面不少人也有本身的看法,因此我这也仅仅是抛砖引玉,结合前面讲述的一些基础技术,从思想中阐述更为深刻的架构思想基础,由于最好的架构思想是架构师结合实际状况思考出来最适合的架构,这里仅仅说明下一些经常使用的原理和思想,主要包含的内容有(内容很泛,因此都是简单阐述入门知识,具体后续深刻探讨):php
一、app切分集群组扩展html
二、app集群组负载均衡前端
三、Memcached原理java
四、db cache应用node
五、db存储类型以及存储cache说明mysql
六、存储条带思想linux
七、数据库集群git
八、数据库分布式存储程序员
九、数据库容灾备份以及监控web
十、nosql思想
十一、无锁分析
一、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,下一次用URL二、再下一次用URL3来给你服务器作负载均衡吧,客户只知道用一个URL访问你,并且那么多的IP在互联网上也会占用很是大的资源,因此不少时候,一个大网站后台可能数万的主机,前端暴露的IP可能只有几个,这个能够成为VIP,他们之间有一个绑定关系,由这个VIP来负责域名的帮顶,而VIP通常会绑定在一个负载均衡器上面,由负载均衡器根据实际请求内容负载到具体的主机上面去,下面第二章就是咱们要写的负载均衡基本原理。
二、app集群组负载均衡
所谓负载均衡就是负载均衡了,呵呵,也就是不让某太机器单独忙,也不让某台机器太闲,将请求进行分发,这就是负载均衡器设计的初衷了。
随着发展的变化,负载均衡器须要承担更大的做用
第一个须要作的就是请求解析,也就是不少不一样的应可能由一个负载均衡器来完成;
进一步,同一个应用发布的不一样的节点或者不一样的端口,负载均衡器能够识别出来并达到分发负载,将并发负载到不少不一样的节点上去运行;
再进一步,某个客户端请求第一次访问了某个节点后,当session未失效时,应当作到继续访问同一台主机,这样保证客户在屡次交互中session内容是一致的,至少不会致使从新登录等现象;
再进一步,在节点失败是,负载均衡器应当识别出来,并能够将访问切换到其余主机,在有必要的状况下须要作session复制。
负载均衡最基本的须要作到以上几点内容才算负载均衡。
负载均衡器通常须要的内容是全局的,可是它并不关注与细节,因此它主要作的事情是全局资源定位,监控,负载均衡,切换动做;通常会有一个单独的管理节点和单独的分发节点,可是每一门负载均衡的机制在设计层面都会有很大的区别,因此无需一律而论。
由于负载均衡器在全部应用的最前端,因此咱们很是关注于它的性能,有不少基于高级语言编写的负载均衡器,甚至于你能够直接经过你的JSP、ASP、PHP等等作一个简单的控制跳转上的负载均衡,可是他们的性能就很低了,扩展性受到明显的限制,Linux内核才是负载均衡器的王道,终极方案,要深刻研究和负载均衡的方案,请你们多多参详Linux内核。
目前市面上很是经常使用的负载均衡器是apache,它自己也能够做为WEB服务器来应用(它的一些模块就能够直接用于php),另外weblogic自带的proxy+domain+managed模式也是一种负载均衡方法,不过我作过几个版本效果不理想,主要缘由仍是主要是由于实现的基础是高级语言吧;而apache虽然性能不错,并且你们广受喜好的一种东西,不过随着互联网并发量的上升,apache在不少极为高并发的系统中仍然受到扩展性的限制,因而乎ngnix出现了,它是目前高并发网站应用中最普遍或者说在大网站中用得最多的负载均衡器,国内的大网站基本都有它的影子,它是俄罗斯一位工程师编写,并且是免费的,性能极高,占用资源极少,而且支持cache以及代理,不少客户端访问的机制均可以配置化,安装和使用都很是简单(要深刻研究就没那么简单),并且故障率很是低,为何那么好,由于它的基础就是unux内核,没有别的,固然写代码必定要写得很好才行;固然国内并不是没有这样的人才存在,并且要看公司是否给这类人才一个机会去完成这样一个东西,由于本身写的可能会更加适合于本身,其实国内也有不少对Unix内核研究很深刻的顶尖高手。
三、Memcached原理
这一章自己就想在数据库后面说明的,不过因为只是简单介绍,并且后面应该几乎都是技术,因此就这这里说明了。
通常应用程序除了处理业务逻辑和必定的计算后,就是访问数据库,作数据库的存、取、事务操做,在OLAP会有更多的是在数据库端的计算,OLAP不是本文的重点,由于OLAP不会涉及并发量的问题,因此更多偏重于OLTP,并且是极高并发的系统。
当前端app并发达到必定程度,即将考虑的问题就是数据库的压力,数据库面对的更多的数据,虽然它在各方面作了很是大的优化,不过它毕竟是存大量锁机制和磁盘读写操做,来保证数据一致性和安全性,而这二者每每是影响性能的关键指标,可是咱们不少时候又不得不用数据库,由于他能够提供给咱们的东西实在是太多了。
在互联网应用中有个几乎全部网站都会拥有的一个共同特征那就是读取次数很是多,而写的次数相对比例较少(并不表明没有写操做),此时人们在设计上第一个想法是让数据库来完成主备或者镜像方式上的读写分离,不过始终会与数据库交互,并且扩展上会受到很是大的限制,在极高并发下,人们又对应用作了对页面输出html的方式,可是最终发如今实施过程当中会受到不少限制(尤为是ajax交互),虽然有不少软件能够支持,此时不少人想到将数据载入到内存中,按照某种方式刷新内存便可,不过咱们上面已经讨论,在集群下它很难作到每一个被切割开的节点他们之间的静态内存是一致的,因此Memcached出现了。
Memcached我看网上写它是分布式的,这个你们最好不要乱理解,由于从基本的设计上讲,它只是将app和静态内存分开了,而并不是真正意义上作到分布式(真正意义上的分布式应当自动将多个Memcached节点的访问如同访问一个节点同样简单),而通常Memcached的访问方式仍是经过程序去控制的,而多个不一样节点划分,也是经过人为的完成的,你能够认为你访问的Memcached是数据库同样的东西,由于它的访问方式相似于数据库,可是它若是命中确定比访问数据库要快不少,并且能够大量减小读的压力,由于一个大网站百分之八九十以上的压力来源于读;一个好的Memcached设计会使得读命中率达到95%以上,而其成本只须要大内存,并具备极大的扩展性;根据实际系统的场景讲Memcached划分数据的方法指定,当命中是获取,当修改时先修改数据库,而后让对应的cached失效便可;主意解决若是它挂掉会产生什么问题,它的基础原理是一种Key-Value方式,可是通用的东西每每不是性能最佳的东西,因此你在有必要的状况下能够适当作下修改,淘宝网的tair开源技术就是一套本身完成的分布式缓存技术,也是很不错的选择。
四、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后,数据库端的内存能够保留,也能够节约一些出来也能够。
五、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以上
六、存储条带思想
你们不要被这个词汇所吓到,它就是RAID0的另外一种说法,其实RAID有不少种,从RAID0~RAID7每一种都有本身的特征所在,并且还有组合的,企业经常使用的有:RAID 十、RAID五、RAID3这几种,本文不对磁盘阵列作详细阐述,而只是经过条带给带出来一些思想。
RAID0,也就是条带,它的思想源于负载均衡,和散列存储,最终在磁盘上的统一实现,并将其做为磁盘组为中心,给外部调用,而无需关心磁盘的内部细节。
它按照必定的数据顺序,将数据分布逐个分布在多个磁盘上,因此看起来就像“条带”同样,同时不论在读仍是写的过程当中,它都将IO负载到了不一样的磁盘上,使得IO的整体性能几乎能够与磁盘数成正比,极大提升IO性能。
可是RAID0自己没有保护,也就是当磁盘坏掉,数据就丢了,找不回来,因此后来出现各类各样的RAID,根据不一样的状况每一种RAID都会有本身的方式来处理,实现补充程度的冗余,仍是那句话,发展到必定的冗余度将会致使成本直线上升,可是并不必定会带来收益的直线上升;RAID10就是经过50%冗余完成,也就是一对一冗余完成,同一个整列下全部的数据坏掉也能够找回来,除非两块磁盘是相互冗余的磁盘同时坏掉;而RAID5属于从RAID三、RAID4作一些算法改进和性能提高上来的,其原理都是奇、偶校验码原则,数据分布式按照条带思想,冗余N+1块磁盘,数据随机存放在N块磁盘上,剩余一块作校验位,相对减小磁头同步粒度,其中任意一块磁盘坏掉,都可恢复,但同一个RAID5阵列同时坏掉2块不行。
顺便说起下,ORACLE个只疯狗什么东西都想独霸,他的ASM就是拿出来和RAID竞争的,它的管理能够基于裸机,更加优于基于操做系统层的调用,而在裸设备的管理上又会有不少新的讲究。
七、数据库集群
数据库集群上,最初是经过一种操做系统机制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节点,而随着节点数量的增长,成本会直线上升,至于性能是否能直线上升呢,你应该能够考虑下当前的各类瓶颈在哪里,也须要和实际状况结合才好说。
八、数据库容灾备份以及监控
接下来一个系统设计应该如何?须要作的就是容灾以及监控运行情况是否良好,对于app端通常不须要容灾,只须要监控,而其通常是经过监控内存、CPU、磁盘使用量(主要是日志和本地缓存文件);若是监控系统作得很差,那么我想不少DBA晚上睡不着(至于夜间作生产变动这类能够经过其余的自动化程序完成),系统的发展也会受到限制,咱们须要一个伸缩性很强的系统就必然会走这一步。
而数据库容灾如今又不少方案,上面已经说了,如今比较多的就是使用dataguard备份到一个或多个备份机器上,dataguard上有多种配置机制,来实现各类经常使用的要求,关于磁盘管理能够使用ASM来管理,数据库也能够负责制过去,也能够异步经过程序度过去,也能够经过触发器+dblink过去等等均可以实现。关键看实际需求。
数据库的监控,这个oracle也提供了系列的监控软件(Statspace、AWR、logmgr等等系列),不过不少时候咱们须要更加精确的参数须要本身去编码,不然就仍是须要本身去查询不少本身作报表什么的,并且很不直观;长期须要监控的除了经常使用的IOPS、TPS、QPS之外,还须要关心不少如latch征用、sql parser(硬解析和软解析的各方面指标)、cache命中率、锁等待、内存指标变化、CPU指标变化、索引、磁盘碎片等等都须要获得全方位的监控
数据库的管理应当自动化,首先从监控下手,彻底自动化管理和资源调配方面是一个理想,不过半自动化也是很容易的,就是在有问题或者在必定状况下有某种方式的通知,如短信息吧。这样DBA就不用整天盯着监控或者后台的某个字典表一直看了。
九、CDN思想基础
后面几个章节不是本文重点,简单阐述下便可,在高可用性网站设计中,即便前端应用增长了Memcached这类东西,不过始终不能很好的效果,要达到极佳的效果,由于不少时候跨网段的开销是很是大的,通过的路由器越多开销越大;其次不少时候,不肯意由于大文件输出(如视频下载)致使应用服务器宕机的事情,这是没有必要的,由于应用服务器更多关心的应该是业务处理。
CDN的出现就是为了解决这个问题,也就是网站加速器,他须要运营商的配合(具体细节请本身查阅资料),在不少地方创建站点,它须要作的事情就是托管DNS,一般DNS是解析域名成IP并访问对应IP内容,而CDN作了一层重写,就是经过域名解析获得的是一个CNAME,它按照提供CNAME会按照最短路径找到对应的CDN网点,接受数据,客户端的数据接受更加快速,而且能够实现冗余保护,另外它只是缓存在这里,能够认为是本地的一个私服,也就是须要跨网段的流量都切换到本地了,这里作一个极端的假设,就是跨网段的开销是2,本网段拖数据是1,有100个请求时,跨网段须要200的开销,而本地网段就只须要101个开销。
大文件下载,是经过缓存到本地的私服上,如视频下载就不少时候这段时间你们看的都是热播电影,就能够经过CDN来进行网站加速。
十、nosql思想
根据上面的描述,咱们不少时候就不想作到百分百的数据安全,或者一致性吧,好比作一个网站的留言板,数据有一点误差也无所谓,并且数据库的sql parser通常是很慢的,很容易达到极限,因此nosql的诞生就出现了,如今不少开源的nosql平台,它也是现有云存储的基础,apache的Hadoop以及谷歌的mapreduce后来作了一个Percolator,还有Redis、MongoDB等等,其实所谓nosql基础的原理就是没有sql,就想刚才说的Memcache同样,只是它有存储以及根据设计不一样,会有一些会存在一些锁机制,而且只是面向对象;有基于行存储的、有基于列存储的他们是根据实际应用场景设计的一种相似于数据库的东西,它具备极高的扩展性和伸缩性,由于控制彻底在于你自己的架构和设计,也是咱们一直所崇尚的:最好的东西确定是最优秀的人根据实际的场景所架构出来的。
不管是哪一门,nosql它首先抛开的是sql parser的一种,可是它没有了SQL的支持,在一些复杂操做上显得比较困难(这些就要看具体场景和nosql的设计了);咱们在结合上述几种技术的基础上如何不将Cached、nosql、RDBMS、app几个结合起来,向后端移动,实现app调用彻底无需关心不少调用的细节,那么这就是真正的云存储了,由于是在分布式存储基础上以及cache管理的基础上实现了对应用的透明调用。
如何设计待之后专门有文章来阐述,今天只是一个开头而已。
十一、无锁分析
经过上面的文章内容,咱们在不少时候不少没必要要的信息没有必要使用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.首先说下线程数量,线程数量不少人认为在配置服务器的线程数量时认为越多越好,各大网站上不少人也给出了本身的测试数据,也有人说了每一个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去作动做,并且压缩的这个过程是须要时间的,这种只会下降速度,而绝对不会提升;那么大文件的方法就是一种是将大文件提早压缩好存放,若是实在太大,须要考虑采用断点传送,并将文件分解。
对大数据来说,和文件相似,不过数据可能对咱们要好处理一点,须要控制访问频率甚至于直接在超过访问频率下拒绝访问请求,每次请求的量也须要控制,若是对特殊大的数据量,建议采用异步方式输出到文件并压缩后,再由客户端下载,这样不管是客户端仍是服务器端都是有好处的。
三、线程上下文切换,对于线程的上下文切换,在通常的系统中基本遇不到,不过一些特殊应用会遇到,好比刚才的异步导出的功能,请求的线程只是将事情提交上去,可是不是由它去下载,而是由其余线程再去处理这个问题,处理完成后再回写某个状态便可;在javaNIO中是很是的多,NIO是一种高性能服务器的解决方案,在有限的线程资源状况下,对极高并发的小请求,并存在不少推拉数据的状况下是颇有效的,最大的要求就是服务器要有较好的链接支撑能力,NIO细节不用多说,理解上就是异步IO,把事情交给异步的一个线程去作,可是它也未必立刻作,它作完再反馈,这段时间交给你的这个线程不是等待而是去作其余的事情,充分利用线程的资源,处理完反馈结果的线程也未必是开始请求的线程,几个来来回回是有不少的开销的,整体其实效率上未必有单个请求好,可是对服务器的性能发挥是很是有效的。
线程之间的开销大小也要看具体应用状况以及配置状况决定,此时将任务和线程没有作一个一对一的绑定,而是放一堆事情在队列中,处理线程也有不少,谁有时间处理谁就处理它,每一个线程都作本身这一类的事情,甚至于将一些内容交给远程去作,交互后就无论了,结果反馈的时候,这边再由一个线程去处理结果请求便可。
在整个过程当中会涉及到一次或屡次的线程切换,这个过程当中的开销在某些时候也是不小的,关键仍是要看应用场景,不能一律而论。
四、内存,最后仍是内存,其实这里我就不想多说了,由于前面几篇文章说得太多了,不管是理论上仍是实现上,以及经验上都说了很是多,不过能够说明的一点就是内存的问题绝大部分来源于代码,而代码有很大一部分可能性来源于工程的程序员编写或者框架,第三方包的内存问题相对较少,通常被开源出来的包内存溢出的可能性不大,可是不排除有写得比较烂的代码;二方包呢,通常指代公司内部人员封装的包,若是在通过不少项目的验证能够比较放心使用,要绝对放心的话仍是须要看看源码才行,至于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有两个隐藏的隐患:
一、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%左右吧,不过具体时间仍是本身决定。
二、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}
系统为何拆分?
系统作大了,并发量没法扛得住,如何作?
业务作复杂了,单个应用中不能个性化,如何作?
模块和逻辑对各种资源开销很是特殊,如何作?
。。。。。。
拆分、拆分、再拆分。
由 全世界用一个系统表达全世界全部的企业和公司的业务开始,注定系统作大后必然拆分的走向,也就是一个大力士没法完成成千上万群众所能作到的一件大事,高集 成度的硬件和软件解决方案,为传统企业提供较为完善的解决方案,并在这种程度上是能够节约成本,高端机和高端存储的解决方案,当达到一个成本的交叉点后,随着数据量以及并发量的不断上升,其解决方案的成本也会随之直线上涨。
如何拆分?拆分后有什么后果,是其中一个问题?
首先咱们看看应用通常是如何拆分的:
一、应用通常的企业内部都是按照业务增加方式比较多,因此随着业务的增长,将系统进行拆分的是比较多的,也就是将一个较大的系统拆分为多个小的系统。
二、在一些企业中,不肯意将系统拆分为小系统(缘由后面说明),而是将全部的内容部署在一块儿,依赖于集群分发到多个节点上去作负载均衡,这样来完成一种切割,前序两种也就是应用系统级别的纵横向切割。
三、 独立工具、模块、服务的独立化和集群化,基于SOA服务的企业级应用,不少模块通过抽象后,并不是子系统,而是一个独立的服务系统,不参与业务,只参与一个技术级别的功能服务,如MQ、JMS、MemCached等,咱们常常也管这一类叫作中间件,也就是平台没有提供本身来作或第三方提供的中间件(固然中间 件也包含应用服务器)。
四、数据库拆分,数据库拆分是也是由于压力上升,以及存储容量的需求,最终在成本上认为拆分是必然的走势;数据库拆分有多重规则存在。
五、因为上述各种拆分致使的运维的困难,在数以万计的计算机集群下,如何动态资源分配和拆分以及抛开分布式的内部细节来编程,如何自动化运维系统就是大型计算机集群下须要考虑的问题-云存储与云计算。
咱们拆分中面临哪些问题?(这些内容在后面的文章中说明,本文再也不阐述)
一、负载均衡器的问题。
二、不一样系统之间的通讯问题。
三、数据写入和查找的问题。
四、跨数据库事务问题。
五、跨数据库序列问题。
六、不一样应用的本地缓存问题。
七、系统之间的直接依赖和间接依赖问题。
八、独立模块面临的单点问题。
九、各种批量分组、切换、扩展的问题。
十、统一监控和恢复问题。
本 文咱们暂时不讨论关于云存储方面的问题,先引入话题,不过每项技术的产生都是为了解决某些特定的问题而存在,因此云也并不是万能的,后面的文章咱们会介绍一 些基于纯Java开发的Hadoop相关架构和模块(如:MapReduce、Hbase、Hive、HDFS、Pig等子系统,说明当今海量信息的互联 网中大象的崛起)。
一、系统按照业务拆分:
首先看下企业中拆分为小系统的过程当中的过程和遇到的问题,在大多数企业中,选择高端企业的解决方案,由于一台两台小型机通常的企业都没有问题,除非是作的项目的确过小了,这类系统的访问量大概天天保持在几十万左右高的有一百多万的,不过为何要拆分,通常有如下缘由
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),固然还有不少相似的负载均衡设备(设计上有些差异)。
二、系统水平拆分:系统水平拆分即同一个子系统,或者整个系统部署在多个node上,这些node能够是多个主机或同一个主机上的多个软件节点;可是这些节点目前来说即便应用拆分得再细,在分布式系统上的这种低端机器也不可能扛得住高并发的访问,通常这类低端服务器代码调解得较好等各类状况下,服务器的QPS通常都是保持在200之内的(这是以16个CPU来处理,一个请求在80ms内处理完成请求分派,业务处理和数据请求,反馈结果等过程已是很是快速的了,不少时候一个SQL都会超过这个时间),固然单用几个字节去作压力测试,反馈几个字节,而且中间几乎没有IO方面的额请求(如数据库、文件、远程方法调用等),那么这个QPS你可能会上千,甚至于在好的机器上能够上万也有可能。
也就是系统真正运行的时候,前端的用的并发量都是有限的,并且不少时候代码很差的时候,通常应用的QPS还会更低;面对高并发,在这种状况下,咱们惟一能够作的就是加机器,也就是水平扩展,它的分发也是依赖于负载均衡设备,加机器的过程就比如是工厂里面的请不少工人来作同一件事情同样,相对来说第一种拆分就是请不一样的人来作不一样的事情,不要让一我的从头作到尾部,那样会搞得很累,并且对人的要求也很高。
这种拆分没有什么过高的要求,只要负载均衡设备能够支撑就能够,为了让负载均衡能够支撑更大的压力,那么就尽可能让节点数量减小,那么就但愿在同一台实体机器上尽可能一个节点(经过对实体机器进行虚拟化能够在某些状况下节约成本,并将物理机自己的性能发挥到一个极限,并能够将一个比较好一点的机器分摊给多个访问量较低的系统,不过虚拟化自己也会产生不少开销,在这些方面须要综合权衡一下好坏),惋惜目前来说Oracle的Hotspot VM还不足以支撑大型的很是好的实时系统(咱们不少时候不得不在同一个大内存机器上部署多个小的JVM节点),尤为面对几种场景显得力不从心:
一、大内存的管理(包括GC、内存分析、跟踪等都没有完善的体系和解决方案)。
二、作实时应用不适合,实时应用的延迟通常是毫秒级别(如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分多钟外部是没法访问的,在实时应用面前这就是垃圾。
三、作缓存不适合,分代垃圾回收考虑的是绝大部分对象都应该死掉,而Old会采用全局GC,即便是CMS也会有各类问题;不少时候咱们在缓存的时候,数据初始化就会装载进去,而不多甚至于不用去作GC,至少能够说99%的内存是不须要考虑GC的;并且作缓存的服务器内存都是大内存,也就是没有地方让本身来操控可存放不作GC的内容,可是程序员发现这部份内容占据了绝大部份内存而本身却没法控制它。
四、目前不支持半长命对象,也就是要么是长命鬼、要么是短命鬼,可是不少很是规应用中,有不少半长命对象,采用不一样的算法,会提升更好的性能,如一些page cache数据,在内存中启用LRU策略,这些队列的数据,不能说他们的寿命很长,也不能说他们的寿命很短,可是LRU自己在使用的过程当中,不想受到相似Yong和Old之间的这种晋升策略,由于放在Eden中以为命过短,来回倒腾,有不少仍是会到Old中(具体有多少进入old要看应用场景),进入old它又并非什么太长命的东西,随时可能就挂掉了,真是无奈啊。
其实还有不少JVM不方便去作的服务器方面的特殊应用,不过随着JVM的发展已经比之前有了很大的飞跃,并且愈来愈多的硬件厂商和学术界的顶尖高手在为java的发展而努力,因此我很期待java能解决掉这些问题。
三、 独立工具、模块、服务的独立化和集群化
其实这种拆分和第一种拆分有类似之处,几乎能够算是同样的拆分模式,不过说到工具化、模块化、服务化、集群化,这种属于更为专业的拆分,第一种拆分的依据是系统各项压力上来,为考虑扩展性,而不得不拆分系统,而将不少高内聚、低耦合的系统拆分出来,也就是模块成为了子系统。
而这种拆分是一种技术独立性拆分,将不少较为复杂,很差解决的技术以及工具特征独立出来,虚拟化为一种服务模式,为外部提供服务,你能够将它理解为一个传统的子系统,不过它是属于不少系统里面都须要的一个公共子系统,而前者仅仅通常只为本身的兄弟模块提供相应的服务以及一些本身的对外用户服务;好比:将邮件系统独立、短信系统独立就是为不少应用服务,你们均可以使用,将通讯技术独立、将分布式缓存独立、将配置管理独立、将版本管理独立就是属于技术上的独立进而逐步个性化成为一种服务。
第一种和这种拆分方法没有明显的区别,能够说这种拆分的思想是受第一种拆分的影响或者说基于它来作的,它的目的是以一个个体或者集群为外部提供一种公共服务;当一个企业或者一个大的互联网公司,将这些公共服务开放出来后,造成一种全局的数据、技术的服务平台。
四、数据库拆分
这个话题扯得有点大了,由于数据库拆分这个拆分方法的确太多,本文也不能彻底说明数据库的拆分方法,只是概要性的说起一些内容。
首先,前端有压力,数据库天然也有,并且数据库压力确定比前端压力会更多(固然前端能够采用不少缓存技术来环节数据库的压力),数据库的复杂性比前端要更多,由于它是数据的核心,须要对数据库的安全、一致性等作保障,在不少处理上它都是采用磁盘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%常见场景的抽象,最为关键的是利用拆分的思想,合理地去作出一些拆分。 啥为合理,就回到预期,你预期将来的系统是什么样子的,若是从拆分的角度来说,就是服务化的板块足够强,本身内部的事情大部分能够内部解决,只有牵涉到其它的服务才会去调用其它的模块。例如你看到的图形中,产品、会员、订单、支付,若是在小规模使用的时候,作在一块儿也无所谓,只要将其模块化就好,大规模使用的时候,业务复杂性开始加强,业务上会相互依赖、发布相互依赖、宕机也相互依赖,各自对系统的压力评估很差作,拆分后业务相对单一。经过网络交互去完成,其实自己也有开销,只不过扩展性会更好。由于每一个模块拆分为子系统后能够更好的评估本身的性能趋势(由于代码相对更加单一),能够更好的去作局部化优化,能够更好地去作本模块的业务扩展和服务优化,它能够一用一组集群去单独提供这样的服务,就像协做式分工同样的道理。 其实若是规模作得足够大,这里面也有可能还会继续拆分的,只是继续拆分可能又会把系统变得更加复杂,关键看是否有必要。总之拆分是自定义的,目的是清晰、业务扩展性、性能扩展,技术是网络,思想是高内聚、低耦合。