Redis做爲一個很是成功的數據庫,提供了很是豐富的數據類型和命令,使用這些,咱們能夠輕易而高效地完成不少緩存操做,但是總有一些比較特殊的問題或需求須要解決,這時候可能就須要咱們本身定製本身的 Redis 數據結構和命令。css
文章歡迎轉載,請尊重做者勞動成果,帶上原文連接:http://www.cnblogs.com/zhenbianshu/p/8416162.htmlhtml
線程安全問題
咱們都知道 Redis 是單線程的,但是它怎麼會有 線程安全 問題呢?nginx
咱們正常理解的線程安全問題是指單進程多線程
模型內部多個線程操做進程內共享內存
致使的數據資源充突。而 Redis 的線程安全問題的產生,並非來自於 Redis 服務器內部。web
Redis 做爲數據服務器,就至關於多個客戶端的共享內存,多個客戶端就至關於同一進程下的多個線程,若是多個客戶端之間沒有良好的數據同步策略,就會產生相似線程安全的問題。redis
典型場景是:數據庫
user5277=idle
;status = get("user5277")
;set("user5277", "busy")
;致使這個問題的緣由就是雖然 Redis 是單線程的,能保證命令的序列化,但因爲其執行效率很高,多個客戶端的命令之間不作好請求同步,一樣會形成命令的順序錯亂。編程
固然這個問題也很好解決,給用戶狀態加鎖就好了,使同一時間內只能有一個客戶端操做用戶狀態。不過加鎖咱們就須要考慮鎖粒度、死鎖等問題了,無疑添加了程序的複雜性,不利於維護。緩存
Redis 做爲一個極其高效的內存數據服務器,其命令執行速度極快,以前看過阿里雲 Redis 的一個壓測結果,執行效率能夠達到 10W寫QPS, 60W讀QPS,那麼,它的效率問題又來自何處呢?安全
答案是網絡,作 Web 的都知道,效率優化要從網絡作起,服務端又是優化代碼,又是優化數據庫,不如網絡鏈接的一次優化,而網絡優化最有效的就是減小請求數。咱們要知道執行一次內存訪問的耗時約是 100ns
,而不一樣機房之間來回一次約須要 500000ns
,其中的差距可想而知。服務器
Redis在單機內效率超高,但工業化部署總不會把服務器和 Redis 放在同一臺機器上,若是觸碰到效率瓶頸的話,那就是網絡。
典型場景就是咱們從 Redis 裏讀出一條數據,再使用這條數據作鍵,讀取另一條數據。這樣來來回回,便有兩次網絡往返。
致使這種問題的緣由就是 Redis 的普通命令沒有服務端計算的能力,沒法在服務器進行復合命令操做,雖然有 Redis 也提供了 pipeline
的特性,但它須要多個命令的請求和響應之間沒有依賴關係。想簡化多個相互依賴的命令就只能將數據拉回客戶端,由客戶端處理後再請求 Redis。
綜上,咱們要更高效更方便的使用 Redis 就須要本身「定製」一些命令了。
萬幸 Redis 內嵌了 Lua 執行環境,支持 Lua 腳本的執行,經過執行 Lua 腳本,咱們能夠把多個命令複合爲一個 Lua 腳本,經過 Lua 腳原本實現上文中提到的 Redis 命令的次序性和 Redis 服務端計算。
Lua 是一個簡潔、輕量、可擴展的腳本語言,它的特性有:
並且徹底不須要擔憂語法問題,Lua 的語法很簡單,分分鐘使用不成問題。
Redis 在 2.6 版本後,啓動時會建立 Lua 環境、載入 Lua 庫、定義 Redis 全局表格、存儲 redis.pcall
等 Redis 命令,以準備 Lua 腳本的執行。
一個典型的 Lua 腳本執行步驟以下:
交互時序如圖
雖然 Lua 腳本使用的是僞客戶端,但 Redis 處理它會跟普通客戶端同樣,也會將執行的 Redis 命令進行 rdb aof 主從複製等操做。
Lua 腳本的使用能夠經過 Redis 的 EVAL
和 EVALSHA
命令。
EVAL
適用於單次執行 Lua 腳本,執行腳本前會由腳本內容生成 sha1 校驗和,在函數表內查詢函數是否已定義,如未定義執行成功後 Redis 會在全局表裏緩存這個腳本的校驗和爲函數名,後續再次執行此命令就不會再建立新的函數了。
而要使用 EVALSHA
命令,就得先使用 SCRIPT LOAD
命令先將函數加載到 Redis,Redis 會返回此函數的 sha1 校驗和, 後續就能夠直接使用這個校驗和來執行命令了。
如下是使用上述命令的例子:
127.0.0.1:6379> EVAL "return 'hello'" 0 0 "hello" 127.0.0.1:6379> SCRIPT LOAD "return redis.pcall('GET', ARGV[1])" "20b602dcc1bb4ba8fca6b74ab364c05c58161a0a" 127.0.0.1:6379> EVALSHA 20b602dcc1bb4ba8fca6b74ab364c05c58161a0a 0 test "zbs"
EVAL 命令的原型是 EVAL script numkeys key [key ...] arg [arg ...]
,在 Lua 函數內部可使用 KEYS[N]
和 ARGV[N]
引用鍵和參數,須要注意 KEYS 和 ARGV 的參數序號都是從 1
開始的。
還須要注意在 Lua 腳本中,Redis 返回爲空時,結果是 false
,而 不是 nil
;
下面寫幾個 Lua 腳本的實例,用來介紹語法的,僅供參考。
// 使用: EVAL script 2 A B local tmpKey = redis.call('HGET', KEYS[1], KEYS[2]); return redis.call('GET', tmpKey);
// 使用: EVAL script 2 list count local list = {}; local item = false; local num = tonumber(KEYS[2]); while (num > 0) do item = redis.call('LPOP', KEYS[1]); if item == false then break; end; table.insert(list, item); num = num - 1; end; return list;
local elements = redis.call('ZRANK', KEYS[1], 0, KEY[2]); local detail = {}; for index,ele in elements do local info = redis.call('HGETALL', ele); table.insert(detail, info); end; return detail;
基本使用語法就是如此,更多應用就看各個具體場景了。
實現以外,還要一些東西要思考:
首先來總結一下 Redis 中 Lua 的使用場景:
使用 Lua 腳本,咱們還須要注意:
local
關鍵字。pipeline
,比 Lua 腳本方便。最近工做有了較大的變更,從業務到技術棧都跟原來徹底不一樣了,全部代碼和業務都脫離了本身掌控的感受真的很不爽,工做中全是「開局一個搜索引擎,語法全靠查」,天天還要熬到很晚熟悉新的東西,有點小累,果真換工做就是找罪受啊。不過走出溫馨區後的充實感也在提醒本身正在不停進步,倒也挺有成就感的。
剛接觸新的東西沒什麼沉澱,又不想寫一些《帶你三天精通 Java》這種水文,工做之餘的時間都被拿去補充工做須要的技術棧了,也沒時間研究些本身以爲有意思的東西,寫文章須要素材啊,爲了避免自砸招牌,最近可能會少更。。
關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦
支持一下我,博客一直在更新,歡迎 關注
。
參考: