简易版Dubbo方法级性能监控(TP90、TP99)
代码:https://gitee.com/szile20/lagou_stage3_module3
一、实现思路
- 创建API模块,提供consumer和provider公用的服务接口及服务方法。consumer模块和provider模块依赖该模块。
- 创建filter模块,创建性能监控Filter类,实现TP90和TP99的性能监控。通过dubbo的SPI功能暴露Filter。consumer模块依赖该模块。
- 该模块功能实现是最难的且是最主要的。
- 通过定时任务每5秒中计算一次TP90和TP99数据。这里使用
SingleThreadScheduledExecutor
实现定时任务。 - 计算TP90和TP99需要存储过去一分钟每个方法调用的响应时间,并进行排序。这里使用
ConcurrentHashMap<String, DelayQueue<SpentTime>>
来存储每个方法每次响应时间。Map的key为方法名,Map的value为方法响应时间数据。SpentTime
类实现了java.util.concurrent.Delayed
接口,存放方法响应时间及过期时间。这里为什么使用DelayQueue
?这是因为DelayQueue
的数据有过期时间,可以移除过期数据。
- 创建provider模块,依赖API模块,实现服务方法。向zookeeper注册服务。
- 创建consumer模块,依赖API模块,从zookeeper中获取服务,调用服务方法。
二、代码实现
1. API模块
创建服务接口,提供服务方法。
public interface CustomServiceApi {
String method1() throws InterruptedException;
String method2() throws InterruptedException;
String method3() throws InterruptedException;
}
2. filter模块
-
创建
TPMonitorFilter
类,实现org.apache.dubbo.rpc.Filter
接口,显示TP90和TP99性能监控。定时任务的主要步骤:
- 获取方法响应时长数据队列:delayQueue
- 移除已经过期的数据
- 转为数组
- 排序
- 计算TP90和TP99
@Activate(group = {CommonConstants.CONSUMER}) // 标明在consumer端启用 public class TPMonitorFilter implements Filter { /*存放每个方法的响应时间数据*/ private final Map<String, DelayQueue<SpentTime>> mappedMethodSpentTime = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); { System.out.println("----开始执行TPMonitor----"); // 启动定时任务 scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)); for (Map.Entry<String, DelayQueue<SpentTime>> entry : mappedMethodSpentTime.entrySet()) { final DelayQueue<SpentTime> delayQueue = entry.getValue(); // 移除已经过期的数据 delayQueue.removeIf(new Predicate<SpentTime>() { @Override public boolean test(SpentTime spentTime) { return spentTime.getDelay(TimeUnit.MILLISECONDS) <= 0; } }); // 取出数据转为数组 final SpentTime[] spentTimes = delayQueue.toArray(new SpentTime[]{}); // 转为List final List<SpentTime> spentTimeList = Arrays.asList(spentTimes); // 排序 Collections.sort(spentTimeList, (o1, o2) -> (int) (o1.getTime() - o2.getTime())); final int size = spentTimeList.size(); System.out.println(entry.getKey() + "() -- TP90:" + spentTimeList.get((int) Math.ceil(0.9d * size)).getTime() + "毫秒 -- TP99:" + spentTimeList.get((int) Math.ceil(0.99d * size)).getTime() + "毫秒, 当前有效个数:" + size); } } }, 30, 5, TimeUnit.SECONDS); // 初始延迟30秒之后开始每5秒执行一次 } @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { long startTime = System.currentTimeMillis(); Result invoke = null; try { invoke = invoker.invoke(invocation); } catch (RpcException e) { e.printStackTrace(); } finally { final long spentTime = System.currentTimeMillis() - startTime; final String serviceName = invocation.getServiceName(); final String methodName = invocation.getMethodName(); final String key = serviceName + "." + methodName; DelayQueue<SpentTime> delayQueue = mappedMethodSpentTime.get(key); if (delayQueue == null) { delayQueue = new DelayQueue<>(); } // 记录方法响应时间,过期时间60秒 delayQueue.put(new SpentTime(spentTime, 60 * 1000)); mappedMethodSpentTime.put(key, delayQueue); } return invoke; } static class SpentTime implements Delayed { /* 响应时长 毫秒*/ private final long time; /* 过期时间 */ private final long expire; SpentTime(long time, long delay) { this.time = time; this.expire = System.currentTimeMillis() + delay; } public long getTime() { return time; } @Override public long getDelay(TimeUnit unit) { return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { final SpentTime o1 = (SpentTime) o; return (int) (this.getTime() - o1.getTime()); } } }
-
使用dubbo的SPI暴露服务
在resources下创建/META-INF/dubbo文件夹,然乎创建org.apache.dubbo.rpc.Filter文件,文件内容:
tpFilter=com.szile.dubbo.filter.TPMonitorFilter
3. provider模块
-
依赖API模块,创建CustomServiceImpl类实现ServiceApi接口。
package com.szile.dubbo.provider.service; import com.szile.dubbo.api.CustomServiceApi; import org.apache.dubbo.config.annotation.Service; import java.util.Random; @Service // 这里的Service不要引错包了 public class CustomServiceImpl implements CustomServiceApi { private Random random = new Random(); @Override public String method1() throws InterruptedException { Thread.sleep(random.nextInt(100)); return "invoke method1()"; } @Override public String method2() throws InterruptedException { Thread.sleep(random.nextInt(100)); return "invoke method2()"; } @Override public String method3() throws InterruptedException { Thread.sleep(random.nextInt(100)); return "invoke method3()"; } }
-
创建启动类,启动类中配置zookeeper,注册服务
public class ProviderApp { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read(); } @Configuration @EnableDubbo(scanBasePackages = {"com.szile.dubbo.provider.service"}) @PropertySource("classpath:/dubbo-provider.properties") static class ProviderConfiguration { @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://127.0.0.1:2181"); return registryConfig; } } }
-
dubbo-provider.properties
dubbo.application.name=dubbo-demo-provider dubbo.protocol.name=dubbo dubbo.protocol.port=20890 #dubbo.registry.address=zookeeper://127.0.0.1:2181
4. consumer模块
-
依赖API模块和filter模块
-
创建
RemoteServiceProxy
类,调用远程服务方法@Component public class RemoteServiceProxy { @Reference // 必须使用 org.apache.dubbo.config.annotation.Reference private CustomServiceApi serviceApi; public void invokeMethod1() throws InterruptedException { serviceApi.method1(); } public void invokeMethod2() throws InterruptedException { serviceApi.method2(); } public void invokeMethod3() throws InterruptedException { serviceApi.method3(); } }
-
启动类ConsumerApp
启动类中使用了
ThreadPoolExecutor
线程池,循环的向线程池中提交调用远程服务方法的任务。这里执行三分钟停下。public class ConsumerApp { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfig.class); context.start(); RemoteServiceProxy serviceProxy = context.getBean(RemoteServiceProxy.class); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50)); long startTime = System.currentTimeMillis(); int minutes = 3; int count = 0; while (true) { try { // 这里适当的休息一会,否则线程池不够用,会拒绝服务 Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } try { threadPoolExecutor.execute(() -> { try { serviceProxy.invokeMethod1(); } catch (InterruptedException e) { System.out.println(e.getLocalizedMessage()); } }); threadPoolExecutor.execute(() -> { try { serviceProxy.invokeMethod2(); } catch (InterruptedException e) { System.out.println(e.getLocalizedMessage()); } }); threadPoolExecutor.execute(() -> { try { serviceProxy.invokeMethod3(); } catch (InterruptedException e) { System.out.println(e.getLocalizedMessage()); } }); } catch (Exception e) { e.printStackTrace(); } count++; // 执行三分钟结束 if ((System.currentTimeMillis() - startTime) > minutes * 60 * 1000) break; } System.out.println("平均每分钟调用 - " + count / minutes + " - 次"); } @Configuration @EnableDubbo @PropertySource(value = "classpath:/dubbo-consumer.properties") @ComponentScan(basePackages = {"com.szile.dubbo.consumer.service"}) static class ConsumerConfig { } }
三、结果验证
验证步骤:
- 确保zookeeper服务启动
- 启动provider服务
- 启动consumer服务
consumer控制台输出信息
平均每分钟每个方法调用10851次,一分钟的TP90是90毫秒,TP99是99毫秒。
四、视频讲解
链接:https://pan.baidu.com/s/1sN_DWMv7p1SZ7BhL7SwbvQ
提取码:bfs0