如何解决系统性能瓶颈?梳理系统的性能瓶颈应该不是一件简单的事情,需要针对不同设计的系统分别分析。首先,一个完整可用的系统应该有ui界面(这里强调的是一个完整可用的系统,而不是单一的中台系统),系统分为前端模块和后端模块。在这里,由于我个人擅长的领域更多的是后端模块,所以我就从后端来分析系统的瓶颈点。这里我结合常用的nginx+tomcat+redis+mysql等常用架构进行分析:所有请求入口的请求都进入后台服务,首先要考虑的一点是:带宽因素:假设有200m流量请求同时进入服务器,但是带宽只有1m,所以接收这批数据信息大概需要200s。带宽可以理解为在指定时间内从一端请求到另一端的流量总量。而且LAN和WAN的带宽计算其实是不一样的。服务器的ulimit一般是我们用的在线服务器是centos系列的。这里我列出centos7相关的系统配置:ulimit配置查看服务器允许打开的最大文件数(linux系统的设计理念是一切都是文件)通常,如果我们的java程序需要增加socket连接数,可以通过调整ulimit中的open参数来配置。12[root@izwz9ic9ggky8kub9x1ptuz~]`#ulimit-a|grepopen`打开文件(-n)1000查看最大用户进程数12[root@izwz9ic9ggky8kub9x1ptuz~]`#ulimit-a|grepusers`maxuserprocess(u)7284相关的配置存放在/etc/security/limits.conf文件中。如果系统的某些内核参数配置在某些压力测试场景下,我们通常会想到这样的错误:apr_socket_recv:Connectionresetbypeer(54)通常这是系统内部一些预防性参数设置导致的。调整/etc/sysctl.conf文件中的相关参数:1234567891011121314net.ipv4.tcp_syncookies=0#当并发请求数超过1000时,服务器本身可能认为受到了synflood攻击,但是对于高并发系统,必须Disablethissettingnet.ipv4.tcp_max_syn_backlog#参数决定了SYN_RECV状态队列的数量,一般默认值为512或1024,即超过这个数量系统将不再接受新的TCP连接请求,这可以在一定程度上防止系统资源消耗。这个值可以适当增加以接受更多的连接请求。net.ipv4.tcp_tw_recycle#参数决定是否加快TIME_WAITsockets的回收,默认0.net.ipv4.tcp_max_tw_buckets#该参数决定了处于TIME_WAIT状态的套接字总数,可以根据连接数和系统资源需求来设置。预防参数也可以修改查看如下:12cd/proc/sys/net/ipv4echo"0">tcp_syncookies一般企业使用nginx接收请求,然后进行负载均衡转发。nginx层会有几个核心参数配置:最大连接数,最大并发访问数123#指定同一个ip的请求数限制为10倍limit_conn_zone$binary_remote_addrzone=perip:10m;limit_connperip10;Tomcat部分分析Tomcat支持三种接收请求的处理方式:BIO、NIO、APR。1.生物模式。阻塞I/O操作使用传统的JavaI/O操作。Tomcat7以下版本默认以bio模式运行。由于每个请求都需要创建一个线程来处理,线程开销比较大。大,无法处理高并发场景,三种模式中性能最低2.Nio模式是JavaSE1.4及后续版本(即java.io)提供的一种新的I/O操作模式,是一种基于缓冲区的JavaAPI提供非阻塞I/O操作,并发性能优于传统I/O操作(bio)。Tomcat8及以上版本默认nio模式3,apr模式,简单理解,就是从操作系统层面解决异步IO问题,大大提高服务器的处理和响应性能,也是Tomcat首选的模式运行高并发应用程序。启用这种模式有点麻烦,需要安装一些依赖库,而apr的本质是利用jni技术调用操作系统底层IO接口,所以需要提前安装好需要的依赖。首先需要安装openssl和aprtomcat来调整连接参数。tomcat中有这么一个经典的配置参数:1234<`Connectorport="80"maxHttpHeaderSize="8192"`maxThreads="4000"minSpareThreads="1000"maxSpareThreads="2000"enableLookups="false"redirectPort="8443"acceptCount="2000"connectionTimeout="20000"disableUploadTimeout="true"/>maxThreads表示tomcat最多可以创建多少个线程来处理请求。minSpareThread表示tomcat启动时会创建多少个线程,即使它是空闲的。maxSpareThread代表tomcat创建的最大空闲线程数。一旦tomcat创建的线程数达到这个瓶颈,就需要进行线程回收。connectionTimeout表示连接的超时时间。假设我们同时有1000个并发访问请求,但是一个tomcat的maxThreads只设置为500,那么这个时候就会出现请求拥塞,这是瓶颈点之一。Redis的部分性能瓶颈分析了一些大的key查询,导致网络拥塞网络拥塞。因为redis被设计成单线程处理请求,所以当其他命令发送到redis服务器时,需要等待redis处理完之前的任务,才能继续执行。线上环境有一些“违规”,是比较常见的非法操作:redis在批量执行keys命令时处于高qps状态时,任意一个keys命令都可能致命。keys指令的时间复杂度为O(n)级别,很可能导致系统卡顿一段时间。内存空间不足当redis内存空间不足时,整个系统基本瘫痪。因此,我们需要为存储在redis中的每个值设置一个合理的过期时间,同时需要考虑存储数据的大小。MySQL部分性能瓶颈分析通常,我们在SQL查询的分析中容易出现一个误区,就是直接进行explian分析,而忽略了系统的运行上下文。假设有一张t_user表,存了千万条数据,也索引了用户ID,但是SQL执行速度还是1s多。这时候就需要换一种方式来分析了。比如从分表的角度,是否将表水平拆分为t_user_01,t_user_02...下面是我针对数据库层面可能存在的性能瓶颈总结的一些总结点:1.Lock检查是否有锁导致数据库响应缓慢的表情况。2、SQL查询还有优化空间,需要改进。通常,我们使用explain命令来查看SQL的执行分析:这里贴出explain的常用参数及含义表,供大家参考:idSELECT标识符。这是SELECT的查询序列号select_typeSIMPLE:简单SELECT(不使用UNION或子查询)PRIMARY:最外层的SELECTUNION:UNION中的第二个或后续SELECT语句DEPENDENTUNION:UNION语句中的第二个或后续SELECT,取决于外部查询UNIONRESULT:UNIONSUBQUERY的结果:子查询中的第一个SELECTDEPENDENTSUBQUERY:子查询中的第一个SELECT,依赖于外层查询DERIVED:导出表的SELECT(FROM子句子查询)表查询sql关联表名类型连接类型。下面给出了各种连接类型,从最好到最差排序:系统:表只有一行(=系统表)。这是const连接类型的特例。const:该表最多有一个匹配行,将在查询开始时读取。由于只有一行,这一行中的列值可以被优化器的其余部分认为是常量。const表很快,因为它们只读一次!eq_ref:对于上表的行的每一种组合,从本表读取一行,性能仅次于const。ref:对于前一个表中行的每个组合,所有具有匹配索引值的行都会从这个表中读取。ref_or_null:这种连接类型类似于ref,但增加了MySQL可以专门搜索包含NULL值的行。index_merge:这种连接类型表示使用了索引合并优化方式。unique_subquery:该类型替代了如下形式的IN子查询的ref:valueIN(SELECTprimary_keyFROMsingle_tableWHEREsome_expr)unique_subquery是索引查找功能,可以完全替代子查询,效率更高。index_subquery:这种连接类型类似于unique_subquery。可以替换IN子查询,但只适用于如下形式的子查询中的非唯一索引:valueIN(SELECTkey_columnFROMsingle_tableWHEREsome_expr)range:只检索给定范围的行,使用索引来选择行。index:这种join类型和ALL一样,只是只扫描索引树。这通常比ALL快,因为索引文件通常比数据文件小。ALL:对于前一个表中行的每个组合,执行完整的表扫描。参数possible_keys更像是mysql的预测,预测在指定sql执行过程中哪些索引会生效。keysql执行过程中实际生效的索引列key_len表示MySQL决定使用的key的长度。ref显示要使用哪个列或常量以及从表中选择行的键。rows显示MySQL认为它必须检查以执行查询的行数。将多行数据相乘可以估计要处理的行数。filtered显示按条件过滤的行数的百分比估计值。ExtraDistinct:MySQL找到第一个匹配行后,停止为当前行组合搜索更多行。不存在:MySQL可以对查询进行LEFTJOIN优化。找到符合LEFTJOIN标准的行后,将不再检查表中的更多行是否为之前的行组合。rangecheckedforeachrecord(indexmap:#):MySQL没有找到好的可以使用的索引,但是发现如果知道上一张表的列值,可能会使用一些索引。使用文件排序:MySQL需要额外的传递来确定如何按排序顺序检索行。使用索引:仅使用索引树中的信息从读取实际行中检索表中的列信息,而无需进一步搜索。使用临时表:为了解析查询,MySQL需要创建一个临时表来保存结果。使用where:WHERE子句用于限制哪一行匹配下一张表或者发送给客户端。使用sort_union(...)、使用union(...)、使用intersect(...):这些函数说明了如何合并index_merge连接类型的索引扫描。Usingindexforgroup-by:与访问表的Usingindex方法类似,Usingindexforgroup-by是指MySQL已经找到一个索引,可以用来查询GROUPBY或DISTINCT查询的所有列,而不是另外搜索硬盘访问实际表面。3、查询的数据量太大。比如SQL查询直接查询整个表的数据信息量,直接占用网络带宽,所以访问时会出现网络拥塞。4、硬件设备不足比如面对一些高qps的查询时,数据库本身的硬件配置比较低,自然处理速度会比较慢。5.AHI是innodb存储引擎在自适应hash出现锁冲突时独有的属性。innodb存储引擎会对索引数据的查询结果进行自适应优化。哈希索引,提高查询效率。与B+Tree索引相比,hash索引可以大大减少访问io的次数,“一键命中”查询数据,具有更高效的性能,hash索引由mysql自动适配,无需adba外部干预过多。早期版本的哈希索引采用单锁模式来防止并发访问问题,这对程序本身的效率有一定的“折扣”。后来对哈希索引进行分区,不同页的数据使用不同的哈希表,每个分区都有对应的锁来防止并发访问。如果有一天你发现很多线程阻塞在RW-latches中,那可能是因为hash索引的高并发访问负载导致的阻塞。这时可以增加哈希索引的分区参数,或者关闭Adaptivehashindex特性进行处理。
