1. 程式人生 > >初學乍練redis:事務與指令碼

初學乍練redis:事務與指令碼

目錄

一、事務

1. 概述

        大部分摘自Redis入門指南(第2版),書摘備查。

一、事務

1. 概述

        redis提供了一個實用的命令INCR,其作用是讓當前鍵值遞增,並返回遞增後的值。用法為:

[[email protected]~]#redis-cli set foo 4
OK
[[email protected]~]#redis-cli incr foo
(integer) 5

        當要操作的鍵不存在時會預設鍵值為0,所以第一次遞增後的結果是1。當鍵值不是整數時redis會提示錯誤。

[[email protected]~]#redis-cli set foo lorem
OK
[[email protected]~]#redis-cli incr foo
(error) ERR value is not an integer or out of range

        有些使用者會想到可以藉助GET和SET兩個命令自己實現incr函式。虛擬碼如下:

def incr($key)
    $value = GET $key
    if not $value
       $value = 0
    $value = $value + 1
    SET $key, $value
    return $value

        如果redis只連線了一個客戶端,那麼上面的程式碼沒有問題(其實還沒有加入錯誤處理)。可當同一時間有多個客戶端連線到redis時則有可能出現競態條件(race condition)。競態條件是指一個系統或者程序的輸出,依賴於不受控制的事件的出現順序或者出現時機。例如有兩個客戶端 A 和 B 都要執行我們自己實現的incr函式並準備將同一個鍵的鍵值遞增。當它們恰好同時執行到程式碼第二行時二者讀取到的鍵值是一樣的,如“5”,而後它們各自將該值遞增到“6”並使用SET命令將其賦給原鍵,結果雖然對鍵執行了兩次遞增操作,最終的鍵值卻是“6”而不是預想中的“7”。

        包括INCR在內的所有redis命令都是原子操作(atomic operation),無論多少個客戶端同時連線,都不會出現上述情況。下面將介紹如何利用redis事務和指令碼實現自定義的原子操作的方法。原子操作取“原子”的“不可拆分”的意思,原子操作是最小的執行單位,不會在執行過程中被其它命令插入打斷。

        redis中的事務是一組命令的集合。事務同命令一樣都是redis的最小執行單位,一個事務中命令要麼都執行,要麼都不執行。與關係資料庫的事務概念不同,實際上redis的事務只是保證了ACID特性中的A,即原子性。事務的原理是先將屬於一個事務的命令傳送給redis,然後再讓redis依次執行這些命令。例如:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd "user:1:following" 2
QUEUED
127.0.0.1:6379> sadd "user:2:followers" 1
QUEUED
127.0.0.1:6379> exec
1) (integer) 0
2) (integer) 0

        上面的程式碼演示了事務的使用方式。首先使用multi命令告訴redis下面的命令屬於同一個事務。redis會把下面的兩個sadd命令暫時存起來,返回QUEUED表示這兩條命令已經進入等待執行的事務佇列中了。當把所有要在一個事務中執行的命令都發給redis後,我們使用exec命令告訴redis將等待執行的事務佇列中的所有命令,即前面所有返回QUEUED的命令,按照發送順序依次執行。exec命令的返回值就是這些命令返回值組成的列表,返回值順序和命令的順序相同。

        redis保證一個事務中的所有命令要麼都執行,要麼都不執行。如果在傳送exec命令前客戶端斷線了,則redis會清空事務佇列,事務中的所有命令都不會執行。而一旦客戶端傳送了exec命令,所有的命令就都會被執行,即使此後斷線也沒關係,因為redis中已經記錄了所有要執行的命令。

        除此之外,redis的事務還能保證一個事務內的命令依次執行而不被其它命令插入。試想客戶端A需要執行幾條命令,同時客戶端B傳送了一條命令。如果不使用事務,則客戶端B的命令可能插入客戶端A的幾條命令中執行。如果不希望發生這種情況,也可以使用事務。

2. 錯誤處理

        如果一個事務中的某個命令執行出錯,redis會怎麼處理呢?要回答這個問題,首先需要知道什麼原因會導致命令執行出錯。

(1)語法錯誤。語法錯誤指命令不存在或者命令引數的個數不對。比如:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key value
QUEUED
127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> errorcommand key
(error) ERR unknown command `errorcommand`, with args beginning with: `key`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

        跟在multi命令後執行了三個命令:一個是正確的命令,成功地加入事務佇列;其餘兩個命令都有語法錯誤。而只要有一個命令有語法錯誤,執行exec命令後redis就會直接返回錯誤,連語法正確的命令也不會執行。

