流資料處理利器
流處理 (Stream processing) 是一種計算機程式設計正規化,其允許給定一個資料序列 (流處理資料來源),一系列資料操作 (函式) 被應用到流中的每個元素。同時流處理工具可以顯著提高程式設計師的開發效率,允許他們編寫有效、乾淨和簡潔的程式碼。
流資料處理在我們的日常工作中非常常見,舉個例子,我們在業務開發中往往會記錄許多業務日誌,這些日誌一般是先傳送到 Kafka,然後再由 Job 消費 Kafaka 寫到 elasticsearch,在進行日誌流處理的過程中,往往還會對日誌做一些處理,比如過濾無效的日誌,做一些計算以及重新組合日誌等等,示意圖如下:
流處理工具 fx
go-zero是一個功能完備的微服務框架,框架中內建了很多非常實用的工具,其中就包含流資料處理工具fx,下面我們通過一個簡單的例子來認識下該工具:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/tal-tech/go-zero/core/fx"
)
func main() {
ch := make(chan int)
go inputStream(ch)
go outputStream(ch)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
<-c
}
func inputStream(ch chan int) {
count := 0
for {
ch <- count
time.Sleep(time.Millisecond * 500)
count++
}
}
func outputStream(ch chan int) {
fx.From(func(source chan<- interface{}) {
for c := range ch {
source <- c
}
}).Walk(func(item interface{}, pipe chan<- interface{}) {
count := item.(int)
pipe <- count
}).Filter(func(item interface{}) bool {
itemInt := item.(int)
if itemInt%2 == 0 {
return true
}
return false
}).ForEach(func(item interface{}) {
fmt.Println(item)
})
}
inputStream 函式模擬了流資料的產生,outputStream 函式模擬了流資料的處理過程,其中 From 函式為流的輸入,Walk 函式併發的作用在每一個 item 上,Filter 函式對 item 進行過濾為 true 保留為 false 不保留,ForEach 函式遍歷輸出每一個 item 元素。
流資料處理中間操作
一個流的資料處理可能存在許多的中間操作,每個中間操作都可以作用在流上。就像流水線上的工人一樣,每個工人操作完零件後都會返回處理完成的新零件,同理流處理中間操作完成後也會返回一個新的流。
fx 的流處理中間操作:
操作函式 | 功能 | 輸入 |
---|---|---|
Distinct | 去除重複的 item | KeyFunc,返回需要去重的 key |
Filter | 過濾不滿足條件的 item | FilterFunc,Option 控制併發量 |
Group | 對 item 進行分組 | KeyFunc,以 key 進行分組 |
Head | 取出前 n 個 item,返回新 stream | int64 保留數量 |
Map | 物件轉換 | MapFunc,Option 控制併發量 |
Merge | 合併 item 到 slice 並生成新 stream | |
Reverse | 反轉 item | |
Sort | 對 item 進行排序 | LessFunc 實現排序演算法 |
Tail | 與 Head 功能類似,取出後 n 個 item 組成新 stream | int64 保留數量 |
Walk | 作用在每個 item 上 | WalkFunc,Option 控制併發量 |
下圖展示了每個步驟和每個步驟的結果:
用法與原理分析
From
通過 From 函式構建流並返回 Stream,流資料通過 channel 進行儲存:
// 例子
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
fx.From(func(source chan<- interface{}) {
for _, v := range s {
source <- v
}
})
// 原始碼
func From(generate GenerateFunc) Stream {
source := make(chan interface{})
go func() {
defer close(source)
// 構造流資料寫入channel
generate(source)
}()
return Range(source)
}
Filter
Filter 函式提供過濾 item 的功能,FilterFunc 定義過濾邏輯 true 保留 item,false 則不保留:
// 例子 保留偶數
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
fx.From(func(source chan<- interface{}) {
for _, v := range s {
source <- v
}
}).Filter(func(item interface{}) bool {
if item.(int)%2 == 0 {
return true
}
return false
})
// 原始碼
func (p Stream) Filter(fn FilterFunc, opts ...Option) Stream {
return p.Walk(func(item interface{}, pipe chan<- interface{}) {
// 執行過濾函式true保留,false丟棄
if fn(item) {
pipe <- item
}
}, opts...)
}
Group
Group 對流資料進行分組,需定義分組的 key,資料分組後以 slice 存入 channel:
// 例子 按照首字元"g"或者"p"分組,沒有則分到另一組
ss := []string{"golang", "google", "php", "python", "java", "c++"}
fx.From(func(source chan<- interface{}) {
for _, s := range ss {
source <- s
}
}).Group(func(item interface{}) interface{} {
if strings.HasPrefix(item.(string), "g") {
return "g"
} else if strings.HasPrefix(item.(string), "p") {
return "p"
}
return ""
}).ForEach(func(item interface{}) {
fmt.Println(item)
})
// 原始碼
func (p Stream) Group(fn KeyFunc) Stream {
// 定義分組儲存map
groups := make(map[interface{}][]interface{})
for item := range p.source {
// 使用者自定義分組key
key := fn(item)
// key相同分到一組
groups[key] = append(groups[key], item)
}
source := make(chan interface{})
go func() {
for _, group := range groups {
// 相同key的一組資料寫入到channel
source <- group
}
close(source)
}()
return Range(source)
}
Reverse
reverse 可以對流中元素進行反轉處理:
// 例子
fx.Just(1, 2, 3, 4, 5).Reverse().ForEach(func(item interface{}) {
fmt.Println(item)
})
// 原始碼
func (p Stream) Reverse() Stream {
var items []interface{}
// 獲取流中資料
for item := range p.source {
items = append(items, item)
}
// 反轉演算法
for i := len(items)/2 - 1; i >= 0; i-- {
opp := len(items) - 1 - i
items[i], items[opp] = items[opp], items[i]
}
// 寫入流
return Just(items...)
}
Distinct
distinct 對流中元素進行去重,去重在業務開發中比較常用,經常需要對使用者 id 等做去重操作:
// 例子
fx.Just(1, 2, 2, 2, 3, 3, 4, 5, 6).Distinct(func(item interface{}) interface{} {
return item
}).ForEach(func(item interface{}) {
fmt.Println(item)
})
// 結果為 1,2,3,4,5,6
// 原始碼
func (p Stream) Distinct(fn KeyFunc) Stream {
source := make(chan interface{})
threading.GoSafe(func() {
defer close(source)
// 通過key進行去重,相同key只保留一個
keys := make(map[interface{}]lang.PlaceholderType)
for item := range p.source {
key := fn(item)
// key存在則不保留
if _, ok := keys[key]; !ok {
source <- item
keys[key] = lang.Placeholder
}
}
})
return Range(source)
}
Walk
Walk 函式併發的作用在流中每一個 item 上,可以通過 WithWorkers 設定併發數,預設併發數為 16,最小併發數為 1,如設定 unlimitedWorkers 為 true 則併發數無限制,但併發寫入流中的資料由 defaultWorkers 限制,WalkFunc 中使用者可以自定義後續寫入流中的元素,可以不寫入也可以寫入多個元素:
// 例子
fx.Just("aaa", "bbb", "ccc").Walk(func(item interface{}, pipe chan<- interface{}) {
newItem := strings.ToUpper(item.(string))
pipe <- newItem
}).ForEach(func(item interface{}) {
fmt.Println(item)
})
// 原始碼
func (p Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
pipe := make(chan interface{}, option.workers)
go func() {
var wg sync.WaitGroup
pool := make(chan lang.PlaceholderType, option.workers)
for {
// 控制併發數量
pool <- lang.Placeholder
item, ok := <-p.source
if !ok {
<-pool
break
}
wg.Add(1)
go func() {
defer func() {
wg.Done()
<-pool
}()
// 作用在每個元素上
fn(item, pipe)
}()
}
// 等待處理完成
wg.Wait()
close(pipe)
}()
return Range(pipe)
}
併發處理
fx 工具除了進行流資料處理以外還提供了函式併發功能,在微服務中實現某個功能往往需要依賴多個服務,併發的處理依賴可以有效的降低依賴耗時,提升服務的效能。
fx.Parallel(func() {
userRPC() // 依賴1
}, func() {
accountRPC() // 依賴2
}, func() {
orderRPC() // 依賴3
})
注意 fx.Parallel 進行依賴並行處理的時候不會有 error 返回,如需有 error 返回或者有一個依賴報錯需要立馬結束依賴請求請使用MapReduce工具進行處理。
總結
本篇文章介紹了流處理的基本概念和 go-zero 中的流處理工具 fx,在實際的生產中流處理場景應用也非常多,希望本篇文章能給大家帶來一定的啟發,更好的應對工作中的流處理場景。
專案地址
https://github.com/tal-tech/go-zero
元件地址
https://github.com/tal-tech/go-zero/tree/master/core/fx
Example
https://github.com/tal-tech/go-zero/tree/master/example/fx
微信交流群
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 資料流處理命令
- 資料預處理利器 Amazon Glue DataBrew
- 海量資料處理利器greenplum——初識
- 處理圖片流資料
- 智慧駕駛資料後處理分析利器—INTEWORK-VDA
- 海量資料處理利器 Roaring BitMap 原理介紹
- 使用資料流的思想處理檔案
- [翻譯]map和reduce,處理資料結構的利器資料結構
- 大資料爭論:批處理與流處理的C位之戰大資料
- 大資料流處理:Flume、Kafka和NiFi對比大資料KafkaNifi
- 使用C#處理基於位元流的資料C#
- 資料族群分析處理利器:領存FT 2000+ 6U VPX 高效能資料處理刀片
- Streams 流處理
- Python資料處理(二):處理 Excel 資料PythonExcel
- 亞信安慧AntDB資料庫——實時流資料處理的先鋒資料庫
- 資料處理
- 使用記憶體NewSQL資料平臺來處理實時資料流的三個好處記憶體SQL
- java處理流 和節點流(在位元組流和字元流中,又分為處理流和節點流)Java字元
- 資料預處理
- javascript - 資料處理JavaScript
- Excel 資料處理Excel
- 海量資料處理
- Panda資料處理
- 在 React 中處理資料流問題的一些思考React
- 基於 RocketMQ Connect 構建資料流轉處理平臺MQ
- 處理百萬級以上的資料處理
- 菜鳥學習筆記:Java提升篇6(IO流2——資料型別處理流、列印流、隨機流)筆記Java資料型別隨機
- 直播預告 | 時序資料處理的雲端利器:TDengine Cloud 詳解與演示Cloud
- 頂尖調查記者的資料處理與視覺化利器推薦視覺化
- 資料清洗和資料處理
- 資料預處理-資料清理
- 資料分析--資料預處理
- 什麼是流處理
- Go Web 路由處理利器 gorilla/mux 庫GoWeb路由UX
- 資料量越發龐大怎麼辦?新一代資料處理利器Greenplum來助攻
- 從資料流角度管窺 Moya 的實現(二):處理響應
- 使用RabbitMQ訊息佇列來處理大規模的資料流MQ佇列
- 三種大資料流處理框架選擇比較:Apache Kafka流、Apache Spark流和Apache Flink - quora大資料框架ApacheKafkaSpark