这里的监控中心以dubbo-monitor-simple项目说
github 地址 https://github.com/apache/dubbo-admin ,被单独移到dubbo-admin中维护,在master 分支中
总的来说是监控中心启动一个sevlet容器,通过web页面向用户多维度的展示dubbo服务信息。如下图
从页面结构来说,图中红框框住的几个部分,第一行是菜单,第二上是导航,第三行是表格title,最后一部分是表格。我们先从服务服务启动类开始
代码语言:javascript复制public class MonitorStarter {
public static void main(String[] args) {
//通过main方法启动
System.setProperty(Constants.DUBBO_PROPERTIES_KEY, "conf/dubbo.properties");
Main.main(args);
}
}
再看下Main 的main方法
代码语言:javascript复制public static void main(String[] args) {
try {
//通过dubbo.container 获取要启动的容器名,多个以逗号分割
if (args == null || args.length == 0) {
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
//通过spi 加装容器实例 放入list
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i ) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" Arrays.toString(args) ") to run dubbo serivce.");
//优雅停机的回调设置
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " container.getClass().getSimpleName() " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
LOCK.lock();//重入锁
STOP.signal();//释放信号量
} finally {
//释放锁
LOCK.unlock();
}
}
}
});
}
//分别调用容器的start方法 启动指定的容器。
for (Container container : containers) {
container.start();
logger.info("Dubbo " container.getClass().getSimpleName() " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) " Dubbo service server started!");
} catch (RuntimeException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
try {
LOCK.lock();//获取锁
STOP.await();//等待信号量
} catch (InterruptedException e) {
//不通过钩子函数停机,记录异常
logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
} finally {
//释放锁
LOCK.unlock();
}
}
在dubbo.properties 文件中配置项
代码语言:javascript复制dubbo.container=log4j,spring,registry,jetty
通过spi相关文件找到如下实现
代码语言:javascript复制spring=com.alibaba.dubbo.container.spring.SpringContainer
log4j=com.alibaba.dubbo.container.log4j.Log4jContainer
registry=com.alibaba.dubbo.monitor.simple.container.RegistryContainer
jetty=com.alibaba.dubbo.monitor.simple.container.JettyContainer
这里重点解析下面spring,registry,jetty三种实现。具体从分析他们的start方法入手
SpringContainer
代码语言:javascript复制public void start() {
//通过指定配置文件,启动了一个spring容器
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (configPath == null || configPath.length() == 0) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\s] "));
context.start();
}
看下spring配置
代码语言:javascript复制<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="location" value="classpath:conf/dubbo.properties"/>
</bean>
<bean id="monitorService" class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService">
</bean>
<dubbo:application name="${dubbo.application.name}" owner="${dubbo.application.owner}"/>
<!--指定注册中心地址-->
<dubbo:registry client="curator" address="${dubbo.registry.address}"/>
<!--服务发布协议和端口-->
<dubbo:protocol name="dubbo" port="${dubbo.protocol.port}"/>
<!--发布com.alibaba.dubbo.monitor.MonitorService服务 实现类是com.alibaba.dubbo.monitor.simple.SimpleMonitorService-->
<!--这就是消费方和服务提供方在上报统计数据时用的服务实现-->
<dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" delay="-1"/>
<!--注入com.alibaba.dubbo.registry.RegistryService服务引用 这个服务实现有dubbo自身实现,不需要从注册中心获取-->
<!--具体在RegistryProtocol的refer方法中做了特殊处理,源码在下面-->
<dubbo:reference id="registryService" interface="com.alibaba.dubbo.registry.RegistryService"/>
</beans>
RegistryProtocol的refer方法:
代码语言:javascript复制 public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//通过register 可以获取具体,注册中心协议,这里是zookeeper,并设置为url 协议。
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//这里会通过ZookeeperRegistryFactory的getRegistry方法实现,得到zookeeper的Registry 实现ZookeeperRegistry类,
//而Registry 接口继承了RegistryService接口
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
//所以这里直接把ZookeeperRegistry作为RegistryService服务的实现,创建代理
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
//这里cluster是Cluster$Adpative类对象
return doRefer(cluster, registry, type, url);
}
这里SpringContainer的主要完成了,MonitorService服务发布,创建RegistryService服务代理实现。
RegistryContainer
代码语言:javascript复制public void start() {
//验证注册中心地址
String url = ConfigUtils.getProperty(REGISTRY_ADDRESS);
if (url == null || url.length() == 0) {
throw new IllegalArgumentException("Please set java start argument: -D" REGISTRY_ADDRESS "=zookeeper://127.0.0.1:2181");
}
//通过spring 容器获取获取registryService服务,所以需要spring容器先启动
registry = (RegistryService) SpringContainer.getContext().getBean("registryService");
URL subscribeUrl = new URL(Constants.ADMIN_PROTOCOL, NetUtils.getLocalHost(), 0, "",
Constants.INTERFACE_KEY, Constants.ANY_VALUE,
Constants.GROUP_KEY, Constants.ANY_VALUE,
Constants.VERSION_KEY, Constants.ANY_VALUE,
Constants.CLASSIFIER_KEY, Constants.ANY_VALUE,
Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY ","
Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false));
//通过registry去注册中心订阅,服务消费者,提供者信息
//这里 subscribeUrl = admin://10.47.16.51?category=providers,consumers&check=false&classifier=*&group=*&interface=*&version=*
//作用是订阅所有的服务提供和消费方节点。
registry.subscribe(subscribeUrl, new NotifyListener() {
//通过监听器回调方法,把订阅的服务信息系分类存储到相应数据结构中
//以备使用
public void notify(List<URL> urls) {
if (urls == null || urls.size() == 0) {
return;
}
Map<String, List<URL>> proivderMap = new HashMap<String, List<URL>>();
Map<String, List<URL>> consumerMap = new HashMap<String, List<URL>>();
for (URL url : urls) {
String application = url.getParameter(Constants.APPLICATION_KEY);
if (application != null && application.length() > 0) {
//应用统计
applications.add(application);
}
String service = url.getServiceInterface();
//服务统计
services.add(service);
//获取url的类别信息,默认分类是 providers
String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
if (Constants.PROVIDERS_CATEGORY.equals(category)) {//服务提供者信息
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
serviceProviders.remove(service);
} else {
List<URL> list = proivderMap.get(service);
if (list == null) {
list = new ArrayList<URL>();
proivderMap.put(service, list);
}
list.add(url);
if (application != null && application.length() > 0) {
//获取提供服务的应用集合
Set<String> serviceApplications = providerServiceApplications.get(service);
if (serviceApplications == null) {
providerServiceApplications.put(service, new ConcurrentHashSet<String>());
serviceApplications = providerServiceApplications.get(service);
}
serviceApplications.add(application);
//应用提供的服务集合
Set<String> applicationServices = providerApplicationServices.get(application);
if (applicationServices == null) {
providerApplicationServices.put(application, new ConcurrentHashSet<String>());
applicationServices = providerApplicationServices.get(application);
}
applicationServices.add(service);
}
}
} else if (Constants.CONSUMERS_CATEGORY.equals(category)) {//消费者列表
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
serviceConsumers.remove(service);
} else {
List<URL> list = consumerMap.get(service);
if (list == null) {
list = new ArrayList<URL>();
consumerMap.put(service, list);
}
list.add(url);
if (application != null && application.length() > 0) {
Set<String> serviceApplications = consumerServiceApplications.get(service);
if (serviceApplications == null) {
//消费者的应用列表
consumerServiceApplications.put(service, new ConcurrentHashSet<String>());
serviceApplications = consumerServiceApplications.get(service);
}
serviceApplications.add(application);
Set<String> applicationServices = consumerApplicationServices.get(application);
if (applicationServices == null) {
consumerApplicationServices.put(application, new ConcurrentHashSet<String>());
applicationServices = consumerApplicationServices.get(application);
}
applicationServices.add(service);
}
}
}
}
if (proivderMap != null && proivderMap.size() > 0) {
//所有的服务提供者
serviceProviders.putAll(proivderMap);
}
if (consumerMap != null && consumerMap.size() > 0) {
//所有消费者信息
serviceConsumers.putAll(consumerMap);
}
}
});
}
RegistryContainer主要完成了从注册中心对服务信息的收集,并提供了相关方法对收集到的数据进行使用,例如
代码语言:javascript复制 //获取所有的应用
public Set<String> getApplications() {
return Collections.unmodifiableSet(applications);
}
/***
* reverse:true 获取某个应用,所有服务的消费者
* false:某个应用,所有服务提供者
* @param application
* @param reverse
* @return
*/
public Set<String> getDependencies(String application, boolean reverse) {
if (reverse) {
Set<String> dependencies = new HashSet<String>();
//应用的所有的服务
Set<String> services = providerApplicationServices.get(application);
if (services != null && services.size() > 0) {
for (String service : services) {
//所有服务消费者
Set<String> applications = consumerServiceApplications.get(service);
if (applications != null && applications.size() > 0) {
dependencies.addAll(applications);
}
}
}
return dependencies;
} else {
Set<String> dependencies = new HashSet<String>();
Set<String> services = consumerApplicationServices.get(application);
if (services != null && services.size() > 0) {
for (String service : services) {
//所有服务提供者
Set<String> applications = providerServiceApplications.get(service);
if (applications != null && applications.size() > 0) {
dependencies.addAll(applications);
}
}
}
return dependencies;
}
}
//获取所有的服务
public Set<String> getServices() {
return Collections.unmodifiableSet(services);
}
//获取某个应用所有提供所有的服务urls
public List<URL> getProvidersByApplication(String application) {
List<URL> urls = new ArrayList<URL>();
if (application != null && application.length() > 0) {
for (List<URL> providers : serviceProviders.values()) {
for (URL url : providers) {
if (application.equals(url.getParameter(Constants.APPLICATION_KEY))) {
urls.add(url);
}
}
}
}
return urls;
}
加上这篇(https://cloud.tencent.com/developer/article/1109464)博文提到的上报信息,可知监控中心的监控信息大致可分为两类,一类是服务消费者和提供者通过MonitorService服务上报来的,服务调用动态信息,还有一类是监控中心通过订阅注册中心获取的服务分布静态信息。
JettyContainer
代码语言:javascript复制 public void start() {
//jetty 服务端口
String serverPort = ConfigUtils.getProperty(JETTY_PORT);
int port;
if (serverPort == null || serverPort.length() == 0) {
//默认端口8080
port = DEFAULT_JETTY_PORT;
} else {
port = Integer.parseInt(serverPort);
}
//初始化连接器
connector = new SelectChannelConnector();
connector.setPort(port);
ServletHandler handler = new ServletHandler();
String resources = ConfigUtils.getProperty(JETTY_DIRECTORY);
if (resources != null && resources.length() > 0) {
//添加过滤器,具体过滤逻辑看ResourceFilter
//指定过滤器,过滤器匹配的路径
FilterHolder resourceHolder = handler.addFilterWithMapping(ResourceFilter.class, "/*", Handler.DEFAULT);
//设置初始参数值
resourceHolder.setInitParameter("resources", resources);
}
//添加servlet处理,处理逻辑可以看PageServlet和匹配路径
ServletHolder pageHolder = handler.addServletWithMapping(PageServlet.class, "/*");
//设置初始参数值
pageHolder.setInitParameter("pages", ConfigUtils.getProperty(JETTY_PAGES));
pageHolder.setInitOrder(2);
Server server = new Server();
server.addConnector(connector);
//添加处理请求的handler
server.addHandler(handler);
try {
//在指定端口启动sevlet容器服务,接受用户请求
server.start();
} catch (Exception e) {
throw new IllegalStateException("Failed to start jetty server on " NetUtils.getLocalHost() ":" port ", cause: " e.getMessage(), e);
}
}
ResourceFilter
代码语言:javascript复制//初始化资源路径
public void init(FilterConfig filterConfig) throws ServletException {
//根据初始化参数,获取资源路径,支持多个资源路径 放入resources
String config = filterConfig.getInitParameter("resources");
if (config != null && config.length() > 0) {
String[] configs = Constants.COMMA_SPLIT_PATTERN.split(config);
for (String c : configs) {
if (c != null && c.length() > 0) {
c = c.replace('\', '/');
if (c.endsWith("/")) {
c = c.substring(0, c.length() - 1);
}
resources.add(c);
}
}
}
}
代码语言:javascript复制 //过滤逻辑
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (response.isCommitted()) {
return;
}
String uri = request.getRequestURI();
String context = request.getContextPath();
if (uri.endsWith("/favicon.ico")) {
uri = "/favicon.ico";
} else if (context != null && !"/".equals(context)) {
uri = uri.substring(context.length());
}
if (!uri.startsWith("/")) {
uri = "/" uri;
}
//获取资源的默认修改时间
long lastModified = getLastModified(uri);
long since = request.getDateHeader("If-Modified-Since");
if (since >= lastModified) {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
byte[] data;
//通过url没有制定资源,就走servlet逻辑
InputStream input = getInputStream(uri);
if (input == null) {
//没有获取到资源,通过过滤器往下走,到servlet层(***看这里**)
chain.doFilter(req, res);
return;
}
//能获取具体资源,直接返回资源
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
data = output.toByteArray();
} finally {
input.close();
}
//设置modified 时间
response.setDateHeader("Last-Modified", lastModified);
OutputStream output = response.getOutputStream();
output.write(data);
output.flush();
}
PageServlet
代码语言:javascript复制//初始化
public void init() throws ServletException {
super.init();
//维护自身实例的引用
INSTANCE = this;
String config = getServletConfig().getInitParameter("pages");
Collection<String> names;
//如果配置了pages 实现名称,赋值给names 集合,可以支持逗号分割多个pages
if (config != null && config.length() > 0) {
names = Arrays.asList(Constants.COMMA_SPLIT_PATTERN.split(config));
} else {
//没有配置,默认获取说有spi扩展的pages
names = ExtensionLoader.getExtensionLoader(PageHandler.class).getSupportedExtensions();
}
for (String name : names) {
PageHandler handler = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtension(name);
pages.put(ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(handler), handler);
//如果实现类上有注解 Menu,收集到添加到menus列表中,用于显示在页面最上方的顶级菜单
Menu menu = handler.getClass().getAnnotation(Menu.class);
if (menu != null) {
menus.add(handler);
}
}
//对menus 通过自定义的MenuComparator菜单排序
Collections.sort(menus, new MenuComparator());
}
代码语言:javascript复制//处理请求过程
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!response.isCommitted()) {
PrintWriter writer = response.getWriter();
String uri = request.getRequestURI();
boolean isHtml = false;
if (uri == null || uri.length() == 0 || "/".equals(uri)) {
//默认用index PageHandler实现
uri = "index";
isHtml = true;
} else {
//uri 去头,截尾
if (uri.startsWith("/")) {
uri = uri.substring(1);
}
if (uri.endsWith(".html")) {
uri = uri.substring(0, uri.length() - ".html".length());
isHtml = true;
}
}
if (uri.endsWith("favicon.ico")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
//到这里 uri 就是某个pageHandler spi 实现名称
ExtensionLoader<PageHandler> pageHandlerLoader = ExtensionLoader.getExtensionLoader(PageHandler.class);
PageHandler pageHandler = pageHandlerLoader.hasExtension(uri) ? pageHandlerLoader.getExtension(uri) : null;
if (isHtml) {
//拼接html代码
writer.println("<html><head><title>Dubbo</title>");
writer.println("<style type="text/css">html, body {margin: 10;padding: 0;background-color: #6D838C;font-family: Arial, Verdana;font-size: 12px;color: #FFFFFF;text-align: center;vertical-align: middle;word-break: break-all; } table {width: 90%; margin: 0px auto;border-collapse: collapse;border: 8px solid #FFFFFF; } thead tr {background-color: #253c46; } tbody tr {background-color: #8da5af; } th {padding-top: 4px;padding-bottom: 4px;font-size: 14px;height: 20px; } td {margin: 3px;padding: 3px;border: 2px solid #FFFFFF;font-size: 14px;height: 25px; } a {color: #FFFFFF;cursor: pointer;text-decoration: underline; } a:hover {text-decoration: none; }</style>");
writer.println("</head><body>");
}
if (pageHandler != null) {
Page page = null;
try {
String query = request.getQueryString();
//把查询条件作为参数放入handle 参数URL
//通过pageHandler返回page 对象
page = pageHandler.handle(URL.valueOf(request.getRequestURL().toString()
(query == null || query.length() == 0 ? "" : "?" query)));
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
String msg = t.getMessage();
if (msg == null) {
msg = StringUtils.toString(t);
}
if (isHtml) {
writer.println("<table>");
writer.println("<thead>");
writer.println(" <tr>");
writer.println(" <th>Error</th>");
writer.println(" </tr>");
writer.println("</thead>");
writer.println("<tbody>");
writer.println(" <tr>");
writer.println(" <td>");
writer.println(" " msg.replace("<", "<").replace(">", "<").replace("n", "<br/>"));
writer.println(" </td>");
writer.println(" </tr>");
writer.println("</tbody>");
writer.println("</table>");
writer.println("<br/>");
} else {
writer.println(msg);
}
}
if (page != null) {
if (isHtml) {
//通过handler方法放回的page对象构造html
String nav = page.getNavigation();
if (nav == null || nav.length() == 0) {
nav = ExtensionLoader.getExtensionLoader(PageHandler.class).getExtensionName(pageHandler);
nav = nav.substring(0, 1).toUpperCase() nav.substring(1);
}
if (!"index".equals(uri)) {
nav = "<a href="/">Home</a> > " nav;
}
//绘制菜单部分
writeMenu(request, writer, nav);
//绘制表格部分
writeTable(writer, page.getTitle(), page.getColumns(),
page.getRows());
} else {
if (page.getRows().size() > 0 && page.getRows().get(0).size() > 0) {
writer.println(page.getRows().get(0).get(0));
}
}
}
} else {
//没有pageHanlder 实现提示 Not found
if (isHtml) {
writer.println("<table>");
writer.println("<thead>");
writer.println(" <tr>");
writer.println(" <th>Error</th>");
writer.println(" </tr>");
writer.println("</thead>");
writer.println("<tbody>");
writer.println(" <tr>");
writer.println(" <td>");
writer.println(" Not found " uri " page. Please goto <a href="/">Home</a> page.");
writer.println(" </td>");
writer.println(" </tr>");
writer.println("</tbody>");
writer.println("</table>");
writer.println("<br/>");
} else {
writer.println("Not found " uri " page.");
}
}
if (isHtml) {
writer.println("</body></html>");
}
//写到客户端
writer.flush();
}
}
通过上面的代码可以知道,serlvet是通过分析uri的请求路径,动态加载相应的PageHandler并通过调用其hanlder方法,来获取页面要展示的数据的。
这里看下PageHandler一个具体扩展实现ProvidersPageHandler,它的hanlder方法如下:
代码语言:javascript复制public Page handle(URL url) {
//通过url获取一些服务的基本信息
String service = url.getParameter("service");
String host = url.getParameter("host");
String application = url.getParameter("application");
if (service != null && service.length() > 0) {
List<List<String>> rows = new ArrayList<List<String>>();
//重点在这,对之前订阅信息的使用
//通过 RegistryContainer.getInstance().getProvidersByService方法
//获取 RegistryContainer容器通过订阅注册中心获取的 服务消费者和提供者信息
List<URL> providers = RegistryContainer.getInstance().getProvidersByService(service);
if (providers != null && providers.size() > 0) {
for (URL u : providers) {
List<String> row = new ArrayList<String>();
String s = u.toFullString();
row.add(s.replace("&", "&"));
row.add("<button onclick="if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?service=" service "&provider=" URL.encode(s) "';}">Unregister</button>");
rows.add(row);
}
}
//Page 对象中,主要是导航,标题,表格列,行信息,对应着我们文章开头的图解说明
return new Page("<a href="services.html">Services</a> > " service
" > Providers | <a href="consumers.html?service=" service
"">Consumers</a> | <a href="statistics.html?service=" service
"">Statistics</a> | <a href="charts.html?service=" service
"">Charts</a>", "Providers (" rows.size() ")",
new String[]{"Provider URL:", "Unregister"}, rows);
} else if (host != null && host.length() > 0) {
List<List<String>> rows = new ArrayList<List<String>>();
List<URL> providers = RegistryContainer.getInstance().getProvidersByHost(host);
if (providers != null && providers.size() > 0) {
for (URL u : providers) {
List<String> row = new ArrayList<String>();
String s = u.toFullString();
row.add(s.replace("&", "&"));
row.add("<button onclick="if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?host=" host "&provider=" URL.encode(s) "';}">Unregister</button>");
rows.add(row);
}
}
return new Page("<a href="hosts.html">Hosts</a> > " NetUtils.getHostName(host) "/" host " > Providers | <a href="consumers.html?host=" host "">Consumers</a>", "Providers (" rows.size() ")",
new String[]{"Provider URL:", "Unregister"}, rows);
} else if (application != null && application.length() > 0) {
List<List<String>> rows = new ArrayList<List<String>>();
List<URL> providers = RegistryContainer.getInstance().getProvidersByApplication(application);
if (providers != null && providers.size() > 0) {
for (URL u : providers) {
List<String> row = new ArrayList<String>();
String s = u.toFullString();
row.add(s.replace("&", "&"));
row.add("<button onclick="if(confirm('Confirm unregister provider?')){window.location.href='unregister.html?application=" application "&provider=" URL.encode(s) "';}">Unregister</button>");
rows.add(row);
}
}
return new Page("<a href="applications.html">Applications</a> > " application " > Providers | <a href="consumers.html?application=" application "">Consumers</a> | <a href="dependencies.html?application=" application "">Depends On</a> | <a href="dependencies.html?application=" application "&reverse=true">Used By</a>", "Providers (" rows.size() ")",
new String[]{"Provider URL:", "Unregister"}, rows);
} else {
throw new IllegalArgumentException("Please input service or host or application parameter.");
}
}
最后
由消费者和服务提供者上报的动态调用信息,是以文件形式存在硬盘上的,包括图表以png形式(由jfreechart工具生成),
存储目录是dubbo.jetty.directory配置指定的。这部分工作是由SimpleMonitorService实现的。
代码语言:javascript复制 public SimpleMonitorService() {
queue = new LinkedBlockingQueue<URL>(Integer.parseInt(ConfigUtils.getProperty("dubbo.monitor.queue", "100000")));
//后台守护线程
writeThread = new Thread(new Runnable() {
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();
//线程池
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);
statisticsDirectory = ConfigUtils.getProperty("dubbo.statistics.directory");
chartsDirectory = ConfigUtils.getProperty("dubbo.charts.directory");
}
具体生成的文件这里简单切几个图,配置文件是存${user.home}/monitor目录下
monitor目录下有 charts 和statistics两个目录,分别存放图片和统计数据文件的。
这里在日期 接口名 方法名 格式的目录下,有生成好的图表文件,形如
下面两个图是statistics目录下统计文件和文件具体内容