99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Redis 創(chuàng)建并修改 Lua 環(huán)境

2018-08-02 14:57 更新

為了在 Redis 服務(wù)器中執(zhí)行 Lua 腳本, Redis 在服務(wù)器內(nèi)嵌了一個(gè) Lua 環(huán)境(environment), 并對(duì)這個(gè) Lua 環(huán)境進(jìn)行了一系列修改, 從而確保這個(gè) Lua 環(huán)境可以滿足 Redis 服務(wù)器的需要。

Redis 服務(wù)器創(chuàng)建并修改 Lua 環(huán)境的整個(gè)過(guò)程由以下步驟組成:

  1. 創(chuàng)建一個(gè)基礎(chǔ)的 Lua 環(huán)境, 之后的所有修改都是針對(duì)這個(gè)環(huán)境進(jìn)行的。
  2. 載入多個(gè)函數(shù)庫(kù)到 Lua 環(huán)境里面, 讓 Lua 腳本可以使用這些函數(shù)庫(kù)來(lái)進(jìn)行數(shù)據(jù)操作。
  3. 創(chuàng)建全局表格 redis , 這個(gè)表格包含了對(duì) Redis 進(jìn)行操作的函數(shù), 比如用于在 Lua 腳本中執(zhí)行 Redis 命令的 redis.call 函數(shù)。
  4. 使用 Redis 自制的隨機(jī)函數(shù)來(lái)替換 Lua 原有的帶有副作用的隨機(jī)函數(shù), 從而避免在腳本中引入副作用。
  5. 創(chuàng)建排序輔助函數(shù), Lua 環(huán)境使用這個(gè)輔佐函數(shù)來(lái)對(duì)一部分 Redis 命令的結(jié)果進(jìn)行排序, 從而消除這些命令的不確定性。
  6. 創(chuàng)建 redis.pcall 函數(shù)的錯(cuò)誤報(bào)告輔助函數(shù), 這個(gè)函數(shù)可以提供更詳細(xì)的出錯(cuò)信息。
  7. 對(duì) Lua 環(huán)境里面的全局環(huán)境進(jìn)行保護(hù), 防止用戶在執(zhí)行 Lua 腳本的過(guò)程中, 將額外的全局變量添加到了 Lua 環(huán)境里面。
  8. 將完成修改的 Lua 環(huán)境保存到服務(wù)器狀態(tài)的 lua 屬性里面, 等待執(zhí)行服務(wù)器傳來(lái)的 Lua 腳本。

接下來(lái)的各個(gè)小節(jié)將分別介紹這些步驟。

創(chuàng)建 Lua 環(huán)境

在最開始的這一步, 服務(wù)器首先調(diào)用 Lua 的 C API 函數(shù) lua_open , 創(chuàng)建一個(gè)新的 Lua 環(huán)境。

因?yàn)?lua_open 函數(shù)創(chuàng)建的只是一個(gè)基本的 Lua 環(huán)境, 為了讓這個(gè) Lua 環(huán)境可以滿足 Redis 的操作要求, 接下來(lái)服務(wù)器將對(duì)這個(gè) Lua 環(huán)境進(jìn)行一系列修改。

載入函數(shù)庫(kù)

