<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一網(wǎng)打盡Redis Lua腳本并發(fā)原子組合操作

          共 1676字,需瀏覽 4分鐘

           ·

          2020-11-08 00:52

          1. 前言

          Redis 提供了豐富的命令來(lái)供我們使用以實(shí)現(xiàn)一些計(jì)算。Redis 的單個(gè)命令都是原子性的,有時(shí)候我們希望能夠組合多個(gè) Redis 命令,并讓這個(gè)組合也能夠原子性的執(zhí)行,甚至可以重復(fù)使用。Redis 開發(fā)者意識(shí)到這種場(chǎng)景還是很普遍的,就在 2.6 版本中引入了一個(gè)特性來(lái)解決這個(gè)問題,這就是 Redis 執(zhí)行 Lua 腳本。

          2. Lua

          Lua 也算一門古老的語(yǔ)言了,玩魔獸世界的玩家應(yīng)該對(duì)它不陌生,WOW 的插件就是用 Lua 腳本編寫的。在高并發(fā)的網(wǎng)絡(luò)游戲中 Lua 大放異彩被廣泛使用。

          Lua 廣泛作為其它語(yǔ)言的嵌入腳本,尤其是 C/C++,語(yǔ)法簡(jiǎn)單,小巧,源碼一共才 200 多 K,這可能也是 Redis 官方選擇它的原因。

          另一款明星軟件 Nginx 也支持 Lua,利用 Lua 也可以實(shí)現(xiàn)很多有用的功能。

          3. Lua 并不難

          Redis 官方指南也指出不要在 Lua 腳本中編寫過(guò)于復(fù)雜的邏輯。

          為了實(shí)現(xiàn)一個(gè)功能就要學(xué)習(xí)一門語(yǔ)言,這看起來(lái)就讓人有打退堂鼓的感覺。其實(shí) Lua 并不難學(xué),而且作為本文的場(chǎng)景來(lái)說(shuō)我們不需要去學(xué)習(xí) Lua 的完全特性,要在 Redis 中輕量級(jí)使用 Lua 語(yǔ)言。這對(duì)掌握了 Java 這種重量級(jí)語(yǔ)言的你來(lái)說(shuō)根本不算難事。這里胖哥只對(duì) Redis 中的涉及到的基本語(yǔ)法說(shuō)一說(shuō)。

          Lua 的簡(jiǎn)單語(yǔ)法

          Lua 在 Redis 腳本中我個(gè)人建議只需要使用下面這幾種類型:

          1. nil
          2. boolean 布爾值
          3. number 數(shù)字
          4. string 字符串
          5. table

          聲明類型

          聲明類型非常簡(jiǎn)單,不用攜帶類型。

          ---?全局變量
          name?=?'felord.cn'
          ---?局部變量
          local?age?=?18

          Redis 腳本在實(shí)踐中不要使用全局變量,局部變量效率更高。

          table 類型

          前面四種非常好理解,第五種table需要簡(jiǎn)單說(shuō)一下,它既是數(shù)組又類似 Java 中的HashMap(字典),它是 Lua 中僅有的數(shù)據(jù)結(jié)構(gòu)。

          數(shù)組不分具體類型,演示如下

          Lua?5.1.5??Copyright?(C)?1994-2012?Lua.org,?PUC-Rio
          >?arr_table?=?{'felord.cn','Felordcn',1}
          >?print(arr_table[1])
          felord.cn
          >?print(arr_table[3])
          1
          >?print(#arr_table)
          3

          作為字典:

          Lua?5.1.5??Copyright?(C)?1994-2012?Lua.org,?PUC-Rio
          >?arr_table?=?{name?=?'felord.cn',?age?=?18}
          >?print(arr_table['name'])
          felord.cn
          >?print(arr_table.name)
          felord.cn
          >?print(arr_table[1])
          nil
          >?print(arr_table['age'])
          18
          >?print(#arr_table)
          0

          混合模式:

          Lua?5.1.5??Copyright?(C)?1994-2012?Lua.org,?PUC-Rio
          >?arr_table?=?{'felord.cn','Felordcn',1,age?=?18,nil}
          >?print(arr_table[1])
          felord.cn
          >?print(arr_table[4])
          nil
          >?print(arr_table['age'])
          18
          >?print(#arr_table)
          3

          ? # 取 table 的長(zhǎng)度不一定精準(zhǔn),慎用。同時(shí)在 Redis 腳本中避免使用混合模式的 table,同時(shí)元素應(yīng)該避免包含空值nil。在不確定元素的情況下應(yīng)該使用循環(huán)來(lái)計(jì)算真實(shí)的長(zhǎng)度。

          判斷

          判斷非常簡(jiǎn)單,格式為:

          local?a?=?10
          if?a?10??then
          ?print('a小于10')
          elseif?a?20?then
          ?print('a小于20,大于等于10')
          else
          ?print('a大于等于20')
          end

          數(shù)組循環(huán)

          local?arr?=?{1,2,name='felord.cn'}

          for?i,?v?in?ipairs(arr)?do
          ????print('i?=?'..i)
          ????print('v?=?'..?v)
          end

          print('-------------------')

          for?i,?v?in?pairs(arr)?do
          ????print('p?i?=?'..i)
          ????print('p?v?=?'..?v)
          end

          打印結(jié)果:

          i?=?1
          v?=?1
          i?=?2
          v?=?2
          -----------------------
          p?i?=?1
          p?v?=?1
          p?i?=?2
          p?v?=?2
          p?i?=?name
          p?v?=?felord.cn

          返回值

          像 Python 一樣,Lua 也可以返回多個(gè)返回值。不過(guò)在 Redis 的 Lua 腳本中不建議使用此特性,如果有此需求請(qǐng)封裝為數(shù)組結(jié)構(gòu)。在 Spring Data Redis 中支持腳本的返回值規(guī)則可以從這里分析:

          public?static?ReturnType?fromJavaType(@Nullable?Class?javaType)?{

          ???if?(javaType?==?null)?{
          ??????return?ReturnType.STATUS;
          ???}
          ???if?(javaType.isAssignableFrom(List.class))?{
          ??????return?ReturnType.MULTI;
          ???}
          ???if?(javaType.isAssignableFrom(Boolean.class))?{
          ??????return?ReturnType.BOOLEAN;
          ???}
          ???if?(javaType.isAssignableFrom(Long.class))?{
          ??????return?ReturnType.INTEGER;
          ???}
          ???return?ReturnType.VALUE;
          }

          胖哥在實(shí)踐中會(huì)使用 List、Boolean、Long三種,避免出現(xiàn)幺蛾子。

          到此為止 Redis Lua 腳本所需要知識(shí)點(diǎn)就完了,其它的函數(shù)、協(xié)程等特性也不應(yīng)該在 Redis Lua 腳本中出現(xiàn),用到內(nèi)置函數(shù)的話搜索查詢一下就行了。

          在接觸一門新的技術(shù)時(shí)先要中規(guī)中矩的使用,如果你想玩花活就意味著更高的學(xué)習(xí)成本。

          4. Redis 中的 Lua

          接下來(lái)就是 Redis Lua 腳本的實(shí)際操作了。

          EVAL 命令

          Redis 中使用EVAL命令來(lái)直接執(zhí)行指定的 Lua 腳本。

          EVAL?luascript?numkeys?key?[key?...]?arg?[arg?...]
          • EVAL 命令的關(guān)鍵字。
          • luascript Lua 腳本。
          • numkeys 指定的 Lua 腳本需要處理鍵的數(shù)量,其實(shí)就是 key數(shù)組的長(zhǎng)度。
          • key 傳遞給 Lua 腳本零到多個(gè)鍵,空格隔開,在 Lua 腳本中通過(guò) KEYS[INDEX]來(lái)獲取對(duì)應(yīng)的值,其中1 <= INDEX <= numkeys。
          • arg是傳遞給腳本的零到多個(gè)附加參數(shù),空格隔開,在 Lua 腳本中通過(guò)ARGV[INDEX]來(lái)獲取對(duì)應(yīng)的值,其中1 <= INDEX <= numkeys。

          接下來(lái)我簡(jiǎn)單來(lái)演示獲取鍵hello的值得簡(jiǎn)單腳本:

          127.0.0.1:6379>?set?hello?world
          OK
          127.0.0.1:6379>?get?hello
          "world"
          127.0.0.1:6379>?EVAL?"return?redis.call('GET',KEYS[1])"?1?hello
          "world"
          127.0.0.1:6379>?EVAL?"return?redis.call('GET','hello')"
          (error)?ERR?wrong?number?of?arguments?for?'eval'?command
          127.0.0.1:6379>?EVAL?"return?redis.call('GET','hello')"?0
          "world"

          從上面的演示代碼中發(fā)現(xiàn),KEYS[1]可以直接替換為hello,但是 Redis 官方文檔指出這種是不建議的,目的是在命令執(zhí)行前會(huì)對(duì)命令進(jìn)行分析,以確保 Redis Cluster 可以將命令轉(zhuǎn)發(fā)到適當(dāng)?shù)募汗?jié)點(diǎn)。

          numkeys無(wú)論什么情況下都是必須的命令參數(shù)。

          call 函數(shù)和 pcall 函數(shù)

          在上面的例子中我們通過(guò)redis.call()來(lái)執(zhí)行了一個(gè)SET命令,其實(shí)我們也可以替換為redis.pcall()。它們唯一的區(qū)別就在于處理錯(cuò)誤的方式,前者執(zhí)行命令錯(cuò)誤時(shí)會(huì)向調(diào)用者直接返回一個(gè)錯(cuò)誤;而后者則會(huì)將錯(cuò)誤包裝為一個(gè)我們上面講的table表格:

          127.0.0.1:6379>?EVAL?"return?redis.call('no_command')"?0
          (error)?ERR?Error?running?script?(call?to?f_1e6efd00ab50dd564a9f13e5775e27b966c2141e):?@user_script:1:?@user_script:?1:?Unknown?Redis?command?called?from?Lua?script
          127.0.0.1:6379>?EVAL?"return?redis.pcall('no_command')"?0
          (error)?@user_script:?1:?Unknown?Redis?command?called?from?Lua?script

          這就像 Java 遇到一個(gè)異常,前者會(huì)直接拋出一個(gè)異常;后者會(huì)把異常處理成 JSON 返回。

          值轉(zhuǎn)換

          由于在 Redis 中存在 Redis 和 Lua 兩種不同的運(yùn)行環(huán)境,在 Redis 和 Lua 互相傳遞數(shù)據(jù)時(shí)必然發(fā)生對(duì)應(yīng)的轉(zhuǎn)換操作,這種轉(zhuǎn)換操作是我們?cè)趯?shí)踐中不能忽略的。例如如果 Lua 腳本向 Redis 返回小數(shù),那么會(huì)損失小數(shù)精度;如果轉(zhuǎn)換為字符串則是安全的。

          127.0.0.1:6379>?EVAL?"return?3.14"?0
          (integer)?3
          127.0.0.1:6379>?EVAL?"return?tostring(3.14)"?0
          "3.14"

          根據(jù)胖哥經(jīng)驗(yàn)傳遞字符串、整數(shù)是安全的,其它需要你去仔細(xì)查看官方文檔并進(jìn)行實(shí)際驗(yàn)證

          原子執(zhí)行

          Lua 腳本在 Redis 中是以原子方式執(zhí)行的,在 Redis 服務(wù)器執(zhí)行EVAL命令時(shí),在命令執(zhí)行完畢并向調(diào)用者返回結(jié)果之前,只會(huì)執(zhí)行當(dāng)前命令指定的 Lua 腳本包含的所有邏輯,其它客戶端發(fā)送的命令將被阻塞,直到EVAL命令執(zhí)行完畢為止。因此 LUA 腳本不宜編寫一些過(guò)于復(fù)雜了邏輯,必須盡量保證 Lua 腳本的效率,否則會(huì)影響其它客戶端。

          腳本管理

          SCRIPT LOAD

          加載腳本到緩存以達(dá)到重復(fù)使用,避免多次加載浪費(fèi)帶寬,每一個(gè)腳本都會(huì)通過(guò) SHA 校驗(yàn)返回唯一字符串標(biāo)識(shí)。需要配合EVALSHA命令來(lái)執(zhí)行緩存后的腳本。

          127.0.0.1:6379>?SCRIPT?LOAD?"return?'hello'"
          "1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
          127.0.0.1:6379>?EVALSHA?1b936e3fe509bcbc9cd0664897bbe8fd0cac101b?0
          "hello"

          SCRIPT FLUSH

          既然有緩存就有清除緩存,但是遺憾的是并沒有根據(jù) SHA 來(lái)刪除腳本緩存,而是清除所有的腳本緩存,所以在生產(chǎn)中一般不會(huì)再生產(chǎn)過(guò)程中使用該命令。

          SCRIPT EXISTS

          以 SHA 標(biāo)識(shí)為參數(shù)檢查一個(gè)或者多個(gè)緩存是否存在。

          127.0.0.1:6379>?SCRIPT?EXISTS?1b936e3fe509bcbc9cd0664897bbe8fd0cac101b??1b936e3fe509bcbc9cd0664897bbe8fd0cac1012
          1)?(integer)?1
          2)?(integer)?0

          SCRIPT KILL

          終止正在執(zhí)行的腳本。但是為了數(shù)據(jù)的完整性此命令并不能保證一定能終止成功。如果當(dāng)一個(gè)腳本執(zhí)行了一部分寫的邏輯而需要被終止時(shí),該命令是不湊效的。需要執(zhí)行SHUTDOWN nosave在不對(duì)數(shù)據(jù)執(zhí)行持久化的情況下終止服務(wù)器來(lái)完成終止腳本。

          其它一些要點(diǎn)

          了解了上面這些知識(shí)基本上可以滿足開發(fā)一些簡(jiǎn)單的 Lua 腳本了。但是實(shí)際開發(fā)中還是有一些要點(diǎn)的。

          • 務(wù)必對(duì) Lua 腳本進(jìn)行全面測(cè)試以保證其邏輯的健壯性,當(dāng) Lua 腳本遇到異常時(shí),已經(jīng)執(zhí)行過(guò)的邏輯是不會(huì)回滾的。
          • 盡量不使用 Lua 提供的具有隨機(jī)性的函數(shù),參見相關(guān)官方文檔。
          • 在 Lua 腳本中不要編寫function函數(shù),整個(gè)腳本作為一個(gè)函數(shù)的函數(shù)體。
          • 在腳本編寫中聲明的變量全部使用local關(guān)鍵字。
          • 在集群中使用 Lua 腳本要確保邏輯中所有的key分到相同機(jī)器,也就是同一個(gè)插槽(slot)中,可采用Redis Hash Tag技術(shù)。
          • 再次重申 Lua 腳本一定不要包含過(guò)于耗時(shí)、過(guò)于復(fù)雜的邏輯。

          5. 總結(jié)

          本文對(duì) Redis Lua 腳本的場(chǎng)景以及編寫 Redis Lua 腳本所需要的 Lua 編程語(yǔ)法進(jìn)行了詳細(xì)的講解和演示,也對(duì) Redis Lua 腳本在實(shí)際開發(fā)中需要注意的一些要點(diǎn)進(jìn)行了分享。希望能夠幫助你掌握此技術(shù)

          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了
          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  插插视频网站 | 色噜噜日韩精品欧美一区二区 | 任你躁精品视频一区二区三区 | 大鸡巴视频网站 | 黑人大屌操B |