日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Redis并發訪問問題詳細講解_Redis

作者:肥肥技術宅 ? 更新時間: 2022-12-29 編程語言

前言

我們在使用Redis的過程中,難免會遇到并發訪問及數據更新的問題。但很多場景對數據的并發修改是很敏感的,比如庫存數據如果沒有做好并發讀取和更新的版本控制,就會導致嚴重的業務問題。今天就來說說應該如何做好并發訪問及數據更新問題。

什么場景需要控制并發訪問

需要控制并發訪問,說明這些并發的訪問可能會對其他的訪問造成影響。比如上面提到的庫存問題,若同一時期有多個客戶端訪問商品A的庫存數據,并且可能要更更新庫存數據,這時候就需要對并發訪問進行控制了。

說到底,并發訪問需要控制的就是對數據的更新動作。 一般來說,客戶端要進行數據更新時可分為2個步驟:

  • 客戶端讀取Redis數據到本地。
  • 確認數據后,修改Redis的數據。

單個訪問來看,這個過程并沒什么問題。但是并發多了,一分為二的過程就會造成數據錯誤的問題。這里還是用庫存的例子來說:

  • 時間a::客戶端1讀取到庫存=10,我們需要對庫存+1=11的操作。
  • 時間b:客戶端2讀取到的庫存也是10,這次要對庫存-1=9的操作。
  • 時間c:客戶端1將+1后的值11寫回到Redis中。
  • 時間d:客戶端2將-1后的值9寫回到Redis中。

這樣下來,很明顯能發現庫存數據錯了。10+1-1 = 10,正確庫存是10,而上述場景最后為9。

由此可見,這個一分為二的操作不具有原子性,從而產生了錯誤的結果。類型這種場景很多,因此我們需要對這些并發訪問的場景加以控制。

并發訪問的控制方法

Redis并發訪問的控制,總的來說有2種方式。分別是加入鎖機制和讓一系列操作原子化。

1、加入鎖機制

首先第一點,加入鎖機制是很常見的解決方案。簡單來說就是一個客戶端訪問數據之前,先要獲取鎖,等數據操作完之后再解鎖。而在這個客戶端擁有鎖的過程中,其他客戶端如果也想訪問修改該數據,必須得等鎖釋放了之后,獲取到了鎖才行。

加鎖這個方案是可以解決并發訪問的數據準確問題,但放在redis這個場景中并不是很好。首先,Redis作為緩存本身并發訪問就很多,頻繁的加鎖解鎖,會大大降低redis的訪問性能;然后,Redis的客戶端在要加鎖時,需要用到分布式鎖。我們又得用額外的精力去維護這個分布式鎖。

2、操作原子化

操作原子化,也就是讓要執行的一系列動作都保持原子性操作。它的優點就是不需要加入額外的鎖機制。并發的數據準確性達到了,對Redis的性能也不會有太大的影響。

Redis要實現原子操作,總結有2種方式:

  • 單命令操作:也就是Redis中的INCR、HINCRBY等命令,直接將簡單的加減操作合成一個命令執行;
  • Lua腳本:借助Lua腳本,讓多個操作在Lua腳本上實現原子性操作。

1.單命令操作

首先,單命令操作,將數值的加減直接用Redis命令來執行。像string的加減可用INCR、DECR操作,hash列表field的加減可用HINCRBY操作。

比如下面截圖,兩個客戶端在不同時刻讀取的linux_pids a值為4,各自+1、-1后a值為4。結果是正確的。

由此可見,用Redis的INCR、DECR等命令可以解決數值簡單增減的并發場景。但如果我們對數據的更新不僅僅是簡單的加減操作時,Redis的這些命令就無能為力了。此時我們可以考慮另一種方案:Lua腳本。

2.Lua腳本

Lua語言是由C寫的,因此支持多平臺和系統。從Redis2.6開始,Redis就內置了Lua解釋器,我們能直接用Redis客戶端來執行lua腳本。

我們可以將需要執行的一系列操作用Lua腳本寫好,然后用Redis執行它。Lua腳本的方法能保證原子性操作的原因是:Redis會將Lua腳本一次性執行,也就是說執行Lua腳本是0-1的操作,要么成功,要么失敗。可以理解成MySQL的事務特性。

Redis使用lua腳本有2種方式:

  • 客戶端中使用:用到script load腳本內容、evalsha等命令
  • 執行直接執行lua腳本

我們一般用第二種方式來執行。

客戶端使用方法:

先用script load加載腳本命令,再用evalsha執行加載得到的sha1值。

127.0.0.1:6379> script load "return 'hello'"
"1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
127.0.0.1:6379> evalsha "1b936e3fe509bcbc9cd0664897bbe8fd0cac101b" 0
"hello"

再來看看Redis使用Lua腳本的語法:

redis-cli --eval {lua_path} KEYS[1] KEYS[2]... , ARGV[1] ARGV[2]...
?
--eval: ??? ??? ?執行lua腳本的命令
{lua_path}: ?? ??? ?lua腳本的路徑
KEYS[1] KEYS[2]: ? ? ? lua腳本中要操作的redis鍵,我們可以在lua腳本中用KEYS[1],KEYS[2],KEYS[3]指定多個
ARGV[1] ARGV[2]:?? ?傳入到lua腳本的參數,在腳本中用ARGV[1],ARGV[2]...來獲取。

Redis使用lua腳本的場景很多,最經典的案例當屬利用lua來控制某個IP的訪問頻率了。比如說需要防止惡意訪問網站的行為,我們規定1分鐘內訪問次數不能超過30次,實現的方法有很多,比如說漏桶方案、令牌桶方案,但使用最多的還是Redis+lua的分布式限流方案。

我們用lua腳本(test_lua.script)來簡單實現一下上述功能,就是1分鐘內若訪問次數超過30,直接攔截,否則訪問次數+1:

-- 限流的key
local limit_key = KEYS[1]
-- 限流次數
local limit_nums = 30
-- 當前訪問次數
local current_num = tonumber(redis.call('get', limit_key) or 0)
-- 超出限流次數
if current_num + 1 > limit_num
?? ?then
? ? return '超出訪問次數'
-- 沒有超出限流數,訪問次數+1
else
? redis.call("INCRBY", limit_key, "1")
? -- 第一次訪問,設置過期時間
? if current_num == 0 then
? ? redis.call("expire", limit_key, "60")
? return current + 1
end

用Redis執行,命令如下:

redis-cli --eval test_lua.script limit_key

小結

本文介紹了Redis并發訪問的控制問題,以及如何保證并發操作的原子化。原子化操作可通過單命令操作和Lua腳本的方式實現。我們在應對相關問題時,可根據需要選擇對應方案解決之。

原文鏈接:https://blog.csdn.net/m0_71777195/article/details/128094212

欄目分類
最近更新