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 91精品国产91久久久久青草,国产午夜精品鲁丝片,一级毛片免费视频网站

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          斐訊k3官改完全突破端口轉發限制的方法

          口轉發默認是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)是一種軟件部署和發布策略,旨在逐步將新版本或功能引入生產環境,以降低風險并確保系統的穩定性。一些常見的漸進式發布形式如下:

          • 金絲雀發布:在發布時會創建一個金絲雀版本的 Deployment 進行驗證,當驗證通過后,再進行全量的工作負載升級,并刪除金絲雀版本的 Deployment。

          • A/B 測試:按照一定的規則將用戶流量切分成 A、B 兩個不相交通路,并將導入不同版本的 Pod 實例進行處理,以此來更好地觀察、對比或者灰度新版本能力。

          金絲雀發布、A/B 測試和藍綠發布都是逐步測試和評估新功能或變更的策略,它們可以根據具體的需求和場景選擇適合的部署和測試策略,并結合流量灰度等技術實現逐步發布和測試新版本或功能。


          為什么需要對網關資源提供支持?

          Kruise Rollout 目前已經對 Gateway API 提供了支持,那么為什么還需要對不同供應商的網關資源提供支持呢?在解釋這個問題之前,我們先來簡單介紹一下 Gateway API。

          當前社區中不同的供應商都有自己的網關資源,并提出了自己的標準,而 Kubernetes 為了提供一個統一的網關資源標準,構建標準化的,獨立于供應商的 API,提出了 Gateway API。目前,盡管 Gateway API 還處于開發階段,但已經有很多項目表示支持或計劃支持 Gateway API。包括:

          • Istio 是最流行的服務網格項目之一,Istio 1.9 版本計劃引入實驗性的 Gateway API 支持。用戶可以通過 Gateway 和 HTTPRoute 資源來配置 Istio 的 Envoy 代理。
          • Apache APISIX 是一個動態、實時、高性能的 API 網關,APISIX 目前支持Gateway API 規范的 v1beta1 版本,用于其 Apache APISIX Ingress Controller。
          • Kong 是一個為混合云和多云環境構建的開源 API 網關,Kong 在 Kong Kubernetes Ingress Controller (KIC) 以及 Kong Gateway Operator 中支持 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 腳本的可擴展流量調度

          Kruise Rollout 使用基于 Lua 腳本的網關資源定制方案,本方案通過調用 Lua 腳本根據發布策略和網關資源原始狀態來獲取并更新資源的期待工作狀態(狀態包含 spec、labels 以及 annotations),可以使用戶能夠輕松地適配和集成不同類型的網關資源,而無需修改現有的代碼和配置。

          本方案對于網關資源的處理可以表示為上圖,整個過程可以描述為:

          1. 用戶定義了 Rollout 流量灰度規則、需要修改的資源等信息,開始金絲雀發布
          2. 根據 Rollout 配置獲取指定資源
          3. 根據資源調用對應的 Lua 腳本
          4. 將資源當前狀態轉為字符串存入資源 annotation 中,并與發布策略一同輸入 Lua 腳本
          5. 利用 Lua 腳本根據當前狀態和發布策略處理得到新狀態并更新資源
          6. 發布結束后,從 annotation 中獲取資源的原始狀態對資源進行恢復

          通過使用 Kruise Rollout,用戶可以:

          • 定制處理網關資源的 Lua 腳本,可以自由的實現對資源的處理邏輯,為更多資源提供支持
          • 利用一套通用的 Rollout 配置模版對不同資源進行配置,降低配置的復雜性,方便用戶配置

          同時,Kruise Rollout 采用的方案僅需要添加 5 個新接口即可實現對多種多樣網關資源的支持。相比之下,其他方案例如 argo-rollouts 則為不同供應商的網關資源提供了不同的接口,對于 Istio 和 Apisix 來說,argo-rollouts 分別提供了 14 個和 4 個新的接口,而且,該方案隨著對更多網關資源的支持,接口數量還會持續增長。相比之下,Kruise Rollout 并不需要為新的網關資源提供新的接口,這使得 Kruise Rollout 成為一種更簡潔、更易于維護的選擇,而不會增加過多的接口負擔。同時,編寫 Lua 腳本相對于開發 Gateway API 對網關資源進行適配,可以大大減小開發人員的工作量。

          以下展示了一個利用 Lua 腳本對 Istio DestinationRule 進行處理的的示例。

          1. 首先定義 rollout 配置文件:
          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


          Kruise Rollout 進行 Istio 資源流量調度實踐

          接下來介紹一個利用我們所提出方案對 Istio 進行支持的具體案例。

          1. 首先部署如下圖所示的服務。該服務由以下幾部分構成:

            • 由 Ingress Gateway 作為外部流量網關
            • 通過 VirtualService 和 DestinationRule 將流量調度至 nginx pod 中
            • 利用 ConfigMap 作為主頁 nginx pod 的主頁

          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 分為兩批發布:

            • 第一批將 20% 的流量轉發至新發布的 pod 中
            • 第二批將帶有 header version=canary 的流量轉發至新版本 pod 中
          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 腳本快速配置網關資源的流量調度

          在調用 Lua 腳本獲取資源狀態新狀態時,Kruise Rollout 支持兩種 Lua 腳本調用方式,分別為:

          • 自定義的 Lua 腳本:用戶自定義的,以 ConfigMap 的形式定義并在 Rollout 中調用
          • 已發布的 Lua 腳本:社區通用的、已經穩定的 Lua 腳本,隨 Kruise Rollout 打包發布

          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]


          未來規劃

          • 更多網關協議支持:Kruise Rollout 目前是以 Lua 腳本插件化的方式支持多類型的網關協議,我們后續會重點加大這方面的投入,但面對百花齊放的協議類型,單靠社區 Maintainer 的單薄力量還遠遠不夠,希望更多的社區小伙伴加入我們,一起來不斷完善這方面的內容。
          • 全鏈路灰度支持:全鏈路灰度是具有更加細粒度和全面的灰度發布模式,它涵蓋了應用程序的所有服務,而不止對單一的服務進行灰度,可以更好的對新服務進行模擬和測試。目前可以通過社區的網關資源如 Istio 進行配置來實現,但人工配置往往需要消耗較大的精力。我們將對這一部分進行探索,從而實現對全鏈路灰度的支持。


          社區參與

          非常歡迎你通過 Github/Slack/釘釘/微信 等方式加入我們來參與 OpenKruise 開源社區。

          你是否已經有一些希望與我們社區交流的內容呢?可以在我們的社區雙周會[3]上分享你的聲音,或通過以下渠道參與討論:

          • 加入社區 Slack channel [4](English)
          • 加入社區釘釘群:搜索群號 23330762 (Chinese)
          • 加入社區微信群(新):添加用戶 openkruise 并讓機器人拉你入群 (Chinese)

          相關鏈接:

          [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)是指單個邏輯單元執行的一系列操作。

          1.1、事務的四大特性ACID

          事務有如下四大特性:

          • 1、原子性(Atomicity): 構成事務的所有操作都必須是一個邏輯單元,要么全部執行,要么全不執行
          • 2、一致性(Consistency): 數據庫在事務執行前后狀態都必須是穩定的或者一致的。A(1000)給B(200)轉賬100后A(900),B(300)總和保持一致。
          • 3、隔離性(Isolation): 事務之間相互隔離,互不影響。
          • 4、持久性(Durability): 事務執行成功后數據必須寫入磁盤,宕機重啟后數據不會丟失。

          2、Redis中的事務

          Redis中的事務通過multi,exec,discard,watch這四個命令來完成。

          Redis的單個命令都是原子性的,所以確保事務的就是多個命令集合一起執行。

          Redis命令集合打包在一起,使用同一個任務確保命令被依次有序且不被打斷的執行,從而保證事務性。

          Redis是弱事務,不支持事務的回滾。

          2.1、事務命令

          事務命令簡介

          • 1、multi(開啟事務)
            • 用于表示事務塊的開始,Redis會將后續的命令逐個放入隊列,然后使用exec后,原子化的執行這個隊列命令。
            • 類似于mysql事務的begin
          • 2、exec(提交事務)
            • 執行命令隊列
            • 類似于mysql事務的commit
          • 3、discard(清空執行命令)
            • 清除命令隊列中的數據
            • 類似于mysql事務的rollback,但與rollback不一樣 ,這里是直接清空隊列所有命令,從而不執行。所以不是的回滾。就是個清除。
          • 4、watch
            • 監聽一個redis的key 如果key發生變化,watch就能后監控到。如果一個事務中,一個已經被監聽的key被修改了,那么此時會清空隊列。
          • 5、unwatch
            • 取消監聽一個redis的key

          事務操作

          # 普通的執行多個命令
          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

          2.2、事務機制分析

          我們前面總是在說,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的事務執行流程分析

          • 1、事務開始時,在Client中,有屬性flags,用來表示是否在事務中,此時設置flags=REDIS_MULTI
          • 2、Client將命令存放在事務隊列中,事務本身的一些命令除外(EXEC,DISCARD,WATCH,MULTI)
          • 3、客戶端將命令放入multiCmd *commands,也就是命令隊列
          • 4、Redis客戶端將向服務端發送exec命令,并將命令隊列發送給服務端
          • 5、服務端接受到命令隊列后,遍歷并一次執行,如果全部執行成功,將執行結果打包一次性返回給客戶端。
          • 6、如果執行失敗,設置flags=REDIS_DIRTY_EXEC, 結束循環,并返回失敗。

          2.3、監聽機制分析

          我們知道,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;

          2.4、Redis的弱事務性

          為什么說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確實不支持事務回滾。因為我們事務失敗了,但是命令卻是執行成功了。

          弱事務總結

          • 1、大多數的事務失敗都是因為語法錯誤(支持回滾)或者類型錯誤(不支持回滾),而這兩種錯誤,再開發階段都是可以遇見的
          • 2、Redis為了性能,就忽略了事務回滾。

          那么,redis就沒有辦法保證原子性了嗎,當然有,Redis的lua腳本就是對弱事務的一個補充。

          3、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(阿里)

          3.1、Lua安裝(Linux)

          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

          3.2、Redis中使用Lua

          Redis從2.6開始,就內置了lua編譯器,可以使用EVAL命令對lua腳本進行求值。

          腳本命令是原子性的,Redis服務器再執行腳本命令時,不允許新的命令執行(會阻塞,不在接受命令)。、

          EVAL命令

          通過執行redis的eval命令,可以運行一段lua腳本。

          EVAL script numkeys key [key ...] arg [arg ...]

          EVAL命令說明

          • 1、script:是一段Lua腳本程序,它會被運行在Redis服務器上下文中,這段腳本不必(也不應該)定義為一個Lua函數。
          • 2、numkeys:指定鍵名參數的個數。
          • 3、key [key ...]:從EVAL的第三個參數開始算起,使用了numkeys個鍵(key),表示在腳本中所用到的哪些Redis鍵(key),這些鍵名參數可以在Lua中通過全局變量KEYS數組,用1為基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)
          • 4、arg [arg ...]:可以在Lua中通過全局變量ARGV數組訪問,訪問的形式和KEYS變量類似(ARGV[1] 、 ARGV[2] ,諸如此類)

          簡單來說,就是

          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呢?

          • 1、redis.call
            • 返回值就是redis命令執行的返回值
            • 如果出錯,則返回錯誤信息,不繼續執行
          • 2、redis.pcall
            • 返回值就是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命令。

          • 1、SCRIPT FLUSH :清除所有腳本緩存。
          • 2、SCRIPT EXISTS :根據給定的腳本校驗和,檢查指定的腳本是否存在于腳本緩存。
          • 3、SCRIPT LOAD :將一個腳本裝入腳本緩存,返回SHA1摘要,但并不立即運行它。
          • 4、SCRIPT KILL :殺死當前正在運行的腳本

          執行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的事務功能確實有些差勁!

          4、Redis的腳本復制

          Redis如果開啟了主從復制,腳本是如何從主服務器復制到從服務器的呢?

          首先,redis的腳本復制有兩種模式,腳本傳播模式和命令傳播模式。

          在開啟了主從,并且開啟了AOF持久化的情況下。

          4.1、腳本傳播模式

          其實就是主服務器執行什么腳本,從服務器就執行什么樣的腳本。但是如果有當前事件,隨機函數等會導致差異。

          主服務器執行命令

          # 執行多個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,也是處于同一個事務中。

          4.2、命令傳播模式

          處于命令傳播模式的主服務器會將執行腳本產生的所有寫命令用事務包裹起來,然后將事務復制到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

          可以看到,在一個事務里面執行了我們腳本執行的命令。

          同樣的道理,主服務器只需要向從服務器發送這些命令就可以實現主從腳本數據同步了。

          5、Redis的管道/事務/腳本

          • 1、管道其實就是一次性執行一批命令,不保證原子性,命令都是獨立的,屬于無狀態操作(也就是普通的批處理)
          • 2、事務和腳本是有原子性的,但是事務是弱原子性,lua腳本是強原子性。
          • 3、lua腳本可以使用lua語言編寫比較復雜的邏輯。
          • 4、lua腳本的原子性強于事務,腳本執行期間,另外的客戶端或其他的任何腳本或命令都無法執行。所以lua腳本的執行事件應該盡可能的短,不然會導致redis阻塞不能做其他工作。

          6、小結

          Redis的事務是弱事務,多個命令開啟事務一起執行性能比較低,且不能一定保證原子性。所以lua腳本就是對它的補充,它主要就是為了保證redis的原子性。

          比如有的業務(接口Api冪等性設計,生成token,(取出toker并判斷是否存在,這就不是原子操作))我們需要獲取一個key, 并且判斷這個key是否存在。就可以使用lua腳本來實現。

          還有很多地方,我們都需要redis的多個命令操作需要保證原子性,此時lua腳本可能就是一個不二選擇。

          7、相關文章

          本人還寫了Redis的其他相關文章,有興趣的可以點擊查看!

          • <<Redis持久化機制分析>>
          • <<Redis的事件處理機制分析>>
          • <<Redis客戶端和服務端如何通信?>>
          • <<redis的淘汰機制分析>>
          • <<Redis的底層數據結構分析>>
          • <<Redis的8種數據類型,什么場景使用?>>
          • <<緩存是什么?緩存有哪些分類?使用它的代價是什么?>>
          • <<緩存的6種常見的使用場景>>

          主站蜘蛛池模板: 男人的天堂av亚洲一区2区 | 日本一区午夜艳熟免费| 亚洲国产综合无码一区二区二三区| 久久国产精品一区| 视频在线一区二区三区| 亚洲日韩中文字幕无码一区| 国产精品日本一区二区不卡视频| 精品国产亚洲一区二区三区在线观看 | 国产亚洲综合一区二区三区| 久久久国产精品亚洲一区| 国产午夜精品一区二区三区嫩草| 日本一区二区三区在线网| 在线电影一区二区三区| 欲色aV无码一区二区人妻| 亚洲va乱码一区二区三区| 精品人妻中文av一区二区三区| 国产精品无码一区二区三级| 中文字幕无码一区二区三区本日| 国产伦精品一区二区三区视频小说 | 亚洲爆乳无码一区二区三区| 精品亚洲一区二区三区在线观看| 国产一区二区三区视频在线观看| 精品国产一区二区三区久| 国产成人一区二区三中文| 99精品国产高清一区二区| 日韩精品区一区二区三VR| 一区二区三区四区免费视频| 免费一区二区三区| 亚洲综合无码一区二区| 无码国产精品一区二区免费3p| 久久久精品人妻一区亚美研究所| 精品视频一区二区三区在线观看 | 亚洲日韩国产精品第一页一区| 中文字幕色AV一区二区三区| 夜夜添无码一区二区三区| 色久综合网精品一区二区| 在线观看一区二区三区av| 国产一区二区在线观看麻豆| 国产一区二区在线观看app| 亚洲乱色熟女一区二区三区丝袜| 国产精品视频一区二区噜噜|