当前位置: 首页 > news >正文

day09_实时类标签/指标

文章目录

  • day09_实时类标签/指标
  • 一、日志数据实时采集
    • 2、Flume简介
      • 2.3 项目日志数据采集Flume配置
        • 2.3.1 涉及的Flume组件和参数
        • 2.3.2 Nginx日志采集
        • 2.3.3 用户行为日志采集
  • 二、Nginx日志数据统计
    • 1、日志格式说明
    • 2、数据ETL
      • 2.1 日志抽取
        • 2.1.1 正则表达式
        • 2.1.2 基于Spark实现Nginx数据匹配
      • 2.2 字段解析
        • 2.2.1 日期格式转换
        • 2.2.2 IP解析地理位置(了解)
        • 2.2.3 UA解析
      • 2.3 完整代码
      • 2.4 使用Hive读取HDFS数据
    • 3、指标统计
    • 1、尝试进行用户行为日志的数据ETL、指标统计

day09_实时类标签/指标

在这里插入图片描述
在这里插入图片描述

一、日志数据实时采集

2、Flume简介

2.3 项目日志数据采集Flume配置

zookeeper、Kafka的启动命令

启动zookeeper(没有启动的,才需要执行)
/export/server/zookeeper/bin/zkServer.sh start启动Kafka
cd /export/server/kafka/bin
nohup ./kafka-server-start.sh ../config/server.sql 2>&1 &Kafka其他的相关命令
cd /export/server/kafka/bin
查看当前集群有哪些Topic
./kafka-topics.sh --list --bootstrap-server up01:9092
新建Topic(分区数没要求,副本数<=broker节点个数)
./kafka-topics.sh --create --bootstrap-server up01:9092 --topic xtzg_nginx_log
参看Topic的详细信息
./kafka-topics.sh --describe --bootstrap-server up01:9092 --topic xtzg_nginx_log注意: 要提前创建好Kafka的Topic
2.3.1 涉及的Flume组件和参数
  • source
type: 类型,固定值TAILDIR。能同时监控一个目录或者多个文件,也能动态监控每个文件的变化,还支持断点续传,不会出现重复消费问题。
fiilegroups:  以空格分隔的文件组列表。每个文件组表示一组要跟踪的文件。
filegroups.<filegroupName>: 文件组的绝对路径。正则表达式(而不是文件系统模式)只能用于文件名。
positionFile: JSON格式的文件,记录每个文件的inode、绝对路径和最后位置。注意: type的TAILDIR大小写不能随便写
  • channel
type: 类型,固定值 org.apache.flume.channel.kafka.KafkaChannel
kafka.bootstrap.servers: Kafka集群中的broker列表。格式:hostname:port,多个用逗号隔开。
kafka.topic: channel要用的topic
parseAsFlumeEvent: 是否需要对采集到的数据解析为Event对象,然后在内容前面增加topic前缀,会导致后续的内容会有部分缺失的情况。一般是false

补充:

如果采集到的数据最终想要输出到Kafka中,可以直接选择使用Kafka Channel。
注意: Kafka Channel和Kafka Sink,虽然都是将数据输出到Kafka中,但是两者的配置参数有区别
2.3.2 Nginx日志采集

在这里插入图片描述

  • 创建nginx_to_kafka.conf文件

在这里插入图片描述

  • nginx_to_kafka.conf配置文件内容如下
#定义组件
a1.sources = r1
a1.channels = c1#配置source
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /export/data/workspace/user_profile/log_generate/datacollection/source_data/access-nginx.*
a1.sources.r1.positionFile = /export/data/flume/nginx_position.json#配置channel
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a1.channels.c1.kafka.bootstrap.servers = up01:9092
a1.channels.c1.kafka.topic = xtzg_nginx_log
a1.channels.c1.parseAsFlumeEvent = false#组装 
a1.sources.r1.channels = c1注意: 1- a1.sources.r1.filegroups.f1该参数值要改成你自己的路径2- 文件的模糊匹配的正则表达式中写的是.*表示匹配任意内容
将上面的配置文件复制到/export/server/flume/conf
cp /export/data/workspace/user_profile/scripts/flume/nginx_to_kafka.conf /export/server/flume/conf
  • 在Kafka上创建topic(前提开启zk,kafka)
cd /export/server/kafka/bin./kafka-topics.sh --create --bootstrap-server up01:9092 --topic xtzg_nginx_log
  • 启动Flume
cd /export/server/flumebin/flume-ng agent -n a1 -c conf/ -f conf/nginx_to_kafka.conf
  • 查看Kafka中的数据
