點擊上方藍色字體,選擇“設爲星標”
回覆”資源“獲取更多資源
大數據領域自 2010 年開始,以 Hadoop、Hive 爲代表的離線計算開始進入各大公司的視野。大數據領域開始瞭如火如荼的發展。我個人在學校期間就開始關注大數據領域的技術迭代和更新,並且有幸在畢業後成爲大數據領域的開發者。
Flink 實時計算
我是抖音主播,我想看帶貨銷售情況的排行?
我是運營,我想看到我們公司銷售商品的 TOP10?
我是開發,我想看到我們公司所有生產環境中服務器的運行情況?
......
Flink 實時數據倉庫
技術選型
實時計算引擎
同步變異步
應用解耦
流量削峯
高吞吐:
可以滿足每秒百萬級別消息的生產和消費,並且可以通過橫向擴展,保證數據處理能力可以得到線性擴展。
低延遲:
以時間複雜度爲 O(1) 的方式提供消息持久化能力,即使對 TB 級以上數據也能保證常數時間複雜度的訪問性能。
高容錯:
Kafka 允許集羣的節點出現失敗。
可靠性:
消息可以根據策略進行磁盤的持久化,並且讀寫效率都很高。
生態豐富:
Kafka 周邊生態極其豐富,與各個實時處理框架結合緊密。
強大的狀態管理。
Flink 使用 State 存儲中間狀態和結果,並且有強大的容錯能力;
非常豐富的 API。
Flink 提供了包含 DataSet API、DataStream API、Flink SQL 等等強大的API;
生態支持完善。
Flink 支持多種數據源(Kafka、MySQL等)和存儲(HDFS、ES 等),並且和其他的大數據領域的框架結合完善;
批流一體。
Flink 已經在將流計算和批計算的 API 進行統一,並且支持直接寫入 Hive。
高度彙總,高度彙總指標一般存儲在 Redis、HBase 中供前端直接查詢使用。
明細數據,在一些場景下,我們的運營和業務人員需要查詢明細數據,有一些明細數據極其重要,比如雙十一派送的包裹中會有一些丟失和破損。
實時消息,Flink 在計算完成後,有一個下游是發往消息系統,這裏的作用主要是提供給其他業務複用;
另外,在一些情況下,我們計算好明細數據也需要再次經過消息系統才能落庫,將原來直接落庫拆成兩步,方便我們進行問題定位和排查。
Hive、Hawq、Impala:
基於 SQL on Hadoop
Presto 和 Spark SQL 類似:
基於內存解析 SQL 生成執行計劃
Kylin:
用空間換時間、預計算
Druid:
數據實時攝入加實時計算
ClickHouse:
OLAP 領域的 HBase,單表查詢性能優勢巨大
Greenpulm:
OLAP 領域的 PostgreSQL
Flink 實時數據倉庫
強一致性
自動故障轉移和容錯性
極高的讀寫 QPS,非常適合存儲 K-V 形式的指標
大廠的實時計算平臺和實時數倉技術方案
作者的經驗
數據源過多
數據源之間時間 GAP 巨大
離線數據和實時數據要求強一致性
這套數據架構引入了 Hbase 作爲中間存儲,數據鏈路變長。導致運維成本大量增加,整個架構的實時性能受制於 Hbase 的變更信息能不能及時發送。
指標沒有分層,會導致 ADB 和 Hologres 成爲查詢瓶頸。在這套數據架構中,我們完全拋棄了中間指標層,完全依賴 SQL 直接彙總查詢。一方面得益於省略中間層後指標的準確性,另一方面因爲 SQL 直接查詢會對 ADB 有巨大的查詢壓力,使得 ADB 消耗了巨大的資源和成本。
騰訊看點的實時數據系統設計
數據採集層
實時數據倉庫層
實時數據存儲層
多核 CPU 並行計算
SIMD 並行計算加速
分佈式水平擴展集羣
稀疏索引、列式存儲、數據壓縮
聚合分析優化
過去 30 分鐘內容的查詢,99% 的請求耗時在1秒內
過去 24 小時內容的查詢,90% 的請求耗時在5秒內,99% 的請求耗時在 10 秒內
阿里巴巴批流一體數據倉庫建設
統一元數據管理
統一計算引擎
統一數據存儲
實戰案例
架構設計
日誌數據上報
日誌數據清洗
實時計算程序
結果存儲
Flume 和 Kafka 整合和部署
Kafka 模擬數據生成和發送
Flink 和 Kafka 整合時間窗口設計
Flink 計算 PV、UV 代碼實現
Flink 和 Redis 整合以及 Redis Sink 實現
Flume 和 Kafka 整合和部署
tar zxf apache-flume-1.8.0-bin.tar.gz
# 定義這個 agent 中各組件的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# source的配置,監聽日誌文件中的新增數據
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F /home/logs/access.log
#sink配置,使用avro日誌做數據的消費
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = flumeagent03
a1.sinks.k1.port = 9000
#channel配置,使用文件做數據的臨時緩存
a1.channels.c1.type = file
a1.channels.c1.checkpointDir = /home/temp/flume/checkpoint
a1.channels.c1.dataDirs = /home/temp/flume/data
#描述和配置 source channel sink 之間的連接關係
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c
$ flume-ng agent
-c conf
-n a1
-f conf/log_kafka.conf >/dev/null 2>&1 &
定義這個 agent 中各組件的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1
#source配置
a1.sources.r1.type = avro
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.port = 9000
#sink配置
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.topic = log_kafka
a1.sinks.k1.brokerList = 127.0.0.1:9092
a1.sinks.k1.requiredAcks = 1
a1.sinks.k1.batchSize = 20
#channel配置
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
#描述和配置 source channel sink 之間的連接關係
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
$ flume-ng agent
-c conf
-n a1
-f conf/flume_kafka.conf >/dev/null 2>&1 &
public class UserClick {
private String userId;
private Long timestamp;
private String action;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public UserClick(String userId, Long timestamp, String action) {
this.userId = userId;
this.timestamp = timestamp;
this.action = action;
}
}
enum UserAction{
//點擊
CLICK("CLICK"),
//購買
PURCHASE("PURCHASE"),
//其他
OTHER("OTHER");
private String action;
UserAction(String action) {
this.action = action;
}
}
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
// 檢查點配置,如果要用到狀態後端,那麼必須配置
env.setStateBackend(new MemoryStateBackend(true));
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "127.0.0.1:9092");
properties.setProperty(FlinkKafkaConsumerBase.KEY_PARTITION_DISCOVERY_INTERVAL_MILLIS, "10");
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("log_user_action", new SimpleStringSchema(), properties);
//設置從最早的offset消費
consumer.setStartFromEarliest();
DataStream<UserClick> dataStream = env
.addSource(consumer)
.name("log_user_action")
.map(message -> {
JSONObject record = JSON.parseObject(message);
return new UserClick(
record.getString("user_id"),
record.getLong("timestamp"),
record.getString("action")
);
})
.returns(TypeInformation.of(UserClick.class));
SingleOutputStreamOperator<UserClick> userClickSingleOutputStreamOperator = dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<UserClick>(Time.seconds(30)) {
@Override
public long extractTimestamp(UserClick element) {
return element.getTimestamp();
}
});
dataStream
.windowAll(TumblingProcessingTimeWindows.of(Time.days(1), Time.hours(-8)))
.trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(20)))
userClickSingleOutputStreamOperator
.keyBy(new KeySelector<UserClick, String>() {
@Override
public String getKey(UserClick value) throws Exception {
return DateUtil.timeStampToDate(value.getTimestamp());
}
})
.window(TumblingProcessingTimeWindows.of(Time.days(1), Time.hours(-8)))
.trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(20)))
.evictor(TimeEvictor.of(Time.seconds(0), true))
...
public class DateUtil {
public static String timeStampToDate(Long timestamp){
ThreadLocal<SimpleDateFormat> threadLocal
= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
String format = threadLocal.get().format(new Date(timestamp));
return format.substring(0,10);
}
}
public class MyProcessWindowFunction extends ProcessWindowFunction<UserClick,Tuple3<String,String, Integer>,String,TimeWindow>{
private transient MapState<String, String> uvState;
private transient ValueState<Integer> pvState;
public void open(Configuration parameters) throws Exception {
super.open(parameters);
uvState = this.getRuntimeContext().getMapState(new MapStateDescriptor<>("uv", String.class, String.class));
pvState = this.getRuntimeContext().getState(new ValueStateDescriptor<Integer>("pv", Integer.class));
}
public void process(String s, Context context, Iterable<UserClick> elements, Collector<Tuple3<String, String, Integer>> out) throws Exception {
Integer pv = 0;
Iterator<UserClick> iterator = elements.iterator();
while (iterator.hasNext()){
pv = pv + 1;
String userId = iterator.next().getUserId();
uvState.put(userId,null);
}
pvState.update(pvState.value() + pv);
Integer uv = 0;
Iterator<String> uvIterator = uvState.keys().iterator();
while (uvIterator.hasNext()){
String next = uvIterator.next();
uv = uv + 1;
}
Integer value = pvState.value();
if(null == value){
pvState.update(pv);
}else {
pvState.update(value + pv);
}
out.collect(Tuple3.of(s,"uv",uv));
out.collect(Tuple3.of(s,"pv",pvState.value()));
}
}
userClickSingleOutputStreamOperator
.keyBy(new KeySelector<UserClick, String>() {
@Override
public String getKey(UserClick value) throws Exception {
return value.getUserId();
}
})
.window(TumblingProcessingTimeWindows.of(Time.days(1), Time.hours(-8)))
.trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(20)))
.evictor(TimeEvictor.of(Time.seconds(0), true))
.process(new MyProcessWindowFunction());
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-redis_2.11</artifactId>
<version>1.1.5</version>
</dependency>
public class MyRedisSink implements RedisMapper<Tuple3<String,String, Integer>>{
/**
* 設置redis數據類型
*/
public RedisCommandDescription getCommandDescription() {
return new RedisCommandDescription(RedisCommand.HSET,"flink_pv_uv");
}
//指定key
public String getKeyFromData(Tuple3<String, String, Integer> data) {
return data.f1;
}
//指定value
public String getValueFromData(Tuple3<String, String, Integer> data) {
return data.f2.toString();
}
}
...
userClickSingleOutputStreamOperator
.keyBy(new KeySelector<UserClick, String>() {
@Override
public String getKey(UserClick value) throws Exception {
return value.getUserId();
}
})
.window(TumblingProcessingTimeWindows.of(Time.days(1), Time.hours(-8)))
.trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(20)))
.evictor(TimeEvictor.of(Time.seconds(0), true))
.process(new MyProcessWindowFunction())
.addSink(new RedisSink<>(conf,new MyRedisSink()));
...
總結
本文分享自微信公衆號 - 大數據技術與架構(import_bigdata)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。