GeoMesa Kafka
GeoMesa 2.3.0和2.3.1支持kafka 0.9之后的版本,但是对于kafka1.0之前的版本有些特性不支持
GeoMesa 2.2.X支持的kafka版本相同。
一、GeoMesa Kafka安装
1、直接从github上下载最新版本(2.3.1)进行安装。
github地址:<https://github.com/locationtech/geomesa/releases>
# download and unpackage the most recent distribution:
$ wget "https://github.com/locationtech/geomesa/releases/download/geomesa_2.11-$VERSION/geomesa-kafka_2.11-$VERSION-bin.tar.gz"
$ tar xzvf geomesa-kafka_2.11-$VERSION-bin.tar.gz
$ cd geomesa-kafka_2.11-$VERSION
$ ls
bin/ conf/ dist/ docs/ examples/ lib/ LICENSE.txt
2、通过maven/sbt (具体安装方法去看官网)。
3、通过命令行安装
命令行工具位于 geomesa-kafka_2.11-$VERSION/bin/
可以在geomesa-kafka_2.11-$VERSION/bin/geomesa-env.sh
中设置环境变量和类路径
可以在geomesa-kafka_2.11-$VERSION
目录下执行bin/geomesa-kafka configure
命令安装命令行:
### in geomesa-kafka_2.11-$VERSION:
$ bin/geomesa-kafka configure
Using GEOMESA_KAFKA_HOME as set: /path/to/geomesa-kafka_2.11-$VERSION
Is this intentional? Y\n y
Current value is /path/to/geomesa-kafka_2.11-$VERSION/lib.
Is this intentional? Y\n y
To persist the configuration please update your bashrc file to include:
export GEOMESA_KAFKA_HOME=/path/to/geomesa-kafka_2.11-$VERSION
export PATH=${GEOMESA_KAFKA_HOME}/bin:$PATH
geomesa-kafka
会读取 $KAKFA_HOME
和$ZOOKEEPER_HOME
来加载所需要的jar包。或者执行bin
目录中的install-kafka.sh
来进行jar包拷贝。
测试是否安装成功:
$ geomesa-kafka
Usage: geomesa-kafka [command] [command options]
Commands:
...
二、GeoMesa Kafka Data Store的使用
1、创建 Data Store
要想创建Kafka DataStore
,需要制定两个参数:
- kafka的连接参数:
kafka.brokers
- zookeeper的连接参数:
kafka.zookeepers
- 可选参数:
kafka.zk.path
(指定在zookeeper中的存储路径,如果没有指定,则使用默认路径)
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
Map<String, Serializable> parameters = new HashMap<>();
parameters.put("kafka.zookeepers", "localhost:2181");
parameters.put("kafka.brokers", "localhost:9092");
DataStore dataStore = DataStoreFinder.getDataStore(parameters);
2、Kafka Data Store 参数
Kafka Data Store
的数据完全存储在内存中。
具体参数(*号为必须)
Parameter | Type | Description |
---|---|---|
kafka.brokers * | String | Kafka brokers地址(可以使多个) e.g. “localhost:9092” |
kafka.zookeepers * | String | Kafka zookeepers地址(可以使多个), e.g “localhost:2181” |
kafka.zk.path | String | Zookeeper 路径,可以命名feature types |
kafka.producer.config | String | 通过properties文件配置kafka生产者, 详情请看 Producer Configs |
kafka.producer.clear | Boolean | 为true 时,可以忽略在kafka启动之前主题中的所有消息 |
kafka.consumer.config | String | 通过properties文件配置kafka消费者, 详情请看 New Consumer Configs |
kafka.consumer.read-back | String | 在启动时,读取在这个时间框架内编写的消息。使用“Inf”读取所有消息。如果启用此功能,在处理所有现有消息之前,将无法查询功能。但是,功能监听器仍然会像正常情况一样被调用。 详情请看 Initial Load (Replay) |
kafka.consumer.count | Integer | 每个 feature type 的消费者的个数,设置为0时禁用消费者 (只能生产) |
kafka.consumer.start-on-demand | Boolean | Start consuming a topic only when that feature type is first requested. This can reduce load if some layers are never queried。当feature type第一次被请求时才去消费主题。 如果某些层永远不会被查询,可以减少负载 。 |
kafka.topic.partitions | Integer | kafka主题中的分区数 |
kafka.topic.replication | Integer | 对kafka主题进行重新分区 |
kafka.serialization.type | String | kafka内消息的存储格式,必须为kryo or avro |
kafka.cache.expiry | String | 设置内存中数据的过期时间, e.g. “10 minutes”. 详情请看 Feature Expiration |
kafka.cache.event-time | String | 根据feature确定过期时间而不是消息时间, 详情请看 Feature Event Time |
kafka.cache.event-time.ordering | Boolean | 根据feature时间而不是message时间对feature进行排序。详情请看 Feature Event Time |
kafka.index.cqengine | String | 为内存中的feature使用基于cqengine的属性索引. 详情请看 CQEngine Indexing |
kafka.index.resolution.x | Integer | 设置x维空间索引的桶的个数。默认为360. 详情请看 Spatial Index Resolution |
kafka.index.resolution.y | Integer | 设置y维空间索引的桶的个数。默认为180. 详情请看 Spatial Index Resolution |
kafka.index.tiers | String | 用于索引具有区段的几何图形的层的数量和大小,格式为 x1:y1,x2:y2 . 详情请看 Spatial Index Tiering |
kafka.serialization.lazy | Boolean | 使用features的懒执行反序列化。这可能会提高处理负载,但代价是查询时间稍微变慢 |
geomesa.query.loose-bounding-box | Boolean | 使用松散的(?)边框,它提供了更好的性能,但并不精确 |
geomesa.query.audit | Boolean | 审计传入的查询。默认情况下,审计被写入日志文件 |
geomesa.security.auths | String | 用于查询数据的默认授权,以逗号分隔 |
三、数据生产者
GeoMesa Kafka Data Store
可以将feature
写入kafka中的特定主题。
可将kafka.consumer.count
设置为0来史消息禁止消费。
使用过程:
1、创建Data Store:
import org.geotools.data.DataStoreFinder;
String brokers = ...
String zookeepers = ...
// build parameters map
Map<String, Serializable> params = new HashMap<>();
params.put("kafka.brokers", brokers);
params.put("kafka.zookeepers", zookeepers);
// create the data store
KafkaDataStore ds = (KafkaDataStore) DataStoreFinder.getDataStore(params);
2、创建Schema,其中一个DataStore可以有多个Schema。
SimpleFeatureType sft = ...
ds.createSchema(sft);
3、Kafka Data Store只允许通过 feature ID来更新feature。
可以使用ID过滤器来显示的创建一个feature修改器。
也可以使用 feature writer ,来覆盖相同ID的feature。
// the name of the simple feature type - will be the same as sft.getTypeName();
String typeName = sft.getTypeName();
SimpleFeatureWriter fw = ds.getFeatureWriterAppend(typeName, Transaction.AUTO_COMMIT);
SimpleFeature sf = fw.next();
// set properties on sf
fw.write();
删除 simple feature:
SimpleFeatureStore store = (SimpleFeatureStore) ds.getFeatureSource(typeName);
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
String id = ...
store.removeFeatures(ff.id(ff.featureId(id)));
删除所有simple feature:
store.removeFeatures(Filter.INCLUDE);
删除、新增、修改都会想kafka主题发送一条消息。
四、数据消费者
创建Data Store:
import org.geotools.data.DataStoreFinder;
String brokers = ...
String zookeepers = ...
// build parameters map
Map<String, Serializable> params = new HashMap<>();
params.put("kafka.brokers", brokers);
params.put("kafka.zookeepers", zookeepers);
// optional - to read all existing messages on the kafka topic
params.put("kafka.consumer.from-beginning", java.lang.Boolean.TRUE);
// create the data store
KafkaDataStore ds = (KafkaDataStore) DataStoreFinder.getDataStore(params);
消费:
String typeName = ...
SimpleFeatureStore store = ds.getFeatureSource(typeName);
Filter filter = ...
store.getFeatures(filter);
Data Store不会去消费提供的SimpleFeatureType
,除非你通过getFeatureSource()
or getFeatureReader()
去访问。一旦进行访问,会在dispose()
之前一直进行访问。
五、kafka索引配置
大多数这些选项都可以在消费者数据存储上配置并立即生效 。
初始加载(Replay)
默认情况下,Kafka消费者数据存储将从主题的末尾开始消费。这意味着它只会看到在它启动之后编写的新更新。消费者可以选择从主题的前面开始,通过设置kafka.consumer.read-back
来指定一个持续时间,例如1 hour
。这允许使用者重播旧消息并建立基线状态。要读取整个消息队列,可以将值设置为Inf
。
从0.10.1版本开始,Kafka只支持按给定的间隔读取数据。较老的版本将会从主题的最开始读取。
一个 feature store 在初始加载期间不会返回任何查询结果,直到它达到head状态。
Feature过期时间
通常,Kafka消费者DataStore将保留由生产者DataStore编写的任何feature,直到生产者使用修改后的feature编写器显式地删除它们。可选地,消费者DataStore可以通过kafka.cache.expiry
指定feature的过期时间。当生产者向现有feature写入更新时,使用者将重置过期超时。一旦超时被命中而没有任何更新,该feature将从消费者缓存中删除,并且在查询时不再返回。
Feature事件时间
默认情况下,过期和更新时间由Kafka消息时间决定。feature更新将替换任何先前的feature消息,并且feature将根据读取的时间过期。
要想启用事件时间,需要指定配置名或者配置kafka.cache.event-time
(值为CQL表达式)。这个表达式将根据每个feature进行计算,并且必须计算到一个日期或一个毫秒数。 这个值会和kafka.cache.expiry
一起为一个feature 设置一个过期时间。
将kafka.cache.event-time.ordering
设置为true
来将事件时间进行排序。当启用时,如果读取的feature更新的事件时间比当前feature的事件时间长,则将丢弃该消息。这对于处理不规则的更新流非常有用。
空间索引分辨率
Kafka消费者DataStore使用内存中的空间索引进行查询
空间索引将世界划分为网格,然后只在运行空间查询时检查相关网格单元 。
可通过kafka.index.resolution.x
和kafka.index.resolution.y
来进行设置默认分别为360和180
提高网格分辨率可以减少查询时必须考虑feature的数量,并可以减少同步更新、删除和查询之间的争用。然而,它也需要更多的内存。
空间索引分层
对于不是点(point)的geometries ,kafka 消费者DataStore使用分层内存空间索引进行查询,根据轮廓大小来确定在哪一层。可通过kafka.index.tiers
来设置层数和大小。默认情况下,四层大小分别为1x1
, 4x4
, 32x32
and 360x180
(值的书写格式为1:1,4:4,32:32,360:180
) 。通常,层的大小要与索引的图形的大小一致。大于任何层的几何图形将不会被索引到,所以要包括一个能够覆盖整个世界的层。
CQEngine 索引
默认情况下,kafka消费者Data Store只会创建一个空间索引,因此对于非空间查询(根据时间进行查询)都会遍历索引中的所有feature(这也是非常快的)。
可以创建额外的内存索引来满足非空间查询,可通过kafka.index.cqengine
来进行设置,值应该为name:type
的逗号分隔列表,name
是属性名,type
是索引的类型。如果没有配置geom:geometry
,则几何图形是不会被索引的。
例如:如果将kafka.index.cqengine
设置为name:String,age:Int,dtg:Date,*geom:Point:srid=4326
,则会为每个属性设置索引。
懒序列化
默认,kafka消费之Data Store为feature的属性使用懒序列化。对于写操作不频繁或者所有feature和属性都是一致读取的,可以通过设置kafka.serialization.lazy
为false
。懒序列化会使运行时的损失非常小。
六、数据管理
kafka主题
每一个SimpleFeatureType 或者schema都会写入一个kafka主题。默认,主题名由kafka.zk.path
的值和SimpleFeatureType的名连在一起,并且会将 “/”替换为的 “-”。例如:kafka.zk.path的值为 “geomesa/ds/kafka”,SimpleFeatureType的名为 “foo”,则kafka中主题的名为 geomesa-ds-kafka-foo 。
也可以自己指定kafka的主题名,需要通过geomesa.kafka.topic参数指定:
SimpleFeatureType sft = ....;
sft.getUserData().put("geomesa.kafka.topic", "myTopicName");
kafka主题配置
当调用createSchema
(如果它还不存在)时,将创建给定SimpleFeatureType的Kafka主题。
kafka中的并行性是指一个主题中的多个分区实现的。每个分区只能有一个kafka消费者读取。消费者的数量可以通过kafka.consumer.count来指定。如果只有一个参数的话,这个参数不会产生任何影响。要想创建多个分区,可通过kafka.topic.prations来指定分区个数。要想对现有的主题进行重新分区,可通过kafka.topic.replication来指定重新分区的个数。
kafka主题压缩
kafka有多种方式来防止数据的无限增长。最简单的方式就是设置基于数据大小或时间的保留策略,当主题达到某个阈值时,可以使旧的数据删除。
从geomesa2.1.0开始,kafka Data Store支持kafka的日志压缩。这将允许管理主题的大小,并且保留每个feature的最新状态,这将可以在停机或者重启时仍可维护系统状态。注意,当使用日志压缩时,需要对每个feature进行显示的删除 。否则该feature将永远不会从日志中压缩出来,并且日志大小将无限增长。
如果使用的geomesa版本为2.1.0之前,在启用压缩之前,kafka主题应该使用基于数据大小或者时间的保留策略运行一段时间,因为在旧版本中kafka中的消息永远不会被压缩。
与其他系统进行集成
通过kafka的主题可以很轻松的与其他系统进行集成。每个消息的key是simple feature 的id(utf-8字节),每个消息体是序列化之后的simple feature,或者为null表示删除。在使用kafka0.11.X及其之后的版本时,内部序列化被设置为键 “v”的消息头。
默认情况下,消息体会通过Kryo进行序列化,对于java/scala,可以通过org.locationtech.geomesa.features.kryo.KryoFeatureSerializer
来进行反序列化。
或者,可以在kafka生产者端,通过kafka.serialization.type
参数来指定序列化器(可以指定为avro)。
七、监听feature事件
GeoTools的API包括了一种机制:可以在每次出现 event时触发一个FeatureEvent对象挡在SimpleFeatureSource中添加、删除、修改数据时触发 event,它有一个 changed()
方法,每当触发FeatureEvent时调用此方法。
生产者可以向GeoMesa Kafka生产三种类型的数据,当对GeoMesa Kafka进行消费时,每个消费者都会触发一个FeatureEvent。所有的feature事件都继承自org.locationtech.geomesa.kafka.utils.KafkaFeatureEvent
。
Message read | 触发的事件类别 | FeatureEvent.Type | Filter |
---|---|---|---|
CreateOrUpdate | KafkaFeatureChanged | CHANGED | IN (<id>) |
添加一个新的simple feature,或者对现有的simple feature进行更新 | |||
Delete | KafkaFeatureRemoved | REMOVED | IN (<id>) |
T根据给定的id删除simple feature | |||
Clear | KafkaFeatureCleared | REMOVED | Filter.INCLUDE |
删除所有的simple feature |
要想注册FeatureListener,可以从GeoMesa Kafka消费者 Data Store中创建SimpleFeatureSource,并且使用addFeatureListener()
import org.geotools.data.FeatureEvent;
import org.geotools.data.FeatureListener;
import org.locationtech.geomesa.kafka.utils.KafkaFeatureEvent.KafkaFeatureChanged;
import org.locationtech.geomesa.kafka.utils.KafkaFeatureEvent.KafkaFeatureRemoved;
import org.locationtech.geomesa.kafka.utils.KafkaFeatureEvent.KafkaFeatureCleared;
// unless specified, the consumer will only read data written after its instantiation
SimpleFeatureSource source = ds.getFeatureSource(sftName);
FeatureListener listener = new FeatureListener() {
@Override
public void changed(FeatureEvent featureEvent) {
if (featureEvent instanceof KafkaFeatureChanged) {
KafkaFeatureChanged event = ((KafkaFeatureChanged) featureEvent);
System.out.println("Received add/update for " + event.feature() +
" at " + new java.util.Date(event.time()));
} else if (featureEvent instanceof KafkaFeatureRemoved) {
KafkaFeatureRemoved event = ((KafkaFeatureRemoved) featureEvent);
System.out.println("Received delete for " + event.id() + " " + event.feature() +
" at " + new java.util.Date(event.time()));
} else if (featureEvent instanceof KafkaFeatureCleared) {
KafkaFeatureCleared event = ((KafkaFeatureCleared) featureEvent);
System.out.println("Received clear at " + new java.util.Date(event.time()));
}
}
};
store.addFeatureListener(listener);
在运行结束时,需要调用removeFeatureListener()
@PreDestroy
public void dispose() throws Exception {
store.removeFeatureListener(listener);
// other cleanup
}
八、融合集成
现在只是试验阶段,先不写了。