源码分析Dubbo监控中心实现原理

remoteValue:为invoker.getUrl().getAddress(),其值为(注册中心地址)或服务提供者地址(客户端直连服务端)。

代码@5:如果为服务端:

localPort :为服务端的服务端口号。

remoteKey:MonitorService.CONSUMER,表示远端为服务消费者。

remoteValue:消费者host(ip:port)。

代码@6:获取本次服务调用请求包的字节数,在服务端解码时会在RpcContext中。

代码@7:获取本次服务调用响应包的字节数,在服务端对响应包编码时会写入,具体代码请参考DubboCountCodec类。

代码@8:调用monitor#collect收集调用信息,Monitor默认实现为DubboMonitor。使用的协议为count://localhost:localPort/service/method?application=applicationName&remoteKey=remoteValue&success|failure=1&elapsed=调用开销&concurrent=并发调用次数&input=入参字节数&output=响应字节数&group=服务所属组&version=版本。

2、源码分析DubboMonitor实现原理

Dubbo中默认的Monitor监控实现类为DubboMonitor:

这里写图片描述

核心属性介绍:

  • private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3, new NamedThreadFactory(“DubboMonitorSendTimer”,

true)):定时调度线程池,使用3个线程的线程池,线程名称以DubboMonitorSendTimer。

  • private final ScheduledFuture< ? > sendFuture:调度任务future。

private final Invoker< MonitorService > monitorInvoker:监控调度Invoker,Dubbo中的监控中心会作为服务提供者暴露服务,服务提供者,服务消费者可以通过注册中心订阅服务,通过该Invoker向监控中心汇报调用统计数据,也就是一次上报就是一次Dubbo RPC服务调用,其实现类为DubboInvoker,也就是可以通过该Invoker使用dubbo协议调用远程Monitor服务。

  • private final MonitorService monitorService:对monitorInvoker的proxy代理,主要是对toString、hashcode、equals无需通过RPC向MonitorServer服务提供者发起调

用。主要是通过AbstractProxyFactory#getProxy创建,默认子类为JavassistProxyFactory,动态代理的InvokerHandler为:

com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

String methodName = method.getName();

Class<?>[] parameterTypes = method.getParameterTypes();

if (method.getDeclaringClass() == Object.class) {

return method.invoke(invoker, args);

}

if (“toString”.equals(methodName) && parameterTypes.length == 0) {

return invoker.toString();

}

if (“hashCode”.equals(methodName) && parameterTypes.length == 0) {

return invoker.hashCode();

}

if (“equals”.equals(methodName) && parameterTypes.length == 1) {

return invoker.equals(args[0]);

}

return invoker.invoke(new RpcInvocation(method, args)).recreate();

}

  • private final long monitorInterval:向监控中心汇报的频率,也就是调用MonitorService RPC服务的调用频率,默认为1分钟。

  • private final ConcurrentMap< Statistics, AtomicReference< long[]>> statisticsMap:统计信息Map。

2.1 构造函数分析

public DubboMonitor(Invoker monitorInvoker, MonitorService monitorService) {

this.monitorInvoker = monitorInvoker;

this.monitorService = monitorService;

this.monitorInterval = monitorInvoker.getUrl().getPositiveParameter(“interval”, 60000); // @1

// collect timer for collecting statistics data

sendFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { // @2

@Override

public void run() {

// collect data

try {

send();

} catch (Throwable t) {

logger.error("Unexpected error occur at send statistic, cause: " + t.getMessage(), t);

}

}

}, monitorInterval, monitorInterval, TimeUnit.MILLISECONDS);

}

代码@1,从url参数中获取interval属性,如果为空,默认为60000,代表60S。

代码@2:启动定时调度任务,默认60S的间隔执行send()方法,向监控中心汇报服务调用统计数据。

2.2 collect 收集统计信息方法

