0%

AbstractBeanDefinition

AbstractBeanDefinition实现了BeanDefinition接口,是最重要的Bean定义类。

AbstractBeanDefinition 常量属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 默认scope
*/
public static final String SCOPE_DEFAULT = "";

/**
* 自动装配类型:不自动装配
*/
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;

/**
* 自动装配类型:根据名称自动装配
*/
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

/**
* 自动装配类型:根据类型自动装配
*/
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

/**
* 自动装配类型:根据构造函数自动装配
*/
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

/**
* 自动装配类型:自动选择装配类型
*/
@Deprecated
public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;

/**
* 依赖检查:不检查
*/
public static final int DEPENDENCY_CHECK_NONE = 0;

/**
* 依赖检查:检查对象
*/
public static final int DEPENDENCY_CHECK_OBJECTS = 1;

/**
* 依赖检查:检查简单的属性
* 基本数据类型及其包装类型,枚举类型/CharSequence/Number/Date及其实现子类,URI,URL,Locale,Class等都被Spring认为是简单值类型
* BeanUtils.isSimpleProperty
*/
public static final int DEPENDENCY_CHECK_SIMPLE = 2;

/**
* 依赖检查:检查对象和简单属性
*/
public static final int DEPENDENCY_CHECK_ALL = 3;

/**
* 若Bean未指定销毁方法,容器应该尝试推断Bean的销毁方法的名字,目前来说,推断的销毁方法的名字一般为close或是shutdown(即未指定Bean的销毁方法,但是内部定义了名为close或是shutdown的方法,则容器推断其为销毁方法)
*/
public static final String INFER_METHOD = "(inferred)";

AbstractBeanDefinition 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* bean的类
*/
@Nullable
private volatile Object beanClass;
/**
* bean 对象的生存周期(作用域)singleton,prototype,request,session,global session
*/
@Nullable
private String scope = SCOPE_DEFAULT;
/**
* 是否是抽象类
*/
private boolean abstractFlag = false;
/**
* 是否懒加载
*/
private boolean lazyInit = false;
/**
* 自动装配类型
*/
private int autowireMode = AUTOWIRE_NO;
/**
* 依赖检查方式
*/
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
/**
* 当前bean依赖的bean名称
*/
@Nullable
private String[] dependsOn;
/**
* 是否作为自动装配候选
* autowire-candidate属性设置为false,这样容器在查找自动装配对象时,
* 将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,
* 但是该bean本身还是可以使用自动装配来注入其他bean的
*/
private boolean autowireCandidate = true;
/**
* 自动装配时出现多个bean候选者时,作为首选者
*/
private boolean primary = false;
/**
* 记录Qualifier,对应子元素qualifier
*/
private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
/**
* 为创建bean的实例指定一个回调,
* 作为声明式指定的工厂方法的替代方法。
* 如果设置了这样一个回调,它将覆盖任何其他构造函数
* 或工厂方法元数据。但是,bean属性的填充和
* 潜在的注释驱动的注入仍然会像往常一样应用
*/
@Nullable
private Supplier<?> instanceSupplier;
/**
* 是否允许访问非公开的构造器和方法
*/
private boolean nonPublicAccessAllowed = true;
/**
* 是否以一种宽松的模式解析构造函数,默认为true,
* 如果为false,则在以下情况
* interface ITest{}
* class ITestImpl implements ITest{};
* class Main {
* Main(ITest i) {}
* Main(ITestImpl i) {}
* }
* 抛出异常,因为Spring无法准确定位哪个构造函数程序设置
*/
private boolean lenientConstructorResolution = true;
/**
* 工厂bean名称
*/
@Nullable
private String factoryBeanName;
/**
* 工厂bean方法
*/
@Nullable
private String factoryMethodName;
/**
* 初始化参数
*/
@Nullable
private ConstructorArgumentValues constructorArgumentValues;
/**
* 对象的属性值
*/
@Nullable
private MutablePropertyValues propertyValues;

/**
* 方法重写的持有者,记录lookup-method、replaced-method元素
*/
@Nullable
private MethodOverrides methodOverrides;
/**
* bean 初始化后回调方法名称,对应bean属性init-method
*/
@Nullable
private String initMethodName;
/**
* bean 销毁前回调方法名称,对应bean属性destroy-method
*/
@Nullable
private String destroyMethodName;
/**
* 是否执行init-method
*/
private boolean enforceInitMethod = true;
/**
* 是否执行destroy-method
*/
private boolean enforceDestroyMethod = true;
/**
* 是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true
* 是否是合成类(是不是应用自定义的,例如生成AOP代理时,会用到某些辅助类,这些辅助类不是应用自定义的,这个就是合成类)
* 是否是一个合成 BeanDefinition,
* // 合成 在这里的意思表示这不是一个应用开发人员自己定义的 BeanDefinition, 而是程序
* // 自己组装而成的一个 BeanDefinition, 例子 :
* // 1. 自动代理的helper bean,一个基础设施bean,因为使用<aop:config> 被自动合成创建;
* // 2. bean errorPageRegistrarBeanPostProcessor , Spring boot 自动配置针对Web错误页面的
* // 一个bean,这个bean不需要应用开发人员定义,而是框架根据上下文自动合成组装而成;
*/
private boolean synthetic = false;
/**
* bean 角色
* APPLICATION:用户,INFRASTRUCTURE:完全内部使用,与用户无关,SUPPORT:某些复杂配置的一部分
*/
private int role = BeanDefinition.ROLE_APPLICATION;
/**
* bean描述
*/
@Nullable
private String description;
/**
* 这个bean定义的资源
*/
@Nullable
private Resource resource;

实现类

  • ChildBeanDefinition
    通用BeanDefinition 实现类,引入了 parentName属性

  • GenericBeanDefinition
    通用BeanDefinition 实现类,引入了 parentName属性

  • AnnotatedGenericBeanDefinition
    基于注解的BeanDefinition实现类,继承GenericBeanDefinition,引入了AnnotationMetadata metadata,MethodMetadata factoryMethodMetadata 两个属性;

  • ScannedGenericBeanDefinition
    基于注解的BeanDefinition实现类,继承ScannedGenericBeanDefinition,引入了AnnotationMetadata metadata 属性;

  • RootBeanDefinition

  • ConfigurationClassBeanDefinition
    继承RootBeanDefinition,为静态内部类,引入了AnnotationMetadata metadata,MethodMetadata factoryMethodMetadata 两个属性;

  • ClassDerivedBeanDefinition
    继承RootBeanDefinition,为静态内部类

AbstractBeanDefinition属性信息可以分为一下几类

  • Bean描述相关信息(否是抽象类、是否单例,Bean所属的类)
  • Bean依赖信息(dependsOn)
  • 自动装配的信息
  • 生命周期相关信息(init、destroy,通过反射调用)
  • 来源信息(工厂方法名、工厂类名,有可能通过工厂FactoryBean创建)
  • 构造函数信息,Bean的属性以及对应的值(propertyValues,constructorArgumentValues)
  • 被IOC容器覆盖的方法(methodOverrides,@Lookup 注解是基于此实现,器初始化的时候会通过cglib字节码库动态生成一个子类,并且会覆盖父类的实现)

数据库ACID特性

(1)原子性(Actomicity):对数据的操作,要么全都执行,要么全都不执行,整个事物视为一个整体
(2)一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的
(3)隔离性(Isolation):数据库系统提供一定的隔离机制,确保事务不受其他事物的影响,同时事物的中间状态对外不可见
(4)持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持

事物隔离级别

事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。

查询当前mysql事物隔离级别

