Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
口轉發默認是10條,通過我的上一篇文章可以突破到16條,但是再多也沒有效果,而除了改html文件,lua腳本是只讀的,而且也沒看到到底哪里限制住了,因為本身是通過iptables實現的,所以取個巧,直接用命令行腳本設置得了按照如下步驟即可:
1.ssh登陸路由器,執行如下命令:
vi /tmp/port-forward.sh
2.把如下內容復制進去然后保存
oip=`ip a show ppp0 | grep inet | awk '{print }'|tr -d "addr:"`
sip=
sport=
oport=
iptables -t nat -A port_forward -d $oip/32 -p tcp -m tcp --dport $oport -j DNAT --to-destination $sip:$sport
iptables -t nat -A port_forward_ctf -p tcp -m tcp --dport $oport -j SKIPCTF
iptables -t nat -A port_forward_post -s 192.168.1.0/24 -d $sip/32 -p tcp -m tcp --dport $sport -j SNAT --to-source $oip:$oport
3.在高級設置啟動任務里按照如下增加一行
這樣就可以把192.168.1.134的3603端口映射到外部3603端口,這三個參數依次:內部ip,內部端口,外部端口。
這樣每次啟動都會執行這個命令進行端口映射,每增加一個端口就在啟動任務里增加一行就行,只是每增加一個還需要在路由器中手動先執行一下,不然只能重啟路由器!
者:潘夢源
Kruise Rollout[1]是 OpenKruise 社區開源的漸進式交付框架。Kruise Rollout 支持配合流量和實例灰度的金絲雀發布、藍綠發布、A/B Testing 發布,以及發布過程能夠基于 Prometheus Metrics 指標自動化分批與暫停,并提供旁路的無感對接、兼容已有的多種工作負載(Deployment、CloneSet、DaemonSet)。
目前 Kruise Rollout 新增了流量調度支持自定義資源的能力,從而更好的支持漸進式發布中的流量調度。本文將對 Kruise Rollout 所提出的方案進行介紹。
什么是漸進式發布?
漸進式發布(Progressive Delivery)是一種軟件部署和發布策略,旨在逐步將新版本或功能引入生產環境,以降低風險并確保系統的穩定性。一些常見的漸進式發布形式如下:
金絲雀發布、A/B 測試和藍綠發布都是逐步測試和評估新功能或變更的策略,它們可以根據具體的需求和場景選擇適合的部署和測試策略,并結合流量灰度等技術實現逐步發布和測試新版本或功能。
為什么需要對網關資源提供支持?
Kruise Rollout 目前已經對 Gateway API 提供了支持,那么為什么還需要對不同供應商的網關資源提供支持呢?在解釋這個問題之前,我們先來簡單介紹一下 Gateway API。
當前社區中不同的供應商都有自己的網關資源,并提出了自己的標準,而 Kubernetes 為了提供一個統一的網關資源標準,構建標準化的,獨立于供應商的 API,提出了 Gateway API。目前,盡管 Gateway API 還處于開發階段,但已經有很多項目表示支持或計劃支持 Gateway API。包括:
然而由于目前 Gateway API 并不能覆蓋供應商所提出網關資源的所有功能,并且仍然有大量用戶使用供應商提供的網關資源,雖然用戶可以通過開發 Gateway API 對網關資源進行適配,但這樣的工作量較大,所以僅僅為 Gateway API 提供支持是遠遠不夠的,盡管隨著 Gateway API 特性的不斷豐富,在未來,使用 Gateway API 將成為一種更加推薦的方式。因此,雖然 Kruise Rollout 目前已經提供了對 Gateway API 的支持,如何對現有供應商多種多樣的網關資源提供支持仍然是一個重要的問題。
如何兼容社區多樣的網關方案?
當前社區中已經存在許多廣泛使用的供應商提供的網關資源,比如:Istio、Kong、Apisix 等,然而正如前文所述,這些資源的配置并沒有形成統一的標準,因此無法設計出一套通用的代碼對資源進行處理,這種情況給開發人員帶來了一些不便和挑戰。
argo-rollouts 與 flagger 兼容方案
為了能夠兼容更多的社區網關資源,一些方案被提出,例如 flagger、argo-rollouts 為每一種網關資源都提供了代碼實現。這些方案的實現相對簡單,但也存在一些問題:
argo-rollouts 不同資源配置
因此,需要一種支持用戶定制,可以靈活插拔的實現方案,以適配社區以及用戶定制的多種多樣的網關資源,來滿足社區不同的用戶的需求,增強 Kruise Rollout 的兼容性和擴展性。
為此,我們提出了一種基于 Lua 腳本的網關資源可擴展流量調度方案。
Kruise Rollout 使用基于 Lua 腳本的網關資源定制方案,本方案通過調用 Lua 腳本根據發布策略和網關資源原始狀態來獲取并更新資源的期待工作狀態(狀態包含 spec、labels 以及 annotations),可以使用戶能夠輕松地適配和集成不同類型的網關資源,而無需修改現有的代碼和配置。
本方案對于網關資源的處理可以表示為上圖,整個過程可以描述為:
通過使用 Kruise Rollout,用戶可以:
同時,Kruise Rollout 采用的方案僅需要添加 5 個新接口即可實現對多種多樣網關資源的支持。相比之下,其他方案例如 argo-rollouts 則為不同供應商的網關資源提供了不同的接口,對于 Istio 和 Apisix 來說,argo-rollouts 分別提供了 14 個和 4 個新的接口,而且,該方案隨著對更多網關資源的支持,接口數量還會持續增長。相比之下,Kruise Rollout 并不需要為新的網關資源提供新的接口,這使得 Kruise Rollout 成為一種更簡潔、更易于維護的選擇,而不會增加過多的接口負擔。同時,編寫 Lua 腳本相對于開發 Gateway API 對網關資源進行適配,可以大大減小開發人員的工作量。
以下展示了一個利用 Lua 腳本對 Istio DestinationRule 進行處理的的示例。
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
...
spec:
...
trafficRoutings:
- service: mocka
createCanaryService: false # 使用原有service,不創建新的canary service
networkRefs: # 需要控制的網關資源
- apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
name: ds-demo
patchPodTemplateMetadata:
labels
version: canary # 為新版本pod打上label
2. 對 Istio DestinationRule 進行處理的 Lua 腳本為:
local spec = obj.data.spec -- 獲取資源的spec,obj.data為資源的狀態信息
local canary = {} -- 初始化一條指向新版本的canary路由規則
canary.labels = {} -- 初始化canary路由規則的labels
canary.name = "canary" -- 定義canary路由規則名稱
-- 循環處理rollout配置的新版本pod label
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v -- 向canary規則中加入pod label
end
table.insert(spec.subsets, canary) -- 向資源的spec.subsets中插入canary規則
return obj.data -- 返回資源狀態
3. 處理完的 DestinationRule 為:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
spec:
...
subsets:
- labels: # -+
version: canary # |- Lua腳本處理后新插入的規則
name: canary # -+
- labels:
version: base
name: version-base
接下來介紹一個利用我們所提出方案對 Istio 進行支持的具體案例。
1. 首先部署如下圖所示的服務。該服務由以下幾部分構成:
nginx 服務的 deployment 如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
version: base
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html
volumes:
- name: html-volume
configMap:
name: nginx-configmap-base # 掛載ConfigMap作為index
2. 創建 rollout 資源,配置發布規則,該 rollout 分為兩批發布:
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
annotations:
rollouts.kruise.io/rolling-style: canary
spec:
disabled: false
objectRef:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deployment
strategy:
canary:
steps:
- weight: 20 # 第一批轉發20%的流量進入新版本pod
- replicas: 1 # 第二批將包含version=canary header的流量轉發入新版本pod
matches:
- headers:
- type: Exact
name: version
value: canary
trafficRoutings:
- service: nginx-service # 舊版本pod使用的service
createCanaryService: false # 不創建新的canary service,新舊pod共用一個service
networkRefs: # 需要修改的網關資源
- apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
name: nginx-vs
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
name: nginx-dr
patchPodTemplateMetadata: # 為新版本pod打上version=canary的label
labels:
version: canary
3. 修改 nginx 服務 deployment 中掛載的 ConfigMap 開始金絲雀發布。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
...
volumes:
- name: html-volume
configMap:
name: nginx-configmap-canary # 掛載新的ConfigMap作為index
4. 開始發布第一批,Kruise Rollout 自動調用定義的 Lua 腳本對 VirtualService 和 DestinationRule 資源進行修改,進行流量調度,將 20% 的流量轉發至新版本 pod 中,此時整個服務的流量表示為下圖所示:
5. 執行命令 kubectl-kruise rollout approve rollout/rollouts-demo,開始發布第二批,Kruise Rollout 自動調用定義的 Lua 腳本對 VirtualService 和 DestinationRule 資源進行修改,進行流量調度,將包含 version=canary header 的流量轉發至新版本 pod 中,此時整個服務的流量表示為下圖所示:
6. 執行命令 kubectl-kruise rollout approve rollout/rollouts-demo,發布結束,VirtualService 和 DestinationRule 資源恢復至發布前狀態,所有流量路由至新版本 pod。
在調用 Lua 腳本獲取資源狀態新狀態時,Kruise Rollout 支持兩種 Lua 腳本調用方式,分別為:
Kruise Rollout 默認首先查找本地是否存在已發布的 Lua 腳本,這些腳本通常需要設計測試案例進行單元測試驗證其可用性,具有更好的穩定性。測試案例的格式如下所示,Kruise Rollout 利用 Lua 腳本根據 rollout 中定義的發布策略對資源原始狀態進行處理,得到發布過程中每一步的資源新狀態,并與測試案例中 expected 中定義的期待狀態進行對比,以驗證 Lua 腳本是否按照預期工作。
rollout:
# rollout配置
original:
# 資源的原始狀態
expected:
# 發布過程中資源的期待狀態
在資源的 Lua 腳本未發布的情況下,用戶還可以快速的通過在 ConfigMap 中配置 Lua 腳本的方式由 Kruise Rollout 調用從而對資源進行處理。
apiVersion: v1
kind: ConfigMap
metadata:
name: kruise-rollout-configuration
namespace: kruise-rollout
data:
# 鍵以lua.traffic.routing.Kind.CRDGroup的形式命名
"lua.traffic.routing.DestinationRule.networking.istio.io": |
--- 定義Lua腳本
local spec = obj.data.spec
local canary = {}
canary.labels = {}
canary.name = "canary"
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v
end
table.insert(spec.subsets, canary)
return obj.data
詳細的 Lua 腳本配置說明參見 Kruise Rollout 官網[2]。
非常歡迎你通過 Github/Slack/釘釘/微信 等方式加入我們來參與 OpenKruise 開源社區。
你是否已經有一些希望與我們社區交流的內容呢?可以在我們的社區雙周會[3]上分享你的聲音,或通過以下渠道參與討論:
相關鏈接:
[1] Kruise Rollout
https://github.com/openkruise/rollouts
[2] Kruise Rollout 官網
https://openkruise.io/rollouts/introduction
[3] 社區雙周會
https://shimo.im/docs/gXqmeQOYBehZ4vqo
[4] Slack channel
https://kubernetes.slack.com/?redir=%2Farchives%2Fopenkruise
簡單來說,事務(transaction)是指單個邏輯單元執行的一系列操作。
事務有如下四大特性:
Redis中的事務通過multi,exec,discard,watch這四個命令來完成。
Redis的單個命令都是原子性的,所以確保事務的就是多個命令集合一起執行。
Redis命令集合打包在一起,使用同一個任務確保命令被依次有序且不被打斷的執行,從而保證事務性。
Redis是弱事務,不支持事務的回滾。
事務命令簡介
事務操作
# 普通的執行多個命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name zhangsan
QUEUED
127.0.0.1:6379> hmset m_set name zhangsan age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
# 執行命令前清空隊列 將會導致事務執行不成功
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name_1 lisi
QUEUED
127.0.0.1:6379> hmset m_set_1 name lisi age 21
QUEUED
# 提交事務前執行了清空隊列命令
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
# 監聽一個key,并且在事務提交之前改變在另一個客戶端改變它的值,也會導致事務失敗
127.0.0.1:6379> set m_name_2 wangwu01
OK
127.0.0.1:6379> watch m_name_2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_name_2 wangwu02
QUEUED
# 另外一個客戶端在exec之前執行之后,這里會返回nil,也就是清空了隊列,而不是執行成功
127.0.0.1:6379> exec
(nil)
# 另外一個客戶端在exec之前執行
127.0.0.1:6379> set m_name_2 niuqi
OK
我們前面總是在說,Redis的事務命令是打包放在一個隊列里的。那么來看一下Redis客戶端的數據結構吧。
client數據結構
typedef struct client {
// 客戶端唯一的ID
uint64_t id;
// 客戶端狀態 表示是否在事務中
uint64_t flags;
// 事務狀態
multiState mstate;
// ...還有其他的就不一一列舉了
} client;
multiState事務狀態數據結構
typedef struct multiState {
// 事務隊列 是一個數組,按照先入先出順序,先入隊的命令在前 后入隊的命令在后
multiCmd *commands; /* Array of MULTI commands */
// 已入隊命令數
int count; /* Total number of MULTI commands */
// ...略
} multiState;
multiCmd事務命令數據結構
/* Client MULTI/EXEC state */
typedef struct multiCmd {
// 命令的參數
robj **argv;
// 參數長度
int argv_len;
// 參數個數
int argc;
// redis命令的指針
struct redisCommand *cmd;
} multiCmd;
Redis的事務執行流程圖解
Redis的事務執行流程分析
我們知道,Redis有一個expires的字典用于key的過期事件,同樣,監聽的key也有一個類似的watched_keys字典,key是要監聽的key,值是一個鏈表,記錄了所有監聽這個key的客戶端。
而監聽,就是監聽這個key是否被改變,如果被改變了,監聽這個key的客戶端的flags屬性就設置為REDIS_DIRTY_CAS。
Redis客戶端向服務器端發送exec命令,服務器判斷Redis客戶端的flags,如果為REDIS_DIRTY_CAS,則清空事務隊列。
redis監聽機制圖解
redis監聽key數據結構
回過頭再看一下RedisDb類的watched_keys,確實是一個字典,數據結構如下:
typedef struct redisDb {
dict *dict; /* 存儲所有的key-value */
dict *expires; /* 存儲key的過期時間 */
dict *blocking_keys; /* blpop存儲阻塞key和客戶端對象*/
dict *ready_keys; /* 阻塞后push,響應阻塞的那些客戶端和key */
dict *watched_keys; /* 存儲watch監控的key和客戶端對象 WATCHED keys for MULTI/EXEC CAS */
int id; /* 數據庫的ID為0-15,默認redis有16個數據庫 */
long long avg_ttl; /* 存儲對象的額平均ttl(time in live)時間用于統計 */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;
為什么說Redis是弱事務性呢? 因為如果redis事務中出現語法錯誤,會暴力的直接清除整個隊列的所有命令。
# 在事務外設置一個值為test
127.0.0.1:6379> set m_err_1 test
OK
127.0.0.1:6379> get m_err_1
"test"
# 開啟事務 修改值 但是隊列的其他命令出現語法錯誤 整個事務會被discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m_err_1 test1
QUEUED
127.0.0.1:6379> sets m_err_1 test2
(error) ERR unknown command `sets`, with args beginning with: `m_err_1`, `test2`,
127.0.0.1:6379> set m_err_1 test3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 重新獲取值
127.0.0.1:6379> get m_err_1
"test"
我們發現,如果命令隊列中存在語法錯誤,是直接的清除隊列的所有命令,并不是進行事務回滾,但是語法錯誤是能夠保證原子性的。
再來看一些,如果出現類型錯誤呢?比如開啟事務后設置一個key,先設置為string, 然后再當成列表操作。
# 開啟事務
127.0.0.1:6379> multi
OK
# 設置為字符串
127.0.0.1:6379> set m_err_1 test_type_1
QUEUED
# 當初列表插入兩個值
127.0.0.1:6379> lpush m_err_1 test_type_1 test_type_2
QUEUED
# 執行
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of valu
# 重新獲取值,我們發現我們的居然被改變了,明明,事務執行失敗了啊
127.0.0.1:6379> get m_err_1
"test_type_1"
直到現在,我們確定了redis確實不支持事務回滾。因為我們事務失敗了,但是命令卻是執行成功了。
弱事務總結
那么,redis就沒有辦法保證原子性了嗎,當然有,Redis的lua腳本就是對弱事務的一個補充。
lua是一種輕量小巧的腳本語言,用標準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。
Lua應用場景:游戲開發、獨立應用腳本、Web應用腳本、擴展和數據庫插件。
OpenResty:一個可伸縮的基于Nginx的Web平臺,是在nginx之上集成了lua模塊的第三方服務器。
OpenResty是一個通過Lua擴展Nginx實現的可伸縮的Web平臺,內部集成了大量精良的Lua庫、第三方模塊以及大多數的依賴項。用于方便地搭建能夠處理超高并發(日活千萬級別)、擴展性極高的動態Web應用、Web服務和動態網 關。 功能和nginx類似,就是由于支持lua動態腳本,所以更加靈活,可以實現鑒權、限流、分流、日志記 錄、灰度發布等功能。
OpenResty通過Lua腳本擴展nginx功能,可提供負載均衡、請求路由、安全認證、服務鑒權、流量控 制與日志監控等服務。
類似的還有Kong(Api Gateway)、tengine(阿里)
lua腳本下載和安裝http://www.lua.org/download.html
lua腳本參考文檔:http://www.lua.org/manual/5.4/
# curl直接下載
curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz
# 解壓
tar zxf lua-5.4.4.tar.gz
# 進入,目錄
cd lua-5.4.4
# 編譯安裝
make all test
編寫lua腳本
編寫一個lua腳本test.lua,就定義一個本地變量,打印出來即可。
local name = "zhangsan"
print("name:",name)
執行lua腳本
[root@VM-0-5-centos ~]# lua test.lua
name: zhangsan
Redis從2.6開始,就內置了lua編譯器,可以使用EVAL命令對lua腳本進行求值。
腳本命令是原子性的,Redis服務器再執行腳本命令時,不允許新的命令執行(會阻塞,不在接受命令)。、
EVAL命令
通過執行redis的eval命令,可以運行一段lua腳本。
EVAL script numkeys key [key ...] arg [arg ...]
EVAL命令說明
簡單來說,就是
eval lua腳本片段 參數個數(假設參數個數=2) 參數1 參數2 參數1值 參數2值
EVAL命令執行
# 執行一段lua腳本 就是把傳入的參數和對應的值返回回去
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 name age zhangsan 20
1) "name"
2) "age"
3) "zhangsan"
4) "20"
lua腳本中調用redis
我們直到了如何接受和返回參數了,那么lua腳本中如何調用redis呢?
其實就是redis.call會把異常拋出來,redis.pcall則時捕獲了異常,不會拋出去。
lua腳本調用redis設置值
# 使用redis.call設置值
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 eval_01 001
OK
127.0.0.1:6379> get eval_01
"001"
EVALSHA命令
前面的eval命令每次都要發送一次腳本本身的內容,從而每次都會編譯腳本。
Redis提供了一個緩存機制,因此不會每次都重新編譯腳本,可能在某些場景,腳本傳輸消耗的帶寬可能是不必要的。
為了減少帶寬的西消耗,Redis實現了evaklsha命令,它的作用和eval一樣,只是它接受的第一個參數不是腳本,而是腳本的SHA1校驗和(sum)。
所以如何獲取這個SHA1的值,就需要提到Script命令。
執行evalsha命令
# 使用script load將腳本內容加載到緩存中,返回sha的值
127.0.0.1:6379> script load "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
# 使用evalsha和返回的sha的值 + 參數個數 參數名稱和值執行
127.0.0.1:6379> evalsha c686f316aaf1eb01d5a4de1b0b63cd233010e63d 1 eval_02 002
OK
# 獲取結果
127.0.0.1:6379> get eval_02
"002"
我們上面都是將腳本寫在代碼行里面,可以不可以將腳本內容寫在xxx.lua中,直接執行呢? 當然是可以的。
使用redis-cli運行外置lua腳本
編寫外置腳本test2.lua, 設置值到redis中。
# 腳本內容 也就是設置一個值
return redis.call('set',KEYS[1],ARGV[1])
# 執行結果,可以使用./redis-cli -h 127.0.0.1 -p 6379 指定redis ip、端口等
root@62ddf68b878d:/data# redis-cli --eval /data/test2.lua eval_03 , test03
OK
利用Redis整合lua腳本,主要是為了保證性能是事務的原子性,因為redis的事務功能確實有些差勁!
Redis如果開啟了主從復制,腳本是如何從主服務器復制到從服務器的呢?
首先,redis的腳本復制有兩種模式,腳本傳播模式和命令傳播模式。
在開啟了主從,并且開啟了AOF持久化的情況下。
其實就是主服務器執行什么腳本,從服務器就執行什么樣的腳本。但是如果有當前事件,隨機函數等會導致差異。
主服務器執行命令
# 執行多個redis命令并返回
127.0.0.1:6379> eval "local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_01 eval_test_02 0001 0002
1) OK
2) OK
127.0.0.1:6379> get eval_test_01
"0001"
127.0.0.1:6379> get eval_test_02
"0002"
那么主服務器將向從服務器發送完全相同的eval命令:
eval "local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_01 eval_test_02 0001 0002
注意:在這一模式下執行的腳本不能有時間、內部狀態、隨機函數等。執行相同的腳本以及參數必須產生相同的效果。在Redis5,也是處于同一個事務中。
處于命令傳播模式的主服務器會將執行腳本產生的所有寫命令用事務包裹起來,然后將事務復制到AOF文件以及從服務器里面.
因為命令傳播模式復制的是寫命令而不是腳本本身,所以即使腳本本身包含時間、內部狀態、隨機函數等,主服務器給所有從服務器復制的寫命令仍然是相同的。
為了開啟命令傳播模式,用戶在使用腳本執行任何寫操作之前,需要先在腳本里面調用以下函數:
redis.replicate_commands()
redis.replicate_commands() 只對調用該函數的腳本有效:在使用命令傳播模式執行完當前腳本之后,服務器將自動切換回默認的腳本傳播模式。
執行腳本
eval "redis.replicate_commands();local result1 = redis.call('set',KEYS[1],ARGV[1]); local result2 = redis.call('set',KEYS[2],ARGV[2]); return {result1, result2}" 2 eval_test_03 eval_test_04 0003 0004
appendonly.aof文件內容
*1
$5
MULTI
*3
$3
set
$12
eval_test_03
$4
0003
*3
$3
set
$12
eval_test_04
$4
0004
*1
$4
EXEC
可以看到,在一個事務里面執行了我們腳本執行的命令。
同樣的道理,主服務器只需要向從服務器發送這些命令就可以實現主從腳本數據同步了。
Redis的事務是弱事務,多個命令開啟事務一起執行性能比較低,且不能一定保證原子性。所以lua腳本就是對它的補充,它主要就是為了保證redis的原子性。
比如有的業務(接口Api冪等性設計,生成token,(取出toker并判斷是否存在,這就不是原子操作))我們需要獲取一個key, 并且判斷這個key是否存在。就可以使用lua腳本來實現。
還有很多地方,我們都需要redis的多個命令操作需要保證原子性,此時lua腳本可能就是一個不二選擇。
本人還寫了Redis的其他相關文章,有興趣的可以點擊查看!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。