# K8s介紹
* k8s是 Kubernetes的簡稱,是一個能協助自動化部屬、擴張、管理app的工具(關於app是甚麼,參考另一篇TSMC紙本筆記,裡面有提到app的開發原則)
* 可以將一個超大型的專案分成許多不同的微服務,如此一來當某個服務crash時,便不會直接癱瘓整個系統;而k8s便是負責管理這些微服務的
# Docker介紹
* Docker是一個可以將程式碼封裝的工具,目的是讓這個程式碼能在不同環境下執行
* 通常會將程式碼打包成Docker Image,接著上傳到Docker hub,如此一來便可以方便的下載這個映像檔並隨時執行了
將程式碼dockerize時,需要一個設定檔
```dockerfile
FROM node:6.2.2 # 使用Node.js版本6.2.2的基礎映像
WORKDIR /app # 設定工作目錄為/app,在容器內建立/app目錄
ADD . /app # 將當前目錄下的所有文件和目錄添加到/app目錄中的容器內
RUN npm install # 執行npm install指令,安裝應用程序所需的依賴項
EXPOSE 300 # 指定容器對外開放的端口為300
CMD npm start # 在容器啟動時執行npm start指令
```
# K8s Pod
* Pod是在k8s上運行的最小部屬單位
* 一個pod就相當於一個application
* 一個pod中可以包含多個container,而一個container就是一個打包好的Docker Image
* 同一個pod 中的container可以透過local port numbers溝通,而不同pod則透過
每個Pod都需要一個.yaml設定檔
```yaml
# my-first-pod.yaml
apiVersion: v1 # 使用的Kubernetes API版本
kind: Pod # 定義一個Pod資源
metadata: # Pod的元數據
name: my-pod # 指定Pod的名稱
labels: # 標籤,用於識別和分類Pod
app: webserver # 添加一個名為"app",值為"webserver"的標籤
spec: # Pod的規範
containers: # 定義Pod中的容器列表
- name: pod-demo # 容器的名稱
image: zxcvbnius/docker-demo # 使用的映像
ports: # 定義容器的端口配置
- containerPort: 3000 # 指定容器對外開放的端口為3000
```
# Pod lifecycle
* pod 的狀態一定是以下幾個的其中之一
1. Pending: 創建中
2. Running: 已成功被創建,並在其中一個Node上運行
3. Failed: Pod的所有container都已被終止,並至少有一個container是因為錯誤而被終止
4. Succeeded: Pod的所有container都已成功被終止
5. Unknown: 無法取得該Pod的狀態
* 容器探測(container probe)是Pod lifestyle中的一項任務,概念是定期檢查這個Pod中的container是否還能正常運作,詳細內容參考下面的k8s healthcare章節
# K8s Node
* Node指的是在k8s上實體運行的一台機器
* 在一個Node上運行多個app,可以妥善利用資源
有了Node的觀念後,便可看看k8s的整體架構