cd /export/server/kafka/bin./kafka-console-consumer.sh --bootstrap-server up01:9092 --topic xtzg_nginx_log
  • 启动

运行python中的NginxLogSimulationData.py。查看kafka中数据变化,如果看到新增数据则配置成功。确认无误后关停Flume采集任务。

2.3.3 用户行为日志采集
  • 创建user_event_to_kafka.conf文件
    在这里插入图片描述

  • user_event_to_kafka.conf配置文件内容如下

#定义组件
a1.sources = r1
a1.channels = c1#配置source
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /export/data/workspace/user_profile/log_generate/datacollection/source_data/user-event.*
a1.sources.r1.positionFile = /export/data/flume/user_event_position.json#配置channel
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a1.channels.c1.kafka.bootstrap.servers = up01:9092
a1.channels.c1.kafka.topic = xtzg_user_event
a1.channels.c1.parseAsFlumeEvent = false#组装 
a1.sources.r1.channels = c1
  • 在Kafka上创建topic(前提开启zk,kafka)
cd /export/server/kafkabin/kafka-topics.sh --create --bootstrap-server up01:9092 --topic xtzg_user_event --partitions 1 --replication-factor 1
  • 启动Flume
cd /export/server/flumebin/flume-ng agent -n a1 -c conf/ -f conf/user_event_to_kafka.conf
  • 查看Kafka中的数据
cd /export/server/kafkabin/kafka-console-consumer.sh --bootstrap-server up01:9092 --from-beginning --topic xtzg_user_event
  • 启动

运行python中的EventSimulationJsonData.py。查看kafka中数据变化,如果看到新增数据则配置成功。确认无误后关停Flume采集任务。

二、Nginx日志数据统计

1、日志格式说明

​ Nginx(发音 恩几可使)是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。该软件由俄罗斯程序员伊戈尔·赛索耶夫(Игорь Сысоев)开发并于2004年首次公开发布

  • Nginx日志包含access_logerror_log两种类型日志数据。项目中分析的数据为:access_log
  • Nginx开源官网:https://nginx.org/
  • 项目采集Nginx数据格式。以下为一条Nginx日志:
116.85.48.25 - - [12/Nov/2024:11:36:46 +0800] "GET /login.html HTTP/1.1" 404 729 "https://xtx.itcast.cn/referAFriend.html" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/7.0.18(0x17001233) NetType/WIFI Language/zh_CN" "-"Nginx日志格式说明:116.85.48.25: 用户访问IP地址- - : 用户标识(cookie信息)[14/Jul/2022:17:40:41 +0800]:  访问时间 + 时区GET : 请求方式/css/40.30d6d2b.css: 请求资源HTTP/1.1 : 请求的协议500 : 请求的状态码 (500 服务器错误,  200 成功  302 重定向  404 访问到未知资源)951 : 响应返回的字节大小"https://www.htv.com/official/component?WT.mc_id=3" : 来源的URL(从那个地方跳转到此页面)"Mozilla/5......:  浏览器标识

2、数据ETL

2.1 日志抽取

