[{"content":"介紹 線段樹（Segment Tree）是一種常見的資料結構，用於解決區間查詢（range query）的問題。\n它主要用於處理數列或數組的區間操作，如區間求和、區間最大值、區間最小值等。\n線段樹的基本思想是將原始的數據按照一定的方式分割成若干個區間，並在每個區間上存儲相應的信息，如區間的總和、最大值、最小值等。這樣，在需要查詢某個區間的統計信息時，可以通過合併相應區間的信息快速計算得到。\n一般而言，線段樹是一種二叉樹結構，每個節點表示一個區間，而樹的根節點表示整個數列或數組的區間。\n每個節點包含了該區間的統計信息，以及指向左右子節點的指標。\n基礎操作 建構線段樹的過程可以使用遞迴或迭代的方式進行。具體而言，建構過程中，將原始數據切分為兩半，然後遞迴構建左子樹和右子樹，最終將左子樹和右子樹的統計信息合併到當前節點。\n線段樹的查詢操作也可以使用遞迴或迭代的方式進行。當查詢的區間與節點表示的區間有交集時，需要遞迴查詢左子節點和右子節點。如果查詢的區間完全包含在節點表示的區間中，則可以直接返回該節點的統計信息。\n線段樹的修改操作通常也使用遞迴或迭代的方式進行。當需要修改某個位置的值時，需要遞迴更新涉及該位置的所有節點，以維護其統計信息的準確性。\n時間複雜度，推薦必看👍 資料來自菜鳥工程師肉豬的部落格，推薦超好懂 👍：\n【為什麼Binary Search 二元搜索法的時間複雜度是O(log(n))】\n優點 線段樹的優點在於可以在O(logN)的時間複雜度下完成區間操作，其中N表示數列或數組的長度。\n它在許多需要頻繁進行區間操作的應用場景中具有重要的應用價值，如區間求和、區間最值查詢、區間更新等。\n問題與限制 然而，線段樹也有一些限制和注意事項。首先，它需要較多的內存空間來存儲統計信息，因此在處理大型數據集時可能會面臨內存限制的問題。此外，線段樹的建構過程較為複雜，並且對於動態更新的情況，可能需要進行結構調整，增加了一定的複雜度。\n結論 總體而言，線段樹是一種強大的數據結構，可以高效地處理區間操作的問題。在算法競賽和一些需要快速解決區間查詢的應用中，線段樹是一個重要的工具。\n以下是使用 JavaScript 實現線段樹的基本範例碼： class SegmentTree { constructor(nums) { this.nums = nums; this.tree = new Array(nums.length * 4); // 建立線段樹陣列 this.buildTree(0, 0, nums.length - 1); } // 建立線段樹 buildTree(node, start, end) { if (start === end) { this.tree[node] = this.nums[start]; } else { const mid = Math.floor((start + end) / 2); const leftChild = 2 * node + 1; const rightChild = 2 * node + 2; this.buildTree(leftChild, start, mid); this.buildTree(rightChild, mid + 1, end); this.tree[node] = this.tree[leftChild] + this.tree[rightChild]; // 修改這裡可以實現不同的區間操作 } } // 區間查詢 query(node, start, end, left, right) { if (left \u0026gt; end || right \u0026lt; start) { return 0; // 區間不相交，返回初始值（這裡假設初始值為 0） } else if (left \u0026lt;= start \u0026amp;\u0026amp; right \u0026gt;= end) { return this.tree[node]; // 區間完全包含在節點表示的區間中，返回該節點的值 } else { const mid = Math.floor((start + end) / 2); const leftChild = 2 * node + 1; const rightChild = 2 * node + 2; const sumLeft = this.query(leftChild, start, mid, left, right); const sumRight = this.query(rightChild, mid + 1, end, left, right); return sumLeft + sumRight; // 修改這裡可以實現不同的區間操作 } } // 修改數組中某個位置的值 update(node, start, end, index, value) { if (start === end) { this.tree[node] = value; } else { const mid = Math.floor((start + end) / 2); const leftChild = 2 * node + 1; const rightChild = 2 * node + 2; if (index \u0026gt;= start \u0026amp;\u0026amp; index \u0026lt;= mid) { this.update(leftChild, start, mid, index, value); } else { this.update(rightChild, mid + 1, end, index, value); } this.tree[node] = this.tree[leftChild] + this.tree[rightChild]; // 修改這裡可以實現不同的區間操作 } } } // 測試 const nums = [1, 3, 5, 7, 9, 11]; const segmentTree = new SegmentTree(nums); console.log(segmentTree.query(0, 0, nums.length - 1, 1, 4)); // 計算索引 1 到 4 的區間和，應為 24 segmentTree.update(0, 0, nums.length - 1, 2, 6); // 修改索引 2 的值為 6 console.log(segmentTree.query(0, 0, nums.length - 1, 1, 4)); // 再次計算索引 1 到 4 的區間和，應為 25 /** * 【以數字來分類】 * [1, 3, 5, 7, 9, 11] * / \\ * [1, 3, 5] [7, 9, 11] * / \\ / \\ * [1, 3] [5] [7, 9] [11] * / \\ / \\ * [1] [3] [7] [9] * * * * 【以 index 來分類】 * (0){0-5} * / \\ * (1){0-2} (2){3-5} * / \\ / \\ * (3){0-1} (4){2-2} (5){3-4} (6){5-5} * / \\ / \\ * (7){0-0} (8){1-1} (11){3-3} (12){4-4} * * * 計算索引 1 到 4 的區間和： * 先找到 {0-2} * 往下再找 {0-1} * 往下再找 {1-1} 得到 {1} 的值 3 * 找 {2-2} 得到 {2} 的值 5 * * 找 {3-5} * 往下再找 {3-4} 得 {3, 4} 的值 16 * * 結束尋找 * 將找到的值加總，總和為 24 * */ 上述範例中，我們首先創建了一個 SegmentTree 類，並在建構函數中初始化數組 nums 和線段樹 tree。\n然後，我們實現了線段樹的三個基本操作：buildTree 用於建立線段樹、query 用於執行區間查詢、update 用於修改數組中某個位置的值。\n最後，我們使用測試數組 [1, 3, 5, 7, 9, 11] 創建了一個線段樹 segmentTree，並通過 query 方法計算了索引 1 到 4 的區間和。 接著，我們使用 update 方法將索引 2 的值修改為 6，並再次使用 query 方法計算了索引 1 到 4 的區間和，驗證了修改操作的正確性。\n這只是線段樹的一個基本實現示例，你可以根據需要進一步擴展和修改線段樹的功能，以滿足不同的區間操作需求。\n解釋 【以數字來分類】 [1, 3, 5, 7, 9, 11] / \\ [1, 3, 5] [7, 9, 11] / \\ / \\ [1, 3] [5] [7, 9] [11] / \\ / \\ [1] [3] [7] [9] 【以 index 來分類】 表達格式：(node 編號){index-index} (0){0-5} / \\ (1){0-2} (2){3-5} / \\ / \\ (3){0-1} (4){2-2} (5){3-4} (6){5-5} / \\ / \\ (7){0-0} (8){1-1} (11){3-3} (12){4-4} 計算索引 1 到 4 的區間和： 先找到 {0-2} 往下再找 {0-1} 往下再找 {1-1} 得到 {1} 的值 3 往回找 {2-2} 得到 {2} 的值 5 再來找 {3-5} 往下再找 {3-4} 得 {3, 4} 的值 16 結束尋找，將找到的值加總，總和為 24 參考資料 為什麼Binary Search 二元搜索法的時間複雜度是O(log(n)) 演算法知識 - Segment Tree 線段樹 | 大衞的筆記 【題解】HDU 4027. Can you answer these queries? 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-30T18:43:13+08:00","permalink":"https://yue-jenny.github.io/data-structure/segment-tree/","title":"資料結構：線段樹 Segment Tree"},{"content":"介紹 約瑟夫問題（Josephus Problem）是一個古老的數學問題，其名稱源自於猶太歷史學家弗拉維奧·約瑟夫斯（Flavius Josephus）。這個問題描述了一個關於生存和順序的遊戲。\n問題的背景是這樣的： 假設有N個人（編號從1到N）站成一個圓圈狀。一開始，從第一個人開始按照順時針方向報數，報到第M個人就將其移出圈子，然後再從下一個人開始重新報數，如此重複，直到只剩下一個人為止。\n問題的目標 目標是找到最後生存下來的那個人的編號**。\n即，如果 N = 7，M = 3，那麼經過一輪報數和移除操作後，順序會變成3，6，2，7，5，1，最後剩下的是4號。\n約瑟夫問題並不僅限於具體的數字，而是一個普遍的抽象問題，可以用數學方式來解決。 解決這個問題的一種經典方法是使用遞迴（recursion）。\n基於遞迴的解法如下： 如果只有一個人（N=1），那麼他就是最後的生存者，回傳他的編號。 否則，從第一個人開始報數，並將每個第M個人移除。 移除完畢後，從下一個人開始重新遞迴調用這個過程（N-1個人）。 將遞迴返回的結果（即下一輪的最後生存者編號）調整到當前的編號對應的位置，然後回傳結果。 這樣，通過不斷遞迴和移除操作，最後得到的結果就是約瑟夫問題的解。\n需要注意的是， 約瑟夫問題的解並不唯一，它取決於初始的 N 和 M 的值。\n這個問題在數學和計算機科學中都有廣泛的應用，包括編程、遊戲理論等領域。\n李永樂老師的講解 Youtube 影片\n紀錄 先介紹了當k=2時的基本情況，然後討論了更一般的情況。\n影片提供了遞推式來解決這個問題，將最後剩下的人表示為f(N, k)，其中N表示最初的人數，k表示每隔多少人殺一個。\n他們的編號不同，例如原先的 4 號現在成了 1 號，原先的 5 號現在成了 2 號，原先的 6 號現在成了 3 號。\n這意味著他們的編號相差3。\n這個差值3正好對應到每隔幾人殺一個的k值。\n因此，我們需要將這個差值加上k，這樣新的編號就會與原先的編號相同了。\n影片解釋了遞推式的計算過程，並提到如果計算結果大於N，則需要減去N，相當於去取餘數。\n最後給予一個範例呈現。\nN = 8，總共 8 個人。\nK = 3，每隔 3 個人殺一個。\n最後生存者是 7 號。\n流程圖 若想要清楚流程圖，大力推薦參考資料【Josephus Problem - GeeksforGeeks】\n當 N = 5, K = 1 時候的流程圖。最後生存者是 3 號。\n實作 // return the position of last man survives function Josephus(n, k) { let i = 1; let ans = 0; while (i \u0026lt;= n) { // update the value of ans ans = (ans + k) % i; // 再進行下一個 i i++; } // 編號從 1 開始 return ans + 1; } // This code is contributed by sarveshc111. let n = 1; // 總人數 let k = 1; // 每次報數的數字 let lastSurvivor = Josephus(n, k); console.log(\u0026#34;最後生存者的編號是：\u0026#34;, lastSurvivor); 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-29T14:03:21+08:00","permalink":"https://yue-jenny.github.io/algo/josephus-problem/","title":"約瑟夫問題（Josephus Problem）"},{"content":"樹的分類 樹（Tree）作為一種常見的資料結構，隨著時間的推移，出現了多種不同的樹型結構，每一種都具有特定的優點和應用場景。以下是樹的演進史中一些重要的樹型結構：\n二叉樹（Binary Tree） 二叉樹是最簡單且最基本的樹型結構。 每個節點最多有兩個子節點，分別稱為左子節點和右子節點。 二叉樹具有簡單的結構和易於實現的特點，但在某些情況下可能出現不平衡的情況，影響查找效率。 二叉搜索樹（Binary Search Tree） 二叉搜索樹在二叉樹的基礎上進一步定義了鍵的有序性。 對於任意節點，其左子樹中的所有鍵都小於節點的鍵，而右子樹中的所有鍵都大於節點的鍵。 這使得二叉搜索樹能夠快速進行查找、插入和刪除操作。然而，如果插入或刪除操作不當，可能導致樹的不平衡，影響性能。 平衡樹（Balanced Tree） 為了解決二叉搜索樹可能出現的不平衡問題，出現了多種平衡樹結構。\n其中最經典的是紅黑樹（Red-Black Tree），它通過在二叉搜索樹的基礎上增加了額外的紅黑色屬性，保持了樹的平衡性。紅黑樹具有良好的平衡性和高效的查找、插入和刪除操作，被廣泛應用於各種應用中。\n平衡樹（Balanced Tree）是一種廣泛的樹型資料結構，其中包括了許多種類的樹，如 AVL 樹、紅黑樹、B 樹、B+ 樹、B* 樹、2-3 樹等。\n這些樹都有共同的性質：在進行插入或刪除操作時，能維持樹的平衡，也就是說，任意兩個葉節點之間的最長路徑和最短路徑的長度差是有限的。這樣的性質確保了在進行搜尋、插入、刪除等操作時，時間複雜度能維持在對數級別，提高了操作效率。\n以下列出幾種平衡樹 AVL 樹（AVL Tree）\nAVL樹是最早提出的自平衡二元搜尋樹。它通過維護每個節點的平衡因子（左子樹高度減去右子樹高度）在每次插入或刪除操作後進行旋轉調整，以確保樹保持平衡。 【資料結構：平衡樹 (Balanced Tree) - AVL Tree】 紅黑樹（Red-Black Tree）\n通過在二叉搜索樹的基礎上增加了額外的紅黑色屬性，保持了樹的平衡性。 B樹（B-Tree, Balanced Sort Tree）\nB樹是一種多路平衡搜索樹(查找路徑不止兩個)，特別適合處理大量的數據和磁盤存儲。B樹的特點是每個節點可以有多個子節點，並且可以容納多個鍵。 B樹通過調整節點的大小和節點的分裂合併策略，保持了樹的平衡性，同時提供了高效的插入、刪除和查找操作。 B樹廣泛應用於文件系統和數據庫等場景。 B樹相對平衡二叉樹在節點空間的利用率上進行改進，B樹在每個節點保存更多的數據，減少了樹的高度，從而提升了查找的性能，在數據庫應用中，B樹的每個節點存儲的數據量大約為4K, 這是因為考慮到磁盤數據存儲是採用塊的形式存儲的，每個塊的大小為4K，每次對磁盤進行IO數據讀取時，同一個磁盤塊的數據會被一次性讀取出來，所以每一次磁盤IO都可以讀取到B樹中一個節點的全部數據。 一種特殊的 B 樹：2-3 樹 2-3 樹是一種特殊的 B 樹，其每個節點可以存儲 1 或 2 個值並且具有 2 或 3 個孩子節點，故名為 2-3 樹。 當一個節點的元素數量超過 2 個（即 3 個）時，則進行一次分裂，將中間的元素推送至父節點，並將原節點分裂為兩個節點。 由於 2-3 樹在插入和刪除操作時會進行動態調整，以確保所有葉子節點的深度保持一致，因此 2-3 樹是一種自平衡樹。 B+樹（B+ Tree）\nB+樹是在B樹的基礎上又一次的改進，其主要對兩個方面進行了提升，一方面是查詢的穩定性，另外一方面是在數據排序方面更友好。 B+樹是在B樹基礎上進一步優化和擴展的一種結構。 與B樹不同，B+樹將鍵值只存儲在葉子節點，內部節點只存儲鍵值的索引。這樣的設計使得B+樹具有更高的查找效率和更好的節點利用率。B+樹常用於關聯數據庫中的索引結構，能夠提供高效的範圍查詢和排序操作。 B* 樹\nB* 樹又是對B+數的再一次改進，在B+樹的構建過程中，為了保持樹的平衡，節點的合併拆分是比較耗費時間的，所以B* 樹就是在如何減少構建中節點合併和拆分的次數，從而提升樹的數據插入、刪除性能。 B* 樹是 B 樹的一種變體，它和 B 樹相似，但在結構和調整規則上稍有不同。 在 B* 樹中，非根和非葉子節點至少有 2/3 的子節點，這比 B 樹的一半多。此外，B* 樹在插入和刪除操作上也做了優化：在插入時，如果當前節點的子節點已經滿了，則會嘗試將數據轉移到相鄰的兄弟節點，只有當轉移無法完成時，才會分裂節點；在刪除時，如果該節點的子節點數少於最小值，則會嘗試從相鄰的兄弟節點借數據，只有當借用無法完成時，才會合併節點。這些優化使得 B* 樹的磁盤 I/O 操作相對較少，提高了效率。 B+ 樹和 B 樹的對比，來源：【平衡二叉樹、B樹、B+樹、B*樹理解其中一種你就都明白了】 B+ 樹查詢速度更穩定：B+ 所有關鍵字數據地址都存在葉子節點上，所以每次查找的次數都相同所以查詢速度要比B樹更穩定。 B+ 樹天然具備排序功能： B+ 樹所有的葉子節點數據構成了一個有序鍊錶，在查詢大小區間的數據時候更方便，數據緊密性很高，緩存的命中率也會比B樹高。 B+ 樹全節點遍歷更快： B+樹遍歷整棵樹只需要遍歷所有的葉子節點即可，而不需要像 B 樹一樣需要對每一層進行遍歷，這有利於數據庫做全表掃描。 B 樹相對於 B+ 樹的優點是，如果經常訪問的數據離根節點很近，而 B 樹的非葉子節點本身存有關鍵字和數據，所以在查詢這種數據檢索的時候會要比B+樹快。 堆（Heap） 一種特殊的樹結構，常用於實現優先級佇列。\n字典樹（Trie） 一種用於有效儲存和檢索字符串的樹結構，也稱為前綴樹（Prefix Tree）。\n哈夫曼樹（Huffman Tree） 一種用於無損數據壓縮的特殊二叉樹，將頻率較高的字符編碼為較短的位元串。\n隨著技術的發展和需求的變化，還出現了其他種類的樹型結構，如AVL樹、Splay樹、Trie樹等，每種結構都針對不同的場景和需求進行了優化和改進。這些樹的演進史反映了對於高效查找和存儲的不斷追求和優化。\n參考 【平衡二叉樹、B樹、B+樹、B*樹理解其中一種你就都明白了】 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-27T15:46:47+08:00","permalink":"https://yue-jenny.github.io/data-structure/classification-of-trees/","title":"資料結構：樹的分類"},{"content":"AVL樹 AVL樹是最早提出的自平衡二元搜尋樹。它通過維護每個節點的平衡因子(左子樹高度減去右子樹高度)。\n在每次插入或刪除操作後進行調整，以確保樹保持平衡。\nAVL 樹由 Georgy Adelson-Velsky 和 Evgenii Landis 於 1962 年提出的一種自平衡二元搜尋樹。\n它的平衡性通過維護每個節點的平衡因子（Balance Factor）來確保，該平衡因子表示左子樹高度減去右子樹高度的差值。\n當在AVL樹中進行插入或刪除操作時，可能會破壞樹的平衡。\n在這種情況下，需要通過旋轉操作來恢復平衡。旋轉操作分為左旋和右旋兩種：\n左旋：當某個節點的右子樹高度過高時，進行左旋操作可以降低右子樹高度，同時提升該節點的左子樹高度。 右旋：當某個節點的左子樹高度過高時，進行右旋操作可以降低左子樹高度，同時提升該節點的右子樹高度。 這些旋轉操作的目的是保持樹的平衡，使得樹的高度始終保持在O(log n)的範圍內。儘管AVL樹提供了較嚴格的平衡性，但相對而言，它需要更多的旋轉操作，因此插入和刪除的成本較高。\n以下是 AVL 樹進行平衡的規則 AVL 樹在插入或刪除節點後，可能會失去平衡，也就是某些節點的左子樹和右子樹的高度差超過 1。為了維護 AVL 樹的平衡性，我們需要進行旋轉操作。\n左旋轉（Left Rotation）： 條件：當一個節點的右子樹比左子樹高度多 2 層或以上時，我們進行左旋轉操作。 作法：左旋轉可以將該節點的右子樹提升為新的根節點，同時將原根節點降低為新根節點的左子樹。這樣可以保持左子樹和右子樹的高度差不超過 1。 右旋轉（Right Rotation）： 條件：當一個節點的左子樹比右子樹高度多 2 層或以上時，我們進行右旋轉操作。 作法：右旋轉可以將該節點的左子樹提升為新的根節點，同時將原根節點降低為新根節點的右子樹。這樣可以保持左子樹和右子樹的高度差不超過 1。 左右旋轉（Left-Right Rotation）： 條件：當一個節點的左子樹比右子樹高度多，且該節點的左子樹的右子樹比左子樹的左子樹高度多時，我們進行左右旋轉操作。 作法：先對該節點的左子節點進行左旋轉，然後對原節點進行右旋轉。 右左旋轉（Right-Left Rotation）： 條件：當一個節點的右子樹比左子樹高度多，且該節點的右子樹的左子樹比右子樹的右子樹高度多時，我們進行右左旋轉操作。 作法：先對該節點的右子節點進行右旋轉，然後對原節點進行左旋轉。 這些旋轉操作可以調整 AVL 樹的結構，使其保持平衡。在進行插入或刪除操作後，我們需要檢查節點的平衡因子，即左子樹高度減去右子樹高度。\n如果平衡因子大於 1，表示左子樹高於右子樹，則需要進行右旋轉或左右旋轉； 如果平衡因子小於 -1，表示右子樹高於左子樹，則需要進行左旋轉或右左旋轉。 通過這些旋轉操作，我們可以保持 AVL 樹的平衡性。\n實作 class AVLNode { constructor(value) { this.value = value; this.left = null; this.right = null; this.height = 1; } } class AVLTree { constructor() { this.root = null; } // Helper function to get the height of a node getHeight(node) { if (node === null) return 0; return node.height; } // Helper function to update the height of a node updateHeight(node) { node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); } // Helper function to perform left rotation leftRotate(z) { const y = z.right; const T2 = y.left; y.left = z; z.right = T2; this.updateHeight(z); this.updateHeight(y); return y; } // Helper function to perform right rotation rightRotate(z) { const y = z.left; const T3 = y.right; y.right = z; z.left = T3; this.updateHeight(z); this.updateHeight(y); return y; } // Helper function to get the balance factor of a node getBalanceFactor(node) { if (node === null) return 0; return this.getHeight(node.left) - this.getHeight(node.right); } // Helper function to insert a value into the AVL tree insertHelper(node, value) { if (node === null) { return new AVLNode(value); } if (value \u0026lt; node.value) { node.left = this.insertHelper(node.left, value); } else if (value \u0026gt; node.value) { node.right = this.insertHelper(node.right, value); } else { // Duplicate values are not allowed in AVL tree return node; } this.updateHeight(node); const balanceFactor = this.getBalanceFactor(node); // Left Left Case if (balanceFactor \u0026gt; 1 \u0026amp;\u0026amp; value \u0026lt; node.left.value) { return this.rightRotate(node); } // Right Right Case if (balanceFactor \u0026lt; -1 \u0026amp;\u0026amp; value \u0026gt; node.right.value) { return this.leftRotate(node); } // Left Right Case if (balanceFactor \u0026gt; 1 \u0026amp;\u0026amp; value \u0026gt; node.left.value) { node.left = this.leftRotate(node.left); return this.rightRotate(node); } // Right Left Case if (balanceFactor \u0026lt; -1 \u0026amp;\u0026amp; value \u0026lt; node.right.value) { node.right = this.rightRotate(node.right); return this.leftRotate(node); } return node; } // Function to insert a value into the AVL tree insert(value) { this.root = this.insertHelper(this.root, value); } } 參考資料 👍 大力推薦參考圖文並茂超完整圖解何時該左旋何時該右旋【JavaScript 學演算法（十五）- AVL-Tree】 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-27T07:15:40+08:00","permalink":"https://yue-jenny.github.io/data-structure/balance-tree-avl-tree/","title":"資料結構：平衡樹 (Balanced Tree) - AVL Tree"},{"content":"拜占庭問題 Byzantine Problem 拜占庭問題源自電腦科學，尤其是分散式系統的領域。此問題是命名來自於拜占庭將軍的想像問題，這些將軍被敵人包圍，他們必須達成共識以確定下一步的行動：攻擊或撤退。他們的唯一通訊方式是通過傳遞消息，但問題在於，其中可能有叛徒傳遞假消息，導致非叛徒將軍無法達成共識。\n在電腦科學中，這類似於一個分散式系統中的節點必須達成共識，即使有些節點可能故障或行為異常。這種情況可能會導致系統的一部分接受一種結果，而另一部分接受另一種結果，進而無法達成共識。\n解決拜占庭問題的一種方法是使用拜占庭容錯（Byzantine Fault Tolerance, BFT）演算法。 BFT演算法旨在處理系統中的故障節點，以達到整個系統的共識。\n最知名的BFT演算法是拜占庭將軍問題的原始解答 - 羅馬軍團演算法。\n然而，這些算法通常需要多數的正常節點來達到共識，並且在節點數量大時效率降低。\n因此，一種比較現代且廣泛使用的方法是使用區塊鏈技術，例如比特幣使用的工作量證明（Proof of Work, PoW）和以太坊等平台使用的權益證明（Proof of Stake, PoS）。這些協議能夠在大規模網路中達成共識，並且對抗拜占庭節點。\n工作證明（Proof of Work，PoW） 工作證明（Proof of Work，PoW）是一種在區塊鏈技術中常用的共識機制。它的目的是確保在新增區塊到區塊鏈上時需要進行一定的計算工作，以驗證該區塊的有效性。Proof of Work 最早是由比特幣的白皮書提出，並成為許多其他加密貨幣所採用的共識機制。\n在Proof of Work中，節點（或礦工）需要解決一個複雜的數學問題，稱為工作證明。這個問題通常是一個哈希函數的輸入，並要求找到特定的輸出，滿足特定的條件。這個問題的解決過程需要進行大量的計算，而且是具有隨機性的。解決問題的節點被稱為礦工，並且他們需要提供工作證明來證明他們完成了計算工作。\n一旦一個節點找到了正確的工作證明，它就可以將該區塊添加到區塊鏈上。其他節點可以輕鬆地驗證這個工作證明的有效性，只需將該證明應用於相同的數學問題上即可驗證其正確性。通過這種方式，工作證明確保了節點必須投入一定的計算資源才能添加新的區塊，從而降低了對惡意行為者的攻擊可能性。\nProof of Work機制的一個重要特點是競爭性。因為節點們需要解決同一個問題，所以他們之間會進行競爭，以便第一個找到正確的工作證明並添加區塊到區塊鏈上。這也就意味著礦工必須擁有更多的計算資源（如計算能力和能源）才能有更高的概率成為下一個添加區塊的節點。\n儘管Proof of Work是一個有效且廣泛使用的共識機制，但它也存在一些問題。首先，它需要大量的計算能力和能源消耗，這導致了高昂的運營成本。\n其次，Proof of Work機制也可能引發礦工的中心化問題，因為那些擁有更多計算資源的節點有更高的概率獲得獎勵，這使得礦池（多個礦工共同合作挖礦）形成，有可能導致某些節點集中控制了整個區塊鏈網絡。\n為了解決這些問題，一些新的共識機制如Proof of Stake（PoS）已經被提出並得到應用。PoS機制通過節點持有的加密貨幣數量來決定他們添加新區塊的權益，從而減少了計算能力和能源的消耗，並有助於達到更高的去中心化程度。\n權益證明（Proof-Of-Stake）, PoS Proof of Stake（PoS）是一種區塊鏈共識機制，與Proof of Work（PoW）相對應。在PoS機制中，節點被選為下一個區塊的添加者，並且他們的選擇是基於他們所持有的加密貨幣的數量，而不是計算能力。\n在PoS機制中，持有加密貨幣的節點被稱為權益者（stakers）。每個權益者都有機會被選為下一個區塊的添加者，並獲得相應的獎勵。權益者的機會被選中的概率與他們持有的加密貨幣數量成正比，也就是說，持有更多貨幣的節點有更高的概率獲得獎勵。\n與PoW不同，PoS機制不需要節點進行大量的計算工作。取而代之的是，節點需要在網絡上鎖定一定數量的加密貨幣作為抵押品。這種抵押品的數量反映了權益者在區塊鏈網絡中的參與程度和利益，並且可以作為選擇下一個區塊添加者的依據。\nPoS機制的優勢之一是節能和環保。由於不需要進行大量的計算工作，PoS消耗的能源較少，這對於環境友好型區塊鏈項目尤其重要。此外，PoS機制減少了中心化的風險，因為節點被選中的概率與他們持有的貨幣數量成正比，而不是與計算能力相關。\n然而，PoS機制也存在一些挑戰和風險。一個主要的挑戰是所謂的\u0026quot;Nothing at Stake\u0026quot;問題，即節點可以同時參與多個分支，並在分支間進行雙重花費攻擊。這是因為在PoS機制中，選擇分支的成本相對較低，而且沒有額外的計算資源需求。為了解決這個問題，一些PoS區塊鏈項目採取了額外的機制，如罰款或貨幣鎖定期，來防止節點進行惡意行為。\n總的來說，PoS機制提供了一種替代PoW的共識機制，並且具有節能、環保和降低中心化風險的潛力。然而，每種共識機制都有其優點和局限性，適用的具體情況取決於項目的目標、需求和環境條件。\n更詳細可以參考【區塊鏈 Blockchain – 共識機制之權益證明 Proof-Of-Stake - Samson\u0026rsquo;s Blog】\nNothing-at-Stake攻擊（無損攻擊） Nothing-at-Stake攻擊（無損攻擊）是一種可能出現在Proof of Stake（PoS）共識機制中的攻擊方式。該攻擊利用了PoS機制的特性，使得節點可以在分叉的區塊鏈上同時參與多個分支，而不需要承擔任何成本或風險。\n在PoS機制中，權益者（stakers）需要在網絡上鎖定一定數量的加密貨幣作為抵押品，以獲得權益。當選擇下一個區塊的添加者時，通常是根據權益者所持有的加密貨幣數量來進行選擇。這意味著持有更多貨幣的節點有更高的概率獲得獎勵並添加新的區塊。\n在Nothing-at-Stake攻擊中，一個權益者可以同時在分叉的區塊鏈上創建多個分支，並在每個分支上添加區塊，而不需要承擔任何成本。這是因為在PoS機制中，選擇分支的成本相對較低，並且節點不需要進行大量的計算工作。因此，一個權益者可以在多個分支上同時參與，並將自己的資源分散到不同的分支中。\n這種攻擊的結果是導致分叉的區塊鏈上存在多個有效的分支，並且權益者可以在每個分支上添加區塊。這會導致區塊鏈的確定性和一致性問題，因為不同的節點可能看到不同的區塊鏈狀態。\nNothing-at-Stake攻擊的解決辦法是引入額外的機制來防止權益者進行這種攻擊。這些機制可能包括罰款機制或貨幣鎖定期。罰款機制可以使得權益者在同時參與多個分支時面臨損失，從而降低了進行攻擊的動機。貨幣鎖定期則要求在權益者切換到新的分支之前需要等待一段時間，這樣可以使得分支間的選擇更加明確，並增加攻擊的成本和風險。\n總結來說，Nothing-at-Stake攻擊是一種利用PoS機制特性的攻擊方式，使得權益者可以在分叉的區塊鏈上同時參與多個分支而不承擔成本。這種攻擊可能導致確定性和一致性問題。為了解決這個問題，需要引入額外的機制來增加攻擊的成本和風險。\n拜占庭容錯（Byzantine Fault Tolerance, BFT）演算法 拜占庭容錯（Byzantine Fault Tolerance, BFT）是一種在分散式計算領域中的演算法，旨在保護系統免於所謂的拜占庭故障（即使是在有故障或惡意節點的情況下也能達成共識）。一個具有拜占庭容錯的系統可以繼續運行，即使一部分節點無法回應，回應錯誤，或者回應惡意信息。\nBFT演算法通常需要一個協議，來確保所有節點在故障或惡意的干擾下也能達成共識。最為人所知的BFT演算法是羅馬軍團演算法（the Byzantine Generals\u0026rsquo; Algorithm）。該演算法需要所有節點都能互相通訊，並且只有當超過三分之二的節點是誠實的（即，不是拜占庭節點），系統才能達成共識。\n舉例來說，假設有一個由 4 個節點的系統，其中 1 個是拜占庭節點。在這種情況下，即使拜占庭節點嘗試分散系統的一致性，其他 3 個誠實的節點仍然可以透過他們的協議達成共識。即使有一個節點返回錯誤或惡意的信息，只要有超過一半的節點能達成共識，系統就能繼續運行。\n然而，該演算法在面對大規模系統時可能效率不高，因為每個節點都必須與其他所有節點通訊。為了解決這一問題，研究人員開發了多種改進的BFT演算法，例如實用拜占庭容錯（Practical Byzantine Fault Tolerance，PBFT）等等，這些都是為了提高在面對拜占庭故障時的效率和效能。\n實現拜占庭容錯的演算法有許多，其中最著名的可能是實用拜占庭容錯（Practical Byzantine Fault Tolerance, PBFT）演算法。PBFT透過一種稱為\u0026quot;三階段握手\u0026quot;的過程，確保了系統中的所有節點都能達成共識。此外，還有很多其他的演算法，例如 Federated Byzantine Agreement（聯盟拜占庭協議）等等。\n拜占庭容錯是分散式系統設計的一個重要環節，尤其是在區塊鏈和分散式資料庫等領域。通過實現拜占庭容錯，系統設計者可以確保他們的系統在面對各種故障和攻擊時，都能保持穩定和正確的運作。\n實用拜占庭容錯（Practical Byzantine Fault Tolerance，PBFT） 實用拜占庭容錯（Practical Byzantine Fault Tolerance，PBFT）是一種共識算法，旨在解決分散式系統中可能存在的拜占庭錯誤問題。拜占庭錯誤是指在分散式系統中的節點之間存在著任意形式的錯誤行為，例如節點故障、節點篡改或惡意行為等。\nPBFT的目標是在存在最多f個拜占庭錯誤的情況下，達成一致並保護系統的正確性和安全性。它基於共識算法，要求系統中的所有節點通過相互之間的通訊達成一致的結論，即使在存在拜占庭錯誤的情況下也能保證正確性。\nPBFT的運作原理如下：\n角色：系統中的節點分為主節點（primary）和副本節點（replica）。主節點負責提議和領導共識過程，而副本節點則驗證和接受主節點的提議。 預准備階段（Pre-prepare phase）：主節點向所有副本節點發送預准備消息，包含提議的內容。副本節點驗證預准備消息的合法性，確認提議內容的有效性。 准備階段（Prepare phase）：當副本節點驗證預准備消息後，將回覆一個確認消息（prepare message）給其他節點，表示該提議是有效的。這些確認消息由主節點收集並廣播給所有節點。 提議階段（Commit phase）：當主節點收到足夠的確認消息，確保有足夠多的節點同意該提議，它就會向副本節點發送確認消息（commit message），要求副本節點執行該提議。 執行階段（Execute phase）：副本節點收到足夠的確認消息後，執行該提議的操作，並將執行結果傳播給其他節點。 完成階段（Reply phase）：主節點收到足夠的執行結果後，將結果回覆給客戶端。 PBFT通過共識階段的多次循環來確保系統的安全性和正確性。它需要2f + 1個節點來容忍最多f個拜占庭錯誤，並保證系統的正常運行。\nPBFT具有良好的容錯性和安全性，但也存在一些限制。例如，它需要大量的網絡通訊，尤其在節點數量很多時，通訊開銷會變得很高。此外，當存在惡意行為的節點時，PBFT的性能可能會受到影響。\n總的來說，PBFT是一種實用的共識算法，適用於需要高度安全性和容錯性的分散式系統，例如區塊鏈和分布式資料庫。它通過多階段的共識過程確保系統的一致性和正確性，同時能夠容忍一定數量的拜占庭錯誤。\n想知道更詳細，推薦可以看【若想搞懂區塊鏈就不能忽視的經典：PBFT】\n這個主題的延伸可探討的問題包括： 如何提高拜占庭容錯演算法的效率？ 工作量證明與權益證明在實際應用中的優劣比較？ 現有的拜占庭容錯演算法是否足夠應對實際運作中的分散式系統？ 為什麼某些分散式系統選擇使用拜占庭容錯，而其他則不？ 如何確保在面對更複雜的拜占庭攻擊時，分散式系統的安全性？ 如何減少達成共識所需的能源消耗，特別是在使用工作量證明這種需要大量計算的方法時？ 參考資料 👐 【若想搞懂區塊鏈就不能忽視的經典：PBFT】 【區塊鏈 Blockchain – 共識機制之權益證明 Proof-Of-Stake - Samson\u0026rsquo;s Blog】 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-26T17:12:26+08:00","permalink":"https://yue-jenny.github.io/algo/byzantine-problem/","title":"拜占庭問題 Byzantine Problem"},{"content":"介紹 LFU（Least Frequently Used）快取是一種用於緩存（cache）中數據替換的算法。\n它的基本思想是，當緩存空間不足時，優先移除那些最少被使用的數據，以便為新的數據騰出空間。\nLFU快取跟其他替換算法（如LRU）不同，它不僅考慮數據的訪問頻率，還考慮了每個數據的實際使用次數。每當數據被訪問時，其相應的使用計數器會增加。當緩存空間不足時，LFU快取將選擇使用計數器值最小的數據進行替換。\n以下是LFU快取的基本操作流程：\n初始化：設定緩存的最大容量和空的緩存數據結構。 存儲數據：當數據要存入緩存時，首先檢查數據是否已存在於緩存中。如果是，則增加相應的使用計數器值；如果不是，則將數據添加到緩存中並將其使用計數器初始化為1。 訪問數據：當數據被訪問時，相應的使用計數器增加1。 替換數據：當緩存空間不足時，找到使用計數器值最小的數據進行替換。如果有多個數據具有相同的最小使用計數器值，則選擇最早被存儲的那個進行替換。 更新緩存：如果緩存中的數據發生變化（新增、訪問或替換），需要相應地更新緩存數據結構。 LFU快取的優點是能有效地保留那些被頻繁訪問的數據，並且在緩存空間不足時優先替換那些較少使用的數據，從而提高緩存的效率。然而，LFU快取也有一些限制，例如計數器可能受到訪問模式的影響，並且需要額外的存儲空間來維護使用計數器。因此，在實際應用中，需要仔細考慮數據訪問模式以及性能和存儲要求等因素，選擇最適合的緩存替換算法。\n實作 class Node { constructor(key, value) { this.key = key; this.value = value; this.frequency = 1; this.prev = null; this.next = null; } } class DLinkedList { constructor() { // 建立兩個 node，分別為 head 與 tail this.head = new Node(null, null); this.tail = new Node(null, null); // head 的 next 指標指向 tail this.head.next = this.tail; // tail 的 prev 指標指向 head this.tail.prev = this.head; } removeNode(node) { // 前一個 node (node.prev) 的 next 指標指向下一個 node (node.next) node.prev.next = node.next; // 下一個 node (node.next)的 prev 指標指向前一個 node (node.prev) node.next.prev = node.prev; } addNode(node) { // node 的 next 指標指向 tail node.next = this.tail; // node 的 prev 指標指向 tail 的前一個 node (tail.prev) node.prev = this.tail.prev; // tail 的前一個 node (tail.prev) 的 next 指標指向 node this.tail.prev.next = node; // tail 的 prev 指標指向 node this.tail.prev = node; } removeHead() { let node = this.head.next; this.removeNode(node); return node.key; } isEmpty() { return this.head.next === this.tail; } } class LFUCache { constructor(capacity) { this.capacity = capacity; this.currentSize = 0; this.leastFrequency = 0; this.cache = new Map(); // store { key : Node } this.frequencyList = new Map(); // store { frequency : DLinkedList } } get(key) { if (!this.cache.has(key)) { return -1; } let node = this.cache.get(key); this.updateFrequency(node); return node.value; } put(key, value) { if (this.capacity === 0) { return; } if (this.cache.has(key)) { let node = this.cache.get(key); node.value = value; this.updateFrequency(node); } else { // 目前快取數量已經到達最大容量，所以要刪除『最小頻率』且『最舊』的快取 if (this.currentSize === this.capacity) { let leastFrequentList = this.frequencyList.get(this.leastFrequency); let deletedKey = leastFrequentList.removeHead(); this.cache.delete(deletedKey); this.currentSize--; } // 要建立新的快取，先建立 Node，再來存入 cache 中。 // 再來則是存入頻率為 1 的的 frequencyList。 // 若沒有頻率為 1 的，建立一個。若有，則新增一個 node。 // 並且要將最小頻率設定為 1。 // 還要將目前的快取數量加上 1。 let node = new Node(key, value); this.cache.set(key, node); if (!this.frequencyList.has(1)) { this.frequencyList.set(1, new DLinkedList()); } this.frequencyList.get(1).addNode(node); this.leastFrequency = 1; this.currentSize++; } } /** * @param {*} node * updateFrequency 方法的工作流程如下： * - 接受一個節點作為參數，這個節點代表一個緩存項。 * - 查找該節點當前的訪問頻率，並找到該頻率對應的雙向鍊表（我們在 frequencyList 中為每個頻率都儲存了一個雙向鍊表，鍊表中的每個節點都是該頻率下的緩存項）。 * - 將這個節點從當前訪問頻率的雙向鍊表中移除。 * - 檢查移除節點後，當前頻率的鍊表是否為空。如果為空，並且該頻率恰好是當前的最小頻率，那麼將最小頻率加 1。 * 這是因為我們剛剛移除了頻率最低的節點，所以最低頻率需要增加。 * - 將節點的訪問頻率加 1。 * - 將節點加入到新的頻率對應的雙向鍊表中。如果新的頻率還沒有對應的鍊表，那麼建立一個新的鍊表。 * * 這個方法的主要作用是維護緩存中的訪問頻率資訊，確保我們能夠正確地追蹤和更新每個緩存項的訪問頻率，並且在需要時能夠找到當前訪問頻率最低的緩存項。 * 這是實現 LFU 緩存算法的關鍵部分。 */ updateFrequency(node) { let oldFrequency = node.frequency; let oldList = this.frequencyList.get(oldFrequency); oldList.removeNode(node); // 檢查移除節點後，當前頻率的鍊表是否為空。 // 如果為空，並且該頻率恰好是當前的最小頻率，那麼將最小頻率加 1。 // 表示 leastFrequency 已經不是最小頻率，往上加 1。 /** * 如果我們將最小頻率增加 1，確實可能會出現沒有對應的 frequencyList 的情況。 * 然而，這並不會產生問題，因為 leastFrequency 僅在需要刪除最少使用的節點時（也就是當 put 操作的時候，緩存容量已滿）才會被用到。 * 而在這種情況下，由於有新的節點進入，必然會有新的 frequencyList 被創建。 */ if (oldFrequency === this.leastFrequency \u0026amp;\u0026amp; oldList.isEmpty()) { this.leastFrequency++; } // 將節點的訪問頻率加 1。 node.frequency++; // 如果新的頻率還沒有對應的鍊表，那麼建立一個新的鍊表。 if (!this.frequencyList.has(node.frequency)) { this.frequencyList.set(node.frequency, new DLinkedList()); } // 將節點加入到新的頻率對應的雙向鍊表中。 this.frequencyList.get(node.frequency).addNode(node); } } let lfuCache = new LFUCache(2); lfuCache.put(\u0026#39;1\u0026#39;, \u0026#39;1\u0026#39;); lfuCache.put(\u0026#39;2\u0026#39;, \u0026#39;2\u0026#39;); console.log(\u0026#39;Output: 1\u0026#39;, lfuCache.get(\u0026#39;1\u0026#39;)); // Output: 1 lfuCache.put(\u0026#39;3\u0026#39;, \u0026#39;3\u0026#39;); console.log(\u0026#39;Output: -1\u0026#39;, lfuCache.get(\u0026#39;2\u0026#39;)); // Output: -1 console.log(\u0026#39;Output: 3\u0026#39;, lfuCache.get(\u0026#39;3\u0026#39;)); // Output: 3 lfuCache.put(\u0026#39;4\u0026#39;, \u0026#39;4\u0026#39;); console.log(\u0026#39;Output: -1\u0026#39;, lfuCache.get(\u0026#39;1\u0026#39;)); // Output: 1 console.log(\u0026#39;Output: 3\u0026#39;, lfuCache.get(\u0026#39;3\u0026#39;)); // Output: 3 console.log(\u0026#39;Output: 4\u0026#39;, lfuCache.get(\u0026#39;4\u0026#39;)); // Output: 4 總結 LFU確實是一種以使用頻率作為判斷標準的緩存替換算法，當緩存空間不足時，它將移除最少被使用的數據。\n這種方法可以保證在空間有限的情況下，最常被使用的數據能夠保留在緩存中。然而該算法也有其限制，如計數器可能受到訪問模式的影響，並且需要額外的存儲空間來維護使用計數器。\n所以在實際應用中，需要根據數據訪問模式、性能需求、存儲空間等因素來選擇最適合的緩存替換算法，可能是LFU，也可能是其他如LRU（Least Recently Used）、**FIFO（First In, First Out）**等算法。\n最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-23T14:42:23+08:00","permalink":"https://yue-jenny.github.io/system-design/performance-optimization/lfu-cache/","title":"效能優化：LFU Cache (Least Frequently Used Cache)"},{"content":"介紹 Trie（也稱為字典樹或前綴樹）是一種特殊的資料結構，用於高效地存儲和檢索字符串集合。\n該結構的名稱來自於英文單詞「retrieval」的前三個字母。\nTrie 使用一種樹狀結構來組織和存儲字符串。\n每個節點代表一個字元，從根節點到葉節點的路徑形成一個字符串。\n在 Trie 中，共享相同前綴的字符串共用相同的前綴節點，從而實現高效的儲存和檢索。\nTrie 的主要優點是： 前綴匹配：Trie 可以非常快速地進行前綴匹配，這使得它在許多字符串檢索的應用中非常有用。例如，你可以使用 Trie 在一個大型字典中快速查詢以特定前綴開始的單詞。 空間效率：儘管 Trie 在存儲字符串集合時可能需要較多的內存，但當字符串共享相同前綴時，Trie 可以有效地壓縮存儲空間，減少重複數據的存儲。 插入和查詢效率：Trie 的插入和查詢操作的時間複雜度都是 O(m)，其中 m 是字符串的平均長度。這使得 Trie 在需要高效的插入和查詢操作的情況下非常有用。 Trie 的基本結構由節點和指向子節點的指針組成。每個節點包含一個字符和一個指向下一級節點的指針數組（通常是一個數組，索引對應於字符集中的字符）。\n此外，每個節點還可以包含其他有關字符串的訊息，例如計數器或標記，以滿足特定的應用需求。\n基本的插入和查詢操作 Trie 的插入操作非常簡單，只需按照字符串的每個字符在 Trie 中進行遞歸查找。如果遇到缺少的字符，則創建相應的節點。當達到字符串的結尾時，可以在終止節點中標記該字符串的結束。\nTrie 的查詢操作也很簡單，只需按照要查詢的字符串的每個字符在 Trie 中進行遞歸查找。如果遇到缺少的字符或到達了 Trie 的結尾，則可以確定該字符串不在 Trie 中。否則，繼續遞歸查找直到字符串的結尾。\n除了基本的插入和查詢操作，Trie 還可以進行前綴匹配、模式匹配和字典排序等操作。\n總之，Trie 是一種非常有用的資料結構，特別適合處理字符串集合和字典應用。\n它提供了高效的插入、查詢和前綴匹配功能，同時具有優秀的空間壓縮性能。\n實作 class TrieNode { constructor() { this.children = {}; this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insert(word) { let current = this.root; for (let i = 0; i \u0026lt; word.length; i++) { const char = word[i]; if (!current.children[char]) { current.children[char] = new TrieNode(); } current = current.children[char]; } current.isEndOfWord = true; } search(word) { let current = this.root; for (let i = 0; i \u0026lt; word.length; i++) { let char = word[i]; if (!current.children[char]) { return false;// 字符不存在，該字符串不在 Trie 中 } current = current.children[char]; } return current.isEndOfWord;// 判斷是否為一個完整的單詞 } startsWith(prefix) { let current = this.root; for (let i = 0; i \u0026lt; prefix.length; i++) { let char = prefix[i]; if (!current.children[char]) { return false; // 字符不存在，該前綴不在 Trie 中 } current = current.children[char]; } return true; // 所有前綴字符都存在於 Trie 中 } } // 使用示例 const trie = new Trie(); trie.insert(\u0026#34;apple\u0026#34;); trie.insert(\u0026#34;banana\u0026#34;); trie.insert(\u0026#34;cat\u0026#34;); console.log(JSON.stringify(trie)); /** {\u0026#34;root\u0026#34;:{\u0026#34;children\u0026#34;:{ \u0026#34;a\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;p\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;p\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;l\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;e\u0026#34;:{\u0026#34;children\u0026#34;:{},\u0026#34;isEndOfWord\u0026#34;:true}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}, \u0026#34;b\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;a\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;n\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;a\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;n\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;a\u0026#34;:{\u0026#34;children\u0026#34;:{},\u0026#34;isEndOfWord\u0026#34;:true}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}, \u0026#34;c\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;a\u0026#34;:{\u0026#34;children\u0026#34;:{\u0026#34;t\u0026#34;:{\u0026#34;children\u0026#34;:{},\u0026#34;isEndOfWord\u0026#34;:true}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}},\u0026#34;isEndOfWord\u0026#34;:false}} */ console.log(trie.search(\u0026#34;apple\u0026#34;)); // true console.log(trie.search(\u0026#34;banana\u0026#34;)); // true console.log(trie.search(\u0026#34;cat\u0026#34;)); // true console.log(trie.search(\u0026#34;dog\u0026#34;)); // false console.log(trie.startsWith(\u0026#34;app\u0026#34;)); // true console.log(trie.startsWith(\u0026#34;ban\u0026#34;)); // true console.log(trie.startsWith(\u0026#34;ca\u0026#34;)); // true console.log(trie.startsWith(\u0026#34;do\u0026#34;)); // false 說明 在 insert 方法中，我們從根節點開始遍歷 Trie。對於要插入的每個字符，我們檢查它是否已經在當前節點的子節點中存在。\n如果不存在，我們創建一個新的節點並將其添加到子節點中。然後，我們將當前節點移至新創建的節點，繼續處理下一個字符。\n當處理完所有字符後，我們將最後一個字符的 isEndOfWord 屬性設置為 true，以標記字符串的結束。\nsearch 方法用於檢查給定的字符串是否存在於 Trie 中。我們從根節點開始遍歷 Trie，對於字符串的每個字符，我們檢查它是否存在於當前節點的子節點中。\n如果遍歷完所有字符後，最後一個節點的 isEndOfWord 屬性為 true，則表示該字符串存在於 Trie 中。\nstartsWith 方法用於檢查是否存在以給定前綴開頭的字符串。過程與 search 方法類似，我們遍歷前綴的每個字符，檢查它們是否存在於當前節點的子節點中。\n如果遍歷完所有前綴字符後，所有字符都存在於 Trie 中，則表示該前綴存在。\n總結 Trie 來自於英文單詞「retrieval」，用於高效地存儲和檢索字符串集合。 每個節點代表一個字符，以及從根節點到葉節點的路徑形成一個字符串。同時，共享相同前綴的字符串共用相同的前綴節點。 關於 Trie 的優點：前綴匹配、空間效率，以及插入和查詢效率。 您對 Trie 節點的組成以及 Trie 插入和查詢操作的描述。 Trie 確實可以進行前綴匹配、模式匹配和字典排序等高級操作。 是一種非常有用的資料結構，特別適合處理字符串集合和字典應用，具有高效的插入、查詢和前綴匹配功能，同時具有優秀的空間壓縮性能。 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-19T14:00:41+08:00","permalink":"https://yue-jenny.github.io/data-structure/trie/","title":"資料結構：字典樹或前綴樹 Trie"},{"content":"介紹 當你想要學習資料結構時，以下是一些建議的學習列表：\n陣列 (Array): 學習如何使用陣列存儲和訪問資料，以及陣列的基本操作，如插入、刪除和搜尋。\n串列 (Linked List): 學習如何使用串列結構來組織和存儲資料，並理解串列的插入、刪除和搜尋操作。\n堆疊 (Stack): 學習堆疊的概念和操作，包括推入 (push) 和彈出 (pop) 元素，以及堆疊的應用場景。\n佇列 (Queue): 學習佇列的概念和操作，包括入列 (enqueue) 和出列 (dequeue) 元素，以及佇列的應用場景。\n樹 (Tree): 學習樹的基本結構、遍歷方法 (如前序、中序和後序遍歷)，以及二元樹、二元搜索樹和平衡樹等特殊類型的樹。\n圖 (Graph): 學習圖的基本結構和表示方法，以及圖的遍歷、搜索和最短路徑算法，如深度優先搜索 (DFS) 和廣度優先搜索 (BFS)。\n哈希表 (Hash Table): 學習哈希表的原理和實現，理解哈希函數、碰撞解決方法和哈希表的查找和插入操作。\n堆積 (Heap): 學習堆積的概念和操作，包括最大堆和最小堆，以及堆排序和優先級佇列的應用。\n鏈接 (Hashing): 學習散列的概念和操作，包括散列函數、碰撞解決方法和散列表的查找和插入操作。\n圖算法 (Graph Algorithms): 學習常見的圖算法，如最短路徑算法 (如Dijkstra和Bellman-Ford)、最小生成樹算法 (如Prim和Kruskal) 和拓撲排序。\n字典樹 (Trie): 學習字典樹的概念和操作，特別適用於字串搜索和自動補全等應用。\n平衡樹 (Balanced Tree): 學習平衡樹的概念和實現，如紅黑樹和AVL樹，了解平衡樹的插入、刪除和查找操作。\n圖示演算法 (Graphical Algorithms): 學習用於圖示設計和模擬的算法，如迭代深化深度優先搜索 (IDDFS)。\n雜湊圖 (Hashgraph): 學習分佈式共識機制的一種形式，可以實現高性能和安全性的分佈式資料結構。\n高級資料結構 (Advanced Data Structures): 學習一些高級的資料結構，如B+樹、約瑟夫問題、線段樹、樹狀數組等。\n總結 這些只是一些常見的資料結構，你可以根據自己的興趣和需求進一步深入學習。\n此外，建議你閱讀相關的教科書或參考網上的資源，並通過編寫程式來實踐這些資料結構，以加深你的理解和熟練程度。\n最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-17T10:21:51+08:00","permalink":"https://yue-jenny.github.io/data-structure/a-list-of-learning-data-structures/","title":"當你想要學習資料結構時，以下是一些建議的學習列表"},{"content":"題目連結 289 Game of Life\n題目說明 這道題目是一個模擬遊戲，稱為康威生命遊戲（Conway\u0026rsquo;s Game of Life）。遊戲的主要元素是一個由細胞組成的網格，每個細胞可以是活著的（用1表示）或死亡的（用0表示）。\n遊戲的規則非常簡單，每個細胞與其周圍的八個鄰居（水平、垂直和對角線方向）進行交互作用，根據以下四條規則來決定下一個時刻的狀態：\n如果一個活細胞周圍的活細胞少於兩個，則該細胞會因為人口不足而死亡。 如果一個活細胞周圍有兩個或三個活細胞，則該細胞在下一代中繼續存活。 如果一個活細胞周圍的活細胞超過三個，則該細胞會因為人口過剩而死亡。 如果一個死細胞周圍正好有三個活細胞，則該細胞會因為繁殖而成為活細胞。 下一個時刻的網格狀態是通過同時應用上述規則到當前時刻的每個細胞上來生成的，出生和死亡是同時發生的。題目給定了當前網格的狀態 board，要求返回下一個時刻的狀態。\n換句話說，你需要根據上述規則對 board 中的每個細胞進行計算，並根據規則更新其狀態。最終返回更新後的網格狀態。\n解題思路 首先解釋一下生命遊戲的規則。生命遊戲在一個二維格子中進行，每一個格子代表一個生命體，這個生命體有兩種狀態：生(1)和死(0)。\n每個生命體的生死由其周圍八個格子的生命體狀態決定，規則如下：\n如果一個生命體周圍有少於2個生命體，該生命體在下一輪會死亡。 如果一個生命體周圍有2個或3個生命體，該生命體在下一輪會保持當前狀態。 如果一個生命體周圍有超過3個生命體，該生命體在下一輪會死亡。 如果一個死亡的生命體周圍有正好3個生命體，該生命體在下一輪會復活。 程式碼中的gameOfLife方法用來進行一次遊戲的迭代。\n它首先遍歷每一個生命體，並對其周圍的生命體進行計數(countLive方法用來計算周圍的生命體數量)。\n根據計數的結果，然後決定每一個生命體在下一輪的狀態。\n為了避免在計數過程中被即時更新的狀態影響，\n這個方法選擇了一種巧妙的方式，使用兩個中間狀態：live(3)代表從死亡狀態變為生存，die(2)代表從生存狀態變為死亡。\n這種方式可以讓我們在遍歷並更新狀態時，仍然可以正確計數周圍原來的生命體數量。\n在完成狀態更新後，程式碼再次遍歷每一個生命體，將之前設置的中間狀態轉換回最終的生或死狀態。\n這樣，我們就完成了生命遊戲的一次迭代。\n實作 /** * @param {number[][]} board * @return {void} Do not return anything, modify board in-place instead. */ let die = 2; let live = 3; var gameOfLife = function (board) { let rowslength = board.length; let colsLength = board[0].length; for (let r = 0; r \u0026lt; rowslength; r++) { for (let c = 0; c \u0026lt; colsLength; c++) { // 開始計算每一個 cell 未來是活細胞還是死細胞 let aroundLives = countAroundLives(board, r, c); /** 1. 如果一個活細胞周圍的活細胞少於兩個，則該細胞會因為人口不足而死亡。 2. 如果一個活細胞周圍有兩個或三個活細胞，則該細胞在下一代中繼續存活。 3. 如果一個活細胞周圍的活細胞超過三個，則該細胞會因為人口過剩而死亡。 4. 如果一個死細胞周圍正好有三個活細胞，則該細胞會因為繁殖而成為活細胞。 */ if (board[r][c] == 1 \u0026amp;\u0026amp; (aroundLives \u0026lt; 2 || aroundLives \u0026gt; 3)) { board[r][c] = die; } else if (board[r][c] == 1 \u0026amp;\u0026amp; (aroundLives == 2 || aroundLives == 3)) { // console.log(\u0026#34;Nothing to do.\u0026#34;); } else if (board[r][c] == 0 \u0026amp;\u0026amp; aroundLives == 3) { board[r][c] = live; } } } // 再來將 die / live 各轉為 0 / 1 for (let x = 0; x \u0026lt; board.length; x++) { for (let y = 0; y \u0026lt; board[0].length; y++) { if (board[x][y] == die) { board[x][y] = 0; } else if (board[x][y] == live) { board[x][y] = 1; } } } }; let countAroundLives = function (board, r, c) { // 假設 board[r][c] 為中心點，設定為 (0, 0)，周圍會有以下的位置： let aroundLocations = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, - 1]]; let count = 0; for (let i = 0; i \u0026lt; aroundLocations.length; i++) { let location = aroundLocations[i]; let x = location[0]; let y = location[1]; // 超過邊界範圍，因為 2D array 的位置不可能為負數 if ((x + r) \u0026lt; 0 || (c + y) \u0026lt; 0) { continue; } else if ((x + r) \u0026gt;= board.length || (c + y) \u0026gt;= board[0].length) { continue; } else if (board[x + r][c + y] == 1 || board[x + r][c + y] == die) { count++; } } return count; } /** * board: * 3 * / \\ * 0 1 0 \\ * 0 0 1 * 1 1 1 4 * 0 0 0 / * * expect result: * 0 0 0 * 1 0 1 * 0 1 1 * 0 1 0 */ let board = [[0, 1, 0], [0, 0, 1], [1, 1, 1], [0, 0, 0]]; let gameOfLifeResult = gameOfLife(board); console.log(gameOfLifeResult); 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-16T15:22:12+08:00","permalink":"https://yue-jenny.github.io/leetcode/289-game-of-life/","title":"289 Game of Life"},{"content":"主題 後序遍歷（Postorder Traversal）是二叉樹遍歷的一種方式。\n其中節點的順序是先訪問左子樹，然後訪問右子樹，最後訪問根節點。\n以下是後序遍歷的過程：\n檢查當前節點是否為空。如果是空節點，則返回上一層。 從當前節點開始，先遞歸地後序遍歷左子樹。 接著，遞歸地後序遍歷右子樹。 最後，訪問當前節點。 這個遍歷過程可以使用遞歸或堆疊來實現。\n以下是一個使用遞歸的後序遍歷的範例： 假設有以下二叉樹：\nA\r/ \\\rB C\r/ \\ \\\rD E F 後序遍歷的結果將是：D, E, B, F, C, A。\n過程如下：\n從根節點 A 開始： 遞歸地後序遍歷左子樹 B。 遞歸地後序遍歷左子樹 D。 D 為葉子節點，訪問 D。 遞歸地後序遍歷右子樹 E。 E 為葉子節點，訪問 E。 訪問 B。 遞歸地後序遍歷右子樹 C。 遞歸地後序遍歷右子樹 F。 F 為葉子節點，訪問 F。 訪問 C。 訪問 A。 因此，後序遍歷的結果為 D, E, B, F, C, A。\n實作 // 定義二元樹節點的結構 class TreeNode { constructor(val, left, right) { this.val = val; this.left = left; this.right = right; } } // 後序遍歷函數 function postOrderTraversal(root) { let result = []; traversal(root, result); return result; } // 後序遍歷函數，使用遞迴（recursion）來實作。 function traversal(root, result) { if (root === null) { return; } // 遍歷左子樹 traversal(root.left, result); // 遍歷右子樹 traversal(root.right, result); // 儲存根節點的值 result.push(root.val); } // 建立二元樹 /** * 4 * / \\ * 2 6 * / \\ / \\ * 1 3 5 7 * / \\ / \\/ \\ / \\ * n n n nn n n n */ const root = new TreeNode( 4, new TreeNode(2, new TreeNode(1, null, null), new TreeNode(3, null, null)), new TreeNode(6, new TreeNode(5, null, null), new TreeNode(7, null, null)) ); // 呼叫後序遍歷函數 const postOrderTraversalResult = postOrderTraversal(root); console.log(postOrderTraversalResult); // 輸出：[1, 3, 2, 5, 7, 6, 4] 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-15T22:01:04+08:00","permalink":"https://yue-jenny.github.io/2023/06/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E4%BA%8C%E5%85%83%E6%A8%B9%E4%BA%8C%E5%8F%89%E6%A8%B9%E7%9A%84%E5%BE%8C%E5%BA%8F%E9%81%8D%E6%AD%B7postorder-traversal/","title":"資料結構：二元樹（二叉樹）的後序遍歷（Postorder Traversal）"},{"content":"前序遍歷（Preorder Traversal） 前序遍歷（Preorder traversal）是一種二叉樹遍歷的方式。\n在前序遍歷中，首先訪問根節點，然後遞歸地遍歷左子樹，最後遞歸地遍歷右子樹。具體步驟如下：\n訪問當前節點（根節點）。 遞歸地對當前節點的左子樹進行前序遍歷。 遞歸地對當前節點的右子樹進行前序遍歷。 下面是一個前序遍歷的示例，我們以二叉樹的形式展示：\nA\r/ \\\rB C\r/ \\ \\\rD E F 前序遍歷的結果是：A -\u0026gt; B -\u0026gt; D -\u0026gt; E -\u0026gt; C -\u0026gt; F\n解釋過程：\n首先訪問根節點 A。 然後遞歸地遍歷左子樹，訪問節點 B。 繼續遞歸地遍歷左子樹，訪問節點 D。 由於節點 D 是葉節點，沒有左子樹或右子樹，因此返回到節點 B。 繼續遍歷節點 B 的右子樹，訪問節點 E。 由於節點 E 是葉節點，沒有左子樹或右子樹，因此返回到節點 B。 返回到根節點 A，開始遍歷右子樹。 遍歷右子樹，訪問節點 C。 由於節點 C 的左子樹為空，直接遍歷右子樹，訪問節點 F。 由於節點 F 是葉節點，沒有左子樹或右子樹，遍歷完成。 因此，前序遍歷的結果是 A -\u0026gt; B -\u0026gt; D -\u0026gt; E -\u0026gt; C -\u0026gt; F。\n實作 以下是以 JavaScript 實現前序遍歷（Preorder Traversal）的程式碼：\n// 定義二元樹節點的結構 class TreeNode { constructor(val, left, right) { this.val = val; this.left = left; this.right = right; } } // 前序遍歷函數 function preorderTraversal(root) { const result = []; traverse(root, result); return result; } // 輔助函數，用於遞歸遍歷節點 function traverse(node, result) { if (node === null) { return; } // 訪問當前節點的值 result.push(node.val); // 遞歸遍歷左子樹 traverse(node.left, result); // 遞歸遍歷右子樹 traverse(node.right, result); } // 創建二叉樹 /** * 4 * / \\ * 2 6 * / \\ / \\ * 1 3 5 7 * / \\ / \\/ \\ / \\ * n n n nn n n n */ const root = new TreeNode( 4, new TreeNode(2, new TreeNode(1, null, null), new TreeNode(3, null, null)), new TreeNode(6, new TreeNode(5, null, null), new TreeNode(7, null, null)) ); // 執行前序遍歷 const preOrderTraversalResult = preorderTraversal(root); console.log(preOrderTraversalResult); // 輸出：[4, 2, 1, 3, 6, 5, 7] 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-15T16:18:30+08:00","permalink":"https://yue-jenny.github.io/2023/06/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E4%BA%8C%E5%85%83%E6%A8%B9%E4%BA%8C%E5%8F%89%E6%A8%B9%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E6%AD%B7preorder-traversal/","title":"資料結構：二元樹（二叉樹）的前序遍歷（Preorder Traversal）"},{"content":"二元樹 二元樹是一種常見的資料結構，由節點和指向左右子節點的指標組成。\n遍歷二元樹是指按照一定的順序訪問二元樹中的所有節點。\n常見的二元樹遍歷方式有三種：前序遍歷、中序遍歷和後序遍歷。\n下面會逐一介紹它們的形式。\n前序遍歷（Preorder Traversal）： 前序遍歷先訪問根節點，然後按照先左後右的順序遞歸地遍歷左子樹和右子樹。具體形式如下：\n訪問當前節點。 遞歸地前序遍歷左子樹。 遞歸地前序遍歷右子樹。 中序遍歷（Inorder Traversal）： 中序遍歷先按照先左後右的順序遞歸地遍歷左子樹，然後中間過程中訪問根節點，最後遞歸地遍歷右子樹。具體形式如下：\n遞歸地中序遍歷左子樹。 訪問當前節點。 遞歸地中序遍歷右子樹。 後序遍歷（Postorder Traversal）： 後序遍歷先按照先左後右的順序遞歸地遍歷左子樹和右子樹，然後最後訪問根節點。具體形式如下：\n遞歸地後序遍歷左子樹。 遞歸地後序遍歷右子樹。 訪問當前節點。 總結 需要注意的是，以上三種遍歷方式都是深度優先搜索（DFS）的一種形式，因為它們在遍歷時會盡可能深地訪問子節點。\n此外，還有一種廣度優先搜索（BFS）的遍歷方式，即層序遍歷，它按照從上到下、從左到右的順序逐層遍歷二元樹的節點。\n｜點這邊看，如何實現二叉樹中序遍歷\n｜點這邊看，如何實現二叉樹前序遍歷\n｜點這邊看，如何實現二叉樹後序遍歷\n最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-15T11:14:41+08:00","permalink":"https://yue-jenny.github.io/2023/06/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E5%B8%B8%E8%A6%8B%E7%9A%84%E4%BA%8C%E5%85%83%E6%A8%B9%E4%BA%8C%E5%8F%89%E6%A8%B9%E9%81%8D%E6%AD%B7%E6%96%B9%E5%BC%8F/","title":"資料結構：常見的二元樹（二叉樹）遍歷方式"},{"content":"中序遍歷 中序遍歷（In-order traversal）是一種遍歷二叉樹的方法，其順序為先遍歷左子樹，然後訪問根節點，最後遍歷右子樹。\n下面是中序遍歷的詳細過程：\n如果當前節點為空，則返回。 對當前節點的左子樹進行中序遍歷，即遞歸調用中序遍歷函數，傳入當前節點的左子節點。 訪問當前節點，可以進行一些操作，例如將節點的值添加到結果列表中。 對當前節點的右子樹進行中序遍歷，即遞歸調用中序遍歷函數，傳入當前節點的右子節點。 以下是一個示例來說明中序遍歷的過程。假設我們有以下的二叉樹：\n4\r/ \\\r2 6\r/ \\ / \\\r1 3 5 7 按照中序遍歷的順序，我們應該依次訪問節點的值為 1, 2, 3, 4, 5, 6, 7。具體步驟如下：\n從根節點開始遍歷，當前節點為 4。遞歸調用中序遍歷函數，傳入左子節點 2。 當前節點為 2，遞歸調用中序遍歷函數，傳入左子節點 1。 當前節點為 1，沒有左子節點，返回到節點 2。將節點 1 的值添加到結果列表中。 返回到節點 4，將節點 2 的值添加到結果列表中。遞歸調用中序遍歷函數，傳入右子節點 3。 當前節點為 3，沒有左子節點，返回到節點 4。將節點 3 的值添加到結果列表中。 返回到節點 4，將節點 4 的值添加到結果列表中。遞歸調用中序遍歷函數，傳入右子節點 6。 當前節點為 6，遞歸調用中序遍歷函數，傳入左子節點 5。 當前節點為 5，沒有左子節點，返回到節點 6。將節點 5 的值添加到結果列表中。 返回到節點 6，將節點 6 的值添加到結果列表中。遞歸調用中序遍歷函數，傳入右子節點 7。 當前節點為 7，沒有左子節點，返回到節點 6。將節點 7 的值添加到結果列表中。 返回到節點 6，返回到節點 4。 返回到根節點 4，遍歷完成。 範例程式碼 以下是一個使用 JavaScript 實現二叉樹中序遍歷的程式碼範例：\n// 定義二叉樹的節點 class TreeNode { constructor(val, left, right) { this.val = val; this.left = left; this.right = right; } } // 中序遍歷函數 function inorderTraversal(root) { const result = []; // 用於儲存結果的陣列 inorder(root, result); // 呼叫中序遍歷輔助函數 return result; } // 中序遍歷輔助函數 function inorder(node, result) { if (node === null) { return; } // 遞歸遍歷左子樹 inorder(node.left, result); // 將目前節點的值添加到結果陣列中，因為已經沒有比它更小的值了。目前節點的值就是最小的值。 result.push(node.val); // 遞歸遍歷右子樹 inorder(node.right, result); } // 創建二叉樹 /** * 4 * / \\ * 2 6 * / \\ / \\ * 1 3 5 7 * / \\ / \\/ \\ / \\ * n n n nn n n n */ const root = new TreeNode( 4, new TreeNode(2, new TreeNode(1, null, null), new TreeNode(3, null, null)), new TreeNode(6, new TreeNode(5, null, null), new TreeNode(7, null, null)) ); // 執行中序遍歷 const inOrderTraversalResult = inorderTraversal(root); console.log(inOrderTraversalResult); // 輸出 [1, 2, 3, 4, 5, 6, 7] 上述程式碼定義了一個 TreeNode 類別來表示二叉樹的節點，並使用遞歸的方式實現了中序遍歷的函數 inorderTraversal 和輔助函數 inorder。\n在主程式中，我們創建了一個二叉樹，並執行中序遍歷，最後將結果輸出到控制台。\n請注意，以上範例只是一個示例程式碼，可以根據自己的需求進行修改和擴展。\n參考資料 👐 最後，如果你覺得我的分享對你有幫助，請給予我一個愛心，並且分享這篇文章，這將是對我最大的鼓勵！\n","date":"2023-06-15T10:16:59+08:00","permalink":"https://yue-jenny.github.io/2023/06/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E4%BA%8C%E5%85%83%E6%A8%B9%E4%BA%8C%E5%8F%89%E6%A8%B9%E7%9A%84%E4%B8%AD%E5%BA%8F%E9%81%8D%E6%AD%B7in-order-traversal/","title":"資料結構：二元樹（二叉樹）的中序遍歷（In-order traversal）"},{"content":"Replication And Sharding 前言 Replication 目的是建立一個高可用的資料庫架構 可以選擇同步或非同步的方式進行 main 與 replication 資料庫的同步方式 Sharding 目的是為了建立高吞吐量的資料庫架構 透過將資料切片，存在不同的節點，可減少對一個節點太多次存取資料造成的負擔，換句話說，即能增加吞吐量 達成 sharding 的策略有 hashing strategy，此部分相關議題為 Consistent Hashing 使用不同 table (需要確認資料夠 unitform) 存取 參考資料 👐 system experts How to Use Consistent Hashing in a System Design Interview? 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-02-14T15:00:24+08:00","permalink":"https://yue-jenny.github.io/2023/02/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E5%8D%81-replication-and-sharding/","title":"系統設計基礎筆記(十) Replication And Sharding"},{"content":"Peer-To-Peer Networks P2P network 介紹 對等式網路，又稱對等技術，依靠使用者群（peers）交換資訊的網際網路體系 目標就是讓所有的客戶端都能提供資源，包括頻寬，儲存空間和計算能力。因此，當有節點加入且對系統請求增多，整個系統的容量也增大。這是具有一組固定伺服器的Client-Server結構不能實現的，因為在上述這種結構中，客戶端的增加意味著所有使用者更慢的資料傳輸。 根據中央化程度，可以區分為一般型P2P、特殊型P2P與混合型P2P，使用一般型P2P技術的網路系統有比特幣、Gnutella或自由網等。 好處 不會因為單點故障就導致整體服務無法運作 不會讓單點遇到效能瓶頸 Gossip Protocol 簡介 又稱作 epidemic protocol，是 P2P network 的核心技術 Gossip protocol 的實際應用如 Cassandra / Redis Cluster / Consul 等集群架構 consul 用於管理 membership 與傳播消息，有興趣可點這邊 廣度優先遍歷(Breadth-First Search, BFS) 假設 A 得到某些資訊，更新了自身的資訊，A 需要將資訊告訴 B、C 等，然後 B、C 告訴其他的 D、E、F、G，一直遍歷。如果節點 B 收到 A 的消息，發現自己早就知道這個消息就直接忽略，從而可以防止圖重複遍歷。 執行過程 Gossip 過程是異步的，也就是說發消息的節點不會關注對方是否收到，即不等待響應；不管對方有沒有收到，它都會每隔1 秒向周圍節點發消息 通信模式 Push: 節點 A 將數據 (key,value,version) 及對應的版本號推送給節點 B，節點 B 更新 A 中比自己新的數據 Pull: A 僅將數據 key, version 推送給 B，B 將本地比 A 新的數據（Key, value, version）推送給 A，A 更新本地 對 A 來說是 pull Push/Pull: 與 Pull 類似，步驟上多一步，A 再將本地比 B 新的數據推送給 B，B 則更新本地 收斂速度最快，收斂速度是指所有節點的資訊達到一致的速度 優點 擴展性 (scalability) 允許任意節點的增加和減少 容錯 (Fault tolerance) 任何節點的故障和重啟都不會影響 Gossip 消息的傳播 去中心化 (decentralization) 所有節點都可以是對等的，任何一個節點無需知道整個狀況，只要網路相通，任意一個節點就可以把消息散播到全網 一致性收斂 消息會以一傳十、十傳百一樣的指數級速度在網路中快速傳播，因此系統狀態的不一致可以在很快的時間內收斂到一致 簡易實現 缺點 latency 節點只會隨機向少數幾個節點發送消息，消息最終是通過多個輪次的散播而到達全網 消息冗餘 節點會定期隨機選擇周圍節點發送消息，而收到消息的節點也會重複該步驟，因此就不可避免的存在消息重複發送給同一節點的情況，造成了消息的冗餘，同時也增加了收到消息的節點的處理壓力。 由於是定期發送，即使收到了消息的節點還會反複收到重複消息，加重了消息的冗餘。 參考資料 👐 對等網路 - 维基百科，自由的百科全书 Day20|P2P網路(1)：P2P網路基礎知識 - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天 Gossip Protocol 介紹 (上) - 從 Cassandra 內部實作認識 Gossip Protocol 的使用 P2P 网络核心技术：Gossip 协议 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-02-10T09:58:46+08:00","permalink":"https://yue-jenny.github.io/2023/02/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%B9%9D-peer-to-peer-networks/","title":"系統設計基礎筆記(九) Peer-To-Peer Networks"},{"content":"Pulling And Streaming Prerequistites Client-Server Model client 發 request，server 提供資料或服務給 client Socket 是一種一種網路傳輸協定，實現 client 與 server 的雙向溝通機制，使用 TCP 連線，透過 HTTP 3-way handshake 建立連線 目的是可以即時地讓雙方交換資訊，應用場景如聊天室 優點是較少開銷、即時性、二進位支援等 Polling 每隔多少固定的時間去更新資料 Streaming (Pushing) 持續與 server 保持連線，並隨時取得最新資料 參考資料 👐 WebSocket - Web APIs | MDN Polling and Streaming - Concept \u0026amp; Scenarios - GeeksforGeeks 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-02-06T23:24:44+08:00","permalink":"https://yue-jenny.github.io/2023/02/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E5%85%AB-pulling-and-streaming/","title":"系統設計基礎筆記(八) Pulling And Streaming"},{"content":"Configuration Static Configuration V.S Dynamic Configuration Static Configuration 以 YAML 或 JSON 為主的設定檔\nYAML: --- receipt: Oz-Ware Purchase Invoice date: 2012-08-06 customer: given: Dorothy family: Gale JSON { \u0026#34;streetAddress\u0026#34;: \u0026#34;21 2nd Street\u0026#34;, \u0026#34;city\u0026#34;: \u0026#34;New York\u0026#34;, \u0026#34;state\u0026#34;: \u0026#34;NY\u0026#34;, \u0026#34;postalCode\u0026#34;: \u0026#34;10021\u0026#34; } 需要跟 source code 結合\n需要 re-deploy 應用才能更新設定\nDynamic Configuration UI 介面去控制設定 需要去實作此 feature 參考資料 👐 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-02-04T10:30:09+08:00","permalink":"https://yue-jenny.github.io/2023/02/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%B8%83-configuration/","title":"系統設計基礎筆記(七) Configuration"},{"content":"Rate Limiting Prerequistites DDOS 分散式阻斷服務攻擊（distributed denial-of-service attack）亦稱洪水攻擊 DoS 阻斷服務攻擊（英語：denial-of-service attack） 預防方式是使用 rate limit, 可以限制 IP, 使用者帳戶, 區域等等。 Rate Limiting 意思就是限流 根據維基百科，定義是「控制計算機發送或接收的請求之頻率」 Rate Limiting for Multiple Server use Redis to implement rate limiting for multiple servers Application Layer 如何實現 Rate Limit 實現方式 在 Node.js 裡，有一個用來做限流的 express middleware 叫做 express-rate-limit，可看這篇 缺點 在 API server 裡面自己用 middleware 做限流，雖然這樣做感覺很方便，但也會讓 API server 無法完全專注在業務邏輯上 AWS WAF (Web Application Firewall) 目的是保護您的 Web 應用程式免受常見 Web 入侵程式的危害 運作方式 關於 AWS WAF components Web ACLs\n目的是保護 AWS resources，藉由建立一組 web ACL 並新增 rules 去定義保護策略 屬於 AWS WAF resource Rules\n顧名思義，定義檢查標準 (inspection criteria)，當有 matching requests，可以做的事情如允許通過、計算他們或者執行 CAPTCHA puzzles 去檢查是否為機器人 不屬於 AWS WAF resource Rule groups\n可以組合自己的 rule groups，也可以直接在 web ACL 中定義規則 屬於 AWS WAF resource 關於 Web ACL capacity units (WCU) 使用 WCU 來計算與控制運行規則、規則組和 Web ACL 所需的操作資源 For example, a size constraint rule statement uses fewer WCUs than a statement that inspects against a regex pattern set. AWS WAF 會管理 Rule capacity, Rule group capacity, Web ACL capacity，詳情可以參考這篇 關於 Resources that you can protect with AWS WAF 使用 AWS WAF web ACL 去保護全球或區域性的 resource types，只需要將 web ACL 連結到你想要保護的 resources 上，官方提供 Amazon CloudFront 以及 Regional resources 特別說明 Note: Amazon CloudFront 是一項內容交付網路(CDN) 服務，可協助您以高速效能、安全和開發人員易用性快速、可靠地分發靜態和動態內容。 限制 一個 web ACL 可以連結多個 AWS resources，一個 AWS resource 僅連結一個 web ACL 一個 web ACL 可以連結多個 CloudFront distributions，且已連結到 CloudFront distribution 的 web ACL 不能再去連結其他 AWS resource type 使用情境，舉例 3 點 篩選 Web 流量 建立規則以根據各種條件篩選 Web 請求，例如 IP 地址、HTTP 標頭和內文，或自訂 URI。 防止帳戶接管詐騙 監控應用程式的登入頁面，偵測以盜用憑證對使用者帳戶進行的未經授權存取。 使用 API 管理 AWS WAF 自動建立和維護規則，並將這些規則併入開發和設計程序中。 比較 AWS WAF, AWS Shield 與 AWS Firewall Manager AWS WAF monitor requests that are forwarded to your web applications and control access to your content. AWS Shield help protect against DDoS attacks. AWS Firewall Manager set up your firewall rules and apply the rules automatically across accounts and resources, even as new resources are added. 參考資料 👐 What are AWS WAF, AWS Shield, and AWS Firewall Manager? How AWS WAF works 低延遲內容交付網路 (CDN) - Amazon CloudFront - Amazon Web Services express-rate-limit 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-02-02T10:55:49+08:00","permalink":"https://yue-jenny.github.io/2023/02/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E5%85%AD-rate-limiting/","title":"系統設計基礎筆記(六) Rate Limiting"},{"content":"Logging And Monitoring 前言 Prerequistites Logging Monitoring Alerting Elastic Search, Logstash, Kibana (ELK) Elasticsearch 的核心是搜索引擎、採集管道Logstash 和可視化工具Kibana。\nElastic Search Introduce 一個建置在 Apache Lucene 上的分散式搜尋和分析引擎，提供 near real-time 的數據搜尋與分析，能夠儲存複雜結構的數據\n授權不是開放原始碼，也不向使用者提供相同的自由。因此引進了 OpenSearch 專案，此專案是一個社群驅動型 ALv2 許可的開放原始碼 Elasticsearch 和 Kibana 分支\n列舉幾項應用場景:\n網站或應用的 search box 儲存與分析 logs, metrics 等 structured 或 unstructured 文字進而找出安全漏洞 將 Elasticsearch 作為儲存引擎去自動化 workflows 將 Elasticsearch 作為 GIS 去整合與分析空間資料 將 Elasticsearch 作為生物訊息搜尋工具去儲存與處理基因資料 以資料流來簡易說明 Elasticsearch 8.6(current) 做的事情 documents and indices\n儲存已序列化為 JSON 文檔的複雜數據結構 有多個 Elasticsearch 節點時，存儲的文檔分佈在集群中，並且可以從任何節點立即訪問 支援快速搜尋，因為使用了 “inverted index” 的數據結構，每種數據都有專屬並優化過的結構，如 text fields are stored in inverted indices numeric and geo fields are stored in BKD trees 支援 schema-less，當不確定如何處理文檔中的字段時使用，需啟用 “dynamic mapping” search and analyze\n支援 structured queries, full text queries 以及結合兩種搜尋方式 除此之外，也有支援高性能地理空間與數值數據搜尋 透過 Elasticsearch’s comprehensive JSON-style query language (Query DSL) 可以去訪問這些搜尋功能 結合 JDBC 與 ODBC drivers 可以讓第三方 applications 更加廣泛地透過 SQL 與 Elasticsearch 互動 scalabilty and resilience\nElasticsearch 可根據您的需求進行擴展，並且知道如何平衡多節點 cluster 運作方式 將 shards 分布到多個 nodes 上，利用確保冗餘 (redundancy) 可以達到防止 hardware failures 以及增加 query capacity (讀取請求的能力)，當 nodes 數量增減，Elasticsearch 會自動遷移 shard 以重新平衡集群 shard 有兩種 types primaries: Each document in an index belongs to one primary shard. 數量固定。 replicas: a copy of a primary shard. 為了冗餘 (redundancy)，可以達到防止 hardware failures 以及增加 query capacity (讀取請求的能力)。 數量可以更改。 Logstash Introduce 一種開放原始碼資料擷取工具，可讓您從各種來源收集資料、轉換資料並將資料傳送到所需目的地。憑藉預先建置的篩選條件和對 200 多個外掛程式的支援，Logstash 可讓使用者輕鬆擷取資料，而不管資料來源或類型如何。 架構: 一個輸入，一個輸出，中間有個管道（不是必須的），這個管道用來收集、解析和轉換日誌的。 three stages: inputs → filters → outputs Inputs generate events, filters modify them, and outputs ship them elsewhere. 簡介 Inputs, Filters 與 Outputs Inputs 如 file, syslog, redis or beat\n參考這篇 configure Filebeat to send log lines to Logstash Filters 如 grok, mutate, drop, clone, geoip\ngrok: 解析與重組文字，Logstash 中用來解析非結構性的 log 的最好方式，可參考這篇 mutate: 能夠 rename, remove, replace, and modify fields in your events drop: 完整剔除一個 event clone: 複製一個 event geoip: 加入一些新的資訊，如 IP 地址 Outputs 如 elasticsearch, file, graphite, statsd\nelasticsearch: 放在這邊的優點是效率、方便且容易搜尋的\nfile: 寫入檔案內\ngraphite: 一個 open-source 的儲存時序資料與 render graphs 工具\n根據官方文件介紹 Graphite does two things:\n1.Store numeric time-series data\n2.Render graphs of this data on demand\nstatsd:\n用途是監控應用程式，方式是將 metrics 收集、儲存並建立對應的警報機制 原理是監聽 UDP (或 TCP) 的程式並收集數據，將數據傳送給其他應用程式。 重要概念 bucket: 每個 stat 擁有自己的 bucket value: 每個 stat 擁有一個 value，通常是 integer type: 指定 c (用於計數器)、g (用於測量儀)、ms (用於計時器)、h (用於長條圖) 或 s (用於 set) 官方文件 Codecs plugins\n可在 input 或者 output 流程去更改數據顯示的格式 codec-plugins 整合多種 data source 參考這篇範例, conf 檔設定值應如下: input { twitter { consumer_key =\u0026gt; \u0026#34;enter_your_consumer_key_here\u0026#34; consumer_secret =\u0026gt; \u0026#34;enter_your_secret_here\u0026#34; keywords =\u0026gt; [\u0026#34;cloud\u0026#34;] oauth_token =\u0026gt; \u0026#34;enter_your_access_token_here\u0026#34; oauth_token_secret =\u0026gt; \u0026#34;enter_your_access_token_secret_here\u0026#34; } beats { port =\u0026gt; \u0026#34;5044\u0026#34; } } output { elasticsearch { hosts =\u0026gt; [\u0026#34;IP Address 1:port1\u0026#34;, \u0026#34;IP Address 2:port2\u0026#34;, \u0026#34;IP Address 3\u0026#34;] } file { path =\u0026gt; \u0026#34;/path/to/target/file\u0026#34; } } Kibana 一種用於檢視日誌和事件的資料視覺化和探索工具。Kibana 提供易於使用的互動式圖表、預先建置的彙總和篩選條件以及地理空間支援 Introduce 一種用於檢視日誌和事件的資料視覺化和探索工具。Kibana 提供易於使用的互動式圖表、預先建置的彙總和篩選條件以及地理空間支援 透過 kibana 能做到: 透過搜尋與觀察你的數據進而找出安全漏洞 分析與視覺化你的數據 管理數據、監測 Elastic Stack cluster 健康程度與權限控管 Kibana Query Language (KQL) only filters data, and has no role in aggregating, transforming, or sorting data filter documents where a value for a field exists, matches a given value, or is within a given range example filter for documents where the http.request.method is GET, use the following query:\nhttp.request.method: GET search for all documents for which http.response.bytes is less than 10000:\nhttp.response.bytes \u0026lt; 10000 filter documents where the http.request.method is not GET, use the following query:\n**NOT** http.request.method: GET find documents where a single value inside the user array contains a first name of “Alice” and last name of “White”, use the following:\nuser:{ first: \u0026#34;Alice\u0026#34; and last: \u0026#34;White\u0026#34; } Lucene query syntax regular expressions or fuzzy term matching Lucene syntax is not able to search nested objects or scripted fields. example find entries that have 4xx status codes and have an extension of php or html:\nstatus:[400 TO 499] AND (extension:php OR extension:html) Prometheus 介紹 open-source systems monitoring and alerting toolkit Cloud Native Computing Foundation (CNCF) in 2016 as the second hosted project NOTE: 第一個被 CNCF hosted 的 project 是 kubernetes collects and stores its metrics as time series data 優點 recording any purely numeric time series support for multi-dimensional data collection and querying Prometheus server is standalone, not depending on network storage or other remote services 缺點 不適合需要 100% accuracy 的服務 功能 a multi-dimensional data model with time series data identified by metric name and key/value pairs PromQL, a flexible query language to leverage this dimensionality no reliance on distributed storage; single server nodes are autonomous time series collection happens via a pull model over HTTP pushing time series is supported via an intermediary gateway targets are discovered via service discovery or static configuration multiple modes of graphing and dashboarding support metrics 介紹 metrics are numeric measurements Metrics play an important role in understanding why your application is working in a certain way. 支援四種 metrics types, 這篇有更詳細的介紹 Counter - only increase or reset\nGauge - 使用情境如計算 number of pods in a cluster, number of events in an queue\nHistogram - 使用情境可以是任何需要計算的值，如 API requests 所花費的時間，Histogram 會將數據存在 buckets 中，會先定義 buckets - lower or equal 0.3 , le 0.5, le 0.7, le 1, and le 1.2，接著計算完每次 request 所花費的時間後可以將對應到 bucket 的 count 加一，如下圖\nSummary - Histogram 的替代方案，因為更便宜，但付出的代價是 lose more data，原理是計算 metrics 的層級是 application level，所以當同一個 process 有諸多 instances 的話會無法計算\nComponents the main Prometheus server which scrapes and stores time series data client libraries for instrumenting application code a push gateway for supporting short-lived jobs special-purpose exporters for services like HAProxy, StatsD, Graphite, etc. an alertmanager to handle alerts various support tools Grafana 介紹 Grafana Labs 開發的 open-source 專案之一，還有其他專案，如 Grafana Loki (Like Prometheus, but for logs!), Grafana k6 (load testing tool)… query, visualize, alert on, and explore your metrics, logs, and traces wherever they are stored. 功能 Explore metrics, logs, and traces 以 RBAC ( Role-based access Control ) 來決定誰能夠 explore 這些數據 利用以下功能來查詢到更多數據趨勢與細節，細節參考這篇 query management in explore logs integration in explore trace integration in explore inspector in explore: 目的是 troubleshoot 你的 queries，可以將數據導出至 csv 檔案，logs 導出至 txt 檔案 Alerts 參考這篇去建立 alert rules，包含設定 threshold, interval, duration 「 缺少數據」也能被設定 alert 行為 Annotations Hover over events to see the full event metadata and tags. 參考這篇可進行 Add annotation, Add region annotation, Edit annotation, Delete annotation, Built-in query, Query by tag 等功能 Grafana provides many ways to authenticate users 預設是提供 password authentication，其他詳細請看這篇 參考資料 👐 what is elk stack Elasticsearch Service Documentation what-is-elasticsearch How Logstash Works | Logstash Reference [8.6] | Elastic elasticsearch introduction kibana introduction Overview | Prometheus Grafana documentation | Grafana documentation 🍀 若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵\n","date":"2023-01-26T12:16:48+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%BA%94-logging-and-monitoring/","title":"系統設計基礎筆記(五) Logging And Monitoring"},{"content":"Kafka 最先是由 LinkedIn 創立的一款分散式訊息系統 (distributed messaging system)，由 Scala 和 Java 編寫的 open-source 專案 前言 Prerequistites Publish/Subscribe Pattern 📌 功能 高效能、容錯且具可擴展性的平台\n用於建置即時串流資料管道，並以 pub/sub pattern 來管理生產者與消費者的資料\nKafka Connect 是 Apache Kafka 的開放原始碼元件，是一個用於將 Apache Kafka 與外部系統 (如資料庫、機碼值存放區、搜尋索引和檔案系統) 連接的架構。 串流資料是一種小型記錄或事件 (記錄或事件通常為幾 KB 大小的記錄) 的持續串流，這類的記錄則由數千台機器、裝置、網站和應用程式所產生。串流資料包含各式各樣的資料，例如客戶使用您的行動或 Web 應用程式產生的日誌檔、電子商務採購、遊戲中的玩家活動、來自社交網路 資料取用的順序以 FIFO (First In First Out) 作為原則\n💫最重要的觀念💫 如何達到高效能、容錯且可擴展的呢?\npartition Partition 是最小的存儲單元 一個 Partition 內部消息有序，一個 Topic 跨 Partition 是無序的。 一個 Kafka cluster由多個 Broker（就是 Server） 構成，每個 Broker 中含有 cluster 的部分數據。\nPartition 分佈在多個 Broker 的話，Consumer 的多個實例就可以連接不同的 Broker 好處 Topic 就可以水平擴展 支持更多的 Consumer 一個 Consumer 實例負責一個 Partition 數據冗餘\n一個 Partition 生成多個副本，並且把它們分散在不同的 Broker。 如果一個 Broker 故障了，Consumer 可以在其他 Broker 上找到 Partition 的副本，繼續獲取消息。 寫入 Partition 的方式\n給 Kafka 決定 若沒有 key 則 kafka 以 round robin 方式將訊息寫入 partition，但這樣就不保證順序性了，若需要有順序性，請參考下面一個方式 使用 Partition Key 寫入特定 Partition kafka 保證使用相同的 key 會將訊息放到同一個 partition，並且保證順序性 ( in order ) 舉例，如果相同客戶的資訊有序地取得，表示需要放在同一個 partition，可以使用 customer id 作為 partition key，這樣同一個 customer 的資料都會放到同一個 partition 讀取 partition consumer group 多個 consumer 組合成一個 consumer group，目的是為了水平擴展(scale out) consumer 決定 consumer group 要從哪裡開始讀取資料的關鍵是 consumer offsets offsets 會被存在 topic name 中 如果 consumer 讀取資料完畢，則會 commit offsets apache zookeeper an open-source server which enables highly reliable distributed coordination 通過冗餘服務實現高可用性 參考資料 👐 Apache Kafka® 101: Partitioning 細說 Kafka Partition 分區 Apache Kafka vs Confluent: Comparing Features \u0026amp; Capabilities 全受管 Apache Kafka - Amazon MSK 常見問答集 - Amazon Web Services 🍀 最後，若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵!✨\n","date":"2023-01-20T13:38:08+08:00","permalink":"https://yue-jenny.github.io/2023/01/kafka-%E7%B0%A1%E4%BB%8B/","title":"Kafka 簡介"},{"content":"Publish/Subscribe Pattern Publish/Subscribe Pattern (Pub/Sub) 簡介 一種包含 publisher、subscriber 與 broker 的訊息模型 publisher - 將訊息傳送至特定 topic(也可能被稱呼為 channels)，不需要擔心誰需要收到這筆消息 subscriber - 訂閱特定 topic 後，將會收到來自此 topic 的訊息 broker - 負責將 topic 的訊息轉發給 subscriber 通常有以下的保證，如「至少傳送一次」、「持久化」、「訊息的順序性」、「訊息可重送多次」 若相同訊息可重送多次，不會影響結果就是符合冪等性(idempotent)，需要 consumer 另外實作 有哪些 Pub/Sub 服務? 這邊舉幾個服務，後續再寫一篇較詳細文章來比較之間差異\nRabbitMQ 最廣泛使用的 message broker，特色是輕量級輕鬆部屬，但也有支援 distributed 以符合 high-scale 與 high-availability 的要求 點我看更多👀 Apache Kafka 最先是由 LinkedIn 創立的一款分散式訊息系統 (distributed messaging system)，由 Scala 和 Java 編寫的 open-source 專案，點我看更多👀 原本的 Kafka 團隊有出雲端受託管版本的 Kafka - Confluent Cloud (Cloud Native Apache Kafka®)，點我看更多👀 另外，AWS 也有是出自己的雲端版本的 Kafka - Amazon MSK，點我看更多 Cloud Pub/Sub Google 創立的雲端 Pub/Sub 服務，可用於串流服務、非同步微服務整合。點我看更多👀 參考資料 👐 system expert wiki - Kafka publisher-subscriber pattern 🍀 最後，若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵!✨\n","date":"2023-01-18T20:13:18+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E5%9B%9B-publish/subscribe-pattern/","title":"系統設計基礎筆記(四) Publish/Subscribe Pattern"},{"content":"MapReduce 前言 MapReduce 是一個 Google 提出的軟體架構，適用於大規模資料的並列運算\nPrerequistites File System 資料的儲存系統 有許多不同型態，例如以垂直結構為主的目錄與資料夾、Object storage 等 Distributed File System 分散式檔案系統，透過一大群機器(cluster)互相合作，對外表現如同一個巨大的 file system，將 data 切成特定大小的 chunks (如 4 MB 或 64 MB)，會透過 central control plane 會決定應該將 chunks 存在哪一個 node，後續應該去哪一個 node 讀取 chunks 主要操作方式是透過網路以定義好的通訊協定進行資料存取 目前現有的產品有 Google File System (GFS)、Hadoop Distributed File System (HDFS) Hadoop 支持 MapReduce 與資料管線的 open-source 框架，最重要的中央組件為 Hadoop Distributed File System (HDFS) 各階段簡介 Map 階段 負責 filtering 和 sorting 並且組合出一個 key value pair 結果 Reduce 階段 負責資料整合 以 wordcount 為例，從 Map 傳過來的 key 若一樣，表示同一個字，因此把一樣的 key 做加總，可以得出最後的出總筆數 冪等性(idempotency)特性 意義: 當操作多次，結果應呈現一致 透過 pub/sub messaging system 應當有冪等性，因為 pub/sub 系統本身允許相同訊息被 consumer 接收多次 舉例，增加資料庫某欄位的 integer value，就不是一個具有冪等性的操作，因為保持每次增加的操作後都不會保持跟前一個相同的數值 另一舉例，將欄位值設定為 \u0026ldquo;DONE\u0026rdquo;，多次重複此操作，還是會顯示為 \u0026ldquo;DONE\u0026rdquo;，因此設定為 \u0026ldquo;DONE\u0026rdquo; 是一個冪等性操作 範例 input 要做計算的原始資料，可以是一堆文字清單等 split 把 input 資料做分散處理 以 hadoop 來說，當 MapReduce 工作被輸入的時候，會被切割到各個 cluster 裡面等待做處理 🔔map MapReduce 的 map 階段 每一個節點有自己的一份資料要分析，會把對應切割出來的資料建立 key value 的結果 key 是字本身，value 是 1 代表找到一筆 combine 在 map 的機器進行以下動作 將一樣的 key 先做一次加總，避免傳送多次出去，例如 combine 後的結果可能是 \u0026ldquo;A\u0026rdquo; 有 2 筆、\u0026ldquo;B\u0026rdquo; 有 1 筆等 shuffle \u0026amp; sort 在進入 reduce 階段之前，會先被做一個排序，因此相關的 key 會放在一起 比如第一批資料的 \u0026ldquo;A\u0026rdquo; 有 2 筆、第二批資料的 \u0026ldquo;A\u0026rdquo; 有 5 筆\u0026hellip;第一批資料的 \u0026ldquo;B\u0026rdquo; 有 1 筆、第二批資料的 \u0026ldquo;B\u0026rdquo; 有 3 筆 🔔reduce 此階段會做實際的加總，因此每一個 key 的 value 會被加總 output 最後得到的結果 參考資料 👐 system expert wiki MapReduce introduction-to-mapreduce 🍀 最後，若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵!✨\n","date":"2023-01-18T10:32:40+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%B8%89-mapreduce/","title":"系統設計基礎筆記(三) MapReduce"},{"content":"Cache 快取 前言 快取對於系統層面上相當重要，用的好、用的巧，有助於整體系統的順暢度。\n因此目標是了解👉為什麼使用、👉使用策略與👉何時使用。\nPrerequistites cache 意思是將一部分的資料儲存起來，需要使用的時候，不需要經過後端或者資料庫再拿一次，優勢是取得資料較快 通常使用的情境是將常用且不經常修改的 response 儲存，不必每次都去跟後端與資料庫請求 cache hit 需要的資料能在快取中找到 🉐 cache miss 需要的資料無法在快取中找到 🈚 content delivery network (CDN) 一種第三方服務，扮演的角色就像快取，為什麼呢 ? 請往下看 越來越多服務的據點散布全球🌏，若 server 只有在幾個國家，其他國家的使用者可能會遇到網頁轉很久等問題⌛，中間網路傳輸耗時太長導致 latency 長，此時若有散布全球的 CDN server，請求就能先傳送到 CDN server 處理，縮短 latency 舉例一些 CDN 廠商，如 Cloudflare 與 Google cloud CDN 3 個使用快取的目的 利用前端快取，減少請求到後端 減少對資料庫的請求，降低資料庫壓力 避免 long compute operation，增加系統速度 快取更新機制 write through cache 同時更新資料庫與快取的資料 write back cache 先更新快取，再以非同步的方式更新資料庫的資料 快取替換機制 Cache eviction policy Least Recently Used (LRU) 依照最近使用時間來排序 思路: 最近使用時間最接近，表示近期內使用到的可能性也越高 優先替換掉最近使用時間距離當下最遠的那組數據 Least Frequently Used (LFU) 依照使用頻率來排序 思路: 使用次數越高⬆️，表示近期內使用到的可能性也越高⬆️ 優先替換掉使用次數最低的那組數據 First in First out (FIFO) 顧名思義，先進先出 思路: 最先進去快取的資料，越早會被淘汰 參考資料 👐 System expert 🍀 最後，若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵!✨\n","date":"2023-01-17T11:04:46+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%BA%8C-cache/","title":"系統設計基礎筆記(二) Cache"},{"content":"Security And HTTPS 前言 除了了解前/後端語言與框架如何使用外，也希望能對 Http/Https 的原理與安全機制有所了解。\nPrerequistites IP Packet\n透過 IP 傳送的最小數據的單位，通常會包含 IP Header 與 payload IP Header 包含來源與目的地的 IP Address payload 就是你要傳送的資料 Man-In-The-Middle Attack (又稱呼為 MITM)\n意思是攔截 client 與 server 間傳送的訊息 💬 若 client 與 server 間傳送訊息有透過加密與 Https 可防止資料被竊取 Symmetric Encryption (對稱加密)\n加密與解密資料都使用同一把 key 🔑 缺點是安全性會有所疑慮，key 通常會被分享到一個點或多個點 優點是速度比非對稱加密快 🐇 最廣泛使用的演算法是 Advanced Ecryption Standard(AES) Asymmetric Encryption (非對稱加密)\n加密與解密資料會利用到兩把 key 🔑🔑，分別為 public key 與 private key public key 負責加密資料，只能利用相對應的 private key 解密資料 所以 public key 分享給需要加密資料的一端，而 private key 則需要 安全地保存 ㊙️ 速度會比對稱加密慢 🐢 Advanced Ecryption Standard(AES)\n最廣泛使用的加密標準 三種對稱演算法分別為 AES-128、AES-192、AES-256 Transport Layer Security (TLS)\n在傳輸層的一種協定，目的是為了網路通訊時的安全，確保沒有第三方能竊聽或者竊取任何資訊 衍生於另一種安全協定 Secure Socket Layer (SSL) SSL certificate\n由 certificate authority (CA) 頒發給 server 的數位憑證 內容包含 server 端的 public key，因為在 TLS Handshake 過程中會使用到 目的是確認 Http vs Https Http 全名為 HyperText Transfer Protocol 一種常見的網路通訊協議 流程是 client 送出 http request，而 server 送出回應 Https 全名為 HyperText Transfer Protocol Secure 顧名思義是為了可於網路上🔐安全地通訊而出現的一種網路通訊協議 為了達到上述要求，因此 server 被要求須具備以下兩項 須獲得可信任的憑證(SSL certificates) 使用 Transport Layer Security (TLS) 加密 client 與 server 端的數據。TLS 如何運作，請往下看。 TLS 連接是透過 TLS Handshake 來啟動 流程 client 送出 client hello (隨機的 bytes) 給 server server 回應 server hello (也是隨機的 bytes) 以及 SSL certificate client 驗證 CA 頒發的憑證，確認憑證正確屬於此 server 擁有 client 接著會送出一組用 public key 加密的 premaster secret 給 server client 和 server 將會使用 client hello, server hello與 premaster secret 產生對稱加密的 session keys，用於後續的通訊內容的加密與解密 🔐 TCP 利用 Handshake 方式來連接與斷連 TCP 三次握手(建立連線) 流程\n第一次握手： 客戶端傳送請求 SYN 報文給服務端，傳送完畢之後，客戶端處於 SYN_Send 狀態。 第二次握手： 服務端收到請求報文之後，如果同意連線，會回傳 SYN + ACK 應答報文，服務端為SYN_Receive狀態 第三次握手： 客戶端接收到服務端的 SYN + ACK ，然後傳送確認報文作為應答，客戶端轉為Established狀態 為什麼一定要三次?\n為了防止已失效的連接請求報文段突然又傳送到了服務端，因而產生錯誤 TCP 四次握手(結束連線) 流程 第一次分手 客戶端傳送FIN=1告訴服務端，客戶端所有的資料全部發送完畢，服務端可以關閉接收了。 第二次分手 服務端接收到客戶端的釋放請求連線之後，知道客戶端沒有資料傳送給自己了，然後服務端傳送ACK=1告訴客戶端接收到你發給我的訊息 第三次分手 告訴客戶端，服務端的所有資料傳送完畢，客戶端你也可以關閉接收資料連線了。 第四次分手 客戶端接收到了服務端傳送完畢的訊息之後，就傳送ACK=1，告訴服務端，客戶端已經接收到你的訊息 參考資料 👐 System expert what-happens-in-a-tls-handshake 🍀 最後，若喜歡我的分享，可以幫我拍拍手👏，是對我最大的鼓勵!✨\n","date":"2023-01-16T12:35:00+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E7%B3%BB%E7%B5%B1%E8%A8%AD%E8%A8%88%E5%9F%BA%E7%A4%8E%E7%AD%86%E8%A8%98%E4%B8%80-security-and-https/","title":"系統設計基礎筆記(一) Security And HTTPS"},{"content":"如何設計與實作 Monorepo 的 Github Workflow? 前言 有鑑於越來越多人使用 monorepo，想研究一下，當只有針對某個 project 有程式碼的異動時，如何實踐 CI/CD\n什麼是 monorepo? 顧名思義，mono 表示一個，repo 表示 repository，也就是一個 repository 包含多個 projects，並且清楚定義它們之間的關係 例如前後端開發，將前後端的程式都放在同一個 repository，就是 monorepo 結構大致上會呈現如下圖，這次舉例有兩個 app 在同一個 repository 中 想知道必用的原因、好處或採坑紀錄，可先參考 👇 what-is-a-monorepo monorepo 之我見 如何設計 Github workflow? 參考Creating separate monorepo CI/CD pipelines with GitHub Actions，\n以 project 來區分 workflows 檔案，有 db.yaml、gateway.yaml 以及worker.yaml，如下圖\n當 gateway application 有程式碼異動，只能執行 gateway.yaml，測試或 build gateway image 等\n實作 Github Workflow YAML 檔案中是以 on 定義，哪些 events 可以去 trigger 一個 workflow，因此我們需要在這邊動一些手腳，讓他只有在異動特定 application 時才會執行 workflow\n利用 workflow syntax paths 指定範圍，當 pet app 底下的檔案有異動，執行這個 workflow\npaths: - \u0026#39;apps/pet/**\u0026#39; 如果想要再加入指定環境呢?比如指定 stage 或 main，可以利用 branches 的 workflow syntax\nbranches: [\u0026#39;stage\u0026#39;] 結合以上，整個看起來應該會像這樣，指定在 stage 的 pet app 有程式碼變化時，執行這個 workflow\non: push: branches: [\u0026#39;stage\u0026#39;] paths: - \u0026#39;apps/pet/**\u0026#39; 那如果是用 pull request 呢?\non: push: branches: [\u0026#39;stage\u0026#39;] paths: - \u0026#39;apps/pet/**\u0026#39; pull_request: branches: [\u0026#39;stage\u0026#39;] 我如何設計? 根據兩種環境 stage 與 main，以及兩種 app，總共會有 4 組 workflow yaml 檔案 stage-app1.yml stage-app2.yml main-app1.yml main-app1.yml 注意事項 ❗ \u0026ldquo;You must store workflow files in the .github/workflows directory of your repository.\u0026rdquo; Github Workflow 只支援第一層的 workflow yaml 檔案 也就是，以下圖的結構來放 workflow 檔案，Github 是不會執行的任何 workflow 的 官方文件說明 心得 經過這次，除了了解 monorepo 外，對於 Github Actions 的 Workflow syntax 更多的認識。\n參考資料 👐 Creating separate monorepo CI/CD pipelines with GitHub Actions what-is-a-monorepo monorepo 之我見 更多 workflow syntax 介紹 🍀 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵! ✨ ✨ ✨\n","date":"2023-01-12T14:56:21+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E5%A6%82%E4%BD%95%E8%A8%AD%E8%A8%88%E8%88%87%E5%AF%A6%E4%BD%9C-monorepo-%E7%9A%84-github-workflow/","title":"如何設計與實作 Monorepo 的 Github Workflow?"},{"content":"在 Argo CD 中使用 Github App Credential 作為驗證方式 前言 在先前文章使用 Docker Desktop 運行 Argo CD 以及在 Argo CD 內建立 App 偵測 repository 狀態 有提到 private repository 驗證的方式有 SSH、Https 以及 Github App Credential\n此文章來筆記下，如何在 Argo CD 中使用 Github App Credential 作為驗證方式\n實作 建立 Github App\n步驟可參考 👉 官方文件\n其中，特別需要設定的值\nGithub App Name: 為你的 Github App 取名稱\nHomepage URL: 你的 web app URL\n我的 argo cd 是以 docker desktop 啟動，並利用 port forward 方式，讓本地利用 localhost 可連線 dashboard，可參考我的文章使用 Docker Desktop 運行 Argo CD Callback URL: 當 github 認證完畢後，要 redirect 回去的 URL\n建立完成 Github App 後，還有兩件事情要做，Argo CD 設定連線時會用到\n查看 Github App Installation ID\n看 URL https://github.com/settings/installations/{Your Github App Installation ID}, 可確認 Github App Installation ID\n建立 private key\n到 Settings / Developer settings / Github Apps / {your-github-app-name} 的 General\n將頁面往下拉可以看見 Private keys，點 Generate a private key 建立一組 private key\n到 Argo CD 的 Repositories 設定連線資訊\n如何到這頁來設定，可參考官方文件\n這邊要填寫與設定的資訊分成兩部分\n先選到 VIA GITHUB APP\n依照上面的訊息完成填寫框 2的內容\n點 CONNECT，完成\n連線成功\n建立一組 NEW APP\n心得 看到官方提供驗證 private repository 的方式有 Github App Credential，讓我相當好奇，於是就有了這篇文章。\n後續再寫一篇文章來記錄 Github App 與 OAuth App差異。\n參考資料 👐 about-apps github-app-credential 🍀 「記錄」是為了有意識地、有條理地統整所學的學習知識。\n🍀 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵! ✨ ✨ ✨\n","date":"2023-01-10T16:05:56+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E5%9C%A8-argo-cd-%E4%B8%AD%E4%BD%BF%E7%94%A8-github-app-credential-%E4%BD%9C%E7%82%BA%E9%A9%97%E8%AD%89%E6%96%B9%E5%BC%8F/","title":"在 Argo CD 中使用 Github App Credential 作為驗證方式"},{"content":"Github 的 OAuth App 如何建立 OAuth App? 請參考 Creating an OAuth App - GitHub Docs Authorizing OAuth Apps Authorization flow 的分類 web application flow\nUsed to authorize users for standard OAuth apps that run in the browser. (The implicit grant type is not supported.) device flow\nUsed for headless apps, such as CLI tools. Web application flow 簡單 3 步驟可完成 👌 Users are redirected to request their GitHub identity\nUsers are redirected back to your site by GitHub\nYour app accesses the API with the user\u0026rsquo;s access token\n讓我們來詳細說說吧! Request a user\u0026rsquo;s GitHub identity\n利用 redirect 方式取得使用者的 GitHub identity，此時瀏覽器會跳出登入頁面讓使用者登入，需輸入帳密\n📌Request 方式如下\nRequired parameters client_id GET https://github.com/login/oauth/authorize Users are redirected back to your site by GitHub，接著請求 access token\n步驟一若成功完成驗證，則頁面會被返回至 App，接著要進行請求 access token 📌Request 方式如下 Required parameters client_id client_secret code，就是步驟一所帶的 state 欄位值 POST https://github.com/login/oauth/access_token 📌Response\n成功的話，你會取得一組 access token Accept: application/json { \u0026#34;access_token\u0026#34;:\u0026#34;gho_16C7e42F292c6912E7710c838347Ae178B4a\u0026#34;, \u0026#34;scope\u0026#34;:\u0026#34;repo,gist\u0026#34;, \u0026#34;token_type\u0026#34;:\u0026#34;bearer\u0026#34; } Use the access token to access the API\n利用步驟二的 access token 去 access API 📌這邊提供兩個 access API 的 request 方式 # 方式一 Authorization: Bearer OAUTH-TOKEN GET https://api.github.com/user # 方式二 curl -H \u0026#34;Authorization: Bearer OAUTH-TOKEN\u0026#34; https://api.github.com/user Device flow 目的: authorize users for a headless app, such as a CLI tool or Git credential manager. 簡單 3 步驟可完成 👌 Your app requests device and user verification codes and gets the authorization URL where the user will enter the user verification code.\nThe app prompts the user to enter a user verification code at https://github.com/login/device.\nThe app polls for the user authentication status. Once the user has authorized the device, the app will be able to make API calls with a new access token.\n讓我們來詳細說說吧! Step 1: App requests the device and user verification codes from GitHub\n📌Request\nRequired parameters client_id POST https://github.com/login/device/code 📌Response\n⚠️記下來 user code \u0026amp; verification uri，步驟二驗證時會使用到 ⚠️記下來 device code，步驟三驗證時會使用到 interval 意義是 minimum polling interval，單位為秒。 步驟三 app 會去 github poll (輪詢) user 是否已驗證完成此 device interval 即指 poll 的最小時間間隔。 若在 interval 內請求超過 1 次，則會到達 rate limit，會得到一些 error response，更詳細請看 👉 rate limits Accept: application/json { \u0026#34;device_code\u0026#34;: \u0026#34;3584d83530557fdd1f46af8289938c8ef79f9dc5\u0026#34;, \u0026#34;user_code\u0026#34;: \u0026#34;WDJB-MJHT\u0026#34;, \u0026#34;verification_uri\u0026#34;: \u0026#34;https://github.com/login/device\u0026#34;, \u0026#34;expires_in\u0026#34;: 900, \u0026#34;interval\u0026#34;: 5 } Step 2: Prompt the user to enter the user code in a browser\n📌利用步驟一得到 user code \u0026amp; verification uri (通常就是 https://github.com/login/device ) 📌到 https://github.com/login/device 輸入 user code Step 3: App polls GitHub to check if the user authorized the device\n📌Request\nRequired parameters 步驟一得到的 device code client id 指定的 grant type ⚠️注意: 發送輪詢請求，請求間隔必須大於最小輪詢時間間隔，否則會得到 error response，更詳細請看 👉 rate limits POST https://github.com/login/oauth/access_token 📌Response\n成功取得 access token Accept: application/json { \u0026#34;access_token\u0026#34;: \u0026#34;gho_16C7e42F292c6912E7710c838347Ae178B4a\u0026#34;, \u0026#34;token_type\u0026#34;: \u0026#34;bearer\u0026#34;, \u0026#34;scope\u0026#34;: \u0026#34;repo,gist\u0026#34; } Rate limits ⚠️當達到 rate limits，會得到 slow_down 的 error response 📌更多 error response 請參考官方文件 📅 續集(二)會介紹 Non-Web application flow、Creating multiple tokens for OAuth Apps 以及 Directing users to review their access 參考資料 👐 OAuth Apps - GitHub Docs error-codes-for-the-device-flow web-application-flow - GitHub Docs Device flow - GitHub Docs Creating an OAuth App - GitHub Docs 🍀 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵! ✨ ✨ ✨\n","date":"2023-01-07T18:10:13+08:00","permalink":"https://yue-jenny.github.io/2023/01/github-%E7%9A%84-oauth-app-%E4%BB%8B%E7%B4%B9-%E4%B8%80/","title":"Github 的 Oauth App 介紹 (一)"},{"content":"如何在 Hugo 中加入評論系統 Gitalk 前言 為了在部落格增加互動系統，選擇以 gitalk 作為 comment 系統\n實作 建立 Github OAuth App 並取得 client id \u0026amp; client secret (很重要，後續會用到)\n作法可參考官方文件 將資訊填入 themes\\hugo-theme-stack\\config.yaml\nclientID: 步驟一取得的 client id clientSecret: 步驟一取得的 client secret gitalk: owner: yue-jenny -\u0026gt; 你的 account name admin: yue-jenny -\u0026gt; 你的 account name repo: yue-jenny.github.io -\u0026gt; 你的 repo name clientID: 123 -\u0026gt; 步驟一取得的 client id clientSecret: 456 -\u0026gt; 步驟一取得的 client secret 上述步驟完成後，需要 admin (通常就是作者) 先 initial comment。\ninitial 完成後，頁面會這樣呈現。\nfinish-initial-comment 到 Github 可以看到，每一篇文章都會開一個 issue 紀錄，就像這樣。如果有留言，會在 issue 內多一個回應。\ngithub-issue-with-comment 關於 Github Oauth App 想知道更多，可參考 👉 這篇文章Github 的 Oauth App 介紹 (一) 參考資料 👐 creating-an-oauth-app 🍀 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵! ✨ ✨ ✨\n","date":"2023-01-06T18:10:13+08:00","permalink":"https://yue-jenny.github.io/2023/01/%E5%A6%82%E4%BD%95%E5%9C%A8-hugo-%E4%B8%AD%E5%8A%A0%E5%85%A5%E8%A9%95%E8%AB%96%E7%B3%BB%E7%B5%B1-gitalk/","title":"如何在 Hugo 中加入評論系統 Gitalk"},{"content":"Argo CD 是什麼? Argo CD是一個用於持續交付（Continuous Delivery）的工具，用於自動化應用程序的部署、更新和操作。\n它是基於Kubernetes的開源項目，旨在幫助團隊更有效地管理和交付應用程序。\nArgo CD允許開發人員和運維團隊使用 GitOps 模型來管理基礎設施和應用程序的狀態。\nGitOps是一種軟體開發實踐，它將Git作為單一可信源來管理應用程序的狀態和配置。\n透過將應用程序的狀態描述為Git存儲庫中的一組聲明，Argo CD可以根據這些聲明自動驅動應用程序的部署和更新。\n前言 這部分會分享 Argo CD 的中心思想、架構與功能。\n中心思想 GitOps pattern Argo CD 遵循 GitOps 模式，即以 Git repositories 作為唯一識別 application 狀態的來源 Argo CD 作為 kubernetes controller Argo CD 作為 kubernetes controller，持續偵測運行中的 application，並比較現在狀態與目標狀態！\n可用以下幾種方式建立 Argo CD 的 kubernetes manifest\nkustomize applications helm charts jsonnet files Plain directory of YAML/json manifests Any custom config management tool configured as a config management plugin Argo CD architecture 架構圖 有哪些 Components？ API Server\n可以被 Web UI, CLI, 以及 CI/CD systems 介接的 gRPC/REST server 它有幾項責任 應用管理與狀態回報 處理 application 的操作，如 sync, rollback, 其他使用者定義的動作 管理 credential，如 K8s secrets 管理驗證與權限 執行 RBAC Git webhook 事件的傾聽者/傳送者 Repository Server\n管理本地 Git repository 的快取 (Cache) 當有新增以下參數，需建立 Kubernetes manifests repository URL revision (commit, tag, branch) application path template specific settings: parameters, helm values.yaml Application Controller\n屬於 Kubernetes controller 更多關於 Kubernetes controller 持續偵測運行中的 application，並比較現在狀態與目標狀態，若偵測到 OutOfSync 則會選擇性採取正確動作 此元件負責為生命週期事件(PreSync, Sync, PostSync)去調用使用者定義的 hooks Features 根據提供的功能，可以將其分為以下幾項：\n部署和管理功能： 自動部署應用程序到指定的目標環境 支持多個配置管理/模板工具（Kustomize、Helm、Jsonnet、純YAML） 能夠管理和部署到多個集群 身份驗證和安全功能： SSO集成（OIDC、OAuth2、LDAP、SAML 2.0、GitHub、GitLab、Microsoft、LinkedIn） 多租戶和RBAC策略用於授權 回滾和配置管理功能： 回滾/任意回滾到Git存儲庫中提交的任何應用程序配置 應用程序資源的健康狀態分析 自動配置漂移檢測和可視化 自動或手動將應用程序同步到所需狀態 用戶界面和集成功能： 提供實時應用程序活動的Web UI 命令行界面（CLI）用於自動化和CI集成 Webhook集成（GitHub、BitBucket、GitLab） 用於自動化的訪問令牌(Access token) 應用程序升級和監控功能： 支持 PreSync、Sync、PostSync 鉤子以支持複雜的應用程序升級（例如藍/綠和金絲雀升級） 應用程序事件和API調用的軌跡 Prometheus metrics 可覆蓋 Helm 參數的參數覆蓋 支援的功能繁多，列出一部分。每一版或者會有些不同，建議可參考官方文件\n這些功能可幫助團隊實現自動化部署、安全驗證、配置管理、回滾、監控等在持續交付環境中必要的任務。\n心得 Argo CD 在 March 26, 2020 被 CNCF 列入，CNCF 的專案們整合程度越來越高，相信未來的多數公司的 CICD、監控、Log、角色權限控管、資料庫等會以 CNCF 專案為主。\n若想知道如何在 docker desktop 建立 Argo CD，請參考文章【使用 Docker Desktop 運行 Argo CD】\n參考資料 Argo CD Argo CD Components Kubernetes Controllers getting started guide user oriented documentation Developer oriented documentation the upgrade guide 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵!\n","date":"2023-01-05T10:56:11+08:00","permalink":"https://yue-jenny.github.io/2023/01/argo-cd-%E6%98%AF%E4%BB%80%E9%BA%BC/","title":"Argo CD 是什麼?"},{"content":"Prerequisites 開始前，需要先確保有以下的先備知識\nkubernetes Argo CD 介紹 以 GitOps 模式為宗旨的持續部屬 (continuous delivery) 工具 關於 GitOps 可以參考文章 GitOps Getting Started with Argo CD on Docker Desktop 讓我們一步一步開始吧! 💪\n建立 namespace kubectl create namespace argocd 安裝 Argo CD 的 kubernetes resources 需要安裝的 resources 都寫在 install.yaml 內，利用 kubectl apply 指令進行安裝\nkubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml 確認 Argo CD 的 pod 的運行狀態為 running -n 表示指定 namespace 為 argocd，沒設定就是 default namespace\nkubectl get pod -n argocd pod 運行狀態\n取得 Argo CD 初始 admin 的密碼 登入 dashboard 使用\nkubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=\u0026#34;{.data.password}\u0026#34; | base64 -d; echo 將 service argocd-server port forward 到本地 將 service argocd-server 的 port 443 導至本地 port 8000\nkubectl port-forward svc/argocd-server -n argocd 8000:443 port forward 成功\n登入 Argo CD dashboard 網址輸入 https://localhost:8000\n成功登入 Argo CD dashboard\n針對 public git repository 建立一組 application 先 fork 範例 repository - argocd-example-apps\n依照官方文件指示步驟進行新增 application\nStep 1 Step 2，填完資訊，按下 CREATE，能在 dashboard 看到 application\napp name: guestbook project: default sync policy: Manual Repository URL Revision: HEAD Path: guestbook Destination/Cluster: https://kubernetes.default.svc Namespace: default 針對 private git repository 建立一組 application 選一組 private repo\n到 Settings/Repositories\n以 ssh-key 方式認證，也能選擇以 Https或者 Github App 的方式驗證\n可參考Argo CD Private Repositories Credential 設定 可參考我的文章使用 Github App Credential 認證 設定完成會顯示 Successful 建立 application\nStep 1 Step 2\n依照官方文件指示步驟填入資料 成功建立一組 private application 🚀\n解除安裝 application 執行 DELETE 按鈕\n解除安裝 Argo CD ⚠️ 解除安裝 Argo CD 前一定要先解除安裝 application\nkubectl -n argocd delete -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml 遇過的問題 參考資料 Argo CD Argo CD getting started 參考文件 kubernetes kubectl port-forward Argo CD Private Repositories Credential 設定 🍀 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵! ✨ ✨ ✨\n","date":"2023-01-04T04:39:13.628Z","permalink":"https://yue-jenny.github.io/2023/01/%E4%BD%BF%E7%94%A8-docker-desktop-%E9%81%8B%E8%A1%8C-argo-cd-%E4%BB%A5%E5%8F%8A%E5%9C%A8-argo-cd-%E5%85%A7%E5%BB%BA%E7%AB%8B-app-%E5%81%B5%E6%B8%AC-repository-%E7%8B%80%E6%85%8B/","title":"使用 Docker Desktop 運行 Argo CD 以及在 Argo CD 內建立 App 偵測 repository 狀態"},{"content":"OpenID Connect (OIDC) 介紹 前言 OIDC 全名為 OpenID Connect，是一種可以 access AWS resources，但不需要存取 AWS credentials 當作 long-lived GitHub secrets 的驗證方式。 官方建議的驗證方式。 OIDC 優點 - 很好的安全實踐 (good security practices) No cloud secrets 不需要以 cloud credentials 當作 long-lived GitHub secrets 在 cloud provider 設定好 OIDC trust，github workflows 就可以利用 OIDC 從 cloud provider 取得一組 short-lived access token Authentication and authorization management 透過 cloud provider 的 authentication (authN) 與 authorization (authZ) 工具能夠控制取得 cloud resources 能更小粒度地控制 workflows 如何使用 credentials Rotating credentials cloud provider 提供一組 short-lived access token 給一個 job，使用完畢後會自動過期。 OIDC 的運作方式與信任機制 運作方式 主要是兩個角色的互動，分別為 Cloud Provider 與 Github OIDC Provider 互動過程 : In your cloud provider, create an OIDC trust between your cloud role and your GitHub workflow(s) that need access to the cloud. Every time your job runs, GitHub\u0026rsquo;s OIDC Provider auto-generates an OIDC token. This token contains multiple claims to establish a security-hardened and verifiable identity about the specific workflow that is trying to authenticate. You could include a step or action in your job to request this token from GitHub\u0026rsquo;s OIDC provider, and present it to the cloud provider. Once the cloud provider successfully validates the claims presented in the token, it then provides a short-lived cloud access token that is available only for the duration of the job. 可參考官方文件 安全 - OIDC trust 當設定 cloud 能信任 GitHub\u0026rsquo;s OIDC provider 後，必須加上一些情境去過濾掉 requests，避免沒有取得信任的 repositories or workflows 可以透過 access token 操作你的 cloud resources。 Configuring OpenID Connect in Amazon Web Services 前言 目的是 Use OpenID Connect within your workflows to authenticate with Amazon Web Services. 官方文件 IAM Role 1. Create a iam role 建立一組 iam role，用於上傳 docker image 到 private ECR 建立 iam role 的時候，會需要填入以下資訊 : provider URL : https://token.actions.githubusercontent.com Audience : sts.amazonaws.com 2. Permissions policies iam role 綁定的 permissions policies 是 AmazonEC2ContainerRegistryPowerUser 此政策允許委託人讀取和寫入儲存庫，以及讀取生命週期政策。委託人不會被授予刪除儲存庫或變更套用至其生命週期政策的許可。 可依據需求設定不同的 permissons policies，可參考官方文件 3. Add the GitHub OIDC provider to IAM Configure the role and trust in IAM.\n到 iam role 頁面點選編輯 Trust relationships\n參考以下的方式將 sub 欄位加入到 Condition 中\n方式一，使用 StringLike\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;Federated\u0026#34;: \u0026#34;arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com\u0026#34; }, \u0026#34;Action\u0026#34;: \u0026#34;sts:AssumeRoleWithWebIdentity\u0026#34;, \u0026#34;Condition\u0026#34;: { \u0026#34;StringLike\u0026#34;: { \u0026#34;token.actions.githubusercontent.com:sub\u0026#34;: \u0026#34;repo:octo-org/octo-repo:*\u0026#34; }, \u0026#34;StringEquals\u0026#34;: { \u0026#34;token.actions.githubusercontent.com:aud\u0026#34;: \u0026#34;sts.amazonaws.com\u0026#34; } } } ] } 方式二，使用 StringEquals\n\u0026#34;Condition\u0026#34;: { \u0026#34;StringEquals\u0026#34;: { \u0026#34;token.actions.githubusercontent.com:aud\u0026#34;: \u0026#34;sts.amazonaws.com\u0026#34;, \u0026#34;token.actions.githubusercontent.com:sub\u0026#34;: \u0026#34;repo:octo-org/octo-repo:ref:refs/heads/octo-branch\u0026#34; } } 官方文件\nUpdating your GitHub Actions workflow 上述是 AWS 相關設定，此步驟是調整 github workflow，做兩件事情 :\nAdding permissions settings，有兩種權限選擇，可依照自身情境去選擇。\nfetch an OIDC token for a workflow, then the permission can be set at the workflow level.\npermissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout only need to fetch an OIDC token for a single job\npermissions: id-token: write # This is required for requesting the JWT Use the aws-actions/configure-aws-credentials action\n此 action 會接收來自 GitHub OIDC provider 的 JWT，並且向 AWS 請求一組 access token - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: arn:aws:iam::1234567890:role/example-role role-session-name: GitHubActionsWithAwsEcrUsingOIDCSession aws-region: ${{env.AWS_DEFAULT_REGION}} 參考資料 configuring-openid-connect-in-amazon-web-services about-security-hardening-with-openid-connect 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵!\n","date":"2022-12-29T14:32:24+08:00","permalink":"https://yue-jenny.github.io/2022/12/%E5%9C%A8-github-workflow-%E4%B8%AD%E4%BD%BF%E7%94%A8-openid-connect-oidc-%E5%8E%BB%E8%AA%8D%E8%AD%89-aws-%E6%9C%8D%E5%8B%99/","title":"在 Github workflow 中使用 OpenID Connect (OIDC) 去認證 AWS 服務"},{"content":"Github Workflow 介紹 前言 為了利用 Github Workflow 完成 CI/CD，分成幾個部分來寫，分別是\nGithub Workflow 的基本設定 Github Workflow 中進行 CI 登入登出 AWS ECR 與 github container registry build \u0026amp; upload image 更新 helm value 中的 image tag，後續讓 argocd 得以偵測到 helm values 的變化後，進行自動化部屬 基本設定 action 為 push 的時候，所有 branch 都會進行 github workflow action 為 pull_request 的時候，只有 main branch 會進行 github workflow types paths on: push: branches: [\u0026#34;*\u0026#34;] pull_request: branches: [\u0026#34;main\u0026#34;] types: - opened paths: - \u0026#34;**.js\u0026#34; env 設定 設定方式如下，可以依照自身需求進行設定 env: AWS_DEFAULT_REGION: ap-southeast-1 GIT_USER_NAME: jennyc permissions 設定 設定方式如下，可以依照自身需求進行設定 permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout 自動化測試 CI checkout repository 使用 actions/checkout@v3 進行\n- name: Checkout repository uses: actions/checkout@v3 執行 npm install - name: Install dependencies run: npm install 執行 npm test - name: Run tests run: npm run test Build multi-platform images 並上傳至 AWS ECR 前言 這部分需要先設定 AWS Credential 才能使用 AWS ECR，\n接著 build multi-platform image 並且上傳至 AWS ECR。\n1. 設定 AWS Credential 設定 AWS Credential\n使用 GitHub\u0026rsquo;s OIDC provider 方式取得 short-lived credentials\nOpenID Connect (OIDC) 設定方式可以參考官方文件 也可以參考這篇文章 workflow 要怎麼寫?\nenv.AWS_DEFAULT_REGION 需要特別設定，與 AWS ECR 相同的 region role-to-assume 填入 AWS role role-session-name 預設是 GitHubActions，可以自行調整名稱 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: arn:aws:iam::1234567890:role/example-role role-session-name: GitHubActionsWithAwsEcrUsingOIDCSession aws-region: ${{env.AWS_DEFAULT_REGION}} 2. 登入 AWS private ECR 有兩種方式 Using access key id and secret access key to login\n- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-southeast-1 Using OpenID Connect (OIDC) to login\n官方推薦使用此方式 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 3. 登入 AWS public ECR 可參考官方說明 4. 登出 AWS ECR - name: Logout of Amazon ECR if: always() run: docker logout ${{ steps.login-ecr.outputs.registry }} 5. 設定 short sha 為 image tag 設定以 7 digits 長度的 short sha 作為 image tag，原因可以參考: 7 digits are the Git default for a short SHA 附上其他參考文件 Chapter 7 of the Pro Git book 將 output 出去的參數命名為 sha_short - name: Set short sha outputs id: vars run: echo \u0026#34;sha_short=$(git rev-parse --short HEAD)\u0026#34; \u0026gt;\u0026gt; $GITHUB_OUTPUT 6. Build multi-platform images 並上傳 images 到 AWS ECR 建立多平台 docker images 的官方文件 IMAGE_TAG 會從步驟 5 取得 output 的變數 sha_short --platform 可接上需要的 platform 參考，例如 linux/amd64, linux/arm64 - name: Build, tag, and push docker image to Amazon ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} REPOSITORY: pet-app IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} run: | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ( 官方建議: docker run --privileged --rm tonistiigi/binfmt --install all ) docker buildx create --name mybuilder --driver docker-container --bootstrap docker buildx use mybuilder docker buildx inspect docker buildx build --platform linux/amd64,linux/arm64 -t $REGISTRY/$REPOSITORY:$IMAGE_TAG --push . 為什麼需要 docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ?\n因為遇到 Error: while loading /usr/local/sbin/node: No such file or directory 問題 先使用方式一 : 連接方式，結果還是有相同問題 ln -s /usr/bin/node /usr/local/sbin/node 後來使用方式二 使用 docker buildx 指令前先執行以下命令\ndocker run --rm --privileged multiarch/qemu-user-static --reset -p yes 詳細原因的 Stack Overflow 原始文章在這邊，以下是節錄部分\n` When you ask the Linux kernel to run some executable file, it needs to know, how to load this specific file, and whether this file is compatible with current machine, or not. By default, the ELF binary compiled for, say, arm64v8 is rejected by the kernel, running on amd64 hardware.\nHowever, the binfmt_misc feature of the kernel allows you to tell it, how to handle the executables it cannot usually handle on its own - this includes the cases when the kernel does not know the binary format or considers it incompatible with current machine.\n`\ngithub issue\n官方建議使用以下命令解決 QEMU binaries 問題，官方文件\ndocker run --privileged --rm tonistiigi/binfmt --install all 更新 helm value 的 image tag 前言 應用是以 terraform 方式部屬，並搭配使用 helm charts。而 docker image 的 tag 是寫在 helm values 內。\n目標是更新 helm values 的 docker image tag 值，後續讓 argocd 得以偵測到 helm values 的變化後，進行自動化部屬。\n需做到兩件事情:\ncheckout 私有 repository 設定與更新 image tag 1. 先 checkout 私有存放 helm value 的 repository 需要先設定 credential 才能 fetch private repository，有兩種設定的方式\n設定 deploy key\n步驟如下，可參考這篇\nCreate a new SSH key pair for your repository. Do not set a passphrase. Copy the contents of the public key (.pub file) to a new repository deploy key and check the box to \u0026ldquo;Allow write access.\u0026rdquo; Add a secret to the repository containing the entire contents of the private key. As shown in the example below, configure actions/checkout to use the deploy key you have created. workflow 要怎麼寫?\nssh-key 填入 private key 的 secrets 名稱 path 設定 checkout 的 repo 會存在哪一個資料夾內 - name: Checkout ${{env.TERRAFORM_REPOSITORY_NAME}} repo and push file to ${{env.TERRAFORM_REPOSITORY_NAME}} uses: actions/checkout@v3 with: repository: ${{env.TERRAFORM_REPOSITORY_OWNER_NAME}}/${{env.TERRAFORM_REPOSITORY_NAME}} ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} path: ${{env.TERRAFORM_REPOSITORY_NAME}} 設定 Personal access token (PAT)\n建立 PAT 的方式，官方文件\n將建立完成的 PAT 設定到 repository 的 secrets 中，設定的 secrets 名稱為 GH_PAT，官方文件\nworkflow 要怎麼寫?\nrepository 設定為 owner/repository_name token 填入 PAT 的 secrets 名稱，這邊是將 secrets 名稱為 GH_PAT 的內容設定為 PAT - name: Checkout ${{env.TERRAFORM_REPOSITORY_NAME}} repo and push file to ${{env.TERRAFORM_REPOSITORY_NAME}} uses: actions/checkout@v3 with: repository: ${{env.TERRAFORM_REPOSITORY_OWNER_NAME}}/${{env.TERRAFORM_REPOSITORY_NAME}} token: ${{ secrets.GH_PAT }} 2. 設定 image tag 設定以 7 digits 長度的 short sha 作為 image tag 7 digits are the Git default for a short SHA 附上其他參考文件 Chapter 7 of the Pro Git book - name: Set short sha outputs id: vars run: echo \u0026#34;sha_short=$(git rev-parse --short HEAD)\u0026#34; \u0026gt;\u0026gt; $GITHUB_OUTPUT 3. 更新 image tag 以 yq 的 github action 工具修改 yaml 檔內的 image tag 值 yq 的官方文件 yq 的 github 進行 git 操作，設定 user name, user email、commit 以及 push tag 取上一個步驟 output 的參數 sha_short - name: Update image tag uses: mikefarah/yq@master with: cmd: yq -i \u0026#39;.pet_app_dashboard_site.image.tag = \u0026#34;${{ steps.vars.outputs.sha_short }}\u0026#34;\u0026#39; ./${{env.TERRAFORM_REPOSITORY_NAME}}/${{env.HELM_FILE_NAME}} - run: | cd ${{env.TERRAFORM_REPOSITORY_NAME}} git config user.name ${{env.GIT_USER_NAME}} git config user.email ${{env.GIT_USER_EMAIL}} git add ${{env.HELM_FILE_NAME}} git commit -m \u0026#34;update image tag to ${{ steps.vars.outputs.sha_short }}\u0026#34; git push origin main 若要將 docker image 上傳至 github container registry 該怎麼做? 1. 登入與登出 github container registry 需要設定 env.REGISTRY 為 ghcr.io\n不需要另外設定 github.actor 與 GITHUB_TOKEN\n- name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} 2. Extract metadata - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 3. Build and push docker image labels 的值可以從步驟二取出 outputs labels 來使用 - name: Build and push Docker image uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . push: true tags: ${{ env.REGISTRY }}/${{ env.REPO }}:${{ steps.vars.outputs.sha_short }} labels: ${{ steps.meta.outputs.labels }} 參考資料 actions/checkout amazon-ecr-login docker/login-action docker-build-fails-for-arm-images yq 的官方文件 yq 的 github Chapter 7 of the Pro Git book creating-a-personal-access-token creating-and-using-encrypted-secrets 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵!\n","date":"2022-12-10T18:33:05+08:00","permalink":"https://yue-jenny.github.io/2022/12/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-github-workflow-%E5%AE%8C%E6%88%90-ci/cd/","title":"如何使用 Github Workflow 完成 CI/CD"},{"content":"如何用 github pages host 靜態檔案? 讓我們一步一步開始吧! 💪\n1. Create a repository 2. Naming repository name 將 repository 依據模板 username.github.io 命名 ⚠️ username 是帳戶名稱 查看自己的 URL 可以查出 username，依據模板 https://github.com/YOUR_USERNAME 呈現 我帳戶名稱是 Yue-Jenny，username 需設定為 yue-jenny 3. 上傳你的靜態檔案到 github 參考\u0026quot;為什麼我決定使用 hugo 建立一個 blog 系統 📙，以及我該如何建立?\u0026quot;，用 hugo 建立你的部落格系統，並將 publishing directory 的靜態檔案上傳到 github repository 4. 設定 到 repository 的 Settings 頁籤\n\u0026ldquo;Code and automation\u0026rdquo; 區塊中點選 Pages\n\u0026ldquo;Source\u0026rdquo; 選 Deploy from a branch\n\u0026ldquo;Branch\u0026rdquo; 選 publishing 的來源\n\u0026ldquo;為什麼我決定使用 hugo 建立一個 blog 系統 📙，以及我該如何建立?\u0026rdquo; 設定 publishing directory 為 docs，所以選 docs 5. 拜訪你的新網站 🔥 瀏覽器輸入 URL username.github.io\n參考資料 github pages quickstart 最後，若喜歡我的分享，可以免費幫我按讚，是對我最大的鼓勵!\n","date":"2022-12-10T18:25:43+08:00","permalink":"https://yue-jenny.github.io/2022/12/%E5%A6%82%E4%BD%95%E7%94%A8-github-pages-host-%E9%9D%9C%E6%85%8B%E6%AA%94%E6%A1%88/","title":"如何用 github pages host 靜態檔案?"},{"content":"為什麼我決定使用 Hugo \u0026amp; Github pages 建立一個 blog 系統? 1. 內容保存與控管 ✅ 文章內容以 markdown 語法撰寫與保存，熟悉 markdown 語法後，會發現很方便 👍 推薦使用 vscode 作為編輯器 ☝️ 加入 extension 可確認 markdown 內容呈現 ✌️ 直接執行指令運行網站 ✅ 內容可定期放上雲端保存備份，不用擔心若部落格系統下線後，文章也跟著消失或者該如何備份 ✅ 修改內容時，不怕線上部落格系統出現問題，輸入到一半的內容直接消失 ( 曾經有類似的經驗，全部重來 😓) 2. 可高度客製化 ✅ 客製字體大小顏色、主題套件等 UI 的部分 ✅ 自由決定要加入哪些功能，建立擁有自我風格的部落格系統 💪 評論區(支援不同多種軟體) LikeCoin button Table of contents 頁首與頁尾設計 \u0026hellip; etc Hugo \u0026amp; Github pages 基本知識 1. Hugo 介紹 引用自官方文件說明 Hugo is a fast and modern static site generator written in Go, and designed to make website creation fun again.\n2. Github pages 介紹 引用自官方文件說明 You can use GitHub Pages to showcase some open source projects, host a blog, or even share your résumé. This guide will help get you started on creating your next website.\nGetting start，讓我們進入正題吧! 基本功能 1. Prerequisites\nInstall Hugo，安裝能夠 compile go language 的工具，不同 OS 安裝不同的檔案 Install Git，後續下載 theme 使用 2. Create a site\nhugo new site quickstart 3. 到 quickstart 目錄，並執行 initial git repository\ncd quickstart git init 4. clone ananke theme，並以 git submodule 方式存在\n若想了解 git submodule，可參考官方文件\ngit submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke 5. 設定 site configuration file\n在 config.toml 加入這行\ntheme = \u0026#39;ananke\u0026#39; 6. 本地啟動 hugo 專案\n本地啟動 hugo 專案，-p 是指定 port 為 8080\n想知道更多 hugo 命令行可以點這邊\nhugo server -p 8080 瀏覽器輸入 https://localhost:8080，可確認頁面已出現 (這時還沒有文章內容)\n7. 開始寫文章\n建立一個 markdown 檔案，markdown 檔案內的設定的意義\ntitle - 文章標題 date - 建立此文章的時間，文章會跟著時間排序 draft - 是否為草稿，production 環境建議只顯示非草稿的文章，寫完文章可改為 false，再執行 build 指令，讓 markdown 變成 html 等靜態檔案。 hugo new content/post/oidc-aws/index.md 8. 運行網站\n寫完文章內容後 ✏️，設定 markdown 檔案內的 draft 為 false，表示非 draft 模式，重刷頁面可看到文章\n若需要在 draft 模式下顯示文章，需調整啟動 server 的命令行\n-D表示 --buildDrafts，會包含註記為 draft 的內容 hugo server -D -p 8080 成功 🚀 🚀 🚀 9. host on github page\n設定 github pages，請參考 如何用 github pages host 靜態檔案 基本客製化功能 hugo 提供一些客製化功能，來看看應該怎麼做?\n如何設定 publish 資料夾? 在 config.toml 中加入以下設定，能將 publish 資料夾設定為 docs，而 Github pages 能 host docs 資料夾內的靜態檔案\npublishDir = \u0026#34;docs\u0026#34; 如何替換成不同 theme? 我使用的主題是 hugo-theme-stack ( 官方文件 )，該如何調整?\nclone hugo-theme-stack theme git submodule add https://github.com/CaiJimmy/hugo-theme-stack themes/hugo-theme-stack 調整 config.toml 的設定值 theme theme = \u0026#39;hugo-theme-stack\u0026#39; 如何客製化文章中的 font family? 調整 themes\\hugo-theme-stack\\layouts\\partials\\head\\custom.html ，內容如下\n以 google fonts 作為字體來源、以 css2 作為樣式來源 \u0026lt;style\u0026gt; :root { --article-font-family: \u0026#34;Literata\u0026#34;, var(--base-font-family); } \u0026lt;/style\u0026gt; \u0026lt;script\u0026gt; (function () { const customFont = document.createElement(\u0026#39;link\u0026#39;); customFont.href = \u0026#34;https://fonts.googleapis.com/css2?family=Literata:wght@400;700\u0026amp;display=swap\u0026#34;; customFont.type = \u0026#34;text/css\u0026#34;; customFont.rel = \u0026#34;stylesheet\u0026#34;; document.head.appendChild(customFont); }()); \u0026lt;/script\u0026gt; 參考官方文件\n如何客製化建立 index.md 的模板? 調整 archetypes\\default.md 有其他更進階的用法，請參考官方文件 進階客製化 ☝️ 如何在 Hugo 的文章中加入 LikeCoin button? LikeCoin 是強調內容有價的而出現的虛擬貨幣，可給作者支持與鼓勵 實作方式: 註冊 Liker ID\n建立 themes\\hugo-theme-stack\\layouts\\partials\\likecoin.html\n\u0026lt;iframe class=\u0026#34;LikeCoin\u0026#34; height=\u0026#34;235\u0026#34; src=\u0026#34;https://button.like.co/in/embed/{{ .Site.Params.likerID }}/button?referrer={{ .Permalink }}\u0026#34; width=\u0026#34;100%\u0026#34; frameborder=0\u0026gt;\u0026lt;/iframe\u0026gt; 在 config.toml 中加入以下程式碼，並將 [LikerID] 更改為你的 Liker ID\n[Params] likerID = \u0026#34;YOUR_LIKERID\u0026#34; 在文章的模板中加入 LikeCoin button\n將以下的程式碼填入 themes\\hugo-theme-stack\\layouts\\partials\\article\\article.html 中\n{{ partial \u0026#34;likecoin.html\u0026#34; . }} 官方文件 進階客製化 ✌️ 如何使用 Google Console Search 偵測網站的收錄情況? 藉由了解網站成效，並針對弱點進行優化，可增加網站的曝光率 🌟 進階客製化 👌 SEO 搜尋引擎優化 google 會依據網頁網址建立 index，搜尋結果會先呈現有建立 index 的網頁 因此為了強化網頁的 SEO，將你建立 sitemap 交給 google ，讓 google 搜尋可快速找到你的網頁 注意事項 ⚠️ 執行完 build 指令後，建議習慣檢查 docs 資料夾 ( 或你的 publish 資料夾 ) 內的 html 變化是否符合預期\n心得 這應該是第二次使用 framework 方式建立靜態文件系統，第一次是使用 vuepress 協助公司建立內部文件系統，這次是使用 hugo。前者是以 javascript 為當作開發語言，後者是以 go 為開發語言。兩者都蠻推薦。 最重要的是了解該框架是如何運行的，在高度客製化功能或者修改問題的時侯才不會瞎子摸象般的亂試。 hugo 有 64.5k 🌟 (截止至 20230103)，很多人持續貢獻與維護 🙏 ，因此軟體工程師從頭到尾依靠官方文件與 google 完成基本建立不難。 參考資料 github pages quickstart hugo documentation self host likecoin button css2 fifty of the most popular hugo themes / hugo 熱門主題大公開 Build and submit a sitemap to google ","date":"2022-12-10T18:22:43+08:00","permalink":"https://yue-jenny.github.io/2022/12/%E7%82%BA%E4%BB%80%E9%BA%BC%E6%88%91%E6%B1%BA%E5%AE%9A%E4%BD%BF%E7%94%A8-hugo-%E5%BB%BA%E7%AB%8B%E4%B8%80%E5%80%8B-blog-%E7%B3%BB%E7%B5%B1-%E4%BB%A5%E5%8F%8A%E6%88%91%E8%A9%B2%E5%A6%82%E4%BD%95%E5%BB%BA%E7%AB%8B/","title":"為什麼我決定使用 hugo 建立一個 blog 系統 📙，以及我該如何建立?"}]