性能调优,无疑是个庞大的话题,也是很多项目中非常重要的一环,性能调优难做是众所周知的,毕竟性能调优涵盖的面实在是太多了,在这里我就大概的讲一下企业中最常用的四种调优——JVM调优、MySQL调优、Nginx调优以及Tomcat调优,一家之言,有什么说的不对的还请多包涵补充。
篇幅所限,有些东西是肯定写不到的,所以本文只是挑了一些重要部分来剖析,如果需要完整详细的掌握性能调优,可以来领取系统整理的性能调优笔记和相关学习资料
话不多说,坐稳扶好,发车喽!
如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
在加载阶段,虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。注意这里的二进制字节流不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以从网络中获取,也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
相对于类加载过程的其他阶段,加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段既可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员们可以通过定义自己的类加载器去控制字节流的获取方式。
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
public static int v = 8080;
实际上变量v在准备阶段过后的初始值为0而不是8080,将v赋值为8080的putstatic
指令是程序被编译后,存放于类构造器
方法之中,这里我们后面会解释。
但是注意如果声明为:
public static final int v = 8080;
在编译阶段会为v生成ConstantValue
属性,在准备阶段虚拟机会根据ConstantValue属性将v赋值为8080。
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:
等类型的常量。
下面我们解释一下符号引用和直接引用的概念:
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
初始化阶段是执行类构造器
方法的过程。
方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证
方法执行之前,父类的
方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成
方法。
注意以下几种情况不会执行类初始化:
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类是来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果。
JVM提供了3种类加载器:
Bootstrap ClassLoader
):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath
参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。Extension ClassLoader
):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。Application ClassLoader
):负责加载用户路径(classpath)上的类库。JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object
,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object
对象。
在有些情境中可能会出现要我们自己来实现一个类加载器的需求,由于这里涉及的内容比较广泛,我想以后单独写一篇文章来讲述,不过这里我们还是稍微来看一下。我们直接看一下jdk中的ClassLoader
的源码实现:
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
Class c = findLoadedClass(name);
判断一个类是否已经被加载过。if (c == null)
中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是Bootstrap ClassLoader
为止。resolve
的值,判断这个class是否需要解析。而上面的findClass()
的实现如下,直接抛出一个异常,并且方法是protected
,很明显这是留给我们开发者自己去实现的,这里我们以后我们单独写一篇文章来讲一下如何重写findClass
方法来实现我们自己的类加载器。
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
这几个存储区最主要的就是栈区和堆区,那么什么是栈什么是堆呢?说的简单点,栈里面存放的是基本的数据类型和引用,而堆里面则是存放各种对象实例的。
堆与栈分开设计是为什么呢?
栈的大小可以通过-XSs设置,如果不足的话,会引起java.lang.StackOverflowError的异常
线程私有,生命周期与线程相同。每个方法执行的时候都会创建一个栈帧(stack frame)用于存放 局部变量表、操作栈、动态链接、方法出口。
存放对象实例,所有的对象的内存都在这里分配。垃圾回收主要就是作用于这里的。
因此一般都建议把这两个参数设置成一样大,可以避免JVM在不断调整大小。
这里记录了线程执行的字节码的行号,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。
类型信息、字段信息、方法信息、其他信息
有两种方式,一种是引用计数(但是无法解决循环引用的问题);另一种就是可达性分析。
判断对象可以回收的情况:
这种方法优点就是减少停顿时间,但是缺点是会造成内存碎片。
这种方法不涉及到对象的删除,只是把可用的对象从一个地方拷贝到另一个地方,因此适合大量对象回收的场景,比如新生代的回收。
这种方法可以解决内存碎片问题,但是会增加停顿时间。
最后的这种方法是前面几种的合体,即目前JVM主要采取的一种方法,思想就是把JVM分成不同的区域。每种区域使用不同的垃圾回收方法。
上面可以看到堆分成两个个区域:
这里可以详细的说一下新生代复制回收的算法流程:
在新生代中,分为三个区:Eden, from survivor, to survior。
这种收集器就是以单线程的方式收集,垃圾回收的时候其他线程也不能工作。
大致的流程为:初始标记–并发标记–重新标记–并发清除
大致的流程为:初始标记–并发标记–最终标记–筛选回收
篇幅所限,关于类字节码文件、调优工具以及GC日志分析这里就不写了,如果有感兴趣的朋友可以点击领取我整理的完整JVM性能调优笔记,里面会有详细叙述。
包含三大组件:
(1)命令解析器:检查语法和转换查询树。
(2)查询执行器:优化查询。
(3)查询优化器:负责执行查询。
包含三大组件:
(1)事务管理器:通过锁来管理数据及维持事务的ACID属性。
(2)数据访问方法:处理对行、索引、页、行版本、空间分配等的I/O请求。
(3)缓冲区管理器:管理SQL Server的主要内存消耗组件Buffer Pool。
包含SQL Server的所有缓存。如计划缓存和数据缓存。
记录事务的所有更改。保证事务ACID属性的重要组件。
数据库的物理存储文件。
6.SQL Server网络接口
建立在客户端和服务器之间的网络连接的协议层
1.2.1 当客户端执行一条T-SQL语句给SQL Server服务器时,会首先到达服务器的网络接口,网络接口和客户端之间有协议层。
1.2.2 客户端和网络接口之间建立连接。使用称为“表格格式数据流”(TDS) 数据包的 Microsoft 通信格式来格式化通信数据。
1.2.3 客户端发送TDS包给协议层。协议层接收到TDS包后,解压并分析包里面包含了什么请求。
1.2.4 命令解析器解析T-SQL语句。命令解析器会做下面几件事情:
(1)检查语法。发现有语法错误就返回给客户端。下面的步骤不执行。
(2)检查缓冲池(Buffer Pool)中是否存在一个对应该T-SQL语句的执行计划缓存。
(3)如果找到已缓存的执行计划,就从执行计划缓存中直接读取,并传输给查询执行器执行。
(4)如果未找到执行计划缓存,则在查询执行器中进行优化并产生执行计划,存放到Buffer Pool中。
1.2.5 查询优化器优化SQL语句
当Buffer Pool中没有该SQL语句的执行计划时,就需要将SQL传到查询优化器,通过一定的算法,分析SQL语句,产生一个或多个候选执行计划。选出开销最小的计划作为最终执行计划。然后将执行计划传给查询执行器。
1.2.6 查询执行器执行查询
查询执行器把执行计划通过OLE DB接口传给存储引擎的数据访问方法。
1.2.7 数据访问方法生成执行代码
数据访问方法将执行计划生成SQL Server可操作数据的代码,不会实际执行这些代码,传送给缓冲区管理器来执行。
1.2.8 缓冲区管理器读取数据。
先在缓冲池的数据缓存中检查是否存在这些数据,如果存在,就把结果返回给存储引擎的数据访问方法;如果不存在,则从磁盘(数据文件)中读出数据并放入数据缓存中,然后将读出的数据返回给存储引擎的数据访问方法。
1.2.9 对于读取数据,将会申请共享锁,事务管理器分配共享锁给读操作。
1.2.10存储引擎的数据访问方法将查询到的结果返回关系引擎的查询执行器。
1.2.11 查询执行器将结果返回给协议层。
1.2.12 协议层将数据封装成TDS包,然后协议层将TDS包传给客户端。
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少io次数,加速查询。(其中primary key和unique key,除了有加速查询的效果之外,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而index key只有加速查询的效果,没有约束效果)
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
强调:一旦为表创建了索引,以后的查询最好先查索引,再根据索引定位的结果去找数据
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等,下面内容看不懂的同学也没关系,能明白这个目录的道理就行了。 那么你想,书的目录占不占页数,这个页是不是也要存到硬盘里面,也占用硬盘空间。
你再想,你在没有数据的情况下先建索引或者说目录快,还是已经存在好多的数据了,然后再去建索引,哪个快,肯定是没有数据的时候快,因为如果已经有了很多数据了,你再去根据这些数据建索引,是不是要将数据全部遍历一遍,然后根据数据建立索引。你再想,索引建立好之后再添加数据快,还是没有索引的时候添加数据快,索引是用来干什么的,是用来加速查询的,那对你写入数据会有什么影响,肯定是慢一些了,因为你但凡加入一些新的数据,都需要把索引或者说书的目录重新做一个,所以索引虽然会加快查询,但是会降低写入的效率。
前面讲了索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,现在我们来看看索引怎么做到减少IO,加速查询的。任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。
即使我们不会这些锁知识,我们的程序在一般情况下还是可以跑得好好的。因为这些锁数据库隐式帮我们加了
UPDATE、DELETE、INSERT
语句,InnoDB会自动给涉及数据集加排他锁(X)SELECT
前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT
等)前,会自动给涉及的表加写锁只会在某些特定的场景下才需要手动加锁,学习数据库锁知识就是为了:
首先,从锁的粒度,我们可以分成两大类:
不同的存储引擎支持的锁粒度是不一样的:
InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁
表锁下又分为两种模式:
从上面已经看到了:读锁和写锁是互斥的,读写操作是串行。
max_write_lock_count
和low-priority-updates
值得注意的是:
concurrent_insert
来指定哪种模式,在MyISAM中它默认是:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别
MVCC(Multi-Version Concurrency Control)多版本并发控制,可以简单地认为:MVCC就是行级锁的一个变种(升级版)。
事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节
在表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC一般读写是不阻塞的(所以说MVCC很多情况下避免了加锁的操作)
MVCC实现的读写不阻塞正如其名:多版本并发控制—>通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。
快照有两个级别:
语句级
针对于Read committed隔离级别
事务级别
针对于Repeatable read隔离级别
我们在初学的时候已经知道,事务的隔离级别有4种:
Read uncommitted
会出现脏读,不可重复读,幻读
Read committed
会出现不可重复读,幻读
Repeatable read
会出现幻读(但在Mysql实现的Repeatable read配合gap锁不会出现幻读!)
Serializable
串行,避免以上的情况!
Read uncommitted会出现的现象—>脏读:一个事务读取到另外一个事务未提交的数据
例子:A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。
出现脏读的本质就是因为操作(修改)完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据。
Read committed避免脏读的做法其实很简单:
就是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作
但Read committed出现的现象—>不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改
注:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】
上面也说了,Read committed是语句级别的快照!每次读取的都是当前最新的版本!
Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。
呃…如果还是不太清楚,我们来看看InnoDB的MVCC是怎么样的吧(摘抄《高性能MySQL》)
InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列一个保存了行的创建时间,一个保存了行的过期(删除)时间。当然存储的并不是真正的时间值,而是系统版本号。每开始一个新的事务,系统版本号会自动递增,事务开始的时候的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
select
InnoDB 会根据以下两个条件检查每行记录:
a. InnoDB 只查找版本早于当前事务版本的数据行,这样可以确保事务读取到的数据,要么是在事务开始前就存在的,要么是事务自身插入或更新的
b. 行的删除版本要么未定义要么大于当前事务版本号,确保了事务读取到的行,在事务开始前未被删除
至于虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
nginx 常用做静态内容服务和反向代理服务器,以及页面前端高并发服务器。适合做负载均衡,直面外来请求转发给后面的应用服务(tomcat什么的)
user root; #运行worker进程的账户,user 用户 [组],默认以nobody账户运行
worker_processes 7; #要使用的worker进程数,可设置为数值、auto(根据机器性能自动设置),默认值1
error_log logs/error.log; #nginx进程(master+worker)的日志设置,保存位置、输出级别,此即为默认保存位置
#error_log logs/error.log notice; #输出级别可选,由低到高依次为:debug(输出信息最多),info,notice,warn,error,erit(输出信息最少)
pid logs/nginx.pid; #nginx主进程的pid的保存位置,此即为默认值
worker_rlimit_nofile 65535; #单个worker进程可打开的最大文件描述符数
**worker_processes: **
实际运营时一般设置为很接近CPU的线程数,比如说CPU是8线程,一般设置为6、7。
我们自己开发、用时一般设置为1、2即可,不然太吃资源。
worker_rlimit_nofile:
r是read,limit是限制,单个worker进程最多只能打开指定个数的文件,超过便不能再读取文件。打开一次文件便会产生一个文件描述符。
此设置是为了防止单个worker进程消耗大量的系统资源。
ps -ef | grep nginx 查询下nginx的进程:
不管设置多少个worker进程,主进程只有一个(即运行sbin/nginx)。
主进程由Linux当前登录的账户运行,工作进程由user指令指定的账户运行。第一列数字是进程的PID。
nginx工作进程和nginx主进程都是Linux中的进程,但主进程(父进程)可以控制worker进程(子进程)的开启、结束。
master进程可以看做老板,worker进程可以看做打工仔。
events {
accept_mutex on; #防止惊群
multi_accept on; #允许单个worker进程可同时接收多个网络连接的请求,默认为off
use epoll; #设置worker进程使用高效模式
worker_connections 1024; #指定单个worker进程最多可建立的网络连接数,默认值1024。
}
accept_mutex:
惊群现象:一个网络连接到来,所有沉睡的worker进程都会被唤醒,但只用一个worker处理连接,其余被唤醒的worker又开始沉睡。
设置为on:要使用几个worker就唤醒几个,不全部唤醒,默认值就是on。
设置为off:一律全部唤醒。一片worker醒来是要占用资源的,会影响性能。
use:
指定nginx的工作模式,可选的值:select、poll、kqueue、epoll、rtsig、/dev/poll。
其中select、poll都是标准模式,kqueue、epoll都是高效模式,
kqueue是在BSD系统中用的,epoll是在Linux系统中用的。(BSD是Unix的一个分支,Linux是一种类Unix系统)。
全局块中的worker_processes、events块中的worker_connections是nginx支持高并发的关键,这2个数值相乘即nginx可建立的最大连接数。
一个连接要用一个文件来保存,
worker_connections设置的单个worker进程的最大连接数,受全局块中worker_rlimit_nofile设置的单个worker进程可打开的最大文件数限制。
而worker_rlimit_nofile只是nginx对单个worker进程的限制,要受Linux系统对单个进程可打开的最大文件描述符数限制。
Linux默认单个进程最多只能打开1024个文件描述符,需要我们修改下Linux的资源限制,设置单个进程可打开的最大文件描述符数:
ulimit -n 65536
ulimit命令可以限制单个进程使用的系统资源的尺寸、数量,包括内存、缓冲区、套接字、栈、队列、CPU占用时间等。
可用ulimit --help查看参数。
http{
#http全局块
#server块
}
可以有多个server块。
http全局块的配置作用于整个http块(http块内的所有server块)。
include mime.types; #将conf/mime.types包含进来
default_type application/octet-stream; #设置默认的MIME类型,二进制流。如果使用的MIME类型在mime.types中没有,就当作默认类型处理。
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log; #设置日志,这个日志保存的是客户端请求的信息,包括客户端地址、使用的浏览器、浏览器内核版本、请求的url、请求时间、请求方式、响应状态等。 #access_log logs/access.log main; #可指定日志格式,上面定义的main格式即默认格式。保存位置默认是logs/access.log
sendfile on; #开启文件高效传输模式,默认为off,不开启。
#tcp_nopush on; #如果响应体积过大,默认会分多个批次传输给客户端,设置为on会一次性传给客户端,可防止网络阻塞
#tcp_nodelay on; #如果响应体积过小,默认会放在缓冲区,缓冲区满了才刷给客户端,设置为on直接刷给客户端,可防止网络阻塞
keepalive_timeout 65; #与客户端保持连接的超时时间,在指定时间内,如果客户端没有向Nginx发送任何数据(无活动),Nginx会关闭该连接。
gzip on; #使用gzip模块压缩响应数据。启用后响应体积变小,传输到客户端所需时间更少,节省带宽,但nginx压缩、客户端解压都有额外的时间、资源开销,nginx的负担也会加大。
upstream servers{ #设置负载均衡器,可同时设置多个负载均衡器。负载均衡器的名称中不能含有_,此处指定名称为servers
server 192.168.1.7:8080; #tomcat服务器节点
server 192.168.1.8:8081; server 192.168.1.7:8080 down; #down表示该节点下线,暂不使用 server 192.168.1.8:8081 backup; #backup表示该节点是备胎,只有在其他节点忙不过来时才会启用(比如一些节点出故障了、其他节点负载变大)。 server 192.168.1.8:8081 max_fails=3 fail_timeout=60s; #如果对该节点的请求失败3次,就60s内暂时不使用该节点,60s后恢复使用
}
日志格式常用的值:
server{ #server全局块 listen 80; #要监听的端口
server_name localhost; #虚拟主机(即域名),要在dns上注册过才有效,没有注册的话只能用localhost。可指定多个虚拟主机,空格分开即可
charset utf-8; #使用的字符集。
#access_log logs/host.access.log main; #在http全局块、server全局块中任意一处设置日志即可。http全局块已经设置了日志,此处可不用设置。 #错误页设置 error_page 404 /404.html; #html目录下默认只有index.html(nginx首页)、50x.html,需要自己写404.html location = /404.html { root html; #指定404.html所在目录,此处使用相对路径,nginx主目录下的html目录,也可以使用绝对路径 }
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
} #处理静态资源 location ~* \.(html|css|js|gif|jpg|png|mp4)$ { #使用正则表达式匹配url,如果请求的是这些文件,就使用下面的处理方式 root static; #如果使用nginx处理静态资源,需使用root指定静态资源所在目录。在nginx主目录下新建目录static,把静态资源放进去即可。 expires 30d; #设置缓存过期时间 #proxy_pass http://192.168.1.10:80; #如果使用apache等其他机器处理静态资源,使用proxy_pass转发过去即可,多台机器集群时使用负载均衡器即可。 }
#设置默认处理方式 location / { #如果url没有指定匹配,就使用默认的处理方式来处理 root html; #指定处理请求的根目录。nginx本身作为web服务器直接处理客户端请求时,比如请求login.jsp,会调用root指定目录下的login来处理请求。 index index.html index.htm; #指定nginx服务器的首页地址。root、index2项配置都是必需的。 proxy_pass http://servers; #指定要使用的负载均衡器,转发给其中某个节点处理。如不设置此项(代理),则默认nginx本身作为web服务器,直接处理请求,会到root指定目录下找请求的文件 }
}
设置的错误页面是nginx作为web服务器(处理静态资源)出现问题时,比如nginx上的静态资源找不到,返回给客户端的。
如果是tomcat出现的问题,比如tomcat上的xxx.jsp找不到,返回的是tomcat的错误页面,不是nginx的。
如果使用nginx本身要作为web服务器,直接处理客户端请求,比如处理静态资源,要将全局块中user 设置为运行nginx的账户(即当前登陆Linux的账户),
否则worker进程(默认nobody账户)无权限读取当前账户(即运行nginx主进程的账户)的静态资源,客户端会显示403禁止访问。
可以使用正则表达式来过滤客户端ip,也可以把客户端的ip过滤规则写在文件中,然后包含进来。
(1)轮询
将列表中的服务器排成一圈,从前往后,找空闲的服务器来处理请求。
轮询适合服务器性能差不多的情况。默认使用的就是轮询,不需要设置什么。
(2)加权轮询
upstream servers{
server 192.168.1.7:8080 weight=1;
server 192.168.1.8:8081 weight=2;
}
设置权重,权重大的轮到的机会更大,适合服务器性能有明显差别的情况。
(3)ip_hash
upstream servers{
ip_hash;
server 192.168.1.7:8080;
server 192.168.1.8:8081;
}
根据客户端ip的hash值来转发请求,同一客户端(ip)的请求都会被转发给同一个服务器处理,可解决session问题。
(4)url_hash(第三方)
upstream servers{
hash $request_uri;
server 192.168.1.7:8080;
server 192.168.1.8:8081;
}
根据请求的url来转发,会将url相同的请求转发给同一服务器处理。
一直处理某个url,服务器上一般都有该url的缓存,可直接从缓存中获取数据作为响应返回,减少时间开销。
(5)fair(第三方)
upstream servers{
fair;
server 192.168.1.7:8080;
server 192.168.1.8:8081;
}
根据服务器响应时间来分发请求,响应时间短的分发的请求多。
fair 公平,nginx先计算每个节点的平均响应时间,响应时间短说明该节点负载小(闲),要多转发给它;响应时间长说明该节点负载大,要少转发给它。
ip_hash、url_hash都是使用特定节点来处理特定请求,如果特定节点故障,nginx会剔除不可用的节点,将特定请求转发给其它节点处理,url_hash影响不大,但ip_hash会丢失之前的session数据。
在server.xml中配置:
tomcat默认的http请求处理模式是bio(即阻塞型,下面第二种),每次请求都新开一个线程处理。下面做一个介绍
我们姑且把上面四种Connector按照顺序命名为 NIO, HTTP, POOL, NIOP。测试性能对比,数值为每秒处理的请求数,越大效率越高
NIO HTTP POOL NIOP
281 65 208 365
666 66 110 398
692 65 66 263
256 63 94 459
440 67 145 363
得出结论:NIOP > NIO > POOL > HTTP 虽然Tomcat默认的HTTP效率最低,但是根据测试次数可以看出是最稳定的。且这只是一个简单页面测试,具体会根据复杂度有所波动。
配置参考:Linux系统每个进程支持的最大线程数是1000,windos是2000。具体跟服务器的内存,Tomcat配置的数量有关联。
Tomcat的部署,是一台服务器部署一个Tomcat(上线多个项目),还是一台服务器部署多个tomact(每个tomcat部署1~n个项目)。多核必选配置多个Tomcat,微服务多线程的思想模式。
修改/bin/catalina.sh,增加如下设置:
JAVA_OPTS='-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】'
需要把这个两个参数值调大,大小的可以根据服务器内存的大小进行调整。例如:
JAVA_OPTS='-Xms1024m –Xmx2048m'
服务器是8G 内存,跑了3个tomcat服务,给分配了2G的内存,因为还有其他进程。
本篇文章写到这里差不多就结束了,当然也有很多东西还没有写到,不过限于篇幅也是没辙,我整理了很详细的JVM、MySQL、NGINX和Tomcat的学习笔记以及资料,需要的朋友直接点击领取就可以了。
最后,码字不易,所以,可以点个赞和收藏吗兄弟们!
end