正如笔者在自己的开源项目【CAT魔改】CAT-LOCAL项目的诞生中所介绍的:
CAT服务端展示层的慢请求过滤时长极限值为5秒钟,但在笔者所参与的部分业务逻辑中,其时间相对要长不少,这导致曾经项目组进行性能优化时,需要额外花费较多时间筛选出真正异常的请求。"
本文尝试突破以上约束,提供相应的解决方案,同时介绍一些相关的CAT底层实现原理。
样例图如下(在原有限度基础上我们新增了10,20,30秒时长的过滤;其它诸如long-sql等同理):
能找到这里的基本都是奔着解决方案来的,所以按照惯例,我们先给出解决方案。
在 LongExecutionProblemHandler
类中给字段m_defaultLongUrlDuration
添加额外的默认值(例如笔者这里新增的 10000(10秒) ):
public class LongExecutionProblemHandler extends ProblemHandler implements Initializable {
public static final String ID = "long-execution";
@Inject
private ServerConfigManager m_configManager;
// 对应cat-home中problem报表页面下的 long-service查询条件 (注意这里的service指代的是remoteService调用,而不是MVC里的Service层; 与下面的longCall呼应)
private int[] m_defaultLongServiceDuration = {
50, 100, 500, 1000, 3000, 5000 };
// 对应long-sql查询条件
private int[] m_defaultLongSqlDuration = {
100, 500, 1000, 3000, 5000 };
// ============================================= 下面这个数组就是我们唯一需要修改的位置
// 对应long-url查询条件; 注意 10000是笔者自己加的
private int[] m_defaultLongUrlDuration = {
1000, 2000, 3000, 5000,10000 };
// 对应long-call查询条件
private int[] m_defalutLongCallDuration = {
100, 500, 1000, 3000, 5000 };
// 对应long-cache查询条件
private int[] m_defaultLongCacheDuration = {
10, 50, 100, 500 };
.....
}
修改完成之后,编译项目重新部署即可。
我们先来个总结:
LongExecutionProblemHandler
中定义——m_defaultLongUrlDuration
字段),而并非我们的第一反应里的"在用户实际查询时候做",毕竟后者意味着每次相关请求处理都得全量筛查一遍,这是不合理的。ProblemStatistics.getDurationsByType(String type, Entity entity)
方法中找到端倪。(结合 problemStatics.jsp
页面中的 ${model.allStatistics.status}
的取值层级, 以及上面的 com.dianping.cat.report.page.problem.Model
中字段的层级 : Model(model字段名) > ProblemStatistics(allStatistics字段名) > TypeStatistics(status字段名) > StatusStatistics( XXX )
能更快理解)。接下来让我们更为近距离地观察一些核心类中的实现逻辑。
LongExecutionProblemHandler
类。该类就是负责我们上面原理总结里提到的"接收到客户端推送来的日志信息时对其进行超时限请求的分类"。
该类隶属于ProblemAnalyzer
(作为MessageAnalyzer
的子类,它负责对请求日志进行Problem维度的分析)。
private void processLongUrl(Machine machine, Transaction transaction, MessageTree tree) {
long duration = (transaction).getDurationInMillis();
String domain = tree.getDomain();
// 这是关键性的一步
long nomarizeDuration = computeLongDuration(duration, domain, m_defaultLongUrlDuration, m_longUrlThresholds);
if (nomarizeDuration > 0) {
String type = ProblemType.LONG_URL.getName();
String status = transaction.getName();
Entity problem = findOrCreateEntity(machine, type, status);
updateEntity(tree, problem, (int) nomarizeDuration);
}
}
public int computeLongDuration(long duration, String domain, int[] defaultLongDuration,
Map<String, Integer> longThresholds) {
int[] messageDuration = defaultLongDuration;
// 优先进行既定规则的分析;将默认规则进行倒序地比较,判断当前请求日志耗时落在哪个区间
// 以long-url为例,如果是 9000则落在5000这个区间; 4500则落在3000这个区间,以此类推。
for (int i = messageDuration.length - 1; i >= 0; i--) {
if (duration >= messageDuration[i]) {
return messageDuration[i];
}
}
// 如果本次请求日志耗时没有落在上面的规则里(注意:此时就只可能是当前请求日志耗时比上面数组里的最小值还要小,这也是为啥笔者选择修改源码的主要原因)
// longThresholds字段值的来源,其实是在 http://context:port/cat/s/config?op=serverConfigUpdate 下的:
//
//
//
//
//
//
Integer value = longThresholds.get(domain);
if (value != null && duration >= value) {
return value;
} else {
return -1;
}
}
ProblemStatistics
类。这个类负责我们上面原理总结里提到的"对应用户查询请求,将大于指定时限的那几类日志统计出来",举个例子就是,如果用户要求查询大于3秒的请求,则返回 3秒,5秒两个类别的日志(这个分类在 LongExecutionProblemHandler
中已经做完了);而如果用户要求查询大于6秒的请求,则返回空。(因为默认情况下CAT是没有"大于6秒"这一分类)
private List<Duration> getDurationsByType(String type, Entity entity) {
List<Duration> durations = new ArrayList<Duration>();
if (ProblemType.LONG_URL.getName().equals(type)) {
// 注意这里Integer类型的key, 以long-url为例,就是上面的{ 1000, 2000, 3000, 5000 } 中的一个;这也是为什么你直接在cat-home前端修改 urlThreshold 的传参是无效的;因为key的最大值只到5;除非修改源码,让CAT一开始就对日志进行单独分类
for (java.util.Map.Entry<Integer, Duration> temp : entity.getDurations().entrySet()) {
if (temp.getKey() >= m_longConfig.getUrlThreshold()) {
durations.add(temp.getValue());
}
}
} else {
durations.add(entity.getDurations().get(0));
}
return durations;
}
该package下对应的就是Problem这一报表功能的后端实现:
com.dianping.cat.report.page.problem.ProblemReport
。我们看到的Problem报表里的信息,对应到内存中就是一个 ProblemReport
实例。com.dianping.cat.report.page.problem.Payload
接收Problem报表前端页面向后端发起请求时携带的参数。例如其中的 m_urlThreshold
字段正是我们所寻找的时长过滤/jsp/report/problem/problemStatics.jsp
这是 Problem前端展示页面。