# 負載平衡和高度可用性
* NCNU OpenSource LSA 課程共筆 [Book mode](https://hackmd.io/@ncnu-opensource/book)
* Load Balancing and High Availability @ 1092 LSA
* 負載平衡和高度可用性 從網路層的 HA / LoadBalance、SLA、資源調配。
[TOC]
## 故事時間

> 網友對於[上週二某 cdn 故障](https://www.ithome.com.tw/news/144911)所作的反應,轉載自 [Twitter](https://twitter.com/ninja_writer21/status/1402231025715933187)
> (不過他們只花 [38 分鐘](https://status.fastly.com/incidents/vpk0ssybt3bj)就修好了)
* 如果你架的服務很常壞掉,而且一壞掉要修很久才修好
* 使用者覺得你不可靠
* 不跟你買服務,不給你錢
* 你只好吃土
* 想辦法降低使用者不能用你服務的時間,就是本章要探討的問題
## 什麼是可用性?
* 一個系統處在可工作狀態的時間的比例
### 計算方法
$Availability = \frac{E[\mathrm{uptime}]}{E[\mathrm{uptime}]+E[\mathrm{downtime}]} =\frac{MTBF}{MTBF + MTTR}$
:::info
* Uptime 正常運行時間
* Downtime 當機時間;停機時間
* MTBF (Mean time between failures) 故障平均間隔時間,平均故障時間
* MTTR (Mean time to repair) 平均修復時間
:::
* 例子:
* 我們的機器的平均每 81.5 年會發生故障
* MTBF = 81.5 x 365 x 24 = 713940 小時
* 每次故障平均要花 1 個小時來修好
* MTTR = 1 小時
* Avalability = 713940 / (713940+1) = 713940 / 713941 = 99.999860%
* 4 個 9,5 個 9 ... 對應到能容許的停機時間, 來自[維基上的轉換表](https://en.wikipedia.org/wiki/High_availability#Percentage_calculation)
| | 90% | 99% | 99.9% | 99.99% | 99.999% |
| -------------- | -------- | - | - | - | - |
| 每個月的停機時間 | 73.05 小時 | 7.31 小時 | 43.83 分鐘 | 4.38 分鐘 | 26.30 秒 |
| 俗稱 | 1 個 9 | 2 個 9 | 3 個 9 | 4 個 9 | 5 個 9 |
### SLA
* Service Level Agreement 服務層級協議
* 服務供應商與客戶之間訂定的協議或契約
* 服務層級目標(Service Level Objective,簡稱 SLO)
* 可用性
* 客服在線上回應你問題的時段,例如 7x24x365 技術支援
* 違約時的賠償政策
* 給抵免額度 credit,該服務的餘額
* 排除之例外
* 排定停機時間
* 測試版功能
* 不可抗力因素
* 客戶違反使用協議
* 例子:
* [臺灣 AI 雲端服務層級協議 (SLA) | zh - HackMD](https://man.twcc.ai/@twccdocs/terms-sla-zh)
* [Amazon Compute Service Level Agreement](https://aws.amazon.com/compute/sla/)
## 高可用性
* 讓服務有很高機率的時間在線上,減少服務無法使用的時間
### 誰需要
* 使用者數量較多的網路服務
* 例如:Google、LINE、YouTube、Cloudflare、AWS...
* 企業的資料庫
* 例如 7-Eleven 平常都用收銀 POS 機來結帳,要是哪天不能用了,只能改用手寫來開收據,大大降低效率
* 股票交易系統,尤其在交易期間
* 延伸閱讀:[台灣證券交易所 - 不停頓與容錯的交易系統](http://sun.csim.scu.edu.tw/~epaper/newspaper/0028/includes/content_news9.php)
* 重要基礎設施,水、電、航空交通管制...
### 需要考慮的環節
* 硬體:跳電、各種硬體(網路卡、硬碟)故障...
* 有備用零件、雙電源、RAID (Redundant Array of Independent Disks)
* 軟體:系統預期外的重啟、遇到 exception 不讓全部卡住
* 網路:線路的多元性
* 環境:火災、淹水、地震、停電、冷氣故障
* 把伺服器放在不同地理位置
* cold site / warm site / hot site [寫在之前備份主題共筆](https://hackmd.io/qh3eBiEtQj--Zp_piczxgQ?view#Backup-site--DR-site)
* 人:操作失誤,ID10T error [梗](https://youtu.be/3klMcY8amOY)
<!-- 例如 sudo rm -rf / -->

* 資料:沒有同步,不一致
### 幾個大原則
* 監控錯誤的發生 (Monitoring)
* 容錯移轉 (Failover)
* 冗餘備援 (Redundancy)
### 監控錯誤的發生 Monitoring
* 定期監控伺服器健康狀況,讓出問題時能趕快啟動救援機制
* 也叫做 Heartbeat / Health Checks
* 自動偵測資源還能不能用
#### 不同層次的監控
* L3 網路層
* 發 ICMP 來看有沒有回覆
* L4 傳輸層
* 看 port 能不能 access
* L7 應用層
* 看能不能存取到特定的 URL
* 發送特定內容的請求,看回應內容是否符合預期結果
* 用自訂 shell / Python 腳本來測應用程式是否正常
#### 應用程式 health check 例子
* 所有 Linux 本機程式都通用
* `pgrep <PATTERN>` 來找 Process name 符合那個 pattern 的 Process ID
* 如果找得到,會回傳 Process ID,exit status code 會回傳 0
* 如果 Process 不存在,exit status code 會回傳 1
* 例如:`pgrep nginx`
```
1410
1411
```
* 在 shell 看 exit status code 用 `echo $?`
`0`
* NGINX
* Passive Health Checks
* 在把 nginx 當作 reverse proxy 的情形下
* 當 nginx 存取 upstream server 失敗時,會自動把那台認為是 unhealthy,並一段時間內停止往它送請求
* 我們可以自訂一些參數
`sudo vim /etc/nginx/sites-available/default `
```=nginx
upstream stream_backend {
server backend1.example.com:12345;
server backend2.example.com:12345 max_fails=2 fail_timeout=30s;
server backend3.example.com:12346;
}
```
* `max_fails` 幾次失敗就認為不健康,預設為 1
* `fail_timeout` 在多久時間內失敗 max_fails 次,就認為不健康;和持續認為不健康多久,預設為 10 秒。(兩個共用同一參數)
* 例如這裡讓 backend2 30 秒內試失敗 2 次的話,就停止往它送請求 30 秒
* MySQL
* 用個低權限的使用者,來看是否能連線進資料庫
* 環境設定
* `sudo mysql` 進入 MySQL 命令列
* `mysql> CREATE DATABASE testdb;` 建立測試用資料庫
* 建立測試用表格
```
mysql> CREATE TABLE example_table (
-> example_column varchar(30)
-> );
```
* `CREATE USER 'testuser' IDENTIFIED BY 'testuser';` 建立測試用使用者
* `GRANT SELECT ON testdb TO 'testuser';` 給予測試使用者權限
* 用 testuser 連進我們建的 testdb
```bash
mysql -u testuser -ptestuser -e 'SELECT * FROM testdb.example_table'
```
* 若成功會回傳 exit status code 0,不成功則是 0 以外數值
* 在 shell 中執行 `echo $?`,會回傳上個指令的 exit status code
* Docker (尚未經測試)
* 在 Dockerfile 裡
```=dockerfile
FROM nginx
RUN apt-get update && apt-get install -y curl
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
```
* 增加 `HEALTHCHECK` 指示,讓 Docker 能執行 `CMD` 裡寫的指令,以確認 container 的健康狀況
* 執行 `CMD` 後回傳的 exit status code 若為
* 0: 代表成功,container 是健康的
* 1: 代表不健康
* 2: 內部保留值,別用
* `--interval`: 兩次健康檢查之間的時間間隔,預設為 30 秒。
* `--timeout`: 當健康檢查運行超過這時間,則本次檢查視為失敗。
* `--retries`: 當健康檢查連續失敗次數多於 retries 時,則視為 unhealthy。
* `docker build -t mynginx .` 來 build 成一個 image
* `docker images` 查看是否有 build 成功
```
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx latest 991d5f1e0725 13 seconds ago 151MB
```
* `docker run mynginx` 把 image 執行起來
* 在 `docker ps` 能在 STATUS 那列看得到 `(healthy)`
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
94b77e6d91da mynginx "/docker-entrypoint.…" 24 seconds ago Up 22 seconds (healthy) 80/tcp vigilant_newton
```
* 用 `docker inspect mynginx` 可以查看 log 檔,也會記載 healthcheck 運行紀錄
> 參考資料:[Dockerfile reference | Docker Documentation](https://docs.docker.com/engine/reference/builder/#healthcheck)
### 容錯移轉 Failover
* 自動從災難中復原(Disaster Recovery)的能力
* 災害復原計畫(Disaster Recovery Plan, DRP)
* 回復時間目標(Recovery Time Objective, RTO)
* 災難發生後,恢復需要花的的時間長短
* 資料回復點目標(Recovery Point Objective, RPO)
* 前一次儲存的時間點
* 會受到資料備份頻率影響
* 如果不能即時複製,備份資料會落後生產環境一段時間,你能忍受有多少資料遺失

> 圖片來源:[File:RPO RTO example converted.png - Wikimedia Commons](https://commons.wikimedia.org/wiki/File:RPO_RTO_example_converted.png), [CC BY 4.0](https://creativecommons.org/licenses/by/4.0)
* Backup Site
* cold site / warm site / hot site [寫在之前備份主題共筆](https://hackmd.io/qh3eBiEtQj--Zp_piczxgQ?view#Backup-site--DR-site)
### 冗餘 Redundancy
* 有多台設備在運行同樣的任務
* 避免單點故障 (single point of failure)
* 在多個環節避免單點故障
:::info
**單點故障 (single point of failure)**
指系統中一旦失效,就會讓整個系統無法運作的部件。
換句話說,單點故障,全部故障。

> [圖片設計原始檔](https://gist.github.com/jiazheng0609/2803c14351060061cf369ce33b2377bc),網路架構參考自:[Cisco Virtualized Multi-Tenant Data Center Framework - Cisco](https://www.cisco.com/c/en/us/td/docs/solutions/Enterprise/Data_Center/VMDC/2-2/vmdcframework.html)
:::
* 藉由設立叢集(Cluster)來達成
::: info
**叢集(Cluster)**
一組鬆散或緊密連接在一起工作的電腦,且每個節點設定為執行相同的任務,由軟體控制和排程。

> 圖片來源:[File:Beowulf.png - Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Beowulf.png)
:::
#### 叢集的架構種類
* Active/Standby(Passive) Cluster

* Hot standby
* Active 那台在提供服務
* Standby 時時刻刻和 Active 保持一致,但不提供服務
* 等到 Active 掛掉的時候,Standby 能馬上跳出來做事 (Failover 機制)
* Warm standby
* Active 那台在提供服務
* Standby 定期複製 Active 的資料,且軟體還沒啟動
* 等到 Active 掛掉的時候,Standby 開始啟動服務 (Failover 機制)
* Active/Active Cluster

> 以上兩張圖片來源:[Active-Active vs. Active-Passive High-Availability Clustering | JSCAPE](https://www.jscape.com/blog/active-active-vs-active-passive-high-availability-cluster)
* 兩台 Active 同時有提供服務
* 靠 load balancer 來平均工作量
* Shared-Nothing vs. Shared-Disk Clusters
* Shared-Disk Clusters

> 圖片來源:[File:Shared Disk Architecture.jpg - Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Shared_Disk_Architecture.jpg)
* 多個前端 server 依靠單一資料庫
* 優點:節省成本
* 缺點:有單點故障危機
* Shared-Nothing

> 圖片來源:[File:Shared Nothing Architecture.jpg - Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Shared_Nothing_Architecture.jpg)
* 分成多個資料庫伺服器
* 優點:沒單點故障
* 缺點:資料要做 replication,很麻煩;要開多台機器,成本高
#### 資料複製 Replication
* 叢集裡不同節點要同步資料,術語是複製 Replication
::: info
**Replication 跟 Backup 有啥差別?**
| | Replication 複製| Backup 備份|
| -------- | -------- | -------- |
| 執行時機 | 每次資料變更 | 固定間隔時間 |
| 災難恢復時間 | 較短 | 較長 |
:::
* 常見的複製方法
* 分散式檔案系統
* 一個經由網路同步的檔案系統
* 最主要設計目標是「通透性(Transparent)」,讓使用者感覺他只是一個本機的檔案系統
* 通透性:使用者完全不需要知道他是個分散式檔案系統,不需要去煩惱各種分散式系統可能帶來的問題,就能正常使用這服務
#### 資料庫叢集
* 這裡介紹最基礎 MySQL Replication 的作法
* source-replica 架構
| 主要角色 | 備用角色 | 註解 |
| -------- | -------- | -------- |
| Source | Replica | MySQL 8 後官方用詞 |
| Master | Slave | 2020 年以前較多人用詞 |
| Active | Standby | 參照本文「叢集的架構種類」段落 |
::: info
**附註 為何有那麼多叫法?**
其實在 MySQL 8 之前,官方用詞是 master-slave,但自從「黑人的命也是命」(Black Lives Matter,BLM)運動受到重視後,許多帶有歧視眼光的技術字彙從 2020 年開始被慢慢改掉。
延伸閱讀:[iThome 新聞](https://www.ithome.com.tw/news/138616)、[官方說明](https://mysqlhighavailability.com/mysql-terminology-updates/)。
:::
* 原理
* binary log
* source 會把所有資料變更動作寫進 binary log
* replica 先去 source 把 binary log 逐行抓下來,存成 relay log
* replica 再把 relay log 裡的資料變更動作套用在自己身上
* relay log
* 記錄了檔案複製的進度,下一個 event 從什麼位置開始,由 mysql 負責更新
* binary log file position-based replication
* 因為每次 replica 每次都是抓整個 binary log 下來
* 要讓 replica 知道從哪個 log 檔,從哪個位置開始要做重做,不然會一直做重複的事
* 一旦設定好,replica 會自己開始記複製到哪了
* 不同格式的 binary log
* statement-based replication (SBR)
* 把 SQL 指令記起來
* row-based replication (RBR)
* 把表格裡每行變更記起來
* 例如
`sudo mysqlbinlog /var/log/mysql/mysql-bin.000002 -vv` 來看 binlog,`#` 開頭的都是工具幫我們做 base64 decode 的結果
```=sql
# at 373
#210606 8:01:12 server id 1 end_log_pos 427 CRC32 0x2e5e0aa4 Write_rows: table id 88 flags: STMT_END_F
BINLOG '
yIC8YBMBAAAAPwAAAHUBAAAAAFgAAAAAAAEABGhhZGIADWV4YW1wbGVfdGFibGUAAQ8CeAABAgP8
/wCmxJQT
yIC8YB4BAAAANgAAAKsBAAAAAFgAAAAAAAEAAgAB/wARZm91cnRoIGFmdGVyIEdUSUSkCl4u
'/*!*/;
### INSERT INTO hadb.example_table
### SET
### @1='fourth after GTID' /* VARSTRING(120) meta=120 nullable=1 is_null=0 */
```
* 光是只有冗餘的資料是不夠的!要成為一個高可用系統,尚未達成的要素:
* Load balance - 透過 HAProxy VIP,實作於[期末專案](https://github.com/NCNU-OpenSource/hanetdb)
* Monitor - 建個沒權限的使用者讓人戳,介紹於「[監控錯誤的發生](#監控錯誤的發生-Monitoring)」段落
* Failover - Group Replication
* source 死掉時,其中一個 replica 要變更角色成為 source
<!--* STONITH(Shoot The Other Node In The Head):當 source 死掉再復活時,他的資料已經落後生產環境了 (split-brain),不能直接上線,先擺進隔離區 (fencing) -->
::: info
BlueT 補充:**CAP 定理(CAP theorem)**
在分散式系統中,不可能同時滿足下面三點:
* 一致性 (Consistency):每次的讀取請求,都能取得最新資料。等同於所有節點都有同一份最新的資料
* 可用性 (Availability):每次請求資料時,都能得到非錯誤回應
* 分區容錯性 (Partition tolerance):就算網路出現問題導致有些資料遺失,整個系統仍然要可以繼續運作
這是個背後有許多討論空間的理論,在此僅略提起有此理論存在。
:::
::: info
BlueT 補充:**Raft Consensus Algorithm**
是一種共識演算法。在本章節所要介紹的 database replication 情境中,能解決「當 source 掛掉時怎麼選出新的 source」的這個問題。實際運作方式可參閱[此互動式網站](http://thesecretlivesofdata.com/raft/)。
:::
#### MySQL 叢集 DEMO
* 以下用 Ubuntu 20.04 上的 MySQL 8.0.25 進行示範
* 檢查版本號碼 `mysql -V`
* 若你的 MySQL 版本號碼小於 MySQL 8.0.22(或大約在 2020 年 7 月之前安裝)
* 把所有大小寫 source 換成 master
* 把所有大小寫 replica 換成 slave
* 有兩台安裝完成的 MySQL Server
* **source** 外界能對他**讀/寫**
* **replica** 外界只能**讀**
* 以下指令會因角色有所區分
1. 若 source 有防火牆,把他設為允許 replica 連入
* 在 **source** 上
`sudo ufw allow from replica-server-ip to any port 3306`
2. 在 **source** 上修改設定檔,預設有在裡面的可把 `#` 刪掉修改
* 在 **source** 上 `sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf`
```=
bind-address = 192.168.57.7
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = hadb
```
* 參數意思
* `bind-address` 填上你 source server 的 IP (可以用`ip a`看)。預設是 `127.0.0.1` 的話他只會接受來自本機的請求,所以要改才能讓 replica 進來
* `server-id` 只要和其他 replica 不重複就好
* `binlog_do_db` 填你想複製的 db 名字
* 如果需要複製更多 db,就多加幾行,像是
```=
binlog_do_db = db
binlog_do_db = db_1
binlog_do_db = db_2
```
* 接著重開 mysql
`sudo systemctl restart mysql.service`
3. 建立要進來做複製的使用者,和給他權限
* 在 **source** 上,用 `sudo mysql` 或 `mysql -u <username> -p` 開啟 mysql 命令列
* mysql 命令列內
```=sql
mysql> CREATE USER 'replica'@'replica-server-ip' IDENTIFIED BY 'replica-password';
```
* 參數意思
* `replica`、`replica-server-ip`、`replica-password` 等等設定 replica 會用到,他能從啥帳密從哪裡登進來
* 給予權限
```=sql
mysql> GRANT REPLICATION SLAVE ON *.* TO 'replica'@'replica-server-ip';
mysql> FLUSH PRIVILEGES;
```
4. 取得 source 的 binary log coordinates
* 在 **source** 上,mysql 命令列內
* 將整個表格鎖定,讓其他連線無法讀取及異動,直到資料處理完畢為止
```=mysql
mysql> FLUSH TABLES WITH READ LOCK;
```
* 取得目前 binary log 檔案名稱和座標位置,等一下在 replica 會用到
```=sql
mysql> SHOW MASTER STATUS;
-------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 73 | hadb | |
+------------------+----------+--------------+------------------+
```
5. 這裡假設 source 資料庫裡還沒有 hadb,建立一個全新的
:::warning
若在執行本教學這些步驟之前 source 裡已經有資料,且想要複製到 replica 的,可參考 [之前的自動化共筆](https://hackmd.io/LqiMw8qRS2usU_IpQBe-Yg#%E8%87%AA%E5%8B%95%E5%8C%96%E4%BE%8B%E5%AD%90%E5%AF%A6%E4%BD%9C) 進行 mysqldump。
> 為啥就算 binlog 有從頭記還是建議 dbdump?因為 replica 包含所有歷史更動,太多。dbdump 只有最終結果。
:::
* 在 **source**,mysql 命令列內
* 剛剛鎖住了,先解鎖才能修改
```=sql
mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)
```
* 建立一個全新的 hadb
```=sql
mysql> CREATE DATABASE hadb;
Query OK, 1 row affected (0.01 sec)
```
6. 開始設定 replica
* 在 **replica** 上,修改設定檔
* `sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf`
```=
bind-address = 192.168.57.8
server-id = 2
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = hadb
relay-log = /var/log/mysql/mysql-relay-bin.log
```
* 參數意思
* `bind-address` 填上你 replica server 的 IP (可以用`ip a`看)
* `server-id` 只要不跟 source 和其他 replica 重複就好
* `binlog_do_db` 填你想複製的 db 名字,跟 source 的一樣
* 接著重開 mysql
`sudo systemctl restart mysql.service`
7. 開始複製
* 在 **replica**,進入 mysql 命令列
`sudo mysql`
* 在 **replica**,mysql 命令列內
```=sql
mysql> CHANGE REPLICATION SOURCE TO
mysql> SOURCE_HOST='source_server_ip',
mysql> SOURCE_USER='replica',
mysql> SOURCE_PASSWORD='replica-password',
mysql> SOURCE_LOG_FILE='mysql-bin.000001',
mysql> SOURCE_LOG_POS=73;
```
* 參數意思
* `SOURCE_USER` 和 `SOURCE_PASSWORD` 是要填在 2. 給權限的那組帳密
* `SOURCE_LOG_FILE` 和 `SOURCE_LOG_POS` 填在 3. 拿到的資訊
* 在 **replica**,mysql 命令列內
`mysql> START REPLICA;`
8. 測試複製是否有在運作
* 在 **source**,mysql 命令列內
* 切換到有在複製的 DB 裡
```=sql
mysql> USE hadb;
```
* 建立新 TABLE
```=sql
mysql> CREATE TABLE example_table (
mysql> example_column varchar(30)
mysql> );
Query OK, 0 rows affected (0.07 sec)
```
* 加入一些資料
```=sql
mysql> INSERT INTO example_table VALUES
mysql> ('This is the first row'),
mysql> ('This is the second row'),
mysql> ('This is the third row');
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
```
* 在 **replica**,mysql 命令列內
* 切換到有在複製的 DB 裡
```=sql
mysql> USE hadb;
```
* 顯示資料
```=sql
mysql> SHOW TABLES;
+----------------+
| Tables_in_hadb |
+----------------+
| example_table |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM example_table;
+-------------------+
| example_column |
+-------------------+
| this first row |
| this second row |
| this third row |
+-------------------+
3 rows in set (0.00 sec)
```
> 資料來源: [How To Set Up Replication in MySQL | DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-set-up-replication-in-mysql)
## 負載平衡 Load balancing
* 把工作(load)分配下去給多台設備,讓每一部設備工作量維持平均,避免其中某一台過載
* 工作可以是外部 traffic / request / API call ,也能來自內部其他服務(例如前端 call 後端)
* 如果 Cluster 其中一台伺服器掛了,Load Balancer 要負責把流量都導到健康的其他台上面
* 也能作為提供可擴縮性(Scalability)手段(這裡先不做討論)
* 這裡討論由 Web Service 提供的 Load Balancer 幫我們做到的 Load Balancing
### L4 與 L7 的 Load balancing
:::info
**OSI Layer 示意圖**,但這裡主要介紹的還是只有 L4 與 L7 層的軟體 Load Balancer

> 圖片來源:[Load balancing in Layer 4 vs Layer 7 with HAPROXY Examples - YouTube](https://www.youtube.com/watch?v=aKMLgFVxZYk&t=180)
:::
* Layer 4 傳輸層資訊的分配機制
* 以實體的對外 IP 或 Virtual IP(VIP) 接收請求,再由 Load Balancer 做負載平衡
* 看 Source IP / port 或 Destination IP / port
* Source: IP Hash(下面會介紹)、Destionation: 轉傳至特定機器
:::info
延伸閱讀: [九個硬體負載平衡器優於軟體負載平衡器的理由](https://blog.avinetworks.com/f5-vs-avi-networks)
:::
* Layer 7 應用層資訊的分配機制
* 在 L4 的基礎之上
* 看請求內容 (URL, Cookie, Header) 來做 Load Balancer
* 例如:在 Nginx 設定反向代理 (Reverse Proxy),讓 `http://example.com/app1 -> http://10.0.0.1:8080` 和 `http://example.com/app2 -> http://10.0.0.2:8080`
:::info
* **Layer 7**
* 某些會有能力進行 SSL 加解密
* 除了負載平衡,某些也常被當作網路防火牆 (Web Application Firewall),阻絕外人直接存取內網資源
> BT 補充:有公司會額外買處理 SSL 的機器 (SSL 加速器)
* **Proxy**
* 外網是在 Proxy 與 Server 之間
* 內網是在 Proxy 與 Client 之間
* Caching
* 假設內網的使用者請求的頁面在 Proxy 有暫存,可以直接回給使用者
* 好處: 減少外部流量,節省頻寬,加快回應速度

* **Reverse proxy**
* 外網是在 Proxy 與 Client 之間
* 內網是在 Proxy 與 Server 之間
* Caching
* 假設外網的使用者請求的頁面在 Proxy 有暫存,可以直接回給使用者
* 好處: 減少 Server 負擔
* 但本章節的 Reverse Proxy 只有做到代理,而無暫存
* Client 只需與 Reverse Proxy 溝通,再由 Reverse Proxy 決定如何分配給 Server
* 若 Server 調整設定後只需通知 Proxy,而使用者則不需知道任何更動
* Ex. 更換內網 IP or Port

> BT 補充:有個軟體叫 Varnish ,是很強的 reverse cache proxy
:::
* 使用者端
* 給一個 server list 讓使用者端決定要連哪個伺服器
* DNS-based Load balancing
* 根據來源 IP 的不同,發給你不同的 IP 產生 Load Balancing
* 或一次回給你很多 IP,依照實作方式的不同(亂數或以第一個為主),達成 Load Balancing
:::info
用 `host google.com` 看到的 IP 會根據不同來源位置,而產生不一樣的結果
:::
### 在 L4 實作 Load Balance 方法
> 本節圖片來源:[Linux伺服器叢集系統(三)--LVS叢集中的IP負載平衡技術 | linux-vs.org (簡體中文)](http://www.linuxvirtualserver.org/zh/lvs3.html)
:::info
**Virtual IP**
Virtual IP 為 Virtual Router Master 負責持有的IP
:::
* NAT 模式
* Load balancer 收到 Client 的要求後,修改封包 Header
* Load balancer,再將要求轉送給後端的其他伺服器處理
* 後端處理完要求後回給 Load Balancer
* Load Balancer 收到後端的封包,修改封包 Header 後,再轉送給 Client
* 缺點:容易造成 Load Balancer 過載

* 封包修改範例
1. 連入封包
`SOURCE 202.100.1.2:3456 DEST 202.103.106.5:80`
2. Load balancer 改完轉傳給 real server
`SOURCE 202.100.1.2:3456 DEST 172.16.0.3:8000`
3. 後端伺服器回覆給 Load balancer
`SOURCE 172.16.0.3:8000 DEST 202.100.1.2:3456`
4. Load balancer 再回給使用者
`SOURCE 202.103.106.5:80 DEST 202.100.1.2:3456`
* IP 隧道模式 (IP Tunneling)
> IP 隧道經常用於連接兩個不是用路由直接連結的 IP 網路
Load Balancer 與 Real Server 的 IP 都必須是 Public IP
Load Balancer 與 Real Server 都擁有相同的 VIP
* Load balancer 先收到 Client 的要求,再根據演算法選出適合的後端伺服器
* Load balancer 將請求封包封裝 (encapsulation) 在另一個 IP 封包中,再將封裝後的 IP 封包轉送給選出的後端伺服器
* Real Server 處理完要求後再將回應封包直接返回給 Client


* Direct Routing Mode (DR)
> Load Balancer 與 Real Server 位於相同的區域網路底下
* Load balancer 收到 Client 的要求後
* 將資料封包的 MAC 地址改為選出後端伺服器的 MAC 地址,再將修改後的資料封包傳給後端伺服器組成的區域網路
* 該負責的伺服器直接回應 Client 而不經過主伺服器。這樣效能比較不會卡在主伺服器上
:::info
Real server 與 Load balancer 同時擁有相同的 VIP。但只有 Load balancer 設定的 VIP 地址是對外的;並且 Real Server 把 ARP 關閉,這裡的 VIP 對外部是不可見的,只是用於處理目標地址為該 VIP 的要求。
:::


| | NAT | TUNNEL | DR |
| - | - | - | - |
| Server | any | Tunneling | Non-arp device |
| Server Network | private | LAN/WAN | LAN |
| Server Connection | low (10~20) | High (100) | High (100) |
| Server Gateway | load balancer | own router | own router |
### 常見的排程演算法 / 規則
[Comparing Load Balancing Algorithms - YouTube](https://youtu.be/iqOTT7_7qXY?t=54) 以下圖片來自這很棒的影片動畫圖解
* 隨機
* 循環法 (round-robin)
* 大家公平輪流

* 加權循環法 (weighted round-robin)
* 有其中一個人能力大於其他人時,能者多勞
* 例如有時候備用伺服器用弱一點的機器

* 最少連線數 (least connections)
* 當 Server 2 的使用者待在線上特別久,而 Server 1 已經沒人用了
* 再繼續輪流塞人的話,Server 2 會超忙,Server 1 超閒


* 基於局部性 (locality-based)
* 最少連線數(Locality-Based Least Connections)
* 找出該 IP 最近使用的伺服器
* 伺服器可用就直接發送請求
* 若不存在或超載,就以「最少連線數」的原則,選出可用的伺服器
* 備援式的最少連線數(Locality-Based Least Connections with Replication)
* 找出該 IP 最近使用的伺服器組
* 以「最少連線數」的原則從伺服器組中選出伺服器
* 若伺服器組中的伺服器皆超載,則從其他未加入組的伺服器中,選出伺服器,並加入到伺服器組中
* 若經過一段時間後,組內有伺服器閒置,則該伺服器會被移出,用以提高叢集效率
> 參考資料:http://kb.linuxvirtualserver.org/wiki/IPVS
* 各種基於雜湊的 (Hash-based)
* 可能會看的東西(取決於是第幾層)
* 使用者請求的 URL
* Protocol 種類
* 來源位址
* 目的地位址
### sticky / persistent session
> sticky session 必要性:1. stateful 2. 且 session 存在 server,沒在任何 share database
* 例如:登入狀態
* 同一個 session 盡量導到同一台機器上
* 用 source address / cookie 決定要分給哪台
* 那台機器中途死掉了怎麼辦
* 架構設計成讓其他資料庫 (ex: redis, memcache) 記住 session,再同步給 server
* 或把資訊存在 client
* 改成 stateless 的方式,降低對 sticky session 的依賴
## 常見的 Load Balancer 軟體(或硬體)
### 硬體
#### F5 Big-IP
#### Citrix Netscaler
#### Radware
### 軟體
#### Linux Virtual Server
* 運作在 TCP/IP 架構中的第 4 層(傳輸層)
* 能設定的參數比較少,但因為沒有可太多設定的東西,可以減少人為出錯的機率
* 除了對 Web Server 做負載平衡外,也可以對 MySQL 做負載平衡
* 實作於[期末專題報告](https://github.com/NCNU-OpenSource/hanetdb)
#### Nginx
* 運作在 TCP/IP 架構中的第 7 層(應用層),可以針對 HTTP 應用做一些分流的策略,比如針對域名、目錄結構
* 安裝與設定較為簡單易懂,測試起來較為方便
* 僅支援 HTTP, HTTPS, E-mail,應用範圍較小
* 預設有三種 Load Balance 方法,Round Robin、weight 以及 ip_hash
#### HAProxy
* 做為 TCP 和 HTTP 應用程式的 high availability load balancer 和 proxy server
* 可以實作 L4 或 L7 load balancer
* HAProxy 可以對 MySQL 進行負載平衡,對後端的資料庫進行檢測和負載平衡。
<!-- > FIXME: 擴張啥 rephrasing
> 他有啥特性,他超強 C100K
> L4 能用什麼模式,L7 支援什麼應用
> session 量
> throughput 量 -->
## 常見的 High Availability 軟體
#### Keepalived
* 提供 Load Balancing 的故障隔離與故障轉移
* 部署和使用非常的簡單,所有設定只需要一個設定檔即可以完成
* 用 Linux Virtual Server IPVS kernel module 來實作的
* 實作了 VRRP 協定
#### Heartbeat
* Heartbeat 2.1.4 後拆分成 3 個子專案,安裝與使用比較複雜
<!-- 功能更強大,配套工具更全,適合做大型叢集管理 (編按: Citation needed) -->
##### VRRP 協定

> 圖片來源: [Red Hat Enterprise Linux 7 Load Balancer 管理 | Red Hat](https://access.redhat.com/documentation/zh-tw/red_hat_enterprise_linux/7/pdf/load_balancer_administration/Red_Hat_Enterprise_Linux-7-Load_Balancer_Administration-zh-TW.pdf), [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/ "Creative Commons — Attribution-ShareAlike 3.0 Unported — CC BY-SA 3.0")
* Virtual Router Redundancy Protocol,虛擬路由器備援協定
* RFC2338, 3768, [5798](https://datatracker.ietf.org/doc/html/rfc5798)
* 虛擬路由器: 一個抽象的物件,有一個辨識號碼(VRID)和一組相關聯的 IP 位址(Virual IP, VIP)
* 一個虛擬路由器底下有很多 VRRP 路由器,同 VRID 的成為同一群組
* VRRP 路由器可以分為:主控制路由器(master)與備援路由器(backup)
* 會根據 priority 選其中一個 VRRP 路由器來作為 master,剩下是 backup
* 對外有一個虛擬 IP (VIP),跟 master 產生關聯 (associate)
* 只有 master 會回覆想問 VIP 是誰的 ARP 請求
* 若是 master 掛掉了,則從 backup 中依優先權再選出一個成為 master,是個 **Failover** 動作
* master 會定期 multicast 發送 ADVERTISEMENT
* 若 backup 一段時間沒聽到 ADVERTISEMENT,就知 master 掛了
<!-- > 步驟這麼多,做個流程圖 UML -->

> 圖片來源: [Best Practices for Floating IP Addresses | Compute Engine 說明文件](https://cloud.google.com/solutions/best-practices-floating-ip-addresses#example_use_case_for_migration), [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0)
### DEMO
* 主要架構

* 摘要
* 
[BT 的 Digital Ocean 邀請連結,你能夠獲得 100 美元的額度,而 BT 能夠獲得 25 美元的額度!還不快點加入!](https://m.do.co/c/7a14e0e84052) (需要信用卡才能註冊)
* 這裡我們用 Keepalived 軟體,以監控叢集成員與叢集服務,並使用 Nginx 作為軟體的 Load Balancer
* 本次 DEMO 利用 DigitalOcean 提供的 Float IP API 作為分配 Virtual IP 的手段
* 附註: 在 AWS 也有 Elastic IP 能使用
* 本次 DEMO 總共用到四台虛擬機,兩台前端用來做 Load Balance 以及 Failover,兩台後端用來做 Application Server
* 建立虛擬機
* 先建立 droplet 這邊選擇 Ubuntu 18.04

* 選最便宜的

* 選亞洲伺服器這樣延遲比較低

* 選擇登入時的驗證方式,這邊自由隨意~~但是他密碼的要求有夠多~~

* 這邊可以直接建立四台虛擬機

* 接下來直接透過 ssh 連入虛擬機
* 後端
* nginx
```=shell
sudo apt-get update
sudo apt-get install nginx
```
* 修改首頁方便我們辨識
```=shell
sudo vim /var/www/html/index.nginx-debian.html
```
* 全部刪掉然後打上
* 第一台
```=html5
<h1>Primary</h1>
```
* 第二台
```=html5
<h1>Secondary</h1>
```
* 前端
* keepalived
* 首先安裝 nginx
```=shell
sudo apt-get update
sudo apt-get install nginx
```
* 直接修改預設設定
```=shell
sudo vim /etc/nginx/sites-available/default
```
* 新增 load balance 的設定
```=shell
upstream backend {
server first_web_server_ip:80;
server second_web_server_ip:80;
}
server {
location / {
proxy_pass http://backend.example.com;
}
}
```
:::info
``first_web_server_ip``,``second_web_server_ip``,``http://backend.example.com`` 都是要修改的地方
:::
* 安裝 keepalived
```=shell
sudo apt install keepalived
```
* 接下來開始建立設定檔
```=shell
sudo vim /etc/keepalived/keepalived.conf
```
* on Primary
:::info
``MASTER_private_IP``,``BACKUP_private_IP``,``password`` 都是要修改的地方
:::
```=shell
vrrp_script chk_nginx {
script "pgrep nginx" # 檢查 nginx 是否有在行程 (Process) 中
interval 2
}
vrrp_instance VI_1 {
interface eth1 # 選擇網路介面卡
state MASTER # 宣告身分 (MASTER OR BACKUP)
priority 200 # 優先權
virtual_router_id 33 # Keepalived 用來識別是否為同一個群組
unicast_src_ip MASTER_private_IP # 用來多播 (multicast) 的來源 IP
unicast_peer {
BACKUP_private_IP # 接收來自哪裡多播 (multicast) 的IP
}
authentication {
auth_type PASS # 驗證方式
auth_pass password # 驗證密碼
}
track_script { # 追蹤服務是否正常 (health check)
chk_nginx
}
notify_master /etc/keepalived/master.sh # 當前節點成為 master 時,執行腳本
}
```
* on Secondary
* Secondary 的地方也要喔,注意兩邊的 IP 互換
```=shell
vrrp_script chk_nginx {
script "pgrep nginx"
interval 2
}
vrrp_instance VI_1 {
interface eth1
state BACKUP
priority 100
virtual_router_id 33
unicast_src_ip BACKUP_private_IP
unicast_peer {
MASTER_private_IP
}
authentication {
auth_type PASS
auth_pass password
}
track_script {
chk_nginx
}
notify_master /etc/keepalived/master.sh
}
```
* 接下來下載 DigitalOcean 提供的 Python script
```=shell
cd /usr/local/bin
sudo curl -LO http://do.co/assign-ip
```
* 建立 Float IP
* 在 DigitalOcean 的設定頁面選擇 Networking -> Floating IPs </br>選擇剛剛建立的 Primary 後按下 Assign Float IP
:::info
申請 Float IP 的用意,是讓使用者透過這個另外申請、能變更綁定 Server 的 Virtual IP 來存取服務,而不是直接去造訪兩台的 Real IP
:::
* 建立 DigitalOcean API Token
* 在 DigitalOcean 的設定頁面選擇 API , 按下 Generate New Token
* 取個名字,然後就可以建立了
* 建立完的 Token 只會出現這一次,所以我們先複製起來
* 回到虛擬機,建立當主機掛掉時需要執行的腳本
* `DO_TOKEN` 變數要改成上一步拿到的 API Token
* `IP` 變數要改成上一步拿到的 Floating IP
```=shell
sudo vim /etc/keepalived/master.sh
```
:::info
這個腳本由 DigitalOcean 提供,用意是當某一台 Real Server 變成 Master state 的時候,會去檢查自身是否擁有 Floating IP,如果沒有就去跟 DO 要求將 Floating IP 導向自身
:::
```=shell
#!/bin/bash
export DO_TOKEN='digitalocean_api_token'
IP='floating_ip_addr'
ID=$(curl -s http://169.254.169.254/metadata/v1/id)
HAS_FLOATING_IP=$(curl -s http://169.254.169.254/metadata/v1/floating_ip/ipv4/active)
if [ $HAS_FLOATING_IP = "false" ]; then
n=0
while [ $n -lt 10 ]
do
python /usr/local/bin/assign-ip $IP $ID && break
n=$((n+1))
sleep 3
done
fi
```
::: warning
先確定你的機器裡有安裝 Python 了,以及 Python requests module,因為這個 script 需要用到
`sudo apt install python python-requests`
:::
* 接下來讓 ``master.sh`` 可以被 keepalived 執行
```=shell
sudo chmod +x /etc/keepalived/master.sh
```
* 可以來啟動我們的 keepalived 了
```=shell
sudo systemctl start keepalived
```
* 測試當 Nginx 故障 (Testing Nginx Failure)
* 在 Primary 上輸入
```=shell
sudo service nginx stop
```
* 在數秒後 keepalived 將會把 Float IP 交給 Secondary
* 在 Primary 上輸入
```=shell
sudo service nginx start
```
* 在數秒後 keepalived 將會把 Float IP 交給 Primary
* 測試當伺服器故障時 (Testing Server Failure)
* 在 Primary 上輸入
```=shell
sudo reboot
```
* 在數秒後 keepalived 將會把 Float IP 交給 Secondary
* 在 Primary 開機完成數秒後 keepalived 將會把 Float IP 交給 Primary
## References
[High-Availability Computer System in 1991](https://jimgray.azurewebsites.net/papers/ieee_HA_Swieorick.pdf)
[Introduction to High Availability | Linode](https://www.linode.com/docs/guides/introduction-to-high-availability/)
[High Availability tutorials, questions and resources | DigitalOcean](https://www.digitalocean.com/community/tags/high-availability)
[Introduction to High Availability | Fusion Middleware High Availability Guide](https://docs.oracle.com/cd/A91202_01/901_doc/rac.901/a89867/pshavdtl.htm)
[Overview of High Availability ](https://docs.oracle.com/database/121/HAOVW/overview.htm#HAOVW113)
[Load Balancing | Administration Guide | SUSE Linux Enterprise High Availability Extension 15](https://documentation.suse.com/sle-ha/15-GA/html/SLE-HA-all/cha-ha-lb.html)
[Synology High Availability White Paper](https://global.download.synology.com/download/Document/Software/WhitePaper/Package/HighAvailability/All/enu/Synology_SHA_White_Paper.pdf)
[SLA服務可用性4個9是什麼意思?如何保證服務的高可用性 HA(High Availability)?_Kotlin 開發者社群 - MdEditor](https://www.gushiciku.cn/pl/pLMh/zh-tw)
[高有效性 (High Availability) 初論 30 講 :: 2011 iT 邦幫忙鐵人賽](https://ithelp.ithome.com.tw/users/20000065/ironman/279)
[HAProxy document](http://cbonte.github.io/haproxy-dconv/2.3/intro.html#3)
[雲端服務品質不卡住?連 Google、微軟都在用的 SLA 你不能不懂! | TechOrange](https://buzzorange.com/techorange/2013/05/21/service-level-agreement-sla/)
[之前 LSA 講如何建立 Cluster - Corosync and Pacemaker](https://hackmd.io/G0DdQLAiSemZv_sMbDagQg#The-way-to-create-the-cluster-Computing)
[之前 LSA 講如何建立 Cluster - HAProxy](https://hackmd.io/@ncnu-opensource/book/https%3A%2F%2Fhackmd.io%2FxhFa9y3TQLCsYrL2WMOK5A)
[MySQL 高可用方案及成功案例](https://www.slideshare.net/fanndywang/mysql-28141817)
[章 2. Keepalived 總覽 Red Hat Enterprise Linux 7 | Red Hat Customer Portal](https://access.redhat.com/documentation/zh-tw/red_hat_enterprise_linux/7/html/load_balancer_administration/ch-keepalived-overview-vsa)
https://www.keepalived.org/
[External HTTP(S) Load Balancing overview | Google Cloud](https://cloud.google.com/load-balancing/docs/https) 好多漂亮的圖解可以拿來用
[High Availability Cluster: Concepts and Architecture | NetApp](https://cloud.netapp.com/blog/cvo-blg-high-availability-cluster-concepts-and-architecture) 參考 Cluster 架構來自這
[Server Load Balancing – 阿喵就像家](https://mlwmlw.org/2011/04/server-load-balancing/)
[國家教育研究院雙語詞彙、學術名詞暨辭書資訊網](https://terms.naer.edu.tw/) 避免使用支語,有英文轉中文的專有名詞都來查一下
[搜尋和下載國際詞彙 - Microsoft | 語言入口網站](https://www.microsoft.com/zh-tw/language/) 查台灣軟體技術用語的另一個好東西,雖然是 M 開頭,BT 別打我 OAO
[make 簡單介紹](http://linux.vbird.org/linux_basic/0520source_code_and_tarball.php#intro_make)
[make 簡單介紹2](https://www.cnblogs.com/tinywan/p/7230039.html)
[libssl-dev](https://blog.csdn.net/WANG__RONGWEI/article/details/54898410)
[build-essential](https://www.ubuntu-tw.org/modules/newbb/viewtopic.php?post_id=37689)
[Module ngx_stream_upstream_module](https://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#server)
[Docker 容器的健康检查 - 张志敏的技术专栏](https://beginor.github.io/2018/03/11/healthy-check-instruction-of-docker.html)
[Important Health Checks for your MySQL Master-Slave Servers](https://scalegrid.io/blog/important-health-checks-for-your-mysql-master-slave-servers/)
[叢集檔案系統 - 維基百科,自由的百科全書](https://zh.wikipedia.org/wiki/%E9%9B%86%E7%BE%A4%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)
[proxy & reverse proxy](https://www.jyt0532.com/2019/11/18/proxy-reverse-proxy/)
[三種 LVS 的模式:LVS-NAT、LVS-TUN、LVS-DR](https://blog.maxkit.com.tw/2016/05/lvs-lvs-natlvs-tunlvs-dr.html)
[How To Set Up Highly Available HAProxy Servers with Keepalived and Floating IPs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-highly-available-haproxy-servers-with-keepalived-and-floating-ips-on-ubuntu-14-04)
#### 延伸閱讀
* L2 load balance: Link Aggregation
* Cloudflare Anycast 原理概述於[期末專題報告](https://github.com/NCNU-OpenSource/hanetdb)
* router multipath
* [Introduction to modern network load balancing and proxying | by Matt Klein | Envoy Proxy](https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236) 介紹近代(文章寫作於 2018 年)的負載平衡,[中文翻譯 by Neil's Note](https://qazwsxedccsqzse.blogspot.com/2018/06/blog-post.html)
* 裡面提到了 Google, Facebook, GitHub 各自有 open source 自己開發的 L4 Load balancer,我還好奇另外去查到 [LINE 自己開發的](https://speakerdeck.com/line_devday2019/software-engineering-that-supports-line-original-lbaas)
* [binhnguyennus/awesome-scalability | GitHub](https://github.com/binhnguyennus/awesome-scalability#availability) 的 Availability 大標
* [分散式資料庫系統上的問題探討 (Web Archive)](https://web.archive.org/web/20220818072702/http://120.105.184.250/lwcheng/Kid51/kidpps/Kid51_CHAP14.pdf) [(原址已失效)](http://120.105.184.250/lwcheng/Kid51/kidpps/Kid51_CHAP14.pdf) by [Frank S.C. Tseng ](http://www2.nkfust.edu.tw/~imfrank)
* [Google - Site Reliability Engineering](https://sre.google/books/)
* [Improving load balancing with a new consistent-hashing algorithm | by arodland | Vimeo Engineering Blog | Medium](https://medium.com/vimeo-engineering-blog/improving-load-balancing-with-a-new-consistent-hashing-algorithm-9f1bd75709ed)
* DNS-based Load Balancing - [EDNS Client Subnet](https://web.archive.org/web/20140702174224/http://www.afasterinternet.com/howitworks.htm)