cat3.0安装配置集成springboot+mybatis
一、环境清单
CentOS 7、mysql5.6、安装jdk8、安装maven3以上、安装tomcat8、安装git
二、cat服务器端安装
1、下载源码
原来下载主要会用到里面的一些资料
a、数据库脚本;
b、客户端包cat_client.jar;
gitee
git clone https://gitee.com/mirrors/CAT.git (国内镜像速度快)
或者
github
git clone https://github.com/dianping/cat
2、下载服务器端包cat.war
http://unidal.org/nexus/service/local/repositories/releases/content/com/dianping/cat/cat-home/3.0.0/cat-home-3.0.0.war 记得重命名为cat.war
mv cat-home-3.0.0.war cat.war
3、发布cat.war包前的准备工作
a、数据库初始化
创建cat库》找到源码中根目录下script/CatApplication.sql文件》在cat库中执行脚本
b、创建配置文件目录(注意目录必须有读写权限)
/data/appdatas/cat ,/data/applogs/cat,然后权限
chmod 777 /data/ -R
c、 进入/data/appdatas/cat 目录,新建server.xml,datasources.xml配置文件
vim server.xml
1 |
|
vim datasources.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<data-sources>
<data-source id="cat">
<maximum-pool-size>3</maximum-pool-size>
<connection-timeout>1s</connection-timeout>
<idle-timeout>10m</idle-timeout>
<statement-cache-size>1000</statement-cache-size>
<properties>
<driver>com.mysql.jdbc.Driver</driver>
<url><![CDATA[jdbc:mysql://127.0.0.1:3306/cat]]></url> <!-- 请替换为真实数据库URL及Port -->
<user>root</user> <!-- 请替换为真实数据库用户名 -->
<password>root</password> <!-- 请替换为真实数据库密码 -->
<connectionProperties><![CDATA[useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&socketTimeout=120000]]></connectionProperties>
</properties>
</data-source>
</data-sources>
d、发布cat.war包
修改tomcat server.xml配置编码1
2
3<Connector port="8080" protocol="HTTP/1.1"
URIEncoding="utf-8" connectionTimeout="20000"
redirectPort="8443" /> <!-- 增加 URIEncoding="utf-8" -->
将cat.war包拷贝到tomcat 的webapps目录中
启动tomcat
http://ip:8080/cat/s/config?op=routerConfigUpdate
记得开放你的8080端口
默认用户名admin admin
e、配置客户端路由1
2
3
4
5
6
7
8
9
<router-config backup-server="cat服务器端的ip" backup-server-port="2280">
<default-server id="cat服务器端的ip" weight="1.0" port="2280" enable="true"/>
<network-policy id="default" title="default" block="false" server-group="default_group">
</network-policy>
<server-group id="default_group" title="default-group">
<group-server id="cat服务器端的ip"/>
</server-group>
</router-config>
配置完成后需要重启tomcat
在application标签中如果能出现下图 则说明服务器端安装成功了
很多网上的事例都只到这步,其实真正使用cat才只得一点准备工作呢,下面我们来看看客户端集成
三、客户端集成
本文主要介绍集成监控spirngboot项目、Service、mybatis。
1、本地jar包上传到仓库
使用源码中 lib/java/jar中的cat_client.jar包上传到仓库;你也可以通过源码打包,但是打包过程可能会出现依赖包下载不下来的情况,建议配置阿里云镜像。
mvn deploy:deploy-file -DgroupId=com.dianping.cat -DartifactId=cat-client -Dversion=3.0.0 -Dpackaging=jar -Dfile=C:\Users\ssd\Desktop\cat-client-3.0.0.jar -Durl=http://{ip}}:{port}/content/repositories/thirdparty/ -DrepositoryId=thirdparty
本地操作时不需要-Durl参数,注意修改-Dfile参数为你自己的目录
2、项目中引入客户端依赖包1
2
3
4
5
6
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.0.0</version>
</dependency>
3、项目更目录下创建/data/appdatas/cat ,/data/applogs/cat,同样需要读写权限
chmod 777 /data/ -R
如果你的项目和服务端发布在一起的话,这不需要本步。如果是不同的机器
那么请在/data/appdatas/cat中创建server.xml
1 |
|
4、将下面的类放到springboot能扫描到的目录下
新建CatFilterConfigure.java类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import com.dianping.cat.servlet.CatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by zhengwenzhu on 2017/1/17.
*/
public class CatFilterConfigure {
public FilterRegistrationBean catFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
CatFilter filter = new CatFilter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("cat-filter");
registration.setOrder(1);
return registration;
}
}
新建OAClientConfigProvider.java类
1 | package com.tanqp.demo.config; |
5、在resources中新建META-INF/services目录
META-INF目录下新建app.properties 内容如下:
app.name=test
services目录中添加文件名为com.dianping.cat.configuration.ClientConfigProvider的文件 内容如下
com.tanqp.demo.config.MyClientConfigProvider
6、集成mybatis,在config配置目录下(springboot能扫描到即可)添加如下类
CatMybatisPlugin.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277package com.tanqp.demo.config;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.mybatis.spring.transaction.SpringManagedTransaction;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
/**
* https://github.com/fanlychie/cat-client-mybatis
*/
({
(
method = "query",
type = Executor.class,
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
(
method = "update",
type = Executor.class,
args = {MappedStatement.class, Object.class}
)
})
public class CatMybatisPlugin implements Interceptor {
private static Log logger = LogFactory.getLog(CatMybatisPlugin.class);
//缓存,提高性能
private static final Map<String, String> sqlURLCache = new ConcurrentHashMap<>(256);
private static final String EMPTY_CONNECTION = "jdbc:mysql://unknown:3306/%s?useUnicode=true";
private Executor target;
// druid 数据源的类名称
private static final String DruidDataSourceClassName = "com.alibaba.druid.pool.DruidDataSource";
// dbcp 数据源的类名称
private static final String DBCPBasicDataSourceClassName = "org.apache.commons.dbcp.BasicDataSource";
// dbcp2 数据源的类名称
private static final String DBCP2BasicDataSourceClassName = "org.apache.commons.dbcp2.BasicDataSource";
// c3p0 数据源的类名称
private static final String C3P0ComboPooledDataSourceClassName = "com.mchange.v2.c3p0.ComboPooledDataSource";
// HikariCP 数据源的类名称
private static final String HikariCPDataSourceClassName = "com.zaxxer.hikari.HikariDataSource";
// BoneCP 数据源的类名称
private static final String BoneCPDataSourceClassName = "com.jolbox.bonecp.BoneCPDataSource";
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//得到类名,方法
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
Transaction t = Cat.newTransaction("SQL", methodName);
//得到sql语句
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sql = showSql(configuration, boundSql);
String s = this.getSQLDatabase();
Cat.logEvent("SQL.Database", s);
//获取SQL类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, sql);
Object returnObj = null;
try {
returnObj = invocation.proceed();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
Cat.logError(e);
} finally {
t.complete();
}
return returnObj;
}
private javax.sql.DataSource getDataSource() {
org.apache.ibatis.transaction.Transaction transaction = this.target.getTransaction();
if (transaction == null) {
logger.error(String.format("Could not find transaction on target [%s]", this.target));
return null;
}
if (transaction instanceof SpringManagedTransaction) {
String fieldName = "dataSource";
Field field = ReflectionUtils.findField(transaction.getClass(), fieldName, javax.sql.DataSource.class);
if (field == null) {
logger.error(String.format("Could not find field [%s] of type [%s] on target [%s]",
fieldName, javax.sql.DataSource.class, this.target));
return null;
}
ReflectionUtils.makeAccessible(field);
javax.sql.DataSource dataSource = (javax.sql.DataSource) ReflectionUtils.getField(field, transaction);
return dataSource;
}
logger.error(String.format("---the transaction is not SpringManagedTransaction:%s", transaction.getClass().toString()));
return null;
}
/**
* 重写 getSqlURL 方法
*
* @author fanlychie (https://github.com/fanlychie)
*/
private String getSqlURL() {
// 客户端使用的数据源
javax.sql.DataSource dataSource = this.getDataSource();
if (dataSource != null) {
// 处理常见的数据源
switch (dataSource.getClass().getName()) {
// druid
case DruidDataSourceClassName:
return getDataSourceSqlURL(dataSource, DruidDataSourceClassName, "getUrl");
// dbcp
case DBCPBasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCPBasicDataSourceClassName, "getUrl");
// dbcp2
case DBCP2BasicDataSourceClassName:
return getDataSourceSqlURL(dataSource, DBCP2BasicDataSourceClassName, "getUrl");
// c3p0
case C3P0ComboPooledDataSourceClassName:
return getDataSourceSqlURL(dataSource, C3P0ComboPooledDataSourceClassName, "getJdbcUrl");
// HikariCP
case HikariCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, HikariCPDataSourceClassName, "getJdbcUrl");
// BoneCP
case BoneCPDataSourceClassName:
return getDataSourceSqlURL(dataSource, BoneCPDataSourceClassName, "getJdbcUrl");
}
}
return null;
}
/**
* 获取数据源的SQL地址
*
* @param dataSource 数据源
* @param runtimeDataSourceClassName 运行时真实的数据源的类名称
* @param sqlURLMethodName 获取SQL地址的方法名称
*/
private String getDataSourceSqlURL(DataSource dataSource, String runtimeDataSourceClassName, String sqlURLMethodName) {
Class<?> dataSourceClass = null;
try {
dataSourceClass = Class.forName(runtimeDataSourceClassName);
} catch (ClassNotFoundException e) {
}
Method sqlURLMethod = ReflectionUtils.findMethod(dataSourceClass, sqlURLMethodName);
return (String) ReflectionUtils.invokeMethod(sqlURLMethod, dataSource);
}
private String getSQLDatabase() {
// String dbName = RouteDataSourceContext.getRouteKey();
String dbName = null; //根据设置的多数据源修改此处,获取dbname
if (dbName == null) {
dbName = "DEFAULT";
}
String url = CatMybatisPlugin.sqlURLCache.get(dbName);
if (url != null) {
return url;
}
url = this.getSqlURL();//目前监控只支持mysql ,其余数据库需要各自修改监控服务端
if (url == null) {
url = String.format(EMPTY_CONNECTION, dbName);
}
CatMybatisPlugin.sqlURLCache.put(dbName, url);
return url;
}
/**
* 解析sql语句
*
* @param configuration
* @param boundSql
* @return
*/
public String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
}
}
}
}
return sql;
}
/**
* 参数解析
*
* @param obj
* @return
*/
private String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
public Object plugin(Object target) {
if (target instanceof Executor) {
this.target = (Executor) target;
return Plugin.wrap(target, this);
}
return target;
}
public void setProperties(Properties properties) {
}
}
7、登录cat服务器端,配置项目 如下图

8 全局配置》消息采样配置
1 |
|
发布你的项目,并发送几个请求,出现如下图则说明成功了,图中显示的service需要做第10步的操作;
10、编写aop监控service
ServiceAspect.java
1 | package com.tanqp.demo.aop; |
四、总结
1、如果项目是从头开始,需要定制监控某些接口,那么可以使用cat,cat更多的是使用它的埋点监控,对于已经上线的项目建议使用pinpoint(0代码侵入监控);
2、另外基于mybatis拦截器的监控 没有select的,只有update和insert,当然可以自己写关键的sql发送到服务器端;
3、侵入式,存在代码改动;

