我应该如何在Cassandra中存储日期间隔?

时间:2013-04-30 14:06:30

标签: java nosql cassandra

我正在开发一个存储传感器测量值的应用程序。有时,传感器将发送错误的测量值(例如,测量值超出范围)。我们不希望单独保留每个测量错误,但我们希望保留有关这些错误的统计信息,例如传感器ID,第一个错误的日期,上一个错误的日期以及其他信息,例如连续错误的数量,我在这里省略......

以下是“ErrorStatistic”类的简化版本:

package foo.bar.repository;

import org.joda.time.DateTime;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

public class ErrorStatistic {

    @Nonnull
    private final String sensorId;
    @Nonnull
    private final DateTime startDate;
    @Nullable
    private DateTime endDate;

    public ErrorStatistic(@Nonnull String sensorId, @Nonnull DateTime startDate) {
        this.sensorId = checkNotNull(sensorId);
        this.startDate = checkNotNull(startDate);
        this.endDate = null;
    }

    @Nonnull
    public String getSensorId() {
        return sensorId;
    }

    @Nonnull
    public DateTime getStartDate() {
        return startDate;
    }

    @Nullable
    public DateTime getEndDate() {
        return endDate;
    }

    public void setEndDate(@Nonnull DateTime endDate) {
        this.endDate = checkNotNull(endDate);
    }

}

我目前使用Hector持久化这些ErrorStatistic,如下所示:

private void persistErrorStatistic(ErrorStatistic errorStatistic) {
    Mutator<String> mutator = HFactory.createMutator(keyspace, StringSerializer.get());

    String rowKey = errorStatistic.getSensorId();
    String columnName = errorStatistic.getStartDate().toString(YYYY_MM_DD_FORMATTER);
    byte[] value = serialize(errorStatistic);

    HColumn<String, byte[]> column = HFactory.createColumn(columnName, value, StringSerializer.get(), BytesArraySerializer.get());
    mutator.addInsertion(rowKey, COLUMN_FAMILY, column);

    mutator.execute();
}

private static final DateTimeFormatter YYYY_MM_DD_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");

当我们错误地收到第一个测量值时,我们会创建一个Error sensorIdstartDate设置的ErrorStatistic,以及null endDate。此ErrorStatistic保存在我们的内存模型中,并保存在Cassandra中。 然后,我们更新内存中的ErrorStatistic,以便进行下一次错误测量,直到我们收到有效的测量结果,此时ErrorStatistic将被保留并从我们的内存模型中删除。

Cassandra因此包含具有开放式间隔的ErrorStatistics(例如[2012-08-01T00:00Z | null])和封闭间隔(例如[2012-08-01T00:00Z | 2013-01-12T10:23Z])

我希望能够按日期查询这些ErrorStatistics。

例如,如果我有这3个错误统计信息:

sensorId  = foo
startDate = 2012-08-01T00:00Z
endDate   = 2012-09-03T02:10Z

sensorId  = foo
startDate = 2012-10-04T03:12Z
endDate   = 2013-02-01T12:28Z

sensorId  = foo
startDate = 2013-03-05T23:22Z
endDate   = null
(this means we have not received a valid measurement since 2013-03-05)

如果我用日期查询Cassandra:

  • 2012-08-04T10:00Z - &gt;它应该返回第一个ErrorStatistic
  • 2012-09-04T00:00Z - &gt;它应该返回此时没有错误
  • 2014-01-03T00:00Z - &gt;它应该返回最后一个ErrorStatistic(因为它是开放式的)

我不确定如何存储和“索引”这些ErrorStatistic对象,以便有效地查询它们。我对Cassandra很陌生,我可能会遗漏一些明显的东西。


编辑:添加以下内容是为了回应Joost的建议,即我应该关注我感兴趣的查询类型。

我将有两种类型的查询:

  • 首先,正如您所猜测的那样,列出给定传感器和时间范围的所有ErrorStatistics。这似乎相对容易。我将遇到的唯一问题是,当ErrorStatistics在我感兴趣的时间范围之前启动时(例如,我查询了4月份的所有错误,我希望我的查询返回ErrorStatistics [2012] -03-29:2012-04-02]也......)
  • 第二个查询似乎更难。我想为给定的传感器和日期找到ErrorStatistics,其间隔包含给定日期,或者startDate在给定日期之前,为空endDate(这意味着我们仍然收到错误对于这个传感器)。我不知道如何有效地做到这一点。我可以加载给定传感器的所有ErrorStatistics,然后检查Java中的间隔...但是如果可能的话我想避免这种情况。我想我希望Cassandra在给定的日期开始并向后看,直到它找到第一个Error startDate在给定日期之前的ErrorStatistics(如果有的话),然后加载它并检查Java是否{{1} }是endDate或在给定日期之后。但我不知道这是否可行,以及效率如何。

1 个答案:

答案 0 :(得分:1)

您必须问自己的问题是您对ErrorStatistics有什么问题。 Cassandra架构设计通常以“每个查询表”方法开头。不要从您拥有的数据(实体)开始,而是从您的问题/查询开始。这与“传统”rdbms设计不同,我发现需要一些时间来习惯。

例如,您要查询每个传感器的统计信息吗?比具有复合键的表(传感器ID,timeuuid)可能是一种解决方案。这样的表允许每个传感器id快速查找,根据时间对结果进行排序。

如果您只想基于时间查询传感器统计信息,那么带有时间单位的(复合)密钥可能会更有帮助,可能使用分片元素来更好地在节点上分配负载。请注意,存在catch:使用Cassandra随机或杂音分区器对主键进行范围查询是不可行的。还有其他分区程序,但它们容易导致群集中负载分布不均匀。

简而言之,从您想要的答案开始,然后“向后”工作到您的桌面设计。使用适当的架构,您的代码将遵循。


Addition(2013-9-5):有什么好处的,Cassandra会在单个分区键的范围内对数据进行排序。这是非常有用的。例如,如果您将表定义为:

,则测量将按start_time以降序排列(最新的第一个)
create table SensorByDate
(
    sensor_id uuid,
    start_date datetime,
    end_date datetime,
    measurement int
    primary key (sensor_id, start_date)
)
with clustering order by (start_time DESC);

在此示例中,sensor_id是分区键,用于确定此行存储的节点。 start_date是组合键中的第二项,用于确定排序顺序。

要在此表中的某个开始日期之后获得第一次测量,您可以制定类似

的查询
select * from SensorByDate 
where sensor_id = ? and start_date < ? limit 1