前言
公司工程是以Docker的方式部署的,并使用了K8S做的容器编排,其中容器镜像是通过Dockerfile制作的
下面列出Dockerfile的部分内容
FROM XXXXX
MAINTAINER XXXXX
WORKDIR /root/
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
COPY ./target/*.jar /root/
ENTRYPOINT ["sh", "-ec", "exec java -server ${JAVA_OPTS} -javaagent:/root/skywalking/agent/skywalking-agent.jar -Dnacos.config.bootstrap.enable=true
-Dskywalking.agent.service_name=qygk-quota
-Dskywalking.collector.backend_service=XXXX:XXXX
-jar XXX-XXX-0.0.1-SNAPSHOT.jar"]
- 在 ENTRYPOINT 中有两段变量我一直不知道是什么意思
-Dskywalking.agent.service_name=qygk-quota
-Dskywalking.collector.backend_service=XXXX:11800
虽然我不知道什么意思 ,但是里面的skywalking我认识啊,感觉肯定有关系,所以我就特意去学了一下skywalking的内容,最后不但弄明白了这两段配置的含义,还学习到了java agent的内容。
- 下面分享学习笔记 (资料来源于XXX官网,嘿嘿不能白占人家便宜)
相关资料我上传到了百度云,有300M左右的大小
链接:https://pan.baidu.com/s/1kdXyJEj5O7__7P2fmMGl4A
提取码:qhpc
里面是案例代码,skywalking二进制安装包,es二进制安装包,测试jar包
skywalking
- 在说skywalking之前需要先了解一下APM系统
APM
- APM系统,是一个应用性能管理系统,可以通过他来监控系统的行为,分析系统的性能,以便在故障时能够快速定位。
主流的开源APM产品:
-
PinPoint 是由一个韩国团队实现并开源,针对Java编写的大规模分布式系统设计.
官方网站:
https://github.com/naver/pinpoint -
SkyWalking 是apache基金会下面的一个开源APM项目,为微服务架构和云原生架构系统设计。
官方网站:
http://skywalking.apache.org/ -
Zipkin是由Twitter开源,是分布式链路调用监控系统。
官方网站:
https://zipkin.io/ -
以上的产品他们都遵从一个规范: OpenTracing
OpenTracing
分布式追踪系统的理念最先由Google在Dapper论文中提出,接着各个厂商根据Dapper论文实现了一套自己的分布式链路追踪系统,而OpenTracing提供了一套与厂商平台无关的API,使得开发人员能够快速添加和更换分布式追踪系统的实现。
-
为了更好的说明OpenTracing在分布式追踪系统中的作用,我直接从网上抄了两个图。
-
一般我们的分布式系统调用是这样的
- 这种不能直观的看到每次调用的情况,如果调用链在长一点,整体则会显示的非常复杂,无法直观的看到每次调用的状态和时间。
- 如果是下面这样 ,则能非常直观的展示调用链之间的状态和时间关系
- 而使用OpenTracing则可以非常轻松的构建出上面这种直观的调用链。
skywalking概念
- 官方描述:Skywalking是一个可观测性分析平台(Observability Analysis Platform简称OAP) 和应用性能管理系统(Application Performance Management简称APM)。
- 特点:
1 多语言自动探针(这个会在下面详细说到) JAVA、NODE.JS等
2 可视化效果优秀
3 整体架构模块化,存储集群多种方案可选
4 多种监控,探针,server mesh
5 支持告警功能
- 架构图(ps:网上抄来的)
这个图可以分成四个部分来看
- skywalking系统的UI界面
- 数据分析平台 OAP(Observability Analysis Platform)
Tracing和Metrice 表示他是用来接收作用在我们的服务上的Tracing和Metrics数据。
通过gRPC远程调用的方式将数据传入OAP
Analysis Core 分析引擎,用来作数据的分析,整合等运算处理
Query Core 用来查询处理过的数据,返回给UI界面展示
- 探针从服务端收集的数据
探针 java agent 会被安装在我们的服务上,后面会告诉大家怎么安装,很简单的一行参数。
探针会从我们服务上获取Tracing和Metrics数据
Tracing数据:我们每一次请求的开始到结束,他会将我们这次请求过程中的范围,涉及到的所有数据,元数据信息等绑定到单个事务中进行处理。
Metrics数据:他是一个聚合数据,这些聚合数据都具有原子性,且可累加,聚合数据都是累加出来的。每个数据都是一个逻辑计量单元。
例如我们一个接口在一秒中内被调用的次数,在这一秒钟内每次请求过来,他都会更新自己的统计。
- 存储部分
这些是skywalking支持的数据持久化的方案
他默认使用的H2 是通过内存缓存来操作的。
而我们后面会将其换成ES。
关于Tracing和Metrics的概念我们可以参考
http://blog.oneapm.com/apm-tech/811.html.
里面讲了Tracing和Metrics和Logging三者的关系
-
小结:skwalking是通过探针,在我们的服务上搜集Tracing和Metrics数据,在通过OAP数据分析平台的Analysis Core 分析引擎,对数据进行处理,然后存入持久层,接着我们可以通过skywalking的UI界面向OAP的Query Core 发生HTTP请求,他会将数据从持久层给我们返回UI界面,供我们查看。
-
以上来看skywalking的总体可以分成三部分
- 探针,被安置在服务上,用来获取Tracing和Metrics数据。
- OAP数据分析平台,用来作数据的分析,然后存入持久层,在通过UI界面查询展示
- skywalking的UI界面,用来作数据的展示。
skywalking的三个概念性术语
- 服务(Service)
我们的独立应用就是一个服务,即我们单个应用搭建的服务集群,这个集群就是一个服务 - 端点(Endpoint)
我们的接口,每个请求的接口就是一个端点 - 实例(Instance)
每个集群内的单个服务,也就是单个应用不同的节点,就是一个实例
skywalking搭建(二进制方式)
GitHub安装文档:https://github.com/apache/skywalking/blob/master/docs/en/setup/README.md
相关资料可以从上面百度云地址下载。
- skywalking 版本: 6.5.0(要求ES 6.3.2版本以上)
- ES版本:6.4.0 (直接安装需要java环境)
- JAVA环境:JDK1.8
- Centos 7.6 设置了2G内核 3.5G内存 50G硬盘
- 测试服务均为SpringBoot工程,也会告诉大家tomcat如何配置
- 创建目录
命令 : mkdir /mnt/skwalking - 将资源全部上传至 /mnt/skwalking/ 目录下
- 安装并启动ES(需要java8 环境 自行安装)
命令: tar -zxvf ./elasticsearch-6.4.0.tar.gz
修改Linux系统权限设置
Linux系统限制应用创建的文件是65535 而ES需要65536
默认限制root用户开启任意线程数,而其他用户开启1024 但是ES需要4096+
因为某种原因,生产环境我们是不允许使用root用户启动这些中间件的。
这里案例也保持一致
-
创建用户,名称为es
创建命令: useradd es
设置密码: passwd es
然后输入你的密码,过于简单的密码会有错误提示,但仍然可以设置成功。(不要使用123456) -
修改文件和创建线程的权限
命令 vim /etc/security/limits.conf (没有vim 命令的 使用vi 就行)
在文件末尾加上下面的内容
es soft nofile 65536
es hard nofile 65536
es soft nproc 4096
es hard nproc 4096
效果图
保存退出。(颜色是使用vim自带的,vi是没有的,如果没vim可以通过 yum -y install vim 安装)
- 修改ES虚拟空间的使用权限
修改系统控制权限
es需要需要开辟一个65536字节以上的空间虚拟内容 linux不允许任何用户和进程直接开辟这么大的虚拟内存
命令 vi /etc/sysctl.conf
文件加入以下内容
vm.max_map_count=262144
效果图
执行 sysctl -p 让刚才的配置生效
- 将刚才的解压出来的ES目录 权限赋予es用户
命令 chown -R es elasticsearch-6.4.0 - 切换es用户启动es
命令 su es - 进入 ES的bin目录执行 启动脚本
命令 cd elasticsearch-6.4.0/bin/
启动 ./elasticsearch -d (-d 表示后台运行)
可以查看启动日志 tail -f …/logs/elasticsearch.log(当前是在bin路面下 …/logs 是切换到上级目录的 logs ) - 测试es是否启动成功
命令行输入 curl http://localhost:9200
返回以下内容是启动成功
{
"name" : "fqXNBBj",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "WhmkRPrfTb-pMsSgxGHfuQ",
"version" : {
"number" : "6.4.0",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "595516e",
"build_date" : "2018-08-17T23:18:47.308994Z",
"build_snapshot" : false,
"lucene_version" : "7.4.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
- 安装skywalking
skwalking 包含两部分,一部分是后台服务,一部分是UI前端服务。
- 切换root用户 回到/mnt/skywalking/ 下 解压skywalking安装包
su root 会提示你输入密码
cd /mnt/skywalking 进入skywalking
tar -zxvf apache-skywalking-apm-6.5.0.tar.gz 解压
cd apache-skywalking-apm-bin 进入刚解压的目录
- 我们前面说过skywalking是默认使用H2数据库 他是内存数据库,我们将他切换为ES的
- 修改配置
vim ./config/application.yml 编辑 application.yml
找到H2数据的内容 将其注释掉 输入 /H2 可以在文件中搜索快速定位
- 找到ES的内容 将其注释打开,他的配置就在H2的上面
- 因为我们的ES就是本地地址和默认端口,不用设置任何内容,打开注释后保存退出即可
- 先启动OAP后台服务
当前目录执行命令 bin/oapService.sh
- 查看UI界面的配置文件
vim webapp/webapp.yml
使用8080端口,保持默认,你也可以根据需要改变端口,保存退出
- 启动UI界面
当前目录执行
/bin/webappService.sh
然后通过浏览器 IP+端口的形式访问
启动成功,你们应该是没有数据的 ,我展示的内容是之前测试的数据结果。搭建完成
shywakling基础使用
- 探针agent
探针可以让我们在不改变代码的情况下,对java应用使用到的组件进行动态监控,获取运行时期数据。
探针在java中使用的是java agent 是用java实现的,使用是不需要修改代码。
java agent 是通过虚拟机vm接口,在运行时修改代码。
- skywalking的 agent 支持java1.6 -12 的版本,与agent相关的配置文件都放置在agent目录下
activations
bootstrap-plugins
config 配置文件
logs 日志
optional-plugins 可选插件,里面的插件是不生效的。因为一些插件不是必要的而且性能不高,所有skywalking将这些插件单独放置。
如果需要使用可以将里面的插件移动到plugins里面。
plugins 组件的所有插件,里面的插件都会生效的
skywalking-agent.jar agent配置在我们的服务中的jar包
下面我们来使用skywalking
skywalking的使用
我们使用这三个服务来演示
- skywalking_springboot.jar
启动:
java -javaagent:/mnt/skwalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=springboot
-Dserver.port=8082 -jar skywalking_springboot.jar &
命令解读:
-javaagent:/mnt/skwalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
指定上文我提到过的作用在java服务上的 agent的位置
-Dskywalking.agent.service_name=springboot
指定探针名字,后续与该服务相关的数据都会以改名字展现。 这也就是我们的Dockerfile那两段配置的含义
-jar skywalking_springboot.jar 就是启动springboot应用
& 表示在后台启动 日志还是会打印在前台,但是我们退出 应用依然会在后台运行
- 内部提供了两个接口 :
/sayBoot 返回Hello Boot!
/exception 返回的是一个除0异常
我们分别请求两个端口 我们是以8082端口启动的
http://ip:8082/sayBoot
http://ip:8082/exception
再次查看skywalking
- 当前服务展示的就是我们 在启动时加入 -Dskywalking.agent.service_name=springboot 指定的探针名字
- 端点为我们请求的接口
- 当前实例展示当前的服务所在IP和Pid。
追踪可以看到请求的调用链,和时长。这个调用链不长,我们换成另外两个jar包测试。
这两个是springboot集成dubbo。一个生产者一个消费者
- 按照刚才我启动的方式 大家可以自己尝试一下。
- (命令有空格 复制后需要调整一下)
- skywalking_dubbo_provider.jar
java -javaagent:/mnt/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=dubbo_provider
-Dserver.port=8083 -jar skywalking_dubbo_provider.jar &
- skywalking_dubbo_consumer.jar
java -javaagent:/mnt/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=dubbo_consumer
-Dserver.port=8084 -jar skywalking_dubbo_consumer.jar &
- 浏览器请求地址
http://ip:8085/hello
- 查看skywalking
这下就看到了,调用链每一层的请求,与关系。
- 关于skywalking的使用部分放在最后写,下面进入skywalking的agent原理部分。
java agent
- 概念:
java agent 是java命令的一个参数,他可以指定一个jar包,当java虚拟机启动时,在执行main函数之前,jvm会先运行jar包内 PremainClass 类的premain方法 。
这个类不一定叫做 PremainClass,是通过打包插件指定某个类,但是类中一定要有premain方法 - jar包条件
- 必须有MANIFEST.MF文件,必须包含 PremainClass类选项。
- PremainClass类选项指定的类必须包含permain方法。
自定义java agent
- 一般我们在统计某个方法或者某个接口的调用时长的时候,会使用SpringAOP技术(笔者是做Java的,只知道这玩意)。
- 我们可以仿照skywalking通过java agent来做这个链路时长统计
- 为了降低使用操作字节码技术的的成本 Byte Buddy对其进行了封装开发了一套java agent 提供我们使用,我们就使用其来操作
- 创建java agent工程 (maven 工程)
- pom文件 加入以下内容
<dependencies>
<!--ByteBuddy 坐标-->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--使用的插件名字-->
<artifactId>maven-assembly-plugin</artifactId>
<!--插件配置信息-->
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<!--无论这个项目添加什么依赖 都会被打入jar包-->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<!--自动添加META-INF/MANIFEST.MF-->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!--指定包含premain方法的类-->
<Premain-Class>PreMainAgent</Premain-Class>
<Agent-Class>PreMainAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 创建PreMainAgent类 内包含permain方法
- PreMainAgent
/**
* premain 有两个参数的 premain(String agentArgs, Instrumentation instrumentation)
* 还有一个参数的 写法为 premain(String agentArgs)
* 当两个方法都存在的时候 默认调用两个参数的方法 。当只有一个参数的方法存在时,调用一个参数的方法。
*/
public class PreMainAgent{
/**
*
* @param agentArgs
* @param instrumentation 个是java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口 也是这个包的核心
* 集中了其中几乎所有的功能方法,例如类定义的转换和操作等
*/
public static void premain(String agentArgs, Instrumentation instrumentation){
// 我们需要创建一个转换器,修改类的实现
// ByteBuddy对java agent 提供了转换器的实现 为AgentBuilder.Transformer 方法
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() { // 这是一个接口
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
// builder.method 指定哪些方法被拦截 需要ElementMatchers类型的参数
// ElementMatchers.<MethodDescription>any() 是所有方法被拦截
// intercept 自定义拦截器 需要MethodDelegation类型的参数
return builder.method(ElementMatchers.<MethodDescription>any())
.intercept(MethodDelegation.to(MyInterceptor.class));//我们自定义的拦截器类
}
};
//创建完拦截器后 通过ByteBuddy的AgentBuilder创建者来构建 agent 对象 指定拦截的类型 同时还需要指定转换器对象
// type属性 指定拦截的类型 这里我们选择 以 "com.ips" 开头的包 内的类都拦截
new AgentBuilder.Default().type(ElementMatchers.<TypeDescription>nameStartsWith("com.ips"))
// 传入上面我们创建的转换器 将拦截到的类交给transformer来处理
// installOn 将该方法 加载到 Instrumentation 让他根据我们的配置生效
.transform(transformer).installOn(instrumentation);
}
}
- 自定义的拦截器类MyInterceptor
public class MyInterceptor {
/**
* 因为这个方法是在运行中进行的 所以我们要加上 @RuntimeType
* 方法需为静态方法
* @param method 第一个参数是反射获取的方法 需要使用@Origin注解
* @param callable 第二个参数为Callable 需要@SuperCall 注解
* @return 返回类型为Callable对象 但是我们不清楚对象是什么状态的 所以这里使用Object
* @throws Exception
*/
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) throws Exception {
// 获取当前系统时间
Long start = System.currentTimeMillis();
//定义字符串
String msg;
try {
// 获取当前执行方法的结果
return callable.call();
}finally {
//计算方法执行时间 /1000.00 毫秒转换为秒 保留两位小数
double end = (System.currentTimeMillis() - start)/ 1000.00;
//如果超过一秒钟
if ( end > 1.0){
msg = "已超时。";
}else {
msg = "正常";
}
System.out.println(method.getName()+" 函数执行时长 : "+ end + "秒. 状态 : " + msg);
}
}
}
- 工程结构
使用maven打包
-
记录jar包位置
-
创建测试工程
-
注意包的名字 要与java agent premain方法里面指定的一样
-
编写main方法
public class JavaAgentTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("main执行");
// 睡眠1.5秒
Thread.sleep(1500L);
}
}
- 添加启动参数
- javaagent:C:\Users\92862\Desktop\skywalking\javaagentTime\target\javaagentTime-1.0-SNAPSHOT.jar
-
添加位置
- 如果大家没有这个 可以先把你创建的工程main方法执行一次就有了。
进入 ,这个位置添加启动参数。然后保存退出
-
再次启动main方法测试
- 成功
skywalking的使用
图里面写错了 是左键 我已经开始迷糊了
如果你使用了Skywalking提供我们Trace工具包
引入坐标
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>6.5.0</version>
</dependency>
API
//使当前链路报错,并且提示报错信息
ActiveSpan.error(new RuntimeException("Test-Error-Throwable"));
//打印info信息
ActiveSpan.info("Test-Info-Msg");
//打印debug信息
ActiveSpan.debug("Test-debug-Msg");
//返回traceId
return TraceContext.traceId();
这些详细信息都会在上图的那个位置展示出来
API 返回的traceId 也可以在这个位置搜索出来
告警功能
默认的告警规则配置
位于skywalking安装目录下的config文件夹下 alarm-settings.yml 文件
大家可以照着这个表自己看
在配置文件中还有这么一个接口
到触发告警的时候 他会去请求你写入的接口。
只支持post请求。
生产过程中,我们可以在该接口里面调用一些短信,邮件等服务,通知处理人员,提高故障处理速度。
指标对比 。选择服务 和粒度 (粒度比如:响应时间这些)
在上文中给大家看了 一个概念图
- 我们讲过一个请求就是一个tracing 这里也是的
- 每个颜色块 也叫做span,span是具有开始时间和执行时长的逻辑单元
(抄的)
Span里面的信息包括:操作的名字,开始时间和结束时间.
可以附带多个 key:value 构成的 Tags(key 必须是String,value可以是 String, bool 或者数字).
还可以附带 Logs 信息(不一定所有的实现都支持) 也是 key:value形式。
- span内的概念 (抄的)
一个span可以和一个或者多个span间存在因果关系。OpenTracing定义了两种关系:
ChildOf 和 FollowsFrom 。
这两种引用类型代表了子节点和父节点间的直接因果关系。未来,OpenTracing将支持非因果关系的span引用关系。(
例如:多个span被批量处理,span在同一个队列中,等等)
ChildOf 很好理解,就是父亲 Span 依赖另一个孩子 Span。比如函数调用,被调者是调用者的孩子,比如说 RPC 调用,服务端那边的Span,就是 ChildOf 客户端的。
很多并发的调用,然后将结果聚合起来的操作,就构成了 ChildOf 关系。
如果父亲 Span 并不依赖于孩子 Span 的返回结果,这时可以说它他构成 FollowsFrom 关系。
-
Log的概念(抄的)
每个span可以进行多次Logs操作,每一次Logs操作,都需要一个带时间戳的时间名称,以及可选的任 意大小的存储结构。 -
Tags的概念 (抄的)
每个span可以有多个键值对(key:value)形式的Tags,Tags是没有时间戳的,支持简单的对span进行 注解和补充。
如下图就是一个Tags的详细信息,其中记录了数据库访问的SQL语句等内容。