(2)執行錯誤。執行錯誤指在命令執行時出現的錯誤,比如使用雜湊型別的命令操作集合型別的鍵。這種錯誤在實際執行之前redis是無法發現的,所以在事務裡這樣的命令是會被redis接受並執行的。如果事務裡的一條命令出現執行錯誤,事務裡其它的命令依然會繼續執行,包括出錯命令之後的命令,示例如下:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> sadd key 2
QUEUED
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get key
"3"

        可見雖然 sadd key 2 出現錯誤,但是 set key 3 依然執行了。事務回滾是指將一個事務已經完成的對資料庫的修改操作撤銷。redis的事務沒有關係資料庫事務的回滾(rollback)功能,為此開發者必須在事務執行出錯後自己收拾剩下的攤子,將資料庫復原回事務之前的狀態。

        不過由於redis不支援回滾功能,也使得redis在事務上可以保持簡潔和快速。另外回顧剛才提到的會導致事務執行失敗的兩種錯誤,其中語法錯誤完全可以在開發時找出並解決,另外如果能夠很好地規劃資料庫(保證鍵名規範等)的使用,是不會出現命令與資料型別不匹配這樣的執行錯誤的。

3. watch命令

        在一個redis事務中,只有當所有命令都依次執行完後才能得到每個結果的返回值。可是有些情況下需要先獲得一條命令的返回值,然後再根據這個值執行下一條命令。例如本文開始介紹的虛擬碼,使用get和set命令自己實現incr函式會出現競態條件。就是說在執行set命令時,之前get獲得的返回值可能已經被修改了。這種情況類似於關係資料庫中的丟失更新問題。關係資料庫一般採用悲觀鎖或樂觀鎖解決丟失更新問題。

        但在redis中我們需要換一種思路。即在get獲得鍵值後保證該值不被其它客戶端修改,直到函式執行完成後才允許客戶端修改該鍵值,這樣也可以防止競態條件。redis使用watch命令實現這一思路。watch命令可以監控一個或多個鍵,一旦其中有一個鍵被修改或刪除,之後的事務就不會執行。監控一直持續到exec命令(事務中的命令是在exec之後才執行的,所以multi命令後可以修改watch監控的鍵值),如:

127.0.0.1:6379> set key 1
OK
127.0.0.1:6379> watch key
OK
127.0.0.1:6379> set key 2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get key
"2"

        上例中在執行watch命令後、事務執行前修改了key值(即 set key 2),所以最後事務中的命令 set key 3 沒有執行,exec命令返回空結果。利用watch命令就可以通過事務自己實現incr函數了,虛擬碼如下:

def incr($key)
    WATCH $key
    $value = GET $key
    if not $value
       $value = 0
    $value = $value + 1
    MULTI
    SET $key, $value
    result = EXEC
    return result[0]

        因為exec命令返回值是多行字串型別,所以程式碼中使用result[0]來獲得其中第一個結果。watch命令的作用只是當被監控的鍵值被修改後阻止之後一個事務的執行,而不能保證其它客戶端不修改這一鍵值,所以我們需要在exec執行失敗後重新執行整個函式。watch使用的實際是一種樂觀鎖的思想。

        執行exec命令後會取消對所有鍵的監控,如果不想執行事務中的命令也可以使用unwatch命令來取消監控。比如,我們要實現 hsetxx 函式,作用於hsetnx命令類似,只不過是僅當欄位存在時才賦值。為了避免競態條件,使用事務來完成這一功能:

def hsetxx($key, $field, $value)
    WATCH $key
    $isFieldExists = HEXISTS $key, $field
    if $isFieldExists is 1
       MULTI
       HSET $key, $field, $value
       EXEC
    else
       UNWATCH
    return $isFieldExists

        在程式碼中會判斷要複製的欄位是否存在,如果欄位不存在的話就不執行事務中的命令,但需要使用UNWATCH命令來保證下一個事務的執行不會受到影響。
 

二、redis指令碼

