<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Tech Share on Bloggings</title>
        <link>https://theoutsidelaine.com/categories/tech-share/</link>
        <description>Recent content in Tech Share on Bloggings</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sun, 02 Jun 2024 07:00:00 +0000</lastBuildDate><atom:link href="https://theoutsidelaine.com/categories/tech-share/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Use Go to develop a price increaser cronjob in a Javascript project</title>
        <link>https://theoutsidelaine.com/p/go-cronjob-price-stealthy-increaser/</link>
        <pubDate>Sun, 02 Jun 2024 07:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/go-cronjob-price-stealthy-increaser/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/go-cronjob-price-stealthy-increaser/cover.png" alt="Featured image of post Use Go to develop a price increaser cronjob in a Javascript project" /&gt;&lt;p&gt;最近開始學 Go，常聽人說最好的學習方法就是直接 build 一個簡單的小 feature，於是乎便想到了在我原本的 js side project 中建構一個簡單的 go cronjob 的方法。以下紀錄了這個小專案的背景、撰寫，與運行方式。&lt;/p&gt;
&lt;h2 id=&#34;1-background&#34;&gt;1. Background&lt;/h2&gt;
&lt;p&gt;根據彭博社報導，全世界近期受到通貨膨脹的衝擊，尤其是美國面臨了嚴峻的物價指數上漲，根據美國勞工統計局的數據分析，牛絞肉、薯片等基本物品如今的平均價格高於 Covid-19 前水準、汽油價格再次上漲，而電費等日常必需品的成本仍然維持高價位。&lt;/p&gt;
&lt;p&gt;有鑑於此，台灣電商 &lt;a class=&#34;link&#34; href=&#34;https://github.com/EHsieh0212/Synoptic&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Synoptic&lt;/a&gt; 為了可以跟上經濟趨勢、降低銷貨成本，因此希望趁著消費者分神時提高商品價格。方式即是透過打造一個 cronjob, 每分鐘偷偷調高各類商品的價格 1 塊錢。&lt;/p&gt;
&lt;h2 id=&#34;2-method&#34;&gt;2. Method&lt;/h2&gt;
&lt;h3 id=&#34;go-module-in-general&#34;&gt;Go module in general&lt;/h3&gt;
&lt;p&gt;Go 的 repo 通常會由一個 module 與多個 packages 組成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;module 在 Go 1.11 引入，用於管理多個 packages 與其依賴關係，並支援版本控制，存在一個 go.mod 紀錄版本、依賴項、替換規則等等。&lt;/li&gt;
&lt;li&gt;package 則是一組相關聯的代碼，用於代碼組織封裝與重新利用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假設有一個 repo 架構如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;myapp/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── utils/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── util.go
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── models/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── model.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可知：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;go.mod 文件位於 myapp 目錄中，定義了 &amp;ldquo;example.com/myapp&amp;rdquo; 這個 module。&lt;/li&gt;
&lt;li&gt;main.go、utils/util.go 和 models/model.go 都屬於同一個 module &amp;ldquo;example.com/myapp&amp;rdquo;。&lt;/li&gt;
&lt;li&gt;utils 和 models 是 package，但它們不需要各自擁有 go.mod 文件。它們共享位於根目錄的 go.mod 文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;file-structure&#34;&gt;File Structure&lt;/h3&gt;
&lt;p&gt;先 top-down 的展示這個小專案的檔案架構：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── server/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── cronjob/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   └── product-price-stealthy-increaser/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       ├── .env
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       ├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       ├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │       └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── services/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   └── prices.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── routes/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        └── priceRouter.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;init-a-go-module&#34;&gt;Init a Go module&lt;/h3&gt;
&lt;p&gt;執行以下指令，init 我們的專案：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go mod init product-price-stealthy-increaser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;這時便會產出 &lt;code&gt;go.mod&lt;/code&gt; 檔如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-v&#34; data-lang=&#34;v&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;module&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;product&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;price&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;stealthy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;increaser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.22.3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;install-related-packages&#34;&gt;Install related packages&lt;/h3&gt;
&lt;p&gt;在這個專案中，我們需要額外兩個 packages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;godotenv&lt;/code&gt;: 用於讀取寫於 &lt;code&gt;.env&lt;/code&gt; 的環境變數&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cron&lt;/code&gt;: 用於執行 cronjob&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;執行安裝指令如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go get -u github.com/joho/godotenv
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go get -u github.com/robfig/cron/v3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此時安裝內容便會被記錄於 &lt;code&gt;go.sum&lt;/code&gt; 檔案中。每個安裝的 packages 會有兩個 hash value, 分別確保直接下載與紀錄於 module 的依賴是否可以通過校驗。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-u&lt;/code&gt; flag 代表在安裝時，同時檢查是否有缺漏沒有安裝到的 dependencies，有的話就安裝。但他不會更新既有的 dependencies。&lt;/p&gt;
&lt;h3 id=&#34;maingo&#34;&gt;main.go&lt;/h3&gt;
&lt;p&gt;main.go 算是 module 的主要入口點。主要的檔案內容如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在 main.go 中，我們需要兩個函數： incrementProductPrices(), 與 main()。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;incrementProductPrices(): 負責與 db 互動，將每項商品的價格調漲一塊。&lt;/li&gt;
&lt;li&gt;main(): 載入環境變數，並設定 incrementProductPrices() 每分鐘執行一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;incrementproductprices&#34;&gt;incrementProductPrices()&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;func incrementProductPrices() {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	nodeServerURL := os.Getenv(&amp;#34;BACKEND_URL&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	resp, err := http.Post(nodeServerURL+&amp;#34;/increasePriceToFightInflation&amp;#34;, &amp;#34;application/json&amp;#34;, nil)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if err != nil {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		log.Fatalf(&amp;#34;Failed to increate price product: %v&amp;#34;, err)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	defer resp.Body.Close()
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	body, err := io.ReadAll(resp.Body)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if err != nil {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        log.Fatalf(&amp;#34;Failed to read response body: %v&amp;#34;, err)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if resp.StatusCode != http.StatusOK {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        log.Fatalf(&amp;#34;Failed to update prices, status code: %d&amp;#34;, resp.StatusCode)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	maxBodyLength := 500
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	bodyToPrint := string(body)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if len(bodyToPrint) &amp;gt; maxBodyLength {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        bodyToPrint = bodyToPrint[:maxBodyLength] + &amp;#34;...&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	log.Printf(&amp;#34;Response Body: %v&amp;#34;, bodyToPrint)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	log.Println(&amp;#34;Product prices incremented successfully.&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;這個函數主要有以下幾個重點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;這個 Go cronjob 會透過打 api 的方式，讓 PriceRepository (written in Javascript) 執行每個商品價格加ㄧ的動作。POST api 裡面連結到 js price service。這麼做有幾個優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;讓 Go cronjob 調用 Javascript repo 的資源。&lt;/li&gt;
&lt;li&gt;我們可以不用在 cronjob 中撰寫 db crud 相關指令，讓程式層級切割的更明確。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;defer response.Body.Close()&lt;/code&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;這個寫法通常見於 HTTP request / response, 是 Go 語言中的一種用法，用於確保在 HTTP 請求完成後關閉response body，以釋放相關資源。&lt;/li&gt;
&lt;li&gt;&amp;ldquo;defer&amp;rdquo; 確保了函數無論在什麼時候結束、如何結束，資源都可以被釋放。&lt;/li&gt;
&lt;li&gt;如果不寫 defer, 僅僅在最後加上 response.Body.Close(), 可能導致某些 error handling 出口忘記釋放資源，讓資源被洩漏。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;io.ReadAll()&lt;/code&gt; 讓 response 成為一個 list：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：有時候 response 長度會太長，這時我們可以指定最長 logging 的長度，再把 response 讀成一個 list, 借此讓 response 可以好看一點。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 &lt;code&gt;log&lt;/code&gt; 而非 &lt;code&gt;fmt&lt;/code&gt;: 讓輸入的狀態擁有時間戳、格式化輸出選擇。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;main&#34;&gt;main()&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;func main() {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if err := godotenv.Load(); err != nil {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		log.Fatalf(&amp;#34;Error loading .env file: %v&amp;#34;, err)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	schedule := cron.New(cron.WithSeconds())
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	_, err := schedule.AddFunc(&amp;#34;0 * * * * *&amp;#34;, incrementProductPrices)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	if err != nil {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		log.Fatalf(&amp;#34;Error scheduling cron job: %v&amp;#34;, err)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	schedule.Start()
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	select {}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;這個函數主要有以下幾個重點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;載入環境變數，並在無法順利載入時 log.Fatalf()&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 main() 而非 incrementProductPrices() 調用環境變數，可以確保調用的行為只會發生一次，降低不必要的開銷。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cron.New(cron.WithSeconds())&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;創建一個 cronjob, 並將精細度由預設的分鐘級調整為秒級&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;schedule.AddFunc(&amp;quot;0 * * * * *&amp;quot;, incrementProductPrices)&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;將 &amp;ldquo;incrementProductPrices&amp;rdquo; 添加至 cronjob中，並設定每小時的第 0 秒（＝每分鐘）執行一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;schedule.Start()&lt;/code&gt;: 開始 cronjob&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;select {}&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go 語言用法，用於創建一個 infinite loop, 讓程式無限執行下去。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，當執行 &lt;code&gt;go run main.go&lt;/code&gt;時，這個 cronjob 就會固定於每分鐘將價格表上的所有商品價格調漲一塊了。&lt;/p&gt;
&lt;h2 id=&#34;3-conclusion&#34;&gt;3. Conclusion&lt;/h2&gt;
&lt;p&gt;很好玩！之後可以看看怎麼結合 Render 的 job 功能，讓 cronjob 的運行腳本可以寫得更簡便、更著重於 service 本身就好。之後 Synoptic 也會因應世界上發生的大小事，再次用 Go 拯救公司營運（Ｘ&lt;/p&gt;
</description>
        </item>
        <item>
        <title>System Design (IV)</title>
        <link>https://theoutsidelaine.com/p/system-design-4/</link>
        <pubDate>Sat, 18 May 2024 20:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/system-design-4/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/system-design-4/cover.png" alt="Featured image of post System Design (IV)" /&gt;&lt;p&gt;這禮拜繼續讀 &amp;lt;System Design Interview- An insider&amp;rsquo;s guide&amp;gt; 第四章。&lt;/p&gt;
&lt;h1 id=&#34;chapter-4-設計-rate-limiter&#34;&gt;Chapter 4: 設計 Rate Limiter&lt;/h1&gt;
&lt;p&gt;這章開始作者用實戰的方法帶大家了解，每一種不同的系統的特性是什麼、以及要設計這樣的元件可以怎麼去做思考。&lt;/p&gt;
&lt;p&gt;首先第一個設計的元件就是 Rate Limiter (網路限速器、限流器)。如果面試時，被要求設計一個 rate limiter，你可以怎麼做？&lt;/p&gt;
&lt;p&gt;所謂的 rate limiter簡單來說，就是一個可以限制 client 在指定時段內發送 request 數量的元件。比如：user 每秒可以發幾則貼文、user 每天只能領幾次的獎勵。他的優點是可以防止 server overload, 進而優化資源分配、降低server成本、並避免 dos 攻擊。&lt;/p&gt;
&lt;h2 id=&#34;第一步驟瞭解問題並確立設計的範圍&#34;&gt;第一步驟：瞭解問題，並確立設計的範圍&lt;/h2&gt;
&lt;p&gt;可以問的問題有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;哪一種 rate limiter: client side or server side?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;這個問題屬於明知故問型的提問，因為 client side rate limiter 可控性不高、可靠性也不高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;是否要根據 ip, user_id etc 去追蹤 api request?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;系統的規模大小？for 大公司 or 小新創?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;系統是否需要運作於分散式環境？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;這個 rate limiter 需要做成一個單獨的服務、或是實作於應用程式中？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;是否需要通知受到限制的 user?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根據回答，可能可以匯總的系統需求如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;必須要準確限制 request -&amp;gt; (：演算法選擇)&lt;/li&gt;
&lt;li&gt;必須要盡可能使用少一點記憶體 -&amp;gt; (：演算法選擇)&lt;/li&gt;
&lt;li&gt;因為系統運作於分散式環境，所以採用分散式做法：多 server 共用一個 rate limiter -&amp;gt; (：rate limiter 實作位置選擇)&lt;/li&gt;
&lt;li&gt;others: 需要高容錯、異常處理&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;第二步驟提出高階設計並取得認可&#34;&gt;第二步驟：提出高階設計，並取得認可&lt;/h2&gt;
&lt;p&gt;這邊主要要思考兩點：(1) rate limiter 施作位置 (2) rate limiter 演算法選擇&lt;/p&gt;
&lt;h3 id=&#34;1-rate-limiter-施作位置&#34;&gt;1. rate limiter 施作位置&lt;/h3&gt;
&lt;p&gt;這邊主要考量的是：放置於api server, 或是施作於 api gateway（microservice 中的一個中介元件，用途廣泛，功能包含身份驗證、白名單etc）.&lt;/p&gt;
&lt;p&gt;書中列出幾個可以拿出來討論的點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;目前公司是否已經使用微服務架構、並且此架構已用到 api gateway? 有的話，或許可以考慮直接施作於 api gateway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所選用的演算法，是否可以於 api gateway中選用，或是需要自製於 server 端？&lt;/p&gt;
&lt;p&gt;-&amp;gt; 2-1. 目前公司使用的程式語言，是否支援實作 server 端 rate limiter?&lt;/p&gt;
&lt;p&gt;-&amp;gt; 2-2. 目前公司提供的時間、成本，是否支援開發 server 端 rate limiter?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;2-rate-limiter-演算法選擇&#34;&gt;2. rate limiter 演算法選擇&lt;/h3&gt;
&lt;p&gt;有五個比較常見的演算法如下：&lt;/p&gt;
&lt;h4 id=&#34;1-token-bucket&#34;&gt;(1) Token Bucket&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;步驟：有兩個控制變數，分別是 bucket 容量、token 填入速度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每當一個 request 進來，系統檢查 bucket
&lt;ul&gt;
&lt;li&gt;如果 token 數量足夠，則消耗一個 token 給該 request，並使 request 通過。&lt;/li&gt;
&lt;li&gt;如果 token 數量不足夠，則不使 request 通過。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;固定一個時間之內 refill bucket&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一個系統可以設計一或多個 bucket&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多個 bucket 的狀況：要針對每一個 api 做比較嚴格的 rate limit&lt;/li&gt;
&lt;li&gt;一個 bucket 的狀況：系統很鬆，每秒可以接收極大量的 total requests&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;容易實作&lt;/li&gt;
&lt;li&gt;bucket 大小固定、有限，因此有比較好的 memory usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可以允許短時間內的流量爆炸&lt;/strong&gt;：它不限定某個時間只能有幾個request, 只要 bucket 裡面還有refilled tokens, 就可以通過 request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;書中提到說要 tune 這兩個參數不是一件容易的事&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;應用該演算法的公司：Amazon, Stripe&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;2-leaking-bucket&#34;&gt;(2) Leaking Bucket&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;步驟：有兩個控制變數，分別是 queue 容量、request 處理速度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每當一個 request 進來，系統檢查 queue
&lt;ul&gt;
&lt;li&gt;如果 queue not full, 便把 request 加入 queue&lt;/li&gt;
&lt;li&gt;如果 queue full, 便丟棄 request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;固定一個 request process rate&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;queue 大小固定、有限，因此有比較好的 memory usage&lt;/li&gt;
&lt;li&gt;request process rate 是固定速率，所以適合需要穩定 outflow rate 、不會有突然流量爆炸的狀況。&lt;/li&gt;
&lt;li&gt;(somehow 書中沒有提到這個作法是容易實作的)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果流量爆炸，新 request 處理效率會變很差&lt;/li&gt;
&lt;li&gt;書中提到說要 tune 這兩個參數也不是一件容易的事&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;應用該演算法的公司：Shopify&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;3-fixed-window-counter&#34;&gt;(3) Fixed Window Counter&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;步驟：每個固定的 time slot (eg: 1分鐘、5分鐘) 有固定的 request capacity 與一個 counter&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每當一個 request 進來，系統
&lt;ul&gt;
&lt;li&gt;counter ++&lt;/li&gt;
&lt;li&gt;如果 ++ 之後 capacity 超過，則丟棄 request&lt;/li&gt;
&lt;li&gt;如果 ++ 之後 capacity 未超過，則通過 request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;重要特性（問題）：
&lt;ul&gt;
&lt;li&gt;兩個 time slot request 如果恰恰好是 capacity 最大值（意即極高需求狀況），則中間切分點之 time slot(滾動視窗) 會允許了 capacity 兩倍的 request進來，導致超乎 loading 的需求被處理了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;容易實作&lt;/li&gt;
&lt;li&gt;request capacity 固定、有限，因此有比較好的 memory usage&lt;/li&gt;
&lt;li&gt;可以被 Redis 支援&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺點：流量激增會有超過 loading 的需求被處理&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;4-sliding-window-log&#34;&gt;(4) Sliding Window Log&lt;/h4&gt;
&lt;p&gt;這是一個有趣的演算法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;步驟：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每當一個 request 進來，系統
&lt;ul&gt;
&lt;li&gt;將這個 request的 time stamp 加入日誌中&lt;/li&gt;
&lt;li&gt;檢查目前日誌中、「新 request 的一個 time slot」以前，是否還存有其他老request的 time stamps
&lt;ul&gt;
&lt;li&gt;有則刪除&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;檢查目前日誌中，time stamp capacity 是否超標
&lt;ul&gt;
&lt;li&gt;無則 approve request&lt;/li&gt;
&lt;li&gt;有則 reject request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;限速效果十分準確：很可以確保滾動的 time slot 也可以穩定的不超標&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必須要額外花很多的 memory 紀錄每一個 request 的 time stamp，不會因為 request 被丟棄而消失&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;5-sliding-window-counter&#34;&gt;(5) Sliding Window Counter&lt;/h4&gt;
&lt;p&gt;這是一個融合 Fixed Window Counter 和 Sliding Windoe Log 的做法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;它主要的目的是解決 Fixed Window Counter 遇到的問題，透過施作一個「百分比公式」:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;滾動視窗的 total request = 目前視窗 request + 前一視窗 request * 前一視窗佔滾動視窗的百分比。&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不會讓滾動視窗突有超高流量出現&lt;/li&gt;
&lt;li&gt;因此有比較好的 memory usage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺點：限速效果不會到非常精確（不過也不會到非常不精確，根據 Cloudflare實驗結果錯誤率是 0.003% in 4b requests）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;3-rate-limiter-資料儲存地點&#34;&gt;3. rate limiter 資料儲存地點&lt;/h3&gt;
&lt;p&gt;通常會把資料放在 cache: (1)存取速度快 (2)支援過期策略。&lt;/p&gt;
&lt;p&gt;Redis 是一個常被施用的元件，如果使用 Fixed Window Counter，可以用到他的 INCR(counter ++)指令與 EXPIRE(使 counter 過期)指令。&lt;/p&gt;
&lt;h2 id=&#34;第三步驟深入設計&#34;&gt;第三步驟：深入設計&lt;/h2&gt;
&lt;p&gt;根據所設計的 rate limiter 架構，延伸的問題通常有以下兩者：&lt;/p&gt;
&lt;h3 id=&#34;1-basic-question-如果超出-rate-limit&#34;&gt;1. basic question: 如果超出 rate limit&lt;/h3&gt;
&lt;p&gt;solution: API 回傳&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;429 error response&lt;/li&gt;
&lt;li&gt;X-Ratelimit-After&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;補充: rate limiter 常傳送的 HTTP headers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;X-Ratelimit-Remaing: window 內剩餘的可用 request數&lt;/li&gt;
&lt;li&gt;X-Ratelimit-Limit: 一個 window 總共可用的 request數&lt;/li&gt;
&lt;li&gt;X-Ratelimit-After: 當獲得 429 error, 多久之後可以再次重新發送 request&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;2-advanced-questions-1-當分散式系統有並行的-request-如何避免-race-condition&#34;&gt;2. advanced questions 1: 當分散式系統有並行的 request, 如何避免 race condition&lt;/h3&gt;
&lt;p&gt;solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lock&lt;/li&gt;
&lt;li&gt;Lua script&lt;/li&gt;
&lt;li&gt;Redis ordered data structure&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;3-advanced-questions-2-當分散式系統有多個-rate-limiters-如何同步每個-rate-limiter-讓所有資料都存在於元件中使-rate-limiting可以正常運作&#34;&gt;3. advanced questions 2: 當分散式系統有多個 rate limiters, 如何同步每個 rate limiter, 讓所有資料都存在於元件中，使 rate limiting可以正常運作？&lt;/h3&gt;
&lt;p&gt;solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Utilize Redis as centralized data storage&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;4-advanced-questions-others-效能最佳化&#34;&gt;4. advanced questions others: 效能最佳化&lt;/h3&gt;
&lt;p&gt;solution:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;地理位置上，設置多資料中心&lt;/li&gt;
&lt;li&gt;使用 eventual consistency model 同步資料&lt;/li&gt;
&lt;li&gt;監控 rate limiter表現，隨時思考演算法選擇的恰當性&lt;/li&gt;
&lt;li&gt;rate limiter設置不同策略：硬限制、軟限制&lt;/li&gt;
&lt;li&gt;不僅在 HTTP 第七層實作，也可以往底下去實作 rate limiter. eg: Ip Table rate limiter.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;我覺得這章蠻有趣的，不僅初步認識與理解 rate limiter的實作概念，也知道要怎麼去討論一個 rate limiter 的設計方式。很好奇有哪些公司會選用 Sliding Window Log 這種很精細又高成本的算法的。除了算法的選擇上，如何做好 trade-off 不是一件容易的事情之外，要怎麼針對每個 follow up question 去提出適合的解決方法，比如說如何使用 Redis 解決 race condition 問題又是需要額外 survey的議題。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>System Design (III)</title>
        <link>https://theoutsidelaine.com/p/system-design-3/</link>
        <pubDate>Fri, 10 May 2024 08:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/system-design-3/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/system-design-3/cover.png" alt="Featured image of post System Design (III)" /&gt;&lt;p&gt;這禮拜繼續讀 &amp;lt;System Design Interview- An insider&amp;rsquo;s guide&amp;gt; 第三章。&lt;/p&gt;
&lt;h1 id=&#34;chapter-3-系統設計面試的框架&#34;&gt;Chapter 3: 系統設計面試的框架&lt;/h1&gt;
&lt;p&gt;這章要介紹的是，如何進行一個系統設計面試。這章應該是讀到目前為止讓我最有感的一章了吧。&lt;/p&gt;
&lt;p&gt;首先，他提到了一個重要的問題：&lt;strong&gt;「為什麼公司要考系統設計？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考系統設計的原因，不是因為公司想要找到一個可以設計出 Google 搜尋引擎的完美工程師，或是一個十分精通於設計的人才。相反的，一場成功的系統設計面試可以探測到面試者的很多面向：面試者可否有效的與面試官針對某個問題進行合作，提出一個可行的解決方案？在這其中，可以觀察到面試者的協作能力、溝通能力、承受壓力的能力、以建設性方法解決問題的能力。另外，還可以看看面試者是否有透露什麼危險的訊號：是否過度設計、展現固執的個性、或是狹隘的思維？&lt;/p&gt;
&lt;p&gt;因此，這章旨在提供一個簡單有效的框架，讓我們可以訓練自己做一個有效的問題解決者。以下，作者提出了四個步驟，並假設本次我們面對到的題目是「設計一個 news feed system」。&lt;/p&gt;
&lt;h2 id=&#34;第一步驟瞭解問題並確立設計的範圍&#34;&gt;第一步驟：瞭解問題，並確立設計的範圍&lt;/h2&gt;
&lt;p&gt;當拿到一個問題後，切記不要馬上提出自己的答案。必須先在心裡好好思考題目的目的、釐清條件，並彙整資訊。&lt;/p&gt;
&lt;p&gt;主要有兩個問題可以詢問。可以再針對這兩個問題的回覆，做更多細節上的詢問：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系統的核心功能有什麼？&lt;/li&gt;
&lt;li&gt;系統的規模大小？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是 news feed system, 可以先詢問產品最主要的功能為何，假設回覆是「發文」，接著便可以接著詢問發文的排列順序、是否僅包含文字或允許多媒體內容、read-write ratio、consistency requirement等等。接著再針對系統規模大小詢問，比如一天流量，然後接著可以詢問使用者最多的朋友數量等等。&lt;/p&gt;
&lt;h2 id=&#34;第二步驟提出高階設計並取得認可&#34;&gt;第二步驟：提出高階設計，並取得認可&lt;/h2&gt;
&lt;p&gt;接著，便是利用以上取得的資訊、做出初步設計的階段了。&lt;/p&gt;
&lt;p&gt;首先可以先用一個 box diagram，畫出主要 entity 以及其關聯。主要 entity 可能有：client side, web server, db, cache, CDN, message queue 等等。也可以註記主要的 api 有哪些。&lt;/p&gt;
&lt;p&gt;關於粗略估算的部分，作者有提到需針對提出的假說做一點估算，但讀書會的當周講者認為不太需要。我認為如果僅僅是要考驗面試者針對系統功能的規劃思路，或許估算的部分相較於決定哪些功能的使用與否確實看起來比較 minor 一點。&lt;/p&gt;
&lt;p&gt;最後，就是使用一個具體的功能/範例，示範一下你的系統會如何運作。&lt;/p&gt;
&lt;p&gt;這期間必須將面試官視為是你的 partner, 與他討論每一個環節，盡量取的他的回饋。&lt;/p&gt;
&lt;p&gt;如果是 news feed system, 可以針對「發布個人動態」去畫一個 diagram, 說明從 client side開始，如何經過 load balancer, 抵達 post service, 然後存取 post cache, post database 等等。&lt;/p&gt;
&lt;h2 id=&#34;第三步驟深入設計&#34;&gt;第三步驟：深入設計&lt;/h2&gt;
&lt;p&gt;第三步驟是一個比較 tricky 的環節。當你已經釐清需求、畫好藍圖之後，面試官可以延伸問你很多問題。諸如以下（內容為讀書會講者所整理）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;high level design: 製作 on demand new feed 的 trade-off 為何&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;技術細節：how to get friend list by graph database？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;潛在效能問題：what if the fan-out service in new feed system cannot process new post fast enough?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;envelop estimation (算數學環節)：how many RAMs are needed for caching all news feed?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;新需求的討論：what will happen if we need to edit page? delete page? how to do fraud detection?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第四步驟彙整總結&#34;&gt;第四步驟：彙整總結&lt;/h2&gt;
&lt;p&gt;這裡要特別注意的是，要留給自己 wrap up 你的方案的時間，不要花太多時間在細節、或是被面試官不小心帶走節奏。如果還有餘裕與時間，可以再額外討論的點有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;系統可能的瓶頸？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可能出錯的狀況？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以觀察哪些衡量指標？&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;系統面試環節既是考驗你對不同技術的理解，也考驗你如何在有限的時間之內，流暢地提出一個問題的解方。最主要的 takeaway 如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;沒有正確的答案、也沒有最好的答案，只有最適合的答案。不要背答案，盡量針對每一個問題去想一個合適的解決方法。&lt;/li&gt;
&lt;li&gt;把面試官當成你的隊友，跟他討論、溝通，切記不要一個人悶頭思考。時常取得面試官的 feedback。&lt;/li&gt;
&lt;li&gt;當卡住的時候，勇於跟面試官要求提示。&lt;/li&gt;
&lt;li&gt;永不放棄。&lt;/li&gt;
&lt;/ol&gt;
</description>
        </item>
        <item>
        <title>System Design (II)</title>
        <link>https://theoutsidelaine.com/p/system-design-2/</link>
        <pubDate>Thu, 02 May 2024 08:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/system-design-2/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/system-design-2/cover.png" alt="Featured image of post System Design (II)" /&gt;&lt;p&gt;這禮拜繼續讀 &amp;lt;System Design Interview- An insider&amp;rsquo;s guide&amp;gt; 第二章。這章很短，不過帶出一個很重要的概念－數字估算。&lt;/p&gt;
&lt;h1 id=&#34;chapter-2-粗略的估算&#34;&gt;Chapter 2: 粗略的估算&lt;/h1&gt;
&lt;p&gt;在了解系統設計的基礎架構思路後，下一步我們便需要了解，依照這個思路所架構的系統，它的&lt;strong&gt;能耐、效能&lt;/strong&gt;表現究竟如何。&lt;/p&gt;
&lt;p&gt;我們必須要先知道有哪些常見的系統效能指標，並且知道該怎麼粗略的去估算它們。在取得這項能力之前，這本書推薦了一些必須熟悉的基礎常識，這有助於我們在估算系統效能指標上可以更駕輕就熟。&lt;/p&gt;
&lt;p&gt;以下我就用 top-down 的方式來 recap 這些概念（會跟原文順序相反），並再補充一點細節。&lt;/p&gt;
&lt;h2 id=&#34;1-常見的系統效能指標&#34;&gt;1. 常見的系統效能指標&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;QPS（Query Per Second）:
&lt;ul&gt;
&lt;li&gt;描述&lt;strong&gt;資料庫&lt;/strong&gt;或是&lt;strong&gt;搜尋引擎&lt;/strong&gt;的每秒鐘查詢數量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;QPS 峰值:
&lt;ul&gt;
&lt;li&gt;一般情況下，QPS peek 會等於 &lt;strong&gt;QPS * 2&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;儲存空間：
&lt;ul&gt;
&lt;li&gt;儲存各種數據，包含document, media, logging等，的空間。會以 bytes 為單位。&lt;/li&gt;
&lt;li&gt;可能指特定檔案儲存空間，也可能指整體儲存空間。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;伺服器數量：
&lt;ul&gt;
&lt;li&gt;用於不同目的的伺服器總數量，包含Web server, DB server, document server, mail server 等。&lt;/li&gt;
&lt;li&gt;可能指特定類型伺服器數量，也可能指總共伺服器數量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;2-必須熟悉的基礎知識&#34;&gt;2. 必須熟悉的基礎知識&lt;/h2&gt;
&lt;h3 id=&#34;1-table-of-metrics&#34;&gt;(1) Table of metrics&lt;/h3&gt;
&lt;h4 id=&#34;quantity-related-metrics&#34;&gt;Quantity related metrics&lt;/h4&gt;
&lt;p&gt;在軟體領域，通常我們會用 2 的次方來定義這些單位（因為我們使用 byte 為單位存儲）；在電子通訊或是物理學領域，通常則會使用 10 的次方來定義這些單位。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;kilo: =  2&lt;sup&gt;10&lt;/sup&gt; ~= 10&lt;sup&gt;3&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;mega = 2&lt;sup&gt;20&lt;/sup&gt; ~= 10&lt;sup&gt;6&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;giga = 2&lt;sup&gt;30&lt;/sup&gt; ~= 10&lt;sup&gt;9&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;tera = 2&lt;sup&gt;40&lt;/sup&gt; ~= 10&lt;sup&gt;12&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;peta = 2&lt;sup&gt;50&lt;/sup&gt; ~= 10&lt;sup&gt;15&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;exa = 2&lt;sup&gt;60&lt;/sup&gt; ~= 10&lt;sup&gt;18&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;zetta = 2&lt;sup&gt;70&lt;/sup&gt; ~= 10&lt;sup&gt;21&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;yotta = 2&lt;sup&gt;80&lt;/sup&gt; ~= 10&lt;sup&gt;24&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;speed-related-metrics&#34;&gt;Speed related metrics&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;milli: = 10&lt;sup&gt;-3&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;micro: = 10&lt;sup&gt;-6&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;nano: = 10&lt;sup&gt;-9&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;pico: = 10&lt;sup&gt;-12&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;femto: = 10&lt;sup&gt;-15&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;atto: = 10&lt;sup&gt;-18&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;zepto: = 10&lt;sup&gt;-21&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;yocto: = 10&lt;sup&gt;-24&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在軟體領域中，由於容量通常以 byte 為儲存單位，因此容量單位習慣使用 2 為基底的乘數，方便二進制系統的溝通；而由於速度通常是看每秒多少個bit，因此習慣以 10 為基底的乘數，可以更直接簡單的反應單位時間內的傳輸位元數量。&lt;/p&gt;
&lt;h3 id=&#34;2-幾個延遲相關的數字&#34;&gt;(2) 幾個延遲相關的數字&lt;/h3&gt;
&lt;p&gt;書中介紹了 13 個常用的延遲相關數字，由 Google 的 Jeff Dean 在 2010 年提出：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time (ns)&lt;/th&gt;
&lt;th&gt;Time (us)&lt;/th&gt;
&lt;th&gt;Time (ms)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 cache reference&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch mispredict&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 cache reference&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;14x L1 cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutex lock/unlock&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main memory reference&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;20x L2 cache, 200x L1 cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compress 1K bytes with Zippy&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send 1K bytes over 1 Gbps network&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 4K randomly from SSD&lt;/td&gt;
&lt;td&gt;150,000&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;~1GB/sec SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from memory&lt;/td&gt;
&lt;td&gt;250,000&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Round trip within same datacenter&lt;/td&gt;
&lt;td&gt;500,000&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from SSD&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk seek&lt;/td&gt;
&lt;td&gt;10,000,000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;20x datacenter roundtrip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read 1 MB sequentially from disk&lt;/td&gt;
&lt;td&gt;20,000,000&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;80x memory, 20X SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send packet CA-&amp;gt;Netherlands-&amp;gt;CA&lt;/td&gt;
&lt;td&gt;150,000,000&lt;/td&gt;
&lt;td&gt;150,000&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;主要要記的就是這 13 個指標，另外書中有提到一些需要記得的延伸內容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;記憶體查詢速度快、disk 慢&lt;/li&gt;
&lt;li&gt;簡單壓縮法（zippy）速度很快&lt;/li&gt;
&lt;li&gt;(其他過於基本暫略)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;3-sla&#34;&gt;(3) SLA&lt;/h3&gt;
&lt;p&gt;SLA (Service Level Agreement, 又稱服務等級協議)，是 service provider（eg: AWS, Google）常使用的術語，定義了 service 提供的服務等級。&lt;/p&gt;
&lt;p&gt;SLA 通常會以 99.9% 為正常基準，小數點後的 9 越多，代表服務等級越好。甚至 9 的數量可以換算成系統停機時間。&lt;/p&gt;
&lt;p&gt;比如書中有提到，正常水準 99.9 %的每天停機時間為 14.4分鐘，99.9999%的每天停機時間為 86.4 毫秒。&lt;/p&gt;
&lt;h2 id=&#34;3-範例估算-twitter-的-qps&#34;&gt;3. 範例：估算 Twitter 的 QPS&lt;/h2&gt;
&lt;p&gt;實際在做系統效能指標估算時，必須謹記三點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把你的假設與思路寫下來：你要用哪些數字計算這些指標？記下他們，方便釐清也方便日後參考。&lt;/li&gt;
&lt;li&gt;標記單位：你使用的這些數字是什麼單位？寫下來，才不會搞混。&lt;/li&gt;
&lt;li&gt;善用近似法、四捨五入：盡量用簡單乾淨的數字做計算比較方便，不求精確程度。比如題目給說每秒 query 數是 197，可以近似為 200。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;假設系統 Twitter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每月有 3 億活躍用戶&lt;/li&gt;
&lt;li&gt;每天有 50% 的用戶使用 Twitter&lt;/li&gt;
&lt;li&gt;用戶平均每天發 2 則推文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要估算 QPS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;QPS = 每日推文總數 / 一天的秒數&lt;/li&gt;
&lt;li&gt;每日推文總數 = 3 * 10&lt;sup&gt;8&lt;/sup&gt; * 50% * 2 = 3 * 10&lt;sup&gt;8&lt;/sup&gt; 則&lt;/li&gt;
&lt;li&gt;推文 QPS = 3 * 10&lt;sup&gt;8&lt;/sup&gt; / (24 * 3600 秒) ~= 3500&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這邊要注意的是，每月 3 億用戶是假設每天流量都是 3 億，整個月都是如此。所以不用把 3 億除以30。&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;這章實際帶系統效能估算只有帶了 QPS 跟 儲存空間，感覺 server 數量也是一個很重要的指標，可能需要額外思考如何計算。&lt;/p&gt;
&lt;p&gt;不過透過這章的介紹，我也瞭解了必須熟記很多容量與速度單位的重要性，以及哪些效能指標是重要的，在估算時可以謹記三點讓自己估算時可以更清楚。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>System Design (I)</title>
        <link>https://theoutsidelaine.com/p/system-design-1/</link>
        <pubDate>Mon, 22 Apr 2024 08:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/system-design-1/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/system-design-1/cover.png" alt="Featured image of post System Design (I)" /&gt;&lt;p&gt;從二月準備正職面試以來，遇到了不只一家公司的面試需要考 system design，有鑒於自己在這方面的知識趨近於 0，剛好朋友分享一個開源社群每週三會定期舉辦一本 system design 書籍的讀書會，便想說藉此機會來讀這個領域有關的書，增加自己的知識量。&lt;/p&gt;
&lt;p&gt;這次要讀的書叫做 &lt;strong&gt;&amp;lt;內行人才知道的系統設計面試指南 System Design Interview- An insider&amp;rsquo;s guide&amp;gt;&lt;/strong&gt;，由 Alex Xu 撰寫，總共有 16 個 chapters，從一開始簡要的介紹 system 由小到大的擴展過程，一步步介紹系統內部元件，最後講解大型系統如 Youtube, Google 的設計思路。&lt;/p&gt;
&lt;p&gt;在這一系列文章中，我會逐章整理每一個章節的內容、並分享一點自己的想法。&lt;/p&gt;
&lt;h1 id=&#34;chapter-1-使用者人數----從零到百萬規模&#34;&gt;Chapter 1: 使用者人數 &amp;ndash; 從零到百萬規模&lt;/h1&gt;
&lt;p&gt;這本書的第一章節是 &amp;lt;使用者人數 &amp;ndash; 從零到百萬規模&amp;gt;，使用循序漸進的方法，帶我們了解系統是怎麼一步步擴大的，包含起點是什麼、怎麼疊加元件，進而長成一個可以承載高流量的系統。以下我把系統擴大的過程拆分為七個階段。&lt;/p&gt;
&lt;h2 id=&#34;phase-1-init-a-system-by-web-server--db-server&#34;&gt;Phase 1: Init a system by Web server &amp;amp; DB server&lt;/h2&gt;
&lt;p&gt;想要打造一個系統，要從哪裡開始？最簡單也是最重要的就是先架一個 Web server。這個 Web server 會與 Web browser user 或是 App user進行互動。&lt;/p&gt;
&lt;p&gt;接著，當使用者稍微開始增加後，我們想要將流量做簡單的分流管理，就會把 server 進一步拆成兩個server: (1) Web server, (2) Database server，如下圖。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/1.png&#34;
	width=&#34;914&#34;
	height=&#34;455&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/1_hu29593f92e61ae743c7e9a4e2fae03161_25902_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/1_hu29593f92e61ae743c7e9a4e2fae03161_25902_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;200&#34;
		data-flex-basis=&#34;482px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;從此開始，我們以這兩種 server 為基礎，逐步擴大系統的元件。&lt;/p&gt;
&lt;h2 id=&#34;phase-2-horizontal-scaling&#34;&gt;Phase 2: Horizontal scaling&lt;/h2&gt;
&lt;p&gt;接著，當使用者再逐漸開始增加，我們想要擴展系統，該怎麼做？首先，我們要選擇一種高彈性、低局限性的擴展策略：水平擴展（Horizontal Scaling）。垂直擴展的意思是升級 server內元件規格，這種方法有硬體設計上的限制；水平擴展的則是指使用多台 server來平均分擔流量的方法。&lt;/p&gt;
&lt;p&gt;在 Web server方面的水平擴展，我們可以使用增加多台server搭配負載平衡器（Load Balancer）的方法來達成目標，Load Balancer 會計算出最優的系統流量分配方法。而在 Database server上，我們可以透過使用資料庫複寫機制，意即使用一台 write-only master DB 搭配多台 read-only slave DB來達成目標。如果 master DB 掛了，會有其中一台 slave DB取代他。由於多數系統的 read 永遠大於 write，這麼做可以讓系統以平行的方式處理更多 query。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/2.png&#34;
	width=&#34;899&#34;
	height=&#34;890&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/2_huf6d604aaf6bad90c246b3a9be9e5b8fc_51031_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/2_huf6d604aaf6bad90c246b3a9be9e5b8fc_51031_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;101&#34;
		data-flex-basis=&#34;242px&#34;
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;horizontal-scaling-known-as-sharding&#34;&gt;Horizontal scaling known as &amp;ldquo;Sharding&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;書中有提到，水平擴展其實有個更廣為人知的名稱：Sharding（分片）。所謂的 Sharding, 意即做出許多個相同 schema 的資料庫，然後平均的把 data 透過 hashing 等方式分配給不同的 sharding DB。在 sharding DB 的使用架構中，最重要的是 hashing 方法中的 &lt;strong&gt;sharding key&lt;/strong&gt; 是如何被選擇的？如果制定的不好，容易讓 data 通通集中於同一個 sharding DB，那就不好了。&lt;/p&gt;
&lt;p&gt;書中提到了幾個有意思的 sharding 要考量到的議題：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如何制定具有一致性的 hashing method:
&lt;ul&gt;
&lt;li&gt;目的是讓 data 不會過度集中於一個 shard，或是解決當某個 shard 容量滿了之後如何重新搬動 data的問題。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;celebrity / hotspot problem:
&lt;ul&gt;
&lt;li&gt;某個 shard 因為太熱門，被過度的 read action 所淹沒。&lt;/li&gt;
&lt;li&gt;可能的解決方法有：(1) 為每個 celebrity 制定一個 shard, (2) 每個 shard 可能要進一步 partition。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;難以取得所有資料：
&lt;ul&gt;
&lt;li&gt;因為資料都放在不同的 sharding DB，難以做 json join。&lt;/li&gt;
&lt;li&gt;可能的解決方法是將 schema 做 de-normalization。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;sharding-vs-partition&#34;&gt;Sharding vs Partition&lt;/h3&gt;
&lt;p&gt;這邊額外整理一個書中沒提到、但是常常看到的比較：sharding 跟 partition 的差異是什麼？簡而言之:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;sharding 是將 data 依據 hashing method 等方式分配儲存於不同的 databases, 分隔成實體的單位（實際意義上，這些資料位於不同地方）。&lt;/li&gt;
&lt;li&gt;partition 是將同一個 database 中的 data, 依據比如 country, department等單位再次做分割，分隔成虛擬的單位（實際意義上，這些資料位於同個地方，只是以邏輯意義分成不同組別）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;phase-3-cache--cdn&#34;&gt;Phase 3: Cache &amp;amp; CDN&lt;/h2&gt;
&lt;p&gt;接著，當使用者再逐漸開始增加，service response time 開始變低，我們想要改善系統效能，該怎麼做？這時候我們以資料性質分成兩種處理方式：使用 cache 改善多數資料的存取速度、使用 CDN 改善靜態資料的存取速度。&lt;/p&gt;
&lt;p&gt;Cache 是一個存取速度很快的臨時資料儲存層，介於 Web server 與 client 之間，根據不同的資料類型、存取模式等等，有許多種 request-cache-server 的互動方式（eg: read-through&amp;hellip;）。要讓 Cache 既可以提升速度、又要維持正確性的方法，是仔細的思考 Cache system 的建構策略：什麼時候該使用 cache 而非query db、多久讓 cache中的資料過期、如何讓 cache 與 db資料保持同步、如果 cache 壞掉怎辦、cache 如果滿了怎辦&amp;hellip;&lt;/p&gt;
&lt;p&gt;CDN 是一個第三方供應商提供、為靜態內容提供快取的server，通常分散於地理位置各處。良好的、可以提升靜態內容存取速度的 CDN 很大關鍵取決於 user 與 CDN 之間的地理距離。如同 cache，要讓 CDN 可以良好正確的提升系統效能的方法是思考 CDN 的使用策略，包含過期時間設定、CDN壞掉時的備案措施。另外，CDN 使用成本也是建構系統需要考慮的一點，因此存放於 CDN 的資料必須是高使用率的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/3.png&#34;
	width=&#34;924&#34;
	height=&#34;913&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/3_hu48c34fa339754230f5dbd39bc65d1e12_44583_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/3_hu48c34fa339754230f5dbd39bc65d1e12_44583_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;101&#34;
		data-flex-basis=&#34;242px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;phase-4-stateless-web-server--auto-scaling&#34;&gt;Phase 4: Stateless web server &amp;amp; Auto scaling&lt;/h2&gt;
&lt;p&gt;接著，當使用者再逐漸開始增加，我們想要進一步優化 service response time，該怎麼做？這時候我們針對 Web 層做進一步的優化，方法就是讓所有的 state 資料（eg: session）從儲存於 Web server 改成儲存於 DB server，使 Web server 成為 Stateless Web server。當 user request 發送到 Web server，相較於一台一台 server 去尋找該 user state data 的存放之處，在 Stateless web server structure 下，Web server 會統一尋找共用的 DB，取得該 user 的 state 資料。&lt;/p&gt;
&lt;p&gt;另外，當變成 Stateless structure 後，就可以引入 auto scaling 機制，讓系統根據流量大小自動添加或移除 Web server。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/4.png&#34;
	width=&#34;974&#34;
	height=&#34;879&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/4_hu382f6b037b20b49579d7f58d0f50b879_52523_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/4_hu382f6b037b20b49579d7f58d0f50b879_52523_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;110&#34;
		data-flex-basis=&#34;265px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;phase-5-data-center&#34;&gt;Phase 5: Data center&lt;/h2&gt;
&lt;p&gt;接著，當使用者再逐漸開始增加，開始有了國際化的使用者族群，我們想要進一步優化 service response time 與可用性，該怎麼做？&lt;/p&gt;
&lt;p&gt;一個常見的方法，就是在不同的地理位置建構資料中心。user request 根據事先定義的流量分配比例，透過 geoDNS 被傳送到最近的資料中心的 servers。建構不同地理位置的資料中心也有其核心議題：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;資料同步：通常不同地理位置的資料都會略有不同，如果一者故障、流量轉移至另一個資料中心時，該怎麼設計資料同步策略，才可以讓不同地區的 request 來到這處的資料中心也可以被 process。&lt;/li&gt;
&lt;li&gt;服務一致性：要怎麼設計一套用於不同地區的測試、部署方法，讓不同地區都可以提供一致性或是相對應的服務？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/5.png&#34;
	width=&#34;964&#34;
	height=&#34;903&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/5_hudb0ab1ec2ef81e3d12c8f8732ef3c18e_52180_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/5_hudb0ab1ec2ef81e3d12c8f8732ef3c18e_52180_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;106&#34;
		data-flex-basis=&#34;256px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;phase-6-message-queue&#34;&gt;Phase 6: Message Queue&lt;/h2&gt;
&lt;p&gt;接著，當使用者再逐漸開始增加，而且我們的服務開始變得複雜：有些 services 需要很長的 process time。我們想要讓所有的 request 都可以順利被處理、同時優化 response time 與可用性，該怎麼做？&lt;/p&gt;
&lt;p&gt;一個常見的的作法，是打造訊息佇列（Message Queue），來支援這種非同步的請求情狀，並把 Web server進一步拆分為 Web server 與 Worker server。&lt;/p&gt;
&lt;p&gt;包含 Message Queue 的系統架構中，我們將發送 request 的 server 稱為 &amp;ldquo;Producer&amp;rdquo;，他會是一般的 Web server 。而接受 request 進行任務處理的 server 稱為 &amp;ldquo;Consumer&amp;rdquo;，他會是一個 Worker server。而 Message Queue 通常可以透過第三方服務 RabbitMQ 或是 AWS 的 SQS 來實現。&lt;/p&gt;
&lt;p&gt;首先，Producer (eg: 接收 user request 的 Web server)會將一個複雜任務請求傳送到 Message Queue中。接著，Consumer (eg: 負責執行複雜任務的 Worker process/cronjob/service) 會從 queue 中提領出任務並解決。整個過程是非同步的，意即當 Producer暫時故障沒有發送 request，Consumer 還是可以繼續從 queue 提領任務；當 Consumer 還在處理上一份複雜工作未完成時，Producer也還是可以繼續接單，並傳送至 queue中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/6.png&#34;
	width=&#34;1044&#34;
	height=&#34;266&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/6_huf29ad6f0cfe43133391a6d76d5ac9496_21003_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/6_huf29ad6f0cfe43133391a6d76d5ac9496_21003_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;392&#34;
		data-flex-basis=&#34;941px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;phase-7-logging--performance-metrics--automation&#34;&gt;Phase 7: Logging &amp;amp; Performance Metrics &amp;amp; Automation&lt;/h2&gt;
&lt;p&gt;最後，我們需要一個機制，讓系統內部發生的事情被紀錄，另外也需要一個元件來監控系統效能表現。透過設定 logging、建構 Performance metrics 並對其進行可視化，我們可以有效地掌握系統狀態並思考如何做得更好。另外，當系統越來越龐大，我們也需要制定 automation strategy (eg: cicd, testing)，以提高生產力。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/system-design-1/7.png&#34;
	width=&#34;885&#34;
	height=&#34;1121&#34;
	srcset=&#34;https://theoutsidelaine.com/p/system-design-1/7_hu986689f4271fbf6887258c0ded2e50b2_57745_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/system-design-1/7_hu986689f4271fbf6887258c0ded2e50b2_57745_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;78&#34;
		data-flex-basis=&#34;189px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;這章算是用很清楚且易懂的方式建構一個系統擴展的架構與思路出來，我覺得對我而言算是一個很好的思考基礎，之後再碰到關於系統設計從零開始設計的議題時，便可以開始想：web server 與 db server 可以分別怎麼擴展、遇到什麼樣的使用情境可以做什麼樣的優化。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>Nodejs APP Monitoring with Prometheus &amp; Grafana (&amp; a little Locust)</title>
        <link>https://theoutsidelaine.com/p/nodejs-prometheus-grafana/</link>
        <pubDate>Sat, 20 Apr 2024 08:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/nodejs-prometheus-grafana/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/nodejs-prometheus-grafana/cover.png" alt="Featured image of post Nodejs APP Monitoring with Prometheus &amp; Grafana (&amp; a little Locust)" /&gt;&lt;p&gt;不論是個人專案或是工作項目，當我們完成功能、想要進一步優化系統效能，我們首先要做的事情，便是知道目前系統的表現狀況為何。表現狀況通常可以簡單分成基本與進階，基本的像是 query 頁面的時間、功能是否都健康，而進階的比如說像是當系統遇到 high traffic 時每個 service表現的狀況、系統會在哪邊出現 bottleneck etc。而一旦知道目前系統的表現，我們也可以開始進一步思考有哪一些部分可以再做得更好，比如說讓查詢效率提升、錯誤率下降etc，可以透過微調哪一些元件來達成我們的目標。&lt;/p&gt;
&lt;p&gt;有很多衡量系統效能的工具，比如 Kibana, Datadog, 以及我們今天要介紹的工具：Prometheus + Grafana。&lt;/p&gt;
&lt;h2 id=&#34;why-using-prometheus--grafana&#34;&gt;Why using Prometheus &amp;amp; Grafana&lt;/h2&gt;
&lt;p&gt;一句話來說的話：免費、開源、社群支持度強。&lt;/p&gt;
&lt;p&gt;Prometheus 最早於 2012 年由 SoundCloud 開發並開源，主要用來收集、儲存與系統有關的數據，以便監控其效能。許多的 Prometheus components 皆以 Go 撰寫，便於 static binaries 的建構以及部署。他主要有六個特點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;multi-dimensional time-series data model: Prometheus 使用一個多維度的時間序列數據模型。每個時間序列由指標名稱和一組 labels 作為唯一標識，這使得在進行查詢和分析時，可以非常靈活地做資料過濾和聚合。&lt;/li&gt;
&lt;li&gt;多種數據取得方法：Prometheus 除了可以從 server的 metrics endpoint 自 HTTP 收集數據之外，還支援 Node Exporter, Blackboc Exporter等方法。另外，Prometheus 有一個名為 Pushgateway的元件，可以透過 HTTP 去 cache 收集到的數據，然後定期的讓 Prometheus從中取得資料進行監控與存儲。&lt;/li&gt;
&lt;li&gt;PromQL(Prometheus Query Language): 一種自定義的查詢語言，提供豐富的功能，可以執行各種數據分析與操作，像是 data selection或 aggregation。&lt;/li&gt;
&lt;li&gt;警報機制：透過設定閥值，當條件被觸發，Prometheus內建的 AlertManager 就會透過比如 email, slack 等發送通知。&lt;/li&gt;
&lt;li&gt;簡單可視化metrics的 web ui: Prometheus 提供一個簡單的 web ui, 可以可視化 PromQL expressions, 並透過 table或是 graph做數據呈現。&lt;/li&gt;
&lt;li&gt;活躍的開源社群：社群會維護許多第三方 &lt;a class=&#34;link&#34; href=&#34;https://prometheus.io/docs/instrumenting/exporters/#exporters-and-integrations&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;exporters, integrators&lt;/a&gt;, 以利於 metrics 的收集。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Prometheus architecture diagram.
&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prom.png&#34;
	width=&#34;1351&#34;
	height=&#34;811&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prom_hud5ed5663cc35a882862e3f8b09e662f5_96834_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prom_hud5ed5663cc35a882862e3f8b09e662f5_96834_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Prometheus architecture diagram&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;166&#34;
		data-flex-basis=&#34;399px&#34;
	
&gt;
Credit: &lt;a class=&#34;link&#34; href=&#34;https://prometheus.io/docs/introduction/overview/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Prometheus.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Grafana 最早則於 2014 年由瑞典公司 Grafana Labs 開發與開源。Grafana 會從多種數據來源（datasources）取得資料，並提供豐富的圖表、面板、儀表板，讓這些被處理後的數據可以展示於上。數據來源可以有很多種，而 Prometheus是其中一種常見的來源。原因大概是因為 Prometheus量測的指標比如 cpu, memory, heap, event loop 都是 time series data, 而 Grafana 儀表板中的 panels 在接收格式與呈現方式上很適合這種資料；Grafana query area支援 PromQL，所以會 PromQL 的話也可以輕鬆在 query area製作 Grafana圖表。以下是 Grafana的幾個特點：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支援多種數據來源：除了 Prometheus之外，也支援時間序列型資料庫（如 Prometheus、InfluxDB、Graphite）、日誌數據（如 Elasticsearch、Loki）、關聯式資料庫（如 MySQL、PostgreSQL）等。&lt;/li&gt;
&lt;li&gt;豐富的圖表、面版：包含折線圖、柱狀圖、餅狀圖、地圖、表格等，並支援多種自定義的配置，以滿足不同的可視化需求。&lt;/li&gt;
&lt;li&gt;豐富的查詢語言：除了 PromQL 之外，也提供像是 InfluxQL等查詢語言。另外，Grafana 也有智能提示、syntax highlighting 等功能。&lt;/li&gt;
&lt;li&gt;警報機制：Grafana 內建警報功能，可以根據數據設定相關閥值，一旦條件被觸發，即可發送警報通知。Grafana 支援多種通知方式，比如 email、Slack、PagerDuty 等。&lt;/li&gt;
&lt;li&gt;活躍的開源社群：一樣，社群會維護許多第三方plugins, 便於 Grafana平台不斷的擴展。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;file-structure--docker-compose&#34;&gt;File structure &amp;amp; docker-compose&lt;/h2&gt;
&lt;p&gt;以下先用 top-down 方式簡單呈現一個 Node.js 專案中如何放入 Prometheus 與 Grafana 相關的檔案，之後再說明各個部分的細節。&lt;/p&gt;
&lt;p&gt;folder structure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── client/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── server/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── grafana/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   ├── datasources/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   │   └── datasources.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   └── dashboards/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │       ├── dashboard.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │       └── dashboard-basic.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │       └── dashboard-advanced.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── prometheus/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   └── prometheus.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── prom-middleware/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   │   └── index.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── index.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── docker-compose.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在 local 分別起 server, prometheus, grafana 三個主要的 containers.&lt;/p&gt;
&lt;p&gt;docker-compose.yml:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;47
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;49
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;50
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;51
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;52
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;53
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;version: &amp;#39;3.8&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  server:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    build:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      context: ./server
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      dockerfile: Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ports:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - &amp;#34;127.0.0.1:4000:4000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - ./server:/usr/app
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - /usr/app/node_modules
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  prometheus:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    image: prom/prometheus:v2.20.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    container_name: prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - ./server/prometheus:/etc/prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - prometheus_data:/prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ports:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - &amp;#34;9090:9090&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    expose:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - 9090
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    networks:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - monitoring
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  grafana:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    image: grafana/grafana:7.1.5
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    container_name: grafana
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - ./server/grafana/provisioning:/etc/grafana/provisioning
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - grafana_data:/var/lib/grafana
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    environment:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - GF_AUTH_DISABLE_LOGIN_FORM=true
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - GF_AUTH_ANONYMOUS_ENABLED=true
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ports:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - &amp;#34;3000:3000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    expose:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - 3000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    networks:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - monitoring
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;networks:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  monitoring:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    driver: bridge
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  db_data:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  prometheus_data: {}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  grafana_data: {}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;prometheus-configuration&#34;&gt;Prometheus configuration&lt;/h2&gt;
&lt;h3 id=&#34;1-prometheusyml&#34;&gt;1. &lt;code&gt;prometheus.yml&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;global:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  scrape_interval: 5s
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;scrape_configs:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - job_name: &amp;#34;synoptic-app&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    static_configs:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      - targets: [&amp;#34;docker.for.mac.localhost:4000&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Prometheus 會根據你設定的 scrape_interval 以固定的時間間隔擷取資料。scrape_configs 用於設定不同 job 的任務目標，此處意指從 target 的 [address:port number] 處擷取資料。&lt;/p&gt;
&lt;p&gt;值得注意的是：targets 的bracket中，由於我們使用docker，且運行於 mac上，所以冒號前者的 value 會是 &lt;code&gt;docker.for.mac.localhost&lt;/code&gt;。我在一開始把位置誤寫成 server container name, 導致 prometheus 遲遲無法取得送到 localhost:4000的資料。&lt;/p&gt;
&lt;h3 id=&#34;2-prom-client&#34;&gt;2. &lt;code&gt;prom-client&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;接著便要開始利用 Prometheus 自 APP server 擷取資料。我們需要將 APP server與&lt;a class=&#34;link&#34; href=&#34;https://prometheus.io/docs/instrumenting/exporters/#exporters-and-integrations&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt; 許多metrics exporter &lt;/a&gt;連接，並透過這些 exporters 傳送資料至 Prometheus server 讓其存儲與監控 APP 的表現資料。&lt;/p&gt;
&lt;p&gt;Express APP 會透過 &lt;code&gt;prom-client&lt;/code&gt; 這個 nodejs library 將 metrics export出去。安裝方法：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm install prom-client
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;完成後，在 &lt;code&gt;index.js&lt;/code&gt; 我們可以開始使用 prom-client 去協助我們取得 APP 中的各種數據。在 Prometheus 中開發者重視的數據基本上可以分成三種：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;defaultMetrics&lt;/strong&gt;: Prometheus 官方推薦了一系列值得觀測的指標，在 prom-client中，這些指標被匯總成為「defaultMetrics」。例子： node_process_cpu_system_seconds, nodejs_eventloop_lag_max_seconds, node_process_heap_bytes 等。這些指標會自帶像是 nodejs, node 等 prefix。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;http_request_duration_seconds&lt;/strong&gt;: 這其實是一個主要功能是計時器的 histogramtimer 指標而已，然後我們把它命名為 http_request_duration_seconds。具體來說，時間對於 routing 來說是最基本的表現指標，因此，這個 metric 最主要的目的是協助我們知道每一個 route 的 http request time performances，透過設定這個指標加上撰寫相關的 PromQL，我們可以知道每一個 route的 response time等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;custom metrics&lt;/strong&gt;: Prometheus 提供開發者自定義四大類 custom metrics: Histogram, Summary, Counter, Gauge. 簡單說明一下每一個指標的定義與用途：
&lt;ul&gt;
&lt;li&gt;Histogram: 直方圖，用於監控某個指標的分布狀況。Eg: Request time的時間分佈。&lt;/li&gt;
&lt;li&gt;Summary: 摘要，用於監控某個指標的分布狀況，特別是其詳細的統計分佈區間，比如 max, min, ?% quantile。Eg: Request time的時間分佈摘要。&lt;/li&gt;
&lt;li&gt;Counter: 計數器，用於監控一個 service 或者 event 的發生次數。Eg: 某 request 的 發生次數。&lt;/li&gt;
&lt;li&gt;Gauge: 計量器，用於監控一個隨時間而變化的數值。Eg: Memory usage。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這邊我們主要針對 defaultMetrics 與 http_request_duration_seconds 去監控。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const client = require(&amp;#39;prom-client&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const register = new client.Registry();
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;client.collectDefaultMetrics({
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  app: &amp;#39;synoptic-monitoring-app&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  prefix: &amp;#39;node_&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  timeout: 10000,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  gcDurationBuckets: [0.001, 0.01, 0.05, 0.1, 0.5, 1, 2, 5], // These are the default buckets.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  register,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;「Registry」是 Prometheus 數據存處所在之處，我們會 init 一個 registry instance，然後將收集到的 defaultMetrics 數據傳入 registry 中。&lt;code&gt;collectDefaultMetrics()&lt;/code&gt; 提供其他客製化的選項，像是 &lt;code&gt;prefix&lt;/code&gt; 可以在每個 default metrics前面加上前綴、&lt;code&gt;timeout&lt;/code&gt; 可以客製化 timeout 時長、&lt;code&gt;gcDurationBuckets&lt;/code&gt; 則是設定 Garbage Collection Histogram(垃圾回收行為監控)的 bucket 寬度。最後再將 register instance 也放入 options 中完成註冊。&lt;/p&gt;
&lt;h3 id=&#34;3-prom-middleware&#34;&gt;3. prom-middleware&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const express = require(&amp;#39;express&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const app = express();
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;app.use(httpRequestHistogramMiddleware(register));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;當 defaultMetrics 已經註冊於 register，我們需要將另一個重要指標 http_request_duration_seconds 也放到 register 中。這邊我使用的方法是簡單寫一個 middleware，讓每一個 route 都可以被其經過，並被紀錄 request 打入後 response time 與 response status code。&lt;/p&gt;
&lt;p&gt;middleware 如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const promClient = require(&amp;#39;prom-client&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const onFinished = require(&amp;#39;on-finished&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const httpRequestHistogramMiddleware = (register) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  const httpRequestDurationMicroseconds = new promClient.Histogram({
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    name: &amp;#39;http_request_duration_seconds&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    help: &amp;#39;Duration of HTTP requests in seconds&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    labelNames: [&amp;#39;method&amp;#39;, &amp;#39;route&amp;#39;, &amp;#39;code&amp;#39;],
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 0.7, 1, 5, 7, 10],
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    registers: [register],
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  });
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  return (req, res, next) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    const end = httpRequestDurationMicroseconds.startTimer();
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    onFinished(res, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      end({ method: req.method, route: req.route.path, code: res.statusCode });
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    next();
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;module.exports = httpRequestHistogramMiddleware;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;從這兩段程式碼我們可以發現，基本上我們做的事情如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1) 創建一個 middleware，其中 new 一個 histogram，把它命名為 http_request_duration_seconds，並將其註冊於 register。&lt;/li&gt;
&lt;li&gt;(2) 在我們的 main app 中，使用 app.use() 讓之後出現的 routes 都可以經過它。值得注意的是，如果你的 express app 有使用 session，必須確保 Prometheus configuration 發生於 session 使用之前、routes註冊之前。&lt;/li&gt;
&lt;li&gt;(3) 當 route 被打時，計時器被啟動，將數據記錄於 histogram，當 route結束時，紀錄 method, route, code。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;app.get(&amp;#39;/metrics&amp;#39;, async (req, res) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  try {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    const metrics = await register.metrics();
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    res.setHeader(&amp;#39;Content-Type&amp;#39;, register.contentType);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    res.end(metrics);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  } catch (error) {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    console.error(&amp;#39;Error generating metrics:&amp;#39;, error);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    res.status(500).json({ error: &amp;#39;Internal server error from Prometheus&amp;#39; });
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;app.listen(port, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;console.log(`Hello server ${port} port.`);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;最後，創建一個 &lt;code&gt;/metrics&lt;/code&gt; endpoint，當 app 被使用，app會將register收集到的 metrics() 返還。由於 metrics() 會返回一個 Promise, 所以必須使用 async-await 來取得數據結果。&lt;/p&gt;
&lt;h2 id=&#34;grafana-configuration&#34;&gt;Grafana configuration&lt;/h2&gt;
&lt;h3 id=&#34;1-datasourcesyml&#34;&gt;1. &lt;code&gt;datasources.yml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;如同前面介紹所說，grafana 從 datasources取得數據呈現於儀表板上，所以這個 yaml 主要用於 configure datasource 來源。以下為 yaml檔內容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apiVersion: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;datasources:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - name: Prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    type: prometheus
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    access: proxy
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    orgId: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    url: http://prometheus:9090
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    basicAuth: false
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    isDefault: true
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    editable: true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;configure後，grafana container 會自動註冊 http://prometheus:9090 於 Settings, 不需額外登入 grafana 手動註冊。&lt;/p&gt;
&lt;h3 id=&#34;2-dashboards&#34;&gt;2. dashboards&lt;/h3&gt;
&lt;p&gt;dashboards 為定義 grafana 儀表板的內容，基本上你可以在 grafana container 起起來之後手動拉儀表板，也可以使用 predefined dashboards，使 container 起起來之後快速的看到儀表板。&lt;/p&gt;
&lt;p&gt;這邊借用 &lt;a class=&#34;link&#34; href=&#34;https://github.com/StackAbuse/node-prometheus-grafana/tree/master/grafana/provisioning/dashboards&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Stack Abuse 網站創建的超完整儀表板&lt;/a&gt;。基本上他建立了四種不同的儀表板，當 Prometheus 數據進來之後，可以依此監控各種目標。四個儀表板分別是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Node Application Dashboard: 監控如 Process CPU Usage, Process Memory Usage等基本 metrics.&lt;/li&gt;
&lt;li&gt;Node Service Level Metrics Dashboard: 以 route/service 為單位，監控每隻 route的 Number of request, error rate, request處理時間等。&lt;/li&gt;
&lt;li&gt;Node Request Flow Dashboard: 以 request 為基礎，監控 request的各種面向，如時長 (duration)，並用各種圖表可視化。&lt;/li&gt;
&lt;li&gt;High Level Application Metrics: 監控整體app的 throughput, response time等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;另外，dashboard 也需要有 yaml 做 configuration. 如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apiVersion: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;providers:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- name: &amp;#39;Prometheus&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  orgId: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  folder: &amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  type: file
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  disableDeletion: false
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  editable: true
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  options:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    path: /etc/grafana/provisioning/dashboards
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;showcase-each-part&#34;&gt;Showcase Each Part&lt;/h2&gt;
&lt;p&gt;當我們成功 configure Prometheus, Grafana，我們便可以看到以下幾個畫面:&lt;/p&gt;
&lt;h3 id=&#34;1-on-localhostportmetrics&#34;&gt;1. on &lt;code&gt;localhost:{port}/metrics&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;main app metrics endpoint 可以看到許多筆監控數據。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/metrics.png&#34;
	width=&#34;1224&#34;
	height=&#34;664&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/metrics_hud8081b730284b09239c539a7e012321b_152701_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/metrics_hud8081b730284b09239c539a7e012321b_152701_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;app metrics endpoint exposes service monitoring data&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;184&#34;
		data-flex-basis=&#34;442px&#34;
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;2-on-localhost9090&#34;&gt;2. on &lt;code&gt;localhost:9090&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus 走 9090 port，有一個簡易 web UI, 讓我們用簡單的 table / graph, 看到這些數據隨時間變化的樣子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prometheus.png&#34;
	width=&#34;1692&#34;
	height=&#34;883&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prometheus_hu1a2cc50b53e456f684ce571b3b596f4b_107549_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/prometheus_hu1a2cc50b53e456f684ce571b3b596f4b_107549_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Prometheus web UI&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;191&#34;
		data-flex-basis=&#34;459px&#34;
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;3-on-localhost3000&#34;&gt;3. on &lt;code&gt;localhost:3000&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Grafana 走 3000 port，透過 dashboard folder中定義的 dashboard.json，直接 config 出儀表板。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana.png&#34;
	width=&#34;1701&#34;
	height=&#34;985&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana_hu833af1013c844be5794df5931d274c43_281920_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana_hu833af1013c844be5794df5931d274c43_281920_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Grafana Dashboard&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;172&#34;
		data-flex-basis=&#34;414px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana2.png&#34;
	width=&#34;1704&#34;
	height=&#34;994&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana2_hu7d6e6064c60fd70be7c941f19cc8cf2c_224286_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/grafana2_hu7d6e6064c60fd70be7c941f19cc8cf2c_224286_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Grafana Dashboard2&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;171&#34;
		data-flex-basis=&#34;411px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;test-the-monitoring-results&#34;&gt;Test the monitoring results&lt;/h2&gt;
&lt;p&gt;基本指標如 cpu, memory, event loop只要 app在動都可以自動進行監測，不過像是 service routes 沒有打的話我們基本上不會從 Prometheus 與 Grafana上知道他們的 response time。因此，這邊便需要借助壓力測試工具 &lt;code&gt;locust&lt;/code&gt; 來協助我們完成這個目標。&lt;/p&gt;
&lt;p&gt;Locust 是一個以 Python 編寫的開源壓力測試工具，可以執行 app defined actions，比如發起 http request, 查詢資料庫並操作回傳資料等。目的是評估 web app 在高流量下的效能。&lt;/p&gt;
&lt;p&gt;以下說明使用 Locust 的步驟：&lt;/p&gt;
&lt;h3 id=&#34;1-build-up-virtual-environment&#34;&gt;1. build up virtual environment&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python3 -m venv locust_venv
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;source locust_venv/bin/activate 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;2-install-locust&#34;&gt;2. install locust&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;3-write-indexpy-and-run-it&#34;&gt;3. write &lt;code&gt;index.py&lt;/code&gt; and run it&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;from locust import HttpUser, task, between
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;class BrowsingUser(HttpUser):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    wait_time = between(1, 2)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    host = &amp;#34;http://localhost:4000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    @task
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    def get_products(self):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        self.client.get(&amp;#34;/api/v1/products/women/?paging=1&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;class CheckoutUser(HttpUser):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    wait_time = between(1, 3)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    host = &amp;#34;http://localhost:4000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    @task
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    def browse_product_details(self):
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        self.client.get(&amp;#34;/api/v1/products/details?id=64&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;這邊假設有兩種 user action: BrowsingUser, 瀏覽主頁面的 /products/women/?paging=1。CheckoutUser, 在結帳之前會點擊某個商品頁面 /products/details?id=64。然後我們使用以下執行執行這兩個用戶行為模擬：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;locust -f index.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;接著便可以在 http://0.0.0.0:8089/ 看到 Locust Dashboard了。可以指定使用者數量、每秒生成新用戶的速率（ramp up）。由於我們有兩個 user, 所以這邊設定使用者數量=2。
&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust1.png&#34;
	width=&#34;1695&#34;
	height=&#34;582&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust1_hu7a6d3b7699e02772fbf01f34a11940d2_69619_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust1_hu7a6d3b7699e02772fbf01f34a11940d2_69619_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Locust Dashboard&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;291&#34;
		data-flex-basis=&#34;698px&#34;
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;4-observations&#34;&gt;4. observations&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result1.png&#34;
	width=&#34;1613&#34;
	height=&#34;555&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result1_huf0b2ef071943cb4d9579a9ebc59ad6ce_82487_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result1_huf0b2ef071943cb4d9579a9ebc59ad6ce_82487_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Locust Result: in table view&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;290&#34;
		data-flex-basis=&#34;697px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result2.png&#34;
	width=&#34;1608&#34;
	height=&#34;1033&#34;
	srcset=&#34;https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result2_huc60787304d7be5fef06b8a113640391f_133773_480x0_resize_box_3.png 480w, https://theoutsidelaine.com/p/nodejs-prometheus-grafana/locust-result2_huc60787304d7be5fef06b8a113640391f_133773_1024x0_resize_box_3.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Locust Result: in graph view&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;155&#34;
		data-flex-basis=&#34;373px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;當同時啟動 server / prometheus / grafana container, 還可以把這些打 api的結果實時的傳送給 grafana dashboards。&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;透過這個簡單的練習，我們知道如何將一個 Node.js App 串接 Prometheus 與 Grafana，以及使用簡單的 locust load test觀察兩隻api的表現。日後可以思考的點是：要怎麼觀察出「異常」與「正常」之差異，並且根據異常狀態設計方法，真正達成系統效能優化的目標。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>base64</title>
        <link>https://theoutsidelaine.com/p/base64-encode-decode/</link>
        <pubDate>Mon, 25 Mar 2024 22:00:00 +0000</pubDate>
        
        <guid>https://theoutsidelaine.com/p/base64-encode-decode/</guid>
        <description>&lt;img src="https://theoutsidelaine.com/p/base64-encode-decode/cover.png" alt="Featured image of post base64" /&gt;&lt;p&gt;沒想到第一篇技術分享文章會先從這個小主題開始。主要原因是之前在面試一家非常喜歡的公司，其中一個面試關卡因為沒有考慮到問題條件限制之一是 input 資料為以 base64 編譯後的資料，需要先用程式 decode 為 binary 再做後續處理，最後很可惜沒有進入下一關。後來回想一下，其實我在實習的時候就有碰過這個東西了，只不過當時做 base64 encode 我都直接把 service 密碼明文丟到 google 上的 &amp;ldquo;online base64 encoder&amp;rdquo; 直接做好 encode 後的 string🫠，算是根本沒懂過這個概念。所以決定針對他好好來研究一下，並記錄自己的研究內容～&lt;/p&gt;
&lt;h2 id=&#34;base64-的基本概念&#34;&gt;base64 的基本概念&lt;/h2&gt;
&lt;p&gt;base64 是一種數據格式轉換方法，基本上 base64 encoding 的輸入資料會是一組二進制（binary）編碼，之後依照某種轉換規則，將一組組的 binary 數據對應到特定的 base64 character，最後再輸出一個 text form 的 output。&lt;/p&gt;
&lt;h2 id=&#34;為什麼要做-base64-轉換&#34;&gt;為什麼要做 base64 轉換?&lt;/h2&gt;
&lt;p&gt;主要原因大概如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提升安全性&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;有時候一些人類很直觀能理解的密碼明文，比如 「&lt;code&gt;elaine_hsieh&lt;/code&gt;」，如果直接撰寫在 service configuration 上，會讓取得密碼的過程太過容易。如果將這樣的資訊包上一層 base64 encoding、變成「&lt;code&gt;ZWxhaW5lX2hzaWVo&lt;/code&gt;」，之後有人想要取得 service 密碼將需要多一道解譯的手續。因此，透過 base64 encoding 可以進而提升一點資訊安全性。&lt;/p&gt;
&lt;p&gt;以 base64 編碼還有個額外好處：便於不同編碼方式之間進行數據轉換後的數據傳輸。&lt;/p&gt;
&lt;p&gt;通常某個 service 密碼明文會使用 &lt;strong&gt;ASCII&lt;/strong&gt; 或是 &lt;strong&gt;UTF-8&lt;/strong&gt; 的編碼方式。使用 ASCII 的明文包含範圍較小，通常僅包含 256 個基本的字元，例如英文字母、數字、標點符號（此處指的是extended ASCII）；使用 UTF-8 的明文包含範圍較大，例如英文字母、數字、標點符號、拉丁字母、中日韓等亞洲文字、表情符號與其他符號。由於 base64 character set包含了部分 ASCII character set，所以不論原始數據是以 ASCII做編碼（例：&lt;code&gt;titan&lt;/code&gt; in ASCII code）、或是以 UTF-8做編碼（例：&lt;code&gt;titan&lt;/code&gt; in UTF-8 code），都可以透過 base64 轉換規則轉換為一組 base64 encoding（例：都會被 base64 轉譯為 &lt;code&gt;dGl0YW4=&lt;/code&gt;）。所以可以說，base64 可用於不同編碼方式之間進行數據轉換後的數據傳輸。&lt;/p&gt;
&lt;p&gt;如果要寬鬆一點說，base64 會有點類似於加密，只不過 base64 可以被輕易的反編碼、讀取到原文，並且此資訊公開給所有人，沒有公私鑰授權特定用戶的功能，所以嚴格來說 base64 不能算是一種加密 (encryption)方法，只能算是一種編譯（encoding）方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;便於圖片、音樂、多媒體文件等資訊在網路世界中傳遞&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;在 HTTP、SMTP 等網絡通訊協議中，想要傳送圖片等多媒體資訊其實會有很多限制。HTTP、SMTP、POP3、FTP 等網絡協議只能傳送 text form data，但是圖片、音樂、多媒體文件等資訊通常都會使用 binary code 來儲存資訊：比如像素、顏色深度、每幀畫面、音頻等，通常會用 16 或 32 bit 資訊做紀錄。&lt;/p&gt;
&lt;p&gt;所以當我們想要用電子郵件傳送圖片時，我們會需要先將「&lt;code&gt;00101010101000001&lt;/code&gt;」的圖片 binary data，用 base64 轉換為「&lt;code&gt;MDAxMDEwMTAxMDEwMDAwMDE=&lt;/code&gt;」，電子郵件的 html 形式會類似於 &amp;lt;img&amp;gt; &lt;code&gt;MDAxMDEwMTAxMDEwMDAwMDE=&lt;/code&gt; &amp;lt;/img&amp;gt;。之後傳送到目的方時，再使用 base64 decode 那串 text，就可以看得到&amp;lt;img&amp;gt; &amp;lt;/img&amp;gt; 裡的內容了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;base64-轉換規則&#34;&gt;base64 轉換規則&lt;/h2&gt;
&lt;p&gt;以string input為例，大致上來說 base64 有四個步驟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;將 string 依照編碼（eg: ASCII） 轉換為 binary。我們知道 ASCII 編碼一共有 256 種可能，所以轉換後每個字母會對應到 0~255 ASCII number，與8 個bits。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;將這些 binary 以每 6 bits 為單位進行分組。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;將每組 bits 轉換為對應的 base64 character:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(1) base64 有一個自己的 character set, 只有 64 個 character，分別是：大小寫字母（A-Z、a-z）、數字（0-9）和兩個特殊字符（+ 和 /）。這也是 base64 得到這個名字的原因。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(2) 每組 binary 有6 bits, 相當於一共有 64 種可能性，正好可以對應到 base64 character set。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果原始的 string長度不是3的倍數，base64 會自動補上一個 &amp;ldquo;=&amp;quot;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(1) 這麼做的理由相當於宣告最後一組bits「後面有補0，才得以是 6個bits」&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(2) &amp;ldquo;=&amp;rdquo; 相當於宣告這組 bits的特殊性&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最後將轉換後的 base64 characters回傳，即大功告成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;值得注意的是，base64 編碼後的數據通常比原始數據更長。這是因為 base64 編碼會將原本以 8 bits 為單位的 string，重新打散單位、轉換為 6 bits 的 base64 characters，這導致編碼後的數據會比原始數據增加約 33% 的大小。&lt;/p&gt;
&lt;h2 id=&#34;舉例說明&#34;&gt;舉例說明&lt;/h2&gt;
&lt;p&gt;假設我們有一串 string名為「Hello」，其長度=5，不是3的倍數。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;依照 ASCII 編碼，「Hello」每一個 character 會對應到一個 8 bits 組合（＝對應到 0~ 255 之間的一個數字）。組合起來即為「01001000 01100101 01101100 01101100 01101111」，一共 40 bits。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;將以上 40 bits 每 6 個分一組，一共可以分成 6 組有完整 6 bits + 1組只有4 bits：「010010」、「000110」、「010101」、「101100」、「011011」、「000110」、「1111」&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最後那一組補 0，讓他還是擁有 6 bits, 只不過後面放入一個 base64 character: &amp;ldquo;=&amp;quot;，標註他的特殊性。所以就變成以下：「010010」、「000110」、「010101」、「101100」、「011011」、「000110」、「11110&lt;strong&gt;0=&lt;/strong&gt;」&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每一組對應到 base64 character set中的數字（對應表可以參考本篇文章的首頁圖）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最後的 output即為 「SGVsbG8=」&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;python-library-實作&#34;&gt;python library 實作&lt;/h2&gt;
&lt;p&gt;在 python中，有很便利的 library協助我們做到 base64 encoding. 可以參考以下的 code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;import base64
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;original_text = &amp;#34;Hello, world!&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;original_bytes = original_text.encode(&amp;#39;utf-8&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;encoded_bytes = base64.b64encode(original_bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;encoded_text = encoded_bytes.decode(&amp;#39;utf-8&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;print(&amp;#34;Base64 編碼結果:&amp;#34;, encoded_text)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;decoded_bytes = base64.b64decode(encoded_bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;decoded_text = decoded_bytes.decode(&amp;#39;utf-8&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;print(&amp;#34;Base64 解碼結果:&amp;#34;, decoded_text)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;結果如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Base64 編碼結果: SGVsbG8sIHdvcmxkIQ==
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Base64 解碼結果: Hello, world!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
        </item>
        
    </channel>
</rss>
