1. 程式人生 > >redis併發問題 && 分散式鎖

redis併發問題 && 分散式鎖

redis中的併發問題

使用redis作為快取已經很久了,redis是以單程序的形式執行的,命令是一個接著一個執行的,一直以為不會存在併發的問題,直到今天看到相關的資料,才恍然大悟~~

具體問題例項

有個鍵,假設名稱為myNum,裡面儲存的是阿拉伯數字,假設現在值為1,存在多個連線對myNum進行操作的情況,這個時候就會有併發的問題。假設有兩個連線linkAlinkB,這兩個連線都執行下面的操作,取出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中命令是滿足原子性的,因此在值為阿拉伯數字的時候,我可以將getset命令修改為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執行的。我們使用的時候,可能會出現併發問題,比如getset這一對

相關推薦

利用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分散式鎖實現的部落格,然而他們的實現卻有著各種各樣的問題,為