译者 | 李睿
审校 | 孙淑娟
Apache Cassandra是大规模管理物联网和时间序列数据的一个可靠选择。在Cassandra中存储、查询和分析物联网设备生成的时间序列的最流行用例已经得到很好的理解和记录。在通常情况下,时间序列是根据其源物联网设备存储和查询的。但是,还有另一类物联网应用程序需要快速访问由一组物联网设备基于已知状态生成的最新数据。此类应用程序需要回答的问题是:哪些物联网设备或传感器当前正在报告特定状态?本文将重点关注这个问题,并提供五种可能的数据建模解决方案,以便在Cassandra中有效地回答这个问题。
1、介绍
物联网正在生成大量需要存储、查询和分析的时间序列数据。Apache Cassandra是这项任务的好选择:不仅因为它的速度、可靠性和可扩展性,还因为它的内部数据模型内置了对时间排序数据的支持。
在Cassandra中,时间序列通常由源(例如物联网设备或传感器)或主题(例如参数或指标)存储和检索。有许多很好的资源非常详细地介绍了这个主题,包括这个会议演示视频,以及用于传感器数据和时间序列的即用型Cassandra数据模型。
本文研究了一些相关的物联网用例,它们需要管理来自许多物联网设备的最新数据的快照。此外,需要根据物联网设备报告的特定状态来查询或过滤这样的快照。换句话说,应该能够在Cassandra中快速回答这个问题:哪些物联网设备当前正在报告特定状态?对于许多现实生活中的用例,这个问题听起来更像是:
- 智能家居中哪些灯是打开(关闭)的?
- 停车场中当前有哪些停车位被占用(空置)?
- 当前在特定位置附近有哪些车辆可用(不可用)?
- 当前在某个区域触发(激活或禁用)哪些安全警报?
- 建筑物中当前打开(关闭、锁定、解锁)哪些门?
- 哪些火灾探测传感器当前报告传感器网络中的异常(正常待机、错误)状态?
本文章更加正式地定义了这些问题,并通过示例CQL实现提出了五个实用的解决方案。
2、问题的定义
给定一组物联网设备或传感器,它们生成包含时间戳、数据点和状态的按时间顺序排列的事件序列,查找所有物联网设备报告的具有已知状态的最新事件。这个问题的三个关键组成部分如下所示:
- 输入由物联网设备生成的时间序列组成。时间序列通常存储在一个或多个Cassandra表中。
- 中间视图仅是物联网设备报告的最新事件的快照。可以单独显式存储最新事件,也可以根据输入动态计算它们。
- 最终结果是所有具有已知状态的最新事件。具有相同状态的最新事件应该存储在一起或易于计算。
3、基于状态管理最新的物联网事件
以下确定了基于状态管理最新物联网事件的几个挑战:
- 最新事件的快照不断发展。可能需要额外的工作来增量捕获任何更改。
- 事件发生的频率通常是不可预测的。仅基于事件的时间戳组件可能难以对事件进行分区和组织。
- 一个状态通常只能采用几个唯一值。基于低基数列对数据进行分区和索引可能会导致大分区。
使用以下运行示例作为起点。表events_by_device是输入。这张具有多行分区的表旨在存储时间序列,这样每个分区对应一个设备,分区中的行表示具有时间戳、状态和值的事件。每个分区中的事件始终按其时间戳降序排序。该表实际上为每个分区存储一个时间序列。将五个事件插入表中并检索一个设备的时间序列。此外,在第二个查询中,演示可以动态计算所有设备的所有最新事件。需要注意的是不应该依赖这个查询来解决问题:它可能会变得代价高昂,因为它访问表中的每个分区。
模式:
CQL
1 -- All events by device
2
3 CREATE TABLE events_by_device (
4
5 device_id UUID,
6
7 timestamp TIMESTAMP,
8
9 state TEXT,
10
11 value TEXT,
12
13 PRIMARY KEY((device_id), timestamp)
14
15 ) WITH CLUSTERING ORDER BY (timestamp DESC);
数据:
CQL
1 -- Event 1-1
2
3 INSERT INTO events_by_device
4
5 (device_id, timestamp, state, value)
6
7 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
8
9 '2021-01-01 01:11:11', 'on', 'event 1-1');
10
11 -- Event 1-2
12
13 INSERT INTO events_by_device
14
15 (device_id, timestamp, state, value)
16
17 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
18
19 '2021-01-01 02:22:22', 'off', 'event 1-2');
20
21 -- Event 1-3
22
23 INSERT INTO events_by_device
24
25 (device_id, timestamp, state, value)
26
27 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
28
29 '2021-01-01 03:33:33', 'on', 'event 1-3');
30
31 -- Event 2-1
32
33 INSERT INTO events_by_device
34
35 (device_id, timestamp, state, value)
36
37 VALUES (22222222-aaaa-bbbb-cccc-12345678abcd,
38
39 '2021-02-02 01:11:11', 'off', 'event 2-1');
40
41 -- Event 3-1
42
43 INSERT INTO events_by_device
44
45 (device_id, timestamp, state, value)
46
47 VALUES (33333333-aaaa-bbbb-cccc-12345678abcd,
48
49 '2021-03-03 01:11:11', 'off', 'event 3-1');
查询:
CQL
1-- Find all events for a device
2
3 SELECT device_id, timestamp, state, value
4
5 FROM events_by_device
6
7 WHERE device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
8
9
10
device_id | timestamp | state | value
11
12--------------------------------------+---------------------------------+-------+-----------
13
14 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | on | event 1-3
15
16 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 02:22:22.000000+0000 | off | event 1-2
17
18 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 01:11:11.000000+0000 | on | event 1-1
19
20
21-- Find the latest events for all devices
22
23 SELECT device_id, timestamp, state, value
24
25 FROM events_by_device
26
27 PER PARTITION LIMIT 1;
28
29
30 device_id | timestamp | state | value
31
32--------------------------------------+---------------------------------+-------+-----------
33
34 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | off | event 3-1
35
36 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | off | event 2-1
37
38 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | on | event 1-3
查询:
CQL
1-- Find all events for a device
2
3 SELECT device_id, timestamp, state, value
4
5 FROM events_by_device
6
7 WHERE device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
8
9
10
device_id | timestamp | state | value
11
12--------------------------------------+---------------------------------+-------+-----------
13
14 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | on | event 1-3
15
16 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 02:22:22.000000+0000 | off | event 1-2
17
18 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 01:11:11.000000+0000 | on | event 1-1
19
20
21-- Find the latest events for all devices
22
23 SELECT device_id, timestamp, state, value
24
25 FROM events_by_device
26
27 PER PARTITION LIMIT 1;
28
29
30 device_id | timestamp | state | value
31
32--------------------------------------+---------------------------------+-------+-----------
33
34 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | off | event 3-1
35
36 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | off | event 2-1
37
38 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | on | event 1-3
需要注意,可以假设每个设备的事件数不超过10万件。否则,可能不得不通过在其分区键定义中引入另一列来进一步拆分表events_by_device中的分区。由于这对于本文中解决的问题并不重要,所以尽量保持简单。
鉴于问题定义和物联网事件的运行CQL示例,在此准备描述具有不同特征的五种解决方案。
4、解决方案一:物化视图
第一个解决方案需要一个新表和一个物化视图。表latest_events_by_device是一个单行分区表,其中每个分区对应一个设备,每一行对应最新的已知事件。此表的目的是仅获取物联网设备报告的最新事件的快照。该表也是物化视图latest_events_by_state的基表,可以使用状态查询最新事件。
需要注意,完全相同的数据被插入到表events_by_device和latest_events_by_device中。对于后者,插入变为更新插入,将行更新为最新事件。
模式:
CQL
1 -- Latest known events by device
2
3 CREATE TABLE latest_events_by_device (
4
5 device_id UUID,
6
7 timestamp TIMESTAMP,
8
9 state TEXT,
10
11 value TEXT,
12
13 PRIMARY KEY((device_id))
14
15 );
16
17
18 -- Latest events by state
19
20 CREATE MATERIALIZED VIEW latest_events_by_state AS
21
22 SELECT * FROM latest_events_by_device
23
24 WHERE state IS NOT NULL AND device_id IS NOT NULL
25
26 PRIMARY KEY ((state), device_id);
数据:
CQL
1-- Event 1-1
2
3 INSERT INTO latest_events_by_device
4
5 (device_id, timestamp, state, value)
6
7 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
8
9 '2021-01-01 01:11:11', 'on', 'event 1-1');
10
11 -- Event 1-2
12
13 INSERT INTO latest_events_by_device
14
15 (device_id, timestamp, state, value)
16
17 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
18
19 '2021-01-01 02:22:22', 'off', 'event 1-2');
20
21 -- Event 1-3
22
23 INSERT INTO latest_events_by_device
24
25 (device_id, timestamp, state, value)
26
27 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
28
29 '2021-01-01 03:33:33', 'on', 'event 1-3');
30
31 -- Event 2-1
32
33 INSERT INTO latest_events_by_device
34
35 (device_id, timestamp, state, value)
36
37 VALUES (22222222-aaaa-bbbb-cccc-12345678abcd,
38
39 '2021-02-02 01:11:11', 'off', 'event 2-1');
40
41 -- Event 3-1
42
43 INSERT INTO latest_events_by_device
44
45 (device_id, timestamp, state, value)
46
47 VALUES (33333333-aaaa-bbbb-cccc-12345678abcd,
48
49 '2021-03-03 01:11:11', 'off', 'event 3-1');
查询:
CQL
1 -- Find all the latest events with state 'on'
2
3 SELECT state, device_id, timestamp, value
4
5 FROM latest_events_by_state
6
7 WHERE state = 'on';
8
9
10 state | device_id | timestamp | value
11
12-------+--------------------------------------+---------------------------------+-----------
13
14 on | 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | event 1-3
15
16
17 -- Find all the latest events with state 'off'
18
19 SELECT state, device_id, timestamp, value
20
21 FROM latest_events_by_state
22
23 WHERE state = 'off';
24
25
26 state | device_id | timestamp | value
27
28-------+--------------------------------------+---------------------------------+-----------
29
30 off | 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | event 2-1
31
32 off | 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | event 3-1
物化视图解决方案具有以下特点:
- 适用性:基于状态的查询返回100K行/100MB或更少的数据。
- 优点:视图自动维护和出色的性能。
- 缺点:物化视图有一些限制;数据分布可能会出现偏差。
为了支持多租户,可以把表的主键改成PRIMARY KEY((tenant,device_id))或者PRIMARY KEY((tenant),device_id),物化视图的主键改成PRIMARY KEY((tenant,state),device_id)。多租户也可能有助于改善数据分布。
只要了解并愿意抵消物化视图的限制,这一数据模型就可以成为许多应用程序的简单、有效和高效的选择。这种数据模型的另一个不太明显的优势是从Apache Pulsar或Apache Kafka等事件流平台提供数据是多么容易。所有事件都可以转到基表,而其余的由物化视图处理。
5、解决方案二:二级索引
第二种解决方案需要一个新表和一个二级索引。该表与物化视图解决方案中的表相同。表latest_events_by_device是一个单行分区表,其中每个分区对应一个设备,每一行对应最新的已知事件。此表的目的是仅获取物联网设备报告的最新事件的快照。为该表创建二级索引latest_events_by_state_2i,用于根据状态查询最新事件。
同样,完全相同的数据被插入到表events_by_device和latest_events_by_device中。对于后者,插入变为更新插入,将行更新为最新事件。
模式:
CQL
1-- Latest known events by device
2
3 CREATE TABLE latest_events_by_device (
4
5 device_id UUID,
6
7 timestamp TIMESTAMP,
8
9 state TEXT,
10
11 value TEXT,
12
13 PRIMARY KEY((device_id))
14
15 );
16
17
18 -- Latest events by state
19
20 CREATE INDEX latest_events_by_state_2i
21
22 ON latest_events_by_device (state);
数据:
CQL
1 -- Event 1-1
2
3 INSERT INTO latest_events_by_device
4
5 (device_id, timestamp, state, value)
6
7 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
8
9 '2021-01-01 01:11:11', 'on', 'event 1-1');
10
11 -- Event 1-2
12
13 INSERT INTO latest_events_by_device
14
15 (device_id, timestamp, state, value)
16
17 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
18
19 '2021-01-01 02:22:22', 'off', 'event 1-2');
20
21 -- Event 1-3
22
23 INSERT INTO latest_events_by_device
24
25 (device_id, timestamp, state, value)
26
27 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
28
29 '2021-01-01 03:33:33', 'on', 'event 1-3');
30
31 -- Event 2-1
32
33 INSERT INTO latest_events_by_device
34
35 (device_id, timestamp, state, value)
36
37 VALUES (22222222-aaaa-bbbb-cccc-12345678abcd,
38
39 '2021-02-02 01:11:11', 'off', 'event 2-1');
40
41 -- Event 3-1
42
43 INSERT INTO latest_events_by_device
44
45 (device_id, timestamp, state, value)
46
47 VALUES (33333333-aaaa-bbbb-cccc-12345678abcd,
48
49 '2021-03-03 01:11:11', 'off', 'event 3-1');
查询:
CQL
1 -- Find all the latest events with state 'on'
2
3 SELECT state, device_id, timestamp, value
4
5 FROM latest_events_by_device
6
7 WHERE state = 'on';
8
9
10 state | device_id | timestamp | value
11
12-------+--------------------------------------+---------------------------------+-----------
13
14 on | 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | event 1-3
15
16
17 -- Find all the latest events with state 'off'
18
19 SELECT state, device_id, timestamp, value
20
21 FROM latest_events_by_device
22
23 WHERE state = 'off';
24
25
26 state | device_id | timestamp | value
27
28-------+--------------------------------------+---------------------------------+-----------
29
30 off | 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | event 3-1
31
32 off | 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | event 2-1
二级索引方案具有以下特点:
- 适用性:基于状态的查询返回100K行/100MBs或更多的数据;基于状态的查询很少执行。
- 优点:在检索大型结果集时,可以更好地在集群中的节点之间分配查询工作负载。
- 缺点:二级索引有一些限制;对于实时应用程序,性能可能会变得让人不满意。
在某些情况下,这个数据模型可能是一个合理的选择。特别是,当通过将表主键更改为PRIMARY KEY((tenant),device_id)来引入多租户时,可以达到使用二级索引进行实时事务查询的好时机。那是在基于分区键和查询谓词中指定的索引列从大型多行分区中检索行时。
6、解决方案三:状态分区表
第三种解决方案依赖于表latest_events_by_state使用状态来组织和查询最新事件。每次向该表中插入具有某种状态的事件时,都必须删除同一物联网设备的具有其他状态的任何过时事件。在这个示例中,每个事件都有一个插入和一个删除,因为只有两个唯一状态。如果有三种可能的状态,每个新事件将导致一次插入和两次删除。
模式:
CQL
1 -- Latest events by state
2
3 CREATE TABLE latest_events_by_state (
4
5 state TEXT,
6
7 device_id UUID,
8
9 timestamp TIMESTAMP,
10
11 value TEXT,
12
13 PRIMARY KEY((state), device_id)
14
15 );
数据:
CQL
1-- Event 1-1
2
3 INSERT INTO latest_events_by_state
4
5 (state, device_id, timestamp, value)
6
7 VALUES ('on', 11111111-aaaa-bbbb-cccc-12345678abcd,
8
9 '2021-01-01 01:11:11', 'event 1-1');
10
11 DELETE FROM latest_events_by_state
12
13 WHERE state = 'off' AND
14
15 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
16
17 -- Event 1-2
18
19 INSERT INTO latest_events_by_state
20
21 (state, device_id, timestamp, value)
22
23 VALUES ('off', 11111111-aaaa-bbbb-cccc-12345678abcd,
24
25 '2021-01-01 02:22:22', 'event 1-2');
26
27 DELETE FROM latest_events_by_state
28
29 WHERE state = 'on' AND
30
31 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
32
33 -- Event 1-3
34
35 INSERT INTO latest_events_by_state
36
37 (state, device_id, timestamp, value)
38
39 VALUES ('on', 11111111-aaaa-bbbb-cccc-12345678abcd,
40
41 '2021-01-01 03:33:33', 'event 1-3');
42
43 DELETE FROM latest_events_by_state
44
45 WHERE state = 'off' AND
46
47 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
48
49 -- Event 2-1
50
51 INSERT INTO latest_events_by_state
52
53 (state, device_id, timestamp, value)
54
55 VALUES ('off', 22222222-aaaa-bbbb-cccc-12345678abcd,
56
57 '2021-02-02 01:11:11', 'event 2-1');
58
59 DELETE FROM latest_events_by_state
60
61 WHERE state = 'on' AND
62
63 device_id = 22222222-aaaa-bbbb-cccc-12345678abcd;
64
65 -- Event 3-1
66
67 INSERT INTO latest_events_by_state
68
69 (state, device_id, timestamp, value)
70
71 VALUES ('off', 33333333-aaaa-bbbb-cccc-12345678abcd,
72
73 '2021-03-03 01:11:11', 'event 3-1');
74
75 DELETE FROM latest_events_by_state
76
77 WHERE state = 'on' AND
78
79 device_id = 33333333-aaaa-bbbb-cccc-12345678abcd;
查询:
CQL
1-- Find all the latest events with state 'on'
2
3 SELECT state, device_id, timestamp, value
4
5 FROM latest_events_by_state
6
7 WHERE state = 'on';
8
9
10 state | device_id | timestamp | value
11
12-------+--------------------------------------+---------------------------------+-----------
13
14 on | 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | event 1-3
15
16
17 -- Find all the latest events with state 'off'
18
19 SELECT state, device_id, timestamp, value
20
21 FROM latest_events_by_state
22
23 WHERE state = 'off';
24
25
26 state | device_id | timestamp | value
27
28-------+--------------------------------------+---------------------------------+-----------
29
30 off | 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | event 2-1
31
32 off | 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | event 3-1
状态分区表解决方案具有以下特点:
- 适用性:基于状态的查询返回100K行/100MB或更少的数据。
- 优点:出色的性能。
- 缺点:需要额外的删除来维护表;可能需要采取措施防止Cassandra的问题;数据分布可能会出现偏差。
在大多数情况下,这三个缺点都不应被视为严重障碍。额外的删除相当于额外的写入,Cassandra可以轻松扩展以处理更多写入。鉴于插入和删除一次又一次地应用于相同的行,Cassandra很可能在MemTable中而不是在SSTable中得到解决,这可以显著地减少Cassandra的总数。例如,对于一个给定的物联网设备,即使是频繁的状态更新都命中同一个MemTable也只能导致一个Cassandra。仍然建议监控表指标以排除任何潜在问题。最后但同样重要的是,数据分布取决于数据和应用程序特征。在本文的最后一个解决方案中,完全控制了数据分布。
可以通过将表主键更改为PRIMARYKEY((tenant,state),device_id)轻松支持多个租户。多租户也可能有助于改善数据分布。总体而言,在性能方面,该解决方案应该可以与物化视图解决方案相媲美。
7、解决方案四:多个表
第四种解决方案的特点是每个状态都有一个单独的表格。对表latest_on_events_by_device的每次插入都必须伴随着从表latest_off_events_by_device中删除,反之亦然。这是为了确保最新事件始终取消同一设备的任何具有不同状态的过时事件。对表的基于状态的查询可能会变得成本非常高昂,因为它们必须扫描表中的所有分区。
模式:
CQL
1 -- Latest 'on' events by device
2
3 CREATE TABLE latest_on_events_by_device (
4
5 device_id UUID,
6
7 timestamp TIMESTAMP,
8
9 value TEXT,
10
11 PRIMARY KEY((device_id))
12
13 );
14
15
16-- Latest 'off' events by device
17
18 CREATE TABLE latest_off_events_by_device (
19
20 device_id UUID,
21
22 timestamp TIMESTAMP,
23
24 value TEXT,
25
26 PRIMARY KEY((device_id))
27
28 );
数据:
CQL
1 - Event 1-1
2
3 INSERT INTO latest_on_events_by_device
4
5 (device_id, timestamp, value)
6
7
VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
8
9 '2021-01-01 01:11:11', 'event 1-1');
10
11 DELETE FROM latest_off_events_by_device
12
13 WHERE device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
14
15-- Event 1-2
16
17 INSERT INTO latest_off_events_by_device
18
19 (device_id, timestamp, value)
20
21 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
22
23 '2021-01-01 02:22:22', 'event 1-2');
24
25 DELETE FROM latest_on_events_by_device
26
27 WHERE device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
28
29-- Event 1-3
30
31 INSERT INTO latest_on_events_by_device
32
33 (device_id, timestamp, value)
34
35 VALUES (11111111-aaaa-bbbb-cccc-12345678abcd,
36
37 '2021-01-01 03:33:33', 'event 1-3');
38
39 DELETE FROM latest_off_events_by_device
40
41 WHERE device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
42
43-- Event 2-1
44
45 INSERT INTO latest_off_events_by_device
46
47 (device_id, timestamp, value)
48
49 VALUES (22222222-aaaa-bbbb-cccc-12345678abcd,
50
51 '2021-02-02 01:11:11', 'event 2-1');
52
53 DELETE FROM latest_on_events_by_device
54
55 WHERE device_id = 22222222-aaaa-bbbb-cccc-12345678abcd;
56
57
-- Event 3-1
58
59 INSERT INTO latest_off_events_by_device
60
61 (device_id, timestamp, value)
62
63 VALUES (33333333-aaaa-bbbb-cccc-12345678abcd,
64
65 '2021-03-03 01:11:11', 'event 3-1');
66
67 DELETE FROM latest_on_events_by_device
68
69 WHERE device_id = 33333333-aaaa-bbbb-cccc-12345678abcd;
查询:
CQL
1 -- Find all the latest events with state 'on'
2
3 SELECT device_id, timestamp, value
4
5FROM latest_on_events_by_device;
6
7
8 device_id | timestamp | value
9
10--------------------------------------+---------------------------------+-----------
11
12 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | event 1-3
13
14
15-- Find all the latest events with state 'off'
16
17 SELECT device_id, timestamp, value
18
19 FROM latest_off_events_by_device;
20
21
22 device_id | timestamp | value
23
24--------------------------------------+---------------------------------+-----------
25
26 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | event 3-1
27
28 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | event 2-1
多表解决方案具有以下特点:
- 适用性:基于状态的查询返回100K行/100MBs或更多的数据;基于状态的查询很少执行。
- 优点:在检索大型结果集时,可以更好地在集群中的节点之间分配查询工作负载。
- 缺点:实时应用程序的性能可能无法令人满意;需要额外的删除来维护表;可能需要采取措施防止与Cassandra有关的问题。
该方案在查询性能上与二级索引方案相当。可以通过将表主键更改为PRIMARYKEY((tenant,device_id))或PRIMARYKEY((tenant),device_id)来支持多个租户。虽然在实践中不推荐这种解决方案,但这种数据模型真正有趣的是它如何为接下来讨论的可定制分区做好准备。
8、解决方案五:可自定义的分区
最终解决方案基于为每个状态使用单独的表的想法。但是这一次,使用人工桶对表进行分区。桶值很容易使用来自设备UUID标识符的用户定义函数散列来计算。在这一示例中,该函数从UUID文字中提取前三位,将生成的十六进制数转换为十进制数,并返回十进制数除以3的余数。因此,最多可以有三个桶或每个表的分区,值为0、1或2。在这一示例中,所有的设备标识符都映射到存储桶0只是巧合。由于版本4UUID是随机生成的,因此对于大量事件,数据应该或多或少均匀分布在三个存储桶中。
与之前的数据模型类似,每次对表latest_on_events_by_bucket的插入都必须伴随着从表latest_off_events_by_bucket中删除,反之亦然。基于状态的查询的性能取决于分区,并且分区是可定制的。
模式:
CQL
1-- Custom hash function
2
3 CREATE FUNCTION hash(id UUID)
4
5 RETURNS NULL ON NULL INPUT
6
7 RETURNS INT
8
9 LANGUAGE Java AS
10
11 'return Integer.parseInt(id.toString().substring(0,3),16) % 3;';
12
13
14-- Latest 'on' events by device
15
16 CREATE TABLE latest_on_events_by_bucket (
17
18 bucket INT,
19
20 device_id UUID,
21
22 timestamp TIMESTAMP,
23
24 value TEXT,
25
26 PRIMARY KEY((bucket), device_id)
27
28 );
29
30
31-- Latest 'off' events by device
32
33 CREATE TABLE latest_off_events_by_bucket (
34
35 bucket INT,
36
37 device_id UUID,
38
39 timestamp TIMESTAMP,
40
41 value TEXT,
42
43 PRIMARY KEY((bucket), device_id)
44
45 );
数据:
CQL
1-- Event 1-1
2
3 INSERT INTO latest_on_events_by_bucket
4
5 (bucket, device_id, timestamp, value)
6
7 VALUES (hash(11111111-aaaa-bbbb-cccc-12345678abcd),
8
9 11111111-aaaa-bbbb-cccc-12345678abcd,
10
11 '2021-01-01 01:11:11', 'event 1-1');
12
13 DELETE FROM latest_off_events_by_bucket
14
15 WHERE bucket = hash(11111111-aaaa-bbbb-cccc-12345678abcd) AND
16
17 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
18
19-- Event 1-2
20
21 INSERT INTO latest_off_events_by_bucket
22
23 (bucket, device_id, timestamp, value)
24
25 VALUES (hash(11111111-aaaa-bbbb-cccc-12345678abcd),
26
27 11111111-aaaa-bbbb-cccc-12345678abcd,
28
29 '2021-01-01 02:22:22', 'event 1-2');
30
31 DELETE FROM latest_on_events_by_bucket
32
33 WHERE bucket = hash(11111111-aaaa-bbbb-cccc-12345678abcd) AND
34
35 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
36
37-- Event 1-3
38
39 INSERT INTO latest_on_events_by_bucket
40
41 (bucket, device_id, timestamp, value)
42
43 VALUES (hash(11111111-aaaa-bbbb-cccc-12345678abcd),
44
45 11111111-aaaa-bbbb-cccc-12345678abcd,
46
47 '2021-01-01 03:33:33', 'event 1-3');
48
49 DELETE FROM latest_off_events_by_bucket
50
51 WHERE bucket = hash(11111111-aaaa-bbbb-cccc-12345678abcd) AND
52
53 device_id = 11111111-aaaa-bbbb-cccc-12345678abcd;
54
55 -- Event 2-1
56
57 INSERT INTO latest_off_events_by_bucket
58
59 (bucket, device_id, timestamp, value)
60
61 VALUES (hash(22222222-aaaa-bbbb-cccc-12345678abcd),
62
63 22222222-aaaa-bbbb-cccc-12345678abcd,
64
65 '2021-02-02 01:11:11', 'event 2-1');
66
67 DELETE FROM latest_on_events_by_bucket
68
69 WHERE bucket = hash(22222222-aaaa-bbbb-cccc-12345678abcd) AND
70
71 device_id = 22222222-aaaa-bbbb-cccc-12345678abcd;
72
73-- Event 3-1
74
75 INSERT INTO latest_off_events_by_bucket
76
77 (bucket, device_id, timestamp, value)
78
79 VALUES (hash(33333333-aaaa-bbbb-cccc-12345678abcd),
80
81 33333333-aaaa-bbbb-cccc-12345678abcd,
82
83 '2021-03-03 01:11:11', 'event 3-1');
84
85 DELETE FROM latest_on_events_by_bucket
86
87 WHERE bucket = hash(33333333-aaaa-bbbb-cccc-12345678abcd) AND
88
89 device_id = 33333333-aaaa-bbbb-cccc-12345678abcd;
查询:
CQL
1 -- Find all the latest events with state 'on'
2
3 SELECT bucket, device_id, timestamp, value
4
5 FROM latest_on_events_by_bucket
6
7 WHERE bucket IN (0,1,2);
8
9
10 bucket | device_id | timestamp | value
11
12--------+--------------------------------------+---------------------------------+-----------
13
14 0 | 11111111-aaaa-bbbb-cccc-12345678abcd | 2021-01-01 03:33:33.000000+0000 | event 1-3
15
16
17-- Find all the latest events with state 'off'
18
19 SELECT bucket, device_id, timestamp, value
20
21 FROM latest_off_events_by_bucket
22
23 WHERE bucket IN (0,1,2);
24
25
26 bucket | device_id | timestamp | value
27
28--------+--------------------------------------+---------------------------------+-----------
29
30 0 | 22222222-aaaa-bbbb-cccc-12345678abcd | 2021-02-02 01:11:11.000000+0000 | event 2-1
31
32 0 | 33333333-aaaa-bbbb-cccc-12345678abcd | 2021-03-03 01:11:11.000000+0000 | event 3-1
可定制的分区方案具有以下特点:
- 适用性:定制时可满足不同要求。
- 优点:灵活性;可以通过自定义分区来优化性能。
- 缺点:必须提供良好的分区功能;需要额外的删除来维护表;可能需要采取措施防止与Cassandra有关的问题。
选择一个良好的分区函数是一个很好的问题。虽然这可能会增加一点复杂性,但该解决方案可以完全控制数据分区和查询性能。找到一个良好的分区函数将取决于特定的数据和应用程序要求,并且可能需要一些经验和实验。例如,从1个分区检索100行通常比从10个分区检索100行快,但从1个分区检索100万行通常比从10个分区检索100万行慢。接下来,额外的删除相当于额外的写入,Cassandra可以轻松扩展以处理更多写入。
鉴于插入和删除一次又一次地应用于相同的行,Cassandra很可能在MemTable中而不是在SS Table中得到解决,这可以显著地减少Cassandra的总数。例如,对于一个给定的物联网设备,即使是频繁的状态更新都命中同一个Mem Table也只能导致一个Cassandra。仍然建议监控表指标以排除任何潜在问题。最后但同样重要的是,数据分布取决于数据和应用程序特征。在本文的最后一个解决方案中,完全控制了数据分布。
这种数据模型提供了极大的灵活性。通过将每个表的主键更改为PRIMARY KEY((tenant,bucket),device_id)可以实现多租户。更重要的是,可以更改分区函数以增加或减少分区的数量。检索较小结果集的查询应访问较少数量的分区以获得更好的性能。检索更大结果集的查询应访问更多分区以更好地分配工作负载。可以针对不同的状态和租户使用不同的功能以实现最佳性能。更好的分区应该会带来更好的性能。
9、结论
本文定义了基于状态管理最新物联网事件的问题,确定了它的挑战,并描述了如何在Apache Cassandra中使用五种不同的数据模型来解决它。此外还阐述了每个数据模型的适用性、优缺点。最终的建议是关注物化视图、状态分区表和可自定义的分区数据模型。选择前两个是因为它们简单易用。当采用其他选项时,考虑可定制的分区以获得最大的灵活性。最后,开放探索新的可能解决方案,这些解决方案可能会将一些计算推向应用程序或依赖专门的搜索索引和其他技术。
原文链接:https://dzone.com/articles/five-data-models-for-iot-managing-the-latest-iot-e