国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Redis與Lua及Redis-py應用Lua

YancyYe / 3360人閱讀

摘要:命令使用解釋器執行腳本。命令根據給定的校驗碼,執行緩存在服務器中的腳本。命令用于校驗指定的腳本是否已經被保存在緩存當中。殺死當前正在運行的腳本。全局變量保護,為了防止不必要的數據泄漏進環境,腳本不允許創建全局變量。

基本命令

Redis 腳本使用 Lua 解釋器來執行腳本。 Reids 2.6 版本通過內嵌支持 Lua 環境。執行腳本的常用命令為 EVAL。

EVAL script numkeys key [key ...] arg [arg ...]
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

1 EVAL script numkeys key [key ...] arg [arg ...] 執行 Lua 腳本。
2 EVALSHA sha1 numkeys key [key ...] arg [arg ...] 執行 Lua 腳本。
3 SCRIPT EXISTS script [script ...] 查看指定的腳本是否已經被保存在緩存當中。
4 SCRIPT FLUSH 從腳本緩存中移除所有腳本。
5 SCRIPT KILL 殺死當前正在運行的 Lua 腳本。
6 SCRIPT LOAD script 將腳本 script 添加到腳本緩存中,但并不立即執行這個腳本。

Redis Eval 命令使用 Lua 解釋器執行腳本。

EVAL script numkeys key [key ...] arg [arg ...]
參數說明
script: 參數是一段 Lua 5.1 腳本程序。腳本不必(也不應該)定義為一個 Lua 函數。
numkeys: 用于指定鍵名參數的個數。
key [key ...]: 從 EVAL 的第三個參數開始算起,表示在腳本中所用到的那些 Redis 鍵(key),這些鍵名參數可以在 Lua 中通過全局變量 KEYS 數組,用 1 為基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。
arg [arg ...]: 附加參數,在 Lua 中通過全局變量 ARGV 數組訪問,訪問的形式和 KEYS 變量類似( ARGV[1] 、 ARGV[2] ,諸如此類)。

Redis Evalsha 命令根據給定的 sha1 校驗碼,執行緩存在服務器中的腳本。
EVALSHA sha1 numkeys key [key ...] arg [arg ...]

redis 127.0.0.1:6379> SCRIPT LOAD "return "hello moto""
"232fd51614574cf0867b83d384a5e898cfd24e5a"
 
redis 127.0.0.1:6379> EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 0
"hello moto"

Redis Script Exists 命令用于校驗指定的腳本是否已經被保存在緩存當中。
SCRIPT EXISTS script [script ...]

redis 127.0.0.1:6379> SCRIPT LOAD "return "hello moto""    # 載入一個腳本
"232fd51614574cf0867b83d384a5e898cfd24e5a"
 
redis 127.0.0.1:6379> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a
1) (integer) 1
 
redis 127.0.0.1:6379> SCRIPT FLUSH     # 清空緩存
OK
 
redis 127.0.0.1:6379> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a
1) (integer) 0

SCRIPT FLUSH 從腳本緩存中移除所有腳本。
SCRIPT KILL 殺死當前正在運行的 Lua 腳本。
SCRIPT LOAD script 將腳本 script 添加到腳本緩存中,但并不立即執行這個腳本。

詳細說明

這是從一個Lua腳本中使用兩個不同的Lua函數來調用Redis的命令的例子:

redis.call()
redis.pcall()

redis.call() 與 redis.pcall()很類似, 他們唯一的區別是當redis命令執行結果返回錯誤時, redis.call()將返回給調用者一個錯誤,而redis.pcall()會將捕獲的錯誤以Lua表的形式返回
redis.call() 和 redis.pcall() 兩個函數的參數可以是任意的 Redis 命令:

> eval "return redis.call("set","foo","bar")" 0
OK

需要注意的是,上面這段腳本的確實現了將鍵 foo 的值設為 bar 的目的,但是,它違反了 EVAL 命令的語義,因為腳本里使用的所有鍵都應該由 KEYS 數組來傳遞,就像這樣:

> eval "return redis.call("set",KEYS[1],"bar")" 1 foo
OK

要求使用正確的形式來傳遞鍵(key)是有原因的,**因為不僅僅是 EVAL 這個命令,所有的 Redis 命令,在執行之前都會被分析,籍此來確定命令會對哪些鍵進行操作。
因此,對于 EVAL 命令來說,必須使用正確的形式來傳遞鍵,才能確保分析工作正確地執行。 **

