目錄php
參考: http://redis.cn/topics/transactions.htmlhtml
事務是一個單獨的隔離操做:事務中的全部命令都會序列化、按順序地執行。事務在執行的過程當中,不會被其餘客戶端發送來的命令請求所打斷。git
事務是一個原子操做:事務中的命令要麼所有被執行,要麼所有都不執行。github
redis事務是一組命令的集合。多組命令進入到等待執行的事務隊列中,執行exec命令告訴redis將等待執行的事務隊列中的全部命令,按順序執行,返回值就是這些命令組成的列表。web
Redis 事務能夠一次執行多個命令, 具備下列保證:redis
一個事務從開始到執行會經歷如下三個階段:算法
事務中的錯誤:數據庫
從 Redis 2.6.5 開始,服務器會對命令入隊失敗的狀況進行記錄,並在客戶端調用 EXEC 命令時,拒絕執行並自動放棄這個事務緩存
在 EXEC 命令執行以後所產生的錯誤, 並無對它們進行特別處理: 即便事務中有某個/某些命令在執行時產生了錯誤, 事務中的其餘命令仍然會繼續執行安全
如:
127.0.0.1:6379> multi OK 127.0.0.1:6379> set a 3 QUEUED 127.0.0.1:6379> lpop a QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
redis 事務入隊只會檢查語法錯誤,對於exec後執行錯誤,沒有回滾措施。並且在事務中沒法在客戶端作查詢判斷,只會獲得queued,沒法進行業務數據判斷,也是很坑。
原子性
一個事務是一個不可分割的最小工做單位,要麼都成功要麼都失敗。
原子操做是指你的一個業務邏輯必須是不可拆分的.好比你給別人轉錢,你的帳號扣錢,別人的帳號增長錢。
單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增長任何維持原子性的機制,因此 Redis 事務的執行並非原子性的。
包含5個命令 MULTI、EXEC、DISCARD、WATCH、UNWATCH。
DISCARD 取消事務,放棄執行事務塊內的全部命令。
EXEC 執行全部事務塊內的命令。
MULTI 標記一個事務塊的開始。
UNWATCH 取消 WATCH 命令對全部 key 的監視。
WATCH key [key ...] 監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。
樂觀的認爲數據不會出現衝突,使用version或timestamp來記錄判斷。樂觀鎖的優勢開銷小,不會出現鎖衝突。
可利用watch命令監聽key,實現樂觀鎖,來保證不會出現衝突,應用場景好比秒殺來防止超賣。
秒殺僞代碼以下:
WATCH 鎖定量 MULTI incr 鎖定量 if 鎖定量 <= 庫存量 減庫存 EXEC
瞭解下相關命令
瞭解下搶購模擬代碼:
<?php namespace app\controllers; use Yii; use yii\web\Controller; use app\modules\Common; /** * 模擬搶購處理 * Class ShopController * @package app\controllers */ class ShopController extends Controller { public $goods = 'huawei P20'; //初始化數據 public function actionInit(){ $redis = Yii::$app->redis; $redis->set('goodNums',100); //設置庫存 $redis->del('order'); //清空搶購訂單 die('success'); } //悲觀鎖 //setnx 實現,有個問題 expire失敗(1.人爲錯誤;2.redis崩了)了,這個鎖就持久化,一直被鎖了 public function actionBuy(){ $userId = mt_rand(1,99999999); $goods = $this->goods; $redis = Yii::$app->redis; $lock = $goods; try { $inventory['num'] = $redis->get('goodNums'); if($inventory['num']<=0){ self::removeLock($lock); throw new \Exception('活動結束'); } if( $redis->setnx($lock,1) ){ $redis->expire($lock,60);//設置過時時間,防止死鎖 //業務處理 減庫存,建立訂單 $redis->decr('goodNums'); $redis->sadd('order',$userId); //todo 實際業務處理時間不可控,因此須要調整過時時間,在業務處理完進行剩餘生命時間的判斷,沒找到回滾業務 $this->removeLock($lock); }else{ throw new \Exception($userId.' 搶購失敗'); } Common::addLog('shop.log',$userId.' 搶購成功'); }catch (\Exception $e){ $this->removeLock($lock); Common::addLog('shop.log',$e->getMessage()); } die('success'); } //刪除鎖 protected function removeLock( $lock ){ $redis = Yii::$app->redis; return $redis->del($lock); } //悲觀鎖 //incr 解決expire失效,解鎖 public function actionBuy2(){ $userId = mt_rand(1,99999999); $goods = $this->goods; $redis = Yii::$app->redis; $lock = $goods; try { $inventory['num'] = $redis->get('goodNums'); if($inventory['num']<=0){ $this->removeLock($lock); throw new \Exception('活動結束'); } $lockset = $redis->incr($lock); if( !$lockset ){ throw new \Exception($userId.' 搶購失敗'); } if($lockset==1){ $redis->expire($lock,60);//設置過時時間,防止死鎖 //業務處理 減庫存,建立訂單 $redis->decr('goodNums'); $redis->sadd('order',$userId); $this->removeLock($lock); } //鎖的數量大於1而且沒有設置過時時間,失敗處理 if( $lockset>1 && $redis->ttl($lock)===-1 ){ $this->removeLock($lock); throw new \Exception($userId.' 搶購失敗'); } Common::addLog('shop.log',$userId.' 搶購成功'); }catch (\Exception $e){ $this->removeLock($lock); Common::addLog('shop.log',$e->getMessage()); } die('success'); } //悲觀鎖 //set key value [expiration EX seconds|PX milliseconds] [NX|XX] 原子命令(redis必須大於2.6版本) public function actionBuy3(){ $userId = mt_rand(1,99999999); $goods = $this->goods; $redis = Yii::$app->redis; $lock = $goods; try { $inventory['num'] = $redis->get('goodNums'); if($inventory['num']<=0){ $this->removeLock($lock); throw new \Exception('活動結束'); } $lockset = $redis->set($lock,1,'EX',60,'NX'); if( !$lockset ){ throw new \Exception($userId.' 搶購失敗'); } if($lockset==1){ //業務處理 減庫存,建立訂單 $redis->decr('goodNums'); $redis->sadd('order',$userId); $this->removeLock($lock); } Common::addLog('shop.log',$userId.' 搶購成功'); }catch (\Exception $e){ $this->removeLock($lock); Common::addLog('shop.log',$e->getMessage()); } die('success'); } # 樂觀鎖 public function actionBuy4(){ $userId = mt_rand(1,99999999); $goods = $this->goods; $redis = Yii::$app->redis; $lock = $goods; try { $inventory['num'] = $redis->get('goodNums'); if($inventory['num']<=0){ throw new \Exception('活動結束'); } $redis->watch($lock); $redis->multi(); //todo:這裏還須要從新判斷下庫存,不然會出現超發,高併發狀況下$inventory['num']確定會出現同時讀取一個值;爲了方便測試,沒寫db操做 //redis事務是將命令放入隊列中,沒法取goodNums來判斷庫存是否結束,此處使用數據庫來判斷庫存合理 //業務處理 減庫存,建立訂單 $redis->decr('goodNums'); $redis->sadd('order',$userId); $redis->exec(); Common::addLog('shop.log',$userId.' 搶購成功'); }catch (\Exception $e){ Common::addLog('shop.log',$e->getMessage()); } die('success'); } # 隊列實現,不作詳述 }
服務器訪問併發比較大,無效訪問頻繁,好比說頻繁請求接口,爬蟲頻繁訪問服務器,搶購瞬時請求過大,咱們須要限流處理。
限流:對訪問來源計數,超過設定次數,設置過時時間,提醒訪問頻繁,稍後再試
limits=500 #設置1秒內限制次數50 if EXISTS userid return '訪問頻繁,鎖定時間剩餘(ttl userid)秒' if userid_count_time > limits exprice userid,3600 return '訪問頻繁,稍後再試' else MUlTI incr userid_count_time # 對用戶每秒的請求進行原子遞增計數 exprice userid_count_time , 60 EXEC //使用事務的目的是避免執行錯誤中斷,userid_count_time持久化到磁盤,高併發下這個頗有必要
計數器限流,缺點也很大,可能會超過限制數。相比下,高併發 漏桶算法、令牌桶算法更適合作限流,此處不作深究。
運用數據格式list,lpush、rpop就能夠入隊、出隊,可是會有個問題 假設出隊的業務執行發生錯誤,數據會不會所以丟失,因此須要確保出隊時確實被消費了,能夠參考下面僞代碼處理:
while(val = lrange(list,0,-1)) try{ //對val這條數據的業務代碼處理 rpop(list) }catch(Exception e){ //記錄錯誤,通知programmer處理 break; }
參考下lrange語法
服務器中的非空數據庫以及數據庫中的健值對統稱數據庫狀態。
redis是內存數據庫,數據庫狀態存在內存中,一旦服務器崩掉,服務器狀態就會消失不見,因此須要將數據庫狀態存與磁盤文件中。
按期的將數據庫狀態保存在一個RDB快照文件中,RDB文件是一個通過壓縮的二進制文件,經過該文件可還原生成RDB文件時的數據庫狀態。
觸發方式:手動和自動
RDB 文件的建立和載入
redis命令:SAVE、BGSAVE
SAVE會阻塞Redis服務器進程,直到RDB文件建立完畢爲止,在服務器進程阻塞期間,服務器不能處理任何命令請求。
BGSAVE命令會派生出一個子進程,而後由子進程負責建立RDB文件,服務器進程(父進程)繼續處理命令請求。
自動觸發
redis.conf 中配置
save 900 1 # 表示900 秒內若是至少有 1 個 key 的值變化,則保存 save 300 10 # 表示300 秒內若是至少有 10 個 key 的值變化,則保存 save 60 10000 # 表示60 秒內若是至少有 10000 個 key 的值變化,則保存
「save m n」。表示m秒內數據集存在n次修改時,自動觸發BGSAVE。
僞代碼
def SAVE(): #建立RDB文件 rdbSave() def BGSAVE(): #建立子進程 pid = fork() if pid == 0: #子進程負責建立RDB文件 rdbSave() #完成以後向父進程發送信號 signal_parent() elif pid > 0: #父進程繼續處理命令請求,並經過輪詢等待子進程的信號 handle_request_and_wait_signal() else: #處理出錯狀況 handle_fork_error()
AOF持久化功能實現分爲命令追加(append)、文件寫入(wirte)、文件同步(sync)三個步驟。
每個寫命令都經過write函數追加到 appendonly.aof 中,配置方式:啓動 AOF 持久化的方式
僞代碼
def eventLoop(): while True: #處理文件事件,接收命令請求以及發送命令回覆 #處理命令請求時可能會有新內容被追加到 aof_buf緩衝區中 processFileEvents() #處理時間事件 processTimeEvents() #考慮是否要將 aof_buf中的內容寫入和保存到 AOF文件裏面 flushAppendOnlyFile()
服務器在執行一個寫命令以後,會以協議格式將執行的寫命令追加到服務器狀態的aof_buf緩衝區的末尾。
操做系統中,用戶調用write函數寫入,將一些數據寫入到文件時,爲了提升存儲的效率,操做系統一般會將數據暫時保存在一個內存緩衝區裏面,緩衝區滿了或者超過指定時間,真正將緩衝區數據存儲到磁盤,提升了效率,可是若是停機,也會形成緩衝區內的數據丟失,
系統提供了fsync、fdatasync兩個同步函數,會強制讓操做系統當即將緩衝區的數據寫入硬盤,確保數據的安全性。
AOF持久化配置 redis.conf :
appendonly yes #開啓AOF appendfilename "appendonly.aof" #默認存儲路徑 # appendfsync 設置持久化策略,三種: #appendfsync always # 每次有數據修改發生時AOF緩衝區數據都會寫入AOF文件並同步 (效率最慢但安全性最高) appendfsync everysec # 每秒鐘寫入AOF文件並同步一次,該策略爲AOF的缺省策略。(效率高,即使丟失數據只會丟失1秒的數據) #appendfsync no # 緩衝區的內容寫入到AOF文件,但並不會對AOF文件進行同步,什麼時候同步由操做系統來決定(效率高,丟失上一次同步到這一次的所有AOF數據)
appendonly yes開啓 AOF 以後,Redis 每執行一個修改數據的命令,都會把它添加到 AOF 文件中,當 Redis 重啓時,將會讀取 AOF 文件進行「重放」以恢復到 Redis 關閉前的最後時刻。
使用 AOF 持久化會讓 Redis 變得很是耐久(much more durable):你能夠設置不一樣的 fsync 策略,好比無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,
Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,因此主線程能夠繼續努力地處理命令請求)。
對於相同的數據集來講,AOF 文件的體積一般要大於 RDB 文件的體積。根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB。 在通常狀況下, 每秒 fsync 的性能依然很是高, 而關閉 fsync 可讓 AOF 的速度和 RDB 同樣快,
即便在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 能夠提供更有保證的最大延遲時間(latency)。
隨着服務器時間的流逝,AOF文件的體積會愈來愈大。
redis能夠看成數據庫來存貯數據,如何解決排序查詢呢?
SORT命令:
keys *
雖然其模糊匹配功能使用很是方便也很強大,在小數據量狀況下使用沒什麼問題,數據量大會致使 Redis 鎖住及 CPU 飆升,在生產環境建議禁用或者重命名!
flushdb
刪除 Redis 中當前所在數據庫中的全部記錄,而且此命令從不會執行失敗
flushall
刪除 Redis 中全部數據庫中的全部記錄,不僅是當前所在數據庫,而且此命令從不會執行失敗。
config
客戶端可修改 Redis 配置。
參考:
https://blog.csdn.net/a169388842/article/details/82838818
# 綁定ip,指定地址域鏈接 bind 192.168.1.100 10.0.0.1