简介
- CAT是一个实时和接近全量的监控系统(调用链监控,不适用于全链路监控),它侧重于对Java应用的监控。提供系统的性能指标、健康状况、监控告警等功能
- github、深入详解美团点评CAT跨语言服务监控
- CAT服务端不可用时,不会影响客户端执行;待服务端重启成功后客户端会将不可用期间的日志重新发给服务端
- 客户端将监控日志上传到服务端,服务端先存储在内存中,定期会将上一个小时的数据落到数据库中(hourlyreport、hourly_report_content),天/周/月数据则在凌晨进行计算落库
- 报表类型
- Transaction报表:一段代码运行时间、次数、失败率、QPS,比如URL、Cache、SQL执行次数和响应时间
- Event报表:一行代码运行次数、失败次数,如Exception出现次数。Event报表的整体结构与Transaction报表几乎一样,只缺少响应时间的统计
- Problem报表:根据Transaction/Event数据分析出来系统可能出现的异常,包括访问较慢的程序等
- Heartbeat报表:JVM内部一些状态信息,比如Memory,Thread等
- Business报表:使用Metric实现业务监控报表,比如订单指标,支付等业务指标。与Transaction、Event、Problem不同,Business更偏向于宏观上的指标,另外三者偏向于微观代码的执行情况
- Cross报表:分布式调用统计
- Transaction、Event、Problem都可分成两类:一级分类(Type)、二级分类(Name)
- Type常见如:URL、SQL、Call、Method、Cache、Task、RemoteCall、PigeonCall、PigeonService
整体设计
- 在实际开发和部署中,cat-consumer和cat-home是部署在一个jvm内部
安装(服务端)
基于docker安装服务端
- 基于
cat v3.0.0
进行测试,参考:https://github.com/dianping/cat/wiki/readme_server - docker-compose.yml,并启动(docker所在宿主机内网ip为192.168.6.10;数据库sq-mysql和自定义网络sq-net创建此处省略)
1 | version: '3' |
- 将
/home/data/cat
设置为可读写chmod 777 /home/data/cat
- 创建
/home/data/cat/appdatas/cat/datasources.xml
1 | "1.0" encoding="utf-8" xml version= |
- 将cat源码的
script/CatApplication.sql
文件导入到mysql的cat数据库中 - 下载cat-home.war到docker-compose.yml所在目录,重命名为
cat.war
(mv cat-home-3.0.0.war cat.war) - 启动容器
docker-compose up -d
- 部署war包
docker cp cat.war sq-tomcat:/usr/local/tomcat/webapps
(每次重新创建了tomcat容器都必须重新部署) - 访问
http://192.168.6.10:8888/cat
- 配置(未配置访问Transaction菜单等会报错),默认用户名密码为
admin/admin
。具体参考下文管理界面使用- 访问
http://192.168.6.10:8888/cat/s/config?op=serverConfigUpdate
进行服务端配置:修改ip为192.168.6.10(视情况修改),启动hdfs的ip可以不用考虑(默认关闭hdfs) - 访问
http://192.168.6.10:8888/cat/s/config?op=routerConfigUpdate
进行客户端路由配置:修改ip为192.168.6.10 - 重启tomcat
- 访问
基于K8S安装
- Dockerfile
1 | FROM bzyep49h.mirror.aliyuncs.com/library/tomcat:jdk8 |
- hlem chart参考
- helm安装后会自动申请PVC,找到对应的PV,并创建 cat/datasources.xml 文件(具体参考上文);然后重新启动POD(之后无需重启)
常见问题
- 基于docker安装,服务端界面显示
出问题CAT的服务端:[192.168.6.10]
,这个显示不影响数据上报和监控,仅仅是IP配置不规范。主要是CAT默认使用获取的内网IP,则此时为docker容器所在宿主机IP,此时可设置host.ip
- 解决办法:在启动参数中加
-Dhost.ip=192.168.6.10
- 解决办法:在启动参数中加
- 加入Cat依赖,客户端启动报错
java.lang.NoClassDefFoundError: org/aspectj/util/PartialOrder$PartialComparable
,表示缺少aspectjweaver
相关jar包- 解决办法:如springboot引入
org.springframework.boot#spring-boot-starter-aop
依赖
- 解决办法:如springboot引入
使用(客户端)
Windows下IDEA启动
- 参考:https://github.com/dianping/cat/blob/master/lib/java/README.zh-CN.md
- 创建文件
D:/data/appdatas/cat/client.xml
- 如果无此文件,Java应用仍然可以正常启动运行
- 此处D盘和tomcat运行盘符一致,或者设置CAT_HOME=D:/data/appdatas/cat环境变量
- windows环境一般为
D:/data/appdatas
,linux系统则为/data/appdatas/cat目录 - 生成的运行日志文件位于
D:/data/applogs/cat
(会自动创建文件夹,和appdatas同级目录) - 部署了多个客户端时,此配置文件和日志文件可以共用
- 客户端启动后会在appdatas目录创建一个1M的
sq-test.mark
二进制文件
1 | "1.0" encoding="utf-8" xml version= |
- maven
1 | <!-- |
- 创建
src/main/resources/META-INF/app.properties
,并写入app.name=sq-test
- 在测试项目中写入埋点代码,即可进行测试(或者参考下文集成方案进行部分场景自动埋点)
1 | public void test() { |
客户端插件集成
- 参考:https://github.com/dianping/cat/tree/v3.0.0/integration
- 对所有的URL路径进行拦截。此时后端API暴露的路径都会进行上报统计,也可额外添加对某个路径的自定义拦截
1 | // https://github.com/dianping/cat/blob/v3.0.0/integration/spring-boot/CatFilterConfigure.java |
- 对SQL进行拦截。此时可以对有的SQL语句进行上报统计
- mybatis。会自动生成
URL
类型的Transaction、Event日志 - 从上述链接复制mybatis插件源码
CatMybatisPlugin.java
- 如果数据库连接池使用的是HikariDataSource,则修改源码中
switchDataSource
方法,可去掉DruidDataSource
判断,并加入HikariDataSource判断:if(dataSource instanceof HikariDataSource) { url = ((HikariDataSource) dataSource).getJdbcUrl(); }
- 如果数据库连接池使用的是HikariDataSource,则修改源码中
- 加入插件
<plugin interceptor="cn.aezo.test.plugin.CatMybatisPlugin"/>
- 如果是使用mybatis-plus则直接在上述类上增加
@Component
即可注入,无需xml配置
- 如果是使用mybatis-plus则直接在上述类上增加
- mybatis。会自动生成
- 与日志框架整合记录Event。此时可以对
logger.error
类型的日志进行上报统计,可以代替Cat.logError(e);
以减少代码量- logback
- 从上述链接复制logback插件源码
CatLogbackAppender.java
,并去掉Cat.logTrace
相关代码(Cat3.0不支持) - 在logback.xml文件中加入对应的Appender和appender-ref
- 注意:logback记录日志的时候需要传入异常对象,如果不传无法在cat中的problem展示错误信息
- 正确的如
logger.error(e.getMessage(), e);
生成的type为error;name(status栏)则无法自定义,自动取e对应的类名 - 错误的如
logger.error("error...");
不会上报的cat
- 正确的如
分布式调用链监控
- 实现Cat.Context接口用来存储RootId(用于标识唯一的一个调用链)、ParentId(谁在调用我)、ChildId(我在调用谁) ^1
- 客户端和服务端基于Header传递上述ID
在Cat中内置了两个方法
Cat.logRemoteCallClient()
以及Cat.logRemoteCallServer()
,可以简化处理逻辑源码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 客户端需要创建一个Context,然后初始化三个ID放入到此Context中
public static void logRemoteCallClient(Context ctx, String domain) {
try {
MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
String messageId = tree.getMessageId();
if (messageId == null) {
messageId = Cat.createMessageId();
tree.setMessageId(messageId);
}
// 生成一个 childId,需要将其放置在如Header中传递到服务端,服务端接受后将此ID设置成自己的MessageId
String childId = Cat.getProducer().createRpcServerId(domain);
// 如果 Event.type 为 CatConstants.TYPE_REMOTE_CALL="RemoteCall" 时,CAT图表中才会显示 "[:: show ::]"
Cat.logEvent(CatConstants.TYPE_REMOTE_CALL, "", Event.SUCCESS, childId);
String root = tree.getRootMessageId();
if (root == null) {
root = messageId;
}
ctx.addProperty(Context.ROOT, root);
ctx.addProperty(Context.PARENT, messageId);
ctx.addProperty(Context.CHILD, childId);
} catch (Exception e) {
errorHandler(e);
}
}
// 服务端需要接受这个context,然后设置到自己的Transaction中
public static void logRemoteCallServer(Context ctx) {
try {
MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
String childId = ctx.getProperty(Context.CHILD);
String rootId = ctx.getProperty(Context.ROOT);
String parentId = ctx.getProperty(Context.PARENT);
if (parentId != null) {
tree.setParentMessageId(parentId);
}
if (rootId != null) {
tree.setRootMessageId(rootId);
}
if (childId != null) {
tree.setMessageId(childId);
}
} catch (Exception e) {
errorHandler(e);
}
}RestTemplate调用服务使用示例
源码如下
1 | // ## 客户端:每次发送请求之前将上述3个ID放入到 Header 中 |
异步/主子线程监控问题
- Hystrix处理时会产生子线程,而主子线程中的MessageTree是不同的。主要是Cat将MessageTree存储在ThreadLocal中
- Feign + Hystrix组合使用时,Hystrix调用服务时是在子线程中完成的,单独使用Feign不会产生子线程。
feign.hystrix.enabled: false
关闭feign对hystrix支持 Hystrix
主子线程传值解决方案 ^2
源码如下
1 |
|
管理界面使用
- 项目配置信息
- 项目基本信息
- 新增或者基于app.name查找项目(暂时没有一个列表进行展示)
- CAT上项目名称
事业部-产品线
是分组,在展示界面可基于此分组展示- 如果客户端只是在
app.properties
中配置app.name
则会归并到Default-Default
的事业部和产品线
- 项目基本信息
全局系统配置(配置信息均保存在数据库的config表中)
- 服务端配置
- 修改remote-servers的值为,如192.168.6.10:8888 和
<server id="192.168.6.10">
(修改为可对外访问的IP,如容器的宿主机IP),启动hdfs的ip可以不用考虑(默认关闭hdfs)。需重启tomcat
- 修改remote-servers的值为,如192.168.6.10:8888 和
客户端路由配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15"1.0" encoding="utf-8" xml version=
<router-config backup-server="192.168.6.10" backup-server-port="2280">
<default-server id="192.168.6.10" weight="1.0" port="2280" enable="true"/>
<network-policy id="default" title="默认" block="false" server-group="default_group">
</network-policy>
<server-group id="default_group" title="default-group">
<group-server id="192.168.6.10"/>
</server-group>
<!-- 客户端会基于HTTP请求服务端,获取数据上报的TCP地址和端口。基于传入参数domain进行判断,无则使用默认default-server -->
<domain id="sq-test">
<group id="default">
<server id="192.168.6.10" port="2280" weight="1.0"/>
</group>
</domain>
</router-config>
- 服务端配置
修改默认admin账号密码:可修改cat-home源码后重新编译,参考:http://www.bubuko.com/infodetail-3091160.html
- 邮件告警需要自行启动邮件发送服务,参考:https://github.com/dianping/cat/blob/master/integration/cat-alert/README.md
源码分析
- 本地启动:把cat-home添加到tomcat中
原理说明
- 基于plexus容器,类似Spring的IOC容器
- 相关类
- UserConfigManager
- TimerSyncTask
相关源码
1 | // ## cat-home |
参考文章