透過內建的 pprof 工具來分析 Golang 發生的 memory leak

前言 某天下午,公司的 cronjob daemon 無預警的被 GCP OOM Kill 了,且程式碼沒有看出明顯的原因。 根據過去的經驗,local 開發時會使用 go tool pprof 來分析 CPU profile 或是 memory 與 trace 的問題,詳細可以參考 Go 官方文件。 由於我們的程式碼是一個基於 gin 的 http service,因此可以使用 gin 提供的 pprof 來快速建立 endpoint。 gin pprof gin 的 pprof package 提供了數個基於 net/http/pprof 的 endpoint,可以分別為: /: 基本的 pprof 的 static page,可以分析 CPU 與 memory 的問題。 /cmdline: 分析 command line 的問題。 /profile: 分析 CPU profile 的問題,可以透過 query string 來指定 CPU profile 的 duration。 /symbol: 分析 symbol table 的問題。 /trace: 分析 trace 的問題。 /goroutine: 分析 goroutine 的問題,這也是本文中重點查看的 endpoint。 /heap: 分析 heap 的問題。 可以在 http server 中加入以下程式碼來啟用 pprof:...

2024-08-17 · 3 min · 441 words

基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作

在工作上有一個需求是需要做一些 OLAP,原訂計畫是使用 Google Looker(ver. Google Cloud Core),礙於量小不符合經濟效益,決定用 Grafana 這個較熟悉的開源套件來幫助我們做視覺化的處理。 這篇文章的範例可以在 omegaatt36/grafana-embed-example 中找到所有的 source code 我的 Use Case 為已經有一組 SHA512 產生的 Key,以下的內容為使用 HS512 進行簽名與認證。 流程 sequenceDiagram autonumber participant U as User participant B as Browser participant I as iframe participant S as Server participant G as Grafana rect rgb(236,239,244) U->>B: Open Web Page B->>S: Request JWT Token S->>B: Return JWT Token B->>I: Load iframe I->>G: Request Dashboard with JWT Token G->>I: Return Dashboard I->>B: Display Dashboard in iframe B->>U: Show Dashboard end Grafana 配置 主要是針對 grafana....

2024-06-10 · 5 min · 959 words

用 testcontainers 在本地開發 Go 應用程式