Redis 修改 Lua 環(huán)境的第一步, 就是將以下函數(shù)庫(kù)載入到 Lua 環(huán)境里面:

  • 基礎(chǔ)庫(kù)(base library): 這個(gè)庫(kù)包含 Lua 的核心(core)函數(shù), 比如 assert 、 error 、 pairs 、 tostring 、 pcall , 等等。 另外, 為了防止用戶從外部文件中引入不安全的代碼, 庫(kù)中的 loadfile 函數(shù)會(huì)被刪除。
  • 表格庫(kù)(table library): 這個(gè)庫(kù)包含用于處理表格的通用函數(shù), 比如 table.concat 、 table.insert 、 table.remove 、 table.sort, 等等。
  • 字符串庫(kù)(string library): 這個(gè)庫(kù)包含用于處理字符串的通用函數(shù), 比如用于對(duì)字符串進(jìn)行查找的 string.find 函數(shù), 對(duì)字符串進(jìn)行格式化的 string.format 函數(shù), 查看字符串長(zhǎng)度的 string.len 函數(shù), 對(duì)字符串進(jìn)行翻轉(zhuǎn)的 string.reverse 函數(shù), 等等。
  • 數(shù)學(xué)庫(kù)(math library): 這個(gè)庫(kù)是標(biāo)準(zhǔn) C 語(yǔ)言數(shù)學(xué)庫(kù)的接口, 它包括計(jì)算絕對(duì)值的 math.abs 函數(shù), 返回多個(gè)數(shù)中的最大值和最小值的 math.max 函數(shù)和 math.min 函數(shù), 計(jì)算二次方根的 math.sqrt 函數(shù), 計(jì)算對(duì)數(shù)的 math.log 函數(shù), 等等。
  • 調(diào)試庫(kù)(debug library): 這個(gè)庫(kù)提供了對(duì)程序進(jìn)行調(diào)試所需的函數(shù), 比如對(duì)程序設(shè)置鉤子和取得鉤子的 debug.sethook 函數(shù)和debug.gethook 函數(shù), 返回給定函數(shù)相關(guān)信息的 debug.getinfo 函數(shù), 為對(duì)象設(shè)置元數(shù)據(jù)的 debug.setmetatable 函數(shù), 獲取對(duì)象元數(shù)據(jù)的debug.getmetatable 函數(shù), 等等。
  • Lua CJSON 庫(kù)(http://www.kyne.com.au/~mark/software/lua-cjson.php): 這個(gè)庫(kù)用于處理 UTF-8 編碼的 JSON 格式, 其中cjson.decode 函數(shù)將一個(gè) JSON 格式的字符串轉(zhuǎn)換為一個(gè) Lua 值, 而 cjson.encode 函數(shù)將一個(gè) Lua 值序列化為 JSON 格式的字符串。
  • Struct 庫(kù)(http://www.inf.puc-rio.br/~roberto/struct/): 這個(gè)庫(kù)用于在 Lua 值和 C 結(jié)構(gòu)(struct)之間進(jìn)行轉(zhuǎn)換, 函數(shù)struct.pack 將多個(gè) Lua 值打包成一個(gè)類結(jié)構(gòu)(struct-like)字符串, 而函數(shù) struct.unpack 則從一個(gè)類結(jié)構(gòu)字符串中解包出多個(gè) Lua 值。
  • Lua cmsgpack 庫(kù)(https://github.com/antirez/lua-cmsgpack): 這個(gè)庫(kù)用于處理 MessagePack 格式的數(shù)據(jù), 其中 cmsgpack.pack 函數(shù)將 Lua 值轉(zhuǎn)換為 MessagePack 數(shù)據(jù), 而 cmsgpack.unpack 函數(shù)則將 MessagePack 數(shù)據(jù)轉(zhuǎn)換為 Lua 值。

通過(guò)使用這些功能強(qiáng)大的函數(shù)庫(kù), Lua 腳本可以直接對(duì)執(zhí)行 Redis 命令獲得的數(shù)據(jù)進(jìn)行復(fù)雜的操作。

創(chuàng)建 redis 全局表格

在這一步, 服務(wù)器將在 Lua 環(huán)境中創(chuàng)建一個(gè) redis 表格(table), 并將它設(shè)為全局變量。

這個(gè) redis 表格包含以下函數(shù):

  • 用于執(zhí)行 Redis 命令的 redis.call 和 redis.pcall 函數(shù)。
  • 用于記錄 Redis 日志(log)的 redis.log 函數(shù), 以及相應(yīng)的日志級(jí)別(level)常量: redis.LOG_DEBUG , redis.LOG_VERBOSE ,redis.LOG_NOTICE , 以及 redis.LOG_WARNING 。
  • 用于計(jì)算 SHA1 校驗(yàn)和的 redis.sha1hex 函數(shù)。
  • 用于返回錯(cuò)誤信息的 redis.error_reply 函數(shù)和 redis.status_reply 函數(shù)。

在這些函數(shù)里面, 最常用也最重要的要數(shù) redis.call 函數(shù)和 redis.pcall 函數(shù) —— 通過(guò)這兩個(gè)函數(shù), 用戶可以直接在 Lua 腳本中執(zhí)行 Redis 命令:

redis> EVAL "return redis.call('PING')" 0
PONG

使用 Redis 自制的隨機(jī)函數(shù)來(lái)替換 Lua 原有的隨機(jī)函數(shù)

為了保證相同的腳本可以在不同的機(jī)器上產(chǎn)生相同的結(jié)果, Redis 要求所有傳入服務(wù)器的 Lua 腳本, 以及 Lua 環(huán)境中的所有函數(shù), 都必須是無(wú)副作用(side effect)的純函數(shù)(pure function)。

但是, 在之前載入到 Lua 環(huán)境的 math 函數(shù)庫(kù)中, 用于生成隨機(jī)數(shù)的 math.random 函數(shù)和 math.randomseed 函數(shù)都是帶有副作用的, 它們不符合 Redis 對(duì) Lua 環(huán)境的無(wú)副作用要求。

因?yàn)檫@個(gè)原因, Redis 使用自制的函數(shù)替換了 math 庫(kù)中原有的 math.random 函數(shù)和 math.randomseed 函數(shù), 替換之后的兩個(gè)函數(shù)有以下特征:

  • 對(duì)于相同的 seed 來(lái)說(shuō), math.random 總產(chǎn)生相同的隨機(jī)數(shù)序列, 這個(gè)函數(shù)是一個(gè)純函數(shù)。
  • 除非在腳本中使用 math.randomseed 顯式地修改 seed , 否則每次運(yùn)行腳本時(shí), Lua 環(huán)境都使用固定的 math.randomseed(0) 語(yǔ)句來(lái)初始化 seed 。

比如說(shuō), 使用以下腳本, 我們可以打印 seed 值為 0 時(shí), math.random 對(duì)于輸入 10 至 1 所產(chǎn)生的隨機(jī)序列:

無(wú)論執(zhí)行這個(gè)腳本多少次, 產(chǎn)生的值都是相同的:

$ redis-cli --eval random-with-default-seed.lua
1) (integer) 1
2) (integer) 2
3) (integer) 2
4) (integer) 3
5) (integer) 4
6) (integer) 4
7) (integer) 7
8) (integer) 1
9) (integer) 7
10) (integer) 2

