網站首頁 編程語言 正文
前言
我們在使用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
相關推薦
- 2022-09-04 mat矩陣和npy矩陣實現互相轉換(python和matlab)_python
- 2023-02-27 pandas中concatenate和combine_first的用法詳解_python
- 2022-03-26 .NET?6中使用DateOnly和TimeOnly類型_ASP.NET
- 2022-09-21 Python腳本開發中的命令行參數及傳參示例詳解_python
- 2022-10-08 react-redux的基本使用_React
- 2022-06-08 ASP.NET?Core中MVC模式實現路由二_實用技巧
- 2022-04-23 R語言兩組變量特征相關關系熱圖繪制畫法_R語言
- 2022-05-10 在 VSCode 中如何設置默認的瀏覽器為Chrome或Firefox
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支