|
|
51CTO旗下网站
|
|
移动端
创建专栏

PyFlink场景案例 - PyFlink实现CDN日志实时分析

CDN将源站资源缓存至遍布全球的加速节点上,当终端用户请求获取该资源时,无需回源,系统自动调用离终端用户最近的CDN节点上已缓存的资源,那么如何进行实时日志分析呢?

作者:金竹|2020-04-08 12:03

CDN 日志实时分析综述

CDN将源站资源缓存至遍布全球的加速节点上,当终端用户请求获取该资源时,无需回源,系统自动调用离终端用户最近的CDN节点上已缓存的资源,那么如何进行实时日志分析呢?

架构

CDN日志的解析一般有一个通用的架构模式,就是首先要将各个边缘节点的日志数据进行采集,一般会采集到消息队列,然后将消息队列和实时计算集群进行集成进行实时的日志分析,最后将分析的结果写到存储系统里面。那么我今天的案例将架构实例化,消息队列采用Kafka,实时计算采用Flink,最终将数据存储到MySql中。如下图所示:

需求说明

阿里云实际的CDN日志数据结构如下(可能会不断丰富字段信息):

为了介绍方便,我们将实际的统计需求进行简化,示例将从CDN访问日志中,根据IP解析出其所属的地区,统计指标:

  • 按地区统计资源访问量
  • 按地区统计资源下载总量
  • 按地区统计资源平均下载速度

CDN实时日志分析UDF定义

这里我们需要定义一个 ip_to_province()的UDF,输入是ip地址,输出是地区名字字符串。UDF的输入类型是一个字符串,输出类型也是一个字符串。同时我们会用到地理区域查询服务

