Redis 處理客戶端請求是基于單線程模型的(?Redis 6.0 開始引入了多線程處理網絡 IO,但命令執行仍然是單線程的)。這意味著,在任意時刻 Redis 只會執行一個命令或腳本。這種單線程特性確保了當 Redis 在執行一個 Lua 腳本時,不會有其他命令或腳本同時執行。
2. Lua 腳本被視為一個整體命令
當使用 EVAL
或 EVALSHA
命令執行 Lua 腳本時,Redis 將整個 Lua 腳本視為一個不可分割的命令。這意味著從開始執行 Lua 腳本直到腳本執行完畢這段時間內,Redis 不會處理任何其他命令。所有在 Lua 腳本中調用的 Redis 命令都會按照它們出現的順序依次執行,且不會被其他客戶端的命令中斷。
3. 原子性和隔離性
由于上述原因,Lua 腳本在執行期間提供了類似于數據庫事務中的原子性和隔離性:
- 原子性:要么整個腳本全部執行成功,要么完全不執行,不存在部分執行的情況。
- 隔離性:腳本執行過程中,其他客戶端的操作不能影響到當前腳本的執行結果,反之亦然。
舉例說明:
if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
elsereturn 0
end為了確保只有鎖的持有者才能刪除鎖(即比較傳入的 requestId 和存儲在 Redis 中的值是否匹配),我們需要連續執行兩個操作:GET 和 DEL。如果這兩個操作不是原子性的,那么在這兩者之間可能會有其他客戶端修改了數據,導致競態條件的發生。但是,通過將這兩個操作封裝在一個 Lua 腳本中,Redis 確保這兩個操作作為一個不可分割的整體來執行,從而避免了競態條件的發生。
總結
Lua 腳本之所以能夠在 Redis 中保證原子性,主要是因為 Redis 的單線程模型以及它對待 Lua 腳本的方式——即將 Lua 腳本作為單一、不可分割的命令來執行。這使得 Lua 腳本不僅可以在分布式環境中安全地執行復雜的邏輯,而且還可以保證這些邏輯以原子方式執行,不受并發操作的影響。