1
select @@tx_isolation;

第一级别:Read Uncommitted(未提交读)

1
set tx_isolation='READ-UNCOMMITTED';

(1)当前事物可以看到其他事务未提交事务的数据,造成脏读
(2)本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少,但是副作用又较大

第二级别:Read Committed(已提交读)

1
set tx_isolation='READ-COMMITTED';

(1)当前事物可以读取到其他事物已经提交的数据,造成不可重复读(Non repeatable Read)
(2)大多数数据库系统的默认隔离级别(但不是MySQL默认的)
(3)不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。
(4)导致这种情况的原因可能有:
a、有一个交叉的事务有新的commit,导致了数据的改变;
b、一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit

第三级别:Repeatable Read(可重读)

1
set tx_isolation='REPEATABLE-READ';

(1)MySQL的默认事务隔离级别
(2)同一事务的多个实例在并发读取数据时,会看到同样的数据行
(3)此级别可能出现的问题–幻读(Phantom Read):
当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”
(4)InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,MultiVersion Concurrency Control)机制解决了该问题

第4级别:Serializable(可串行化),完美,但是并发低

1
set tx_isolation='SERIALIZABLE';

(1)这是最高的隔离级别,使读写操作串行化,一般不用
(2)它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。它在每个读的数据行上加上共享锁。
(3)在这个级别,可能导致大量的超时现象和锁竞争

数据库实现事物隔离级别的方式

(1)对其加锁,阻止其他事务对数据进行修改
(2)MutiVersion Concurrency Control,简称MVCC或MCC多版本控制,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),
并用这个快照来提供一定级别的一致性读取
在MVCC并发控制中,读操作可以分成快照读 (snapshot read)与当前读 (current read)。
快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
InnoDB就是基于多版本控制的引擎

并发事务带来的问题

相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,但并发事务处理也会带来一些问题,主要包括以下几种情况:
(1)更新丢失(Lost Update):当多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,多个事物同时更新数据,就会发生丢失更新问题 -> 乐观锁
(2)脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。
(3)不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。
(4)幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

网关的作用

  • 用户验证登陆权限等管理
  • 限流、熔断、日志
  • 统一接口地址,统一跨域控制,方便https证书等管理
  • 安全控制、黑名单白名单

zuul网关和gateway 网关对比

zuul是第一代spring cloud网关,由netfix开发,使用servlet阻塞式的api,不支持长连接,目前基本已经被gateway替代,
gateway是spring cloud第二代网关,由spring cloud开发,使用webflux、reactive异步非阻塞(响应式)api,性能优于zuul

nginx 和 gateway的区别

共同点:api拦截、负载均衡、反向代理、请求过滤
差异:nginx使用lua语言,对于开发来讲,相对难管理

网关持久化方案

当网关服务器启动的时候,从数据库查询路由配置,将数据读入内存中,并定时从数据库拉取最新数据

gateway核心技术点