(http://whois.pconline.com.cn/ipJson.jsp?ip=27.184.139.25),大家在自己的生产环境要替换为可靠的地域查询服务。

  1. import re 
  2. import json 
  3. from pyflink.table import DataTypes 
  4. from pyflink.table.udf import udf 
  5. from urllib.parse import quote_plus 
  6. from urllib.request import urlopen 
  7.  
  8. @udf(input_types=[DataTypes.STRING()], result_type=DataTypes.STRING()) 
  9. def ip_to_province(ip): 
  10.    """ 
  11.    format: 
  12.        { 
  13.        'ip': '27.184.139.25', 
  14.        'pro': '河北省', 
  15.        'proCode': '130000', 
  16.        'city': '石家庄市', 
  17.        'cityCode': '130100', 
  18.        'region': '灵寿县', 
  19.        'regionCode': '130126', 
  20.        'addr': '河北省石家庄市灵寿县 电信', 
  21.        'regionNames': '', 
  22.        'err': '' 
  23.        } 
  24.    """ 
  25.    try: 
  26.        urlobj = urlopen( \ 
  27.         'http://whois.pconline.com.cn/ipJson.jsp?ip=%s' % quote_plus(ip)) 
  28.        data = str(urlobj.read(), "gbk") 
  29.        pos = re.search("{[^{}]+\}", data).span() 
  30.        geo_data = json.loads(data[pos[0]:pos[1]]) 
  31.        if geo_data['pro']: 
  32.            return geo_data['pro'] 
  33.        else: 
  34.            return geo_data['err'] 
  35.    except: 
  36.        return "UnKnow" 

数据读取和结果写入定义

按照通用的作业结构,需要定义Source connector来读取Kafka数据,定义Sink connector来将计算结果存储到MySQL。最后是编写统计逻辑。在这特别说明一下,在PyFlink中也支持SQL DDL的编写,我们用一个简单的DDL描述,就完成了Source Connector的开发。其中connector.type填写kafka。SinkConnector也一样,用一行DDL描述即可,其中connector.type填写jdbc。

1. Kafka 数据源读取DDL定义

数据统计需求我们只选取核心的字段,比如:uuid,表示唯一的日志标示,client_ip表示访问来源,request_time表示资源下载耗时,response_size表示资源数据大小。Kafka数据字段进行简化如下:

DDL定义如下:

  1. kafka_source_ddl = ""
  2. CREATE TABLE cdn_access_log ( 
  3.  uuid VARCHAR, 
  4.  client_ip VARCHAR, 
  5.  request_time BIGINT, 
  6.  response_size BIGINT, 
  7.  uri VARCHAR 
  8. ) WITH ( 
  9.  'connector.type' = 'kafka', 
  10.  'connector.version' = 'universal', 
  11.  'connector.topic' = 'access_log', 
  12.  'connector.properties.zookeeper.connect' = 'localhost:2181', 
  13.  'connector.properties.bootstrap.servers' = 'localhost:9092', 
  14.  'format.type' = 'csv', 
  15.  'format.ignore-parse-errors' = 'true' 
  16. """ 

2. MySql 数据写入DDL定义

其中我们发现我们需求是按地区分组,但是原始日志里面并没有地区的字段信息,所以我们需要定义一个Python UDF 更具 client_ip 来查询对应的地区。所以我们对应的MySqL统计结果表如下:

DDL定义如下:

  1. mysql_sink_ddl = ""
  2. CREATE TABLE cdn_access_statistic ( 
  3.  province VARCHAR, 
  4.  access_count BIGINT, 
  5.  total_download BIGINT, 
  6.  download_speed DOUBLE 
  7. ) WITH ( 
  8.  'connector.type' = 'jdbc', 
  9.  'connector.url' = 'jdbc:mysql://localhost:3306/Flink', 
  10.  'connector.table' = 'access_statistic', 
  11.  'connector.username' = 'root', 
  12.  'connector.password' = '123456', 
  13.  'connector.write.flush.interval' = '1s' 
  14. """ 

核心统计逻辑

我们首先要将client_ip转换为地区名字,然后在做数据统计,如下核心统计逻辑:

  1. # 核心的统计逻辑 
  2. t_env.from_path("cdn_access_log")\ 
  3.    .select("uuid, " 
  4.            "ip_to_province(client_ip) as province, " # IP 转换为地区名称 
  5.            "response_size, request_time")\ 
  6.    .group_by("province")\ 
  7.    .select( # 计算访问量 
  8.            "province, count(uuid) as access_count, "  
  9.            # 计算下载总量  
  10.            "sum(response_size) as total_download,  "  
  11.            # 计算下载速度 
  12.            "sum(response_size) * 1.0 / sum(request_time) as download_speed") \ 
  13.    .insert_into("cdn_access_statistic") 

完整的作业代码

我们整体看一遍完整代码结构,首先是核心依赖的导入,然后是我们需要创建一个ENV,并设置采用的planner(目前Flink支持Flink和blink两套planner)建议大家采用 blink planner。 接下来将我们刚才描述的kafka和mysql的ddl进行表的注册。再将Python UDF进行注册,这里特别提醒一点,UDF所依赖的其他文件也可以在API里面进行制定,这样在job提交时候会一起提交到集群。然后是核心的统计逻辑,最后调用executre提交作业。这样一个实际的CDN日志实时分析的作业就开发完成了。具体代码如下:

  1. import os 
  2.  
  3. from pyFlink.datastream import StreamExecutionEnvironment 
  4. from pyflink.table import StreamTableEnvironment, EnvironmentSettings 
  5. from enjoyment.cdn.cdn_udf import ip_to_province 
  6. from enjoyment.cdn.cdn_connector_ddl import kafka_source_ddl, mysql_sink_ddl 
  7.  
  8. # 创建Table Environment, 并选择使用的Planner 
  9. env = StreamExecutionEnvironment.get_execution_environment() 
  10. t_env = StreamTableEnvironment.create( 
  11.    env, 
  12.    environment_settings=EnvironmentSettings.new_instance().use_blink_planner().build()) 
  13.  
  14. # 创建Kafka数据源表 
  15. t_env.sql_update(kafka_source_ddl) 
  16. # 创建MySql结果表 
  17. t_env.sql_update(mysql_sink_ddl) 
  18.  
  19. # 注册IP转换地区名称的UDF 
  20. t_env.register_function("ip_to_province", ip_to_province) 
  21.  
  22. # 添加依赖的Python文件 
  23. t_env.add_Python_file( 
  24.     os.path.dirname(os.path.abspath(__file__)) + "/enjoyment/cdn/cdn_udf.py") 
  25. t_env.add_Python_file(os.path.dirname( 
  26.     os.path.abspath(__file__)) + "/enjoyment/cdn/cdn_connector_ddl.py") 
  27.  
  28. # 核心的统计逻辑 
  29. t_env.from_path("cdn_access_log")\ 
  30.    .select("uuid, " 
  31.            "ip_to_province(client_ip) as province, " # IP 转换为地区名称 
  32.            "response_size, request_time")\ 
  33.    .group_by("province")\ 
  34.    .select( # 计算访问量 
  35.            "province, count(uuid) as access_count, "  
  36.            # 计算下载总量  
  37.            "sum(response_size) as total_download,  "  
  38.            # 计算下载速度 
  39.            "sum(response_size) * 1.0 / sum(request_time) as download_speed") \ 
  40.    .insert_into("cdn_access_statistic") 
  41.  
  42. # 执行作业 
  43. t_env.execute("pyFlink_parse_cdn_log") 

环境搭建(MacOS)

1. 安装MySQL

  1. $  brew install mysql 
  2. Updating Homebrew... 
  3. ==> Auto-updated Homebrew! 
  4. ... 
  5. ... 
  6. MySQL is configured to only allow connections from localhost by default 
  7.  
  8. To connect run: 
  9.     mysql -uroot 
  10.  
  11. To have launchd start mysql now and restart at login: 
  12.   brew services start mysql 
  13. Or, if you don't want/need a background service you can just run: 
  14.   mysql.server start 

如果没有安装brew,执行以下指令安装:

  1. $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 

安装完成后,执行安全配置,将root用户的密码设置为JxXX&&l2j2#:

  1. $ mysql_secure_installation 

启动mysql服务:

  1. $ brew services start mysql 
  2.  
  3. ==> Tapping homebrew/services 
  4. Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-services'... 
  5. ... 
  6. ... 
  7. ==> Successfully started `mysql` (label: homebrew.mxcl.mysql) 

登录mysql并创建flink数据库和cdn_access_statistic表,并确认开放端口号为默认的3306:

  1. $ mysql -uroot -p 
  2. Enter password: JxXX&&l2j2# 
  3. mysql> use flink; 
  4. Database changed 
  5. mysql> CREATE TABLE cdn_access_statistic( 
  6.     ->   province VARCHAR(255) PRIMARY KEY, 
  7.     ->   access_count BIGINT, 
  8.     ->   total_download BIGINT, 
  9.     ->   download_speed DOUBLE 
  10.     -> ) ENGINE=INNODB CHARSET=utf8mb4
  11. Query OK, 0 rows affected (0.01 sec) 
  12. exit 

这样mysql的环境就准备好了。

2. 安装Kafka

在Mac系统安装Kafka也非常方便,如下:

  1. brew install kafka 
  2. Updating Homebrew... 
  3. ==> Installing dependencies for kafka: zookeeper 
  4. ... 
  5. ... 
  6. ==> Summary 
  7. /usr/local/Cellar/zookeeper/3.5.7: 394 files, 11.3MB 
  8. ==> Installing kafka 
  9. ... 
  10. ... 
  11. ==> kafka 
  12. To have launchd start kafka now and restart at login: 
  13.   brew services start kafka 
  14. Or, if you don't want/need a background service you can just run: 
  15.   zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties & kafka-server-start /usr/local/etc/kafka/server.properties 

安装完成后执行以下指令启动zookeeper和kafka:

  1. $ cd /usr/local/opt/kafka/libexec/bin/ 
  2. $ ./zookeeper-server-start.sh ../config/zookeeper.properties & 
  3. $ (回车) 
  4. $ ./kafka-server-start.sh ../config/server.properties & 
  5. $ (回车) 

然后在kafka中创建名为cdn_access_log的topic:

  1. $ kafka-topics --create --replication-factor 1 --partitions 1 --topic cdn_access_log --zookeeper localhost:2181 
  2. ... 
  3. Created topic cdn_access_log. 

这样kafka的环境也准备好了.

3. 安装PyFlink

确保使用Python3.5+的版本,检验如下:

  1. $ python --version 
  2. Python 3.7.6 

说过pip install安装PyFlink

  1. python -m pip install apache-flink==1.10.0 
  2. ... 
  3. ... 
  4. Successfully installed apache-beam-2.15.0 dill-0.2.9 pyarrow-0.14.1 

检查我们是否成功安装了PyFlink和依赖的Beam,如下:

  1. $ python -m pip list |grep apache- 
  2. apache-beam                   2.15.0                  
  3. apache-flink                  1.10.0 

如上显示说明已经完成安装。

4. Copy使用的Connector的JAR包

Flink默认是没有打包connector的,所以我们需要下载各个connector所需的jar包并放入PyFlink的lib目录。首先拿到PyFlink的lib目录的路径:

  1. PYFLINK_LIB=`python -c "import pyflink;import os;print(os.path.dirname(os.path.abspath(pyflink.__file__))+'/lib')"` 
  2. $ echo $PYFLINK_LIB 
  3. /usr/local/lib/python3.7/site-packages/pyflink/lib 

然后想需要的jar包下载到lib目录中去:

  1. $ cd $PYFLINK_LIB 
  2. $ curl -O https://repo1.maven.org/maven2/org/apache/flink/flink-sql-connector-kafka_2.11/1.10.0/flink-sql-connector-kafka_2.11-1.10.0.jar 
  3. $ curl -O https://repo1.maven.org/maven2/org/apache/flink/flink-jdbc_2.11/1.10.0/flink-jdbc_2.11-1.10.0.jar 
  4. $ curl -O https://repo1.maven.org/maven2/org/apache/flink/flink-csv/1.10.0/flink-csv-1.10.0-sql-jar.jar 
  5. $ curl -O https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar 

最终lib的JARs如下:

  1. $ ls -la 
  2. total 310448 
  3. drwxr-xr-x  12 jincheng.sunjc  admin        384  3 27 15:48 . 
  4. drwxr-xr-x  28 jincheng.sunjc  admin        896  3 24 11:44 .. 
  5. -rw-r--r--   1 jincheng.sunjc  admin      36695  3 27 15:48 flink-csv-1.10.0-sql-jar.jar 
  6. -rw-r--r--   1 jincheng.sunjc  admin  110055308  2 27 13:33 flink-dist_2.11-1.10.0.jar 
  7. -rw-r--r--   1 jincheng.sunjc  admin      89695  3 27 15:48 flink-jdbc_2.11-1.10.0.jar 
  8. -rw-r--r--   1 jincheng.sunjc  admin    2885638  3 27 15:49 flink-sql-connector-kafka_2.11-1.10.0.jar 
  9. -rw-r--r--   1 jincheng.sunjc  admin   22520058  2 27 13:33 flink-table-blink_2.11-1.10.0.jar 
  10. -rw-r--r--   1 jincheng.sunjc  admin   19301237  2 27 13:33 flink-table_2.11-1.10.0.jar 
  11. -rw-r--r--   1 jincheng.sunjc  admin     489884  2 27 13:33 log4j-1.2.17.jar 
  12. -rw-r--r--   1 jincheng.sunjc  admin    2356711  3 27 15:48 mysql-connector-java-8.0.19.jar 
  13. -rw-r--r--   1 jincheng.sunjc  admin      20275 12  5 17:01 pyflink-demo-connector-0.1.jar 
  14. -rw-r--r--   1 jincheng.sunjc  admin       9931  2 27 13:33 slf4j-log4j12-1.7.15.jar 

这样所有的环境就准备好了。

本地运行作业

  • 启动本地集群:
  1. $PYFLINK_LIB/../bin/start-cluster.sh local 

启动成功后可以打开web界面(默认8081端口,我更改为4000)

  • 提交作业

示例涉及到如下三个py文件:

我们在作业文件所在目录进行执行:

  1. $ $PYFLINK_LIB/../bin/flink run -m localhost:4000 -py cdn_demo.py 
  2. Job has been submitted with JobID e71de2db44fd6cfb9bae93bd04ee2ec9 

查看控制台如下:

目前我们已经成功的将作业启动起来了,接下来我们向Kafka里面进行数据的写入。

Mock 数据

我们对CDN数据进行Mock,并进行统计可视化,工具代码请下载

进行源码编译

  1. $ python setup.py sdist 
  2. $ sudo python -m pip install dist/* 

运行工具

  1. $ start_dashboard.py  
  2. --------------------------------------environment config-------------------------------------- 
  3. Target kafka port: localhost:9092 
  4. Target kafka topic: cdn_access_log 
  5. Target mysql://localhost:3306/flink 
  6. Target mysql table: cdn_access_statistic 
  7. Listen at: http://localhost:64731 
  8. ---------------------------------------------------------------------------------------------- 
  9. To change above environment config, edit this file: /usr/local/bin/start_dashboard.py 

打开可视化界面: (根据命令行提示的 Listen at:后面的地址)

小结

本篇是场景案例系列,开篇分享了老子教导我们要学会放空自己,空杯才能注水。全篇围绕阿里云CDN日志实时分析需求进行展开,描述了任何利用PyFlink解决实际业务问题。最后又为大家提供了示例的数据的Mock工具和可视化统计页面,希望对大家有所帮助。

作者介绍

本人孙金城,淘宝花名“金竹”,阿里巴巴高级技术专家。2011年加入阿里,在2016年开始ASF社区贡献,目前是 ASF Member, PMC member of @ApacheFlink and a Committer for @ApacheFlink, @ApacheBeam, @ApacheIoTDB。

【本文为51CTO专栏作者“金竹”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

【编辑推荐】

  1. 日志配置热更新技术实践
  2. Java中GC原理及GC日志剖析
  3. 每天处理千亿级日志量,Kafka是如何做到的?
  4. 腾讯万亿级日志量下,ES如何做到秒级响应?
【责任编辑:赵宁宁 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