1. 指令碼介紹

        在前面使用事務實現的incr函式中,為避免出現競態條件,用watch檢測$key鍵的變動。但是這樣做比較麻煩,而且還需要判斷事務是否因為鍵被改動而沒有執行。除此之外這段程式碼在不使用管道的情況下要向redis請求5條命令,在網路傳輸上會浪費很多時間。而此時正是redis指令碼功能的用武之地。

        redis在2.6版推出了指令碼功能,允許開發者使用Lua語言編寫指令碼傳到redis中執行。在Lua指令碼中可以呼叫大部分redis命令。使用指令碼的好處如下:

  • 減少網路開銷:使用指令碼實現自定義incr同樣的操作只需要傳送一個請求即可,減少了網路往返時延。
  • 原子操作:redis會將整個指令碼作為一個整體執行,中間不會被其它命令插入。換句話說在編寫指令碼的過程中無需擔心會出現競態條件,也就無需使用事務。事務可以完成的所有功能都可以使用指令碼來實現。
  • 複用:客戶端傳送的指令碼會永久儲存在redis中,這就意味著其它客戶端(可以是其它語言開發的專案)可以複用這一指令碼而不需要編寫程式碼完成同樣的邏輯。

        從以上總結可以看出指令碼功能特點類似於關係資料庫中的儲存過程。

2. 例項:自定義incr

        因為無需考慮事務,使用redis指令碼實現incr非常簡單。Lua程式碼如下:

local num = redis.call('get', KEYS[1])
local cycles = tonumber(ARGV[1])

for i = 0, cycles do
    local a = 1
end

if not num then
   num = 0
else 
   local n = tonumber(num)
   if n then
      if math.ceil(n) ~= n then
         return "Key's value is not an integer!"
      end
   else
      return "Key's value is not a number!"
   end 
end

num = num + 1
redis.call('set', KEYS[1], num)
return num

        for迴圈用來模擬sleep,目的是驗證指令碼的原子性。將這段程式碼儲存為incr.lua檔案,然後執行下面的步驟進行測試。

(1)設定一個鍵值作為初始值。

[[email protected]~]#redis-cli set foo 5
OK

(2)在終端1執行如下命令列,命令將處於等待狀態。

redis-cli --eval incr.lua foo , 400000000

(3)5秒之內終端2執行同樣的命令列

[[email protected]~]#redis-cli --eval incr.lua foo , 400000000

(4)幾秒後待兩個終端都執行完成,驗證兩個終端的輸出結果
        第一個終端的輸出為:

[[email protected]~]#redis-cli --eval incr.lua foo , 400000000
(integer) 6

        第二個終端的輸出為:

[[email protected]~]#redis-cli --eval incr.lua foo , 400000000
(integer) 7

        最終的鍵值是7而不是6。lua本身沒有提供sleep函式,而在redis中使用lua指令碼時又不能使用全域性變數(os、socket、posix等等),所以合理的等待時間只能通過測試得到,400000000就是我的環境下測試的結果,約產生5秒的等待。如果該引數太小,難以模擬併發情況,若太大,使得第一個終端的執行時間超過5秒,則第二個終端會報錯:
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

        這個錯誤是由引數lua-time-limit所控制:

127.0.0.1:6379> config get lua*
1) "lua-time-limit"
2) "5000"
127.0.0.1:6379>

        命令列中的 --eval 引數是告訴redis-cli讀取並執行後面的Lua指令碼,後面是指令碼檔名,再後面跟著的是傳給Lua指令碼的引數。其中“,”前面的foo是要操作的鍵,可以在指令碼中使用KEYS[1]獲取(Lua語言區分大小寫)。“,”後面的400000000是其它引數,在指令碼中能夠使用ARGV[1]獲得。注意命令列中“,”兩邊的空格不能省略,否則會出錯。

3. redis與Lua

(1)在指令碼中呼叫redis命令     

        在指令碼中可以使用redis.call函式呼叫redis命令。就像這樣:

redis.call('set', 'foo', 'bar')
local value = redis.call('get', 'foo')

        redis.call函式的返回值就是redis命令的執行結果。redis命令的返回值有5種類型,redis.call函式會將這5種類型的返回值轉換成Lua的資料型別,具體的對應規則如表1所示(空結果比較特殊,其對應Lua的false)。

Redis返回值型別

Lua資料型別

整數

數字

字串

字串

多行字串

表型別(陣列形式)

狀態

表型別(只有一個ok欄位儲存狀態資訊)

錯誤

