pyflink 中使用自定义仪表盘方法

本次主要探讨如何使用pyflink1.12中内置的metrics模块,实现自定义监控方法,最终得到仪表盘如下图所示:

pyflink 中使用自定义仪表盘方法_第1张图片


1.仪表盘主要使用的类和方法

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类,用于统计变量分布信息

2.如何实现仪表盘定义

仪表盘定义主要分为两个步骤:方法注册,方法计算

2.1.方法注册

方法注册主要由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在仪表盘中显示的名称,如下图所示:

2.2.方法计算

方法计算主要通过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处理,但是最终仪表盘中未显示该指标。此处黑人三问号???

3.完整代码展示

既然已经了解了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

你可能感兴趣的:(pyflink,python,大数据,数据分析)