Redis進階實踐之七Redis和Lua初步整合使用

1、引言

        Redis學了一段時間了,基本的東西都沒問題了。從今天開始講寫一些redis和lua腳本的相關的東西,lua這個腳本是一個好東西,能夠運行在任何平臺上,也能夠嵌入到大多數語言當中,來擴展其功能。lua腳本是用C語言寫的,體積很小,運行速度很快,而且每次的執行都是做爲一個原子事務來執行的,咱們能夠在其中作不少的事情。因爲篇幅不少,一次沒法概述所有,這個系列可能要經過多篇文章的形式來寫,好了,今天咱們進入正題吧。

2、Lua簡介
    
        Lua 是一個小巧的腳本語言。是巴西里約熱內盧天主教大學(Pontifical Catholic University of Rio de Janeiro)裏的一個研究小組,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所組成並於1993年開發。 其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。Lua由標準C編寫而成,幾乎在全部操做系統和平臺上均可以編譯,運行。Lua並無提供強大的庫,這是由它的定位決定的。因此Lua不適合做爲開發獨立應用程序的語言。Lua 有一個同時進行的JIT項目,提供在特定平臺上的即時編譯功能。

       Lua腳本能夠很容易的被C/C++ 代碼調用,也能夠反過來調用C/C++的函數,這使得Lua在應用程序中能夠被普遍應用。不只僅做爲擴展腳本,也能夠做爲普通的配置文件,代替XML,ini等文件格式,而且更容易理解和維護。 Lua由標準C編寫而成,代碼簡潔優美,幾乎在全部操做系統和平臺上均可以編譯,運行。一個完整的Lua解釋器不過200k,在目前全部腳本引擎中,Lua的速度是最快的。這一切都決定了Lua是做爲嵌入式腳本的最佳選擇。

3、使用Lua腳本的好處

       一、減小網絡開銷:能夠將多個請求經過腳本的形式一次發送,減小網絡時延和請求次數。

       二、原子性的操做:Redis會將整個腳本做爲一個總體執行,中間不會被其餘命令插入。所以在編寫腳本的過程當中無需擔憂會出現競態條件,無需使用事務。

       三、代碼複用:客戶端發送的腳步會永久存在redis中,這樣,其餘客戶端能夠複用這一腳原本完成相同的邏輯。

       四、速度快:見 與其它語言的性能比較, 還有一個 JIT編譯器能夠顯著地提升多數任務的性能; 對於那些仍然對性能不滿意的人, 能夠把關鍵部分使用C實現, 而後與其集成, 這樣還能夠享受其它方面的好處。

       五、能夠移植:只要是有ANSI C 編譯器的平臺均可以編譯,你能夠看到它能夠在幾乎全部的平臺上運行:從 Windows 到Linux,一樣Mac平臺也沒問題, 再到移動平臺、遊戲主機,甚至瀏覽器也能夠完美使用 (翻譯成JavaScript).

       六、源碼小巧:20000行C代碼,能夠編譯進182K的可執行文件,加載快,運行快。

4、redis和lua整合詳解

      一、調用Lua腳本的語法:
              $ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...

              --eval,告訴redis-cli讀取並運行後面的lua腳本
               path/to/redis.lua,是lua腳本的位置
               KEYS[1] KEYS[2],是要操做的鍵,能夠指定多個,在lua腳本中經過KEYS[1], KEYS[2]獲取
               ARGV[1] ARGV[2],參數,在lua腳本中經過ARGV[1], ARGV[2]獲取。

              注意: KEYS和ARGV中間的 ',' 兩邊的空格,不能省略。html

             redis支持大部分Lua標準庫linux

庫名 說明
Base 提供一些基礎函數
String 提供用於字符串操做的函數
Table 提供用於表操做的函數
Math 提供數學計算函數
Debug 提供用於調試的函數


       二、在腳本中調用redis命令
               在腳本中可使用redis.call函數調用Redis命令

              redis.call('set', 'foo', 'bar')
              local value=redis.call('get', 'foo') --value的值爲bar

              redis.call函數的返回值就是Redis命令的執行結果

              Redis命令的返回值有5種類型,redis.call函數會將這5種類型的回覆轉換成對應的Lua的數據類型,具體的對應規則以下(空結果比較特殊,其對應Lua的false)redis

               redis返回值類型和Lua數據類型轉換規則數據庫