表型別(只有一個err欄位儲存錯誤資訊)

        表1 redis返回值型別和Lua資料型別轉換規則

        redis還提供了redis.pcall函式,功能與redis.call相同,唯一的區別是當命令執行出錯時redis.pcall會記錄錯誤性繼續執行,而redis.call會直接返回錯誤,不會繼續執行。

(2)從指令碼中返回值        

        在很多情況下都需要指令碼返回值。在指令碼中可以使用return語句將值返回給客戶端,如果沒有執行return語句則預設返回nil。因為我們可以像呼叫其它redis內建命令一樣呼叫自己寫的指令碼,所以同樣redis會自動將指令碼返回值的Lua資料型別轉換成redis的返回值型別。具體的轉換規則見表2(其中Lua的false比較特殊,會被轉換成空結果)。

Lua資料型別

Redis返回值型別

數字

整數(Lua的數字型別會被自動轉換成整數)

字串

字串

表型別(陣列形式)

多行字串

表型別(只有一個ok欄位儲存狀態資訊)

狀態

表型別(只有一個err欄位儲存錯誤資訊)

錯誤

        表2 Lua資料型別和redis返回值型別轉換規則

(3)指令碼相關命令

  • EVAL

        編寫完指令碼後最重要的就是在程式中執行指令碼。redis提供了eval命令可以使開發者像呼叫其它redis內建命令一樣呼叫指令碼。eval命令的格式是:eval 指令碼內容 key引數的數量 [key ...] [arg ...]。可以通過key和arg這兩類引數向指令碼傳遞資料,它們的值可以在指令碼中分別使用 KEYS 和 ARGV 兩個表型別的全域性變數訪問。比如希望指令碼功能實現一個set命令,指令碼內容是這樣的:

return redis.call('set', KEYS[1], ARGV[1])

        現在開啟redis-cli執行此指令碼:

[[email protected]~]#redis-cli
127.0.0.1:6379> eval "return redis.call('set', KEYS[1], ARGV[1])" 1 foo bar
OK
127.0.0.1:6379> get foo
"bar"

        其中要讀寫的鍵名應該作為 key 引數,其它的資料都作為 arg 引數。eval命令依據第二個引數將後面的所有引數分別存入KEYS和ARGV兩個表型別的全域性變數。

  • 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外,redis還提供了其它4個指令碼相關的命令,一般都會被客戶端封裝起來,開發者很少能使用到。

  • SCRIPT LOAD

        將指令碼加入快取。每次執行命令時redis都會將指令碼的sha1摘要加入指令碼快取中,以便下次客戶端可以使用evalsha命令呼叫該指令碼。如果只是希望將指令碼加入指令碼快取而不執行則可以使用SCRIPT LOAD命令,返回值是指令碼的sha1摘要。就像這樣:

127.0.0.1:6379> script load "return 1"
"e0e1f9fabfc9d4800c877a703b823ac0578ff8db"
  • SCRIPT EXISTS

        判斷指令碼是否已經被快取。SCRIPT EXISTS命令可以同時查詢1個或多個指令碼的sha1摘要是否被快取,如:

127.0.0.1:6379> script exists e0e1f9fabfc9d4800c877a703b823ac0578ff8db abcdefghijklmnopqrstuvwxyzabcdefghijklmn
1) (integer) 1
2) (integer) 0
  • SCRIPT FLUSH

        清空指令碼快取。redis將指令碼的sha1摘要加入到指令碼快取後會永久保留,不會刪除,但可以手動使用SCRIPT FLUSH命令清空指令碼快取。

127.0.0.1:6379> script flush
OK
  • SCRIPT KILL

        強制終止當前指令碼的執行。如果想終止當前正在執行的指令碼使用SCRIPT KILL命令。

