redis併發問題 && 分散式鎖
redis中的併發問題
使用redis作為快取已經很久了,redis是以單程序的形式執行的,命令是一個接著一個執行的,一直以為不會存在併發的問題,直到今天看到相關的資料,才恍然大悟~~
具體問題例項
有個鍵,假設名稱為myNum
,裡面儲存的是阿拉伯數字,假設現在值為1,存在多個連線對myNum
進行操作的情況,這個時候就會有併發的問題。假設有兩個連線linkA
和linkB
,這兩個連線都執行下面的操作,取出myNum
的值,+1,然後再存回去,看看下面的互動:
linkA get myNum => 1
linkB get myNum => 1
linkA set muNum => 2
linkB set myNum => 2
執行完操作之後,結果可能是2,這和我們預期的3不一致。
再看一個具體的例子:
<?php
require "vendor/autoload.php";
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
for ($i = 0; $i < 1000; $i++) {
$num = intval($client->get("name"));
$num = $num + 1;
$client->setex("name", $num, 10080);
usleep(10000);
}
設定name
初始值為0,然後同時用兩個終端執行上面的程式,最後name
的值可能不是2000,而是一個<2000的值,這也就證明了我們上面的併發問題的存在,這個該怎麼解決呢?
redis中的事務
redis中也是有事務的,不過這個事務沒有mysql中的完善,只保證了一致性和隔離性,不滿足原子性和永續性。
redis事務使用multi、exec命令
原子性,redis會將事務中的所有命令執行一遍,哪怕是中間有執行失敗也不會回滾。kill訊號、宿主機宕機等導致事務執行失敗,redis也不會進行重試或者回滾。
永續性,redis事務的永續性依賴於redis所使用的持久化模式,遺憾的是各種持久化模式也都不是持久化的。
隔離性,redis是單程序,開啟事務之後,會執行完當前連線的所有命令直到遇到exec命令,才處理其他連線的命令。
一致性,看了文件,覺得挺扯的,但是貌似說的沒有問題。
redis中的事務不支援原子性,所以解決不了上面的問題。
當然了redis還有一個watch
命令,這個命令可以解決這個問題,看下面的例子,對一個鍵執行watch,然後執行事務,由於watch的存在,他會監測鍵a
,當a
被修該之後,後面的事務就會執行失敗,這就確保了多個連線同時來了,都監測著a
,只有一個能執行成功,其他都返回失敗。
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> exec
1) (integer) 2
127.0.0.1:6379> get a
"2"
失敗時候的例子,從最後可以看出,test
的值被其他連線修改了:
127.0.0.1:6379> set test 1
OK
127.0.0.1:6379> watch test
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby test 11
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get test
"100"
我的問題如何解決
redis中命令是滿足原子性的,因此在值為阿拉伯數字的時候,我可以將get
和set
命令修改為incr
或者incrby
來解決這個問題,下面的程式碼開啟兩個終端同時執行,得到的結果是滿足我們預期的2000
。
<?php
require "vendor/autoload.php";
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
for ($i = 0; $i < 1000; $i++) {
$client->incr("name");
$client->expire("name", 10800);
usleep(10000);
}
@manzilu 提到的方法
評論中manzilu提到的方法查了下資料,確實可行,效果還不錯,這裡寫了個例子
<?php
require "vendor/autoload.php";
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
class RedisLock
{
public $objRedis = null;
public $timeout = 3;
/** * @desc 設定redis例項 * * @paramobj object | redis例項 */
public function__construct($obj){
$this->objRedis = $obj;
}
/** * @desc 獲取鎖鍵名 */
public function getLockCacheKey($key){
return "lock_{$key}";
}
/** * @desc 獲取鎖 * * @paramkey string | 要上鎖的鍵名 * @paramtimeout int | 上鎖時間 */
public function getLock($key,$timeout = NULL){
$timeout = $timeout ? $timeout : $this->timeout;
$lockCacheKey = $this->getLockCacheKey($key);
$expireAt = time() + $timeout;
$isGet = (bool)$this->objRedis->setnx($lockCacheKey, $expireAt);
if ($isGet) {
return $expireAt;
}
while (1) {
usleep(10);
$time = time();
$oldExpire = $this->objRedis->get($lockCacheKey);
if ($oldExpire >= $time) {
continue;
}
$newExpire = $time + $timeout;
$expireAt = $this->objRedis->getset($lockCacheKey, $newExpire);
if ($oldExpire != $expireAt) {
continue;
}
$isGet = $newExpire;
break;
}
return $isGet;
}
/** * @desc 釋放鎖 * * @paramkey string | 加鎖的欄位 * @paramnewExpire int | 加鎖的截止時間 * * @return bool | 是否釋放成功 */
public function releaseLock($key,$newExpire){
$lockCacheKey = $this->getLockCacheKey($key);
if ($newExpire >= time()) {
return $this->objRedis->del($lockCacheKey);
}
return true;
}
}
$start_time = microtime(true);
$lock = new RedisLock($client);
$key = "name";
for ($i = 0; $i < 10000; $i++) {
$newExpire = $lock->getLock($key);
$num = $client->get($key);
$num++;
$client->set($key, $num);
$lock->releaseLock($key, $newExpire);
}
$end_time = microtime(true);
echo "花費時間 : ". ($end_time - $start_time) . "\n";
執行shell
php
setnx.php & php setnx.php&
,最後會得到結果:
$ 花費時間 : 4.3004920482635
[2] + 72356 done php setnx.php
# root @ ritoyan-virtual-pc in ~/PHP/redis-high-concurrency [20:23:41]
$ 花費時間 : 4.4319710731506
[1] + 72355 done php setnx.php
同樣迴圈1w次,去掉usleep,使用incr直接進行增加,耗時在2s左右。
而獲取所得時候取消usleep,時間不但沒減少,反而增加了,這個usleep的設定要合理,免得程序做無用的迴圈
總結
看了這麼多,簡單的總結下,其實redis本事是不會存在併發問題的,因為他是單程序的,再多的command
都是one
by one執行的。我們使用的時候,可能會出現併發問題,比如get
和set
這一對
相關推薦
利用Redis實現分散式鎖 使用mysql樂觀鎖解決併發問題
寫在最前面 我在之前總結冪等性的時候,寫過一種分散式鎖的實現,可惜當時沒有真正應用過,著實的心虛啊。正好這段時間對這部分實踐了一下,也算是對之前填坑了。 分散式鎖按照網上的結論,大致分為三種:1、資料庫樂觀鎖; 2、基於Redis的分散式鎖;3.、基於ZooKeeper的分散式鎖; 關於樂觀鎖的實現其實
基於redis的分散式鎖 | 併發程式設計網
1 介紹 這篇博文講介紹如何一步步構建一個基於Redis的分散式鎖。會從最原始的版本開始,然後根據問題進行調整,最後完成一個較為合理的分散式鎖。 本篇文章會將分散式鎖的實現分為兩部分,一個是單機環境,另一個是叢集環境下的Redis鎖實現。在介紹分散式鎖的實現之前,先來了解下分散式鎖的一些資訊。 2 分散式
實現基於redis的分散式鎖並整合spring-boot-starter
文章目錄 概述 使用 1.導包 2.寫一個實現鎖功能的service 3.檢查redis的key 4.呼叫(鎖成功) 5.呼叫(鎖失敗) 實現
Redis之分散式鎖的原理
一、使用分散式鎖要滿足的幾個條件: 系統是一個分散式系統(關鍵是分散式,單機的可以使用ReentrantLock或者synchronized程式碼塊來實現,即單程序多個執行緒訪問的話) 共享資源(各個系統訪問同一個資源,資源的載體可能是傳統關係型資料庫或者NoSQL) 同步訪問
【redis】使用redisTemplate優雅地操作redis及使用redis實現分散式鎖
前言: 上篇已經介紹了redis及如何安裝和叢集redis,這篇介紹如何通過工具優雅地操作redis. Long Long ago,程式猿們還在通過jedis來操作著redis,那時候的猿類,一個個累的沒日沒夜,重複的造著輪子,忙得沒時間陪家人,終於有一天猿類的春天來了,spring家族的r
【轉】【Redis】分散式鎖的幾種使用方式(redis、zookeeper、資料庫)
https://blog.csdn.net/u010963948/article/details/79006572?utm_source=blogxgwz9 https://blog.csdn.net/qq_37606901/article/details/79569250?utm_source
基於redis的分散式鎖(轉)
基於redis的分散式鎖 1 介紹 這篇博文講介紹如何一步步構建一個基於Redis的分散式鎖。會從最原始的版本開始,然後根據問題進行調整,最後完成一個較為合理的分散式鎖。 本篇文章會將分散式鎖的實現分為兩部分,一個是單機環境,另一個是叢集環境下的Redis鎖實現。在介紹分散式鎖的實
基於Redis實現分散式鎖
背景 在很多網際網路產品應用中,有些場景需要加鎖處理,比如:秒殺,全域性遞增ID,樓層生成等等。大部分的解決方案是基於DB實現的,Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係。其次Redis提供一些命令SETNX,GETSET,可以方便
利用Redis實現分散式鎖
為什麼需要分散式鎖? 在傳統單體應用單機部署的情況下,可以使用Java併發相關的鎖,如ReentrantLcok或synchronized進行互斥控制。但是,隨著業務發展的需要,原單體單機部署的系統,漸漸的被部署在多機器多JVM上同時提供服務,這使得原單機部署情況下的併發控制鎖策略失效了,為了解決這個問
如何用 Redis 實現分散式鎖和超時情況處理
目前各種分散式的架構和微服務架構無處不在,在這種類似架構中處理併發和分散式併發問題,本場 Chat 就主要以 Redis 為例,使用分散式鎖的方式如何處理併發問題和避免超時情況的出現,主要從以下幾個方面講述: Redis 的 Setnx 命令是如何實現分散式鎖的; Setnx 的實現鎖的
dubbo 常用的基於redis的分散式鎖實現
小弟本著先會用在學習原理的原則 先用了dubbo 現在在實際業務中 因為分散式專案做了叢集,需要用的分散式鎖,就用到了基於redis的分散式鎖,廢話不多說,先來程式碼: package com.tiancaibao.utils; import org.slf4j.Logger
[翻譯]基於redis的分散式鎖
本篇翻譯自【redis.io/topics/dist… 在很多不同程序必須以相互排斥的方式競爭分片資源的情況下,分散式鎖是非常有用的原始功能。 有很多的實現和部落格都描述瞭如何基於Redis來實現分散式鎖管理器(DLM,Distributed Lock Manager)。有的使用了不同的途徑,但是大多都是
使用redis做分散式鎖
1、使用setnx命令。先看下官方文件http://redis.cn/commands/setnx.html 2、使用getset命令。先獲取,再set 實現案例: * create 2018-12-03 16:22 * <p> * desc **/ @Compo
ZooKeeper分散式鎖簡單實踐 利用Redis實現分散式鎖
寫在最前面 前幾周寫了篇 利用Redis實現分散式鎖 ,今天簡單總結下ZooKeeper實現分散式鎖的過程。其實生產上我只用過Redis或者資料庫的方式,之前還真沒了解過ZooKeeper怎麼實現分散式鎖。這周簡單寫了個小Demo,更堅定了我繼續使用Redis的信心了。 ZooKeep
Redis之分散式鎖
一、加鎖原因 二、原子操作 三、分散式鎖 四、分散式鎖常見問題 一、加鎖原因 在一些比較高併發的業務場景,經常聽到通過加鎖的方法實現執行緒安全。 下面簡單介紹一下 1.1 加鎖方式 資料庫鎖 資料庫本身提供了鎖機制,
分散式鎖-使用Redis實現分散式鎖
使用Redis實現分散式鎖 關於分散式鎖的實現,我的前一篇文章講解了如何使用Zookeeper實現分散式鎖。關於分散式鎖的背景此處不再做贅述,我們直接討論下如何使用Redis實現分散式鎖。 關於Redis,筆主不打算做長篇大論的介紹,只介紹下Redis優秀的特性
REDIS 學習(10)流程圖解使用redis實現分散式鎖
redis作為集中式快取,可以通過它來實現分散式鎖。 首先用到的redis操作有: setnx key value: 當key不存在的時候生效並返回1,當已經有此key的時候返回0 getset key value:
RedLock演算法-使用redis實現分散式鎖服務
譯自Redis官方文件 在多執行緒共享臨界資源的場景下,分散式鎖是一種非常重要的元件。 許多庫使用不同的方式使用redis實現一個分散式鎖管理。 其中有一部分簡單的實現方式可靠性不足,可以通過一些簡單的修改提高其可靠性。 這篇文章介紹了一種指導性的redis分散式鎖演算法RedLock,RedL
Redis實現分散式鎖(spring定時任務叢集應用Redis分散式鎖)
之前2片文章介紹了 描述: 不管用不用動態執行,單機服務都是沒有問題的,但是如果服務是叢集模式下,那麼一個任務在每臺機器都會執行一次,這肯定不是我們需要的,我們要實現的是整個叢集每次只有一個任務執行成功,但是spring
【Redis實現分散式鎖】Redis實現分散式鎖
前言 分散式鎖一般有三種實現方式:1. 資料庫樂觀鎖;2. 基於Redis的分散式鎖;3. 基於ZooKeeper的分散式鎖。本篇部落格將介紹第二種方式,基於Redis實現分散式鎖。雖然網上已經有各種介紹Redis分散式鎖實現的部落格,然而他們的實現卻有著各種各樣的問題,為