标签:obb ret 发送 2.3 window action serve 端口 报错
http://jerrypeng.me/2014/08/08/server-side-java-monitoring-java/
这个漫长的系列文章今天要迎来最后一篇了,也是真正与 Java 有关的部分。前面介绍了我们的监控方案的 Ganglia 和 Nagios 及其整合的部分,这一次则介绍如何记录 Java 应用内的性能参数并将其暴露给监控系统。
主要介绍的内容有 JMX 以及将监控 JMX 并发送数据到 Ganglia 的 jmxtrans,同时还会介绍我实现的一个简单的记录性能参数的方法。
JMX 基本上是 Java 应用监控的标准解决方案,JVM 本身的诸多性能指标如内存使用、GC、线程等都有对应的 JMX 参数可供监控。自定义 MBean 也是十分简单的一件事。可以用两种方式来定义 MBean,第一种是通过自定义接口和对应的实现类,另一种则是实现 javax.management.DynamicMBean
接口来定义动态的 MBean。我们采用的是第二种方式,因此略过第一种方式的介绍,有兴趣的读者请参考Java Tutorial 里的教程和 Javalobby 上的文章。
下面是我们内部使用的 MetricMBean
,使用 DynamicMBean
实现:
public class MetricsMBean implements DynamicMBean { private final Map<String, Metric> metrics; public MetricsMBean(Map<String, Metric> metrics) { this.metrics = new HashMap<>(metrics); } @Override public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { Metric metric = metrics.get(attribute); if (metric == null) { throw new AttributeNotFoundException("Attribute " + attribute + " not found"); } return metric.getValue(); } @Override public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { // 我们仅仅需要做监控,没有设置属性的需要,所以直接抛异常 throw new UnsupportedOperationException("Setting attribute is not supported"); } @Override public AttributeList getAttributes(String[] attributes) { AttributeList attrList = new AttributeList(); for (String attr : attributes) { Metric metric = metrics.get(attr); if (metric != null) attrList.add(new Attribute(attr, metric.getValue())); } return attrList; } @Override public AttributeList setAttributes(AttributeList attributes) { // 我们仅仅需要做监控,没有设置属性的需要,所以直接抛异常 throw new UnsupportedOperationException("Setting attribute is not supported"); } @Override public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { // 方法调用也是不需要实现的 throw new UnsupportedOperationException("Invoking is not supported"); } @Override public MBeanInfo getMBeanInfo() { SortedSet<String> names = new TreeSet<>(metrics.keySet()); List<MBeanAttributeInfo> attrInfos = new ArrayList<>(names.size()); for (String name : names) { attrInfos.add(new MBeanAttributeInfo(name, "long", "Metric " + name, true, false, false)); } return new MBeanInfo(getClass().getName(), "Application Metrics", attrInfos.toArray(new MBeanAttributeInfo[attrInfos.size()]), null, null, null); } }
其中 Metric 是我们设计的一个接口,用于定义不同的监控指标:
public interface Metric { long getValue(); }
最后是一个工具类 Metrics
用于注册和创建 MBean:
public class Metrics { private static final Logger log = LoggerFactory.getLogger(Metrics.class); private static final Metrics instance = new Metrics(); private Map<String, Metric> metrics = new HashMap<>(); public static Metrics instance() { return instance; } private Metrics() { } public Metrics register(String name, Metric metric) { metrics.put(name, metric); return this; } public void createMBean() { MetricsMBean mbean = new MetricsMBean(metrics); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { final String name = MetricsMBean.class.getPackage().getName() + ":type=" + MetricsMBean.class.getSimpleName(); log.debug("Registering MBean: {}", name); server.registerMBean(mbean, new ObjectName(name)); } catch (Exception e) { log.warn("Error registering trafree metrics mbean", e); } } }
在应用启动的时候这样调用以注册指标并创建 MBean:
// createMaxValueMetric 和 createCountMetric 可以基于同一份数据来得到 // 最大值和次数的指标,详见下面 AverageMetric 的具体实现。 Metrics.instance() .register("SearchAvgTime", MetricLoggers.searchTime) .register("SearchMaxTime", MetricLoggers.searchTime.createMaxValueMetric()) .register("SearchCount", MetricLoggers.searchTime.createCountMetric()) .createMBean();
其中注册时指定的名称也是最后从通过 JMX 看到的属性名。
当然上面只是我们内部的监控框架的做法,你需要关注的是如何实现自定义 MBean 而已。
上面提到的 Metric
接口,我并没有给出实现。下面介绍我们内部常用的一个实现 AverageMetric
(平均值指标)。它可以记录某个性能数值,并计算单位时间内的平均值,最大值和次数。例如上面的 MetricLoggers
中定义的 searchTime
,它用来记录我们系统的搜索功能的一分钟平均耗时,一分钟最大耗时和一分钟的搜索次数。
public class MetricLoggers { public static final AverageMetric searchTime = new AverageMetric(); }
在实际的搜索功能处记录耗时:
long startTime = System.currentTimeMillis(); doSearch(request); long timeCost = System.currentTimeMillis() - startTime; MetricLoggers.searchTime.log(timeCost);
这样通过 JMX 就可以监控到我们系统过去一分钟内的平均搜索耗时,最大搜索耗时以及搜索次数。
下面是 AverageMetric
类的具体实现,比较长,请慢慢看。基本思路就是使用 AtomicReference 和一个值对象,通过非阻塞算法来实现并发。经过测试,在并发度不高的情况下性能不错,但在线程很多,竞争激烈的时候不是很好。再次重申,这个实现仅供参考。
public class TimeWindowSupport { final long timeWindow; TimeWindowSupport(long timeWindow) { this.timeWindow = timeWindow; } long currentSlot() { return System.currentTimeMillis() / timeWindow; } } public class AverageMetric extends TimeWindowSupport implements Metric { final AtomicReference<Value> currentValue = new AtomicReference<Value>(); private volatile Value lastValue = null; public AverageMetric(long timeWindow) { super(timeWindow); } public AverageMetric() { super(TimeUnit.MINUTES.toMillis(1)); } public Value getLastValue() { long slot = currentSlot(); while(true) { Value curValue = currentValue.get(); if (curValue != null && slot != curValue.slot) { if (currentValue.compareAndSet(curValue, Value.create(slot))) { lastValue = curValue; break; } } else { break; } } return lastValue; } public void log(long value) { long slot = currentSlot(); while (true) { Value curValue = currentValue.get(); if (curValue == null) { if (currentValue.compareAndSet(null, Value.create(slot, value))) return; } else if (slot == curValue.slot) { if (currentValue.compareAndSet(curValue, curValue.add(value))) return; } else { if (currentValue.compareAndSet(curValue, Value.create(slot, value))) { lastValue = curValue; return; } } } } /** * 基于同样的数据,创建一个计数度量,其返回值是过去的单位时间内的log事件发生次数 * * @return 返回计数度量 */ public Metric createCountMetric() { return new Metric() { @Override public long getValue() { Value val = getLastValue(); if (val != null) return (long) val.n; else return 0L; } }; } /** * 基于同样的数据,创建一个最大值度量,其返回值是过去的单位时间内记录的最大数值 * * @return 返回最大值度量 */ public Metric createMaxValueMetric() { return new Metric() { @Override public long getValue() { Value val = getLastValue(); if (val != null) return val.max; else return 0L; } }; } @Override public long getValue() { Value lastValue = getLastValue(); long lastSlot = currentSlot() - 1; if (lastValue != null && lastValue.n != 0 && lastSlot == lastValue.slot) return lastValue.total / lastValue.n; else return 0L; } static class Value { final long slot; final int n; final long total; final long max; Value(long slot, int n, long total, long max) { this.slot = slot; this.n = n; this.total = total; this.max = max; } static Value create(long slot, long value) { return new Value(slot, 1, value, value); } static Value create(long slot) { return new Value(slot, 0, 0, 0); } Value add(long value) { return new Value(this.slot, this.n + 1, this.total + value, (value > this.max) ? value : this.max); } } }
有了 JMX,我们还缺少最后一环:将监控数据发给我们前面辛苦搭建的监控系统。我们的核心系统是 Ganglia,所以要将数据发送给它。我们选择的是 jmxtrans 这个解决方案。它本身也是用 Java 实现的,使用 JSON 作为配置文件。
它提供了 deb,rpm 和标准的 zip 包 ,很方便安装。按照发行版选择安装即可。
jmxtrans 的配置文件在 /var/lib/jmxtrans
下,使用 JSON 格式。针对要监控的每个应用创建一个 JSON 文件,按下面的格式配置即可。下面我附加了注释,但实际的配置文件如果有这种注释貌似会报错,请注意。
{ "servers" : [ { "host" : "localhost", // JMX IP "port" : "19008", // JMX 端口 // 别名,用于Ganglia对参数来源的识别,写成本机IP和Hostname即可 "alias" : "192.168.221.29:fly2save02", "queries" : [ { "outputWriters" : [ { "@class" : "com.googlecode.jmxtrans.model.output.GangliaWriter", "settings" : { "groupName" : "myapp", //Ganglia里的参数组名 "host" : "192.168.1.9", //Ganglia的IP "port" : 8648, //Ganglia的端口 "slope" : "BOTH", "units" : "bytes", //参数单位 "tmax" : 60, "dmax" : 0, "sendMetadata": 30 } } ], "obj" : "java.lang:type=Memory", //要监控的 MBean 的标识 "resultAlias" : "app", //别名,使用别名可以避免名称过长 "attr" : [ "HeapMemoryUsage", "NonHeapMemoryUsage" ] //要监控的MBean属性 }, // 要监控多个 MBean,需要写多组 query,其中 outputWriters 部分会冗 // 余,这个比较恶心。 { "outputWriters" : [ { "@class" : "com.googlecode.jmxtrans.model.output.GangliaWriter", "settings" : { "groupName" : "myapp", "host" : "192.168.1.9", "port" : 8648, "slope" : "BOTH", "tmax" : 60, "dmax" : 0, "sendMetadata": 30 } } ], "obj" : "com.trafree.metrics:type=MetricsMBean", //我们应用的MBean "resultAlias" : "app" //未指定attr意味着要监控所有属性 } ] } ] }
更详细的配置请参考官方WIKI。
首先应用一定要打开 JMX Remote,为应用添加如下的 JVM 参数。
1
2
3
4
5
|
|
我们的应用和 jmxtrans 是运行在同一台机器上的,所以把 local.only
改成了 true
,仅允许本地连接,同时去掉了认证和 SSL 的支持。如果你们的部署方式不同,请按需求调整。
jmxtrans 的运行很简单,启动相应的服务即可(确保 java
在 PATH
里):
1
2
|
|
至此,我们的完整监控方案基本成型了。借助 Ganglia,Nagios,JMX 和 jmxtrans,我们可以完整地监控从 OS 到应用的方方面面,可以很轻松地做告警支持,也可以很方便地查看历史趋势。
下面 Show 两张图,是我们的核心机票检索引擎的性能参数在 Ganglia 和 Nagios 里的样子:
终于完成了这个系列的文章,欢迎读者留下自己的想法,欢迎交流。
在研究这些的时候,我也发现了一些其他的解决方案,在这里一并提一下,感兴趣的可以深入研究下(欢迎交流):
MetricMBean
的最佳替代品,功能强大,并且支持很多常用组件如 Jetty,Ehcache,Log4j 等,并且可以发送数据到 Ganglia。如果早点发现这个,我可能就不会自己写上面介绍的那一套方案了。对了,它还有 Clojure 绑定,如果是 Clojure 应用,那更可以考虑使用它了。标签:obb ret 发送 2.3 window action serve 端口 报错
原文地址:http://www.cnblogs.com/hyl8218/p/7927746.html