* master node是整個k8s的控制中心,負責管理和監控整個Kubernetes Cluster(圖上沒畫出)
* Load balancer通常由 Node 供應商提供,負責決定要將 request 送給哪個 Node
* 每個Node都有屬於自己的iptables,可限制哪些連線可以連進來,並決定決定收到的 request 要交給哪個 Pod
* 每個Node都有屬於自己的kubelet,負責管理該Node上的所有 Pod,並與 master node 即時溝通(master node之後會提)
* 每個Node都有屬於自己的kube-proxy,負責將目前該 Node 上所有 Pod 的資訊傳給 iptables,以便iptables可以隨時掌握Pod的最新狀態
* 一個白色框代表一個Node
* 一個橘色框代表一個Pod,是k8s上的最小運行單位,可以觀察到一個Node可能有不只一個Pod
* 一個綠色框代表一個Container(對應到一個Docker image),可以觀察到一個Pod可能有不只一個Container
# K8s Deployment
* Deployment負責k8s中一切服務的佈署(例如Pod)
* Deplayment會自動創建指定數量的Pod replicas(副本)並維護,維護的意思是始終讓Pod replicas的數量保持固定
* Deplayment與Deplayment負責維護的Pod共存亡
* 若有兩個label 相同的Pod1,Pod2,在deployment時在k8s cluster實際運行的Pod為?
* 可以使用kubectl set image的方式rollout(升級)這個deployment內Pod的container;升級過程中,會另外生成Pod來取代原本Pod,以實現zero downtime(無停機服務遷移)
* 可以使用kubectl rollout undo的方式rollback(回朔)置過去的版本
(補充)
Replication的概念是將工作內容相同的Pod多複製幾份,以因應Pod 故障或是大量請求
每個Deployment都需要一個.yaml設定檔
```yaml
# for kubectl versions >= 1.9.0 use apps/v1
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: hello-deployment # Deployment的名稱
spec:
replicas: 3 # 定義了副本數量(replicas)為3
selector: # 以便選擇屬於這個Deployment的Pod
matchLabels:
app: my-deployment
template: # 定義了Pod的模板(template)
metadata:
labels:
app: my-deployment
spec:
containers: # 定義了容器(container)的相關設定
- name: my-pod
image: zxcvbnius/docker-demo:latest
ports:
- containerPort: 3000 # 容器將會在Port 3000上監聽連線
```
* 值得注意的是
* 只有label=matchLabels的那些Pod,才在deployment的管理範圍之內
* deployment會在創建的每個Pod中加上"template中的labels"
# k8s service
* service的功能是作為Pod與外部溝通的橋樑
每個service都需要一個.yaml設定檔
```yaml
apiVersion: v1
kind: Service
metadata:
name: hello-service
spec:
type: NodePort
ports:
- port: 3000
nodePort: 30390
protocol: TCP
targetPort: 3000
selector:
app: my-deployment # 將request導入至指定label的Pod上
```
* port: 建立好的 Service "本身"要以哪個埠號連接到 Pod 上,通常會與targetPort相同
* nodePort:指定節點(Node)上對外開放的埠號,外部想要連結到這個service就是靠這個
* targetPort: 建立好的 Service 要連接到埠號上的哪個port,通常會與targetPort相同
這樣設定後,外部便可透過這個網址與Pod互動了
> http://192.168.99.100:30390
* 192.168.99.100是Cluster ID,每個service都會有一個。沒特別指定的話由系統隨機生成
應用: Blue Green deployment
* 概念: 先將 v2 版本部屬到正式環境,經過測試後再把流量從 v1 版本切換至 v2 版本,若發現有問題,可以快速切回v1版本
* 實作:
1. 原本的情況,流量會均勻分攤到三個版本之中
2. 透過設置service的label,可以將流量全部移轉到指定版本
* https://ithelp.ithome.com.tw/articles/10292369
# K8s Label
* K8s中的Pod 或Node,可以帶有Label(一對具有辨識度的key/value),以更加清楚的識別這個物件的功用、層級
* 每個Pod 或Node可以擁有0或多個label
* 應用1: deployment時能夠透過label找到指定的Pod以佈署
* 應用2: 在Pod的.yaml中新增nodeSelector選項,以指定這個Pod只能被部署於有這個標籤的Node上
# k8s healthcare
* 目的是定期送request至Pod中的container,用來偵測這個container是否有正常運作
* 用來送訊息的Probe(探針)有三種類型:
1. livenessProbe: 定期送請求確認,若失敗則會根據設定自動重啟
2. readinessProbe: 定期送請求確認,若失敗則會切斷與這個Container之間的聯繫
3. startupProbe: 只會在這個Container啟動時執行一次,在startupProbe檢測通過前不會啟動其他的Probe,若失敗則會根據設定自動重啟
livenessProbe 範例
修改一下之前的deployment .yaml檔
```yaml
apiVersion: apps/v1beta2 # for kubectl versions >= 1.9.0 use apps/v1
kind: Deployment
metadata:
name: hello-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-deployment
template:
metadata:
labels:
app: my-deployment
spec:
containers:
- name: webapp
image: zxcvbnius/docker-demo
ports:
- name: webapp-port
containerPort: 3000
livenessProbe:
httpGet:
path: /
port: webapp-port
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 30
successThreshold: 1
failureThreshold: 3
```
* 相較於之前deployment的設定,在container中新增了livenessProbe屬性
* 根據範例設定檔,k8s系統將使用http get的方法,在啟動後的15秒(initialDelaySeconds)後,每隔15秒(periodSeconds)檢查一次這個container負責的根目錄是否能正常回應;若連續3次(failureThreshold)都沒在30秒(timeoutSeconds)內收到回應,將重啟這個Container;若連續1次(successThreshold)成功在30秒收到(timeoutSeconds)內收到回應,則被視為正常運行
# k8s secret
* 可以創建secret物件以儲存密碼等重要資料
* 使用時,可以將這個物件掛載至環境變數中
先創建一個secret 物件
```yaml
apiVersion: v1
kind: Secret
metadata:
name: demo-secret-from-yaml
type: Opaque
data:
username: cm9vdA==
password: cm9vdHBhc3M=
```
接著可以在創建Pod時,將secret物件放到這個Pod的環境變數中
```yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: webserver
spec:
containers:
- name: demo-pod
image: zxcvbnius/docker-demo
ports:
- containerPort: 3000
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: demo-secret-from-yaml
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: demo-secret-from-yaml
key: password
```
* 注意env的部分
第二種方法是在創建Pod時,將secret物件掛載(mount)到這個Pod的檔案路徑下
```yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod-with-mounting-secret
labels:
app: webserver
spec:
containers:
- name: demo-pod
image: zxcvbnius/docker-demo
ports:
- containerPort: 3000
volumeMounts:
- name: secret-volume
mountPath: /etc/creds
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: demo-secret-from-yaml
```
* 注意mountPath
(補充:掛載的概念)
先在Pod中設定一個volumes,可以當作是這個Pod中的虛擬硬碟
接著在Container中使用volumeMounts關鍵字決定要掛載的檔案跟掛載的路徑
# k8s Sidecar
*若一個pod中包含了不只一個container,可以依照sidecar模式設計,將Container分成
1. Application Container: 負責主要業務邏輯
2. Sidecar Container: 負責擴增及改善主應用容器的功能性,這部分通常須盡量提高通用性
範例:
```yaml
```
# Azure 介紹
* Mirecroft 提供的服務,讓其他人可以付費租用該公司的計算、儲存、網路等資源
* 是全球第二大的雲端運算平台
* Web app的在Azure的開發與部屬屬於Paas(Platform as a service),使用者只需要管理application跟data的儲存即可
# Azure 使用架構
* 要想使用Azure上的資源(如虛擬機、資料庫),需要遵從一定模式存取