2.1.1 正则表达式
Java版本:
(?<ip>\d+\.\d+\.\d+\.\d+) (- - \[)(?<datetime>[\s\S]+)(?<t1>\][\s"]+)(?<request>[A-Z]+) (?<url>[\S]*) (?<protocol>[\S]+)["] (?<code>\d+) (?<sendbytes>\d+) ["](?<refferer>[\S]*) ["](?<useragent>[\S\s]+)["] ["](?<proxyaddr>[\S\s]+)["]Python版本:
(?P<ip>.*?) - - \[(?P<time>.*?)\] "(?P<request>.*?)" (?P<status>.*?) (?P<bytes>.*?) "(?P<referer>.*?)" "(?P<ua>.*?)" "(?P<proxy_address>.*)"
2.1.2 基于Spark实现Nginx数据匹配

代码实现:

from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
from pyspark.sql.types import StringTypeos.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'if __name__ == '__main__':# 1- 创建SparkSession对象spark = SparkSession.builder\.appName("nginx_etl")\.master("local[*]")\.config("spark.sql.shuffle.partitions",2)\.getOrCreate()# 2- 数据输入:读取Kafka中的数据""""startingOffsets","earliest":该配置,在实际工作中一般不需要配置。这里是为了开发代码方便"""init_df = spark.readStream.format("kafka")\.option("kafka.bootstrap.servers","192.168.88.166:9092")\.option("subscribe","xtzg_nginx_log")\.option("startingOffsets","earliest")\.load()# 结构化流中不能以show()方式打印数据数据内容# init_df.show()# 3- 数据ETL处理# 3.1- value字段解码的操作"""cast(StringType()):将字段数据类型强制转换为字符串。等同于SQL语句中的cast(value as string)下面两种方式都可以,推荐使用第一种,因为性能更好"""# type_cast_df = init_df.select(init_df.value.cast(StringType()).alias("value"))type_cast_df = init_df.selectExpr("cast(value as string) as value")# 3.2- 通过正则表达式提取Nginx的字段pattern = '(?<ip>\d+\.\d+\.\d+\.\d+) (- - \[)(?<datetime>[\s\S]+)(?<t1>\][\s"]+)(?<request>[A-Z]+) (?<url>[\S]*) (?<protocol>[\S]+)["] (?<code>\d+) (?<sendbytes>\d+) ["](?<refferer>[\S]*) ["](?<useragent>[\S\s]+)["] ["](?<proxyaddr>[\S\s]+)["]'regexp_df = type_cast_df.select(F.regexp_extract("value",pattern,1).alias("ip"),F.regexp_extract("value",pattern,3).alias("datetime"),F.regexp_extract("value",pattern,4).alias("t1"),F.regexp_extract("value",pattern,5).alias("request"),F.regexp_extract("value",pattern,6).alias("url"),F.regexp_extract("value",pattern,7).alias("protocol"),F.regexp_extract("value",pattern,8).alias("code"),F.regexp_extract("value",pattern,9).alias("sendbytes"),F.regexp_extract("value",pattern,10).alias("refferer"),F.regexp_extract("value",pattern,11).alias("useragent"),F.regexp_extract("value",pattern,12).alias("proxyaddr"))# 4- 数据输出,启动流式任务regexp_df.writeStream.format("console").outputMode("append").start().awaitTermination()

运行结果截图:
在这里插入图片描述

可能遇到的错误:
在这里插入图片描述

原因: regexp_extract函数只能传递Java版的正则表达式,不能用Python的

2.2 字段解析

需求:根据nginx日志,ip标识唯一的用户,需要ip分组,统计得到用户访问的pv、uv、区域、状态码、终端设备的操作系统、设备品牌、浏览器、访问时间(年-月-日 时:分:秒)

2.2.1 日期格式转换

Python的datetime函数库

  • 相关函数:
    • strftime(): 把日期对象转成指定的时间格式的字符串
    • strptime(): 把指定格式的日期字符串转换为日期对象
  • 参考文档: https://docs.python.org/zh-cn/3/library/datetime.html#strftime-strptime-behavior
  • 解析格式: %d/%b/%Y:%H:%M:%S %z => %Y-%m-%d %H:%M:%S
    • 28/Jul/2022:16:22:07 +0800 => 日期对象 => 2022-07-28 16:22:07
  • 测试代码

Python方式

from datetime import datetimeif __name__ == '__main__':date_str = "11/Feb/2025:14:34:49 +0800"print(datetime.strptime(date_str, "%d/%b/%Y:%H:%M:%S %z").strftime("%Y-%m-%d %H:%M:%S"))

SparkSQL方式(重点掌握)

regexp_df.withColumn("datetime",F.from_unixtime(F.unix_timestamp("datetime","dd/MMM/yyyy:HH:mm:ss Z"),"yyyy-MM-dd HH:mm:ss"))

在这里插入图片描述

2.2.2 IP解析地理位置(了解)

根据IP解析地理位置

  • 方式一: 使用ip解析地理位置API
    • ip地址:http://opendata.baidu.com/api.php?query=117.136.12.79&co=&resource_id=6006&oe=utf8
    • 像百度地图开发平台 / 高德地图开放平台 … 都会提供IP解析的服务接口
    • 百度地图:https://lbs.baidu.com/faq/api?title=webapi/ip-api-base
    • 高德地图:https://lbs.amap.com/api/webservice/guide/api/ipconfig
    • 其他平台:https://www.nowapi.com/
  • 方式二: (了解)使用geo_ip依赖包和GeoLite2-City.mmdb库
    • 依赖包:geoip2~=4.5.0
    • 下载地址:https://gitcode.com/crownp/geolite2_demo/blob/master/src/main/resources/GeoLite2-City.mmdb
  • IP在线解析测试代码

Python的Requests库的介绍:https://requests.readthedocs.io/en/latest/

#!/usr/bin/env python
# @desc : 
__coding__ = "utf-8"
__author__ = "bytedance"import requestsdef parse_ip(ip_str):params = {"query": ip_str,"co": "","resource_id": "6006","oe": "utf8",}# 发送请求response = requests.get(url="https://opendata.baidu.com/api.php", params=params)# 解析响应内容result = response.json()status = result['status']if status == '0':# 正常try:return result['data'][0]['location'].split(" ")[0]except:return "未知区域"else:return "未知区域"if __name__ == '__main__':ip_str = "127.0.0.1"ip_str = "10.254.1.97"ip_str = "157.148.69.76"area = parse_ip(ip_str)print(area)
2.2.3 UA解析

UA说明

  • UA为useragent简称,特指用户访问系统使用的客户端信息,一般包含操作系统,浏览器,设备品牌信息等
  • UA字符串信息:http://useragentstring.com/
  • 使用,需导入UA解析依赖包:from user_agents import parse
  • UA的作用
    • 1.客户端识别:通过User-Agent,服务器能够识别客户端的类型和版本,从而提供相应的内容和服务。比如,在移动设备上展示适合屏幕大小的网页布局,或在不同浏览器上提供兼容性优化。
    • 2.统计分析:网站和应用开发者可以利用User-Agent来收集客户端的信息,进行用户行为分析和统计。这有助于了解用户使用的设备和偏好,以便进行产品和服务的改进。
    • 3.安全性:User-Agent也可以用于安全验证和防止恶意行为。通过分析User-Agent,服务器可以检测到异常或伪造的请求,并采取相应的安全措施。
  • 测试代码:
from user_agents import parseif __name__ == '__main__':ua_str = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 MQQBrowser/11.0.7 Mobile/15E148 Safari/604.1 QBWebViewUA/2 QBWebViewType/1 WKType/1"result = parse(ua_str)# os操作系统信息print("os----------")print(result.os.family)print(result.os.version)print(result.os.version_string)# brower浏览器信息print("browser----------")print(result.browser.family)print(result.browser.version)print(result.browser.version_string)# device设备信息print("device----------")print(result.device.family)print(result.device.model)

2.3 完整代码

需要将结果数据同时写入到Kafka和HDFS。清洗后的日志,可以用于其他业务分析,具有一定的价值。因为Kafka不能永久保存数据,所以需要把数据存储到HDFS一份。

因为每天都有很多日志,所以需要对日志进行分区。可以通过partitionBy()方法进行分区写入到HDFS。分区的字段需要进行计算。

另外,为了减少小文件生成,可以使用trigger来指定写入的时间间隔。

  • 先创建Kafka的Topic
cd /export/server/kafka/bin
./kafka-topics.sh --create --bootstrap-server up01:9092 --topic dwd_nginx_etl_result
  • 完整代码
from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
from pyspark.sql.types import StringType, MapType
import requests
from user_agents import parseos.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'if __name__ == '__main__':# 1- 创建SparkSession对象spark = SparkSession.builder\.appName("nginx_etl")\.master("local[*]")\.config("spark.sql.shuffle.partitions",2)\.getOrCreate()# 配置checkpointLocation路径,推荐使用HDFS路径spark.conf.set("spark.sql.streaming.checkpointLocation", "hdfs://192.168.88.166:8020/xtzg/chk")# 2- 数据输入:读取Kafka中的数据""""startingOffsets","earliest":该配置,在实际工作中一般不需要配置。这里是为了开发代码方便"""init_df = spark.readStream.format("kafka")\.option("kafka.bootstrap.servers","192.168.88.166:9092")\.option("subscribe","xtzg_nginx_log")\.option("startingOffsets","earliest")\.load()# 结构化流中不能以show()方式打印数据数据内容# init_df.show()# 3- 数据ETL处理# 3.1- value字段解码的操作"""cast(StringType()):将字段数据类型强制转换为字符串。等同于SQL语句中的cast(value as string)下面两种方式都可以,推荐使用第一种,因为性能更好"""# type_cast_df = init_df.select(init_df.value.cast(StringType()).alias("value"))type_cast_df = init_df.selectExpr("cast(value as string) as value")# 3.2- 通过正则表达式提取Nginx的字段pattern = '(?<ip>\d+\.\d+\.\d+\.\d+) (- - \[)(?<datetime>[\s\S]+)(?<t1>\][\s"]+)(?<request>[A-Z]+) (?<url>[\S]*) (?<protocol>[\S]+)["] (?<code>\d+) (?<sendbytes>\d+) ["](?<refferer>[\S]*) ["](?<useragent>[\S\s]+)["] ["](?<proxyaddr>[\S\s]+)["]'# 这里不允许使用Python正则表达式,只能使用Java正则表达式# pattern = '(?P<ip>.*?) - - \[(?P<time>.*?)\] "(?P<request>.*?)" (?P<status>.*?) (?P<bytes>.*?) "(?P<referer>.*?)" "(?P<ua>.*?)" "(?P<proxy_address>.*)"'regexp_df = type_cast_df.select(F.regexp_extract("value",pattern,1).alias("ip"),F.regexp_extract("value",pattern,3).alias("datetime"),F.regexp_extract("value",pattern,4).alias("t1"),F.regexp_extract("value",pattern,5).alias("request"),F.regexp_extract("value",pattern,6).alias("url"),F.regexp_extract("value",pattern,7).alias("protocol"),F.regexp_extract("value",pattern,8).alias("code"),F.regexp_extract("value",pattern,9).alias("sendbytes"),F.regexp_extract("value",pattern,10).alias("refferer"),F.regexp_extract("value",pattern,11).alias("useragent"),F.regexp_extract("value",pattern,12).alias("proxyaddr"))# 3.3- 日期时间格式转换datetime_df = regexp_df.withColumn("datetime",F.from_unixtime(F.unix_timestamp("datetime","dd/MMM/yyyy:HH:mm:ss Z"),"yyyy-MM-dd HH:mm:ss"))# 3.4- IP地理位置解析@F.udf(returnType=StringType())def parse_ip(ip_str):params = {"query": ip_str,"co": "","resource_id": "6006","oe": "utf8",}# 发送请求response = requests.get(url="https://opendata.baidu.com/api.php", params=params)# 解析响应内容result = response.json()status = result['status']if status == '0':# 正常try:return result['data'][0]['location'].split(" ")[0]except:return "未知区域"else:return "未知区域"area_df = datetime_df.withColumn("area",parse_ip("ip"))# 3.5- UA解析"""为什么这里用户自定义函数推荐返回字典?方便后续取值"""@F.udf(returnType=MapType(keyType=StringType(),valueType=StringType()))def parse_ua(ua_str):result = parse(ua_str)os = result.os.familybrowser = result.browser.familydevice = result.device.modelreturn {"os":os,"browser":browser,"device":device}ua_df = area_df.withColumn("os",parse_ua("useragent")['os'])\.withColumn("browser", parse_ua("useragent")['browser'])\.withColumn("device", parse_ua("useragent")['device'])# 4- 数据输出,启动流式任务# 4.1- 输出到HDFS# 新增一个分区字段dt_df = ua_df.withColumn("dt",F.split("datetime"," ")[0])# partitionBy表示按照哪个字段进行分区dt_df.writeStream.format("orc").partitionBy("dt")\.option("path","hdfs://192.168.88.166:8020/xtzg/etl/dwd_nginx_etl_result")\.start()# 4.2- 输出到Kafka# 注意:一般将数据内容转换为JSON格式输出到Kafka中,为了后续使用方便# 注意:输出到Kafka中的字段名称只能叫valuekafka_df = ua_df.select(F.to_json(F.struct("ip","datetime","t1","request","url","protocol","code","sendbytes","refferer","useragent","proxyaddr","area","os","browser","device")).alias("value"))kafka_df.writeStream.format("kafka")\.option("kafka.bootstrap.servers","192.168.88.166:9092")\.option("topic","dwd_nginx_etl_result")\.start()# 4.3- 输出到控制台(为了测试)# awaitTermination()只能加在最后一个start()的后面dt_df.writeStream.format("console").outputMode("append").start().awaitTermination()

可能遇到的错误一:
在这里插入图片描述

原因: 结构化流中将数据输出到文件系统中,需要配置checkpointLocation

可能遇到的错误二:
在这里插入图片描述

原因: 输出到Kafka中的字段名称只能叫value

2.4 使用Hive读取HDFS数据

  • 创建表
CREATE external TABLE dwd.dwd_nginx_etl_result (ip string,`datetime` string,t1 string,request string,url string,protocol string,code string,sendbytes string,refferer string,useragent string,proxyaddr string,area string,os string,browser string,device string
)COMMENT 'nginx日志'PARTITIONED BY (dt string)STORED AS ORCLOCATION '/xtzg/etl/dwd_nginx_etl_result'TBLsql ('orc.compress' = 'SNAPPY')
;
  • 同步分区
MSCK REPAIR TABLE dwd.dwd_nginx_etl_result;

3、指标统计

  • 需求
统计实时请求总数(pv)
统计用户数(uv)
统计用户访问所在区域省(类似抖音的位置显示)
统计用户响应状态码
统计用户使用设备终端信息
统计用户操作系统信息
统计用户首次访问系统的时间
统计用户末次访问系统的时间ip: 用户访问系统的唯一地址
pv:访问系统的页面次数
uv:访问系统的用户数
area:访问系统用户来自的区域,根据ip解析出地址位置
status_code:访问系统用户请求http协议响应状态码
device_os:设备终端,从ua中提取手机或电脑的系统
device_brand:设备品牌名称,从ua中提取手机或电脑的品牌
browser_name:访问系统用户使用的浏览器名称
first_access_time:用户首次访问系统的时间
last_access_time:用户首次访问系统的时间
  • Doris建表语句

使用unique模型。

CREATE DATABASE IF NOT EXISTS log_analysis_db;
CREATE TABLE IF NOT EXISTS log_analysis_db.nginx_log_result
(ip varchar(15) comment 'ip地址',pv int comment 'pv数',uv int comment 'uv数',area varchar(50) comment '用户所在区域,根据ip解析',status_code varchar(10) comment '请求响应状态码',device_os varchar(50) comment '设备系统,从ua中提取手机或电脑使用的系统',device_brand varchar(50) comment ',从ua中提取手机或电脑的品牌',browser_name varchar(50) comment '电脑和手机,使用浏览器,记录浏览器简称',first_access_time datetime comment 'nginx日志记录首次访问时间',last_access_time datetime comment 'nginx日志记录末次访问时间'
)
UNIQUE KEY(ip)
DISTRIBUTED BY HASH(ip) BUCKETS 10
sql("replication_num" = "1");
  • 完整代码
from pyspark.sql import SparkSession, DataFrame
import os
import pyspark.sql.functions as F
from pyspark.sql.types import StringTypeos.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'if __name__ == '__main__':# 1- 创建SparkSession对象spark = SparkSession.builder \.appName("nginx_analysis") \.master("local[*]") \.config("spark.sql.shuffle.partitions", 2) \.getOrCreate()# 2- 数据输入:读取Kafka中的数据init_df = spark.readStream.format("kafka") \.option("kafka.bootstrap.servers", "192.168.88.166:9092") \.option("subscribe", "dwd_nginx_etl_result") \.option("startingOffsets", "earliest") \.load()# 3- 数据处理# 3.1- value字段类型转换type_cast_df = init_df.select(init_df.value.cast(StringType()).alias("value"))# 3.2- 从JSON中提取一个个字段"""json_tuple与get_json_object的区别get_json_object:优点:同时能够解析嵌套的JSON缺点:一次只能得到一个字段json_tuple:优点:一次能得到多个字段缺点:针对嵌套JSON,只能一层层解析"""parse_json_df = type_cast_df.select(F.json_tuple("value","ip","datetime","code","area","os","browser","device")\.alias("ip","datetime","status_code","area","device_os","browser_name","device_brand"))# 3.3- 指标统计# F.lit(1)生成一列,每行的数据内容一样,全都是1。与F.col函数作用类似# 因为类似area的这些字段的数据类型是字符串,聚合函数没有太适合的,因此使用firstresult_df = parse_json_df.groupBy("ip").agg(F.count("ip").alias("pv"),F.lit(1).alias("uv"),F.first("area").alias("area"),F.first("status_code").alias("status_code"),F.first("device_os").alias("device_os"),F.first("device_brand").alias("device_brand"),F.first("browser_name").alias("browser_name"),F.min("datetime").alias("first_access_time"),F.max("datetime").alias("last_access_time"))# 4- 数据输出# 4.1- 输出到Dorisdef write_2_doris(batch_df:DataFrame, batch_id):"""将DataFrame输出到Doris中:param batch_df: 有界的DataFrame:param batch_id: 批次ID:return:"""# 注意:一般先用append。如果明确知道要怎么做,那可以再使用overwritebatch_df.write.jdbc(url="jdbc:mysql://192.168.88.166:9030/log_analysis_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false",table="nginx_log_result",mode="append",sql={ 'user' : 'root', 'password' : '123456' })result_df.writeStream.foreachBatch(write_2_doris).outputMode("update").start()# 4.2- 输出到控制台result_df.writeStream.format("console").outputMode("update").start().awaitTermination()
  • 结果数据核对
./kafka-console-producer.sh --broker-list up01:9092 --topic dwd_nginx_etl_result
{"ip":"210.27.147.62","cookie":"- - [","datetime":"2024-11-14 11:11:11","t1":"] \"","request":"GET","url":"/search.html","protocol":"HTTP/1.1","code":"401","sendbytes":"58840","refferer":"https://www.douyin.com/goods-recommend/search.html?keyword=美味\"","useragent":"Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.1.6.1096 Mobile Safari/537.36","proxyaddr":"-","area":"广东省广州市","os":"Android","browser":"UC Browser","device":"XiaoMi MI 9","dt":"2024-11-12"}

1、尝试进行用户行为日志的数据ETL、指标统计

提示:核心是如何解析JSON格式,得到一个个独立的字段

相关文章:

day09_实时类标签/指标

文章目录 day09_实时类标签/指标一、日志数据实时采集2、Flume简介2.3 项目日志数据采集Flume配置2.3.1 涉及的Flume组件和参数2.3.2 Nginx日志采集2.3.3 用户行为日志采集 二、Nginx日志数据统计1、日志格式说明2、数据ETL2.1 日志抽取2.1.1 正则表达式2.1.2 基于Spark实现Ngi…...

【前端开发学习笔记16】Vue_9

文章分类架子 多个页面复用&#xff0c;封装成组件&#xff1a; props 定制标题默认插槽 default 定制内容主体具名插槽 extra 定制按钮 <template><el-card class"page-container"><template #header><div class"header"><s…...

Bash 中的运算方式

目录 概述&#xff1a; 1. (()) 运算符 2. let 命令 3. expr 命令 4. $[] 直接运算 5. bc&#xff08;计算器&#xff0c;支持浮点数&#xff09; 6. awk&#xff08;强大的文本处理工具&#xff0c;也可计算&#xff09; 概述&#xff1a; Bash 本身只支持整数运算&am…...

2025年3月营销灵感日历

2025年的第一场营销大战已经拉开帷幕了&#xff01; 三月可是全年最值钱的营销黄金月——妇女节、植树节、315消费者日三大爆点连击&#xff0c;还有春分、睡眠日、世界诗歌日等20隐藏流量密码。 道叔连夜扒了18个行业数据&#xff0c;整理了这份《2025年3月营销灵感日历》&a…...

MySQL的innoDB引擎

一、逻辑存储结构 表空间:ibd文件,一个MySQL实例可以对应多个表空间,用于存储记录,索引等数据; 段:分为数据段(leaf node segment)、索引段(non-leaf node segment)、回滚段(rollback segment),innodb是索引组织表,数据段就是B+树的非叶子节点。段用来管理多个e…...

HCIA项目实践---OSPF的知识和原理总结

9.5 OSPF 9.5.1 从哪些角度评判一个动态路由协议的好坏&#xff1f; &#xff08;1&#xff09;选路佳&#xff08;是否会出环&#xff09; OSPF 协议采用链路状态算法&#xff0c;通过收集网络拓扑信息来计算最短路径&#xff0c;从根本上避免了路由环路的产生。 &#xff08…...

hexo 魔改 | 修改卡片透明度

hexo 魔改 | 修改卡片透明度 ** 博客食物用更佳 博客地址 ** 这是笔者自己瞎倒腾的。作为前端菜鸡一枚&#xff0c;大佬们随便看看就好~ 我用的主题是 butterfly 4.12.0 分析 通过开发者工具可以看出来卡片的背景和 --card-bg 变量有关 再在 sources 下的 css 文件夹下的…...

今日AI和商界事件(2025-02-13)

今日AI领域的主要事件包括&#xff1a; 一、OpenAI相关动态 取消独立发布o3模型计划&#xff1a; OpenAI首席执行官奥尔特曼宣布&#xff0c;公司取消独立发布o3模型的计划。未来几个月内&#xff0c;OpenAI将推出GPT-5&#xff0c;该模型将整合多项技术&#xff0c;并应用于C…...

38.日常算法

1.最短无序连续子数组 题目来源 给你一个整数数组 nums &#xff0c;你需要找出一个 连续子数组 &#xff0c;如果对这个子数组进行升序排序&#xff0c;那么整个数组都会变为升序排序。请你找出符合题意的 最短 子数组&#xff0c;并输出它的长度。 示例 1&#xff1a; 输入…...

如何构建有效的人工智能代理

目录 什么是 AI 代理? 何时应使用 AI 代理? 人工智能代理的构建模块 构建 AI 代理的常用方法 1. 提示链接(分步说明) 2.路由(将任务发送到正确的地方) 3.并行处理(同时做多件事) 4. 协调者和工作者 AI(团队合作) 5. 评估器和优化器(修复错误) 如何让人工…...

qt 事件的传递顺序

在 Qt 中&#xff0c;事件的传递顺序遵循以下基本规则&#xff1a; 事件的产生&#xff1a;当用户与界面交互时&#xff0c;操作&#xff08;如鼠标点击、键盘输入等&#xff09;会生成相应的事件&#xff08;如 QMouseEvent、QKeyEvent 等&#xff09;。 事件的传递顺序&…...

全面掌握Flutter开发:从核心原理到跨平台实战,构建高效应用

文章目录 引言 一、Flutter框架概述二、Flutter开发环境搭建三、Flutter核心技术解析1. **Widget树与状态管理**2. **路由与导航**3. **网络请求与数据解析**4. **本地存储与数据库**5. **包管理与依赖** 四、实战案例&#xff1a;开发跨平台新闻客户端五、Flutter开发工具与调…...

Flutter 添加 iOS widget 小组件

环境 macOS 15.1 Xcode16.1 Flutter 3.27.4 前言 本篇文章主要记录&#xff0c;在Flutter 项目中如何正确地添加iOS 小组件&#xff0c;iOS 小组件 相关的知识在另一篇文章有记录。 iOS 14 widget 添加小组件 WidgetExtension 打开Xcode New -> Target 选择 iOS -> 搜…...

2025年金三银四经典自动化测试面试题

概述 觉得自动化测试很难&#xff1f; 是的&#xff0c;它确实不简单。但是学会它&#xff0c;工资高啊&#xff01; 担心面试的时候被问到自动化测试&#xff1f; 嗯&#xff0c;你担心的没错&#xff01;确实会被经常问到&#xff01; 现在应聘软件测试工程师的岗位&…...

C++17 中 std::lcm:从入门到精通

文章目录 一、引言二、std::lcm 的基本概念三、入门示例四、计算多个整数的最小公倍数五、std::lcm 的实现原理六、在实际项目中的应用七、注意事项八、总结 一、引言 在 C 编程中&#xff0c;处理数学运算时&#xff0c;计算最小公倍数&#xff08;Least Common Multiple&…...

初阶c语言(循环语句习题,完结)

前言&#xff1a; c语言为b站鹏哥&#xff0c;嗯对应视频37集 昨天做的c语言&#xff0c;今天在来做一遍&#xff0c;发现做错了 今天改了平均值的计算&#xff0c; 就是说最大值加上最小值&#xff0c;如果说这个数值非常大的话&#xff0c;两个值加上会超过int类型的最大…...

本地Deepseek-r1:7b模型集成到Google网页中对话

本地Deepseek-r1:7b网页对话 基于上一篇本地部署的Deepseek-r1:7b&#xff0c;使用黑窗口对话不方便&#xff0c;现在将本地模型通过插件集成到Google浏览器中 安装Google插件 在Chrome应用商店中搜索page assis 直接添加至Chrome 修改一下语言 RAG设置本地运行的模型&#…...

SSM课设-学生选课系统

【课设者】SSM课设-学生选课系统 分为 管理员 和 老师 和 学生端 技术栈 前端: HtmlCssJavaScriptAjax 后端: Spring、Spring MVC、MyBatis、MySQL、JSP 学生端 --选课 选课 搜索 --查看选课结果 --退选 --查看已修课程 --管理个人信息 老师端 --添加教学课程 添加 …...

Windows中使用Docker安装Anythingllm,基于deepseek构建自己的本地知识库问答大模型,可局域网内多用户访问、离线运行

文章目录 Windows中使用Docker安装Anythingllm&#xff0c;基于deepseek构建自己的知识库问答大模型1. 安装 Docker Desktop2. 使用Docker拉取Anythingllm镜像2. 设置 STORAGE_LOCATION 路径3. 创建存储目录和 .env 文件.env 文件的作用关键配置项 4. 运行 Docker 命令docker r…...

AI前端开发技能提升与ScriptEcho:拥抱AI时代的前端开发新范式

随着人工智能技术的飞速发展&#xff0c;AI前端开发岗位对技能的要求也水涨船高。越来越多的企业需要具备AI相关知识和高级前端开发能力的工程师&#xff0c;这使得传统的前端开发模式面临着巨大的挑战。如何提升开发效率&#xff0c;降低人力成本&#xff0c;成为了摆在所有前…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

结构化文件管理实战:实现目录自动创建与归类

手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题&#xff0c;进而引发后续程序异常。使用工具进行标准化操作&#xff0c;能有效降低出错概率。 需要快速整理大量文件的技术用户而言&#xff0c;这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB&#xff0c;…...

python打卡第47天

昨天代码中注意力热图的部分顺移至今天 知识点回顾&#xff1a; 热力图 作业&#xff1a;对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图&#xff0c;展示模…...

GB/T 43887-2024 核级柔性石墨板材检测

核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标&#xff1a; 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...

Excel 怎么让透视表以正常Excel表格形式显示

目录 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总...

解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法

目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客&#xff1a;【写在创作纪念日】基于SpringBoot和PostG…...