但是, 如果我們?cè)诹硪粋€(gè)腳本里面, 調(diào)用 math.randomseed 將 seed 修改為 10086 :

那么這個(gè)腳本生成的隨機(jī)數(shù)序列將和使用默認(rèn) seed 值 0 時(shí)生成的隨機(jī)序列不同:

$ redis-cli --eval random-with-new-seed.lua
1) (integer) 1
2) (integer) 1
3) (integer) 2
4) (integer) 1
5) (integer) 1
6) (integer) 3
7) (integer) 1
8) (integer) 1
9) (integer) 3
10) (integer) 1

創(chuàng)建排序輔助函數(shù)

上一個(gè)小節(jié)說(shuō)到, 為了防止帶有副作用的函數(shù)令腳本產(chǎn)生不一致的數(shù)據(jù), Redis 對(duì) math 庫(kù)的 math.random 函數(shù)和 math.randomseed 函數(shù)進(jìn)行了替換。

對(duì)于 Lua 腳本來(lái)說(shuō), 另一個(gè)可能產(chǎn)生不一致數(shù)據(jù)的地方是那些帶有不確定性質(zhì)的命令。

比如對(duì)于一個(gè)集合鍵來(lái)說(shuō), 因?yàn)榧显氐呐帕惺菬o(wú)序的, 所以即使兩個(gè)集合的元素完全相同, 它們的輸出結(jié)果也可能并不相同。

考慮下面這個(gè)集合例子:

redis> SADD fruit apple banana cherry
(integer) 3

redis> SMEMBERS fruit
1) "cherry"
2) "banana"
3) "apple"

redis> SADD another-fruit cherry banana apple
(integer) 3

redis> SMEMBERS another-fruit
1) "apple"
2) "banana"
3) "cherry"

這個(gè)例子中的 fruit 集合和 another-fruit 集合包含的元素是完全相同的, 只是因?yàn)榧咸砑釉氐捻樞虿煌?nbsp;SMEMBERS 命令的輸出就產(chǎn)生了不同的結(jié)果。

Redis 將 SMEMBERS 這種在相同數(shù)據(jù)集上可能會(huì)產(chǎn)生不同輸出的命令稱為“帶有不確定性的命令”, 這些命令包括:

  • SINTER
  • SUNION
  • SDIFF
  • SMEMBERS
  • HKEYS
  • HVALS
  • KEYS

為了消除這些命令帶來(lái)的不確定性, 服務(wù)器會(huì)為 Lua 環(huán)境創(chuàng)建一個(gè)排序輔助函數(shù) __redis__compare_helper , 當(dāng) Lua 腳本執(zhí)行完一個(gè)帶有不確定性的命令之后, 程序會(huì)使用 __redis__compare_helper 作為對(duì)比函數(shù), 自動(dòng)調(diào)用 table.sort 函數(shù)對(duì)命令的返回值做一次排序, 以此來(lái)保證相同的數(shù)據(jù)集總是產(chǎn)生相同的輸出。