Lua 數據類型和 Redis 數據類型之間轉換

當 Lua 通過 call() 或 pcall() 函數執行 Redis 命令的時候,命令的返回值會被轉換成 Lua 數據結構。 同樣地,當 Lua 腳本在 Redis 內置的解釋器里運行時,Lua 腳本的返回值也會被轉換成 Redis 協議(protocol),然后由 EVAL 將值返回給客戶端。
下面兩點需要重點注意:
lua中整數和浮點數之間沒有什么區別。因此,我們始終將Lua的數字轉換成整數的回復,這樣將舍去小數部分。如果你想從Lua返回一個浮點數,你應該將它作為一個字符串
有兩個輔助函數從Lua返回Redis的類型。

redis.error_reply(error_string) returns an error reply. This function simply returns the single field table with the err field set to the specified string for you.

redis.status_reply(status_string) returns a status reply. This function simply returns the single field table with the ok field set to the specified string for you.

return {err="My Error"}
return redis.error_reply("My Error")
腳本的原子性

Redis 使用單個 Lua 解釋器去運行所有腳本,并且, Redis 也保證腳本會以原子性(atomic)的方式執行: 當某個腳本正在運行的時候,不會有其他腳本或 Redis 命令被執行。 這和使用 MULTI / EXEC 包圍的事務很類似。 在其他別的客戶端看來,腳本的效果(effect)要么是不可見的(not visible),要么就是已完成的(already completed)。

腳本緩存和 EVALSHA

EVAL 命令要求你在每次執行腳本的時候都發送一次腳本主體(script body)。Redis 有一個內部的腳本緩存機制,因此它不會每次都重新編譯腳本
EVALSHA 命令,它的作用和 EVAL 一樣,都用于對腳本求值,但它接受的第一個參數不是腳本,而是腳本的 SHA1 校驗和(sum)。
客戶端庫的底層實現可以一直樂觀地使用 EVALSHA 來代替 EVAL ,并期望著要使用的腳本已經保存在服務器上了,只有當 NOSCRIPT 錯誤發生時,才使用 EVAL 命令重新發送腳本,這樣就可以最大限度地節省帶寬。
刷新腳本緩存的唯一辦法是顯式地調用 SCRIPT FLUSH 命令,這個命令會清空運行過的所有腳本的緩存。通常只有在云計算環境中,才會執行這個命令。

Redis對lua腳本做出的限制

不能訪問系統時間或者其他內部狀態

Redis 會返回一個錯誤,阻止這樣的腳本運行: 這些腳本在執行隨機命令之后(比如 RANDOMKEY 、 SRANDMEMBER 或 TIME 等),還會執行可以修改數據集的 Redis 命令。如果腳本只是執行只讀操作,那么就沒有這一限制。

每當從 Lua 腳本中調用那些返回無序元素的命令時,執行命令所得的數據在返回給 Lua 之前會先執行一個靜默(slient)的字典序排序(lexicographical sorting)。舉個例子,因為 Redis 的 Set 保存的是無序的元素,所以在 Redis 命令行客戶端中直接執行 SMEMBERS ,返回的元素是無序的,但是,假如在腳本中執行 redis.call(“smembers”, KEYS[1]) ,那么返回的總是排過序的元素。

對 Lua 的偽隨機數生成函數 math.random 和 math.randomseed 進行修改,使得每次在運行新腳本的時候,總是擁有同樣的 seed 值。這意味著,每次運行腳本時,只要不使用 math.randomseed ,那么 math.random 產生的隨機數序列總是相同的。

全局變量保護,為了防止不必要的數據泄漏進 Lua 環境, Redis 腳本不允許創建全局變量。如果一個腳本需要在多次執行之間維持某種狀態,它應該使用 Redis key 來進行狀態保存。避免引入全局變量的一個訣竅是:將腳本中用到的所有變量都使用 local 關鍵字定義為局部變量。

可用庫

Redis Lua解釋器可用加載以下Lua庫:
base
table
string
math
debug
struct 一個Lua裝箱/拆箱的庫
cjson 為Lua提供極快的JSON處理
cmsgpack為Lua提供了簡單、快速的MessagePack操縱
bitop 為Lua的位運算模塊增加了按位操作數。
redis.sha1hex function. 對字符串執行SHA1算法
每一個Redis實例都擁有以上的所有類庫,以確保您使用腳本的環境都是一樣的。
struct, CJSON 和 cmsgpack 都是外部庫, 所有其他庫都是標準。

