OpenTSDB数据存储架构图如下,它分四个步骤:
1. 主机报告检测结果给本地的tcollection。
2. tcollection发送检测结果给远程的tsd。
3. tsd构造记录,并把数据写入HBase。
4. HBase存储数据,并确认写入请求。
tcollector使用类Telnet的协议的发送监控信息的示例代码如下:
package test.telnet;
import java.io.PrintStream;
import org.apache.commons.net.telnet.TelnetClient;
public class TelnetUtil {
private TelnetClient telnetClient = new TelnetClient();
private PrintStream out;
public TelnetUtil(String ip, Integer port) {
try {
telnetClient.connect(ip, port);
out = new PrintStream(telnetClient.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
testGet("localhost", 4242);
}
public static void testGet(String url, Integer port) {
TelnetUtil telnetTest = new TelnetUtil(url, port);
String strTime = "" + System.currentTimeMillis();
System.out.println(strTime);
telnetTest.write("put t22 " + strTime + " 11 host=foo");
telnetTest.disconnect();
}
public void write(String command) {
try {
out.println(command);
out.flush();
System.out.println("本次执行的telnet命令:" + command);
} catch (Exception e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
Thread.sleep(10);
telnetClient.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
tsd服务主要用来接收监控信息、构造记录并写入HBase。OpenTSDB在TSDBMain.main()方法中启动netty服务器用来接收来自远程的Http或RPC请求,针对不同的请求封装成HttpRpc 或TelnetRpc来进行处理。
TSDBMain类方法:
public static void main(String[] args) throws IOException {
// ./build/tsdb tsd --port=4242 --staticroot=build/staticroot
// --cachedir=/tmp/tsdtmp –zkquorum=localhost
args = new String[] { "--port=4242", "--staticroot=F:/workspace/OpenTsdb/staticroot",
"--cachedir=D:/doc_hadoop/_00_hbase/opentsdb/opentsdb2_linux/build/tmp" };
Logger log = LoggerFactory.getLogger(TSDMain.class);
log.info("Starting.");
log.info(BuildData.revisionString());
log.info(BuildData.buildString());
try {
System.in.close(); // Release a FD we don't need.
} catch (Exception e) {
log.warn("Failed to close stdin", e);
}
final ArgP argp = new ArgP();
CliOptions.addCommon(argp);
argp.addOption("--port", "NUM", "TCP port to listen on.");
argp.addOption("--bind", "ADDR", "Address to bind to (default: 0.0.0.0).");
argp.addOption("--staticroot", "PATH", "Web root from which to serve static files (/s URLs).");
argp.addOption("--cachedir", "PATH", "Directory under which to cache result of requests.");
argp.addOption("--worker-threads", "NUM", "Number for async io workers (default: cpu * 2).");
argp.addOption("--async-io", "true|false", "Use async NIO (default true) or traditional blocking io");
argp.addOption("--backlog", "NUM", "Size of connection attempt queue (default: 3072 or kernel" + " somaxconn.");
argp.addOption("--flush-interval", "MSEC", "Maximum time for which a new data point can be buffered" + " (default: " + DEFAULT_FLUSH_INTERVAL
+ ").");
CliOptions.addAutoMetricFlag(argp);
args = CliOptions.parse(argp, args);
args = null; // free().
// get a config object
Config config = CliOptions.getConfig(argp);
// check for the required parameters
try {
if (config.getString("tsd.http.staticroot").isEmpty())
usage(argp, "Missing static root directory", 1);
} catch (NullPointerException npe) {
usage(argp, "Missing static root directory", 1);
}
try {
if (config.getString("tsd.http.cachedir").isEmpty())
usage(argp, "Missing cache directory", 1);
} catch (NullPointerException npe) {
usage(argp, "Missing cache directory", 1);
}
try {
if (!config.hasProperty("tsd.network.port"))
usage(argp, "Missing network port", 1);
config.getInt("tsd.network.port");
} catch (NumberFormatException nfe) {
usage(argp, "Invalid network port setting", 1);
}
// validate the cache and staticroot directories
try {
checkDirectory(config.getString("tsd.http.staticroot"), DONT_CREATE, !MUST_BE_WRITEABLE);
checkDirectory(config.getString("tsd.http.cachedir"), CREATE_IF_NEEDED, MUST_BE_WRITEABLE);
} catch (IllegalArgumentException e) {
usage(argp, e.getMessage(), 3);
}
final ServerSocketChannelFactory factory;
if (config.getBoolean("tsd.network.async_io")) {// 默认为true(在Config中设置)
/**
* 默认为处理器的两倍
*/
int workers = Runtime.getRuntime().availableProcessors() * 2;
if (config.hasProperty("tsd.network.worker_threads")) {
try {
workers = config.getInt("tsd.network.worker_threads");
} catch (NumberFormatException nfe) {
usage(argp, "Invalid worker thread count", 1);
}
}
// System.out.println("workers=" + workers);
factory =
new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool(), workers);
} else {
factory =
new OioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
}
TSDB tsdb = null;
try {
tsdb = new TSDB(config);
tsdb.initializePlugins(true);
// Make sure we don't even start if we can't find our tables.
tsdb.checkNecessaryTablesExist().joinUninterruptibly();
// 注册关闭程序
registerShutdownHook(tsdb);
final ServerBootstrap server = new ServerBootstrap(factory);
/**
* 此方法是本类的重点
* PipelineFactory内部负责netty服务器的具体实现
*/
server.setPipelineFactory(new PipelineFactory(tsdb));
if (config.hasProperty("tsd.network.backlog")) {
server.setOption("backlog", config.getInt("tsd.network.backlog"));
}
server.setOption("child.tcpNoDelay",
config.getBoolean("tsd.network.tcp_no_delay"));
server.setOption("child.keepAlive",
config.getBoolean("tsd.network.keep_alive"));
server.setOption("reuseAddress",
config.getBoolean("tsd.network.reuse_address"));
// null is interpreted as the wildcard address.
InetAddress bindAddress = null;
if (config.hasProperty("tsd.network.bind")) {
bindAddress =
InetAddress.getByName(config.getString("tsd.network.bind"));
}
// we validated the network port config earlier
final InetSocketAddress addr = new InetSocketAddress(bindAddress,
config.getInt("tsd.network.port"));
server.bind(addr);
log.info("Ready to serve on " + addr);
} catch (Throwable e) {
factory.releaseExternalResources();
try {
if (tsdb != null)
tsdb.shutdown().joinUninterruptibly();
} catch (Exception e2) {
log.error("Failed to shutdown HBase client", e2);
}
throw new RuntimeException("Initialization failed", e);
}
// The server is now running in separate threads, we can exit main.
}
RpcHandler类方法
/**
* TSDB服务可以监听telnet和http两种信号
*/
@Override
public void messageReceived(final ChannelHandlerContext ctx,
final MessageEvent msgevent) {
try {
final Object message = msgevent.getMessage();
/**
* telnet信号
*/
if (message instanceof String[]) {
handleTelnetRpc(msgevent.getChannel(), (String[]) message);
/**
* http信号
*/
} else if (message instanceof HttpRequest) {
handleHttpQuery(tsdb, msgevent.getChannel(), (HttpRequest) message);
} else {
logError(msgevent.getChannel(), "Unexpected message type " +
message.getClass() + ": " + message);
exceptions_caught.incrementAndGet();
}
} catch (Exception e) {
Object pretty_message = msgevent.getMessage();
if (pretty_message instanceof String[]) {
pretty_message = Arrays.toString((String[]) pretty_message);
}
logError(msgevent.getChannel(), "Unexpected exception caught" +
" while serving " + pretty_message, e);
exceptions_caught.incrementAndGet();
}
}
在telnet示例代码向tsd服务发送监控信息之后,tsd将接受的信息封装成PutDataPointRpc(implements TelnetRpc, HttpRpc)并实现其excute()方法,并调用TSDB类的addPointInternal()方法向HBase中添加数据。
public void execute(final TSDB tsdb, final HttpQuery query) throws IOException {
requests.incrementAndGet();
// only accept POST
if (query.method() != HttpMethod.POST) {
throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED,
"Method not allowed", "The HTTP method [" + query.method().getName()
+ "] is not permitted for this endpoint");
}
final List dps = query.serializer().parsePutV1();
if (dps.size() < 1) {
throw new BadRequestException("No datapoints found in content");
}
final boolean show_details = query.hasQueryStringParam("details");
final boolean show_summary = query.hasQueryStringParam("summary");
final ArrayList> details = show_details ? new ArrayList>() : null;
long success = 0;
long total = 0;
for (IncomingDataPoint dp : dps) {
total++;
try {
if (dp.getMetric() == null || dp.getMetric().isEmpty()) {
if (show_details) {
details.add(this.getHttpDetails("Metric name was empty", dp));
}
LOG.warn("Metric name was empty: " + dp);
continue;
}
if (dp.getTimestamp() <= 0) {
if (show_details) {
details.add(this.getHttpDetails("Invalid timestamp", dp));
}
LOG.warn("Invalid timestamp: " + dp);
continue;
}
if (dp.getValue() == null || dp.getValue().isEmpty()) {
if (show_details) {
details.add(this.getHttpDetails("Empty value", dp));
}
LOG.warn("Empty value: " + dp);
continue;
}
if (dp.getTags() == null || dp.getTags().size() < 1) {
if (show_details) {
details.add(this.getHttpDetails("Missing tags", dp));
}
LOG.warn("Missing tags: " + dp);
continue;
}
if (Tags.looksLikeInteger(dp.getValue())) {
tsdb.addPoint(dp.getMetric(), dp.getTimestamp(), Tags.parseLong(dp.getValue()), dp.getTags());
} else {
tsdb.addPoint(dp.getMetric(), dp.getTimestamp(), Float.parseFloat(dp.getValue()), dp.getTags());
}
success++;
} catch (NumberFormatException x) {
if (show_details) {
details.add(this.getHttpDetails("Unable to parse value to a number", dp));
}
LOG.warn("Unable to parse value to a number: " + dp);
invalid_values.incrementAndGet();
} catch (IllegalArgumentException iae) {
if (show_details) {
details.add(this.getHttpDetails(iae.getMessage(), dp));
}
LOG.warn(iae.getMessage() + ": " + dp);
illegal_arguments.incrementAndGet();
} catch (NoSuchUniqueName nsu) {
if (show_details) {
details.add(this.getHttpDetails("Unknown metric", dp));
}
LOG.warn("Unknown metric: " + dp);
unknown_metrics.incrementAndGet();
}
}
final long failures = total - success;
if (!show_summary && !show_details) {
if (failures > 0) {
throw new BadRequestException(HttpResponseStatus.BAD_REQUEST, "One or more data points had errors",
"Please see the TSD logs or append \"details\" to the put request");
} else {
query.sendReply(HttpResponseStatus.NO_CONTENT, "".getBytes());
}
} else {
final HashMap summary = new HashMap();
summary.put("success", success);
summary.put("failed", failures);
if (show_details) {
summary.put("errors", details);
}
if (failures > 0) {
query.sendReply(HttpResponseStatus.BAD_REQUEST, query.serializer().formatPutV1(summary));
} else {
query.sendReply(query.serializer().formatPutV1(summary));
}
}
}
private Deferred