0

我正在开发一个存储传感器测量值的应用程序。有时,传感器会发送错误的测量值(例如,测量值超出范围)。我们不想单独保留每个测量错误,但我们希望保留有关这些错误的统计信息,例如传感器 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");

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

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

我希望能够按日期查询这些 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 --> 它应该返回第一个 ErrorStatistic
  • 2012-09-04T00:00Z --> 此时应该返回没有错误
  • 2014-01-03T00:00Z --> 它应该返回最后一个 ErrorStatistic(因为它是开放式的)

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


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

我将有两种类型的查询:

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

1 回答 1

1

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

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

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

简而言之,从你想要的答案开始,然后“倒退”到你的桌子设计。使用适当的模式,您的代码将遵循。


补充(2013-9-5):值得一提的是,Cassandra 在单个分区键的范围内对数据进行排序。这是非常有用的东西。例如,如果您将表定义为:

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
于 2013-05-01T13:16:18.450 回答