public void collect(URL url) {

// data to collect from url

int success = url.getParameter(MonitorService.SUCCESS, 0);

int failure = url.getParameter(MonitorService.FAILURE, 0);

int input = url.getParameter(MonitorService.INPUT, 0);

int output = url.getParameter(MonitorService.OUTPUT, 0);

int elapsed = url.getParameter(MonitorService.ELAPSED, 0);

int concurrent = url.getParameter(MonitorService.CONCURRENT, 0);

// init atomic reference

Statistics statistics = new Statistics(url);

AtomicReference<long[]> reference = statisticsMap.get(statistics);

if (reference == null) {

statisticsMap.putIfAbsent(statistics, new AtomicReference<long[]>());

reference = statisticsMap.get(statistics);

}

// use CompareAndSet to sum

long[] current;

long[] update = new long[LENGTH];

do {

current = reference.get();

if (current == null) {

update[0] = success;

update[1] = failure;

update[2] = input;

update[3] = output;

update[4] = elapsed;

update[5] = concurrent;

update[6] = input;

update[7] = output;

update[8] = elapsed;

update[9] = concurrent;

} else {

update[0] = current[0] + success;

update[1] = current[1] + failure;

update[2] = current[2] + input;

update[3] = current[3] + output;

update[4] = current[4] + elapsed;

update[5] = (current[5] + concurrent) / 2;

update[6] = current[6] > input ? current[6] : input;

update[7] = current[7] > output ? current[7] : output;

update[8] = current[8] > elapsed ? current[8] : elapsed;

update[9] = current[9] > concurrent ? current[9] : concurrent;

}

} while (!reference.compareAndSet(current, update));

}

收集的信息主要是10个字段

update[0] :调用成功的次数

update[1] :调用失败的次数

update[2] :总调用流量(请求包的总大小)。

update[3] :总响应流量(响应包的总大小)。

update[4] :总响应时长(总服务调用开销)。

update[5] :一次收集周期的平均TPS。

update[6] :最大请求包大小。

update[7] :最大响应包大小。

update[8] :最大响应时间。

update[9] :最大TPS。

2.3 send方法

通过monitorService,最终通过monitorInvoker去调用RPC服务向监控中心汇报数据。接下来看一下监控中心的具体实现。

3、Dubbo监控中心实现原理

Dubbo官方提供了简易版本的监控中心,其项目为dubbo-ops:dubbo-monitor-simple。该项目是个spring-boot项目,启动后可以看到后台管理界面。

该项目服务提供者文件如下:

这里写图片描述

从中可以看出,监控中心服务提供者实现类为SimpleMonitorService,其实现接口为MonitorService。

接下来重点分析SimpleMonitorService监控中心的实现,关注如下两个点:

1、监控数据持久化。

2、监控报表生成逻辑。

核心属性说明:

  • ScheduledExecutorService scheduledExecutorService:定时调度线程,将监控数据写入饼图的定时任务,固定1个线程。

  • Thread writeThread:监控数据持久化线程。

  • BlockingQueue< URL > queue:持久化数据任务阻塞队列。

  • String statisticsDirectory = “statistics”:数据持久化目录,SimpleMonitorService将数据持久化到磁盘文件。该值指定目录名称。

  • String chartsDirectory = “charts”:饼图存储目录。

  • private volatile boolean running = true:持久化数据线程是否处于运行状态。

3.1 SimpleMonitorService构造函数

public SimpleMonitorService() {

queue = new LinkedBlockingQueue(Integer.parseInt(ConfigUtils.getProperty(“dubbo.monitor.queue”, “100000”))); // @1

writeThread = new Thread(new Runnable() { // @2 start

public void run() {

while (running) {

try {

write(); // write statistics

} catch (Throwable t) {

logger.error("Unexpected error occur at write stat log, cause: " + t.getMessage(), t);

try {

Thread.sleep(5000); // retry after 5 secs

} catch (Throwable t2) {

}

}

}

}

});

writeThread.setDaemon(true);

writeThread.setName(“DubboMonitorAsyncWriteLogThread”);

writeThread.start(); // @2 end

chartFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

public void run() {

try {

draw(); // draw chart

} catch (Throwable t) {

logger.error("Unexpected error occur at draw stat chart, cause: " + t.getMessage(), t);

}

}

}, 1, 300, TimeUnit.SECONDS); // @3

statisticsDirectory = ConfigUtils.getProperty(“dubbo.statistics.directory”);

chartsDirectory = ConfigUtils.getProperty(“dubbo.charts.directory”); // @4

}

代码@1:创建有界阻塞队列LinkedBlockingQueue,容量默认为100000个,可通过配置参数dubbo.monitor.queue改变默认值,如果队列中已挤压未被处理,后续监控数据将被默认丢弃。

代码@2:创建持久化监控数据线程,名称为DubboMonitorAsyncWriteLogThread,其使命是从LinkedBlockingQueue中获取监控原始数据,如果队列中没数据则被阻塞,然后写入文件中。