redis 127.0.0.1:6379> eval "return cjson.encode({["foo"]= "bar"})" 0
"{"foo":"bar"}"
redis 127.0.0.1:6379> eval "return cjson.decode(ARGV[1])["foo"]" 0 "{"foo":"bar"}"
"bar"

127.0.0.1:6379> eval "return cmsgpack.pack({"foo", "bar", "baz"})" 0
"x93xa3fooxa3barxa3baz"
127.0.0.1:6379> eval "return cmsgpack.unpack(ARGV[1])" 0 "x93xa3fooxa3barxa3baz"
1) "foo"
2) "bar"
3) "baz"
使用腳本記錄Redis 日志

在 Lua 腳本中,可以通過調用 redis.log 函數來寫 Redis 日志(log):

redis.log(loglevel,message)

其中, message 參數是一個字符串,而 loglevel 參數可以是以下任意一個值:

redis.LOG_DEBUG

redis.LOG_VERBOSE

redis.LOG_NOTICE

redis.LOG_WARNING
上面的這些等級(level)和標準 Redis 日志的等級相對應。

只有那些和當前 Redis 實例所設置的日志等級相同或更高級的日志才會被散發。
以下是一個日志示例:

redis.log(redis.LOG_WARNING, "Something is wrong with this script.")
執行上面的函數會產生這樣的信息:
[32343] 22 Mar 15:21:39 # Something is wrong with this script.
沙箱(sandbox)和最大執行時間

腳本應該僅僅用于傳遞參數和對 Redis 數據進行處理,它不應該嘗試去訪問外部系統(比如文件系統),或者執行任何系統調用。
除此之外,腳本還有一個最大執行時間限制,它的默認值是 5 秒鐘,一般正常運作的腳本通常可以在幾分之幾毫秒之內完成,花不了那么多時間,這個限制主要是為了防止因編程錯誤而造成的無限循環而設置的。
最大執行時間的長短由 lua-time-limit 選項來控制(以毫秒為單位),可以通過編輯 redis.conf 文件或者使用 CONFIG GET 和 CONFIG SET 命令來修改它。

當一個腳本達到最大執行時間的時候,它并不會自動被 Redis 結束,因為 Redis 必須保證腳本執行的原子性,而中途停止腳本的運行意味著可能會留下未處理完的數據在數據集(data set)里面。
因此,當腳本運行的時間超過最大執行時間后,以下動作會被執行:
Redis 記錄一個腳本正在超時運行
Redis 開始重新接受其他客戶端的命令請求,但是只有 SCRIPT KILL 和 SHUTDOWN NOSAVE 兩個命令會被處理,對于其他命令請求, Redis 服務器只是簡單地返回 BUSY 錯誤。
可以使用 SCRIPT KILL 命令將一個僅執行只讀命令的腳本殺死,因為只讀命令并不修改數據,因此殺死這個腳本并不破壞數據的完整性
如果腳本已經執行過寫命令,那么唯一允許執行的操作就是 SHUTDOWN NOSAVE ,它通過停止服務器來阻止當前數據集寫入磁盤

pipeline上下文(context)中的 EVALSHA

一旦在pipeline中因為 EVALSHA 命令而發生 NOSCRIPT 錯誤,那么這個pipeline就再也沒有辦法重新執行了,否則的話,命令的執行順序就會被打亂。
為了防止出現以上所說的問題,客戶端庫實現應該實施以下的其中一項措施:

總是在pipeline中使用 EVAL 命令

檢查pipeline中要用到的所有命令,找到其中的 EVAL 命令,并使用 SCRIPT EXISTS 命令檢查要用到的腳本是不是全都已經保存在緩存里面了。如果所需的全部腳本都可以在緩存里找到,那么就可以放心地將所有 EVAL 命令改成 EVALSHA 命令,否則的話,就要在pipeline的頂端(top)將缺少的腳本用 SCRIPT LOAD 命令加上去。

案例1-實現訪問頻率限制:

實現訪問者 $ip 在一定的時間 $time 內只能訪問 $limit 次.
非腳本實現

private boolean accessLimit(String ip, int limit, int time, Jedis jedis) {
    boolean result = true;

    String key = "rate.limit:" + ip;
    if (jedis.exists(key)) {
        long afterValue = jedis.incr(key);
        if (afterValue > limit) {
            result = false;
        }
    } else {
        Transaction transaction = jedis.multi();
        transaction.incr(key);
        transaction.expire(key, time);
        transaction.exec();
    }
    return result;
}