舉個(gè)例子, 如果我們?cè)?Lua 腳本中對(duì) fruit 集合和 another-fruit 集合執(zhí)行 SMEMBERS 命令, 那么兩個(gè)腳本將得出相同的結(jié)果 —— 因?yàn)槟_本已經(jīng)對(duì) SMEMBERS 命令的輸出進(jìn)行過(guò)排序了:

redis> EVAL "return redis.call('SMEMBERS', KEYS[1])" 1 fruit
1) "apple"
2) "banana"
3) "cherry"

redis> EVAL "return redis.call('SMEMBERS', KEYS[1])" 1 another-fruit
1) "apple"
2) "banana"
3) "cherry"

創(chuàng)建 redis.pcall 函數(shù)的錯(cuò)誤報(bào)告輔助函數(shù)

在這一步, 服務(wù)器將為 Lua 環(huán)境創(chuàng)建一個(gè)名為 __redis__err__handler 的錯(cuò)誤處理函數(shù), 當(dāng)腳本調(diào)用 redis.pcall 函數(shù)執(zhí)行 Redis 命令, 并且被執(zhí)行的命令出現(xiàn)錯(cuò)誤時(shí), __redis__err__handler 就會(huì)打印出錯(cuò)代碼的來(lái)源和發(fā)生錯(cuò)誤的行數(shù), 為程序的調(diào)試提供方便。

舉個(gè)例子, 如果客戶端要求服務(wù)器執(zhí)行以下 Lua 腳本:

那么服務(wù)器將向客戶端返回一個(gè)錯(cuò)誤:

$ redis-cli --eval wrong-command.lua
(error) @user_script: 4: Unknown Redis command called from Lua script

其中 @user_script 說(shuō)明這是一個(gè)用戶定義的函數(shù), 而之后的 4 則說(shuō)明出錯(cuò)的代碼位于 Lua 腳本的第四行。

保護(hù) Lua 的全局環(huán)境

在這一步, 服務(wù)器將對(duì) Lua 環(huán)境中的全局環(huán)境進(jìn)行保護(hù), 確保傳入服務(wù)器的腳本不會(huì)因?yàn)橥浭褂?nbsp;local 關(guān)鍵字而將額外的全局變量添加到了 Lua 環(huán)境里面。

因?yàn)槿肿兞勘Wo(hù)的原因, 當(dāng)一個(gè)腳本試圖創(chuàng)建一個(gè)全局變量時(shí), 服務(wù)器將報(bào)告一個(gè)錯(cuò)誤:

redis> EVAL "x = 10" 0
(error) ERR Error running script
(call to f_df1ad3745c2d2f078f0f41377a92bb6f8ac79af0):
@enable_strict_lua:7: user_script:1:
Script attempted to create global variable 'x'

除此之外, 試圖獲取一個(gè)不存在的全局變量也會(huì)引發(fā)一個(gè)錯(cuò)誤:

redis> EVAL "return x" 0
(error) ERR Error running script
(call to f_03c387736bb5cc009ff35151572cee04677aa374):
@enable_strict_lua:14: user_script:1:
Script attempted to access unexisting global variable 'x'

不過(guò) Redis 并未禁止用戶修改已存在的全局變量, 所以在執(zhí)行 Lua 腳本的時(shí)候, 必須非常小心, 以免錯(cuò)誤地修改了已存在的全局變量:

redis> EVAL "redis = 10086; return redis" 0
(integer) 10086

將 Lua 環(huán)境保存到服務(wù)器狀態(tài)的 lua 屬性里面

經(jīng)過(guò)以上的一系列修改, Redis 服務(wù)器對(duì) Lua 環(huán)境的修改工作到此就結(jié)束了, 在最后的這一步, 服務(wù)器會(huì)將 Lua 環(huán)境和服務(wù)器狀態(tài)的 lua屬性關(guān)聯(lián)起來(lái), 如圖 IMAGE_REDIS_SERVER_LUA 所示。

因?yàn)?Redis 使用串行化的方式來(lái)執(zhí)行 Redis 命令, 所以在任何特定時(shí)間里, 最多都只會(huì)有一個(gè)腳本能夠被放進(jìn) Lua 環(huán)境里面運(yùn)行, 因此, 整個(gè) Redis 服務(wù)器只需要?jiǎng)?chuàng)建一個(gè) Lua 環(huán)境即可。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)