InfluxDB-Java 对Point类时间属性的扩展
在使用influxdb-java-2.7函数库写InfluxDB数据库的时候,发现调用
public void write(final String database, final String retentionPolicy, final Point point);
函数时,写入到数据库中的time字段不是数据库自动生成的时间,导致在分布式环境中的数据错位的现象,
例如:本该是A主机的数据早于B主机写入数据库,但实际却是B主机先于A主机写入了。
于是在代码中通过设置断点,惊奇的发现,确确实实是A主机的数据先于B主机到达,但为什么InfluxDB数据库中的数据却是B主机先于A主机到达?于是怀疑是A B两台主机的时间不同步导致的。查看分布式环境中的A B两台主机的时间,发现B主机系统时间比A主机尽快了2秒种,所以上述数据错位的现象得到了证实。
但由此引出另外一个问题,在用write函数写point的时候并没有设置time,本应该采用数据库自动生成的时间索引,但为什么采用的却是当前主机的本地时间,而不是数据库时间。
带着这个问题查看了influxdb-java-2.7的源码,发现如下的代码实现:
public Point build() {
...
if (this.time != null) {
point.setTime(this.time);
point.setPrecision(this.precision);
} else {
point.setTime(System.currentTimeMillis());
point.setPrecision(TimeUnit.MILLISECONDS);
}
...
}
问题很明显,当我们没有设置时间的时候,自动为我们添加了当前的系统时间。
为了实现当前业务的数据同步问题,对Point类进行了扩展,如下是实现的原代码,命名为PointExt:
package org.influxdb.dto;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;
/**
* Representation of a InfluxDB database PointExt.
*
* @author stefan.majer [at] gmail.com
*
*/
public class PointExt {
private static Builder Builder;
private String measurement;
private Map<String, String> tags;
private Map<String, Object> fields;
private boolean autoTime = false;
private Long time;
private TimeUnit precision = TimeUnit.NANOSECONDS;
private static final Escaper FIELD_ESCAPER = Escapers.builder().addEscape('"', "\\\"").build();
private static final Escaper KEY_ESCAPER = Escapers.builder().addEscape(' ', "\\ ").addEscape(',', "\\,").build();
PointExt() {
this.autoTime = false;
}
PointExt(boolean autoTime) {
this.autoTime = autoTime;
}
/**
* Create a new PointExt Build build to create a new PointExt in a fluent manner-
*
* @param measurement
* the name of the measurement.
* @return the Builder to be able to add further Builder calls.
*/
public static Builder measurement(final String measurement) {
return new Builder(measurement);
}
/**
* Builder for a new PointExt.
*
* @author stefan.majer [at] gmail.com
*
*/
public static final class Builder {
private final String measurement;
private final Map<String, String> tags = Maps.newTreeMap(Ordering.natural());
private Long time;
private TimeUnit precision = TimeUnit.NANOSECONDS;
private final Map<String, Object> fields = Maps.newTreeMap(Ordering.natural());
/**
* @param measurement
*/
Builder(final String measurement) {
this.measurement = measurement;
}
/**
* Add a tag to this point.
*
* @param tagName
* the tag name
* @param value
* the tag value
* @return the Builder instance.
*/
public Builder tag(final String tagName, final String value) {
this.tags.put(tagName, value);
return this;
}
/**
* Add a Map of tags to add to this point.
*
* @param tagsToAdd
* the Map of tags to add
* @return the Builder instance.
*/
public Builder tags(final Map<String, String> tagsToAdd) {
this.tags.putAll(tagsToAdd);
return this;
}
/**
* Add a field to this point.
*
* @param field
* the field name
* @param value
* the value of this field
* @return the Builder instance.
*/
public Builder field(final String field, final Object value) {
this.fields.put(field, value);
return this;
}
/**
* Add a Map of fields to this point.
*
* @param fieldsToAdd
* the fields to add
* @return the Builder instance.
*/
public Builder fields(final Map<String, Object> fieldsToAdd) {
this.fields.putAll(fieldsToAdd);
return this;
}
/**
* Add a time to this point
*
* @param precisionToSet
* @param timeToSet
* @return the Builder instance.
*/
public Builder time(final long timeToSet, final TimeUnit precisionToSet) {
Preconditions.checkNotNull(precisionToSet, "Precision must be not null!");
this.time = timeToSet;
this.precision = precisionToSet;
return this;
}
/**
* Create a new PointExt.
*
* @return the newly created PointExt.
*/
public PointExt build() {
Preconditions.checkArgument(!Strings.isNullOrEmpty(this.measurement), "PointExt name must not be null or empty.");
Preconditions.checkArgument(this.fields.size() > 0, "PointExt must have at least one field specified.");
PointExt point = new PointExt();
point.setFields(this.fields);
point.setMeasurement(this.measurement);
if (this.time != 0) {
point.setTime(this.time);
point.setPrecision(this.precision);
} else {
point.setTime(System.currentTimeMillis());
point.setPrecision(TimeUnit.MILLISECONDS);
}
point.setTags(this.tags);
return point;
}
}
PointExt autoTime() {
this.autoTime = true;
return this;
}
/**
* @param measurement
* the measurement to set
*/
void setMeasurement(final String measurement) {
this.measurement = measurement;
}
/**
* @param time
* the time to set
*/
void setTime(final Long time) {
this.time = time;
}
/**
* @param tags
* the tags to set
*/
void setTags(final Map<String, String> tags) {
this.tags = tags;
}
/**
* @return the tags
*/
Map<String, String> getTags() {
return this.tags;
}
/**
* @param precision
* the precision to set
*/
void setPrecision(final TimeUnit precision) {
this.precision = precision;
}
/**
* @param fields
* the fields to set
*/
void setFields(final Map<String, Object> fields) {
this.fields = fields;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PointExt [name=");
builder.append(this.measurement);
builder.append(", autoTime=");
builder.append(this.autoTime);
builder.append(", time=");
builder.append(this.time);
builder.append(", tags=");
builder.append(this.tags);
builder.append(", precision=");
builder.append(this.precision);
builder.append(", fields=");
builder.append(this.fields);
builder.append("]");
return builder.toString();
}
/**
* calculate the lineprotocol entry for a single PointExt.
*
* Documentation is WIP : https://github.com/influxdb/influxdb/pull/2997
*
* https://github.com/influxdb/influxdb/blob/master/tsdb/README.md
*
* @return the String without newLine.
*/
public String lineProtocol() {
final StringBuilder sb = new StringBuilder();
sb.append(KEY_ESCAPER.escape(this.measurement));
sb.append(concatenatedTags());
sb.append(concatenateFields());
if(!this.autoTime) {
sb.append(formatedTime());
}
return sb.toString();
}
private StringBuilder concatenatedTags() {
final StringBuilder sb = new StringBuilder();
for (Entry<String, String> tag : this.tags.entrySet()) {
sb.append(",");
sb.append(KEY_ESCAPER.escape(tag.getKey())).append("=").append(KEY_ESCAPER.escape(tag.getValue()));
}
sb.append(" ");
return sb;
}
private StringBuilder concatenateFields() {
final StringBuilder sb = new StringBuilder();
final int fieldCount = this.fields.size();
int loops = 0;
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
numberFormat.setMaximumFractionDigits(340);
numberFormat.setGroupingUsed(false);
numberFormat.setMinimumFractionDigits(1);
for (Entry<String, Object> field : this.fields.entrySet()) {
sb.append(KEY_ESCAPER.escape(field.getKey())).append("=");
loops++;
Object value = field.getValue();
if (value instanceof String) {
String stringValue = (String) value;
sb.append("\"").append(FIELD_ESCAPER.escape(stringValue)).append("\"");
} else if (value instanceof Number) {
sb.append(numberFormat.format(value));
} else {
sb.append(value);
}
if (loops < fieldCount) {
sb.append(",");
}
}
return sb;
}
private StringBuilder formatedTime() {
final StringBuilder sb = new StringBuilder();
if (null == this.time) {
this.time = System.nanoTime();
}
sb.append(" ").append(TimeUnit.NANOSECONDS.convert(this.time, this.precision));
return sb;
}
public static void main(String[] args){
Map<String, String> tags = new HashMap<>();
Map<String, Object> fields = new HashMap<>();
tags.put("host", "192.168.10.188");
tags.put("region", "ShenZhen");
tags.put("taskname", "luobenjihua");
fields.put("length", 187.68);
fields.put("width", 68.95);
fields.put("height", 36.21);
fields.put("desc", "this is a point about luobenjihua!");
PointExt.Builder builder = new PointExt.Builder("usrAutoTime")
.time(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
.tags(tags)
.fields(fields);
PointExt pointExt = builder.build();
String noAutoTimeStr = pointExt.lineProtocol();
System.out.println(String.format("noAutoTimeStr: %s", noAutoTimeStr));
PointExt pointExt1 = builder.build().autoTime();
String autoTimeStr = pointExt1.lineProtocol();
System.out.println(String.format("autoTimeStr: %s", autoTimeStr));
String ip = "127.0.0.1";
InfluxDB influxDB = InfluxDBFactory.connect("http://" + ip + ":8086", "root", "root");
influxDB.createDatabase("autotime");
influxDB.write("autotime", "autogen", InfluxDB.ConsistencyLevel.ONE, pointExt.lineProtocol());
influxDB.write("autotime", "autogen", InfluxDB.ConsistencyLevel.ONE, pointExt1.lineProtocol());
}
}
[END]