以上代碼有兩點缺陷

可能會出現競態條件: 解決方法是用 WATCH 監控 rate.limit:$IP 的變動, 但較為麻煩;

以上代碼在不使用 pipeline 的情況下最多需要向Redis請求5條指令, 傳輸過多.

Lua腳本實現
Redis 允許將 Lua 腳本傳到 Redis 服務器中執行, 腳本內可以調用大部分 Redis 命令, 且 Redis 保證腳本的 原子性 :
首先需要準備Lua代碼: script.lua

local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]

local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
    if redis.call("INCR", key) > limit then
        return 0
    else
        return 1
    end
else
    redis.call("SET", key, 1)
    redis.call("EXPIRE", key, expire_time)
    return 1
end

Java

private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {
    List keys = Collections.singletonList(ip);
    List argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));

    return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);
}

// 加載Lua代碼
private String loadScriptString(String fileName) throws IOException {
    Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));
    return CharStreams.toString(reader);
}

Lua 嵌入 Redis 優勢:

減少網絡開銷: 不使用 Lua 的代碼需要向 Redis 發送多次請求, 而腳本只需一次即可, 減少網絡傳輸;

原子操作: Redis 將整個腳本作為一個原子執行, 無需擔心并發, 也就無需事務;

復用: 腳本會永久保存 Redis 中, 其他客戶端可繼續使用.

案例2-使用Lua腳本重新構建帶有過期時間的分布式鎖.

案例來源: < Redis實戰 > 第6、11章, 構建步驟:

鎖申請

首先嘗試加鎖:

成功則為鎖設定過期時間; 返回;

失敗檢測鎖是否添加了過期時間;

wait.

鎖釋放

檢查當前線程是否真的持有了該鎖:

持有: 則釋放; 返回成功;

失敗: 返回失敗.

非Lua實現

String acquireLockWithTimeOut(Jedis connection, String lockName, long acquireTimeOut, int lockTimeOut) {
    String identifier = UUID.randomUUID().toString();
    String key = "lock:" + lockName;

    long acquireTimeEnd = System.currentTimeMillis() + acquireTimeOut;
    while (System.currentTimeMillis() < acquireTimeEnd) {
        // 獲取鎖并設置過期時間
        if (connection.setnx(key, identifier) != 0) {
            connection.expire(key, lockTimeOut);
            return identifier;
        }
        // 檢查過期時間, 并在必要時對其更新
        else if (connection.ttl(key) == -1) {
            connection.expire(key, lockTimeOut);
        }

        try {
            Thread.sleep(10);
        } catch (InterruptedException ignored) {
        }
    }
    return null;
}

boolean releaseLock(Jedis connection, String lockName, String identifier) {
    String key = "lock:" + lockName;

    connection.watch(key);
    // 確保當前線程還持有鎖
    if (identifier.equals(connection.get(key))) {
        Transaction transaction = connection.multi();
        transaction.del(key);
        return transaction.exec().isEmpty();
    }
    connection.unwatch();

    return false;
}

Lua腳本實現
Lua腳本: acquire

local key = KEYS[1]
local identifier = ARGV[1]
local lockTimeOut = ARGV[2]

-- 鎖定成功
if redis.call("SETNX", key, identifier) == 1 then
    redis.call("EXPIRE", key, lockTimeOut)
    return 1
elseif redis.call("TTL", key) == -1 then
    redis.call("EXPIRE", key, lockTimeOut)
end
return 0

Lua腳本: release

local key = KEYS[1]
local identifier = ARGV[1]

if redis.call("GET", key) == identifier then
    redis.call("DEL", key)
    return 1
end
return 0

參考:http://www.redis.cn/commands/...
http://www.redis.net.cn/tutor...
http://www.oschina.net/transl...
http://www.tuicool.com/articl...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38307.html

相關文章

  • Redis-py官方文檔翻譯

    摘要:采取兩種實現命令其一類盡量堅持官方語法,但是以下除外沒有實現,應該是線程安全的原因。線程安全性是線程安全的。由于線程安全原因,不提供實現,因為它會導致數據庫的切換。 官網:https://github.com/andymccurd...當前版本:2.10.5注:這不是完整翻譯,只提取了關鍵信息。省略了部分內容,如lua腳本支持。 pip install redis pip instal...

    Alfred 評論0 收藏0

發表評論

0條評論

YancyYe

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<