1. KSQL 介绍
KSQL 引擎——一个基于流的 SQL。推出 KSQL 是为了降低流式处理的门槛,为处理 Kafka 数据提供简单而完整的可交互式 SQL 接口。KSQL 目前可以支持多种流式操作,包括聚合(aggregate)、连接(join)、时间窗口(window)、会话(session),等等。
2. KSQL 与传统数据库的区别
KSQL 与关系型数据库中的 SQL 还是有很大不同的。传统的 SQL 都是即时的一次性操作,不管是查询还是更新都是在当前的数据集上进行。而 KSQL 则不同,KSQL 的查询和更新是持续进行的,而且数据集可以源源不断地增加。KSQL 所做的其实是转换操作,也就是流式处理。
3. KSQL 的适用场景
1). 实时监控
一方面,可以通过 KSQL 自定义业务层面的度量指标,这些指标可以实时获得。底层的度量指标无法告诉我们应用程序的实际行为,所以基于应用程序生成的原始事件来自定义度量指标可以更好地了解应用程序的运行状况。另一方面,可以通过 KSQL 为应用程序定义某种标准,用于检查应用程序在生产环境中的行为是否达到预期。
2). 安全检测
KSQL 把事件流转换成包含数值的时间序列数据,然后通过可视化工具把这些数据展示在 UI 上,这样就可以检测到很多威胁安全的行为,比如欺诈、入侵,等等。KSQL 为此提供了一种实时、简单而完备的方案。
3). 在线数据集成
大部分的数据处理都会经历 ETL(Extract—Transform—Load)这样的过程,而这样的系统通常都是通过定时的批次作业来完成数据处理的,但批次作业所带来的延时在很多时候是无法被接受的。而通过使用 KSQL 和 Kafka 连接器,可以将批次数据集成转变成在线数据集成。比如,通过流与表的连接,可以用存储在数据表里的元数据来填充事件流里的数据,或者在将数据传输到其他系统之前过滤掉数据里的敏感信息。
4). 应用开发
对于复杂的应用来说,使用 Kafka 的原生 Streams API 或许会更合适。不过,对于简单的应用来说,或者对于不喜欢 Java 编程的人来说,KSQL 会是更好的选择。
4. KSQL 架构
KSQL 是一个独立运行的服务器,多个 KSQL 服务器可以组成集群,可以动态地添加服务器实例。集群具有容错机制,如果一个服务器失效,其他服务器就会接管它的工作。KSQL 命令行客户端通过 REST API 向集群发起查询操作,可以查看流和表的信息、查询数据以及查看查询状态。因为是基于 Streams API 构建的,所以 KSQL 也沿袭了 Streams API 的弹性、状态管理和容错能力,同时也具备了仅一次(exactly once)语义。KSQL 服务器内嵌了这些特性,并增加了一个分布式 SQL 引擎、用于提升查询性能的自动字节码生成机制,以及用于执行查询和管理的 REST API。
Kafka+KSQL 要颠覆传统数据库
传统关系型数据库以表为核心,日志只不过是实现手段。而在以事件为中心的世界里,情况却恰好相反。日志成为了核心,而表几乎是以日志为基础,新的事件不断被添加到日志里,表的状态也因此发生变化。将 Kafka 作为中心日志,配置 KSQL 这个引擎,我们就可以创建出我们想要的物化视图,而且视图也会持续不断地得到更新。
5. KSQL 的核心抽象
KSQL 是基于 Kafka 的 Streams API 进行构建的,所以它的两个核心概念是流(Stream)和表(Table)。流是没有边界的结构化数据,数据可以被源源不断地添加到流当中,但流中已有的数据是不会发生变化的,即不会被修改也不会被删除。表即是流的视图,或者说它代表了可变数据的集合。它与传统的数据库表类似,只不过具备了一些流式语义,比如时间窗口,而且表中的数据是可变的。KSQL 将流和表集成在一起,允许将代表当前状态的表与代表当前发生事件的流连接在一起。
6. Quick start
Important: This release is a developer preview and is free and open-source from Confluent under the Apache 2.0 license. Do not run KSQL against a production cluster.
环境准备: Docker环境与非Docker环境
以下采用第二种环境方式,但是kafka也是搭建在Docker环境呢,上述环境区别是:
Docker环境,镜像会包含一份confluentinc公司的docker发行版和ksql环境。此次测试非Docker环境采用kafka官方版本做成的容器,ksql为单独编译版本
Kafka地址192.168.102.51:9092
Zookeeper地址 192.168.102.51:2181
Topic:test1
1). 代码新建测试所用topic
object CreateTopic {
def main(args: Array[String]): Unit = {
val config = new java.util.Properties()
config.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.102.51:9092")
val admin = AdminClient.create(config)
val topic = new NewTopic("test1", 1, 1)
admin.createTopics(Collections.singletonList(topic))
// for (tt <- admin.listTopics().names().get()) {
// println(tt)
// }
admin.close()
}
}
2). 进去ksql命令行新建一个stream
CREATE stream stream_test (key string, c1 bigint,c2 bigint,c3 bigint) WITH (kafka_topic='test1', value_format='JSON');
制定格式为json, 与kafka producer发送消息类型要对应
3). 给kafka topic 发送消息
object KsqlTest {
def main(args: Array[String]): Unit = {
val topic = "test1"
val props = new Properties();
props.put("bootstrap.servers", "192.168.102.51:9092")
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer")
// props.put("property", "parse.key=true")
// props.put("property", "key.separator=:")
val producer = new KafkaProducer[String, String](props)
while (true) {
(1 to 3).foreach(i => {
val date = new java.util.Date()
val record = new ProducerRecord[String, String](topic, "{\"key\":\"" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
+ "\",\"c1\":" + i + ",\"c2\":" + i + ",\"c3\":" + i + "}")
producer.send(record)
producer.flush()
})
Thread.sleep(1000)
}
}
}
发送的消息如下:
{"key":"2017-09-19 14:45:26","c1":2,"c2":2,"c3":2}
{"key":"2017-09-19 14:45:26","c1":3,"c2":3,"c3":3}
{"key":"2017-09-19 14:45:26","c1":3,"c2":3,"c3":3}
标准的json格式与stream里的字段对应
4). 命令行执行 select * from stream_test;
5). 查看stream
Show streams: 查看所有stream
Describe (stream name): 查看制定stream的类型
6). 根据已有的stream建新的stream
create stream stream_test2 as select date, c1 from stream_test WINDOW TUMBLING (size 10 second);
Show queries; 结果
Select * from stream_test2 结果
7). 支持简单的函数
- -暂不支持自定义函数
7. ksql-clickstream-demo
https://github.com/confluentinc/ksql/tree/0.1.x/ksql-clickstream-demo
8. 代码部分
1). 消息获取部分
Ksqlcontext
public void sql(String sql) throws Exception {
List<QueryMetadata> queryMetadataList = ksqlEngine.buildMultipleQueries(
false, sql, Collections.emptyMap());
for (QueryMetadata queryMetadata: queryMetadataList) {
if (queryMetadata instanceof PersistentQueryMetadata) {
PersistentQueryMetadata persistentQueryMetadata = (PersistentQueryMetadata) queryMetadata;
persistentQueryMetadata.getKafkaStreams().start();
ksqlEngine.getPersistentQueries()
.put(persistentQueryMetadata.getId(), persistentQueryMetadata);
} else {
System.err.println("Ignoring statemenst: " + sql);
System.err.println("Only CREATE statements can run in KSQL embedded mode.");
log.warn("Ignoring statemenst: {}", sql);
log.warn("Only CREATE statements can run in KSQL embedded mode.");
}
}
}
直接调用的kafka stream 代码 获取record :
public void run() {
log.info("{} Starting", logPrefix);
boolean cleanRun = false;
try {
runLoop();
cleanRun = true;
} catch (final KafkaException e) {
// just re-throw the exception as it should be logged already
throw e;
} catch (final Exception e) {
// we have caught all Kafka related exceptions, and other runtime exceptions
// should be due to user application errors
log.error("{} Streams application error during processing: ", logPrefix, e);
throw e;
} finally {
shutdown(cleanRun);
}
}
/**
* Main event loop for polling, and processing records through topologies.
*/
private void runLoop() {
long recordsProcessedBeforeCommit = UNLIMITED_RECORDS;
consumer.subscribe(sourceTopicPattern, rebalanceListener);
while (stillRunning()) {
timerStartedMs = time.milliseconds();
// try to fetch some records if necessary
final ConsumerRecords<byte[], byte[]> records = pollRequests();
if (records != null && !records.isEmpty() && !activeTasks.isEmpty()) {
streamsMetrics.pollTimeSensor.record(computeLatency(), timerStartedMs);
addRecordsToTasks(records);
final long totalProcessed = processAndPunctuate(activeTasks, recordsProcessedBeforeCommit);
if (totalProcessed > 0) {
final long processLatency = computeLatency();
streamsMetrics.processTimeSensor.record(processLatency / (double) totalProcessed,
timerStartedMs);
recordsProcessedBeforeCommit = adjustRecordsProcessedBeforeCommit(recordsProcessedBeforeCommit, totalProcessed,
processLatency, commitTimeMs);
}
}
maybeCommit(timerStartedMs);
maybeUpdateStandbyTasks(timerStartedMs);
maybeClean(timerStartedMs);
}
log.info("{} Shutting down at user request", logPrefix);
}
/**
* Get the next batch of records by polling.
* @return Next batch of records or null if no records available.
*/
private ConsumerRecords<byte[], byte[]> pollRequests() {
ConsumerRecords<byte[], byte[]> records = null;
try {
records = consumer.poll(pollTimeMs);
} catch (final InvalidOffsetException e) {
resetInvalidOffsets(e);
}
if (rebalanceException != null) {
if (!(rebalanceException instanceof ProducerFencedException)) {
throw new StreamsException(logPrefix + " Failed to rebalance.", rebalanceException);
}
}
return records;
}
2). sql解析
Antlr
3) API
暂未发现有java api调用,quickstart为命令行形式
最后:
推荐文章1:Introducing KSQL: Streaming SQL for Apache Kafka | Confluent
推荐文章2:KSQL Tutorials and Examples - 知乎
快速入门指南:https://github.com/confluentinc/ksql/tree/0.1.x/docs/quickstart#quick-start