书接上文,本文介绍一些简单的例程,用来测试集群部署情况。
一、kakfa与python串口测试
配置串口
修改配置打开串口功能
$sudovim/boot/orangepiEnv.txt
overlays=uart0-m2 uart1-m1 uart3-m0 uart4-m0
查看串口
rangepi@orangepi:~$ls/dev/ttyS*
/dev/ttyS0 /dev/ttyS1 /dev/ttyS3 /dev/ttyS4 /dev/ttyS9
串口回显例程(测试串口是否好用)
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <termios.h>
#include <string.h>
/* 115200, 8, N, 1 */
int uart_setup(int fd)
{
struct termios options;
// 获取原有串口配置
if (tcgetattr(fd, &options) < 0) {
return -1;
}
// 修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;
// 修改控制模式,能够从串口读取数据
options.c_cflag |= CREAD;
// 不使用流控制
options.c_cflag &= ~CRTSCTS;
// 设置数据位
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// 设置奇偶校验位
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
// 设置停止位
options.c_cflag &= ~CSTOPB;
// 设置最少字符和等待时间
options.c_cc[VMIN] = 1; // 读数据的最小字节数
options.c_cc[VTIME] = 0; //等待第1个数据,单位是10s
// 修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 设置波特率
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
// 清空终端未完成的数据
tcflush(fd, TCIFLUSH);
// 设置新属性
if(tcsetattr(fd, TCSANOW, &options) < 0) {
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
int fd;
int ret;
char ch;
if (argc != 2) {
printf("usage: ./test_uart [device]\n");
return -1;
}
/* 打开串口 */
fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
printf("open dev fail!\n");
return -1;
} else {
fcntl(fd, F_SETFL, 0);
}
/* 设置串口 */
ret = uart_setup(fd);
if (ret < 0) {
printf("uart setup fail!\n");
close(fd);
return -1;
}
/* 串口回传实验 */
while (1) {
scanf("%c", &ch);
ret = write(fd, &ch, 1);
printf("write [%c] , ret is %d!\r\n", ch, ret);
ret = read(fd, &ch, 1);
if (ret < 1) {
printf("read fail, ret is %d\r\n", ret);
} else {
printf("recv a char:[0x%02x][%c]\r\n", ch, ch);
}
}
close(fd);
}
python库pyserial测试
import serial
import time
# 打开 /dev/ttyS0,将波特率配置为115200
ser = serial.Serial(port="/dev/ttyS0",
baudrate=115200)
ser.write('Hello, world!'.encode()) # 向串口发送数据
time.sleep(1)
data = ser.readline() # 从串口读取一行数据
print(data)
ser.close() # 关闭串口连接
kafka创建topic
#创建topic
kafka-topics.sh --create --zookeeper slave1:2181 --replication-factor 1 --partitions 1 --topic my-topic
#查看topic
kafka-topics.sh --list --zookeeper slave1:2181
#生产者测试
kafka-console-producer.sh --broker-list slave1:9092 --topic my-topic
#消费者测试
kafka-console-consumer.sh --bootstrap-server master:9092 --topic my-topic --from-beginning
##安装pip和pykafka
sudo apt install python3-pip
pip3 install kafka-python
pip换源
cd ~ # 进入家目录
mkdir .pip # 新建.pip隐藏文件夹
# 或者 mkdir -p .config/pip
cd .pip # 进入.pip文件夹
# 或者 cd .config/pip
touch pip.conf # 新建pip.conf文件
vim pip.conf # 用vim编辑pip.conf文件
[global]
index-url=https://pypi.tuna.tsinghua.edu.cn/simple
timeout = 6000
[install]
trusted-host=pypi.tuna.tsinghua.edu.cn
disable-pip-version-check = true
kafka-python例程
from kafka import KafkaProducer, KafkaConsumer
from kafka.errors import kafka_errors
import traceback
import json
def producer_demo():
# 假设生产的消息为键值对(不是一定要键值对),且序列化方式为json
producer = KafkaProducer(
bootstrap_servers=['slave1:9092'],
key_serializer=lambda k: json.dumps(k).encode(),
value_serializer=lambda v: json.dumps(v).encode())
# 发送三条消息
for i in range(0, 3):
future = producer.send(
'my-topic', #topic名称
key='count_num', # 同一个key值,会被送至同一个分区
value=str(i))
print("send {}".format(str(i)))
try:
future.get(timeout=10) # 监控是否发送成功
except kafka_errors: # 发送失败抛出kafka_errors
traceback.format_exc()
def consumer_demo():
consumer = KafkaConsumer(
'my-topic',
bootstrap_servers='slave1:9092',
)
for message in consumer:
print("receive, key: {}, value: {}".format(
json.loads(message.key.decode()),
json.loads(message.value.decode())
)
)
二、python spark streaming
注意!Spark 0.8.2 开始,Kafka 3.0 支持已弃用,且0.10没有pythonAPI因此放弃方案
在/etc/profile中加入,在Python3中引入pyspark库
export PYTHONPATH=$SPARK_HOME/python:$SPARK_HOME/python/lib/py4j-0.10.9.5-src.zip:$PYTHONPATH
import sys
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
if __name__ == "__main__":
conf = SparkConf()
conf.setAppName('PythonStreamingKafkaWordCount')
conf.setMaster('local[2]')
sc = SparkContext(conf = conf)
ssc = StreamingContext(sc, 1)
zkQuorum = 'slave1:9092'
topic = 'my-topic'
kvs = KafkaUtils.createStream(ssc, zkQuorum, "spark-streaming-consumer", {topic: 1})
lines = kvs.map(lambda x: x[1])
counts = lines.flatMap(lambda line: line.split(" ")) \
.map(lambda word: (word, 1)) \
.reduceByKey(lambda a, b: a+b)
counts.pprint()
ssc.start()
ssc.awaitTermination()
三、PySpark Structured Streaming kafka
# -*- coding: utf-8 -*-
from pyspark.sql import SparkSession
from pyspark.sql.functions import from_json
from pyspark.sql.types import StructType, StringType
if __name__ == '__main__':
#创建SparkSession
spark = SparkSession \
.builder \
.appName("pyspark_structured_streaming_kafka") \
.getOrCreate()
#设置使用kafka输入,配置地址,topic
df = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "slave2:9092") \
.option("subscribe", "my-topic") \
.load()
words = df.selectExpr("CAST(value AS STRING)")
schema = StructType() \
.add("name", StringType()) \
.add("age", StringType()) \
.add("sex", StringType())
# 通过from_json,定义schema来解析json
res = words.select(from_json("value", schema).alias("data")).select("data.*")
query = res.writeStream \
.format("console") \
.outputMode("append") \
.start()
query.awaitTermination()
spark-submit --master yarn --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.3.1
程序启动后,在kafka的product命令行,输入{"name":"caocao","age":"32","sex":"male"}
,在控制台输入:
教程
使用readStream来读入流,指定format为kafka,kafka的broker配置以及绑定的主题(可以绑定多个主题)。还可以指定offset的位置(有latest, earliest以及具体对每一个topic的每一个分区进行指定)。
df = spark \
.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "host1:port1,host2:port2") \
.option("subscribe", "topic1,topic2") \
.option("startingOffsets", """{"topic1":{"0":23,"1":-2},"topic2":{"0":-2}}""") \
.load()
步骤1:定义输入源
# In Python
spark = SparkSession...
lines = (spark
.readStream.format("socket")
.option("host", "localhost")
.option("port", 9999)
.load())
步骤2:转换数据
例如将行拆分为单个单词,然后对它们进行计数,如以下代码所示:
from pyspark.sql.functions import *
words = lines.select(split(col("value"), "\\s").alias("word"))
counts = words.groupBy("word").count()
counts是一个流式DataFrame(即,无边界流数据上的一个DataFrame),它表示一旦流查询开启则会对流输入数据进行连续处理,执行计数操作。
步骤3:定义输出接收器和输出模式
转换数据后,我们可以通过定义DataFrame.writeStream决定写入处理后的输出数据的模式。
让我们从输出的细节开始(我们将在下一步重点关注处理细节)。例如,下面的片段展示了如何将最终计数写入到控制台:
# In Python
writer = counts.writeStream.format("console").outputMode("complete")
在这里,我们指定"console"作为输出流接收器并且指定了"complete"输出模式。流查询的输出模式指定在处理新的输入数据之后要写入更新后输出的哪一部分。在此示例中,随着大量新输入数据的处理和单词字数的更新,我们可以选择将到目前为止看到的所有单词(即complete模式)的计数打印到控制台,也可以仅将最后一个输入数据块中更新的内容打印到控制台中。这是由指定的输出模式决定的,可以是以下输出模式之一(正如我们在“结构化流的编程模型”中已经看到的那样:
*追加模式(Append mode)*
默认是追加模式,在该模式下,仅将自上次触发以来添加到结果表或者 DataFrame(例如,表)中的新行输出到接收器。从语义上讲,此模式可以保证将来查询不会更改或更新输出的任何行。因此,只有那些永远不会修改以前的输出数据的查询(例如,无状态查询)才支持追加模式。相反,我们的计数查询需要更新以前生成的字数;因此,它不支持追加模式。
*全量模式(Complete mode)*
在这种模式下,结果表或者 DataFrame的所有行将在每个触发器的末尾输出。在结果表可能比输入数据小得多的查询中可以支持此操作,因此可以将其保留在内存中。例如,我们的计数查询支持全量模式,因为计数数据可能比输入数据小得多。
*更新模式(Update mode)*
在此模式下,将仅在每个触发器的末尾输出自上一个触发器以来已更新的结果表或者 DataFrame的行。这与追加模式相反,因为输出行可能会被查询然后修改并在将来再次输出。大多数查询都支持更新模式。
在最新的《结构化流编程指南》中可以找到有关不同查询支持的输出模式的完整详细信息。
除了将输出写入控制台外,结构化流还原生支持对文件和Apache Kafka的流写入。此外,你可以使用foreachBatch()和foreach()API方法写入任意位置。实际上,你可以使用foreachBatch()方法基于现有的批处理数据源来写入流输出(但是无法保证精确一次性)。这些接收器及其支持的选项的详细信息将在本章后面讨论。
步骤4:指定处理详细信息
开始查询之前的最后一步是指定如何处理数据的详细信息。继续我们的计数示例,我们将指定处理细节,如下所示:
# In Python
checkpointDir = "..."
writer2 = (writer
.trigger(processingTime="1 second")
.option("checkpointLocation", checkpointDir))
在这里,我们使用DataFrame.writeStream 创建DataStreamWriter指定了两种类型的详细信息:
*触发细节*
这指示何时触发发现和处理新的可用的流数据。有四个选项:
*默认*
如果未显式指定触发器,则默认情况下,流查询将以微批处理执行数据计算,一旦前一个微批处理完成,就会触发下一个微批处理。
*带触发间隔的处理时间*
你可以使用ProcessingTime 触发器指定时间间隔,查询将以该固定间隔触发微批。
*只触发一次*
在这种模式下,流查询将仅执行一个微批处理——它会在单个批处理中处理所有可用的新数据,然后自行停止。当你想要通过外部调度程序来控制触发和处理时,该调度程序将使用任何自定义调度来重新启动查询(例如,通过每天仅执行一次查询来控制成本),该功能非常有用。
*连续触发*
这是一种实验性模式(从Spark 3.0开始),在这种模式下,流查询将连续处理数据而不是微批处理。尽管只有一小部分的DataFrame操作允许使用此模式,但它可以提供比微批触发模式低得多的延迟(低至毫秒)。有关最新信息,请参阅最新的《结构化流编程指南》。
*检查点位置触发*
这是任何与HDFS兼容的文件系统中的目录,流式查询可以保存其进度信息,即已成功处理了哪些数据。失败时,此元数据将用于从失败查询的结束位置重新启动失败的查询。因此,设置此选项对于具有精确一次保证的故障恢复是必要的。
步骤5:开始查询
指定所有内容后,最后一步是启动查询,你可以执行以下操作:
# In Python
streamingQuery = writer2.start()
实时流计算:Structured Streaming - 知乎 (zhihu.com)
PySpark Structured Streaming实时消费kafka数据写入MongoDB
四、串口kafka以及mongodb例程
kafka生产者
运行在香橙派上
# -*- coding: utf-8 -*-
import serial
import time
from kafka import KafkaProducer
import json
# 打开 /dev/ttyS0,将波特率配置为115200
ser = serial.Serial(port="/dev/ttyS0",baudrate=115200)
# 假设生产的消息为键值对(不是一定要键值对),且序列化方式为json
producer = KafkaProducer(
bootstrap_servers=['slave1:9092'],
key_serializer=lambda k: json.dumps(k).encode(),
value_serializer=lambda v: json.dumps(v).encode())
# 读取串口数据
while True:
data = ser.readline()
producer.send("my-topic",data.decode('utf-8'))
print(data.decode('utf-8'))
producer.close()
kafka消费者
运行在master上
# -*- coding: utf-8 -*-
#kafka消费者
from kafka import KafkaConsumer;
import pymongo
import datetime as dt
from pymongo import MongoClient
# 获取数据库连接
client = pymongo.MongoClient(host='192.168.142.128', port=27017)
db = client.data
stb=db.co
# 生成Kafka消费者
consumer=KafkaConsumer('my-topic',bootstrap_servers='slave1:9092')
for msg in consumer:
msgData = msg.value.decode()
print(msgData)
# 得到地点ID,RFID,当前时间
placeID,RFID=str(msgData).split('+')
now_time = dt.datetime.now().strftime('%Y-%m-%d %H:%M')
Complete_data={'time':now_time,'placeID':place[1:],'RFID':RFID[0:8]}
insert_res = stb.insert_one(Complete_data)
print(f"insert_id={insert_res.inserted_id}: {Complete_data}")