本次主要探讨如何使用pyflink1.12中内置的metrics模块,实现自定义监控方法,最终得到仪表盘如下图所示:
pyflink中metric方法主要定义在pyflink.metrics中,主要定义了GenericMetricGroup类和add_group、counter、gauge、meter、distribution5个方法。
add_group:创建一个新的metric group,并将其作为当前metric group的子group
counter:在当前metric group中注册Counter类,作为计数器
gauge:注册gauge方法
meter:在当前metric group中注册Meter类,作为吞吐量指标
distribution:在当前metric group中注册Distribution类,用于统计变量分布信息
仪表盘定义主要分为两个步骤:方法注册,方法计算
方法注册主要由open方法完成,可参考如下代码:
def open(self, function_context):
# Add metric
metric_group = function_context.get_metric_group().add_group("online_ml")
self.metric_counter = metric_group.counter('sample_count')
metric_group.gauge("prediction_acc", lambda: int(self.metric_predict_acc * 100))
self.metric_distribution_y = metric_group.distribution("metric_distribution_y")
self.metric_total_10_sec = metric_group.meter("total_10_sec", time_span_in_seconds=10)
self.metric_right_10_sec = metric_group.meter("right_10_sec", time_span_in_seconds=10)
该方法默认会传入function_context变量,表示udf的上下文,可通过function_context.get_metric_group()方式获取当前metric group。本次演示进一步调用add_group方法创建子metric group,并在子metric group内注册相关监控方式。
在获取得到metric group之后,便可以通过counter、gauge、meter、distribution这四个方法注册相关监控方式。
add_group可传入两个参数,name和extra,name用于定义得到的metric group在仪表盘中显示的名称,如下图所示:
extra笔者还未曾使用,可参考官方定义进行使用:
If extra is not None, creates a new key-value MetricGroup pair. The key group is added to this group's sub-groups, while the value group is added to the key group's sub-groups. In this case, the value group will be returned and a user variable will be defined.
counter只需传入一个参数name即可,表示counter在仪表盘中显示的名称,如下图所示:
gauge需要传入两个参数,name和obj,name表示gauge在仪表盘中显示的名称,obj表示一个函数或者一个实现了__call__方法的类。虽然笔者注册了gauge方法,但是未在仪表盘中得到显示,希望实现的大佬此处能解一下惑。此处黑人三问号???
meter类需要传入两个参数,name和time_span_in_seconds,name表示meter在仪表盘中显示的名称,time_span_in_seconds表示统计的时间段,比如笔者设定了time_span_in_seconds=10,表示统计每10s的数据。名称如下图所示:
distribution方法需要传入一个参数name即可,表示distribution在仪表盘中显示的名称,如下图所示:
方法计算主要通过eval方法自定义完成,可参考如下代码:
def eval(self, value):
import time
# Metric calcutation
self.metric_counter.inc(1)
self.metric_predict_acc += 1 # It dosen't work
self.metric_distribution_y.update(int(time.time() * 1000) % 100) # It dosen't work
self.metric_total_10_sec.mark_event(10)
self.metric_right_10_sec.mark_event(5)
return 'helloworld' + value
该方法是udf的常用接口,可访问open方法中的变量完成相关计算。
Counter类主要实现了inc、dec、get_count三个方法,inc表示每次处理一个数据,计数器自动增加相关值,笔者自定义为1,即每一个数据增加1,从而可以计算处理中的数据量统计。dec与inc相反,用于减少计数器值。get_count可用于获取当前计数器的值,可用于回调。
Distribution类主要实现了update方法,可传入一个参数value,笔者认为该值为动态值,表示每次处理完数据后相关监控对象的动态变化,但是在仪表盘显示的时候,笔者的代码失效了,希望成功实现的大佬能在此处解一下惑。此处黑人三问号???
Meter类主要实现了mark_event和get_count两个方法,mark_event用于给事件标记一个值,用于进行统计,类似于Counter类中的inc。get_count用于获取当前的指标值,用于回调。
metrics模块中未定义Gauge类,而是通过obj定义的函数或者实现了__call__方法的类来进行相关值的监测,笔者监测了self.metric_predict_acc变量,并在每次处理数据时对其进行加1处理,但是最终仪表盘中未显示该指标。此处黑人三问号???
既然已经了解了pyflink中仪表盘相关指标的自定义方式,那么我们一起来实践一下来感受pyflink的魅力。
以下是笔者的demo,需要提前准备两个kafka的topic:source_topic和sink_topic。
from pyflink.datastream import StreamExecutionEnvironment, TimeCharacteristic
from pyflink.datastream.state_backend import *
from pyflink.common import RestartStrategies
from pyflink.table.table_environment import StreamTableEnvironment
from pyflink.table.descriptors import *
from pyflink.table import DataTypes
from pyflink.table.table_schema import TableSchema
from pyflink.table.udf import udtf, udf, ScalarFunction
class TestFunction(ScalarFunction):
def __init__(self):
# Add metric
self.metric_predict_acc = 0
def open(self, function_context):
# Add metric
metric_group = function_context.get_metric_group().add_group("online_ml")
self.metric_counter = metric_group.counter('sample_count')
metric_group.gauge("prediction_acc", lambda: int(self.metric_predict_acc * 100))
self.metric_distribution_y = metric_group.distribution("metric_distribution_y")
self.metric_total_10_sec = metric_group.meter("total_10_sec", time_span_in_seconds=10)
self.metric_right_10_sec = metric_group.meter("right_10_sec", time_span_in_seconds=10)
def eval(self, value):
import time
# Metric calcutation
self.metric_counter.inc(1)
self.metric_predict_acc += 1 # It dosen't work
self.metric_distribution_y.update(int(time.time() * 1000) % 100) # It dosen't work
self.metric_total_10_sec.mark_event(10)
self.metric_right_10_sec.mark_event(5)
return 'helloworld' + value
test_function = udf(TestFunction(),
input_types=[DataTypes.STRING()],
result_type=DataTypes.STRING())
def kafka_connection(env, topic, table_name):
# Create kafka connector.
kafka = Kafka()
kafka.version(version='universal')
kafka.topic(topic=topic)
kafka.properties(property_dict={'bootstrap.servers': 'localhost:9092',
'zookeeper.connect': 'localhost:2181'})
kafka.start_from_group_offsets()
# Create schema.
field_names = ['id', 'name1', 'name2', 'name3', 'eventTime']
field_types = [DataTypes.STRING(), DataTypes.STRING(), DataTypes.STRING(),
DataTypes.STRING(), DataTypes.STRING()]
fields = [DataTypes.FIELD(field_name, field_type) for field_name, field_type \
in zip(field_names, field_types)]
json = Json()
json.schema(DataTypes.ROW(fields))
table_schema = TableSchema(field_names, field_types)
schema = Schema(table_schema)
# Create connection for environment.
env.connect(kafka)\
.with_format(json)\
.with_schema(schema)\
.create_temporary_table(table_name)
def execution():
# Create stream environment and table environment.
stream_env = StreamExecutionEnvironment.get_execution_environment()
stream_env = stream_env.set_parallelism(parallelism=1)
stream_env = stream_env.set_max_parallelism(max_parallelism=32767)
stream_env = stream_env.set_buffer_timeout(timeout_millis=-1)
stream_env = stream_env.set_state_backend(state_backend=MemoryStateBackend())
stream_env.set_restart_strategy(restart_strategy_configuration=RestartStrategies.no_restart())
stream_env.set_stream_time_characteristic(characteristic=TimeCharacteristic.EventTime)
stream_env.set_default_local_parallelism(parallelism=2)
table_env = StreamTableEnvironment.create(stream_execution_environment=stream_env)
# Change some config.
table_env.get_config().get_configuration().set_string("taskmanager.memory.task.off-heap.size", "200m")
# Register python function.
table_env.create_temporary_function('test_function', test_function)
# Create source from kafka.
kafka_connection(env=table_env, topic='source_topic', table_name='source')
# Create sink to kafka.
kafka_connection(env=table_env, topic='sink_topic', table_name='sink')
# Transform data.
sql = """insert into sink
select id, test_function(name1), name2, name3, eventTime
from source"""
table_env.execute_sql(sql)
if __name__ == '__main__':
execution()
该job可在standlone模式下进行提交运行。最后,需要自定义一个kafka producer源源不断往source_topic中写入数据,使得该job工作起来。
https://blog.csdn.net/weixin_39334709/article/details/109893599?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_15138049/article/details/114664879