介紹 使用 testcontainers 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下,模擬應用程式在實際生產環境中的運行狀況。 安裝 testcontainers 在 Go 專案中,我們可以通過以下指令來導入 testcontainers go get github.com/testcontainers/testcontainers-go 透過 Redis 實踐一個 rate limiter package user type Limiter struct { client *redis.Client limit int limitPeriod time.Duration // 1 hour for limitPeriod counterWindow time.Duration // 1 minute for example, 1/60 of the period } func NewLimiter(client *redis.Client, limit int, period, expiry time.Duration) *Limiter { return &Limiter{ client: client, limit: limit, limitPeriod: period, counterWindow: expiry, } } func (r *Limiter) AllowRequest(ctx context....

2024-05-19 · 3 min · 474 words

如何利用 Open Policy Agent 配合 Golang 建構彈性的 RBAC 模組

RBAC 概念簡介 在我們探討如何利用 Open Policy Agent (以下簡稱 OPA) 和 Golang 建立一個彈性的 RBAC 模組之前,先讓我們來了解一下 RBAC的基本概念。 RBAC(Role-Based Access Control,基於角色的訪問控制)是一種廣泛應用的訪問控制策略,在軟體安全性領域尤為重要。其核心思想是將系統訪問權限與用戶的角色(職位、責任或職務)關聯起來,而不是直接與個別用戶關聯。這意味著訪問權限被捆綁到角色上,然後將用戶分配給這些角色。舉個例子,一個「管理員」角色可能有權訪問系統的所有資源,而「員工」角色則只能訪問特定部分的資源。 RBAC 的主要優勢在於其靈活性和簡化的權限管理。當需要變更權限時,只需修改角色的訪問權限,而不需要為每個用戶單獨設定。這不僅使權限管理更為高效,也減少了錯誤配置的可能性,提高了整體的系統安全性。 在實踐中,RBAC 允許創建精細且靈活的策略,以滿足複雜的商業和安全需求。無論是大型企業還是小型團隊,RBAC 都提供了一個可靠的框架,來確保正確的用戶擁有適當的訪問權限,從而保護關鍵資源免受未授權訪問。 可以參考 Cloudflare 的文章,簡單來說就是什麼「角色」能夠對什麼「資源」做什麼「操作」。 Open Policy Agent (OPA) 介紹 OPA 是一個「Strategy as Code」的開源專案,專門設計用於統一地管理和執行跨不同系統的策略。它不僅提供了一個高級的策略語言——Rego,還支援將策略作為代碼與應用程式的其他部分一同存儲、版本控制和部署。OPA 的這種設計使其能夠輕鬆集成到微服務、Kubernetes、CI/CD 管道、API 網關等多種環境中。顯著特點是其策略的編寫方式。Rego 是一種專門為策略和規則定制的查詢語言,它使開發者能夠以聲明式方式描述策略和規則,從而確保這些策略既容易理解又易於維護。這對於建立複雜的 RBAC 系統尤為重要,因為它允許策略的靈活性和可擴展性,同時又保持了清晰和易於審查的結構。 我們可以利用 OPA 提供的 API 來評估和執行這些策略。這意味著開發者可以在 Golang 程式碼中直接嵌入策略判斷的邏輯,從而實現動態、細粒度的訪問控制。這種方法的一個優點是,它支援在 runtime 動態更新策略,或是編譯進 binary,從而提供更大的靈活性和即時性。 整合 OPA 與 Golang 透過官方案例來了解如何使用 參考了 OPA 官方的 rbac 章節,並加以修改。使用最簡單的例子: admin can read user bob is admin ------------------- bob can read user 轉化成 RBAC 模型即為:...

2024-04-04 · 4 min · 724 words

Golang 1.22 中 http routing 的改進

Golang 作為一個偏向 server 應用的程式語言,一般的 web server 並不會直接使用原生的 package net/http,而更多的使用 gin-gonic/gin 或是 gorilla/mux,後來也有 labstack/echo 以及 go-chi/chi 等等選擇,在效能、輕量、好維護、好擴充中,都能找到對應的 third party package,其中的原因不外乎是原生的 package 提供的功能過於簡潔。 好在 1.22 中,官方改進了 net/http 中對於多工器、路由,甚至出了一篇部落格,現在更可以「大膽的」直接使用 standard library。 Path Parameter 若要將應用的 Web API 定義成 RESTful,我們會使用 /資源/{資源唯一識別符}/子資源/{子資源唯一識別符} 來定義路徑。假如要獲取一個使用者的訂單,則會使用 GET /users/1/orders 來獲取。在 1.22 以前,我們只能定義到 /users,再自行解析往後的 path: http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { subPath := strings.TrimPrefix(req.URL.Path, "/users/") if len(subPath) == 0 { xxx } else { ooo } ... }) 而在 1.22 中新增了 net....

2024-03-31 · 3 min · 469 words

Golang 隱式記憶體別名 Implicit Memory Aliasing 與其檢測方法