* 上圖為一個Azure帳號存取平台上資源的架構
* Resourse: 代表這個雲端運算平台提供的資源
* Resourse group: 資源組,將資源放到資料夾中,方便組織與管理
* Subscription: 為了使用這些資源,必須"訂閱"這些服務才有辦法使用
* Management groups: 負責管理一個Azure下的許多訂閱
# Istio 介紹
* istio元件支援k8s的架構,最大的用途是對k8s中的流量進行管理與分流;除此之外,還可以透過log觀察並紀錄流量、提供底層的安全通信管道等
* Istio分成兩個主要的部份
* Control Plane: 負責管理和控制,是Istio的核心部分,已安裝在 istio-system Namespace 裡
* Data Plane: 負責實際處理和轉發流量,將會以 Sidecar 模式注入到每一個 Pod之中(Sidecar是Pod中的container,也就是說Istio會在Pod中心曾一個container以完成其工作)
先在電腦上安裝Istio,接著再安裝至k8s中,即可在k8s中使用Istio
> kubectl get pods -n istio-system (在k8s中使用Istio範例)
# Istio Request Routing
* 實作如何用Istio實現"隨心所欲的控制流量"
* 需要透過Istio內建的DestinationRule及VirtualService來將流量移轉至特定版本;
首先,使用Istio Gateway的方法引流(像是k8s service的加強版)
Gateway 範例:
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: my-gateway
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- my-service.example.com
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: my-certs
```
接著必須配置以下兩個文件,並使用以下指令部屬於系統之上
> kubectl apply -f \<file>
DestinationRule(.yaml):
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews #定義這個DestinationRule本身的名稱
spec:
host: reviews
subsets:
- name: v1 #目的地v1
labels: #label 為 "version:v1"的所有pod將會被當作目的地v1
version: v1
- name: v2 #目的地v2
labels:
version: v2
- name: v3 #目的地v3
labels:
version: v3
```
* 負責定義目的地為何
VirtualService(.yaml):
```yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
```
* 負責定義分配規則,可以為
1. Prefix Routing: 透過url 前綴的方式來決定要分配到哪個目的地
2. Weight Routing: 直接指定要分配到各目的地的流量比例分布
3. Header Match Routing: 透過http header來決定要分配到哪個目的地
配置完以上文件後,接著創建Pods並為其加上特定的標籤,即可自由分配流進gateway中的流量
# CI/CD 實作教學
有了k8s跟Azure的概念後,便可以在Azure將一個Web app部屬到K8s上
## CI
CI 必不可少的檔案便是DockerFile,有了這個檔案,便可在CI的流程中將code打包成Docker Image,以便部屬到K8s上
另一個CI 必不可少的檔案便是azure-pipeline.yaml,這個檔案會放在Web app的那個資料夾中,裡面必須包含至少兩個步驟,分別是"Build code"跟"建立Docker Image"
CI成功後,會產生一個Artifact,CD時便可拿去使用
## CD
在CD的過程中,會分成設定Artifact跟Stages兩個部分,其中Artifact便是之前CI產生的那個,而在Stages中,一定要記得選擇Agent job中的Agent pool,代表CD時要用哪個運算資源,除此之外海有一些細節
1. Deployment type 選擇helm
2. Deployment Repository URL選擇Azure Repos中有Deployment.yaml跟Service.yaml的那個
service.yaml 架構
```yaml
apiVersion: v1
kind: Service
metadata:
name:
spec:
selector:
app:
ports:
- protocol: TCP
port:
targetPort:
```
deployment.yaml 架構,包含:
這段 YAML 文件設定了一個 Deployment,其中包含以下內容:
* Deployment 名稱
* 規格 (spec):
* Selector:選擇 Deployment 相關的 Pod
* 副本數 (replicas):設定為 1。
* Pod 的範本 (template):
* Pod 的標籤 (labels):
* Pod 的註釋 (annotations):
* 容器 (containers):
* 容器名稱
* 容器映像檔 (image)
* 容器拉取策略 (imagePullPolicy)
* 資源需求 (resources)
* 容器的 Port:
* 環境變數 (env)
# ELK介紹
參考資料: https://ithelp.ithome.com.tw/articles/10259069
* 由以下三個開源工具組合而成的資料分析平台,可以輕鬆的蒐集、儲存、可視化大數據
1. Elasticsearch: 全文檢索與分析資料,並可以當作no sql類型的database使用
2. Logstash: 轉換資料格式後傳輸到指定的位置
3. Kibana: 可視化資料
完整的流程是:
1. 啟動ELK,啟動filebeat
2. Filebeat讀取log檔並傳給Logstash
3. Logstash接收資料後,分析、過濾資料並轉換資料格式,接著傳給Elasticsearch
4. Elasticsearch接收資料後,將資料儲存至Index中
5. Kibana從Elasticsearch取得資料,並進行資料可視化操作
6. 使用者在瀏覽器中輸入 http://localhost:5601 查看可視化的資料
# 啟動ELK
> sudo systemctl start elasticsearch (預設在9200,9300等待請求)
> sudo systemctl start logstash (預設在9600,5044等待請求)
> sudo systemctl start kibana (預設在5601等待請求)
# Filebeat
Filebeat的功能是從特定位置抓log下來,接著將資料傳給其他人
簡單的filebeat範例,可以將抓下來的資料傳給kibana可視化,同時傳給Elasticsearch儲存並分析
```yaml
filebeat.inputs:
# 設定要抓取log的路徑
- type: filestream
enabled: true
paths:
- /usr/local/var/log/nginx/*.log
# 設定連結到kibana的位址
setup.kibana:
host: "localhost:5601"
# 設定連結到elasticsearch的位址
output.elasticsearch:
hosts: ["localhost:9200"]
```
接著啟動filebeat
> ./filebeat -e -c filebeat.yml
除此之外,filebeat還能做到自訂索引、增加自訂的欄位、使用re表達視初步過濾資料等等
# Elasticsearch 介紹
## 重要名詞解釋
* Node: Elasticsearch在運作時的實體
* Document: Elasticsearch可以處理的資料結構
* Index: Elasticsearch透過Index來讀取或寫入Document
* Shard: Node中實際儲存資料的地方,分成Primary(主要)跟Replica(副本)
# Kibana介紹
https://ithelp.ithome.com.tw/articles/10266681
Kibana是一個資料分析跟可視化的工具,可以將資料繪製成各式圖表、實時數據分析、搜尋和過濾特定資料等、建立自己的dashboard等等
## 建立index pattern
* index pattern 概念: 在Elasticsearch中資料存在index中,為了要在kibana中取得index 中的資料,必須要在Kibana中設定index pattern,以選擇查看一個或多個存在Elasticsearch index 中的資料
* 舉例來說當index pattern 設定成""
1. 連到kibana,並來到home介面
2. 點選右上角三條線,選擇management中的stack management
3. 選擇kibana下的index patterns
4. 點選右上角的create index pattern
## 使用Discover找到資料
1. 使用前須先創建index pattern,接著來到來到home介面
2. 點選右上角三條線,接著選擇Analyties中的Discover
3. 點選左上角的按鈕選擇想要查看的index pattern
4. 可以從下方的Available field選擇想查看的欄位
5. 可以從上方的Search 欄位
## 使用filter過濾資料
1. 使用前須先創建index pattern,接著來到來到home介面
2. 點選右上角三條線,接著選擇Analyties中的Discover
3. 點選左上角的按鈕選擇想要查看的index pattern
4. 點選左上角的add filter,選擇想要的過濾的field (column) 跟Operator (代表filter的邏輯),接著按Save儲存即可
## 建立Visualize物件
Visualize物件是將資料可視化後的結果,建立後可以被拉到Dashboard中
1. 使用前須先創建index pattern,接著來到來到home介面
2. 點選右上角三條線,接著選擇Analyties中的Visualize Library
3. 點選右上角的create visualization按鈕,接著選擇Len卡片
4. 選擇index pattern,接著將想要的欄位拖拉到中間
5. 點選右上角的save 命名加儲存,即可成功建立Visualize物件
## 建立Dashboard
1. 使用前須先創建Visualize物件,接著來到來到home介面
2. 點選右上角三條線,接著選擇Analyties中的Dashboard,再點擊右上角的Create dashboard
3. 點選剛剛建立好的Add from library匯入之前建立好的Visualize物件
4. 可以在這個Dashboard 介面調整圖表顯示區大小和位置
5. 點選右上角的save 命名加儲存,即可成功建立Dashboard
# Redis HA
參考資料:
https://skychang.github.io/2017/04/09/Redis-Create_Redis_HA/
* HA 指的是High availability,也就是高可用性
* 要保持HA, 至少需要三台server扮演不同的工作
* Master: 負責處理主要的資料讀寫等事務(可讀、寫)
* Slave(replica): 負責備份資料,會定期從Master同步資料(只可讀)
* Sentinel(哨兵): 負責決定目前哪台server還活著並可以提供服務,若Master無回應時,會將Slave切換成Master;Master重啟後,會將原本的 Master 改為 Slave
可以透過修改redis的config來設定Master,Slave及Sentinel
## Master
設定:
在原本的 bind 127.0.0.1 後面加上這台的私有 ip
加上密碼
## Slave
設定:
找到 # slaveof 這行,拿掉註解,修改成 slaveof 10.0.0.4 6379 ( 代表 Master 的 ip 位置和 Port )
找到# masterauth,改成 masterauth foobared ( 代表 Master 的密碼 )
Bind 127.0.0.1 也要改成 Bind 127.0.0.1 10.0.0.5
把 requirepass foobared 取消註解, 讓 Slave 也可以透過 10.0.0.5 並且搭配密碼 foobred 來連入
## Sentinel
設定:
```conf
port 26379
bind 127.0.0.1 10.0.0.6
sentinel monitor master1 10.0.0.4 6379 1
sentinel down-after-milliseconds master1 5000
sentinel failover-timeout master1 900000
sentinel parallel-syncs master1 2
sentinel auth-pass master1 foobared
```
port : 代表 Sentinel Port 的位置,通常大家都會用 26379
bind : 這很重要,至少要加上 127.0.0.1,不然會因為通訊的原因,而造成不斷的去嘗試切換 Master 和 Slave,如果有三台 Sentinel ,後面也要加上這台的 IP,真的不想加這行,也可以把安全模式關閉,改成 protected-mode no
sentinel monitor : 要監控的 Master ip 和 port,其中 master1 是自訂名稱。
down-after-milliseconds : 如果在幾毫秒內, 没有回應 sentinel , 那麼 Sentinel 就會將這台標記為下線 SDOWN ( subjectively down )
failover-timeout : 轉移過程多久沒完成,算失敗。
parallel-syncs: 執行完failover任務任務後,其他的slave必須要從新的master中同步數據,這個參數是用來設定同一時間內允許參與同步的最大slave數量;這個數字越大,代表同步完成的時間越短,但會消耗更多系統資源
還要回到 Master 的機器,設定 redis.windows-service.conf, 找到 # masterauth,改成 masterauth foobared ( 代表 Master 的密碼 )。 需要這樣的原因,是因為,到時候 Master 會被切換成 Slave,如果沒有設定 masterauth foobared, 到時候這台機器會無法和未來的 Master 進行連線。 ( 設定完也不忘記重新啟動 )
## Slave 詳細機制
* redis 預設使用asynchronous(異步) replication,優點是低延遲跟高性能,缺點是可能會有資料丟失的問題
* 建議在Master及Slave中開啟關於persistence的設定;若真的要關閉,應該要避免伺服器重啟後直接重啟redis系統,否則可能會因為系統重啟速度過快,導致Sentinel來不及偵測,而重啟後又將清空後的資料同步至其他Slave中,導致資料丟失
* Redis 有key過期的機制,但在Slave中,不會自動地讓key過期並刪除,而是收到來自Master的Del通知後才會將過期的key刪除;但在使用者讀取的時候,則會根據自己的時間來判斷這個Key是否過期,並回傳對應的結果給使用者
## Sentinel詳細機制
* failiver是指一個master節點無回應後,自動將它的任務轉移到一個可用的slave節點上的過程
* quorum是指需要多少個Sentinel同意某個master節點無回應,才會將其標記為失敗(ODOWN),並根據ODOWN狀態啟動failiver任務
ex: 有五個Sentinel,quorum=2,當至少有兩個Sentinel覺得Master無回應後,且負責執行failiver任務的Sentinel獲得至少三個其他Sentinel授權,便會執行failiver任務
* Configuration propagation: 當Sentinel成功對Master進行failiver,將開始將新配置發給其他相同Master的所有Sentinel
* 因為redis屬於非同步寫入,故在連線中斷時,可能會導致使用者確定寫入的資料遺失,可以加入以下參數來解決,當master發現自己沒辦法將寫入的資料至少轉移至指定數量的slave時,會中止使用者繼續寫入
```
min-replicas-to-write 1
min-replicas-max-lag 10
```
* Sentinel可以透過__sentinel__:hello 這個chennel找到Master與其相同的其他Sentinel;也可以透過查詢Redis來找到Master的Slave們。Master新增Sentinels後,會自動更新所有Sentinel的list;由於Sentinel永遠不會忘記其他Sentinel,若確定要移除某個Sentinel,必須先確定移除該Sentinel後,一一reset其他 Sentinel;由於Sentinel永遠不會忘記其他Master,所以想要刪除Master,也必須使用類似的操作
* 可以設定Slave的優先級,當Master故障時優先級較高的Slave會優先成為新的Master。當Master無回應時選擇新的Master邏輯如下:
1. 忽略過早就與Master斷開的Slaves
2. 選擇優先級較高的Slaves
3. 若優先級相同 , 檢查replication offset,並選擇從Master中接收到更多資料的Slaves
4. 若 replication offset還是相同,則按照字典序挑出一個ID最小的Slaves(字典序最小的不一定比較好,這麼做是為了讓整個過程是可被確定的)
* SDOWN 與 ODOWN:
* SDOWN: 客觀下線狀態,當Sentinel檢查某個Master是否有正常運作時,沒有在指定的秒數內收到來自Master的有效回復時,便會進入SDOWN狀態;處於SDOWN狀態下的slave永遠不會成為新Master的候選對象。
* ODOWN: 主觀下線狀態,當指定數量的 Sentinel都回報SDOWN時,便會升級成ODOWN;若稍後條件不滿足,則ODOWN狀態會被清除;只有在ODOWN狀態下才可能進行failiver任務。
* TILT mode: 一般來說,Sentinel會每秒確認當前時間十次,並紀綠上次確認時間是甚麼時候;若某個瞬間確認的時間跟上次確認的時間相差過大或是負數,就會進入TILT mode: Sentinel仍然持續監測,但其檢測故障的能力已不再被信任,針對任何請求都會做出否定的回應;若30秒內一切正常,則退出TILT mode
# Redis persistance
redis 是將資料存在memory中的,若電腦關機資料將遺失;為了能永久保存資料,可以使用Redis persistance
* 可能的選項為:
1. 無持久性
2. RDB: 儲存資料庫某個時間點的快照,並寫進硬碟裡
3. AOF: 儲存對資料庫下的所有write指令,並在電腦重新啟動時重播這些指令
4. RDB+AOF(重啟redis後,會優先使用AOF)
(redis 重啟後,若發現有Redis persistance相關的檔案,將會自動使用)
## RDB
原理:
RDB fork(), main process 繼續處理資料庫query,child process 將資料寫進暫時的RDB檔案;完成後,新的檔案將取代舊的(可以在取代前將檔案複製到其他地方以儲存舊版本)
備份方式: RDB file 在產生後便不會再發生任何修改,故可以直接將該檔案複製到任意想要儲存的地方(ex: 雲端)
### 優點
* 非常適合備份,以還原某一時間點的資料
* RDB是一份檔案,可以自由的傳到遠端的數據中心儲存
* 能更快重啟
### 缺點
* 在上一次備份過後才加入的資料會丟失
* 雖然RDB的備份操作是使用fork()進行的,但資料量過大時可能會消耗系統性能
## AOF (Append-only file)
原理:
記錄使用者的每條write指令,並包含重寫機制: 只紀錄key值的最終結果,以盡可能地減少AOF的檔案大小;此機制不會中斷本來的query 服務,且不會有資料丟失的問題
(補充機制)
若AOF寫入到一半的時候因為意外而被強制中斷了,預設的情況下系統重啟時會忽略這條被中斷,寫入不完整的指令
若AOF檔案損壞了,預設的情況下會直接出錯;解決方法為使用fix的方法修復
備份方式: 直接複製AOF檔案所在的資料夾即可;注意: 不要在AOF進行rewrite時複製,否則可能會複製到無效的檔案
### 優點
* AOF只紀錄指令,且可以設定將指令同步至硬碟上的方式,如此一來資料丟失的可能性較小;同步方式可以為
* 不同步,交給作業系統來執行
* 每秒同步一次
* 每次操作時同步
### 缺點
* AOF檔案通常較RDB大
# Redis cluster
* 目的是進行水平拓展,將資料平均分散到不同伺服器上儲存
* 跟redis HA相容,可同時使用
* 原理是在cluster進行寫入操作後,透過一個hash table將資料映射至同一cluster的不同node(節點,代表伺服器)上儲存;hash table 中總共有16384個槽位,可以自由分配給node,當資料流進某槽位時,便會根據分配的設定決定要存到哪個node上
* 每個節點的資訊是互通的;會透過heart beat機制定期追蹤有哪些節點加入及離開這個cluster;為了實現這樣的通訊,每個node都需要開兩個port,一個用來與客戶端通訊(ex: 6379),一個用來實現node之間的通訊(ex: 16379)
* 想要啟用redis cluster這個功能,會需要兩個設定檔;除了本來就有的redis設定檔(redis.conf)外,還多了專門用於redis cluster的設定檔(nodes.conf)
* 整體啟動流程為:
啟動cluster -> 加入node(cluster 中的node間會自動進行通訊) ->(建立Master跟Slave關係) ->分配hash table的槽位(只有Master node可以被分到) -> 寫入資料(加上 -c 參數以重新導向到hash table所分配的node中寫入)
* redis cluster沒辦法保證回應OK給客戶端的write能成功被儲存,例如寫入後、將data傳給Slave前,伺服器因故當機重啟
# redis 底層原理
參考資料: https://xie.infoq.cn/article/78215ce2bb651e7079ea3c80b
* string 使用SDS實作
* list 使用linked list實作
* hash 使用hashtable實作
* set 使用hashtable實作
* sorted set 使用skiplist實作
* 若以上資料結構需要儲存的資料量夠小,則會改成intset或ziplist來儲存資料
## SDS(simple dynamic string)
為了節省空間,SDS像C++ vector一樣,用來儲存字串的空間是動態分配的;當空間不夠時,會自動配置一個兩倍的空間,但已配出的空間不會收回去
## linked list
底層是double linked list,並使用struct list 來管理這個結構(儲存linked list head, tail, size等資訊),節點內可以儲存任意的值
## hashtable
實作方法為: 當key發生碰撞時,會使用linked list的方式掛在某個key的後面;當達成一定條件後執行rehash
rehash條件: 已保存的value數量/目前的key數量 >5
rehash時,會配置一個兩倍大小的空間,並慢慢地將key-value轉移到新的hash table(具體作法是進行一次對redis的operation時,順便轉移一組key-value至新的hash table中,直到全部都順利轉移後,將新的hash table完全取代舊的,並預先配置一個新的hash table準備下一次的rehash)
## skiplist
## intset
當一個set只有包含整數,且數量不多時,會改用intset的資料結構儲存。
在儲存這些數字時,還會根據這些數字的大小,為每個數字配置適當的記憶體空間以儲存,目的是為了節省空間;若新加入了一個更大的數字,則會擴展每一個數字所分配的記憶體空間大小(因為數字量不大,顧時間成本不會很高)
## ziplist
# Nexflix workflow
目的是管理一系列任務的執行順序,並隨時監測執行的狀態
## 基本概念
* Task:
* 最基本的工作單元,可以是微服務等
* 可以由任意的不同語言實作
* 分成三種類型:
* simple task: 執行簡單的任務,例如單純的操作資料庫等等
* System task: 一些基本的系統指令,例如sleep等
* operator: 自己定義的複雜任務,例如微服務等等
* workflow:
* 由一系列的task組成,定義了如何呼叫這些task的順序跟處理邏輯
* 使用json定義,但可以被可視化成流程圖
* 方框: task
* 菱形框: operator
* 可以使用常見的邏輯(operator)管理tesk的執行順序,例如迴圈、條件判斷等
## 執行流程
定義並創建task -> implement task worker -> 定義並創建Workflow -> run Workflow(提供輸入) -> 查看結果
## 定義並創建task
使用以下json檔案來定義一個task
```json!
{
"name": "hello_world_test", //定義task的名稱
"description": "a simple task", //關於這個task的說明
"inputKeys": [ //task的輸入
"name"
],
"outputKeys": [ //task的輸出
"message"
],
"retryCOunt": 3, //當微服務無法正常啟動的時候,嘗試重啟的次數
"timeoutSeconds": 3600, //當超過這個時間後,這個task將被視為TIMED_OUT
"timeoutPolicy": "TIME_OUT_WF", //處理timeout的方式
"retryLogic": "FIXED", //重試的方法
"retryDelaySeconds": 60, //多久後再重試一次
"responseTimeoutSeconds": 600, //當任務經過設定的時間後還沒有更新狀態,便會重新安排這個任務(預設3600)
"responseLimitPerFrequency": 0,
"rateLimitFrequencyInSeconds": 1, //跟分配任務的頻率有關
"backoffScaleFactor": 1, //重新安排任務時的間隔延長係數,第一次重新安排時延遲1分鐘,第二次加1變成兩分鐘,以此類推
"ownerEmail": "foo@bar.com"
}
```
除此之外,還有一些範例中沒提到的參數:
* pollTimeoutSeconds: 當超過這個時間後這個任務還沒有被執行,便會標記成TIMED_OUT狀態
* inputTemplate: 預設的input
* concurrentExecLimit: 最大可同時執行的task數量
## implement task worker
## 定義並創建Workflow
```json!
{
"name": "first_sample_workflow", //workflow 的名字
"description": "First Sample Workflow", //針對這個workflow的描述(可選)
"version": 1, //這個worlflow的版本
"tasks": [ //定義這個workflow會用到的task configurations,可以被視為這個workflow的藍圖,非常重要
{
"name": "get_population_data", //task的名字,必須要在啟動workflow之前先註冊好
"taskReferenceName": "get_population_data",//在此workflow執行過程中task所使用的暫時名稱,可能有許多暫時名稱指向都代表同一個task(也就是name一樣但taskReferenceName不一樣)
"inputParameters": { //這個task所需的輸入參數
"http_request": {
"uri": "https://datausa.io/api/data?drilldowns=Nation&measures=Population",
"method": "GET"
}
},
"type": "HTTP" //這個task的類型,SIMPLE代表將會被remote use執行,否則應為其中一種可執行的system type
}
],
"inputParameters": [], //這個workflow的輸入,通常會將這個workflow中所有的task所需用到的參數都先傳進這裡,接著task configuration中的inputParameters在使用json path 的方式取得自己所需的參數(ex: "movieId": "${workflow.input.movieId}",);或是從其他task中的output取得(ex: "lang": "${loc_task.output.languages[0]}",)
"outputParameters": { //這個workflow的輸出
"data": "${get_population_data.output.response.body.data}",
"source": "${get_population_data.output.response.body.source}"
},
"schemaVersion": 2, //通常固定為2
"restartable": true, //是否允許workflow 重啟
"workflowStatusListenerEnabled": false, //是否允許workflow狀態監聽器運作
"ownerEmail": "example@email.com", //擁有這個workflow的人的email
"timeoutPolicy": "ALERT_ONLY", //timeout後如何處理
"timeoutSeconds": 0 //設定timeout的秒數,若這個workflow超過這個時間後還沒有結束則會被標記成timeout狀態,0代表沒有timeout限制
}
```
* 除此之外,還有一些範例中沒提到的可選設定
* inputTemplate: 預設的輸入
* failureWorkflow: 當執行某個task失敗時,可以指定一個failureWorkflow來處理這個失敗任務
* task configuration中:
* description: 描述這個task(可選)
* optional: 若task執行失敗,此workflow是否繼續(預設為true)
* inputExpression: 直接定義這個task所需輸入的來源路徑,跟inputParameters擇一使用
* asyncComplete: 若為false(預設)代表執行到需要外部資源的task時,一執行便將執行狀態設定成"COMPLETED";若為true,則會先設成IN_PROGRESS ,並等待到外部資源完成這個任務
* startDelay: 執行這個任務前的延遲等待時間
## 實戰演練
1. 在conductor UI那邊定義好workflow及task
2. 使用postman trigger workflow(workflow中包含一至多個task)
3. 本地run一支程式負責接可以執行的task
