前文:
數據倉庫中ods層一般使用外部表,一般默認採用 \001 作爲分隔符,但textfile格式在遇到文本數據就會有分隔符及換行符問題,所以採用parquet作爲存儲格式,但也會引進數據類型轉換的問題。
對於日誌數據,kafka中經常存放不同來源的日誌數據,可通過Flume的正則匹配將數據發送到不同的hdfs文件夾。
對於部分mysql百萬級進行模糊查詢將會產生慢查詢,一般我們用可以存放在es中。
一、Mysql導數據導Hive
1.1 建表
create external table if not exists ods.ods_stu(
`id` int comment '主鍵Id',
`name` string comment '名稱',
`addtime` string commint '添加時間'
)comment '姓名錶'
partitioned by (dt string)
stored as parquet
location '/user/hive/warehouse/ods.db/ods_stu';
備註:parquet存儲格式查詢效率高
1.2 使用sqoop導數據
sqoop import \
--connect jdbc:mysql://localhost:3306/ods_stu\
--username root\
--password 123456\
--null-string 'NULL' \
--null-non-string 'NULL' \
--query '
select *
from stu
where $CONDITIONS' \
--target-dir /user/hive/warehouse/ods.db/ods_stu/dt=2020-06-04 \
--map-column-java addtime=String \
--map-column-hive addtime=String
--append \
--split-by id \
--num-mappers 2 \
--as-parquetfile \
存在問題:sqoop直接從mysql導入parquet格式的hive表有一個數據類型異常
Failed with exception java.io.IOException:org.apache.hadoop.hive.ql.metadata.HiveException: java.lang.ClassCastException: org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.TimestampWritabl
解決方式:採用 --map-column-java 和 --map-column-hive 把mysql中時間類型之間轉換爲string類型存儲hive中
load data inpath '/user/hive/warehouse/ods.db/ods_stu/dt=2020-06-04' into table ods.ods_minisns_systemtag partition (dt = '2020-06-04');
1.3 使用spark導數據
/**
* 通過拉取mysql數據,創建臨時表寫入hive中
*/
val df: DataFrame = spark.read.format("jdbc")
.options(mysqlParam)
//增量:單線程
.option("query", query_mysql)
.load()
df.createOrReplaceTempView(tmp_tablename)
spark.sql(insert_hive);
備註:spark可以直接寫入parquet格式的hive表,並且可以直接數據清洗。但spark連接查詢mysql會有一個散列id排序的問題,並且使用query自定義sql查詢的話只有1個線程拉數據。而sqoop可以完美的解決這兩個問題,使用方式簡單。
1.4sqoop與spark導數據的比較
sqoop | spark | |
textfile格式外部表 | 不支持複雜分隔符,需要編譯 | 無需關心分隔符 |
paquet格式 | 需處理數據類型轉換 | 無需關心數據類型問題 |
散列id同步 | 存在傾斜問題 |
存在傾斜問題 |
並行度 | 完美處理 | query自定義查詢只有單線程查詢mysql |
直接清洗 | 僅在mysql查詢時清洗,性能較差 | 拉取數據後直接清洗 |
使用難度 | 簡單配置 | 寫個工具類傳參也可,但較複雜 |
二、Flume採集Kafka數據
2.1kafka數據到hive外部表
a1.sources = r1
a1.channels = c1
a1.sinks = k1
#———————————————————— sources ————————————————————#
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.batchSize = 5000
a1.sources.r1.batchDurationMillis = 2000
a1.sources.r1.kafka.bootstrap.servers = bigdata-test:9092
a1.sources.r1.kafka.topics = test_0603
a1.sources.r1.kafka.consumer.group.id = custom.g.id
a1.sources.r1.kafka.consumer.auto.offset.reset=earliest
#———————————————————— interceptors ————————————————————#
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = regex_extractor
# 正則獲取捕獲組並命名,用於sink文件夾路徑設置
a1.sources.r1.interceptors.i1.regex = (\\d{4}\\-\\d{2}\\-\\d{2})
a1.sources.r1.interceptors.i1.serializers = s1
a1.sources.r1.interceptors.i1.serializers.s1.name = eventTime
#———————————————————— channels ————————————————————#
a1.channels.c1.type = memory
a1.channels.c1.capacity = 10000
a1.channels.c1.transactionCapacity = 10000
a1.channels.c1.byteCapacityBufferPercentage = 20
a1.channels.c1.byteCapacity = 800000
#———————————————————— sinks ————————————————————#
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /tmp/flume_test_0603/%{eventTime}
# 使用當地時間(而不是從事件的時間戳頭)而取代轉義序列。
a1.sinks.k1.hdfs.useLocalTimeStamp = true
# DataStream不會壓縮輸出文件
a1.sinks.k1.hdfs.fileType = DataStream
# 默認爲1024,觸發滾動的文件大小,以字節爲單位(0:從不基於文件大小滾動)
a1.sinks.k1.hdfs.rollSize = 104857600
# 默認爲30,滾動當前文件之前要等待的秒數(0 =根據時間間隔從不滾動)
#a1.sinks.k1.hdfs.rollInterval = 7200
# 默認爲10,滾動之前寫入文件的事件數(0 =基於事件數從不滾動)
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.filePrefix = %Y%m%d_
a1.sinks.k1.hdfs.fileSuffix = .log
#———————————————————— 綁定 ————————————————————#
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
備註:通過正則匹配kafka日誌中的數據,將數據發送到不同的文件夾下,但是正在寫入時文件格式爲tmp類型,hive不能識別出文件,所以可以按2小時形成一個正式文件。當沒有數據寫入時,就不會生成tmp文件。
2.2flume切割文件相關參數
#———————————————————— 滾動切換:控制寫文件的切換規則 ————————————————————#
# 按文件體積(字節)來切
ag1.sinks.sink1.hdfs.rollSize = 512000
# 按event條數切
ag1.sinks.sink1.hdfs.rollCount = 1000000
# 按時間間隔切換文件,創建文件多久後
ag1.sinks.sink1.hdfs.rollInterval = 60
#———————————————————— 時間切換:控制生成目錄的規則 ————————————————————#
ag1.sinks.sink1.hdfs.path =hdfs://hadoop04:9000/flume/%y/%m/%d/%H
ag1.sinks.sink1.hdfs.round = true
ag1.sinks.sink1.hdfs.roundValue = 10
ag1.sinks.sink1.hdfs.roundUnit = minute
ag1.sinks.sink1.hdfs.useLocalTimeStamp = true
三、Es數據採集
3.1採集mysql數據到Es
stu.conf文件
input {
jdbc{
# mysql 數據庫鏈接
jdbc_connection_string => "jdbc:mysql://localhost:3306/minisns?useUnicode=true&characterEncoding=utf-8&useSSL=false"
# 用戶名和密碼
jdbc_user => "123456"
jdbc_password => "123456"
#驅動
jdbc_driver_library => "/opt/mysql/mysql-connector-java.jar"
# 驅動類名
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
#jdbc_page_size => "5000"
jdbc_page_size => "50000"
jdbc_default_timezone =>"Asia/Shanghai"
# mysql文件, 也可以直接寫SQL語句在此處,如下:
statement_filepath => "/opt/logstash/config/jdbc_sql/stu.sql"
# 這裏類似crontab,可以定製定時操作,比如每分鐘執行一次同步(分 時 天 月 年)
schedule => "*/5 * * * *"
#type => "jdbc"
# 是否記錄上次執行結果, 如果爲真,將會把上次執行到的 tracking_column 字段的值記錄下來,保存到 last_run_metadata_path 指定的文件中
record_last_run => true
# 是否需要記錄某個column 的值,如果record_last_run爲真,可以自定義我們需要 track 的 column 名稱,此時該參數就要爲 true. 否則默認 track 的是 timestamp 的值.
use_column_value => true
# 如果 use_column_value 爲真,需配置此參數. track 的數據庫 column 名,該 column 必須是遞增的. 一般是mysql主鍵
tracking_column => "addtime"
tracking_column_type => "datatime"
last_run_metadata_path => "/opt/logstash/config/track_value/stu"
# 是否清除 last_run_metadata_path 的記錄,如果爲真那麼每次都相當於從頭開始查詢所有的數據庫記錄
clean_run => false
# 是否將 字段(column) 名稱轉小寫
lowercase_column_names => false
}
}
output {
elasticsearch {
hosts => ["http://es-01:9200","http://es-02:9200","http://es-03:9200"]
index => "stu_v1"
document_id => "%{Id}"
user => "elastic"
password => "123456"
}
}
stu.sql文件
select
Id
,name
,date_format(addtime, '%Y-%m-%d %H:%i:%s') as addtime
from student
where addtime >= date_add(:sql_last_value, interval -8 hour)
3.2採集Kafka數據到Es
#=========================== Filebeat inputs =============================
filebeat.inputs:
# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.
- type: kafka
hosts:
- kafka-01:9092
- kafka-02:9092
- kafka-03:9092
topics: ["stu"]
group_id: "test_group"
# Change to true to enable this input configuration.
#enabled: false
#==================== Elasticsearch template setting ==========================
setup.template.settings:
index.number_of_shards: 3
index.number_of_replicas: 1
#index.codec: best_compression
#_source.enabled: false
#================================ Outputs =====================================
# Configure what output to use when sending the data collected by the beat.
#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
hosts: ["es-01:9200","es-02:9200","es-03:9200"]
username: "elastic"
password: "123456"
index: "student_%{+yyyyMM}"
setup.template.name: "student_"
setup.template.pattern: "student_*"
setup.ilm.enabled: false
#================================ Processors =====================================
# Configure processors to enhance or manipulate events generated by the beat.
# This allows to enable 6.7 migration aliases
#migration.6_to_7.enabled: true
processors:
- drop_fields:
fields: ['host','agent','ecs','kafka']
- decode_json_fields:
fields: ["message"]
process_array: true
max_depth: 1
target: ""
overwrite_keys: false
add_error_key: true
3.3Logstash與FileBeat的對比
logstash | filebeat | |
語言 | Jruby | golang |
應用場景 | 採集kafka數據後過濾分析發送到es | 採集日誌,然後發送到消息隊列 |
優勢 | 豐富的input|filter|output插件 | 輕量級 |