redis返回值類型 Lua數據類型
整數回覆 數字類型
字符串回覆 字符串類型
多行字符串回覆 table類型(數組形式)
狀態回覆 table類型(只有一個ok字段存儲狀態信息)
錯誤回覆 table類型(只有一個err字段存儲錯誤信息)


                  redis還提供了redis.pcall函數,功能與redis.call相同,惟一的區別是當命令執行出錯時,redis.pcall會記錄錯誤並繼續執行,而redis.call會直接返回錯誤,不會繼續執行。在腳本中可使用return語句將值返回給客戶端,若是沒有執行return語句則默認返回nil編程

                  Lua數據類型和redis返回值類型轉換規則數組

Lua數據類型 redis返回值類型
數字類型 整數回覆(Lua的數字類型會被自動轉換成整數)
字符串類型 字符串回覆
table類型(數組形式) 多行字符串回覆
table類型(只有一個ok字段存儲狀態信息) 狀態回覆
table類型(只有一個err字段存儲錯誤信息) 錯誤回覆


        三、腳本相關命令
             EVAL語法: eval script numkeys key [key ...] arg [arg ...]

              經過key和arg這兩類參數向腳本傳遞數據,它們的值在腳本中分別使用KEYS和ARGV兩個表類型的全局變量訪問。瀏覽器

              script: 是lua腳本緩存

              numkeys:表示有幾個key,分別是KEYS[1],KEYS[2]...,若是有值,從第numkeys+1個開始就是參數值,ARGV[1],ARGV[2]...

             注意: EVAL命令依據參數numkeys來將其後面的全部參數分別存入腳本中KEYS和ARGV兩個table類型的全局變量。當腳本不須要任何參數時,也不能省略這個參數(設爲0)服務器

       192.168.127.128:6379>eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name liulei
       OK

       192.168.127.128:6379>get name
       "liulei"


       四、 EVALSHA命令
              在腳本比較長的狀況下,若是每次調用腳本都須要將整個腳本傳給Redis會佔用較多的帶寬。爲了解決這個問題,Redis提供了EVALSHA命令,容許開發者經過腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL同樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。

             Redis在執行EVAL命令時會計算腳本的SHA1摘要並記錄在腳本緩存中,執行EVALSHA命令時Redis會根據提供的摘要從腳本緩存中查找對應的腳本內容,若是找到了則執行腳本,不然會返回錯誤:"NOSCRIPT No matching script. Please use EVAL."

             在程序中使用EVALSHA命令的通常流程以下。

                 1)、先計算腳本的SHA1摘要,並使用EVALSHA命令執行腳本。

                 2)、得到返回值,若是返回「NOSCRIPT」錯誤則使用EVAL命令從新執行腳本。

              雖然這一流程略顯麻煩,但值得慶幸的是不少編程語言的Redis客戶端都會代替開發者完成這一流程。執行EVAL命令時,先嚐試執行EVALSHA命令,若是失敗了纔會執行EVAL命令。

               SCRIPTLOAD "lua-script"  將腳本加入緩存,但不執行, 返回:腳本的SHA1摘要

               SCRIPT EXISTS lua-script-sha1   判斷腳本是否已被緩存

       五、 SCRIPT FLUSH(該命令不區分大小寫)
               清空腳本緩存,redis將腳本的SHA1摘要加入到腳本緩存後會永久保留,不會刪除,但能夠手動使用SCRIPT FLUSH命令狀況腳本緩存。網絡

       192.168.127.128:6379>script flush
       OK

       192.168.127.128:6379>SCRIPT FLUSH
       OK


       六、SCRIPT KILL(該命令不區分大小寫)
              強制終止當前腳本的執行。 可是,若是當前執行的腳步對redis的數據進行了寫操做,則SCRIPT KILL命令不會終止腳本的運行,以防止腳本只執行了一部分。腳本中的全部命令,要麼都執行,要麼都不執行。

       192.168.127.128:6379>script kill
      (error)NOTBUSY No scripts in execution right now

      192.168.127.128:6379>SCRIPT KILL
      (error)NOTBUSY No scripts in execution right now
       //這是當前沒有腳本在執行,因此提示該錯誤


       七、lua-time-limit 5000(redis.conf配置文件中)

              爲了防止某個腳本執行時間過長致使Redis沒法提供服務(好比陷入死循環),Redis提供了lua-time-limit參數限制腳本的最長運行時間,默認爲5秒鐘。當腳本運行時間超過這一限制後,Redis將開始接受其餘命令但不會執行(以確保腳本的原子性,由於此時腳本並無被終止),而是會返回「BUSY」錯誤。

