迁移collector,合并到整个工程
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.gradle
|
||||
build/
|
||||
out/
|
||||
micronaut-cli.yml
|
||||
.mvn/
|
||||
mvnw
|
||||
mvnw.bat
|
||||
.log
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
207
LICENSE
Normal file
207
LICENSE
Normal file
@@ -0,0 +1,207 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the
|
||||
copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other
|
||||
entities that control, are controlled by, or are under common control with
|
||||
that entity. For the purposes of this definition, "control" means (i) the
|
||||
power, direct or indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (ii) ownership of
|
||||
fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
|
||||
ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation source,
|
||||
and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation
|
||||
or translation of a Source form, including but not limited to compiled
|
||||
object code, generated documentation, and conversions to
|
||||
other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object
|
||||
form, made available under the License, as indicated by a copyright notice
|
||||
that is included in or attached to the work (an example is provided in the
|
||||
Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent,
|
||||
as a whole, an original work of authorship. For the purposes of this
|
||||
License, Derivative Works shall not include works that remain separable
|
||||
from, or merely link (or bind by name) to the interfaces of, the Work and
|
||||
Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original
|
||||
version of the Work and any modifications or additions to that Work or
|
||||
Derivative Works thereof, that is intentionally submitted to Licensor for
|
||||
inclusion in the Work by the copyright owner or by an individual or
|
||||
Legal Entity authorized to submit on behalf of the copyright owner.
|
||||
For the purposes of this definition, "submitted" means any form of
|
||||
electronic, verbal, or written communication sent to the Licensor or its
|
||||
representatives, including but not limited to communication on electronic
|
||||
mailing lists, source code control systems, and issue tracking systems
|
||||
that are managed by, or on behalf of, the Licensor for the purpose of
|
||||
discussing and improving the Work, but excluding communication that is
|
||||
conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on
|
||||
behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor
|
||||
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
||||
royalty-free, irrevocable copyright license to reproduce, prepare
|
||||
Derivative Works of, publicly display, publicly perform, sublicense,
|
||||
and distribute the Work and such Derivative Works in
|
||||
Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor
|
||||
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
||||
royalty-free, irrevocable (except as stated in this section) patent
|
||||
license to make, have made, use, offer to sell, sell, import, and
|
||||
otherwise transfer the Work, where such license applies only to those
|
||||
patent claims licensable by such Contributor that are necessarily
|
||||
infringed by their Contribution(s) alone or by combination of their
|
||||
Contribution(s) with the Work to which such Contribution(s) was submitted.
|
||||
If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or
|
||||
contributory patent infringement, then any patent licenses granted to
|
||||
You under this License for that Work shall terminate as of the date such
|
||||
litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works
|
||||
thereof in any medium, with or without modifications, and in Source or
|
||||
Object form, provided that You meet the following conditions:
|
||||
|
||||
1. You must give any other recipients of the Work or Derivative Works a
|
||||
copy of this License; and
|
||||
|
||||
2. You must cause any modified files to carry prominent notices stating
|
||||
that You changed the files; and
|
||||
|
||||
3. You must retain, in the Source form of any Derivative Works that You
|
||||
distribute, all copyright, patent, trademark, and attribution notices from
|
||||
the Source form of the Work, excluding those notices that do not pertain
|
||||
to any part of the Derivative Works; and
|
||||
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable copy
|
||||
of the attribution notices contained within such NOTICE file, excluding
|
||||
those notices that do not pertain to any part of the Derivative Works,
|
||||
in at least one of the following places: within a NOTICE text file
|
||||
distributed as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or, within a
|
||||
display generated by the Derivative Works, if and wherever such
|
||||
third-party notices normally appear. The contents of the NOTICE file are
|
||||
for informational purposes only and do not modify the License.
|
||||
You may add Your own attribution notices within Derivative Works that You
|
||||
distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may
|
||||
provide additional or different license terms and conditions for use,
|
||||
reproduction, or distribution of Your modifications, or for any such
|
||||
Derivative Works as a whole, provided Your use, reproduction, and
|
||||
distribution of the Work otherwise complies with the conditions
|
||||
stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally
|
||||
submitted for inclusion in the Work by You to the Licensor shall be under
|
||||
the terms and conditions of this License, without any additional
|
||||
terms or conditions. Notwithstanding the above, nothing herein shall
|
||||
supersede or modify the terms of any separate license agreement you may
|
||||
have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor
|
||||
provides the Work (and each Contributor provides its Contributions)
|
||||
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any risks
|
||||
associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort
|
||||
(including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed
|
||||
to in writing, shall any Contributor be liable to You for damages,
|
||||
including any direct, indirect, special, incidental, or consequential
|
||||
damages of any character arising as a result of this License or out of
|
||||
the use or inability to use the Work (including but not limited to damages
|
||||
for loss of goodwill, work stoppage, computer failure or malfunction,
|
||||
or any and all other commercial damages or losses), even if such
|
||||
Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose
|
||||
to offer, and charge a fee for, acceptance of support, warranty,
|
||||
indemnity, or other liability obligations and/or rights consistent with
|
||||
this License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf of any
|
||||
other Contributor, and only if You agree to indemnify, defend, and hold
|
||||
each Contributor harmless for any liability incurred by, or claims
|
||||
asserted against, such Contributor by reason of your accepting any such
|
||||
warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included
|
||||
on the same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright tomsun28 usthe.com.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
6
README.md
Normal file
6
README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## monitor
|
||||
|
||||
- 提供监控管理服务
|
||||
- 提供监控任务下发服务
|
||||
- 提供监控数据查询服务
|
||||
- 告警服务
|
||||
18
alerter/pom.xml
Normal file
18
alerter/pom.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>monitor</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>alerter</artifactId>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
42
collector/README.md
Normal file
42
collector/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
### TanCloud Collector
|
||||
|
||||
* 操作系统
|
||||
* Linux
|
||||
* Windows
|
||||
* Ubuntu
|
||||
* CentOs
|
||||
* 数据库
|
||||
* Mysql
|
||||
* Oracle
|
||||
* PostgreSQL
|
||||
* 中间件
|
||||
* Kafka
|
||||
* Zookeeper
|
||||
* RocketMq
|
||||
* Etcd
|
||||
* 云原生
|
||||
* Docker
|
||||
* Kubernetes
|
||||
* Istio
|
||||
* 应用服务
|
||||
* Tomcat
|
||||
* Jetty
|
||||
* Http
|
||||
* Ping
|
||||
* 服务端口
|
||||
|
||||
#### HELP
|
||||
|
||||
1. ARK插件类隔离未生效
|
||||
> 注意需构建在jdk1.8环境中运行
|
||||
> 插件是否配置导入并配置正确
|
||||
> 本地DEBUG时需单独IDEA打开运行collector工程,不能将plugin和collector在同一工程打开运行
|
||||
|
||||
2. metaspace元空间内存占用多或溢出
|
||||
> 建议调整JVM参数 ```-Dsun.reflect.inflationThreshold=100000```
|
||||
> 由于使用太多反射,超过参数`inflationThreshold`默认值15阈值导致触发JVM反射优化(加快反射速度),
|
||||
> 反射获取类信息由使用*JNI存取器**膨胀(Inflation)*
|
||||
> 为*反射每个方法生成一个类加载器DelegatingClassLoader和Java类MethodAccessor*.
|
||||
> 动态加载的字节码导致PermGen持续增长.
|
||||
|
||||
|
||||
19
collector/plugins/pom.xml
Normal file
19
collector/plugins/pom.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>collector</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>plugins</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>sample-plugin</module>
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
||||
48
collector/plugins/sample-plugin/pom.xml
Normal file
48
collector/plugins/sample-plugin/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>plugins</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sample-plugin</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<!--link https://www.sofastack.tech/projects/sofa-boot/sofa-ark-ark-plugin/ -->
|
||||
<groupId>com.alipay.sofa</groupId>
|
||||
<artifactId>sofa-ark-plugin-maven-plugin</artifactId>
|
||||
<version>1.1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-cli</id>
|
||||
<goals>
|
||||
<goal>ark-plugin</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<!-- 配置优先级,数字越小,优先级越高,优先启动,优先导出类,默认1000 -->
|
||||
<priority>2000</priority>
|
||||
|
||||
<!-- 配置导出类、资源 -->
|
||||
<exported>
|
||||
<!-- 配置类级别导出类 -->
|
||||
<classes>
|
||||
<class>com.com.usthe.plugin.sample.ExportDemo</class>
|
||||
</classes>
|
||||
</exported>
|
||||
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.usthe.collector.plugin;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/10/8 15:12
|
||||
*/
|
||||
public class SameClass {
|
||||
|
||||
public static String hello() {
|
||||
return "hello plugin";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.usthe.plugin.sample;
|
||||
|
||||
import com.usthe.collector.plugin.SameClass;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/10/8 15:11
|
||||
*/
|
||||
public class ExportDemo {
|
||||
|
||||
public String hello() {
|
||||
return SameClass.hello();
|
||||
}
|
||||
}
|
||||
18
collector/pom.xml
Normal file
18
collector/pom.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>monitor</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>collector</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>server</module>
|
||||
<module>plugins</module>
|
||||
</modules>
|
||||
</project>
|
||||
118
collector/server/pom.xml
Normal file
118
collector/server/pom.xml
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>collector</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>server</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- isolation -->
|
||||
<dependency>
|
||||
<groupId>com.alipay.sofa</groupId>
|
||||
<artifactId>sofa-ark-springboot-starter</artifactId>
|
||||
<version>1.1.6</version>
|
||||
</dependency>
|
||||
<!-- common -->
|
||||
<dependency>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- etcd -->
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-core</artifactId>
|
||||
<version>0.5.10</version>
|
||||
</dependency>
|
||||
<!-- kafka -->
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
<!-- http -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<!-- lru hashmap -->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
|
||||
<artifactId>concurrentlinkedhashmap-lru</artifactId>
|
||||
<version>1.4.2</version>
|
||||
</dependency>
|
||||
<!-- 工具依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>31.0.1-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.aviator</groupId>
|
||||
<artifactId>aviator</artifactId>
|
||||
<version>5.2.7</version>
|
||||
</dependency>
|
||||
<!-- plugins -->
|
||||
<dependency>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<artifactId>sample-plugin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.alipay.sofa</groupId>
|
||||
<artifactId>sofa-ark-maven-plugin</artifactId>
|
||||
<version>1.1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-cli</id>
|
||||
|
||||
<!--goal executed to generate executable-ark-jar -->
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
<!--specify destination where executable-ark-jar will be saved, default saved to ${project.build.directory}-->
|
||||
<outputDirectory>./target</outputDirectory>
|
||||
|
||||
<!--default none-->
|
||||
<arkClassifier>executable</arkClassifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.usthe.collector;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* collector start
|
||||
* @author tomsun28
|
||||
* @date 2021/10/7 18:02
|
||||
*/
|
||||
@SpringBootApplication(exclude = {KafkaAutoConfiguration.class})
|
||||
@Slf4j
|
||||
public class Collector {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Collector.class, args);
|
||||
// DatagramSocket s = new DatagramSocket();
|
||||
//
|
||||
// /** 2、提供数据,封装打包 ---DatagramPacket(byte[] buf, int length, InetAddress address, int port) */
|
||||
//
|
||||
// byte[] bs = "正在使用UDP发送--我是数据! ".getBytes();
|
||||
// DatagramPacket dp = new DatagramPacket(bs, bs.length, InetAddress.getByName("192.168.1.189"), 8070);
|
||||
//
|
||||
// /** 3、使用send发送 */
|
||||
// try {
|
||||
// s.send(dp);
|
||||
// s.receive(dp)
|
||||
// } catch (IOException e) {
|
||||
// System.out.println("发送失败: ");
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.usthe.collector.collect;
|
||||
|
||||
|
||||
import com.usthe.common.entity.job.Metrics;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
|
||||
/**
|
||||
* 具体的指标组采集实现抽象类
|
||||
* @author tomsun28
|
||||
* @date 2021/11/4 9:35
|
||||
*/
|
||||
public abstract class AbstractCollect {
|
||||
|
||||
/**
|
||||
* 真正的采集实现接口
|
||||
* @param builder response builder
|
||||
* @param appId 应用监控ID
|
||||
* @param app 应用类型
|
||||
* @param metrics 指标组配置
|
||||
* return response builder
|
||||
*/
|
||||
public abstract void collect(CollectRep.MetricsData.Builder builder, long appId, String app, Metrics metrics);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.usthe.collector.collect.database;
|
||||
|
||||
import com.usthe.collector.common.cache.CacheIdentifier;
|
||||
import com.usthe.collector.common.cache.CommonCache;
|
||||
import com.usthe.collector.common.cache.support.CommonJdbcConnect;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 数据库JDBC通用查询
|
||||
* @author tomsun28
|
||||
* @date 2021/9/1 21:37
|
||||
*/
|
||||
@Slf4j
|
||||
public class JdbcCommonCollect {
|
||||
|
||||
|
||||
private Statement getConnection(String username, String password, String url) {
|
||||
CacheIdentifier identifier = CacheIdentifier.builder()
|
||||
.ip(url)
|
||||
.username(username).password(password).build();
|
||||
Optional<Object> cacheOption = CommonCache.getInstance().getCache(identifier, true);
|
||||
Statement statement = null;
|
||||
if (cacheOption.isPresent()) {
|
||||
CommonJdbcConnect jdbcConnect = (CommonJdbcConnect) cacheOption.get();
|
||||
try {
|
||||
statement = jdbcConnect.getConnection().createStatement();
|
||||
// 设置查询超时时间10秒
|
||||
statement.setQueryTimeout(10);
|
||||
// 设置查询最大行数1000行
|
||||
statement.setMaxRows(1000);
|
||||
} catch (Exception e) {
|
||||
log.info("The jdbc connect form cache, create statement error: {}", e.getMessage());
|
||||
try {
|
||||
if (statement != null) {
|
||||
statement.close();
|
||||
}
|
||||
jdbcConnect.close();
|
||||
statement = null;
|
||||
} catch (Exception e2) {
|
||||
log.error(e2.getMessage());
|
||||
}
|
||||
CommonCache.getInstance().removeCache(identifier);
|
||||
}
|
||||
}
|
||||
if (statement != null) {
|
||||
return statement;
|
||||
}
|
||||
// 复用失败则新建连接
|
||||
try {
|
||||
Connection connection = DriverManager.getConnection(url, username, password);
|
||||
statement = connection.createStatement();
|
||||
// 设置查询超时时间10秒
|
||||
statement.setQueryTimeout(10);
|
||||
// 设置查询最大行数1000行
|
||||
statement.setMaxRows(1000);
|
||||
CommonJdbcConnect jdbcConnect = new CommonJdbcConnect(connection);
|
||||
CommonCache.getInstance().addCache(identifier, jdbcConnect, 10000L);
|
||||
} catch (SQLException sqlException) {
|
||||
log.error("Jdbc sql error: {}, code: {}.", sqlException.getMessage(), sqlException.getErrorCode(), sqlException);
|
||||
} catch (Exception e) {
|
||||
log.error("Jdbc error: {}.", e.getMessage(), e);
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一行数据, 通过查询返回结果集的列名称,和查询的字段映射
|
||||
* eg:
|
||||
* 查询字段:one tow three four
|
||||
* 查询SQL:select one, tow, three, four from book limit 1;
|
||||
* @param statement 执行器
|
||||
* @param sql sql
|
||||
* @param columns 查询的列头(一般是数据库表字段,也可能包含特殊字段,eg: responseTime)
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private List<List<String>> queryOneRow(Statement statement, String sql, List<String> columns) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
statement.setMaxRows(1);
|
||||
ResultSet resultSet = statement.executeQuery(sql);
|
||||
List<List<String>> rowList = new LinkedList<>();
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
LinkedList<String> rows = new LinkedList<>();
|
||||
for (String column : columns) {
|
||||
if ("responseTime".equals(column)) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
rows.add(String.valueOf(time));
|
||||
} else {
|
||||
String value = resultSet.getString(column);
|
||||
value = value == null ? "" : value;
|
||||
rows.add(value);
|
||||
}
|
||||
}
|
||||
rowList.add(rows);
|
||||
}
|
||||
} finally {
|
||||
resultSet.close();
|
||||
}
|
||||
return rowList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一行数据, 通过查询的两列数据(key-value),key和查询的字段匹配,value为查询字段的值
|
||||
* eg:
|
||||
* 查询字段:one tow three four
|
||||
* 查询SQL:select key, value from book;
|
||||
* 返回的key映射查询字段
|
||||
* @param statement 执行器
|
||||
* @param sql sql
|
||||
* @param columns 查询的列头(一般是数据库表字段,也可能包含特殊字段,eg: responseTime)
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private List<List<String>> queryOneRowByMatchTwoColumns(Statement statement, String sql, List<String> columns) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
ResultSet resultSet = statement.executeQuery(sql);
|
||||
List<List<String>> rowList = new LinkedList<>();
|
||||
try {
|
||||
HashMap<String, String> values = new HashMap<>(columns.size());
|
||||
while (resultSet.next()) {
|
||||
if (resultSet.getString(1) != null) {
|
||||
values.put(resultSet.getString(1).toLowerCase(), resultSet.getString(2));
|
||||
}
|
||||
}
|
||||
LinkedList<String> rows = new LinkedList<>();
|
||||
for (String column : columns) {
|
||||
if ("responseTime".equals(column)) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
rows.add(String.valueOf(time));
|
||||
} else {
|
||||
String value = values.get(column.toLowerCase());
|
||||
value = value == null ? "" : value;
|
||||
rows.add(value);
|
||||
}
|
||||
}
|
||||
rowList.add(rows);
|
||||
} finally {
|
||||
resultSet.close();
|
||||
}
|
||||
return rowList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询多行数据, 通过查询返回结果集的列名称,和查询的字段映射
|
||||
* eg:
|
||||
* 查询字段:one tow three four
|
||||
* 查询SQL:select one, tow, three, four from book limit 1;
|
||||
* @param statement 执行器
|
||||
* @param sql sql
|
||||
* @param columns 查询的列头(一般是数据库表字段,也可能包含特殊字段,eg: responseTime)
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
private List<List<String>> queryMultiRow(Statement statement, String sql, List<String> columns) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
ResultSet resultSet = statement.executeQuery(sql);
|
||||
List<List<String>> rowList = new LinkedList<>();
|
||||
try {
|
||||
while (resultSet.next()) {
|
||||
LinkedList<String> rows = new LinkedList<>();
|
||||
for (String column : columns) {
|
||||
if ("responseTime".equals(column)) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
rows.add(String.valueOf(time));
|
||||
} else {
|
||||
String value = resultSet.getString(column);
|
||||
value = value == null ? "" : value;
|
||||
rows.add(value);
|
||||
}
|
||||
}
|
||||
rowList.add(rows);
|
||||
}
|
||||
} finally {
|
||||
resultSet.close();
|
||||
}
|
||||
return rowList;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.usthe.collector.collect.http;
|
||||
|
||||
import com.usthe.collector.collect.AbstractCollect;
|
||||
import com.usthe.collector.common.http.HttpClientPool;
|
||||
import com.usthe.collector.dispatch.DispatchConstants;
|
||||
import com.usthe.common.entity.job.Metrics;
|
||||
import com.usthe.common.entity.job.protocol.HttpProtocol;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* http https 采集实现类
|
||||
* @author tomsun28
|
||||
* @date 2021/11/4 15:37
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
private HttpCollectImpl() {}
|
||||
|
||||
public static HttpCollectImpl getInstance() {
|
||||
return Singleton.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder,
|
||||
long appId, String app, Metrics metrics) {
|
||||
// 简单校验必有参数
|
||||
if (metrics == null || metrics.getHttp() == null) {
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Http/Https collect must has http params");
|
||||
return;
|
||||
}
|
||||
HttpContext httpContext = createHttpContext(metrics.getHttp());
|
||||
HttpUriRequest request = createHttpRequest(metrics.getHttp());
|
||||
try {
|
||||
CloseableHttpResponse response = HttpClientPool.getHttpClient()
|
||||
.execute(request, httpContext);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.debug("http response status: {}", statusCode);
|
||||
if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) {
|
||||
// 1XX 3XX 4XX 5XX 状态码 失败
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("statusCode: " + statusCode);
|
||||
return;
|
||||
} else {
|
||||
// 2xx 状态码 成功
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
// 根据不同的解析方式解析
|
||||
if (resp == null || "".equals(resp)) {
|
||||
log.info("http response entity is empty, status: {}.", statusCode);
|
||||
builder.setCode(CollectRep.Code.SUCCESS);
|
||||
builder.setMsg("statusCode: " + statusCode + ",entity empty.");
|
||||
return;
|
||||
}
|
||||
String parseType = metrics.getHttp().getParseType();
|
||||
try {
|
||||
if (DispatchConstants.PARSE_DEFAULT.equals(parseType)) {
|
||||
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
} else if (DispatchConstants.PARSE_PROMETHEUS.equals(parseType)) {
|
||||
parseResponseByPrometheus(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
} else if (DispatchConstants.PARSE_JSON_PATH.equals(parseType)) {
|
||||
parseResponseByJsonPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
} else if (DispatchConstants.PARSE_XML_PATH.equals(parseType)) {
|
||||
parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
} else {
|
||||
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (ClientProtocolException e1) {
|
||||
log.error(e1.getMessage(), e1);
|
||||
} catch (UnknownHostException e2) {
|
||||
// 对端不可达
|
||||
log.info(e2.getMessage());
|
||||
builder.setCode(CollectRep.Code.UN_REACHABLE);
|
||||
builder.setMsg("unknown host");
|
||||
return;
|
||||
} catch (InterruptedIOException | ConnectException | SSLException e3) {
|
||||
// 对端连接失败
|
||||
log.info(e3.getMessage());
|
||||
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
|
||||
builder.setMsg(e3.getMessage());
|
||||
return;
|
||||
} catch (IOException e4) {
|
||||
// 其它IO异常
|
||||
log.info(e4.getMessage());
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(e4.getMessage());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// 其它异常
|
||||
log.error(e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg(e.getMessage());
|
||||
return;
|
||||
} finally {
|
||||
if (request != null) {
|
||||
request.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResponseByXmlPath(String resp, List<String> preFields, HttpProtocol http, CollectRep.MetricsData.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
private void parseResponseByJsonPath(String resp, List<String> preFields, HttpProtocol http, CollectRep.MetricsData.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
private void parseResponseByPrometheus(String resp, List<String> preFields, HttpProtocol http, CollectRep.MetricsData.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
private void parseResponseByDefault(String resp, List<String> preFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建httpContext
|
||||
* @param httpProtocol http protocol
|
||||
* @return context
|
||||
*/
|
||||
private HttpContext createHttpContext(HttpProtocol httpProtocol) {
|
||||
HttpProtocol.Authorization auth = httpProtocol.getAuthorization();
|
||||
if (auth != null && !DispatchConstants.BEARER_TOKEN.equals(auth.getType())) {
|
||||
HttpClientContext clientContext = new HttpClientContext();
|
||||
if (DispatchConstants.BASIC_AUTH.equals(auth.getType())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials
|
||||
= new UsernamePasswordCredentials(auth.getBasicAuthUsername(), auth.getBasicAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
} else if (DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials
|
||||
= new UsernamePasswordCredentials(auth.getBasicAuthUsername(), auth.getBasicAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
} else {
|
||||
clientContext = null;
|
||||
}
|
||||
return clientContext;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据http配置参数构造请求头
|
||||
* @param httpProtocol http参数配置
|
||||
* @return 请求体
|
||||
*/
|
||||
private HttpUriRequest createHttpRequest(HttpProtocol httpProtocol) {
|
||||
RequestBuilder requestBuilder;
|
||||
// method
|
||||
if (HttpMethod.GET.matches(httpProtocol.getMethod())) {
|
||||
requestBuilder = RequestBuilder.get();
|
||||
} else if (HttpMethod.POST.matches(httpProtocol.getMethod())) {
|
||||
requestBuilder = RequestBuilder.post();
|
||||
} else if (HttpMethod.PUT.matches(httpProtocol.getMethod())) {
|
||||
requestBuilder = RequestBuilder.put();
|
||||
} else if (HttpMethod.DELETE.matches(httpProtocol.getMethod())) {
|
||||
requestBuilder = RequestBuilder.delete();
|
||||
} else if (HttpMethod.PATCH.matches(httpProtocol.getMethod())) {
|
||||
requestBuilder = RequestBuilder.patch();
|
||||
} else {
|
||||
// not support the method
|
||||
log.error("not support the http method: {}.", httpProtocol.getMethod());
|
||||
return null;
|
||||
}
|
||||
// params
|
||||
Map<String, String> params = httpProtocol.getParams();
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
requestBuilder.addParameter(param.getKey(), param.getValue());
|
||||
}
|
||||
}
|
||||
// headers
|
||||
Map<String, String> headers = httpProtocol.getHeaders();
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
// keep-alive
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, "keep-alive");
|
||||
// add accept
|
||||
if (DispatchConstants.PARSE_DEFAULT.equals(httpProtocol.getParseType())
|
||||
|| DispatchConstants.PARSE_JSON_PATH.equals(httpProtocol.getParseType())) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
} else if (DispatchConstants.PARSE_XML_PATH.equals(httpProtocol.getParseType())) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, "text/xml,application/xml");
|
||||
} else if (DispatchConstants.PARSE_PROMETHEUS.equals(httpProtocol.getParseType())) {
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT, DispatchConstants.PARSE_PROMETHEUS_ACCEPT);
|
||||
requestBuilder.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
|
||||
}
|
||||
|
||||
// 判断是否使用Bearer Token认证
|
||||
if (httpProtocol.getAuthorization() != null
|
||||
&& DispatchConstants.BEARER_TOKEN.equals(httpProtocol.getAuthorization().getType())) {
|
||||
// 若使用 将token放入到header里面
|
||||
String value = DispatchConstants.BEARER + " " + httpProtocol.getAuthorization().getBearerTokenToken();
|
||||
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
|
||||
}
|
||||
// todo 处理请求内容 body 暂不支持body
|
||||
|
||||
// uri
|
||||
requestBuilder.setUri(httpProtocol.getUrl());
|
||||
return requestBuilder.build();
|
||||
}
|
||||
|
||||
private static class Singleton {
|
||||
private static final HttpCollectImpl INSTANCE = new HttpCollectImpl();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.usthe.collector.common;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* java common 的配置属性
|
||||
* @author tomsun28
|
||||
* @date 2021/10/16 14:23
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "collector.common")
|
||||
public class CollectorProperties {
|
||||
|
||||
}
|
||||
14
collector/server/src/main/java/com/usthe/collector/common/cache/CacheCloseable.java
vendored
Normal file
14
collector/server/src/main/java/com/usthe/collector/common/cache/CacheCloseable.java
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.usthe.collector.common.cache;
|
||||
|
||||
/**
|
||||
* 连接资源关闭回调接口
|
||||
* @author tomsun28
|
||||
* @date 2021/9/1 21:03
|
||||
*/
|
||||
public interface CacheCloseable {
|
||||
|
||||
/**
|
||||
* 在缓存remove掉此对象前,回调接口对连接对象进行相关资源的释放
|
||||
*/
|
||||
void close();
|
||||
}
|
||||
15
collector/server/src/main/java/com/usthe/collector/common/cache/CacheDetectable.java
vendored
Normal file
15
collector/server/src/main/java/com/usthe/collector/common/cache/CacheDetectable.java
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.usthe.collector.common.cache;
|
||||
|
||||
/**
|
||||
* 缓存可用性检测接口
|
||||
* @author tomsun28
|
||||
* @date 2020-08-10 23:21
|
||||
*/
|
||||
public interface CacheDetectable {
|
||||
|
||||
/**
|
||||
* 可用性探测,探测缓存对象资源的可用性,若不可用,上层可删除此缓存对象
|
||||
* @return true 可用, false 不可用
|
||||
*/
|
||||
boolean available();
|
||||
}
|
||||
23
collector/server/src/main/java/com/usthe/collector/common/cache/CacheIdentifier.java
vendored
Normal file
23
collector/server/src/main/java/com/usthe/collector/common/cache/CacheIdentifier.java
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.usthe.collector.common.cache;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 缓存key唯一标识符
|
||||
* @author tomsun28
|
||||
* @date 2021/9/1 21:30
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class CacheIdentifier {
|
||||
|
||||
private String ip;
|
||||
|
||||
private String port;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
}
|
||||
218
collector/server/src/main/java/com/usthe/collector/common/cache/CommonCache.java
vendored
Normal file
218
collector/server/src/main/java/com/usthe/collector/common/cache/CommonCache.java
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
package com.usthe.collector.common.cache;
|
||||
|
||||
|
||||
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* lru cache 对连接对象进行缓存
|
||||
* @author tomsun28
|
||||
* @date 2020-08-10 23:17
|
||||
*/
|
||||
@Slf4j
|
||||
public class CommonCache {
|
||||
|
||||
/**
|
||||
* 默认缓存时间 30minute
|
||||
*/
|
||||
private static final long DEFAULT_CACHE_TIMEOUT = 30 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* 默认最大缓存数量
|
||||
*/
|
||||
private static final int DEFAULT_MAX_CAPACITY = 10000;
|
||||
|
||||
/**
|
||||
* cacheTime数组大小
|
||||
*/
|
||||
private static final int CACHE_TIME_LENGTH = 2;
|
||||
|
||||
/**
|
||||
* 存储对象的数据过期时间点
|
||||
*/
|
||||
private Map<Object, Long[]> timeoutMap;
|
||||
|
||||
/**
|
||||
* 存储缓存对象
|
||||
*/
|
||||
private ConcurrentLinkedHashMap<Object, Object> cacheMap;
|
||||
|
||||
/**
|
||||
* 过期数据清理线程池
|
||||
*/
|
||||
private ThreadPoolExecutor cleanTimeoutExecutor;
|
||||
|
||||
private CommonCache() { init();}
|
||||
|
||||
/**
|
||||
* 初始化 cache
|
||||
*/
|
||||
private void init() {
|
||||
// 初始化lru hashmap
|
||||
cacheMap = new ConcurrentLinkedHashMap
|
||||
.Builder<>()
|
||||
.maximumWeightedCapacity(DEFAULT_MAX_CAPACITY)
|
||||
.listener((key, value) -> {
|
||||
timeoutMap.remove(key);
|
||||
if (value instanceof CacheCloseable) {
|
||||
((CacheCloseable)value).close();
|
||||
}
|
||||
log.info("lru cache discard key: {}, value: {}.", key, value);
|
||||
}).build();
|
||||
|
||||
// 初始化时间纪录map
|
||||
timeoutMap = new ConcurrentHashMap<>(DEFAULT_MAX_CAPACITY >> 6);
|
||||
|
||||
// 初始化过期数据清理线程池
|
||||
cleanTimeoutExecutor = new ThreadPoolExecutor(1, 1,
|
||||
1, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(1), r -> new Thread("lru-cache-timeout-cleaner"),
|
||||
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||
|
||||
// 初始化可用性探测定位任务,每次探测间隔时间为20分钟
|
||||
ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1,
|
||||
r -> new Thread(r, "lru-cache-available-detector"));
|
||||
scheduledExecutor.scheduleWithFixedDelay(this::detectCacheAvailable,
|
||||
2,20, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 探测所有可探测的缓存对象的可用性,清除不可用和过期对象
|
||||
*/
|
||||
private void detectCacheAvailable() {
|
||||
try {
|
||||
cacheMap.forEach((key, value) -> {
|
||||
// 先判断是否过期
|
||||
Long[] cacheTime = timeoutMap.get(key);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH
|
||||
|| cacheTime[0] + cacheTime[1] < currentTime) {
|
||||
cacheMap.remove(key);
|
||||
timeoutMap.remove(key);
|
||||
if (value instanceof CacheCloseable) {
|
||||
((CacheCloseable)value).close();
|
||||
}
|
||||
|
||||
} // 对实现了CacheDetectable 的对象探测检查
|
||||
else if (value instanceof CacheDetectable && !((CacheDetectable)value).available()) {
|
||||
cacheMap.remove(key);
|
||||
timeoutMap.remove(key);
|
||||
if (value instanceof CacheCloseable) {
|
||||
((CacheCloseable)value).close();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("detect cache available error: {}.", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期线程
|
||||
*/
|
||||
private void cleanTimeoutCache() {
|
||||
try {
|
||||
cacheMap.forEach((key, value) -> {
|
||||
// index 0 is startTime, 1 is timeDiff
|
||||
Long[] cacheTime = timeoutMap.get(key);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH) {
|
||||
timeoutMap.put(key, new Long[]{currentTime, DEFAULT_CACHE_TIMEOUT});
|
||||
} else if (cacheTime[0] + cacheTime[1] < currentTime) {
|
||||
// 过期了 discard 关闭这个cache的资源
|
||||
timeoutMap.remove(key);
|
||||
cacheMap.remove(key);
|
||||
if (value instanceof CacheCloseable) {
|
||||
((CacheCloseable)value).close();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("clean timeout cache error: {}.", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或更新cache
|
||||
* @param key 存储对象key
|
||||
* @param value 存储对象
|
||||
* @param timeDiff 缓存对象保存时间 millis
|
||||
*/
|
||||
public void addCache(Object key, Object value, Long timeDiff) {
|
||||
if (timeDiff == null) {
|
||||
timeDiff = DEFAULT_CACHE_TIMEOUT;
|
||||
}
|
||||
cacheMap.put(key, value);
|
||||
timeoutMap.put(key, new Long[]{System.currentTimeMillis(), timeDiff});
|
||||
cleanTimeoutExecutor.execute(() -> {
|
||||
try {
|
||||
cleanTimeoutCache();
|
||||
Thread.sleep(10 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缓存key获取缓存对象
|
||||
* @param key key
|
||||
* @param refreshCache 是否刷新命中的缓存的存活时间 true是,false否
|
||||
* @return 缓存对象
|
||||
*/
|
||||
public Optional<Object> getCache(Object key, boolean refreshCache) {
|
||||
Long[] cacheTime = timeoutMap.get(key);
|
||||
if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (cacheTime[0] + cacheTime[1] < System.currentTimeMillis()) {
|
||||
timeoutMap.remove(key);
|
||||
cacheMap.remove(key);
|
||||
return Optional.empty();
|
||||
}
|
||||
Object value = cacheMap.get(key);
|
||||
if (value == null) {
|
||||
cacheMap.remove(key);
|
||||
timeoutMap.remove(key);
|
||||
} else if (refreshCache) {
|
||||
cacheTime[0] = System.currentTimeMillis();
|
||||
timeoutMap.put(key, cacheTime);
|
||||
}
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缓存key删除缓存对象
|
||||
* @param key key
|
||||
*/
|
||||
public void removeCache(Object key) {
|
||||
timeoutMap.remove(key);
|
||||
cacheMap.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存实例
|
||||
* @return cache
|
||||
*/
|
||||
public static CommonCache getInstance() {
|
||||
return SingleInstance.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态内部类
|
||||
*/
|
||||
private static class SingleInstance {
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
private static final CommonCache INSTANCE= new CommonCache();
|
||||
}
|
||||
}
|
||||
55
collector/server/src/main/java/com/usthe/collector/common/cache/support/CommonJdbcConnect.java
vendored
Normal file
55
collector/server/src/main/java/com/usthe/collector/common/cache/support/CommonJdbcConnect.java
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.usthe.collector.common.cache.support;
|
||||
|
||||
import com.usthe.collector.common.cache.CacheCloseable;
|
||||
import com.usthe.collector.common.cache.CacheDetectable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/9/1 21:24
|
||||
*/
|
||||
@Slf4j
|
||||
public class CommonJdbcConnect implements CacheCloseable, CacheDetectable {
|
||||
|
||||
private Connection connection;
|
||||
|
||||
public CommonJdbcConnect(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("close jdbc connect error: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean available() {
|
||||
if (connection == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return !connection.isClosed();
|
||||
} catch (Exception e) {
|
||||
log.error("detect jdbc connect error: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.usthe.collector.common.http;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.HttpRequestRetryHandler;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.client.RequestWrapper;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.Args;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* http request网络请求重试处理器,替换默认的 DefaultHttpRequestRetryHandler
|
||||
* 判断是否需要重试请求
|
||||
* @author tomsun28
|
||||
* @date 2021/8/30 22:00
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomHttpRequestRetryHandler implements HttpRequestRetryHandler {
|
||||
|
||||
/**
|
||||
* 不需要重试的异常情况
|
||||
*/
|
||||
private final static Set<Class<? extends IOException>> NON_RETRY_CLASSES = new HashSet<>(Arrays.asList(
|
||||
// io 中断
|
||||
InterruptedIOException.class,
|
||||
// 对端不可达
|
||||
UnknownHostException.class,
|
||||
// 连接异常
|
||||
ConnectException.class,
|
||||
// ssl异常
|
||||
SSLException.class));
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private static final int MAX_RETRY_TIMES = 3;
|
||||
|
||||
@Override
|
||||
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
|
||||
Args.notNull(exception, "Exception parameter");
|
||||
Args.notNull(context, "HTTP context");
|
||||
if (executionCount > MAX_RETRY_TIMES) {
|
||||
return false;
|
||||
}
|
||||
if (NON_RETRY_CLASSES.contains(exception.getClass())) {
|
||||
return false;
|
||||
}
|
||||
for (final Class<? extends IOException> rejectException : NON_RETRY_CLASSES) {
|
||||
if (rejectException.isInstance(exception)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// SocketTimeoutException: Read timed 只做一次重试
|
||||
// read time out 可能是对端不支持长连接 重试前清理边空闲无效连接
|
||||
if (executionCount == 1 && exception instanceof SocketTimeoutException && exception.getMessage() != null
|
||||
&& (exception.getMessage().contains("Read timed out")
|
||||
|| exception.getMessage().contains("Request already aborted"))) {
|
||||
// 清理连接池无效空闲连接
|
||||
HttpClientPool.getConnectionManager().closeExpiredConnections();
|
||||
return true;
|
||||
}
|
||||
|
||||
final HttpClientContext clientContext = HttpClientContext.adapt(context);
|
||||
final HttpRequest request = clientContext.getRequest();
|
||||
|
||||
if(requestIsAborted(request)){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handleAsIdempotent(request)) {
|
||||
// Retry if the request is considered idempotent
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!clientContext.isRequestSent()) {
|
||||
// Retry if the request has not been sent fully or
|
||||
// if it's OK to retry methods that have been sent
|
||||
return true;
|
||||
}
|
||||
// otherwise do not retry
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
protected boolean handleAsIdempotent(final HttpRequest request) {
|
||||
return !(request instanceof HttpEntityEnclosingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*
|
||||
* @deprecated (4.3)
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean requestIsAborted(final HttpRequest request) {
|
||||
HttpRequest req = request;
|
||||
if (request instanceof RequestWrapper) {
|
||||
// does not forward request to original
|
||||
req = ((RequestWrapper) request).getOriginal();
|
||||
}
|
||||
return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.usthe.collector.common.http;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.HttpRequestRetryHandler;
|
||||
import org.apache.http.client.config.CookieSpecs;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.Lookup;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.util.PublicSuffixMatcher;
|
||||
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
|
||||
import org.apache.http.cookie.CookieSpecProvider;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.impl.cookie.DefaultCookieSpecProvider;
|
||||
import org.apache.http.impl.cookie.IgnoreSpecProvider;
|
||||
import org.apache.http.impl.cookie.NetscapeDraftSpecProvider;
|
||||
import org.apache.http.impl.cookie.RFC6265CookieSpecProvider;
|
||||
import org.apache.http.impl.cookie.RFC6265CookieSpecProvider.CompatibilityLevel;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 统一的http客户端连接池
|
||||
* @author tomsun28
|
||||
* @date 2021/8/30 21:23
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpClientPool {
|
||||
|
||||
private static CloseableHttpClient httpClient;
|
||||
|
||||
private static PoolingHttpClientConnectionManager connectionManager;
|
||||
|
||||
/**
|
||||
* 此连接池所能提供的最大连接数
|
||||
*/
|
||||
private final static int MAX_TOTAL_CONNECTIONS = 50000;
|
||||
|
||||
/**
|
||||
* 每个路由所能分配的最大连接数
|
||||
*/
|
||||
private final static int MAX_PER_ROUTE_CONNECTIONS = 80;
|
||||
|
||||
/**
|
||||
* 从连接池中获取连接的默认超时时间 4秒
|
||||
*/
|
||||
private final static int REQUIRE_CONNECT_TIMEOUT = 4000;
|
||||
|
||||
/**
|
||||
* 双端建立连接超时时间 4秒
|
||||
*/
|
||||
private final static int CONNECT_TIMEOUT = 4000;
|
||||
|
||||
/**
|
||||
* socketReadTimeout 响应tcp报文的最大间隔超时时间
|
||||
*/
|
||||
private final static int SOCKET_TIMEOUT = 60000;
|
||||
|
||||
/**
|
||||
* 空闲连接免检的有效时间,被重用的空闲连接若超过此时间,需检查此连接的可用性
|
||||
*/
|
||||
private final static int INACTIVITY_VALIDATED_TIME = 10000;
|
||||
|
||||
/**
|
||||
* ssl版本
|
||||
*/
|
||||
private final static String[] SUPPORTED_SSL = {"TLSv1","TLSv1.1","TLSv1.2","SSLv3"};
|
||||
|
||||
static {
|
||||
try {
|
||||
// 初始化ssl上下文
|
||||
SSLContext sslContext = SSLContexts.createDefault();
|
||||
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
};
|
||||
sslContext.init(null, new TrustManager[]{x509TrustManager}, null);
|
||||
// 设置支持的ssl版本
|
||||
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, SUPPORTED_SSL, null, new NoopHostnameVerifier());
|
||||
// 注册 http https
|
||||
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register("http", PlainConnectionSocketFactory.INSTANCE)
|
||||
.register("https", sslFactory)
|
||||
.build();
|
||||
// 网络请求默认配置
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
// 从连接池获取连接超时时间
|
||||
.setConnectionRequestTimeout(REQUIRE_CONNECT_TIMEOUT)
|
||||
// 和对端新连接建立时间,三次握手时间
|
||||
.setConnectTimeout(CONNECT_TIMEOUT)
|
||||
// 数据传输最大响应间隔时间
|
||||
.setSocketTimeout(SOCKET_TIMEOUT)
|
||||
// 遇到301 302不自动重定向跳转
|
||||
.setRedirectsEnabled(false)
|
||||
.build();
|
||||
// 连接池
|
||||
connectionManager = new PoolingHttpClientConnectionManager(registry);
|
||||
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
|
||||
connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE_CONNECTIONS);
|
||||
connectionManager.setValidateAfterInactivity(INACTIVITY_VALIDATED_TIME);
|
||||
// 请求网络异常重试处理器
|
||||
HttpRequestRetryHandler requestRetryHandler = new CustomHttpRequestRetryHandler();
|
||||
// cookie处理策略
|
||||
PublicSuffixMatcher suffixMatcher = PublicSuffixMatcherLoader.getDefault();
|
||||
CookieSpecProvider defaultCookieSpecProvider = new DefaultCookieSpecProvider(suffixMatcher);
|
||||
CookieSpecProvider laxStandardProvider = new RFC6265CookieSpecProvider(CompatibilityLevel.RELAXED, suffixMatcher);
|
||||
CookieSpecProvider strictStandardProvider = new RFC6265CookieSpecProvider(CompatibilityLevel.STRICT, suffixMatcher);
|
||||
Lookup<CookieSpecProvider> cookieSpecProviderLookup = RegistryBuilder.<CookieSpecProvider>create()
|
||||
.register(CookieSpecs.DEFAULT, defaultCookieSpecProvider)
|
||||
.register("best-match", defaultCookieSpecProvider)
|
||||
.register("compatibility", defaultCookieSpecProvider)
|
||||
.register(CookieSpecs.STANDARD, laxStandardProvider)
|
||||
.register(CookieSpecs.STANDARD_STRICT, strictStandardProvider)
|
||||
.register(CookieSpecs.NETSCAPE, new NetscapeDraftSpecProvider())
|
||||
.register(CookieSpecs.IGNORE_COOKIES, new IgnoreSpecProvider())
|
||||
.register(IgnoreReqCookieSpec.COOKIE_SPEC_NAME, new IgnoreReqCookieSpecProvider(suffixMatcher))
|
||||
.build();
|
||||
// 构造单例 httpClient
|
||||
httpClient = HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.setRetryHandler(requestRetryHandler)
|
||||
.setDefaultCookieSpecRegistry(cookieSpecProviderLookup)
|
||||
// 定期清理不可用过期连接
|
||||
.evictExpiredConnections()
|
||||
// 定期清理可用但空闲的连接
|
||||
.evictIdleConnections(100, TimeUnit.SECONDS)
|
||||
.build();
|
||||
// 构造连接清理器
|
||||
Thread connectCleaner = new Thread(() -> {
|
||||
while (Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(30000);
|
||||
connectionManager.closeExpiredConnections();
|
||||
connectionManager.closeIdleConnections(100, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
connectCleaner.setName("HttpConnectCleaner");
|
||||
connectCleaner.setDaemon(true);
|
||||
connectCleaner.start();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static CloseableHttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public static PoolingHttpClientConnectionManager getConnectionManager() {
|
||||
return connectionManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.usthe.collector.common.http;
|
||||
|
||||
import org.apache.http.cookie.Cookie;
|
||||
import org.apache.http.cookie.CookieOrigin;
|
||||
import org.apache.http.impl.cookie.DefaultCookieSpec;
|
||||
|
||||
/**
|
||||
* 在request请求时忽略携带cookie,返回response时更新cookie的策略
|
||||
* @author tomsun28
|
||||
* @date 2021/8/30 22:37
|
||||
*/
|
||||
public class IgnoreReqCookieSpec extends DefaultCookieSpec {
|
||||
|
||||
public static final String COOKIE_SPEC_NAME = "IGNORE_REQUEST_COOKIE";
|
||||
|
||||
public IgnoreReqCookieSpec() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Cookie cookie, CookieOrigin origin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return COOKIE_SPEC_NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.usthe.collector.common.http;
|
||||
|
||||
import org.apache.http.conn.util.PublicSuffixMatcher;
|
||||
import org.apache.http.cookie.CookieSpec;
|
||||
import org.apache.http.impl.cookie.DefaultCookieSpecProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* IGNORE_REQUEST_COOKIE cookie策略提供者
|
||||
* @author tomsun28
|
||||
* @date 2021/8/30 22:40
|
||||
*/
|
||||
public class IgnoreReqCookieSpecProvider extends DefaultCookieSpecProvider {
|
||||
|
||||
private volatile CookieSpec cookieSpec;
|
||||
|
||||
public IgnoreReqCookieSpecProvider(PublicSuffixMatcher publicSuffixMatcher) {
|
||||
super(publicSuffixMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CookieSpec create(HttpContext context) {
|
||||
if (cookieSpec == null) {
|
||||
synchronized (this) {
|
||||
if (cookieSpec == null) {
|
||||
this.cookieSpec = new IgnoreReqCookieSpec();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.cookieSpec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
|
||||
import com.usthe.collector.dispatch.timer.WheelTimerJob;
|
||||
import com.usthe.common.entity.job.Metrics;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
|
||||
/**
|
||||
* 采集数据调度器接口
|
||||
* @author tomsun28
|
||||
* @date 2021/11/2 11:20
|
||||
*/
|
||||
public interface CollectDataDispatch {
|
||||
|
||||
/**
|
||||
* 处理分发采集结果数据
|
||||
* @param timerJob 时间轮任务
|
||||
* @param metrics 下面的指标组采集任务
|
||||
* @param metricsData 采集结果数据
|
||||
*/
|
||||
void dispatchCollectData(WheelTimerJob timerJob, Metrics metrics, CollectRep.MetricsData metricsData);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import com.usthe.collector.dispatch.export.KafkaDataExporter;
|
||||
import com.usthe.collector.dispatch.timer.TimerDispatch;
|
||||
import com.usthe.collector.dispatch.timer.WheelTimerJob;
|
||||
import com.usthe.common.entity.job.Job;
|
||||
import com.usthe.common.entity.job.Metrics;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 指标组采集任务与响应数据调度器
|
||||
* @author tomsun28
|
||||
* @date 2021/11/2 11:24
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatch {
|
||||
|
||||
/**
|
||||
* 指标组采集任务超时时间值
|
||||
*/
|
||||
private static final long DURATION_TIME = 120_000L;
|
||||
/**
|
||||
* 指标组采集任务优先级队列
|
||||
*/
|
||||
private MetricsCollectorQueue jobRequestQueue;
|
||||
/**
|
||||
* 时间轮任务调度器
|
||||
*/
|
||||
private TimerDispatch timerDispatch;
|
||||
/**
|
||||
* kafka采集数据导出器
|
||||
*/
|
||||
private KafkaDataExporter kafkaDataExporter;
|
||||
/**
|
||||
* 指标组任务与开始时间映射map
|
||||
*/
|
||||
private Map<String, MetricsTime> metricsTimeoutMonitorMap;
|
||||
|
||||
public CommonDispatcher(MetricsCollectorQueue jobRequestQueue, TimerDispatch timerDispatch,
|
||||
KafkaDataExporter kafkaDataExporter, WorkerPool workerPool) {
|
||||
this.kafkaDataExporter = kafkaDataExporter;
|
||||
this.jobRequestQueue = jobRequestQueue;
|
||||
this.timerDispatch = timerDispatch;
|
||||
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 2, 1,
|
||||
TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(), r -> {
|
||||
Thread thread = new Thread(r);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
// 从任务队列拉取指标组采集任务放入线程池执行
|
||||
poolExecutor.execute(() -> {
|
||||
Thread.currentThread().setName("metrics-task-dispatcher");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
MetricsCollect metricsCollect = null;
|
||||
try {
|
||||
metricsCollect = jobRequestQueue.getJob();
|
||||
workerPool.executeJob(metricsCollect);
|
||||
} catch (RejectedExecutionException rejected) {
|
||||
log.info("[Dispatcher]-the worker pool is full, reject this metrics task, " +
|
||||
"sleep and put in queue again.");
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
if (metricsCollect != null) {
|
||||
// 在队列里的优先级增大
|
||||
metricsCollect.setRunPriority((byte) (metricsCollect.getRunPriority() + 1));
|
||||
jobRequestQueue.addJob(metricsCollect);
|
||||
}
|
||||
} catch (InterruptedException interruptedException){}
|
||||
} catch (Exception e) {
|
||||
log.error("[Dispatcher]-{}.", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 监控指标组采集任务执行时间
|
||||
metricsTimeoutMonitorMap = new ConcurrentHashMap<>(128);
|
||||
poolExecutor.execute(() -> {
|
||||
Thread.currentThread().setName("metrics-task-monitor");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
// 检测每个指标组采集单元是否超时2分钟,超时则丢弃并返回异常
|
||||
long deadline = System.currentTimeMillis() - DURATION_TIME;
|
||||
for (Map.Entry<String, MetricsTime> entry : metricsTimeoutMonitorMap.entrySet()) {
|
||||
MetricsTime metricsTime = entry.getValue();
|
||||
if (metricsTime.getStartTime() < deadline) {
|
||||
// 指标组采集超时
|
||||
CollectRep.MetricsData metricsData = CollectRep.MetricsData.newBuilder()
|
||||
.setId(metricsTime.getTimerJob().getJob().getAppId())
|
||||
.setApp(metricsTime.getTimerJob().getJob().getApp())
|
||||
.setMetrics(metricsTime.getMetrics().getName())
|
||||
.setTime(System.currentTimeMillis())
|
||||
.setCode(CollectRep.Code.TIMEOUT).setMsg("collect timeout").build();
|
||||
dispatchCollectData(metricsTime.getTimerJob(), metricsTime.getMetrics(), metricsData);
|
||||
}
|
||||
}
|
||||
Thread.sleep(20000);
|
||||
} catch (Exception e){
|
||||
log.error("[Monitor]-{}.", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchMetricsTask(WheelTimerJob timerJob) {
|
||||
// 将单个应用的采集任务根据其下的指标组拆分为对应的指标组采集任务 AbstractCollect
|
||||
// 将每个指标组放入线程池进行调度
|
||||
Job job = timerJob.getJob();
|
||||
job.constructMetrics();
|
||||
Set<Metrics> metricsSet = job.getNextCollectMetrics(null, true);
|
||||
metricsSet.forEach(metrics -> {
|
||||
MetricsCollect metricsCollect = new MetricsCollect(metrics, timerJob, this);
|
||||
jobRequestQueue.addJob(metricsCollect);
|
||||
metricsTimeoutMonitorMap.put(job.getId() + metrics.getName(),
|
||||
new MetricsTime(System.currentTimeMillis(), metrics, timerJob));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchCollectData(WheelTimerJob timerJob, Metrics metrics, CollectRep.MetricsData metricsData) {
|
||||
Job job = timerJob.getJob();
|
||||
Set<Metrics> metricsSet = job.getNextCollectMetrics(metrics, false);
|
||||
if (job.isCyclic()) {
|
||||
// 若是异步的周期性循环任务,直接发送指标组的采集数据到消息中间件
|
||||
kafkaDataExporter.send(metricsData);
|
||||
if (metricsSet == null) {
|
||||
// 此Job所有指标组采集执行完成
|
||||
// 周期性任务再次将任务push到时间轮
|
||||
// 先判断此次任务执行时间与任务采集间隔时间
|
||||
long spendTime = System.currentTimeMillis() - job.getTimestamp();
|
||||
long interval = job.getInterval() - spendTime / 1000;
|
||||
interval = interval <= 0 ? 0 : interval;
|
||||
timerDispatch.cyclicJob(timerJob, interval, TimeUnit.SECONDS);
|
||||
} else if (!metricsSet.isEmpty()) {
|
||||
// 当前级别指标组执行完成,开始执行下一级别的指标组
|
||||
metricsSet.forEach(metricItem -> {
|
||||
MetricsCollect metricsCollect = new MetricsCollect(metricItem, timerJob, this);
|
||||
jobRequestQueue.addJob(metricsCollect);
|
||||
metricsTimeoutMonitorMap.put(job.getId() + metrics.getName(),
|
||||
new MetricsTime(System.currentTimeMillis(), metrics, timerJob));
|
||||
});
|
||||
} else {
|
||||
// 当前执行级别的指标组列表未全执行完成,
|
||||
// 需等待其它同级别指标组执行完成后进入下一级别执行
|
||||
}
|
||||
} else {
|
||||
// 若是临时性一次任务,需等待所有指标组的采集数据统一包装返回
|
||||
// todo 将当前指标组数据插入job里统一组装
|
||||
if (metricsSet == null) {
|
||||
// 此Job所有指标组采集执行完成
|
||||
// todo 将所有指标组数据组合一起发送到回调函数
|
||||
} else if (!metricsSet.isEmpty()) {
|
||||
// 当前级别指标组执行完成,开始执行下一级别的指标组
|
||||
metricsSet.forEach(metricItem -> {
|
||||
MetricsCollect metricsCollect = new MetricsCollect(metricItem, timerJob, this);
|
||||
jobRequestQueue.addJob(metricsCollect);
|
||||
metricsTimeoutMonitorMap.put(job.getId() + metrics.getName(),
|
||||
new MetricsTime(System.currentTimeMillis(), metrics, timerJob));
|
||||
});
|
||||
} else {
|
||||
// 当前执行级别的指标组列表未全执行完成,
|
||||
// 需等待其它同级别指标组执行完成后进入下一级别执行
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
private static class MetricsTime {
|
||||
private long startTime;
|
||||
private Metrics metrics;
|
||||
private WheelTimerJob timerJob;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import com.googlecode.aviator.AviatorEvaluator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/11/3 12:55
|
||||
*/
|
||||
@Configuration
|
||||
public class DispatchConfiguration {
|
||||
|
||||
private static final int AVIATOR_LRU_CACHE_SIZE = 1024;
|
||||
|
||||
@Bean
|
||||
public void configAviatorEvaluator() {
|
||||
// 配置AviatorEvaluator使用LRU缓存编译后的表达式
|
||||
AviatorEvaluator.getInstance().useLRUExpressionCache(AVIATOR_LRU_CACHE_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
/**
|
||||
* dispatch 常量
|
||||
* @author tomsun28
|
||||
* @date 2021/11/3 16:50
|
||||
*/
|
||||
public interface DispatchConstants {
|
||||
|
||||
/**
|
||||
* 可用性指标组
|
||||
*/
|
||||
String AVAILABILITY = "availability";
|
||||
|
||||
// 协议类型相关 - start //
|
||||
/**
|
||||
* 协议 http
|
||||
*/
|
||||
String PROTOCOL_HTTP = "http";
|
||||
/**
|
||||
* 协议 icmp
|
||||
*/
|
||||
String PROTOCOL_ICMP = "icmp";
|
||||
/**
|
||||
* 协议 jdbc
|
||||
*/
|
||||
String PROTOCOL_JDBC = "jdbc";
|
||||
// 协议类型相关 - end //
|
||||
|
||||
// http协议相关 - start 需尽可能先复用 HttpHeaders //
|
||||
/**
|
||||
* 认证方式 Bearer Token
|
||||
*/
|
||||
String BEARER_TOKEN = "Bearer Token";
|
||||
/**
|
||||
* Bearer Token 的认证参数字符
|
||||
*/
|
||||
String BEARER = "Bearer";
|
||||
/**
|
||||
* 认证方式 Basic Auth
|
||||
*/
|
||||
String BASIC_AUTH = "Basic Auth";
|
||||
/**
|
||||
* 认证方式 Digest Auth
|
||||
*/
|
||||
String DIGEST_AUTH = "Digest Auth";
|
||||
/**
|
||||
* 解析方式 默认规则
|
||||
*/
|
||||
String PARSE_DEFAULT = "default";
|
||||
/**
|
||||
* 解析方式 自定义json path
|
||||
*/
|
||||
String PARSE_JSON_PATH = "json_path";
|
||||
/**
|
||||
* 解析方式 自定义xml path
|
||||
*/
|
||||
String PARSE_XML_PATH = "xml_path";
|
||||
/**
|
||||
* 解析方式 prometheus规则
|
||||
*/
|
||||
String PARSE_PROMETHEUS = "prometheus";
|
||||
String PARSE_PROMETHEUS_ACCEPT = "application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1";
|
||||
// http协议相关 - end //
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 调度分发任务配置属性
|
||||
* @author tomsun28
|
||||
* @date 2021/10/16 14:54
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "collector.dispatch")
|
||||
public class DispatchProperties {
|
||||
|
||||
/**
|
||||
* 调度入口配置属性
|
||||
*/
|
||||
private EntranceProperties entrance;
|
||||
|
||||
/**
|
||||
* 调度数据出口配置属性
|
||||
*/
|
||||
private ExportProperties export;
|
||||
|
||||
public EntranceProperties getEntrance() {
|
||||
return entrance;
|
||||
}
|
||||
|
||||
public void setEntrance(EntranceProperties entrance) {
|
||||
this.entrance = entrance;
|
||||
}
|
||||
|
||||
public ExportProperties getExport() {
|
||||
return export;
|
||||
}
|
||||
|
||||
public void setExport(ExportProperties export) {
|
||||
this.export = export;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度入口配置属性
|
||||
* 入口可以时etcd信息,http请求,消息中间件消息请求
|
||||
*/
|
||||
public static class EntranceProperties {
|
||||
/**
|
||||
* etcd配置信息
|
||||
*/
|
||||
private EtcdProperties etcd;
|
||||
|
||||
public EtcdProperties getEtcd() {
|
||||
return etcd;
|
||||
}
|
||||
|
||||
public void setEtcd(EtcdProperties etcd) {
|
||||
this.etcd = etcd;
|
||||
}
|
||||
|
||||
public static class EtcdProperties {
|
||||
|
||||
/**
|
||||
* etcd调度是否启动
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* etcd的连接端点url
|
||||
*/
|
||||
private String[] endpoints = new String[] {"http://127.0.0.1:2379"};
|
||||
|
||||
/**
|
||||
* etcd连接用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* etcd连接密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* etcd租约的有效时间 单位秒
|
||||
*/
|
||||
private long ttl = 200;
|
||||
|
||||
/**
|
||||
* 采集器注册目录
|
||||
*/
|
||||
private String collectorDir = "/usthe/dispatch/collector/";
|
||||
|
||||
/**
|
||||
* 任务调度分发目录
|
||||
*/
|
||||
private String assignDir = "/usthe/dispatch/assign/";
|
||||
|
||||
/**
|
||||
* 任务详细目录
|
||||
*/
|
||||
private String jobDir = "/usthe/dispatch/job/";
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String[] getEndpoints() {
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
public void setEndpoints(String[] endpoints) {
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public long getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public void setTtl(long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public String getCollectorDir() {
|
||||
return collectorDir;
|
||||
}
|
||||
|
||||
public void setCollectorDir(String collectorDir) {
|
||||
this.collectorDir = collectorDir;
|
||||
}
|
||||
|
||||
public String getAssignDir() {
|
||||
return assignDir;
|
||||
}
|
||||
|
||||
public void setAssignDir(String assignDir) {
|
||||
this.assignDir = assignDir;
|
||||
}
|
||||
|
||||
public String getJobDir() {
|
||||
return jobDir;
|
||||
}
|
||||
|
||||
public void setJobDir(String jobDir) {
|
||||
this.jobDir = jobDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度数据出口配置属性
|
||||
*/
|
||||
public static class ExportProperties {
|
||||
|
||||
/**
|
||||
* kafka配置信息
|
||||
*/
|
||||
private KafkaProperties kafka;
|
||||
|
||||
public KafkaProperties getKafka() {
|
||||
return kafka;
|
||||
}
|
||||
|
||||
public void setKafka(KafkaProperties kafka) {
|
||||
this.kafka = kafka;
|
||||
}
|
||||
|
||||
public static class KafkaProperties {
|
||||
/**
|
||||
* kafka数据出口是否启动
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* kafka的连接服务器url
|
||||
*/
|
||||
private String servers = "http://127.0.0.1:2379";
|
||||
/**
|
||||
* 发送数据的topic名称
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public void setServers(String servers) {
|
||||
this.servers = servers;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public void setTopic(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import com.google.protobuf.ProtocolStringList;
|
||||
import com.googlecode.aviator.AviatorEvaluator;
|
||||
import com.googlecode.aviator.Expression;
|
||||
import com.usthe.collector.collect.AbstractCollect;
|
||||
import com.usthe.collector.collect.http.HttpCollectImpl;
|
||||
import com.usthe.collector.dispatch.timer.WheelTimerJob;
|
||||
import com.usthe.common.entity.job.Metrics;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* parent job
|
||||
* @author tomsun28
|
||||
* @date 2021/10/10 15:35
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
|
||||
|
||||
protected byte runPriority;
|
||||
|
||||
protected long newTime;
|
||||
|
||||
protected long startTime;
|
||||
|
||||
protected Metrics metrics;
|
||||
|
||||
protected WheelTimerJob timerJob;
|
||||
|
||||
protected CollectDataDispatch collectDataDispatch;
|
||||
|
||||
public MetricsCollect(Metrics metrics, WheelTimerJob timerJob, CollectDataDispatch collectDataDispatch) {
|
||||
this.newTime = System.currentTimeMillis();
|
||||
this.timerJob = timerJob;
|
||||
this.metrics = metrics;
|
||||
this.collectDataDispatch = collectDataDispatch;
|
||||
if (DispatchConstants.AVAILABILITY.equals(metrics.getName())) {
|
||||
runPriority = (byte) 1;
|
||||
} else {
|
||||
runPriority = (byte) -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.startTime = System.currentTimeMillis();
|
||||
setNewThreadName(timerJob, metrics);
|
||||
CollectRep.MetricsData.Builder response = CollectRep.MetricsData.newBuilder();
|
||||
response.setApp(timerJob.getJob().getApp());
|
||||
response.setId(timerJob.getJob().getId());
|
||||
response.setMetrics(metrics.getName());
|
||||
|
||||
// 根据指标组采集协议,应用类型等来调度到真正的应用指标组采集实现类
|
||||
AbstractCollect abstractCollect = null;
|
||||
switch (metrics.getProtocol()) {
|
||||
case DispatchConstants.PROTOCOL_HTTP:
|
||||
abstractCollect = HttpCollectImpl.getInstance();
|
||||
break;
|
||||
// todo
|
||||
default: break;
|
||||
}
|
||||
if (abstractCollect == null) {
|
||||
log.error("[Dispatcher] - not support this: app: {}, metrics: {}, protocol: {}.",
|
||||
timerJob.getJob().getApp(), metrics.getName(), metrics.getProtocol());
|
||||
response.setCode(CollectRep.Code.FAIL);
|
||||
response.setMsg("not support " + timerJob.getJob().getApp() + ", "
|
||||
+ metrics.getName() + ", " + metrics.getProtocol());
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
abstractCollect.collect(response, timerJob.getJob().getAppId(),
|
||||
timerJob.getJob().getApp(), metrics);
|
||||
} catch (Exception e) {
|
||||
log.error("[Metrics Collect]: {}.", e.getMessage(), e);
|
||||
response.setCode(CollectRep.Code.FAIL);
|
||||
response.setMsg(e.getMessage());
|
||||
}
|
||||
}
|
||||
// 别名属性表达式替换计算
|
||||
calculateFields(metrics, response);
|
||||
CollectRep.MetricsData metricsData = validateResponse(response);
|
||||
collectDataDispatch.dispatchCollectData(timerJob, metrics, metricsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 calculates 和 aliasFields 配置计算出真正的指标(fields)值
|
||||
* @param metrics 指标组配置
|
||||
* @param collectData 采集数据
|
||||
*/
|
||||
private void calculateFields(Metrics metrics, CollectRep.MetricsData.Builder collectData) {
|
||||
collectData.addAllFields(metrics.getFields());
|
||||
// 若不存在需要计算的表达式,则 别名指标aliasFields 的数据就是真正指标 fields的数据
|
||||
if (metrics.getCalculates() == null || metrics.getCalculates().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<CollectRep.ValueRow> aliasRowList = collectData.getValuesList();
|
||||
if (aliasRowList == null || aliasRowList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 先预处理 calculates
|
||||
Map<String, Expression> fieldExpressionMap = metrics.getCalculates()
|
||||
.stream()
|
||||
.map(cal -> {
|
||||
int splitIndex = cal.indexOf("=");
|
||||
String field = cal.substring(0, splitIndex);
|
||||
String expressionStr = cal.substring(splitIndex + 1);
|
||||
Expression expression = AviatorEvaluator.compile(expressionStr, true);
|
||||
return new Object[]{field, expression}; })
|
||||
.collect(Collectors.toMap(arr -> (String)arr[0], arr -> (Expression) arr[1]));
|
||||
|
||||
List<String> fields = metrics.getFields();
|
||||
List<String> aliasFields = metrics.getAliasFields();
|
||||
Map<String, Object> aliasFieldValueMap = new HashMap<>(16);
|
||||
for (int index = 0; index < aliasRowList.size(); index++) {
|
||||
CollectRep.ValueRow aliasRow = aliasRowList.get(index);
|
||||
for (int aliasIndex = 0; aliasIndex < aliasFields.size(); aliasIndex++) {
|
||||
aliasFieldValueMap.put(aliasFields.get(aliasIndex), aliasRow.getColumns(aliasIndex));
|
||||
}
|
||||
ProtocolStringList columnList = aliasRow.getColumnsList();
|
||||
columnList.clear();
|
||||
for (int realIndex = 0; realIndex < fields.size(); realIndex++) {
|
||||
String realField = fields.get(realIndex);
|
||||
Expression expression = fieldExpressionMap.get(realField);
|
||||
String value = "";
|
||||
if (expression != null) {
|
||||
// 存在计算表达式 则计算值
|
||||
value = (String) expression.execute(aliasFieldValueMap);
|
||||
} else {
|
||||
// 不存在 则映射别名值
|
||||
value = (String) aliasFieldValueMap.get(realField);
|
||||
}
|
||||
columnList.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CollectRep.MetricsData validateResponse(CollectRep.MetricsData.Builder builder) {
|
||||
long endTime = System.currentTimeMillis();
|
||||
builder.setTime(endTime);
|
||||
log.debug("[Collect]: newTime: {}, startTime: {}, spendTime: {}.", newTime, startTime, endTime - startTime);
|
||||
if (builder.getCode() != CollectRep.Code.SUCCESS) {
|
||||
log.info("[Collect Fail]-reason:{}", builder.getMsg());
|
||||
} else {
|
||||
log.info("[Collect Success]-{},{},{}.", builder.getId(), builder.getApp(), builder.getMetrics());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void setNewThreadName(WheelTimerJob timerJob, Metrics metrics) {
|
||||
String currentName = timerJob.getJob().getAppId() + timerJob.getJob().getApp() + metrics.getName() + timerJob.getJob().getId();
|
||||
Thread.currentThread().setName(currentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MetricsCollect collect) {
|
||||
return runPriority - collect.runPriority;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 待运行的job队列
|
||||
* @author tomsun28
|
||||
* @date 2021/10/10 20:20
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MetricsCollectorQueue {
|
||||
|
||||
private final PriorityBlockingQueue<MetricsCollect> jobQueue;
|
||||
|
||||
public MetricsCollectorQueue() {
|
||||
jobQueue = new PriorityBlockingQueue<>(2000);
|
||||
}
|
||||
|
||||
public void addJob(MetricsCollect job) {
|
||||
jobQueue.offer(job);
|
||||
}
|
||||
|
||||
public MetricsCollect getJob() throws InterruptedException {
|
||||
return jobQueue.poll(2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import com.usthe.collector.dispatch.timer.WheelTimerJob;
|
||||
|
||||
/**
|
||||
* 指标组采集任务调度器接口
|
||||
* @author tomsun28
|
||||
* @date 2021/11/2 11:19
|
||||
*/
|
||||
public interface MetricsTaskDispatch {
|
||||
|
||||
/**
|
||||
* 调度
|
||||
* @param timerJob timerJob
|
||||
*/
|
||||
void dispatchMetricsTask(WheelTimerJob timerJob);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.usthe.collector.dispatch;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 采集任务工作线程池
|
||||
* @author tomsun28
|
||||
* @date 2021/10/15 0:01
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WorkerPool {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
|
||||
public WorkerPool() {
|
||||
initWorkExecutor();
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
// 线程工厂
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("workerExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable); })
|
||||
.setDaemon(true)
|
||||
.setNameFormat("collect-worker-%d")
|
||||
.build();
|
||||
workerExecutor = new ThreadPoolExecutor(100,
|
||||
800,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行采集任务线程
|
||||
* @param runnable 任务
|
||||
* @throws RejectedExecutionException when 线程池满
|
||||
*/
|
||||
public void executeJob(Runnable runnable) throws RejectedExecutionException {
|
||||
workerExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.usthe.collector.dispatch.entrance.http;
|
||||
|
||||
import com.usthe.common.entity.job.Job;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 采集job管理提供api接口
|
||||
* @author tomsun28
|
||||
* @date 2021/11/6 13:58
|
||||
*/
|
||||
@RestController
|
||||
public class CollectJobController {
|
||||
|
||||
/**
|
||||
* 执行一次性采集任务,获取采集数据响应
|
||||
* @return 采集结果
|
||||
*/
|
||||
@PostMapping("/job")
|
||||
public Mono<Object> collectJobData(Job job) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.usthe.collector.dispatch.export;
|
||||
|
||||
import com.usthe.collector.dispatch.DispatchProperties;
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* kafka采集数据消息发送
|
||||
* @author tomsun28
|
||||
* @date 2021/11/3 15:22
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "collector.dispatch.export.kafka",
|
||||
name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
@AutoConfigureAfter(value = {DispatchProperties.class})
|
||||
@Slf4j
|
||||
public class KafkaDataExporter {
|
||||
|
||||
KafkaProducer<String, CollectRep.MetricsData> kafkaProducer;
|
||||
DispatchProperties.ExportProperties.KafkaProperties kafkaProperties;
|
||||
public KafkaDataExporter(DispatchProperties dispatchProperties) {
|
||||
try {
|
||||
kafkaProperties = dispatchProperties.getExport().getKafka();
|
||||
Properties properties = new Properties();
|
||||
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getServers());
|
||||
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaMetricsDataSerializer.class);
|
||||
kafkaProducer = new KafkaProducer<>(properties);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param metricsData 指标组采集数据
|
||||
*/
|
||||
public void send(CollectRep.MetricsData metricsData) {
|
||||
if (kafkaProducer != null) {
|
||||
kafkaProducer.send(new ProducerRecord<>(kafkaProperties.getTopic(), metricsData));
|
||||
} else {
|
||||
log.error("kafkaProducer is not enable");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.usthe.collector.dispatch.export;
|
||||
|
||||
|
||||
import com.usthe.common.entity.message.CollectRep;
|
||||
import org.apache.kafka.common.serialization.Serializer;
|
||||
|
||||
/**
|
||||
* MetricsData的序列化
|
||||
* @author tomsun28
|
||||
* @date 2021/11/3 16:14
|
||||
*/
|
||||
public class KafkaMetricsDataSerializer implements Serializer<CollectRep.MetricsData> {
|
||||
|
||||
@Override
|
||||
public byte[] serialize(String topicName, CollectRep.MetricsData metricsData) {
|
||||
return metricsData.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,809 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* A {@link Timer} optimized for approximated I/O timeout scheduling.
|
||||
*
|
||||
* <h3>Tick Duration</h3>
|
||||
* <p>
|
||||
* As described with 'approximated', this timer does not execute the scheduled
|
||||
* {@link TimerTask} on time. {@link HashedWheelTimer}, on every tick, will
|
||||
* check if there are any {@link TimerTask}s behind the schedule and execute
|
||||
* them.
|
||||
* <p>
|
||||
* You can increase or decrease the accuracy of the execution timing by
|
||||
* specifying smaller or larger tick duration in the constructor. In most
|
||||
* network applications, I/O timeout does not need to be accurate. Therefore,
|
||||
* the default tick duration is 100 milliseconds and you will not need to try
|
||||
* different configurations in most cases.
|
||||
*
|
||||
* <h3>Ticks per Wheel (Wheel Size)</h3>
|
||||
* <p>
|
||||
* {@link HashedWheelTimer} maintains a data structure called 'wheel'.
|
||||
* To put simply, a wheel is a hash table of {@link TimerTask}s whose hash
|
||||
* function is 'dead line of the task'. The default number of ticks per wheel
|
||||
* (i.e. the size of the wheel) is 512. You could specify a larger value
|
||||
* if you are going to schedule a lot of timeouts.
|
||||
*
|
||||
* <h3>Do not create many instances.</h3>
|
||||
* <p>
|
||||
* {@link HashedWheelTimer} creates a new thread whenever it is instantiated and
|
||||
* started. Therefore, you should make sure to create only one instance and
|
||||
* share it across your application. One of the common mistakes, that makes
|
||||
* your application unresponsive, is to create a new instance for every connection.
|
||||
*
|
||||
* <h3>Implementation Details</h3>
|
||||
* <p>
|
||||
* {@link HashedWheelTimer} is based on
|
||||
* <a href="http://cseweb.ucsd.edu/users/varghese/">George Varghese</a> and
|
||||
* Tony Lauck's paper,
|
||||
* <a href="http://cseweb.ucsd.edu/users/varghese/PAPERS/twheel.ps.Z">'Hashed
|
||||
* and Hierarchical Timing Wheels: data structures to efficiently implement a
|
||||
* timer facility'</a>. More comprehensive slides are located
|
||||
* <a href="http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt">here</a>.
|
||||
* @author from netty | dubbo
|
||||
*/
|
||||
@SuppressWarnings("PMD")
|
||||
public class HashedWheelTimer implements Timer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HashedWheelTimer.class);
|
||||
|
||||
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();
|
||||
private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean();
|
||||
private static final int INSTANCE_COUNT_LIMIT = 64;
|
||||
private static final AtomicIntegerFieldUpdater<HashedWheelTimer> WORKER_STATE_UPDATER =
|
||||
AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState");
|
||||
|
||||
private final Worker worker = new Worker();
|
||||
private final Thread workerThread;
|
||||
|
||||
private static final int WORKER_STATE_INIT = 0;
|
||||
private static final int WORKER_STATE_STARTED = 1;
|
||||
private static final int WORKER_STATE_SHUTDOWN = 2;
|
||||
|
||||
/**
|
||||
* 0 - init, 1 - started, 2 - shut down
|
||||
*/
|
||||
@SuppressWarnings({"unused", "FieldMayBeFinal"})
|
||||
private volatile int workerState;
|
||||
|
||||
private final long tickDuration;
|
||||
private final HashedWheelBucket[] wheel;
|
||||
private final int mask;
|
||||
private final CountDownLatch startTimeInitialized = new CountDownLatch(1);
|
||||
private final Queue<HashedWheelTimeout> timeouts = new LinkedBlockingQueue<>();
|
||||
private final Queue<HashedWheelTimeout> cancelledTimeouts = new LinkedBlockingQueue<>();
|
||||
private final AtomicLong pendingTimeouts = new AtomicLong(0);
|
||||
private final long maxPendingTimeouts;
|
||||
|
||||
private volatile long startTime;
|
||||
|
||||
/**
|
||||
* Creates a new timer with the default thread factory
|
||||
* ({@link Executors#defaultThreadFactory()}), default tick duration, and
|
||||
* default number of ticks per wheel.
|
||||
*/
|
||||
public HashedWheelTimer() {
|
||||
this(Executors.defaultThreadFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer with the default thread factory
|
||||
* ({@link Executors#defaultThreadFactory()}) and default number of ticks
|
||||
* per wheel.
|
||||
*
|
||||
* @param tickDuration the duration between tick
|
||||
* @param unit the time unit of the {@code tickDuration}
|
||||
* @throws NullPointerException if {@code unit} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code tickDuration} is <= 0
|
||||
*/
|
||||
public HashedWheelTimer(long tickDuration, TimeUnit unit) {
|
||||
this(Executors.defaultThreadFactory(), tickDuration, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer with the default thread factory
|
||||
* ({@link Executors#defaultThreadFactory()}).
|
||||
*
|
||||
* @param tickDuration the duration between tick
|
||||
* @param unit the time unit of the {@code tickDuration}
|
||||
* @param ticksPerWheel the size of the wheel
|
||||
* @throws NullPointerException if {@code unit} is {@code null}
|
||||
* @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0
|
||||
*/
|
||||
public HashedWheelTimer(long tickDuration, TimeUnit unit, int ticksPerWheel) {
|
||||
this(Executors.defaultThreadFactory(), tickDuration, unit, ticksPerWheel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer with the default tick duration and default number of
|
||||
* ticks per wheel.
|
||||
*
|
||||
* @param threadFactory a {@link ThreadFactory} that creates a
|
||||
* background {@link Thread} which is dedicated to
|
||||
* {@link TimerTask} execution.
|
||||
* @throws NullPointerException if {@code threadFactory} is {@code null}
|
||||
*/
|
||||
public HashedWheelTimer(ThreadFactory threadFactory) {
|
||||
this(threadFactory, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer with the default number of ticks per wheel.
|
||||
*
|
||||
* @param threadFactory a {@link ThreadFactory} that creates a
|
||||
* background {@link Thread} which is dedicated to
|
||||
* {@link TimerTask} execution.
|
||||
* @param tickDuration the duration between tick
|
||||
* @param unit the time unit of the {@code tickDuration}
|
||||
* @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code tickDuration} is <= 0
|
||||
*/
|
||||
public HashedWheelTimer(
|
||||
ThreadFactory threadFactory, long tickDuration, TimeUnit unit) {
|
||||
this(threadFactory, tickDuration, unit, 512);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer.
|
||||
*
|
||||
* @param threadFactory a {@link ThreadFactory} that creates a
|
||||
* background {@link Thread} which is dedicated to
|
||||
* {@link TimerTask} execution.
|
||||
* @param tickDuration the duration between tick
|
||||
* @param unit the time unit of the {@code tickDuration}
|
||||
* @param ticksPerWheel the size of the wheel
|
||||
* @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null}
|
||||
* @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0
|
||||
*/
|
||||
public HashedWheelTimer(
|
||||
ThreadFactory threadFactory,
|
||||
long tickDuration, TimeUnit unit, int ticksPerWheel) {
|
||||
this(threadFactory, tickDuration, unit, ticksPerWheel, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new timer.
|
||||
*
|
||||
* @param threadFactory a {@link ThreadFactory} that creates a
|
||||
* background {@link Thread} which is dedicated to
|
||||
* {@link TimerTask} execution.
|
||||
* @param tickDuration the duration between tick
|
||||
* @param unit the time unit of the {@code tickDuration}
|
||||
* @param ticksPerWheel the size of the wheel
|
||||
* @param maxPendingTimeouts The maximum number of pending timeouts after which call to
|
||||
* {@code newTimeout} will result in
|
||||
* {@link RejectedExecutionException}
|
||||
* being thrown. No maximum pending timeouts limit is assumed if
|
||||
* this value is 0 or negative.
|
||||
* @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null}
|
||||
* @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0
|
||||
*/
|
||||
public HashedWheelTimer(
|
||||
ThreadFactory threadFactory,
|
||||
long tickDuration, TimeUnit unit, int ticksPerWheel,
|
||||
long maxPendingTimeouts) {
|
||||
|
||||
if (threadFactory == null) {
|
||||
throw new NullPointerException("threadFactory");
|
||||
}
|
||||
if (unit == null) {
|
||||
throw new NullPointerException("unit");
|
||||
}
|
||||
if (tickDuration <= 0) {
|
||||
throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
|
||||
}
|
||||
if (ticksPerWheel <= 0) {
|
||||
throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
|
||||
}
|
||||
|
||||
// Normalize ticksPerWheel to power of two and initialize the wheel.
|
||||
wheel = createWheel(ticksPerWheel);
|
||||
mask = wheel.length - 1;
|
||||
|
||||
// Convert tickDuration to nanos.
|
||||
this.tickDuration = unit.toNanos(tickDuration);
|
||||
|
||||
// Prevent overflow.
|
||||
if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
|
||||
tickDuration, Long.MAX_VALUE / wheel.length));
|
||||
}
|
||||
workerThread = threadFactory.newThread(worker);
|
||||
|
||||
this.maxPendingTimeouts = maxPendingTimeouts;
|
||||
|
||||
if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
|
||||
WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
|
||||
reportTooManyInstances();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
super.finalize();
|
||||
} finally {
|
||||
// This object is going to be GCed and it is assumed the ship has sailed to do a proper shutdown. If
|
||||
// we have not yet shutdown then we want to make sure we decrement the active instance count.
|
||||
if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) {
|
||||
INSTANCE_COUNTER.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
|
||||
if (ticksPerWheel <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"ticksPerWheel must be greater than 0: " + ticksPerWheel);
|
||||
}
|
||||
if (ticksPerWheel > 1073741824) {
|
||||
throw new IllegalArgumentException(
|
||||
"ticksPerWheel may not be greater than 2^30: " + ticksPerWheel);
|
||||
}
|
||||
|
||||
ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);
|
||||
HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
|
||||
for (int i = 0; i < wheel.length; i++) {
|
||||
wheel[i] = new HashedWheelBucket();
|
||||
}
|
||||
return wheel;
|
||||
}
|
||||
|
||||
private static int normalizeTicksPerWheel(int ticksPerWheel) {
|
||||
int normalizedTicksPerWheel = ticksPerWheel - 1;
|
||||
normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 1;
|
||||
normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 2;
|
||||
normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 4;
|
||||
normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 8;
|
||||
normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 16;
|
||||
return normalizedTicksPerWheel + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the background thread explicitly. The background thread will
|
||||
* start automatically on demand even if you did not call this method.
|
||||
*
|
||||
* @throws IllegalStateException if this timer has been
|
||||
* {@linkplain #stop() stopped} already
|
||||
*/
|
||||
public void start() {
|
||||
switch (WORKER_STATE_UPDATER.get(this)) {
|
||||
case WORKER_STATE_INIT:
|
||||
if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {
|
||||
workerThread.start();
|
||||
}
|
||||
break;
|
||||
case WORKER_STATE_STARTED:
|
||||
break;
|
||||
case WORKER_STATE_SHUTDOWN:
|
||||
throw new IllegalStateException("cannot be started once stopped");
|
||||
default:
|
||||
throw new Error("Invalid WorkerState");
|
||||
}
|
||||
|
||||
// Wait until the startTime is initialized by the worker.
|
||||
while (startTime == 0) {
|
||||
try {
|
||||
startTimeInitialized.await();
|
||||
} catch (InterruptedException ignore) {
|
||||
// Ignore - it will be ready very soon.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Timeout> stop() {
|
||||
if (Thread.currentThread() == workerThread) {
|
||||
throw new IllegalStateException(
|
||||
HashedWheelTimer.class.getSimpleName() +
|
||||
".stop() cannot be called from " +
|
||||
TimerTask.class.getSimpleName());
|
||||
}
|
||||
|
||||
if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) {
|
||||
// workerState can be 0 or 2 at this moment - let it always be 2.
|
||||
if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) {
|
||||
INSTANCE_COUNTER.decrementAndGet();
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
try {
|
||||
boolean interrupted = false;
|
||||
while (workerThread.isAlive()) {
|
||||
workerThread.interrupt();
|
||||
try {
|
||||
workerThread.join(100);
|
||||
} catch (InterruptedException ignored) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} finally {
|
||||
INSTANCE_COUNTER.decrementAndGet();
|
||||
}
|
||||
return worker.unprocessedTimeouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStop() {
|
||||
return WORKER_STATE_SHUTDOWN == WORKER_STATE_UPDATER.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
|
||||
if (task == null) {
|
||||
throw new NullPointerException("task");
|
||||
}
|
||||
if (unit == null) {
|
||||
throw new NullPointerException("unit");
|
||||
}
|
||||
|
||||
long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
|
||||
|
||||
if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
|
||||
pendingTimeouts.decrementAndGet();
|
||||
throw new RejectedExecutionException("Number of pending timeouts ("
|
||||
+ pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
|
||||
+ "timeouts (" + maxPendingTimeouts + ")");
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
// Add the timeout to the timeout queue which will be processed on the next tick.
|
||||
// During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.
|
||||
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
|
||||
|
||||
// Guard against overflow.
|
||||
if (delay > 0 && deadline < 0) {
|
||||
deadline = Long.MAX_VALUE;
|
||||
}
|
||||
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
|
||||
timeouts.add(timeout);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pending timeouts of this {@link Timer}.
|
||||
*/
|
||||
public long pendingTimeouts() {
|
||||
return pendingTimeouts.get();
|
||||
}
|
||||
|
||||
private static void reportTooManyInstances() {
|
||||
logger.error("You are creating too many HashedWheelTimer instances. " +
|
||||
"HashedWheelTimer is a shared resource that must be reused across the JVM," +
|
||||
"so that only a few instances are created.");
|
||||
}
|
||||
|
||||
private final class Worker implements Runnable {
|
||||
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
|
||||
|
||||
private long tick;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Initialize the startTime.
|
||||
startTime = System.nanoTime();
|
||||
if (startTime == 0) {
|
||||
// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
|
||||
startTime = 1;
|
||||
}
|
||||
|
||||
// Notify the other threads waiting for the initialization at start().
|
||||
startTimeInitialized.countDown();
|
||||
|
||||
do {
|
||||
final long deadline = waitForNextTick();
|
||||
if (deadline > 0) {
|
||||
int idx = (int) (tick & mask);
|
||||
processCancelledTasks();
|
||||
HashedWheelBucket bucket =
|
||||
wheel[idx];
|
||||
transferTimeoutsToBuckets();
|
||||
bucket.expireTimeouts(deadline);
|
||||
tick++;
|
||||
}
|
||||
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
|
||||
|
||||
// Fill the unprocessedTimeouts so we can return them from stop() method.
|
||||
for (HashedWheelBucket bucket : wheel) {
|
||||
bucket.clearTimeouts(unprocessedTimeouts);
|
||||
}
|
||||
for (; ; ) {
|
||||
HashedWheelTimeout timeout = timeouts.poll();
|
||||
if (timeout == null) {
|
||||
break;
|
||||
}
|
||||
if (!timeout.isCancelled()) {
|
||||
unprocessedTimeouts.add(timeout);
|
||||
}
|
||||
}
|
||||
processCancelledTasks();
|
||||
}
|
||||
|
||||
private void transferTimeoutsToBuckets() {
|
||||
// transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
|
||||
// adds new timeouts in a loop.
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
HashedWheelTimeout timeout = timeouts.poll();
|
||||
if (timeout == null) {
|
||||
// all processed
|
||||
break;
|
||||
}
|
||||
if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
|
||||
// Was cancelled in the meantime.
|
||||
continue;
|
||||
}
|
||||
|
||||
long calculated = timeout.deadline / tickDuration;
|
||||
timeout.remainingRounds = (calculated - tick) / wheel.length;
|
||||
|
||||
// Ensure we don't schedule for past.
|
||||
final long ticks = Math.max(calculated, tick);
|
||||
int stopIndex = (int) (ticks & mask);
|
||||
|
||||
HashedWheelBucket bucket = wheel[stopIndex];
|
||||
bucket.addTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCancelledTasks() {
|
||||
for (; ; ) {
|
||||
HashedWheelTimeout timeout = cancelledTimeouts.poll();
|
||||
if (timeout == null) {
|
||||
// all processed
|
||||
break;
|
||||
}
|
||||
try {
|
||||
timeout.remove();
|
||||
} catch (Throwable t) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("An exception was thrown while process a cancellation task", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate goal nanoTime from startTime and current tick number,
|
||||
* then wait until that goal has been reached.
|
||||
*
|
||||
* @return Long.MIN_VALUE if received a shutdown request,
|
||||
* current time otherwise (with Long.MIN_VALUE changed by +1)
|
||||
*/
|
||||
private long waitForNextTick() {
|
||||
long deadline = tickDuration * (tick + 1);
|
||||
|
||||
for (; ; ) {
|
||||
final long currentTime = System.nanoTime() - startTime;
|
||||
long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
|
||||
|
||||
if (sleepTimeMs <= 0) {
|
||||
if (currentTime == Long.MIN_VALUE) {
|
||||
return -Long.MAX_VALUE;
|
||||
} else {
|
||||
return currentTime;
|
||||
}
|
||||
}
|
||||
if (isWindows()) {
|
||||
sleepTimeMs = sleepTimeMs / 10 * 10;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(sleepTimeMs);
|
||||
} catch (InterruptedException ignored) {
|
||||
if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<Timeout> unprocessedTimeouts() {
|
||||
return Collections.unmodifiableSet(unprocessedTimeouts);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HashedWheelTimeout implements Timeout {
|
||||
|
||||
private static final int ST_INIT = 0;
|
||||
private static final int ST_CANCELLED = 1;
|
||||
private static final int ST_EXPIRED = 2;
|
||||
private static final AtomicIntegerFieldUpdater<HashedWheelTimeout> STATE_UPDATER =
|
||||
AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state");
|
||||
|
||||
private final HashedWheelTimer timer;
|
||||
private final TimerTask task;
|
||||
private final long deadline;
|
||||
|
||||
@SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization"})
|
||||
private volatile int state = ST_INIT;
|
||||
|
||||
/**
|
||||
* RemainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the
|
||||
* HashedWheelTimeout will be added to the correct HashedWheelBucket.
|
||||
*/
|
||||
long remainingRounds;
|
||||
|
||||
/**
|
||||
* This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list.
|
||||
* As only the workerThread will act on it there is no need for synchronization / volatile.
|
||||
*/
|
||||
HashedWheelTimeout next;
|
||||
HashedWheelTimeout prev;
|
||||
|
||||
/**
|
||||
* The bucket to which the timeout was added
|
||||
*/
|
||||
HashedWheelBucket bucket;
|
||||
|
||||
HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) {
|
||||
this.timer = timer;
|
||||
this.task = task;
|
||||
this.deadline = deadline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timer timer() {
|
||||
return timer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimerTask task() {
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
// only update the state it will be removed from HashedWheelBucket on next tick.
|
||||
if (!compareAndSetState(ST_INIT, ST_CANCELLED)) {
|
||||
return false;
|
||||
}
|
||||
// If a task should be canceled we put this to another queue which will be processed on each tick.
|
||||
// So this means that we will have a GC latency of max. 1 tick duration which is good enough. This way
|
||||
// we can make again use of our MpscLinkedQueue and so minimize the locking / overhead as much as possible.
|
||||
timer.cancelledTimeouts.add(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void remove() {
|
||||
HashedWheelBucket bucket = this.bucket;
|
||||
if (bucket != null) {
|
||||
bucket.remove(this);
|
||||
} else {
|
||||
timer.pendingTimeouts.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean compareAndSetState(int expected, int state) {
|
||||
return STATE_UPDATER.compareAndSet(this, expected, state);
|
||||
}
|
||||
|
||||
public int state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return state() == ST_CANCELLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return state() == ST_EXPIRED;
|
||||
}
|
||||
|
||||
public void expire() {
|
||||
if (!compareAndSetState(ST_INIT, ST_EXPIRED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
task.run(this);
|
||||
} catch (Throwable t) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("An exception was thrown by " + TimerTask.class.getSimpleName() + '.', t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final long currentTime = System.nanoTime();
|
||||
long remaining = deadline - currentTime + timer.startTime;
|
||||
|
||||
StringBuilder buf = new StringBuilder(192)
|
||||
.append("HashedWheelTimer")
|
||||
.append('(')
|
||||
.append("deadline: ");
|
||||
if (remaining > 0) {
|
||||
buf.append(remaining)
|
||||
.append(" ns later");
|
||||
} else if (remaining < 0) {
|
||||
buf.append(-remaining)
|
||||
.append(" ns ago");
|
||||
} else {
|
||||
buf.append("now");
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
buf.append(", cancelled");
|
||||
}
|
||||
|
||||
return buf.append(", task: ")
|
||||
.append(task())
|
||||
.append(')')
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucket that stores HashedWheelTimeouts. These are stored in a linked-list like datastructure to allow easy
|
||||
* removal of HashedWheelTimeouts in the middle. Also the HashedWheelTimeout act as nodes themself and so no
|
||||
* extra object creation is needed.
|
||||
*/
|
||||
private static final class HashedWheelBucket {
|
||||
|
||||
/**
|
||||
* Used for the linked-list datastructure
|
||||
*/
|
||||
private HashedWheelTimeout head;
|
||||
private HashedWheelTimeout tail;
|
||||
|
||||
/**
|
||||
* Add {@link HashedWheelTimeout} to this bucket.
|
||||
*/
|
||||
void addTimeout(HashedWheelTimeout timeout) {
|
||||
assert timeout.bucket == null;
|
||||
timeout.bucket = this;
|
||||
if (head == null) {
|
||||
head = tail = timeout;
|
||||
} else {
|
||||
tail.next = timeout;
|
||||
timeout.prev = tail;
|
||||
tail = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire all {@link HashedWheelTimeout}s for the given {@code deadline}.
|
||||
*/
|
||||
void expireTimeouts(long deadline) {
|
||||
HashedWheelTimeout timeout = head;
|
||||
|
||||
// process all timeouts
|
||||
while (timeout != null) {
|
||||
HashedWheelTimeout next = timeout.next;
|
||||
if (timeout.remainingRounds <= 0) {
|
||||
next = remove(timeout);
|
||||
if (timeout.deadline <= deadline) {
|
||||
timeout.expire();
|
||||
} else {
|
||||
// The timeout was placed into a wrong slot. This should never happen.
|
||||
throw new IllegalStateException(String.format(
|
||||
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
|
||||
}
|
||||
} else if (timeout.isCancelled()) {
|
||||
next = remove(timeout);
|
||||
} else {
|
||||
timeout.remainingRounds--;
|
||||
}
|
||||
timeout = next;
|
||||
}
|
||||
}
|
||||
|
||||
public HashedWheelTimeout remove(HashedWheelTimeout timeout) {
|
||||
HashedWheelTimeout next = timeout.next;
|
||||
// remove timeout that was either processed or cancelled by updating the linked-list
|
||||
if (timeout.prev != null) {
|
||||
timeout.prev.next = next;
|
||||
}
|
||||
if (timeout.next != null) {
|
||||
timeout.next.prev = timeout.prev;
|
||||
}
|
||||
|
||||
if (timeout == head) {
|
||||
// if timeout is also the tail we need to adjust the entry too
|
||||
if (timeout == tail) {
|
||||
tail = null;
|
||||
head = null;
|
||||
} else {
|
||||
head = next;
|
||||
}
|
||||
} else if (timeout == tail) {
|
||||
// if the timeout is the tail modify the tail to be the prev node.
|
||||
tail = timeout.prev;
|
||||
}
|
||||
// null out prev, next and bucket to allow for GC.
|
||||
timeout.prev = null;
|
||||
timeout.next = null;
|
||||
timeout.bucket = null;
|
||||
timeout.timer.pendingTimeouts.decrementAndGet();
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this bucket and return all not expired / cancelled {@link Timeout}s.
|
||||
*/
|
||||
void clearTimeouts(Set<Timeout> set) {
|
||||
for (; ; ) {
|
||||
HashedWheelTimeout timeout = pollTimeout();
|
||||
if (timeout == null) {
|
||||
return;
|
||||
}
|
||||
if (timeout.isExpired() || timeout.isCancelled()) {
|
||||
continue;
|
||||
}
|
||||
set.add(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private HashedWheelTimeout pollTimeout() {
|
||||
HashedWheelTimeout head = this.head;
|
||||
if (head == null) {
|
||||
return null;
|
||||
}
|
||||
HashedWheelTimeout next = head.next;
|
||||
if (next == null) {
|
||||
tail = this.head = null;
|
||||
} else {
|
||||
this.head = next;
|
||||
next.prev = null;
|
||||
}
|
||||
|
||||
// null out prev and next to allow for GC.
|
||||
head.next = null;
|
||||
head.prev = null;
|
||||
head.bucket = null;
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean IS_OS_WINDOWS = System.getProperty("os.name", "").toLowerCase(Locale.US).contains("win");
|
||||
|
||||
private boolean isWindows() {
|
||||
return IS_OS_WINDOWS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
/**
|
||||
* A handle associated with a {@link TimerTask} that is returned by a
|
||||
* {@link Timer}.
|
||||
* @author from netty | dubbo
|
||||
*/
|
||||
@SuppressWarnings("PMD")
|
||||
public interface Timeout {
|
||||
|
||||
/**
|
||||
* Returns the {@link Timer} that created this handle.
|
||||
*/
|
||||
Timer timer();
|
||||
|
||||
/**
|
||||
* Returns the {@link TimerTask} which is associated with this handle.
|
||||
*/
|
||||
TimerTask task();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the {@link TimerTask} associated
|
||||
* with this handle has been expired.
|
||||
*/
|
||||
boolean isExpired();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the {@link TimerTask} associated
|
||||
* with this handle has been cancelled.
|
||||
*/
|
||||
boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Attempts to cancel the {@link TimerTask} associated with this handle.
|
||||
* If the task has been executed or cancelled already, it will return with
|
||||
* no side effect.
|
||||
*
|
||||
* @return True if the cancellation completed successfully, otherwise false
|
||||
*/
|
||||
boolean cancel();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Schedules {@link TimerTask}s for one-time future execution in a background
|
||||
* thread.
|
||||
* @author from netty | dubbo
|
||||
*/
|
||||
public interface Timer {
|
||||
|
||||
/**
|
||||
* Schedules the specified {@link TimerTask} for one-time execution after
|
||||
* the specified delay.
|
||||
*
|
||||
* @param task the {@link TimerTask
|
||||
* @param delay the delay
|
||||
* @param unit the unit of time
|
||||
* @return a handle which is associated with the specified task
|
||||
* @throws IllegalStateException if this timer has been {@linkplain #stop() stopped} already
|
||||
* @throws RejectedExecutionException if the pending timeouts are too many and creating new timeout
|
||||
* can cause instability in the system.
|
||||
*/
|
||||
Timeout newTimeout(TimerTask task, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Releases all resources acquired by this {@link Timer} and cancels all
|
||||
* tasks which were scheduled but not executed yet.
|
||||
*
|
||||
* @return the handles associated with the tasks which were canceled by
|
||||
* this method
|
||||
*/
|
||||
Set<Timeout> stop();
|
||||
|
||||
/**
|
||||
* the timer is stop
|
||||
*
|
||||
* @return true for stop
|
||||
*/
|
||||
boolean isStop();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
|
||||
import com.usthe.common.entity.job.Job;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 时间轮调度接口
|
||||
* @author tomsun28
|
||||
* @date 2021/10/17 22:14
|
||||
*/
|
||||
public interface TimerDispatch {
|
||||
|
||||
/**
|
||||
* 增加新的job
|
||||
* @param addJob job
|
||||
*/
|
||||
void addJob(Job addJob);
|
||||
|
||||
/**
|
||||
* 调度循环周期性job
|
||||
* @param timerTask timerTask
|
||||
* @param interval 开始调度的间隔时间
|
||||
* @param timeUnit 时间单位
|
||||
*/
|
||||
void cyclicJob(WheelTimerJob timerTask, long interval, TimeUnit timeUnit);
|
||||
|
||||
/**
|
||||
* 删除存在的job
|
||||
* @param jobId jobId
|
||||
* @param isCyclic 是否是周期性任务,true是, false为临时性任务
|
||||
*/
|
||||
void deleteJob(long jobId, boolean isCyclic);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
import com.usthe.common.entity.job.Job;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/10/17 23:06
|
||||
*/
|
||||
@Component
|
||||
public class TimerDispatcher implements TimerDispatch {
|
||||
|
||||
/**
|
||||
* 时间轮调度
|
||||
*/
|
||||
private Timer wheelTimer;
|
||||
/**
|
||||
* 已存在的周期性调度任务
|
||||
*/
|
||||
private Map<Long, Timeout> currentCyclicTaskMap;
|
||||
/**
|
||||
* 已存在的临时性调度任务
|
||||
*/
|
||||
private Map<Long, Timeout> currentTempTaskMap;
|
||||
|
||||
public TimerDispatcher() {
|
||||
this.wheelTimer = new HashedWheelTimer(r -> {
|
||||
Thread ret = new Thread(r, "wheelTimer");
|
||||
ret.setDaemon(true);
|
||||
return ret;
|
||||
}, 10, TimeUnit.SECONDS, 512);
|
||||
this.currentCyclicTaskMap = new ConcurrentHashMap<>(1024);
|
||||
this.currentTempTaskMap = new ConcurrentHashMap<>(1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addJob(Job addJob) {
|
||||
WheelTimerJob timerJob = new WheelTimerJob(addJob);
|
||||
Timeout timeout = wheelTimer.newTimeout(timerJob, addJob.getInterval(), TimeUnit.SECONDS);
|
||||
if (addJob.isCyclic()) {
|
||||
currentCyclicTaskMap.put(addJob.getId(), timeout);
|
||||
} else {
|
||||
currentTempTaskMap.put(addJob.getId(), timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cyclicJob(WheelTimerJob timerTask, long interval, TimeUnit timeUnit) {
|
||||
Long jobId = timerTask.getJob().getId();
|
||||
// 判断此周期性job是否已经被取消
|
||||
if (currentCyclicTaskMap.containsKey(jobId)) {
|
||||
Timeout timeout = wheelTimer.newTimeout(timerTask, interval, TimeUnit.SECONDS);
|
||||
currentCyclicTaskMap.put(timerTask.getJob().getId(), timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteJob(long jobId, boolean isCyclic) {
|
||||
if (isCyclic) {
|
||||
Timeout timeout = currentCyclicTaskMap.remove(jobId);
|
||||
if (timeout != null) {
|
||||
timeout.cancel();
|
||||
}
|
||||
} else {
|
||||
Timeout timeout = currentTempTaskMap.remove(jobId);
|
||||
if (timeout != null) {
|
||||
timeout.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A task which is executed after the delay specified with
|
||||
* {@link Timer#newTimeout(TimerTask, long, TimeUnit)} (TimerTask, long, TimeUnit)}.
|
||||
* @author from netty | dubbo
|
||||
*/
|
||||
public interface TimerTask {
|
||||
|
||||
/**
|
||||
* Executed after the delay specified with
|
||||
* {@link Timer#newTimeout(TimerTask, long, TimeUnit)}.
|
||||
*
|
||||
* @param timeout a handle which is associated with this task
|
||||
* @throws Exception when error happen
|
||||
*/
|
||||
void run(Timeout timeout) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.usthe.collector.dispatch.timer;
|
||||
|
||||
import com.usthe.collector.dispatch.MetricsTaskDispatch;
|
||||
import com.usthe.collector.util.SpringContextHolder;
|
||||
import com.usthe.common.entity.job.Job;
|
||||
|
||||
/**
|
||||
* TimerTask实现
|
||||
* @author tomsun28
|
||||
* @date 2021/11/1 17:18
|
||||
*/
|
||||
public class WheelTimerJob implements TimerTask {
|
||||
|
||||
private Job job;
|
||||
private MetricsTaskDispatch metricsTaskDispatch;
|
||||
|
||||
public WheelTimerJob(Job job) {
|
||||
this.job = job;
|
||||
this.metricsTaskDispatch = SpringContextHolder.getBean(MetricsTaskDispatch.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Timeout timeout) throws Exception {
|
||||
metricsTaskDispatch.dispatchMetricsTask(this);
|
||||
}
|
||||
|
||||
public Job getJob() {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.usthe.collector.plugin;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/10/8 15:12
|
||||
*/
|
||||
public class SameClass {
|
||||
|
||||
public static String hello() {
|
||||
return "hello collector";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.usthe.collector.plugin;
|
||||
|
||||
import com.usthe.plugin.sample.ExportDemo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author tomsun28
|
||||
* @date 2021/10/8 15:31
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TestPlugin implements CommandLineRunner {
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info(SameClass.hello());
|
||||
log.info(new ExportDemo().hello());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.usthe.collector.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring的ApplicationContext的持有者,可以用静态方法的方式获取spring容器中的bean
|
||||
* @author tomsun28
|
||||
* @date 21:07 2018/4/18
|
||||
*/
|
||||
@Component
|
||||
public class SpringContextHolder implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
set(applicationContext);
|
||||
}
|
||||
|
||||
private static void set(ApplicationContext applicationContext) {
|
||||
SpringContextHolder.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
assertApplicationContext();
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String beanName) {
|
||||
assertApplicationContext();
|
||||
return (T) applicationContext.getBean(beanName);
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> tClass) {
|
||||
assertApplicationContext();
|
||||
return (T) applicationContext.getBean(tClass);
|
||||
}
|
||||
|
||||
private static void assertApplicationContext() {
|
||||
if (null == SpringContextHolder.applicationContext) {
|
||||
throw new RuntimeException("applicationContext为空,请检查是否注入springContextHolder");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
collector/server/src/main/resources/application.yml
Normal file
16
collector/server/src/main/resources/application.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
server:
|
||||
port: 8080
|
||||
spring:
|
||||
application:
|
||||
name: ${HOSTNAME:@collecor@}${PID}
|
||||
profiles:
|
||||
active: dev
|
||||
collector:
|
||||
dispatch:
|
||||
entrance:
|
||||
etcd:
|
||||
endpoints: http://139.198.109.64:2379
|
||||
export:
|
||||
kafka:
|
||||
servers: localhost:9092
|
||||
enabled: true
|
||||
6
collector/server/src/main/resources/banner.txt
Normal file
6
collector/server/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
██████╗ ██████╗ ██╗ ██╗ ███████╗ ██████╗████████╗ ██████╗ ██████╗
|
||||
██╔════╝██╔═══██╗██║ ██║ ██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
|
||||
██║ ██║ ██║██║ ██║ █████╗ ██║ ██║ ██║ ██║██████╔╝
|
||||
██║ ██║ ██║██║ ██║ ██╔══╝ ██║ ██║ ██║ ██║██╔══██╗ Profile: ${spring.profiles.active}
|
||||
╚██████╗╚██████╔╝███████╗███████╗███████╗╚██████╗ ██║ ╚██████╔╝██║ ██║ Name: ${spring.application.name} Port: ${server.port} Pid: ${pid}
|
||||
╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
79
collector/server/src/main/resources/logback-spring.xml
Normal file
79
collector/server/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true">
|
||||
<springProperty scope="context" name="application_name" source="spring.application.name" defaultValue="collector"/>
|
||||
<!-- 输出日志到控制台 ConsoleAppender -->
|
||||
<appender name="ConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
|
||||
<!--格式化输出:%d:表示日期 %thread:表示线程名 %-5level:级别从左显示5个字符宽度 %msg:日志消息 %n:是换行符-->
|
||||
<pattern>1-%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="SystemOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 归档的日志文件的路径。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
|
||||
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<!-- 除按日志记录之外,还配置了日志文件不能超过200M,若超过200M,日志文件会以索引0开始 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 追加方式记录日志 -->
|
||||
<append>true</append>
|
||||
<!-- 日志文件的格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ErrOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}-error.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 追加方式记录日志 -->
|
||||
<append>true</append>
|
||||
<!-- 日志文件的格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
|
||||
<charset>utf-8</charset>
|
||||
</encoder>
|
||||
<!-- 此日志文件记录error及以上级别的 -->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--这个logger的设置是:举例在org.springframework包下面的所有输出日志必须级别level在info及以上级别才会被输出!-->
|
||||
<!--这样可以避免输出一些spring框架的许多常见debug信息!-->
|
||||
<logger name="org.springframework" level="info" />
|
||||
<logger name="org.json" level="error"/>
|
||||
<logger name="io.netty" level="info"/>
|
||||
<logger name="org.slf4j" level="info"/>
|
||||
<logger name="ch.qos.logback" level="info"/>
|
||||
|
||||
<!-- 生产环境配置 -->
|
||||
<springProfile name="prod">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 开发环境配置 -->
|
||||
<springProfile name="dev">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
</configuration>
|
||||
35
common/pom.xml
Normal file
35
common/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>monitor</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- etcd -->
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-core</artifactId>
|
||||
<version>0.5.10</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- 工具依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>31.0.1-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.usthe.common.entity.job;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 监控配置参数属性及值
|
||||
* 过程中需要将协议配置参数里面的标识符为^_^key^_^的内容替换为配置参数里的真实值
|
||||
* @author tomsun28
|
||||
* @date 2021/10/29 22:04
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class Configmap {
|
||||
|
||||
/**
|
||||
* 参数key,将协议配置参数里面的标识符为^^_key_^^的内容替换为配置参数里的真实值
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 参数value
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* number,string,secret
|
||||
* 数字,非加密字符串,加密字符串
|
||||
*/
|
||||
private String type;
|
||||
}
|
||||
162
common/src/main/java/com/usthe/common/entity/job/Job.java
Normal file
162
common/src/main/java/com/usthe/common/entity/job/Job.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package com.usthe.common.entity.job;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 采集任务详情
|
||||
* @author tomsun28
|
||||
* @date 2021/10/17 21:19
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Slf4j
|
||||
public class Job {
|
||||
|
||||
private static final String AVAILABILITY = "availability";
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private long id;
|
||||
/**
|
||||
* 监控ID 应用ID
|
||||
*/
|
||||
private long appId;
|
||||
/**
|
||||
* 监控的类型 eg: linux | mysql | jvm
|
||||
*/
|
||||
private String app;
|
||||
/**
|
||||
* 任务派发开始时间戳
|
||||
*/
|
||||
private long timestamp;
|
||||
/**
|
||||
* 任务采集时间间隔(单位秒) eg: 30,60,600
|
||||
*/
|
||||
private long interval;
|
||||
/**
|
||||
* 是否是循环周期性任务 true为是,false为否
|
||||
*/
|
||||
private boolean isCyclic;
|
||||
/**
|
||||
* 指标组配置 eg: cpu memory
|
||||
*/
|
||||
private List<Metrics> metrics;
|
||||
/**
|
||||
* 监控配置参数属性及值 eg: username password timeout host
|
||||
*/
|
||||
private List<Configmap> configmap;
|
||||
|
||||
/**
|
||||
* collector使用 - 任务版本,此字段不存储于etcd
|
||||
*/
|
||||
private transient long version;
|
||||
/**
|
||||
* collector使用 - 指标组任务执行优先级视图
|
||||
* 0 - availability
|
||||
* 1 - cpu | memory
|
||||
* 2 - health
|
||||
* 3 - otherMetrics
|
||||
* ....
|
||||
* 126 - otherMetrics
|
||||
* 127 - lastPriorMetrics
|
||||
*/
|
||||
private transient List<Set<Metrics>> priorMetrics;
|
||||
|
||||
/**
|
||||
* collector使用 - 构造初始化标志
|
||||
*/
|
||||
private transient boolean isConstruct = false;
|
||||
|
||||
/**
|
||||
* collector使用 - 构造初始化指标组
|
||||
*/
|
||||
public synchronized void constructMetrics() {
|
||||
if (isConstruct) {
|
||||
return;
|
||||
}
|
||||
Map<Byte, List<Metrics>> map = metrics.stream()
|
||||
.peek(metric -> {
|
||||
// 判断是否配置aliasFields 没有则配置默认
|
||||
if (metric.getAliasFields() == null || metric.getAliasFields().isEmpty()) {
|
||||
metric.setAliasFields(metric.getFields());
|
||||
}
|
||||
// 设置默认的指标组执行优先级
|
||||
if (metric.getPriority() == null) {
|
||||
if (AVAILABILITY.equals(metric.getName())) {
|
||||
metric.setPriority((byte)0);
|
||||
} else {
|
||||
metric.setPriority(Byte.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(Collectors.groupingBy(Metrics::getPriority));
|
||||
// 构造指标组任务执行顺序链表
|
||||
priorMetrics = new LinkedList<>();
|
||||
map.values().forEach(metric -> {
|
||||
Set<Metrics> metricsSet = new HashSet<>(metric);
|
||||
priorMetrics.add(metricsSet);
|
||||
});
|
||||
priorMetrics.sort(Comparator.comparing(e -> {
|
||||
Optional<Metrics> metric = e.stream().findAny();
|
||||
if (metric.isPresent()) {
|
||||
return metric.get().getPriority();
|
||||
} else {
|
||||
return Byte.MAX_VALUE;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* collector使用 - 获取下一组优先级的指标组任务
|
||||
* @param metrics 当前指标组
|
||||
* @param first 是否是第一次获取
|
||||
* @return 指标组任务
|
||||
* 返回null表示:job已完成,所有指标组采集结束
|
||||
* 返回empty的集合表示:当前级别下还有指标组采集任务未结束,无法进行下一级别的指标组任务采集
|
||||
* 返回有数据集合表示:获取到下一组优先级的指标组任务
|
||||
*/
|
||||
public synchronized Set<Metrics> getNextCollectMetrics(Metrics metrics, boolean first) {
|
||||
if (!isConstruct || priorMetrics == null || priorMetrics.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Set<Metrics> metricsSet = priorMetrics.get(0);
|
||||
if (first) {
|
||||
log.error("metrics must has one [availability] metrics at least.");
|
||||
return metricsSet;
|
||||
}
|
||||
if (metrics == null) {
|
||||
log.error("metrics can not null when not first get");
|
||||
}
|
||||
if (metrics != null && !metricsSet.remove(metrics)) {
|
||||
log.error("Job {} appId {} app {} metrics {} remove empty error in priorMetrics.",
|
||||
id, appId, app, metrics.getName());
|
||||
}
|
||||
if (metricsSet.isEmpty()) {
|
||||
if (priorMetrics.size() == 1) {
|
||||
return null;
|
||||
}
|
||||
priorMetrics.remove(0);
|
||||
return priorMetrics.get(0);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.usthe.common.entity.job;
|
||||
|
||||
import com.usthe.common.entity.job.protocol.HttpProtocol;
|
||||
import com.usthe.common.entity.job.protocol.IcmpProtocol;
|
||||
import com.usthe.common.entity.job.protocol.JdbcProtocol;
|
||||
import com.usthe.common.entity.job.protocol.TcpUdpProtocol;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 监控采集的指标集合详情 eg: cpu | memory | health
|
||||
* @author tomsun28
|
||||
* @date 2021/10/17 21:24
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class Metrics {
|
||||
|
||||
/**
|
||||
* 公共属性-名称 eg: cpu | memory | health
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 公共属性-采集监控协议 eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
*/
|
||||
private String protocol;
|
||||
/**
|
||||
* 范围(0-127)指标组调度优先级,数值越小优先级越高
|
||||
* 优先级高的调度采集完成后才会调度下一优先级的指标组采集任务
|
||||
* 可用性指标组(availability)默认优先级为0,其它普通指标组范围为1-127,即需要等availability采集成功后才会调度后面的指标组任务
|
||||
*/
|
||||
private Byte priority;
|
||||
/**
|
||||
* 公共属性-采集监控的最终结果属性集合 eg: speed | times | size
|
||||
*/
|
||||
private List<String> fields;
|
||||
/**
|
||||
* 公共属性-采集监控的前置查询属性集合 eg: size1 | size2 | speedSize
|
||||
*/
|
||||
private List<String> aliasFields;
|
||||
/**
|
||||
* 公共属性-表达式计算,将前置查询属性(preFields)与最终属性(fields)映射,计算出最终属性(fields)值
|
||||
* eg: size = size1 + size2, speed = speedSize
|
||||
* https://www.yuque.com/boyan-avfmj/aviatorscript/ban32m
|
||||
*/
|
||||
private List<String> calculates;
|
||||
|
||||
/**
|
||||
* 使用http协议的监控配置信息
|
||||
*/
|
||||
private HttpProtocol http;
|
||||
/**
|
||||
* 使用icmp协议进行ping的监控配置信息
|
||||
*/
|
||||
private IcmpProtocol icmp;
|
||||
/**
|
||||
* 使用socket实现的tcp或ucp进行服务端口探测配置信息
|
||||
*/
|
||||
private TcpUdpProtocol tcpUdp;
|
||||
/**
|
||||
* 使用公共的jdbc规范实现的数据库配置信息
|
||||
*/
|
||||
private JdbcProtocol jdbc;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.usthe.common.entity.job.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* http 协议配置
|
||||
* @author tomsun28
|
||||
* @date 2021/10/31 12:41
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class HttpProtocol {
|
||||
/**
|
||||
* 对端主机ip或域名
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* http/https 请求访问的url链接
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* http是否使用链路加密ssl/tls,即是http还是https
|
||||
*/
|
||||
private boolean ssl = false;
|
||||
/**
|
||||
* http请求方法: get, post, put, delete, patch
|
||||
*/
|
||||
private String method;
|
||||
/**
|
||||
* http请求携带头 eg: Content-Type = application/json
|
||||
*/
|
||||
private Map<String, String> headers;
|
||||
/**
|
||||
* http请求携带查询参数 eg: localhost:80/api?paramKey=value
|
||||
*/
|
||||
private Map<String, String> params;
|
||||
/**
|
||||
* 认证信息
|
||||
*/
|
||||
private Authorization authorization;
|
||||
/**
|
||||
* 响应数据解析方式
|
||||
* default - 自有的数据解析规则
|
||||
* json_path 自定义jsonPath脚本 https://www.jsonpath.cn/
|
||||
* xml_path 自定义xmlPath脚本
|
||||
* prometheus Prometheus数据规则
|
||||
*/
|
||||
private String parseType;
|
||||
/**
|
||||
* 数据解析脚本 当解析方式为 jsonPath or xmlPath时存在
|
||||
*/
|
||||
private String parseScript;
|
||||
|
||||
/**
|
||||
* 认证信息
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Authorization {
|
||||
/**
|
||||
* 认证类型:Bearer Token, Basic Auth, Digest Auth
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* Bearer Token's token
|
||||
*/
|
||||
private String bearerTokenToken;
|
||||
/**
|
||||
* Basic Auth's username
|
||||
*/
|
||||
private String basicAuthUsername;
|
||||
/**
|
||||
* Basic Auth's password
|
||||
*/
|
||||
private String basicAuthPassword;
|
||||
/**
|
||||
* Digest Auth's username
|
||||
*/
|
||||
private String digestAuthUsername;
|
||||
/**
|
||||
* Digest Auth's password
|
||||
*/
|
||||
private String digestAuthPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.usthe.common.entity.job.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* icmp(即ping)协议配置
|
||||
* @author tomsun28
|
||||
* @date 2021/10/31 16:41
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class IcmpProtocol {
|
||||
/**
|
||||
* 对端主机ip或域名
|
||||
*/
|
||||
private String host;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.usthe.common.entity.job.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 公共的jdbc规范实现的数据库配置信息
|
||||
* @author tomsun28
|
||||
* @date 2021/10/31 17:33
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class JdbcProtocol {
|
||||
/**
|
||||
* 对端主机ip或域名
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 端口号
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* 数据库用户名(可选)
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 数据库密码(可选)
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 数据库链接url eg: jdbc:mysql://localhost:3306
|
||||
*/
|
||||
private String url;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.usthe.common.entity.job.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 使用socket实现的tcp或ucp进行服务端口可用性探测
|
||||
* @author tomsun28
|
||||
* @date 2021/10/31 17:27
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TcpUdpProtocol {
|
||||
/**
|
||||
* 具体协议类型 tcp, udp
|
||||
*/
|
||||
private String protocol;
|
||||
/**
|
||||
* 对端主机ip或域名
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 端口号
|
||||
*/
|
||||
private Integer port;
|
||||
}
|
||||
3225
common/src/main/java/com/usthe/common/entity/message/CollectRep.java
Normal file
3225
common/src/main/java/com/usthe/common/entity/message/CollectRep.java
Normal file
File diff suppressed because it is too large
Load Diff
41
common/src/main/java/com/usthe/common/util/GsonUtil.java
Normal file
41
common/src/main/java/com/usthe/common/util/GsonUtil.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.usthe.common.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* gson 工具类
|
||||
* @author tomsun28
|
||||
* @date 2021/10/16 20:49
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class GsonUtil {
|
||||
|
||||
private static Gson gson;
|
||||
|
||||
static {
|
||||
gson = new GsonBuilder().enableComplexMapKeySerialization()
|
||||
.serializeNulls()
|
||||
.create();
|
||||
}
|
||||
|
||||
public static String toJson(Object source) {
|
||||
return gson.toJson(source);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String jsonStr, Class<T> clazz) {
|
||||
return gson.fromJson(jsonStr, clazz);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(ByteSequence byteSequence, Class<T> clazz) {
|
||||
if (byteSequence == null || byteSequence.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return gson.fromJson(byteSequence.toString(StandardCharsets.UTF_8), clazz);
|
||||
}
|
||||
|
||||
}
|
||||
46
common/src/main/message/collect_rep.proto
Normal file
46
common/src/main/message/collect_rep.proto
Normal file
@@ -0,0 +1,46 @@
|
||||
syntax = "proto3";
|
||||
package com.usthe.common.entity.message;
|
||||
|
||||
message MetricsData
|
||||
{
|
||||
// 监控的ID
|
||||
uint64 id = 1;
|
||||
// 监控的类型 eg: linux | mysql | jvm
|
||||
string app = 2;
|
||||
// 监控采集的指标集合 eg: cpu | memory | health
|
||||
string metrics = 3;
|
||||
// 采集时间
|
||||
uint64 time = 4;
|
||||
// 采集响应码
|
||||
Code code = 5;
|
||||
// 采集响应信息
|
||||
string msg = 6;
|
||||
// 采集指标名
|
||||
repeated string fields = 7;
|
||||
// 采集指标值集合(fields作为字段名称与ValueRow映射)
|
||||
repeated ValueRow values = 8;
|
||||
}
|
||||
|
||||
message ValueRow
|
||||
{
|
||||
// 主键实例,唯一标识这行数据
|
||||
string instance = 1;
|
||||
// 采集指标值
|
||||
repeated string columns = 2;
|
||||
}
|
||||
|
||||
enum Code
|
||||
{
|
||||
// 采集成功
|
||||
SUCCESS = 0;
|
||||
// 采集器不可用
|
||||
UN_AVAILABLE = 1;
|
||||
// 对端不可达(网络层icmp)
|
||||
UN_REACHABLE = 2;
|
||||
// 对端连接失败(传输层tcp,udp)
|
||||
UN_CONNECTABLE = 3;
|
||||
// 数据采集失败(应用层http,ssh,snmp)
|
||||
FAIL = 4;
|
||||
// 采集超时
|
||||
TIMEOUT = 5;
|
||||
}
|
||||
18
manager/pom.xml
Normal file
18
manager/pom.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>monitor</artifactId>
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>manager</artifactId>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
129
pom.xml
Normal file
129
pom.xml
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.usthe.tancloud</groupId>
|
||||
<artifactId>monitor</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<modules>
|
||||
<module>scheduler</module>
|
||||
<module>manager</module>
|
||||
<module>alerter</module>
|
||||
<module>common</module>
|
||||
<module>collector</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j.version>1.7.21</slf4j.version>
|
||||
<xml.bind.version>2.3.0</xml.bind.version>
|
||||
<lombok.version>1.18.20</lombok.version>
|
||||
<!-- Test 3rd-party dependencies: -->
|
||||
<junit.version>5.7.0</junit.version>
|
||||
<easymock.version>4.0.2</easymock.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- Import dependency management from Spring Boot -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>${xml.bind.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- 工具 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Test 3rd-party dependencies: -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
<version>${easymock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<!-- java code style check -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-pmd-plugin</artifactId>
|
||||
<version>3.8</version>
|
||||
<configuration>
|
||||
<rulesets>
|
||||
<ruleset>rulesets/java/ali-comment.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-constant.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-exception.xml</ruleset>
|
||||
<!-- <ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>-->
|
||||
<ruleset>rulesets/java/ali-naming.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-oop.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-orm.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-other.xml</ruleset>
|
||||
<ruleset>rulesets/java/ali-set.xml</ruleset>
|
||||
</rulesets>
|
||||
<printFailingErrors>true</printFailingErrors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.p3c</groupId>
|
||||
<artifactId>p3c-pmd</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Reference in New Issue
Block a user