路由配置核心字段

  • id(如果没填,会自动生成)
  • url(转发地址,支持负载均衡形式,ld://application-name)
  • filter(过滤器GatewayFilter,GatewayFilterFactory)
  • predicates(谓词匹配路由规则,PredicateFactory)

核心流程

client -> handler maping -> filter -> 代理 -> 输出

失败重试filter配置

1
2
3
4
5
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY

常见配置

1
2
3
4
5
6
7
8
9
10
11
12
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: orderPayCenterService
uri: lb://order-pay-center-service
predicates:
- Path=/orderPayCenterService/**
filters:
- StripPrefix=1

Global Filter与Gateway Filter的区别

Global Filter作用在所有的路由上面,Gateway Filter作用在某些路由上面

参考:https://img2018.cnblogs.com/blog/292888/201810/292888-20181014143215674-1048839967.png

查看路由(RestControllerEndpoint,GatewayControllerEndpoint)

http://127.0.0.1/actuator/gateway/routes
http://127.0.0.1/actuator/gateway/routes/save(增删改查)

查看 filter

http://127.0.0.1/actuator/gateway/globalfilters
http://127.0.0.1/actuator/gateway/routefilters

JVM之jps进程状况工具

显示当前所有java进程,类似于ps命令

options说明

  • -q:只输出进程 ID
  • -m:输出传入 main 方法的参数
  • -l:输出完全的包名,应用主类名,jar的完全路径名
  • -v:输出jvm参数
  • -V:输出通过flag文件传递到JVM中的参数

    jps 原理

    java程序在启动以后,会在java.io.tmpdir指定的目录(临时目录)下,生成一个类似于hsperfdata_{$user}的文件夹,这个文件夹里(在Linux中为/tmp/hsperfdata_{$user}/),有几个文件,名字就是java进程的pid,因此列出当前运行的java进程,只是把这个目录里的文件名列一下而已。 至于系统的参数什么,就可以解析这几个文件获得

    jps失效原因

    现象: 用ps -ef|grep java能看到启动的java进程,但是用jps查看却不存在该进程的id
  • 磁盘读写、目录权限问题 若该用户没有权限写/tmp目录或是磁盘已满,则无法创建/tmp/hsperfdata_userName/pid文件。或该文件已经生成,但用户没有读权限
  • 临时文件丢失,被删除或是定期清理,对于linux机器,一般都会存在定时任务对临时文件夹进行清理,导致/tmp目录被清空
  • java进程信息文件存储地址被设置,不在/tmp目录下 上面我们在介绍时说默认会在/tmp/hsperfdata_userName目录保存进程信息,但由于以上1、2所述原因,可能导致该文件无法生成或是丢失,所以java启动时提供了参数(-Djava.io.tmpdir),可以对这个文件的位置进行设置,而jps、jconsole都只会从/tmp目录读取,而无法从设置后的目录读物信息

JVM之jstat统计信息监控工具

options说明

  • -class:显示ClassLoad的相关信息;
  • -compiler:显示JIT编译的相关信息;
  • -gc:显示和gc相关的堆信息;
  • -gccapacity:显示各个代的容量以及使用情况;
  • -gcmetacapacity:显示metaspace的大小
  • -gcnew:显示新生代信息;
  • -gcnewcapacity:显示新生代大小和使用情况;
  • -gcold:显示老年代和永久代的信息;
  • -gcoldcapacity:显示老年代的大小;
  • -gcutil:显示垃圾收集信息;
  • -gccause:显示垃圾回收的相关信息(通-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
  • -printcompilation:输出JIT编译的方法信息
OPTION 描述
class 用于查看类加载情况的统计,显示加载class的数量,及所占空间等信息。
compiler 查看HotSpot中即时编译器编译情况的统计
gc 查看JVM中堆的垃圾收集情况的统计,可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
gccapacity 查看新生代、老生代及持久代的存储容量情况,可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小
gccause 查看垃圾收集的统计情况(这个和-gcutil选项一样),如果有发生垃圾收集,它还会显示最后一次及当前正在发生垃圾收集的原因
gcmetacapacity 显示关于metaspace大小的统计信息。
gcnew 查看新生代垃圾收集的情况,new对象的信息
gcnewcapacity 用于查看新生代的存储容量情况,new对象的信息及其占用量
gcold 用于查看老生代及持久代发生GC的情况,old对象的信息
gcoldcapacity 用于查看老生代的容量,old对象的信息及其占用量
gcpermcapacity 用于查看持久代的容量,perm对象的信息及其占用量
gcutil 查看新生代、老生代及持代垃圾收集的情况
printcompilation 当前VM执行的信息

类加载统计 class

1
2
3
[root@gpu02_6 yangbin]# jstat -class 12308
Loaded Bytes Unloaded Bytes Time
12114 22216.9 1 0.9 7.55
  • Loaded:加载class的数量
  • Bytes:所占用空间大小
  • Unloaded:未加载数量
  • Bytes:未加载占用空间
  • Time:时间

    编译统计 compiler

    1
    2
    3
    [root@gpu02_6 yangbin]# jstat -compiler 12308
    Compiled Failed Invalid Time FailedType FailedMethod
    14812 5 0 94.03 1 java/util/TreeMap getEntry
  • Compiled:编译数量
  • Failed:失败数量
  • Invalid:不可用数量
  • Time:时间
  • FailedType:失败类型
  • FailedMethod:失败的方法

    垃圾回收统计 gc

    1
    2
    3
    [root@gpu02_6 yangbin]# jstat -gc 12308
    S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
    512.0 512.0 0.0 320.0 53760.0 12922.9 265728.0 55319.9 73728.0 69032.6 8960.0 8184.7 1521 15.311 3 0.310 15.621
  • S0C:第一个幸存区的大小
  • S1C:第二个幸存区的大小
  • S0U:第一个幸存区的使用大小
  • S1U:第二个幸存区的使用大小
  • EC:伊甸园区的大小
  • EU:伊甸园区的使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • MC:方法区大小
  • MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

堆内存统计 gccapacity

1
2
3
[root@gpu02_6 yangbin]# jstat -gccapacity 12308
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
170496.0 2727936.0 1584128.0 512.0 512.0 53760.0 341504.0 5455872.0 265728.0 265728.0 0.0 1114112.0 73728.0 0.0 1048576.0 8960.0 1521 3
  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0C:第一个幸存区大小
  • S1C:第二个幸存区的大小
  • EC:伊甸园区的大小
  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:当前老年代大小
  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代gc次数
  • FGC:老年代GC次数

总结垃圾回收统计 gcutil【常用】

1
2
3
[root@gpu02_6 yangbin]# jstat -gcutil 12308
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 62.50 32.03 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
  • S0:幸存1区当前使用比例
  • S1:幸存2区当前使用比例
  • E:伊甸园区使用比例
  • O:老年代使用比例
  • M:元数据区使用比例
  • CCS:压缩使用比例
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间
    eg:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 每一秒输出一次Jvm信息
    jstat -gcutil 12308 1000
    # 返回结果:
    S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.02 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    0.00 62.50 40.03 20.82 93.63 91.35 1521 15.311 3 0.310 15.621
    gcnew、gcnewcapacity、gcold、gcmetacapacity等用法及返回结果和上述选项类似,不再赘述。重点掌握gcutil 的用法既可

JVM之jinfo获取Java配置信息

执行命令

1
jinfo 12308

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Debugger attached successfully.
Server compiler detected.
JVM version is 25.144-b01
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.144-b01
sun.boot.library.path = /usr/local/jdk1.8.0_144/jre/lib/amd64
java.protocol.handler.pkgs = org.springframework.boot.loader
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = CN
user.dir = /home/yangbin/weclip_backend/java/webclip_statistic_cms_java
java.vm.specification.name = Java Virtual Machine Specification
PID = 12308
java.runtime.version = 1.8.0_144-b01
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/local/jdk1.8.0_144/jre/lib/endorsed
line.separator =

java.io.tmpdir = /tmp
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = :/usr/local/cuda/lib64:/usr/local/cuda/lib64:/usr/local/cuda/lib64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
spring.beaninfo.ignore = true
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 3.10.0-957.10.1.el7.x86_64
user.home = /root
user.timezone = Asia/Shanghai
catalina.useNaming = false
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
catalina.home = /tmp/tomcat.9001045804562870259.13120
user.name = root
java.class.path = /data0/dk/webclip-statistic-cms-java/target/webclip-statistic-cms-java.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = /data0/dk/webclip-statistic-cms-java/target/webclip-statistic-cms-java.jar -Xms256M -Xmx2G -XX:SurvivorRatio=6 -XX:NewRatio=2 -XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=1G -XX:PermSize=64M -XX:MaxPermSize=1G -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/data0/logs/gc/webclip-statistic-cms-java_gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data0/ -Djava.security.egd=file:/dev/./urandom --spring.profiles.active=test
java.home = /usr/local/jdk1.8.0_144/jre
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_144
java.ext.dirs = /usr/local/jdk1.8.0_144/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /usr/local/jdk1.8.0_144/jre/lib/resources.jar:/usr/local/jdk1.8.0_144/jre/lib/rt.jar:/usr/local/jdk1.8.0_144/jre/lib/sunrsasign.jar:/usr/local/jdk1.8.0_144/jre/lib/jsse.jar:/usr/local/jdk1.8.0_144/jre/lib/jce.jar:/usr/local/jdk1.8.0_144/jre/lib/charsets.jar:/usr/local/jdk1.8.0_144/jre/lib/jfr.jar:/usr/local/jdk1.8.0_144/jre/classes
java.awt.headless = true
java.vendor = Oracle Corporation
catalina.base = /tmp/tomcat.9001045804562870259.13120
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist =

VM Flags:
Non-default VM flags: -XX:CICompilerCount=15 -XX:InitialHeapSize=524288000 -XX:MaxHeapSize=8380219392 -XX:MaxNewSize=2793406464 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=174587904 -XX:OldSize=349700096 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line:

JVM之jmap内存映射工具

参数选项:

  • -dump:[live,]format=b,file= 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件
  • -finalizerinfo 打印正等候回收的对象的信息.
  • -heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况
  • -histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量
  • -permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来
  • -F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效
  • -J 传递参数给jmap启动的jvm.

    示例 jmap -heap 12308

    返回值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    [root@gpu02_6 yangbin]# jmap -heap 12308
    Attaching to process ID 12308, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.144-b01

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

    Heap Configuration:
    MinHeapFreeRatio = 0
    MaxHeapFreeRatio = 100
    MaxHeapSize = 8380219392 (7992.0MB)
    NewSize = 174587904 (166.5MB)
    MaxNewSize = 2793406464 (2664.0MB)
    OldSize = 349700096 (333.5MB)
    NewRatio = 2
    SurvivorRatio = 8
    MetaspaceSize = 21807104 (20.796875MB)
    CompressedClassSpaceSize = 1073741824 (1024.0MB)
    MaxMetaspaceSize = 17592186044415 MB
    G1HeapRegionSize = 0 (0.0MB)

    Heap Usage:
    PS Young Generation
    Eden Space:
    capacity = 55050240 (52.5MB)
    used = 32079944 (30.59381866455078MB)
    free = 22970296 (21.90618133544922MB)
    58.27394031343006% used
    From Space:
    capacity = 524288 (0.5MB)
    used = 327680 (0.3125MB)
    free = 196608 (0.1875MB)
    62.5% used
    To Space:
    capacity = 524288 (0.5MB)
    used = 0 (0.0MB)
    free = 524288 (0.5MB)
    0.0% used
    PS Old Generation
    capacity = 272105472 (259.5MB)
    used = 56647600 (54.02336120605469MB)
    free = 215457872 (205.4766387939453MB)
    20.818250946456526% used

    31019 interned Strings occupying 3185528 bytes.

示例 jmap -histo:live 34940 | more

查找最费内存的对象

1
2
3
4
5
6
7
8
9
10
11
12
 num     #instances         #bytes  class name
----------------------------------------------
1: 67495 6134832 [C
2: 6393 3088488 [B
3: 21840 1921920 java.lang.reflect.Method
4: 67375 1617000 java.lang.String
5: 45996 1471872 java.util.concurrent.ConcurrentHashMap$Node
6: 10811 1193152 java.lang.Class
7: 30108 963456 java.util.HashMap$Node
8: 6236 857416 [I
9: 6481 664072 [Ljava.util.HashMap$Node;
10: 11193 628224 [Ljava.lang.Object;

上述输出信息中,查看最大内存对象。如果某个对象占用空间很大,比如超过了100Mb,应该着重分析,为何没有释放,是否存在内存泄漏等。该命令会造成jvm强制执行一次full gc,线上慎用,可以采取dump内存快照,线下采用可视化工具进行分析

示例 导出内存快照 jmap -dump:format=b,file=/data0/34940.dump 34940

导出的文件可以通过jhat、mat等工具分析,分析方法赞不赘述,读者可自行了解

JVM之jhat堆快照分析工具

jhat 命令与jmap搭配使用,用来分析map生产的堆快存储快照。jhat内置了一个微型http/Html服务器,可以在浏览器打开查看

1
jhat  heapDum.dump

JVM之jstack堆栈跟踪工具

jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

  • -F 当进程挂起,执行jstack 命令没有任何输出后,将强制转储堆内的线程信息
  • -m 在混合模式下,打印 java 和 native c/c++ 框架的所有栈信息
  • -l 长列表。打印关于锁的附加信息,例如属于 java.util.concurrent 的 ownable synchronizers 列表

OOM内存泄露

java堆内的OOM异常是实际应用中常见的内存溢出异常,OOM的三种情况:

  • 申请资源(内存)过小,不够用。
  • 申请资源太多,没有释放。
  • 申请资源过多,资源耗尽。比如:线程过多,线程内存过大等。

排查申请资源问题

查看新生代,老生代堆内存的分配大小以及使用情况,看是否本身分配过小

1
jmap -heap 11869 

排查gc

排查gc特别是full gc情况下,各个分代内存情况

1
2
# 每秒输出一次gc的分代内存分配情况,以及gc时间
jstat -gcutil 11938 1000

查找最费内存的对象

命令

1
jmap -histo:live 11869 | more

导出内存快照分析

命令

1
jmap -dump:format=b,file=/tmp/dump.dat 11869 

java进程的线程快照信息

常用来分析内存占用高、死锁等问题

1
jstack -l 11869

pstack

可以查看某个进程的当前线程栈运行情况,top -Hp

参考文档

https://my.oschina.net/u/1859679/blog/1552290
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

Server组件

1
<Server port="8005" shutdown="SHUTDOWN">

这会让Tomcat启动一个server实例(即一个JVM),它监听在8005端口以接收shutdown命令
相关属性:
className: 实现此Server容器的全类名称,默认为org.apache.catalina.core.StandardServer
port: 接收shutdown指令的端口,默认仅允许通过本机访问,默认为8005
shutdown:发往此Server用于实现关闭tomcat实例的命令字符串,默认为SHUTDOWN
说明:管理员可以通过telnet连接8005端口后直接执行SHUTDOWN命令关闭Server容器(只允许本机执行)。

Connector组件

1
2
<Connector port="8443" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" acceptCount="100"
debug="0" scheme="https" secure="true" clientAuth="false" sslProtocol=”TLS” />

相关属性:
port:监听的端口,默认:0
address:指定连接器监听的地址,默认:0.0.0.0;
protocol:连接器使用的协议,默认:HTTP/1.1,定义AJP协议时通常为AJP/1.3
redirectPort:如果某连接器支持的协议是HTTP,当接收客户端发来的HTTPS请求时,则转发至此端口
connectionTimeout:等待客户端发送请求的超时时间,单位为毫秒,默认为60000,即1分钟
enableLookups:是否通过request.getRemoteHost()进行DNS查询以获取客户端的主机名,默认为true,一般不需要,可以设置为false
acceptCount:等待队列的最大长度,tomcat所有处理线程均处于繁忙状态时,新发来的请求将被放置于等待队列中
executor:指定配置的线程池名称
maxConnections:最大连接数,连接满时后续连接放入最大为acceptCount的队列中,对NIO和NIO2连接,默认:10000,对 APR/native,默认:8192
maxThreads:支持的最大并发连接数,默认:200, 如果指定了Executor此属性不生效
minSpareThreads:Connector创建线程池的最小活跃线程数,默认:10,如果指定了Executor此属性不生效

Executor 组件

1
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="300" minSpareThreads="25"/>

相关属性:
name:线程池名称,要求唯一, 供Connector元素的executor属性使用
className:这个类必须实现org.apache.catalina.Executor接口
namePrefix:线程名称前缀
maxThreads:最大活跃线程数,默认:200
minSpareThreads:最小活跃线程数,默认:25
maxIdleTime:空闲线程关闭的等待最大时间,默认:60000ms,当前活跃线程大于minSpareThreads并且空闲时间大于maxIdleTime,该线程将会被回收。
maxQueueSize:线程池满情况下的请求排队大小,默认:Integer.MAX_VALUE

Engine组件

Engine是Servlet处理器的一个实例,默认为定义在server.xml中的Catalina。Engine需要defaultHost属性来为其定义一个接收所有发往非明确定义虚拟主机的请求的host组件

1
<Engine name="Catalina" defaultHost="localhost">

相关属性:
defaultHost:设置默认虚拟主机,当引擎的连接器收到一个发往非明确定义虚拟主机的请求时,将发送到该虚拟主机
name:Engine组件的名称,用于日志和错误信息记录时区别不同的引擎
Engine容器中可以包含Realm、Host、Listener和Valve子容器。

Host组件:

Host组件用于定义虚拟主机,这些主机中至少要有一个跟defaultHost定义的主机名称同名

1
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"></Host>

相关属性:
appBase:此Host的webapps目录,war文件的存放目录路径,可以使用基于$CATALINA_HOME的相对路径
autoDeploy:是否自动进行deploy,默认:true
unpackWars:在启用此webapps时是否对war格式的归档文件先进行展开,默认为true
Engine和Host组合示例:

1
2
3
4
5
6
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps">
<Context path="" docBase="ROOT"/>
<Context path="/bbs" docBase="/web/bss" reloadable="true" crossContext="true"/>
</Host>
</Engine>

Context组件

Context在某些意义上类似于apache中的路径别名,一个Context定义用于标识tomcat实例中的一个Web应用程序;如下面的定义,在Tomcat6中,每一个context定义也可以使用一个单独的XML文件进行,其文件的目录为$CATALINA_HOME/conf//。可以用于Context中的XML元素有Loader,Manager,Realm,Resources和WatchedResource。

1
2
3
4
<Context path="" docBase="/web/webapps"/>
<Context path="/bbs" docBase="/web/threads/bbs" reloadable="true"></Context>
<Context path="/chat" docBase="/web/chat"/>
<Context path="/darian" docBase="darian"/>

相关属性:
docBase:Web应用程序的存放位置,也可以使用相对路径,起始路径为此Context所属Host中appBase定义的路径,docBase的路径名不能与相应的Host中appBase中定义的路径名有包含关系,比如,如果appBase为deploy,而docBase绝不能为deploy-bbs类的名字;
path:相对于Web服务器根路径而言的URI;如果为空,则表示为此webapp的根路径,不能不配置该属性,如果context定义在一个单独的xml文件中,此属性不需要定义,有可能是别名
reloadable:/WEB-INF/classes/和/WEB-INF/lib/目录中class文件发生变化是否自动重新加载,默认:false
altDDName:web.xml部署描述符路径,默认:/WEB-INF/web.xml
failCtxIfServletStartFails,同Host中的failCtxIfServletStartFails, 只对当前Context有效,默认:false
logEffectiveWebXml:是否日志打印web.xml内容(web.xml由默认的web.xml和应用中的web.xml组成),默认:false
privileged:是否使用Tomcat提供的manager servlet
swallowOutput:System.out和System.err输出是否定向到web应用日志中,默认:false

Realm组件

一个Realm表示一个安全上下文,它是一个授权访问某个给定Context的用户列表和某用户所允许切换的角色相关定义的列表。因此,Realm就像是一个用户和组相关的数据库。定义Realm时惟一必须要提供的属性是classname,它是Realm的多个不同实现,用于表示此Realm认证的用户及角色等认证信息的存放位置。

1
2
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
<Realm className="org.apache.catalina.realm.JDBCRealm" debug="99" driverName="org.gjt.mm.mysql.Driver" connectionURL="jdbc:mysql://localhost/authority" connectionName="test" connectionPassword="test" userTable="users" userNameCol="user_name" userCredCol="user_pass" userRoleTable="user_roles" roleNameCol="role_name" />

相关属性:
JAASRealm:基于Java Authintication and Authorization Service实现用户认证
JDBCRealm:通过JDBC访问某关系型数据库表实现用户认证
JNDIRealm:基于JNDI使用目录服务实现认证信息的获取
MemoryRealm:查找tomcat-user.xml文件实现用户信息的获取
UserDatabaseRealm:基于UserDatabase文件(通常是tomcat-user.xml)实现用户认证,它实现是一个完全可更新和持久有效的MemoryRealm,因此能够跟标准的MemoryRealm兼容;它通过JNDI实现;

Valve组件

Valve类似于过滤器,它可以工作于Engine和Host、Context之间、Host和Context之间以及Context和Web应用程序的某资源之间。一个容器内可以建立多个Valve,而且Valve定义的次序也决定了它们生效的次序。Tomcat6中实现了多种不同的Valve:
相关属性:
AccessLogValve:访问日志Valve
ExtendedAccessValve:扩展功能的访问日志Valve
JDBCAccessLogValve:通过JDBC将访问日志信息发送到数据库中
RequestDumperValve:请求转储Valve
RemoteAddrValve:基于远程地址的访问控制
RemoteHostValve:基于远程主机名称的访问控制
SemaphoreValve:用于控制Tomcat主机上任何容器上的并发访问数量
JvmRouteBinderValve:在配置多个Tomcat为以Apache通过mod_proxy或mod_jk作为前端的集群架构中,当期望停止某节点时,可以通过此Valve将用记请求定向至备用节点;使用此Valve,必须使JvmRouteSessionIDBinderListener
ReplicationValve:专用于Tomcat集群架构中,可以在某个请求的session信息发生更改时触发session数据在各节点间进行复制
SingleSignOn:将两个或多个需要对用户进行认证webapp在认证用户时连接在一起,即一次认证即可访问所有连接在一起的webapp
ClusterSingleSingOn:对SingleSignOn的扩展,专用于Tomcat集群当中,需要结合ClusterSingleSignOnListener进行工作
RemoteHostValve和RemoteAddrValve可以分别用来实现基于主机名称和基于IP地址的访问控制,控制本身可以通过allow或deny来进行定义,如:

1
2
3
<Context path="/probe" docBase="probe">
<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127.0.0.1"/>
</Context>

其中相关属性定义有:
className:相关的java实现的类名,相应于分别应该为org.apache.catalina.valves.RemoteHostValve或org.apache.catalina.valves.RemoteAddrValve
allow:以逗号分开的允许访问的IP地址列表,支持正则表达式,因此,点号“.”用于IP地址时需要转义;仅定义allow项时,非明确allow的地址均被deny
deny: 以逗号分开的禁止访问的IP地址列表,支持正则表达式;使用方式同allow

GlobalNamingResources 应用于整个服务器的JNDI映射,此可以避免每个Web应用程序都需要在各自的web.xml创建,这在web应用程序以WAR的形式存在时尤为有用。它通常可以包含三个子元素:Environment,Resource、ResourceEnvRef
WatchedResource可以用于Context中监视指定的webapp程序文件的改变,并且能够在监视到文件内容发生改变时重新装载此文件
Listener用于创建和配置LifecycleListener对象,而LifecycleListener通常被开发人员用来创建和删除容器
Loader Java的动态装载功能是其语言功能强大表现之一,Servlet容器使用此功能在运行时动态装载servlet和它们所依赖的类。Loader可以用于Context中控制java类的加载
Manger对象用于实现HTTP会话管理的功能,Tomcat6中有5种Manger的实现

  1. StandardManager
    Tomcat6的默认会话管理器,用于非集群环境中对单个处于运行状态的Tomcat实例会话进行管理。当Tomcat关闭时,这些会话相关的数据会被写入磁盘上的一个名叫SESSION.ser的文件,并在Tomcat下次启动时读取此文件。
  2. PersistentManager
    当一个会话长时间处于空闲状态时会被写入到swap会话对象,这对于内存资源比较吃紧的应用环境来说比较有用。
  3. DeltaManager
    用于Tomcat集群的会话管理器,它通过将改变了会话数据同步给集群中的其它节点实现会话复制。这种实现会将所有会话的改变同步给集群中的每一个节点,也是在集群环境中用得最多的一种实现方式。
  4. BackupManager
    用于Tomcat集群的会话管理器,与DeltaManager不同的是,某节点会话的改变只会同步给集群中的另一个而非所有节点。
  5. SimpleTcpReplicationManager Tomcat4时用到的版本,过于老旧了。

14、Stores PersistentManager必须包含一个Store元素以指定将会话数据存储至何处。这通常有两种实现方式:FileStore和JDBCStore。
15、Resources 经常用于实现在Context中指定需要装载的但不在Tomcat本地磁盘上的应用资源,如Java类,HTML页面,JSP文件等。
16、Cluster 专用于配置Tomcat集群的元素,可用于Engine和Host容器中。在用于Engine容器中时,Engine中的所有Host均支持集群功能。在Cluster元素中,需要直接定义一个Manager元素,这个Manager元素有一个其值为org.apache.catalina.ha.session.DeltaManager或org.apache.catalina.ha.session.BackupManager的className属性。同时,Cluster中还需要分别定义一个Channel和ClusterListener元素。
16.1、Channel 用于Cluster中给集群中同一组中的节点定义通信“信道”。Channel中需要至少定义Membership、Receiver和Sender三个元素,此外还有一个可选元素Interceptor。
16.2、Membership 用于Channel中配置同一通信信道上节点集群组中的成员情况,即监控加入当前集群组中的节点并在各节点间传递心跳信息,而且可以在接收不到某成员的心跳信息时将其从集群节点中移除。Tomcat6中Membership的实现是org.apache.catalina.tribes.membership.McastService。
16.3、Sender 用于Channel中配置“复制信息”的发送器,实现发送需要同步给其它节点的数据至集群中的其它节点。发送器不需要属性的定义,但可以在其内部定义一个Transport元素。
16.4 Transport 用于Sender内部,配置数据如何发送至集群中的其它节点。Tomcat6有两种Transport的实现:

  1. PooledMultiSender 基于Java阻塞式IO,可以将一次将多个信息并发发送至其它节点,但一次只能传送给一个节点。
  2. PooledParallelSener 基于Java非阻塞式IO,即NIO,可以一次发送多个信息至一个或多个节点。

16.5 Receiver 用于Channel定义某节点如何从其它节点的Sender接收复制数据,Tomcat6中实现的接收方式有两种BioReceiver和NioReceiver。

Tomcat 各组件之间的关系

顶级组件:位于整个配置的顶层,server
容器类:可以包含其它组件的组件,service
连接器组件:连接用户请求至tomcat,connector
被嵌套类的组件:位于一个容器当中,不能包含其它组件
engine: 核心容器,catalina引擎,负责通过connector接收用户请求
host: 虚拟主机
context: 最内层的容器类组件,一个context代表一个web应用程序,配置context的主要目的,指定对应的webapp的根目录;还能为webapp指定额外的属性,如部署方式等
valve: 拦截请求并在将其转至对应的webapp之前进行某种处理操作,可以用于任何容器中
realm: 可以用于任何容器类的组件中,关联一个用户认证库,实现认证和授权户

webapp体系结构

/WEB-INF: 包含当前webapp的deploy描述符,如所有的servlets和JSP等动态文件的详细信息,会话超时时间和数据源等;因此,其也通常用于定义当前webapp特有的资源,通常web.xml和context.xml均放置于此目录
/WEB-INF/classes: 包含所有服务器端类及当前应用程序相关的其它第三方类等
/WEB-INF/lib: 包含JSP所用到的JAR文件,此webapp自有能够被打包为jar格式的类

Tomcat的HTTP连接器:

1、HTTP/1.1连接器,Tomcat6默认使用的连接器,即Coyote,它是Tomcat作为standalone模式工作时所用到的连接器,可直接响应来自用户浏览器的关于JSP、servlet和HTML的请求;此连接器是一个Java类,定义在server.xml当中
2、NIO HTTP/1.1连接器,它支持非阻塞式IO和Comnet,
3、C/C++开发的native APR HTTP/1.1连接器,在负载较大的场景中,可以提供非常好的性能
说明:APR即Apache Portable Runtime,它是一个能让开发者采用与平台无关的风格的方式来开发C/C++代码本地库,它能够很好的跨Windows, Linux和Unix平台工作。主要从三个方面优化了系统性能:
(1)使用sendfile()内核模式调用发送大的静态文件
(2)仅使用native code保持大量的连接
(3)使用能够加速SSL请求处理的OpenSSL本地代码
备注:启用APR连接器的条件:将连接器的protocol属性设定为org.apache.coyote.http11.Http11AprProtocol,APR的库文件已经在系统库文件的搜索路径内,必须额外编译安装apr
基于连接器提高Tomcat性能的方法:

  1. 设置tcpNoDelay属性值为 true
  2. 通过maxKeepAliveRequest属性调整允许keep-alive功能的请求的最大数目,值为1时表示禁用
  3. 调整socketBuffer属性的值以改变套接字缓冲的大小
  4. 将enableLookups设置为false以禁用DNS反解
  5. Tomcat是一个多线程的Servlet容器,使用线程池能对服务器性能带去很大影响;这主要通过maxThreads、maxSpareThreads和minSpareThreads来定义
  6. 通过JAVA_OPTS,如-Xms和-Xmx设定JVM相关的参数以定义其使用内存的能力

Tomcat Server处理一个HTTP请求的过程

  • 用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得
  • Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
  • 匹配Engine,Engine获得请求并匹配虚拟主机Host
  • 匹配Context,如果匹配不到就把该请求交给路径名为””的Context去处理
  • 匹配Servlet,path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet
  • 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost(),执行业务逻辑
  • Context把执行完之后的HttpServletResponse对象返回给Host
  • Host把HttpServletResponse对象返回给Engine
  • Engine把HttpServletResponse对象返回Connector
  • Connector把HttpServletResponse对象返回给客户Browser

Tomcat IO模型

BIO 阻塞式IO,采用传统的java IO进行操作,该模式下每个请求都会创建一个线程,适用于并发量小的场景
NIO 同步非阻塞,比传统BIO能更好的支持大并发,tomcat 8.0 后默认采用该模式
APR tomcat 以JNI形式调用http服务器的核心动态链接库来处理文件读取或网络传输操作,需要编译安装APR库
AIO 异步非阻塞,tomcat8.0后支持
配置方法:在tomcat conf 下找到server.xml

BIO: protocol =” org.apache.coyote.http11.Http11Protocol”
NIO: protocol =”org.apache.coyote.http11.Http11NioProtocol”
AIO: protocol =”org.apache.coyote.http11.Http11Nio2Protocol”
APR: protocol =”org.apache.coyote.http11.Http11AprProtocol”

Tcpdump抓包参数

抓包选项

-A: 打印数据包的具体内容(非常常用,非常重要),如:抓取80端口查看http请求内容

1
tcpdump port 80 -A

显示结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
21:39:52.507728 IP 172.31.0.20.http > 100.67.93.33.fj-hdnet: Flags [P.], seq 1:389, ack 41, win 29, length 388
E...E*@.@.......dC]!.P...D.... .P...o6..HTTP/1.1 200 OK
Server: Tengine
Date: Wed, 11 Sep 2018 13:39:52 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 335
Last-Modified: Wed, 12 Sep 2018 12:24:38 GMT
Connection: close
ETag: "5b990586-14f"
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Accept-Ranges: bytes
21:39:52.507783 IP 172.31.0.20.http > 100.67.93.33.fj-hdnet: Flags [F.], seq 389, ack 41, win 29, length 0

其中可以看到完整的Http请求信息,对于排查问题非常有帮助,博主曾用这个方法解决了非常多实际问题

-c:指定抓取满足条件数据包数量
-i interface:指定抓取包的网卡,若未指定该选项,将从系统网卡列表中查找编号最小且配置好的网卡(不包括loopback)作为抓包网卡(只会找一个),用any表示所有网卡
-n:不解析主机名,显示ip
-nn:除了-n的作用外,还把端口显示为数值,否则显示端口服务名。如:

1
2
使用   -nn 参数  100.64.23.22.57271 > 172.31.0.20.80
不使用 -nn 参数 100.64.23.22.57271 > 172.31.0.20.http

-N:不打印出host的域名部分。例如tcpdump将会打印’nic’而不是’nic.ddn.mil’
-P:指定要抓取的包是流入还是流出的包。in:流入的包,out:流出的包,inout:所有包,默认为inout
-s len:设置数据包的抓取长度,默认65535字节,对于要抓取的数据包较大时,长度设置不够可能会产生包截断

输出数据选项

-e:输出的每行中都将包括数据链路层头部信息,如:源MAC和目标MAC
-q:快速打印输出,打印很少的协议相关信息
-X:输出包的头部数据,以16进制和ASCII两种方式同时输出,可以看到数据包传输的内容
-XX:输出包的头部数据,会以16进制和ASCII两种方式同时输出,比-X更加详细
-v:当分析和打印的时候,产生详细的输出,主要是tcp协议相关的一些东西
-vv:产生比-v更详细的输出
-vvv:产生比-vv更详细的输出

其他功能性选项

-D:列出可用于抓包的接口。列出网卡的编号和名称,第一个网卡为默认抓取网卡
-F:从文件中读取数据包
-w:将抓包数据输出到文件中
-r:从指定的数据包文件中读取数据

数据包过滤

主机过滤

1、抓取所有经过eth0,目的或源地址是 192.168.0.1的数据包
tcpdump -i eth0 host 192.168.1.101
2、抓取所有经过eth0,源地址是192.168.0.1的数据包
tcpdump -i eth0 src host 192.168.1.101
3、抓取所有经过eth0,目的地址是192.168.0.1的数据包
tcpdump -i eth0 dst host 192.168.1.101
4、抓取所有经过eth0,目的或源地址不是192.168.0.1的数据包(可以用!表示取反)
tcpdump -i eth0 host !192.168.1.101

端口过滤(常用)

1、抓取所有经过eth0,源端口或目的端口为80的包

1
tcpdump -i eth0 port 80

2、抓取所有经过eth0,源端口是为80的包

1
tcpdump -i eth0 src port 80

3、抓取所有经过eth0,目的端口为80的包

1
tcpdump -i eth0 dst port 80

#协议过滤
1、抓取所有经过eth0,tcp协议数据包

1
tcpdump -i eth0 tcp

2、抓取所有经过eth0,udp协议数据包

1
tcpdump -i eth0 udp

3、其他协议

1
2
3
tcpdump -i eth0 arp
tcpdump -i eth0 ip
tcpdump -i eth0 icmp

多个过滤条件之间可以用and或者or连接

1、抓取所有经过eth0,tcp协议并且端口为80的数据包

1
tcpdump  tcp and port 80

抓包实战

抓取mysql 3306端口数据包,看看mysql传输了那些数据包

1
tcpdump port 3306 -A

抓包部分数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.,P....b..'....SELECT @@session.transaction_read_only
21:49:34.891958 IP 120.78.162.155.mysql > 172.31.0.20.12558: Flags [P.], seq 6582:6634, ack 3636, win 65010, length 52
E..\.D@./.y;xN........1.M.<=v.M.P.......0..........(Rows matched: 1 Changed: 0 Warnings: 0
21:49:34.892100 IP 172.31.0.20.12558 > 120.78.162.155.mysql: Flags [P.], seq 3636:3647, ack 6634, win 65535, length 11
E..3..@.@.. ....xN..1...v.M.M.<qP....B.......commit
21:49:34.914180 IP 120.78.162.155.mysql > 172.31.0.20.12228: Flags [P.], seq 7096:7175, ack 3892, win 65535, length 79
.,....P...r........5....def....@@session.transaction_read_only..?................0...........
21:49:34.914343 IP 172.31.0.20.12228 > 120.78.162.155.mysql: Flags [P.], seq 3892:4288, ack 7175, win 65535, length 396
.{P............update cc_server
SET server_id = '2df43a93-689c-8ca0-4cf0-a1f82ff99a92',
device_id = '90878db0-b62a-9dab-8949-47ea698513f8',
server_brand = 'GDC ',
server_type = '',
server_no = 'A63203'
`status` = 1,
where id = 1
21:49:34.931906 IP 120.78.162.155.mysql > 172.31.0.20.12226: Flags [P.], seq 2957:3158, ack 2210, win 65010, length 201
E...Z0@./.).xN......../....r.A..P....`.......J....def.china_cinema
QRTZ_LOCKS
qrtz_locks
SCHED_NAME
SCHED_NAME.-.......P...H....def.china_cinema
QRTZ_LOCKS
qrtz_locks LOCK_NAME LOCK_NAME.-.......P........MyScheduler.TRIGGER_ACCESS...........
21:49:34.932229 IP 172.31.0.20.12226 > 120.78.162.155.mysql: Flags [P.], seq 2210:2378, ack 3158, win 65535, length 168
E...J.@.@.(.....xN../....A.....;P............UPDATE QRTZ_TRIGGERS SET TRIGGER_STATE = 'WAITING' WHERE SCHED_NAME = 'MyScheduler' AND JOB_NAME = 'JOB_18' AND JOB_GROUP = 'DEFAULT' AND TRIGGER_STATE = 'BLOCKED'
21:49:34.933501 IP 120.78.162.155.mysql > 172.31.0.20.12558: Flags [P.], seq 6634:6645, ack 3647, win 65010, length 11
E..3.E@./.ycxN........1.M.<qv.M.P...WJ.............
21:49:34.933635 IP 172.31.0.20.12558 > 120.78.162.155.mysql: Flags [P.], seq 3647:3668, ack 6645, win 65535, length 21
E..=..@.@.......xN..1...v.M.M.<|P....L.......SET autocommit=1

从中可以看到客户端发送到mysql的sql及mysql返回的数据,读者可以自己试一试,可以看到很多数据(注意网卡不要弄错了,否则抓取不到数据)

抓取redis端口6000数据包并分析redis协议

1
tcpdump port 6000 -A

抓包部分数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
22:02:21.236360 IP 172.31.0.20.37090 > 172.31.0.15.x11: Flags [P.], seq 396:429, ack 61, win 14600, length 33
E..I..@.@..............pX..n..LJP.9.X...*2
$4
RPOP
$12
xinhua_push2

22:02:21.236639 IP 172.31.0.15.x11 > 172.31.0.20.37090: Flags [P.], seq 61:66, ack 429, win 14600, length 5
E..-s)@.@.o@.........p....LJX...P.9.....$-1

22:02:21.236650 IP 172.31.0.20.37090 > 172.31.0.15.x11: Flags [.], ack 66, win 14600, length 0
E..(..@.@..............pX.....LOP.9. ...
22:02:21.236758 IP 172.31.0.20.23648 > 172.31.0.9.x11: Flags [P.], seq 396:429, ack 61, win 14600, length 33
E..IoW@.@.r........ \`.p...r7...P.9.X...*2
$4
RPOP
$12
xinhua_push3

22:02:21.236927 IP 172.31.0.9.x11 > 172.31.0.20.23648: Flags [P.], seq 61:66, ack 429, win 14600, length 5
E..-.;@.@..3... .....p\`7.......P.9.....$-1

22:02:21.236936 IP 172.31.0.20.23648 > 172.31.0.9.x11: Flags [.], ack 66, win 14600, length 0
E..(oX@.@.s........ \`.p....7...P.9.a...
22:02:21.237765 IP 172.31.0.20.37090 > 172.31.0.15.x11: Flags [P.], seq 429:462, ack 66, win 14600, length 33
E..I..@.@..............pX.....LOP.9.X...*2
$4
RPOP
$12
xinhua_push6

22:02:21.237981 IP 172.31.0.15.x11 > 172.31.0.20.37090: Flags [P.], seq 66:71, ack 462, win 14600, length 5
E..-s*@.@.o?.........p....LOX...P.9..l..$-1

22:02:21.238084 IP 172.31.0.20.23648 > 172.31.0.9.x11: Flags [P.], seq 429:462, ack 66, win 14600, length 33
E..IoY@.@.r........ \`.p....7...P.9.X...*2
$4
RPOP
$12
xinhua_push7

22:02:21.238222 IP 172.31.0.9.x11 > 172.31.0.20.23648: Flags [P.], seq 66:71, ack 462, win 14600, length 5
E..-.<@.@..2... .....p\`7.......P.9.....$-1

从中可以看到 redis的请求数据(返回数据也可以看,只是本次抓包没有返回数据),从中也可以看出redis的传输协议:
对其中一个请求分析(客户端执行的命令是:RPOP xinhua_push7):

1
2
3
4
5
*2
$4
RPOP
$12
xinhua_push7

*2 表示这个命令有3个参数
$4 表示第一个参数的长度是4
RPOP 表示定义长度为$4的参数
$12 表示第二个参数的长度是12
xinhua_push7 表示定义长度为$12的参数

redis协议resp其实非常简单,这里只做简单说明,读者有兴趣可以自行深入了解,也可以尝试写一个redis客户端。简单的协议也是redis高效的原因之一

简单的总结下

同理,可以用相同的方法分析http、kafka、zk等服务数据包的格式及了解其传输协议。学会抓包对学习TCP协议、tcp协议,排查网络异常问题有非常大的帮助
从数据包中可以看到tcp三次握手、四次挥手的全过程,有兴趣的读者可以去尝试一下

快速安装脚本

1、该脚本可快速一键安装Nginx(适用于CentOS,其他环境可自行调整脚本)
2、脚本内容请自行下载文件并查看
3、Nginx使用淘宝Tengine版本,下载链接,如果安装时有新版本,可以尝试替换下载链接为新版本
4、脚本安装临时目录在/data0/temp/目录,安装完成后可直接删除

1
wget -qO- http://cdn.laohand.com/sh/install_nginx.sh | sh

安装基础软件

一下为linux下常用的一些库文件,建议安装操作系统时安装以下库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
yum -y install pcre
yum -y install pcre-devel
yum -y install gcc gcc-c++
yum -y install autoconf
yum -y install libjpeg
yum -y install libjpeg-devel
yum -y install freetype
yum -y install freetype-devel
yum -y install libxml2
yum -y install libxml2-devel
yum -y install zlib
yum -y install zlib-devel
yum -y install glibc
yum -y install glibc-devel
yum -y install glib2
yum -y install glib2-devel
yum -y install bzip2
yum -y install bzip2-devel
yum -y install curl
yum -y install curl-devel
yum -y install make
yum -y install gd
yum -y install gd-devel
yum -y install gd2
yum -y install gd2-devel
yum -y install git
yum -y install unzip
yum -y install git
yum -y install cmake
yum -y install patch
yum -y install lrzsz
yum -y install iftop
yum -y install openssl-devel
yum -y install openssl
yum -y nss

安装脚本文件解析

注意:以下步骤为帮助读者理解脚本内容,因脚本里面会适用一些前后关联的变量,不能直接运行脚本,完整内容请下载
http://cdn.laohand.com/sh/install_nginx.sh

创建用户、获取系统内存信息

1、downloadFile函数用于下载文件,该方法在文件存在时不会重复下载文件,对于调试脚本多次运行时有帮助,另外在下载文件失败时终止程序
2、注意downloadFile下载文件不完整时,该方法无法检测并且不会重新下载,需要手动清理不完整文件(网络差时可能遇到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
tempPath="/data0/temp/"
mkdir ${tempPath}
cpuNum=$(cat /proc/cpuinfo | grep processor | wc -l)
function downloadFile(){
saveFilename=$1
downloadUrl=$2
if [ ! -f ${saveFilename} ];then
wget -O ${saveFilename} "${downloadUrl}"
if [ ! -f ${saveFilename} ];then
echo "${saveFilename} download fail url=>${saveFilename}"
exit
fi
fi
}
groupadd -f www
useradd -g www www

安装openssl

如果已经安装openssl,可忽略该步骤,使用https时需要openssl库,强烈建议安装

1
2
3
4
5
6
cd ${tempPath}
downloadFile openssl-1.0.2p.tar.gz https://www.openssl.org/source/openssl-1.0.2p.tar.gz
tar xzf openssl-1.0.2p.tar.gz
opensslDir=${tempPath}/openssl-1.0.2p
cd openssl-1.0.2p
./config --prefix=/usr && make -j ${cpuNum} && make install

安装jemalloc

该步骤可选,建议安装jemalloc优化nginx内存分配,如已安装可直接跳过

1
2
3
4
cd ${tempPath}
downloadFile jemalloc-5.1.0.tar.bz2 https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2
tar jxf jemalloc-5.1.0.tar.bz2
jemallocDir=${tempPath}/jemalloc-5.1.0

安装Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cd ${tempPath}
downloadFile tengine-2.2.3.tar.gz http://tengine.taobao.org/download/tengine-2.2.3.tar.gz
tar xzf tengine-2.2.3.tar.gz && cd tengine-2.2.3
./configure --prefix=/usr/local/nginx \
--user=www --group=www \
--with-jemalloc \
--with-ld-opt=-lrt \
--with-openssl=${opensslDir} \
--with-jemalloc=${jemallocDir} \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-pcre
make -j ${cpuNum} && make install

配置nginx主配置文件

该配置文件适用于比较通用的场景,如需要特殊配置,请自行修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
(cat << EOF
user www www;
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 102400;
error_log /data0/logs/nginx_error.log;
pid logs/nginx.pid;

events {
use epoll;
worker_connections 20480;
multi_accept on;
}

http {
include mime.types;
default_type application/octet-stream;
charset utf-8;
server_names_hash_bucket_size 256;
client_header_buffer_size 256k;
large_client_header_buffers 4 256k;
client_header_timeout 300s;
client_body_timeout 300s;
client_max_body_size 100m;
server_name_in_redirect off;
log_format main '\$remote_addr - \$remote_user [\$time_local] \$request \$status \$body_bytes_sent \$host \$upstream_addr \$upstream_response_time \$http_user_agent \$http_referer \$gzip_ratio';
access_log off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
keepalive_timeout 300;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

gzip on;
gzip_min_length 5k;
gzip_buffers 96 8k;
gzip_comp_level 5;
gzip_vary on;
gzip_static on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\." "Mozilla/4\.0[678]";
gzip_types text/xml text/plain text/css application/javascript application/x-javascript application/rss+xml image/jpeg image/gif image/png application/json;

open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

upstream unix_tmp_php_cgi_sock {
server unix:/dev/shm/php_fpm_1.sock;
server unix:/dev/shm/php_fpm_2.sock;
}
include vhost/*.conf;
}

EOF
) > /usr/local/nginx/conf/nginx.conf

生成默认的虚拟主机配置文件

在生成的默认虚拟主机文件中包含了常见场景的配置,可直接修改并用于生产环境,不用的配置文件直接删除即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
mkdir -p /usr/local/nginx/conf/vhost
(cat << EOF
server {
listen 80;
server_name api.laohand.com;
root /data0/dk/api/;
access_log /data0/logs/api.access.log main buffer=64k flush=15s;
location / {
index index.php index.html;
}
location ~ \.php\$ {
fastcgi_pass unix_tmp_php_cgi_sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}
EOF
) > /usr/local/nginx/conf/vhost/default_api.conf

(cat << EOF
server {
listen 80;
server_name cms.laohand.com;
root /data0/dk/cms;

access_log /data0/logs/cms.access.log main buffer=64k flush=15s;

location / {
index index.php index.html;
}

location ~ \.php\$ {
fastcgi_pass unix_tmp_php_cgi_sock;
fastcgi_index index.php;
include fastcgi.conf;
}

location /cms/{
index index.php;
if (!-e \$request_filename) {
rewrite ^/(.*)$ /cms/index.php?\$1 last;
break;
}
}
}
EOF
) > /usr/local/nginx/conf/vhost/default_cms.conf

(cat << EOF
server {
listen 80;
server_name cdn.laohand.com;
root /data0/dk/cdn;
access_log /data0/logs/cdn.access.log main buffer=64k flush=15s;
add_header 'Access-Control-Allow-Origin' '*';

location / {
index index.html;
}

location ~* ^.+.(gif|jpg|jpeg|png|mp4|flv|mov|avi|mp3|ts)\$ {
expires 300s;
}

location ~* ^.+.(html|js|css|m3u8)\$ {
expires 30s;
}
}
EOF
) > /usr/local/nginx/conf/vhost/default_cdn.conf

启动、重启Nginx

建议直接用命令维护nginx,以便更能掌握nginx,而且在所有操作系统中都适用

1
2
3
4
5
6
# 启动Nginx
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx/conf
# 检测Nginx配置文件
/usr/local/nginx/sbin/nginx -t
# 重启Nignx
/usr/local/nginx/sbin/nginx -s