一、具体要求
- 在真实业务场景中,经常需要对各个业务接口的响应性能进行监控(常用指标为:TP90、TP99)
- 通过扩展Dubbo的Filter(TPMonitorFilter),完成简易版本 Dubbo 接口方法级性能监控,记录下TP90、TP99请求的耗时情况
| 要求 | 说明 |
|---|---|
| 1 | 提供一个Dubbo服务,提供3个方法(eat、sleep、beatBeanBean),每方法都实现了随机休眠0-100ms |
| 2 | 编写一个消费端程序,不断调用Dubbo服务的3个方法(建议利用线程池进行并行调用,确保在1分钟内可以被调用2000次以上) |
| 3 | 利用TPMonitorFilter在消费端记录每个方法的请求耗时时间(异步调用不进行记录) |
| 4 | 每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况 |
二、实现思路介绍

说明:api/consumer/provider 三个模块的内容 在这里就不多介绍了,感兴趣的可以到下方的源码下载中,下载没有过滤器的基础案例
三、自定义过滤器,实现方法拦截,统计方法执行时间,存储方法执行信息,计算TP90/99
步骤1:自定义拦截器TPNMonitorFilter,方法执行就拦截,利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
TPNMonitorFilter 实现接口Filter 重写invoke方法,在方法内部进行方法拦截,并计算方法耗时
//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{
/**
* 方法执行就拦截:利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
* @param invoker
* @param invocation
* @return
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//(具体实现看下方功能实现代码)
//获取方法执行的起始时间
//执行方法
//计算方法执行的耗时
//将当前的方法添加到全局map中,在map中指定一个集合进行记录当前方法执行情况
}
}
步骤2:创建一个集合存储三个方法执行得相关信息(包括:方法名称,方法执行耗时,方法执行结束时间)
//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{
/**
* 存储方法执行得相关信息(方法名称,方法执行耗时,方法执行结束时间)
*/
Map<String, List<MethodInfo>> methodTimes = new ConcurrentHashMap<>();
}
自定义bean存储方法执行信息
public class MethodInfo {
// 方法名
private String name;
// 执行耗时
private long times;
// 结束时间
private long endTimes;
public MethodInfo() {
}
public MethodInfo(String name, long times,long endTimes) {
this.name = name;
this.times = times;
this.endTimes = endTimes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTimes() {
return times;
}
public void setTimes(long times) {
this.times = times;
}
public long getEndTimes() {
return endTimes;
}
public void setEndTimes(long endTimes) {
this.endTimes = endTimes;
}
}
步骤3:自定义线程,计算tp90/tp99
TPNMonitorFilter 实现接口Runnable重写run方法,在方法内部进行方法拦截,并计算方法耗时
//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{
//在消费者端进行激活此拦截器
@Activate(group = {CommonConstants.CONSUMER})
public class TPNMonitorFilter implements Filter ,Runnable{
/**
* 线程方法
*/
@Override
public void run() {
//计算并打印最近1分钟内每个方法的TP90、TP99的耗时情况(具体实现看下方功能实现代码)
}
}
}
步骤4:配置filter的spi

此时整个自定义Filter的整理结构就介绍完了
功能实现1:每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
//每隔5s调用一次下方的run方法,统计计算一次1分钟内每个方法的TP90、TP99的耗时情况
public TPNMonitorFilter(){
// 创建定时线程,每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(this, 5,5, TimeUnit.SECONDS);
}
}
线程具体执行操作:
/**
* 线程方法
*/
@Override
public void run() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String dateStr = sdf.format(date);
/**
* 线程使用情况
*/
for(Map.Entry<String,List<MethodInfo>> methodInfos : methodTimes.entrySet()){
System.out.println(dateStr+ methodInfos.getKey() +"的TP90:" + getTP(methodInfos.getValue(),0.9) + "毫秒,"
+ "TP99:" + getTP(methodInfos.getValue(),0.99)+ "毫秒" );
}
}
TP90、TP99的耗时情况计算:
/**
* 后续会每隔5s调用一次此方法,打印最近1分钟内每个方法的TP90、TP99的耗时情况
*
* 计算tp90和tp99
* @param methodInfos
* @param rate 代表百分比 90 传入 0.9 即可 99 就是 0.99
* @return
*/
private long getTP(List<MethodInfo> methodInfos,double rate){
// 构建一个临时集合保存 用于满足1一分钟之内的数据
List<MethodInfo> sortInfo = new ArrayList<>();
// 计算最近一分钟的TP90 和 TP99
long endTime = System.currentTimeMillis();
long startTime = System.currentTimeMillis() - 60000;
// 遍历列表集合
int length = methodInfos.size();
for (int i=0;i<length;i++){
MethodInfo methodInfo = methodInfos.get(i);
if (methodInfo.getEndTimes() >= startTime && methodInfo.getEndTimes() <= endTime){
//将满足条件的方法信息存储到临时集合中
sortInfo.add(methodInfo);
}
}
//对满足1一分钟之内的数据进行排序
sortInfo.sort(new Comparator<MethodInfo>() {
@Override
public int compare(MethodInfo o1, MethodInfo o2) {
if(o1.getTimes() > o2.getTimes()){
return 1;
}else if(o1.getTimes() < o2.getTimes()){
return -1;
}else{
return 0;
}
}
});
//获取当前排序后集合中的指定百分比数值的位置,此位置存储的数据就是当前计算的tp90/99
int index = (int)(sortInfo.size() * rate);
return sortInfo.get(index).getTimes();
}
功能实现2:方法执行就拦截,利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
/**
* 方法执行就拦截:利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
* @param invoker
* @param invocation
* @return
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获取执行方法名
Result result = null;
Long takeTime = 0L;
try
{
//获取方法执行的起始时间
Long startTime = System.currentTimeMillis();
//执行方法
result = invoker.invoke(invocation);
if (result.getException() instanceof Exception) {
throw new Exception(result.getException());
}
//计算方法执行的耗时
takeTime = System.currentTimeMillis() - startTime;
}
catch (Exception e)
{
e.printStackTrace();
return result;
}
System.out.println("method="+invocation.getMethodName() + "消耗的时间:"+takeTime +"毫秒");
String methodName = invocation.getMethodName();
List<MethodInfo> methodInfos = methodTimes.get(methodName);
//方法执行第一次的时候,创建空集合进行存储
if (methodInfos == null){
methodInfos = new ArrayList<>();
methodTimes.put(methodName,methodInfos);
}
//将当前的方法添加到map中指定的记录当前方法执行情况的集合中
methodInfos.add(new MethodInfo(invocation.getMethodName(),takeTime,System.currentTimeMillis()));
return result;
}
运行程序
- 启动zookeepr
2. 启动provider
3. 启动consumer

源码下载
| 案例 | 地址 |
|---|---|
| 基础案例 | 下载地址:gitee代码下载 |
| 功能实现案例 | 下载地址:gitee代码下载 |

1608