代码@3:开启定时调度任务,已每个5分钟的频率,根据持久化的监控数据,生成饼图。

代码@4:获取数据持久化目录与饼图存放目录。

3.2 SimpleMonitorService#write

private void write() throws Exception {

URL statistics = queue.take();

if (POISON_PROTOCOL.equals(statistics.getProtocol())) {

return;

}

String timestamp = statistics.getParameter(Constants.TIMESTAMP_KEY);

Date now;

if (timestamp == null || timestamp.length() == 0) {

now = new Date();

} else if (timestamp.length() == “yyyyMMddHHmmss”.length()) {

now = new SimpleDateFormat(“yyyyMMddHHmmss”).parse(timestamp);

} else {

now = new Date(Long.parseLong(timestamp));

}

String day = new SimpleDateFormat(“yyyyMMdd”).format(now);

SimpleDateFormat format = new SimpleDateFormat(“HHmm”);

for (String key : types) {

try {

String type;

String consumer;

String provider;

if (statistics.hasParameter(PROVIDER)) {

type = CONSUMER;

consumer = statistics.getHost();

provider = statistics.getParameter(PROVIDER);

int i = provider.indexOf(‘:’);

if (i > 0) {

provider = provider.substring(0, i);

}

} else {

type = PROVIDER;

consumer = statistics.getParameter(CONSUMER);

int i = consumer == null ? -1 : consumer.indexOf(‘:’);

if (i > 0) {

consumer = consumer.substring(0, i);

}

provider = statistics.getHost();

}

String filename = statisticsDirectory

  • “/” + day

  • “/” + statistics.getServiceInterface()

  • “/” + statistics.getParameter(METHOD)

  • “/” + consumer

  • “/” + provider

  • “/” + type + “.” + key;

File file = new File(filename);

File dir = file.getParentFile();

if (dir != null && !dir.exists()) {

dir.mkdirs();

}

FileWriter writer = new FileWriter(file, true);

try {

writer.write(format.format(now) + " " + statistics.getParameter(key, 0) + “\n”);

writer.flush();

} finally {

writer.close();

}

} catch (Throwable t) {

logger.error(t.getMessage(), t);

}

}

}

数据存储在物理磁盘上,其文件为为:" d u b b o . s t a t i s t i c s . d i r e c t o r y / {dubbo.statistics.directory} / dubbo.statistics.directory/{day}/ i n t e r f a c e n a m e / {interfacename}/ interfacename/{method}/${consumer}/ ${provider}/[consume|provider]/key",

key:{SUCCESS, FAILURE, ELAPSED, CONCURRENT, MAX_ELAPSED, MAX_CONCURRENT},分别调用成功次数、调用失败次数、调用开销(响应时间),TPS、最大响应时间,最大TPS。其文件存储如下:

这里写图片描述

以provider.concurrent为例,说明一下其内容:

这里写图片描述

其内容组织方式为:时间(时分:采集的值)。

3.3 draw

根据持久化的数据,在特定的目录下创建饼图,创建饼图方法createChart,具体使用JFreeChart相关类图,在这里就不细细讲解了,感兴趣的朋友可以百度查询相关用法。

3.4 监控中心使用效果一览

3.4.1 应用一览表

这个功能可以描述系统与系统的关联关系。

这里写图片描述

表格字段说明:

1、Application Name:应用名称

2、Providers:该应用包含的服务提供者信息,点击进去可以查看具体的服务提供者URL。

3、Consumers(1):该应用包含的服务消费者信息,点击进去可以查看具体的服务消费者URL。

4、Depends On:该应用依懒的应用。

这里写图片描述

5、Used By:该应用被依懒的应用。

这里写图片描述

3.4.2服务一览表

这里写图片描述

表格字段说明:

Service Name:服务名。

Application:服务所属应用名。

Providers:服务提供者信息,点击进去,可以看到详细的服务提供者信息。

这里写图片描述

Consumers:该服务的消费者信息。

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

t/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

表格字段说明:

Service Name:服务名。

Application:服务所属应用名。

Providers:服务提供者信息,点击进去,可以看到详细的服务提供者信息。

这里写图片描述

Consumers:该服务的消费者信息。

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

[外链图片转存中…(img-bbaDmZQF-1714751563993)]

[外链图片转存中…(img-EVy69Mug-1714751563995)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值