在使用 Golang 時,我們可能會遇到一種稱為隱式記憶體別名(Implicit Memory Aliasing)的問題。這篇文章將探討這個問題以及如何使用工具和語言特性來解決它。 隱式記憶體別名的問題 隱式記憶體別名主要發生在 range 語句中。當使用 range 對一個切片 slice 或映射 map 進行迭代時,Iterator 在每次迭代中並不是唯一的實例。這可能導致意外的行為,特別是在並發環境或當迭代變量被指針引用時。 slice with Implicit Memory Aliasing 下面示例可能是基本的 golang 面試題,會問你迭代後的 pointers 內的 Name 為何 答案是 Joe Joe Joe package main import "fmt" type Person struct { Name string Gender string } func main() { persons := []Person{ {Name: "John", Gender: "M"}, {Name: "Jane", Gender: "F"}, {Name: "Joe", Gender: "X"}, } pointers := make([]*string, len(persons)) for index, person := range persons { fmt....

2023-11-12 · 2 min · 368 words

golang 樂觀鎖、悲觀鎖 學習筆記與實驗

介紹 改變數值的三個步驟 取出 修改 保存 但這三者間的時間差在不同 process、不同 thread、不同 corutine/goroutine 中會造成競爭危害(race condition)。 可以使用多種發法確保並行(concurrency)處理時保持資料的一致性,這裡介紹的是最常使用的悲觀鎖與樂觀鎖。 悲觀鎖: 總可能發生問題 lock (1) 取值 (2) 修改 (3) 保存 unlock 樂觀鎖: 不會總是發生問題 (1) 取值 if *addr == old { (2) 修改 (3) 保存 return true } return false 悲觀鎖 golang 中主要使用 sync.Mutex 作為悲觀鎖,看似會阻塞住其他 goroutines,但其實 sync.Mutex 中也使用到了 CAS。 sync.Mutex 中有一個 int32 的 state 與 uint32 的 sema(semaphore) type Mutex struct { state int32 sema uint32 } const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexStarving mutexWaiterShift = iota starvationThresholdNs = 1e6 ) func (m *Mutex) Lock() { if atomic....

2023-05-14 · 3 min · 503 words

golang 中使用 Line LIFF 實作 Single Sign-On

文章中的程式碼放在https://github.com/omegaatt36/line-verify-id-token中。 Requirements Line Login Channel channel id channel secret Line LIFF APP 用 liff.getIDToken() 獲取 ID Token 在現代網站中,單一登錄 (Single Sign-On, SSO) 已經成為了一個普遍存在的功能,它能夠讓使用者在不同的應用程式和服務之間自動地登錄,而不需要再輸入帳號和密碼。這樣可以方便使用者的使用,並且也能夠增加安全性,減少帳號密碼被盜用的風險。 我們將使用 Golang 語言來實作單一登錄功能,並且使用 Line LIFF 來進行身份驗證。在此之前,我們需要先了解幾個概念。 Line LIFF (Line Front-end Framework) 是一個由 Line 提供的 Web 應用程式框架,開發者可以使用它來建立 Line 的客戶端應用程式。使用 Line LIFF 開發的應用程式可以在 Line 客戶端中被直接執行,而不需要額外安裝或下載。此外,Line LIFF 還提供了一些功能,例如使用者的身份驗證、分享資料等等。 JWT (JSON Web Token) 則是一種開放標準,用於在不同系統之間安全地傳輸訊息。它通常用於認證和授權,因為它可以確保傳輸的訊息是可信的,而且在傳輸過程中不會被竄改。 我們可以使用 Line 的 Verify API,同時也可以選擇後端自己驗證,解出 jwt 中的資訊。 我們的目的是拿到每個使用者在 Line 的 UserID,做為身分識別。於是根據 Line 提供的 JWT 欄位對應,定義一個結構用來存放驗證後的資訊。 // DecodedIDToken defines decoded payload by id token....

2023-04-22 · 3 min · 571 words

跟風寫了個使用 OpenAI API 的 Telegram Chat Bot

前言 放在最前面 Github repo 原本打算使用別人寫好的 bot,免費的版本均是透過儲存 cookies 的方式,直接透過 ChatGPT 網頁建立新的 「Chat」。好景不常,OpenAI 馬上把登入頁面到聊天頁面中間安插了類似 reCaptcha,來驗證是否為真人。看到有好心人提供了 go 版本的 OpenAi API,就打算自己寫一寫,順便當作 《Clean Architecture》 的讀後作業。 依賴注入 架構圖待補 我需要對 OpenAI 詢問,並用 stream(逐字)的方式回給我,也需要對 Telegram 逐字回答。 在這個 8931bf 版本中,很明確定義對於 OpenAI 與 Telegram 的 usecase chatgpt_usecase.go // ChatGPTUseCase defines ChatGPT send question use case. type ChatGPTUseCase interface { Stream(ctx context.Context, question string) (<-chan string, <-chan error) } telegram_usecase.go // TelegramUseCase defines telegram send message use case. type TelegramUseCase interface { SendAsLiveOutput(chatID int64, feed <-chan string) error } 在 *_repository....

2023-03-17 · 2 min · 242 words

gin 搭配 html/template 包實現動態生成 HTML 文件

起因 在網站註冊流程中,若是用信箱驗證,網站會寄送一封驗證信到指定的電子信箱。信中可能含有 verify token 或是直接是寫好的 verify URL。至於實作方面我們可以透過 Go 1.16 推出的 Embedding Files 搭配 html/template,實現動態生成 HTML 信件,用以寄送至指定信箱。 本篇內容的詳細程式碼可以到 github 頁面查看。 實作 資料夾結構: project/ ├─ templates/ │ ├─ template.go │ ├─ success.tmpl │ ├─ verify.tmpl ├─ main.go templates project/templates/template.go 在包內宣告私有變數,透過 Embedding Files 讀出目錄內的所有檔案。 package template //go:embed * var f embed.FS 宣告需要被轉換成 HTML 模板的檔案名 TemplateName,可以用 go-enum 來自動生成變數。並將其註冊進陣列 _TemplateNameNames 內。 package template // ENUM( // success.tmpl // verify.tmpl // ) type TemplateName string func (x TemplateName) String() string { return string(x) } var _TemplateNameNames = []string{ string(TemplateNameVerifyTmpl), string(TemplateNameSuccessTmpl), } // TemplateNameNames returns a list of possible string values of TemplateName....

2023-01-26 · 2 min · 344 words