(4)KEYS和ARGV

        向指令碼傳遞的引數分為 KEYS 和 ARGV 兩類,前者表示要操作的鍵名,後者表示非鍵名引數。但事實上這一要求並不是強制的,比如 eval "return redis.call('get', KEYS[1])" 1 user:Bob 可以獲得 user:Bob 的鍵值,同樣也可以使用 eval "return redis.call('get', 'user:' .. ARGV[1])" 0 Bob 完成同樣的功能。此時我們雖然並未按照redis的規則使用KEYS引數傳遞鍵名,但還是獲得了正確的結果。

        雖然規則不是強制的,但不遵守規則依然有一定的代價。redis 3.0及以後版本帶有就叢集(cluster)功能,叢集的作用是將資料庫中的鍵分散到不同的節點上。這意味著在指令碼執行前就需要知道指令碼會操作哪些鍵以便找到對應的節點,所以如果指令碼中的鍵名沒有使用 KEYS 引數傳遞則無法相容叢集。

        有時候鍵名是根據指令碼某部分的執行結果生成的,這時就無法在執行前將鍵名明確標出。比如一個集合型別鍵儲存了使用者ID列表,每個使用者使用雜湊鍵儲存,其中有一個欄位是年齡。下面的指令碼可以計算某個集合中使用者的平均年齡:

local sum = 0
local users = redis.call('smembers', KEYS[1])
for _, user_id in ipairs(users) do 
    local user_age = redis.call('hget', 'user:' .. user_id, 'age')
    sum = sum + user_age
end 

return sum / #users

        這個指令碼同樣無法相容叢集功能,因為第4行中訪問了 KEYS 變數中沒有的鍵,但卻十分實用,避免了資料往返客戶端和伺服器端的開銷。為了相容叢集,可以在客戶端獲取集合中的使用者ID列表,然後將使用者ID組裝成鍵名列表傳給指令碼並計算平均年齡。兩種方案都是可行的,至於實際採用哪種就需要開發者自行權衡了。

(5)沙盒與隨機數

        redis指令碼禁止使用Lua標準庫中與檔案或系統呼叫相關的函式,在指令碼中只允許對redis的資料進行處理。並且redis還通過禁用指令碼的全域性變數的方式保證每個指令碼都是相對隔離的,不會互相干擾(類似於ACID中的事務隔離性)。

        使用沙盒不僅是為了保證伺服器的安全性,而且還確保了指令碼的執行結果只和指令碼本身和執行時傳遞的引數有關,不依賴外界條件,如系統時間、系統中某個檔案的內容、其它指令碼的執行結果等。這是因為在執行復制和AOF持久化操作時記錄的是指令碼的內容而不是指令碼呼叫的命令(有點類似於MySQL複製中的 binlog_format=statement),所以必須保證在指令碼內容和引數一樣的前提下指令碼的執行結果必須是一樣的。此概念與關係資料庫函式定義中的deterministic選項是一致。

        除使用沙盒外,為了確保執行的結果可以重現,redis還對隨機數和會產生隨機結果的命令進行了特殊的處理。對於隨機數而言,redis替換了math.random和math.randomseed函式,使得每次執行指令碼時生成的隨機數序列都相同,如果希望獲得不同的隨機數序列,最簡單的方法是由程式生成隨機數並通過引數傳遞給指令碼。或者採用更靈活的方法,即在程式中生成隨機數傳給指令碼作為隨機數種子(通過math.randomseed(tonumber(ARGV[種子引數索引]))),這樣在指令碼中再呼叫math.random產生的隨機數就不同了(由隨機數種子決定)。

        對於產生隨機結果的命令如smembers(因為集合型別是無序的)或hkeys(因為雜湊型別的欄位也是無序的)等,redis會對結果按照字典順序排序。內部是通過呼叫Lua標準庫的table.sort函式實現的,程式碼與下面這段很相似:

function __redis__compare_helper(a,b)
  if a == false then a = '' end
  if b == false then b = '' end
  return a < b
end 
table.sort(result_array, __redis__compare_helper)

        對於會產生隨機結果但無法排序的命令(比如會產生一個元素),redis會在這類命令執行後將該指令碼狀態標記為lua_random_dirty,此後只允許呼叫只讀命令,不允許修改資料庫的值,否則返回錯誤:“Write commands not allowed after non deterministic commands.”。屬於此類的redis命令有spop、srandmember、randomkey和time。

(6)原子性和執行時間

        redis的指令碼執行是原子的,即指令碼執行期間redis不會執行其它命令。所有的命令都必須等待指令碼執行完成後才能執行(實際上就是指令碼序列化了並行的redis命令)。為了防止某個指令碼執行時間過長,導致redis無法提供服務(比如陷入死迴圈),redis提供了lua_time_limit引數限制指令碼的最長執行時間,預設為5秒鐘。當指令碼執行時間超過這一限制後,redis將開始接受其它命令但不會執行(以確保指令碼的原子性,因為此時指令碼並沒有被終止),而是會返回“BUSY”錯誤。現在我們開啟兩個redis-cli例項A和B來演示這一情況。首先在A中執行一個死迴圈指令碼:

127.0.0.1:6379> eval "while true do end" 0

然後馬上在B中執行一條命令:

127.0.0.1:6379> get foo

此時例項B中的命令並沒有馬上返回結果,因為redis已經被例項A傳送的死迴圈指令碼阻塞了,無法執行其它命令。等到5秒後例項B收到了“BUSY”錯誤:

(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

此時redis雖然可以接受任何命令,但實際能執行的只有兩個命令:script kill 和 shutdown nosave。在例項B中執行 script kill 命令可以終止當前指令碼的執行:

127.0.0.1:6379> script kill
OK

此時指令碼被終止並且例項A中會返回錯誤:

127.0.0.1:6379> eval "while true do end" 0
(error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): @user_script:1: Script killed by user with SCRIPT KILL... 
(33.60s)

        需要注意的是如果當前執行的指令碼對redis的資料進行了修改(如呼叫set、lpush或del等命令),則 script kill 命令不會終止指令碼的執行以防止只執行了一部分。因為如果指令碼只執行了一部分就被終止,會違背指令碼的原子性要求。比如在例項A中執行:

eval "redis.call('set', 'foo', 'bar') while true do end" 0

5秒鐘後在例項B中嘗試終止該指令碼:

127.0.0.1:6379> script kill
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.

此時只能通過shutdown nosave命令強制終止redis。shutdown nosave與shutdown命令的區別在於前者不會進行持久化操作,這意味著所有發生在上一次快照後的資料庫修改都會丟失。

        由於redis指令碼非常高效,所以在大部分情況下都不用擔心指令碼的效能。但同時由於指令碼的強大功能,很多原本在程式中執行的邏輯可以放到指令碼中執行,這時就需要開發者根據具體應用權衡到底哪些任務適合交給指令碼。通常來講不應該在指令碼中進行大量耗時的計算,因為畢竟redis是單程序單執行緒執行指令碼,而程式能夠多程序或多執行緒執行。
 

相關推薦

初學redis事務指令碼

目錄 一、事務 1. 概述         大部分摘自Redis入門指南(第2版),書摘備查。 一、事務 1. 概述         redis提供了一個實用的命令INCR,其作用是讓當前鍵值遞增,並返回遞增後的值。

初學redis兩行shell指令碼實現slowlog持久化轉儲(去重保留歷史條目、時間戳格式化)

目錄 一、問題提出         在排查redis效能問題時,從slowlog中找執行緩慢的命令進行優化是一種常規手段。redis slowlog被設計成記憶體中一個先進先出的佇列結構,一旦容量被填滿,新的條目就會擠出舊條目。特別是在慢日

Java開發Redis事務Watch原理分析

緬甸鉆石國際 開戶13378837779Redis中的業務(transaction)是一組指令的調集。業務同指令一樣都是Redis最小的履行單位,一個業務中的指令要麽都履行,要麽都不履行。Redis業務的完結需求用到 MULTI 和 EXEC兩個指令,業務開端的時分先向Redis服務器發送 MULTI 指

Oracle事務鎖關係

事務與鎖是不同的。 事務具有ACID( 原子性、一致性、隔離性和永續性),鎖是用於解決隔離性的一種機制。 事務的隔離級別通過鎖的機制來實現。 另外鎖有不同的粒度,同時事務也是有不同的隔離級別的 (一般有四種:讀未提交Read uncommitted, 讀已提交Read committ

java初學之用Calendar列印萬年曆

題目:列印萬年曆,如:                2018年 4月  日曆日      一      二      三      四      五      六 1       2       3       4       5       6       7 8    

redis事務管道

redis的事務與管道 一、redis的管道 1. 使用管道技術的原因 redis是一個客戶端-伺服器(CS)模型和請求/響應協議的TCP伺服器,使用和http類似的請求響應協議。一個client可以通過一個socket連線發起多個請求命令。每個請求命令發出後

Docker初學之單主機網絡

ip 地址 ont nec val 診斷 外部 multicast mon docker 一、Docker bridge network Docker 橋接網絡使用軟件橋接, 該軟件橋接允許連接到同一橋網的容器進行通信, 同時提供與未連接到該橋網絡的容器的隔離。Docker

分布式緩存技術redis學習系列(三)——redis高級應用(主從、事務鎖、持久化)

master ica not ood www working can 出了 owin 上文《詳細講解redis數據結構(內存模型)以及常用命令》介紹了redis的數據類型以及常用命令,本文我們來學習下redis的一些高級特性。 回到頂部 安全性設置 設置客戶端操作秘密

從銀行微信約戰棋牌源碼下載轉賬失敗到分布式事務總結思考

網上 不執行 分布式存 sys 優缺點 用戶 分別是 擇業 col 思考這微信約戰棋牌源碼下載( h5.super-mans.com Q:2012035031)微信約戰棋牌源碼下載個問題的初衷,是有一次給朋友轉賬,結果我的錢被扣了,朋友沒收到錢。而我之前一直認為銀行轉

Redis筆記整理(一)Redis安裝配置數據類型操作

數據庫 NoSQL Redis [TOC] Redis筆記整理(一):Redis安裝配置與數據類型操作 Redis簡介 Redis是一個開源(BSD許可),內存存儲的數據結構服務器,可用作數據庫,高速緩存和消息隊列代理。 它支持字符串、哈希表、列表、集合、有序集合,位圖,hyperloglo

從銀行轉賬失敗到分布式事務總結思考

額外 nav 一般來說 以及 不執行 there 上大 區別 信息 目錄 關系型數據庫事務 分布式事務 2PC 3PC TCC 基於消息的分布式事務 1PC 思考與總結 references 正文   思考這個問題的初衷,是有一次給朋友轉賬,結果我的錢被扣

Redis學習筆記(1)Redis的說明安裝

sets cti ansi c sde pos AR bsd 學習 ash Redis學習筆記(1):Redis說明的安裝 說明 什麽是Redis REmote DIctionary Server(Redis) 是一個由Salvatore Sanfilippo寫的key-v

Redis多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖

width uno ... ack spool 場景 .html 高並發 遇到的問題 本篇文章是通過watch(監控)+mutil(事務)實現應用於在分布式高並發處理等相關場景。下邊先通過redis-cli.exe來測試多個線程修改時,遇到問題及解決問題。 高並發下修改同

分散式事務 解決資料一致性(一)事務原則實現事務、sql事務

事務: 定義:       是以一種可靠、一致的方式,訪問和操作資料庫中資料的程式單元。 原則:      *a、 原子性   * b、一致性  * c、隔離性 &nbs

redis教程redis的安裝php擴充套件配置

Redis的安裝 cd /usr/local wget http://download.redis.io/redis-stable.tar.gz tar zxvf redis-stable.tar.gz cd redis-stable //進入解壓目錄 make /

Redis基本使用二(事務鎖)

開發環境: JDK11 Redis3.2 Redis事務機制: 與傳統的關係型資料庫類似,NoSQL也存在許多併發訪問的情況,因此出現瞭如何保證資料一致性的問題,處理的方式有很多。 針對不同的業務層次有不同的解決方案: 檢視層:前端來保證資料一致性,筆者對前端技

Redis資料庫】命令學習筆記——釋出訂閱、事務指令碼、連線命令彙總

本篇基於redis 4.0.11版本,學習釋出訂閱、事務、指令碼、連線的相關命令。 Redis 釋出訂閱(pub/sub)是一種訊息通訊模式:傳送者(pub)傳送訊息,訂閱者(sub)接收訊息。 序

jedis操作redis事務管道

Jedis對管道、事務以及Watch的操作詳細解析 1、Pipeline 利用pipeline的方式從client打包多條命令一起發出,不需要等待單條命令的響應返回,而redis服務端會處理完多條命令後會將多條命令的處理結果打包到一起返回給客戶端。所以p

Redis 事務Lua

事務 Redis支援簡單的事務: 命令 說明 mutli 代表事物開始 exec 代表事物結束 discard 命令表示停止事物。 watch 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動

Redis學習八Redis事務

一、是什麼 可以一次執行多個命令,本質是一組命令的集合。一個事務中的所有命令都會序列化,按順序地序列化執行而不會被其它命令插入,不許加塞。 二、能幹嘛  一個佇列中,一次性、順序性、排他性的執行一系列命令 三、怎麼玩 1.常用命令 2.Case1:正常執行 3.Case2:放棄事