5、安裝和使用Lua腳本

       一、安裝lua類庫環境

              1.一、yum install -y readline
                    
              1.二、yum install -y readline-devel

                    

        二、下載lua最新的版本安裝

                2.一、去官網下載lua,能夠直接經過wget下載,地址以下:http://www.lua.org/download.html

           [root@lunux~]# wget http://www.lua.org/ftp/lua-5.3.4.tar.gz /root/software/download/lua/

                        



               2.二、經過ssh SSH Secure File Transfer Client工具,把軟件包上傳到Linux服務器上。目錄是:/root/software/download/lua/

           [root@linux~]# cd ./software/download/lua/

           [root@linux lua]# tar zxvf lua-5.3.4.tar.gz

                         

                       
                2.三、進入到已經解壓的目錄lua-5.3.4,準備安裝文件。

            [root@linux lua]# ls

            [root@linux lua]# lua-5.3.4 lua-5.3.4.tar.gz

            [root@linux lua]# cd lua-5.3.4

            [root@linux lua-5.3.4]#


             2.四、準備安裝環境,使用make linux命令,當前也是須要gcc命令的支持,事先必須安裝,安裝gcc命令:yum install gcc。

            [root@linux lua-5.3.4]# make linux


                       

            2.五、開始安裝lua軟件包,使用make install命令

            [root@linux lua-5.3.4]# make install

                       


           2. 六、最後進行測試,進到Linux的命令行,而後輸入lua命令,開始測試。

          [root@linux lua-5.3.4]# lua

          >print('lua')
          lua
>print("lua") lua

                   


            2.七、按Ctrl+C退出lua命令模式。

           >^C
          [root@linux lua-5.3.4]# 


           2.八、lua腳本文件名必須以.lua後綴名,若是在Linux命令行執行lua腳本,直接lua 腳本名稱。

         [root@linux lua-5.3.4]# cd /root/application/program/   //執行文件都在這個目錄裏面

         [root@linux program]# mkdir luascript  //建立luaScript腳本目錄,存放lua腳本文件

         [root@linux program]# cd luascript  

         [root@linux luascript]# lua 01.lua    //執行01.lua腳本文件


           2.九、redis與lua腳本結合使用,若是在lua腳本里使用了 redis.call命令來操做Redis,執行lua腳步以下面:

          //redis-cli和lua腳本的路徑能夠是相對路徑,也能夠是絕對路徑
          //如下代碼就是經過絕對地址來執行

          //絕對地址:
          [root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua

          //相對地址:
          //當前目錄
          192.168.127.128:6379>pwd
          [root@linux redis-tool]/root/application/program/redis-tool/

          [root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua


            2.十、Redis客戶端執行帶有參數的lua腳本,腳本文件的名稱是:03.lua。

           //當前redis 數據庫中只有name和age兩個key,其餘數據已經清空。

           //當前所在目錄
           192.168.127.128:6379>keys *
           1)"name"
           2)"age"

           192.168.127.128:6379>get name
           "liulei"

           192.168.127.128:6379>get age
           "15"
     

          //03.lua腳本代碼以下:

           local name=redis.call("get",KEYS[1])

           local age=redis.call("get",KEYS[2])

           if name=="LLL" then

             redis.call("set",KEYS[1],ARGV[1])
 
             redis.call("incr",KEYS[2])
            end

          //執行改腳本的命令,必須在Linux的命令行,不是在Redis的命令行
          [root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/03.lua name age , patrickLiu

          //執行腳本命令後
          192.168.127.128:6379>keys *
          1)"name"
          2)"age"

          192.168.127.128:6379>get name
          "patrickLiu"

          192.168.127.128:6379>get age
          "16"

         //說明帶參數的執行Lua腳本成功


             2.十一、Redis客戶端執行有參數lua,並返回lua的表類型。

            //04.lua文件的源碼

            local b1=redis.call("hgetall",KEYS[1])
            return b1

            //代碼很簡單,話很少說

            //清空當前數據庫
            192.168.127.128:6379>flushdb

            192.168.127.128:6379>keys *
            (empty list or set)

            192.168.127.128:6379>hmset myhash name zhangsan sex nan address hebeibaoding school laiyuanyizhong
            OK

            192.168.127.128:6379>hmget myhash name sex address school
            1)"zhangsan"
            2)"nan"
            3)"hebeibaoding"
            4)"laiyuanyizhong"

            //咱們經過redis客戶端獲取myhash的結果,進入到redis客戶端的當前目錄

            [root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval ../luascript/04.lua myhash
            1)"name"
            2)"zhangsan"
            3)"sex"
            4)"nan"
            5)"address"
            6)"hebeibaoding"
            7)"school"
            8)"laiyuanyizhong"

          //成功獲取myhash的列表


6、總結   今天就寫到這裏,還有不少內容要寫,一次也寫不完,慢慢來吧,我也須要消化一下。若是有時間,下一篇文章或者下某一篇文章準備來單獨寫一些關於Lua腳本語法的東西,我也是第一次接觸這個東西,你們一塊兒學習吧。

相關文章
相關標籤/搜索