欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

高并發(fā)下Redis如何保持數據一致性(避免讀后寫)

 更新時間:2022年03月18日 10:03:39   作者:grey256  
本文主要介紹了高并發(fā)下Redis如何保持數據一致性(避免讀后寫),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

“讀后寫”

通常意義上我們說讀后寫是指針對同一個數據的先讀后寫,且寫入的值依賴于讀取的值。

關于這個定義要拆成兩部分來看,一:同一個數據;二:寫依賴于讀。(記住這個拆分,后續(xù)會用到,記為定義一、定義二)只有當這兩部分都成立時,讀后寫的問題才會出現。

在項目中,當面對較多的并發(fā)時,使用redis進行讀后寫操作,是非常容易出問題的,常常使得程序不具備魯棒性,bug很難穩(wěn)定復現(得到的值往往跟并發(fā)數有關)。

舉個栗子:

存在A、B兩個進程,同時操作下面這段代碼:

$objRedis = new Redis();
//獲取key
$intNum ? = $objRedis->get('key');
if ($intNum == 1) {
? ? //如果key的值為1,則給key加1
? ? $bolRet ? = $objRedis->incr('key');

? ? //do something...
}
  • 如果A進程先get到了key,而此時key的值為1;
  • 同時,B進程此時也get到了key,同樣key值為1;
  • B進程運行的快,先進行了if判斷,發(fā)現滿足條件,于是對key進行了累加操作,此時key變成了2;
  • A進程對B進程修改了key這個操作茫然無知,所以當它繼續(xù)運行走到if判斷條件時,由于它get的key是1,因此也滿足條件,于是A進程也會對key進行累加操作,但是由于key已經被B進行累加過一次(key的值已經是2),因此當A再累加,key最終就變成了3。

實際上,代碼的本意是希望key為1時執(zhí)行一些操作,但當出現并發(fā)的時候,這段代碼很難滿足期望!
如果這樣的代碼出現在抽獎、秒殺等活動中,那就只能期望公司不會讓個人承擔損失了(汗)。
以上就是一個比較簡單的讀后寫的問題。

對于這段代碼其實很好解決,尤其是如果key的值本身沒有意義的時候:

$objRedis = new Redis();
//獲取key
$intNum   = $objRedis->incr('key');
if ($intNum == 1) {
    //do something...
}

以上代碼使用了incr原子型操作,限制了并發(fā)(相當于加鎖),就不會出現上述問題了。

但是,如果這個key如果是有意義的呢,那就不能隨意改變,這種情況我們該怎么辦?

詳細說明

下面我舉一個更具體的例子,然后從這個例子出發(fā)來拋幾塊磚(個人想的解決辦法),希望引出更多的玉。

例子如下:
有一個活動,需要根據用戶連續(xù)參與天數進行發(fā)獎,規(guī)則如下:

  • 連續(xù)參與1-3天,每天額外獎勵10金幣;
  • 連續(xù)參加4-7天,每天額外獎勵50金幣;
  • 連續(xù)參加8-15天,每天額外獎勵100金幣;
  • 連續(xù)參加15天以上,每天額外獎勵200金幣;

簡單思路(使用讀后寫):

對每個用戶使用一個hash存儲,其中一個字段表示連續(xù)天數(‘sequence’),另一個字段存儲最近參與日期(‘lastdate’)。
精簡版代碼如下:

$objRedis = new Redis();
//根據用戶ID,生成redis的key
$strRedisKey = 'activity_' . $intUid;
//從Hash中獲取最近參與時間
$mixDate ? ? = $objRedis->HGET($strRedisKey, 'lastdate');

$intLastDate ?= intval($mixDate);
$intYesterDay = intval(date("Ymd", strtotime("-1 day")));
$intCurrDate ?= intval(date('Ymd'));
$intNum ? ? ? = 0;//連續(xù)天數
if ($intCurrDate == $intLastDate) {
? ? //今天已經參與過,直接跳過
? ? return;
} elseif ($intLastDate == $intYesterDay) {
? ? //日期連續(xù),增加連續(xù)天數
? ? $intNum = $objRedis->HINCRBY($strRedisKey, 'sequence', 1);
? ? if ($intNum > 0) {
? ? ? ? //將最近參與時間設置為當天
? ? ? ? $objRedis->HSET($strRedisKey, 'lastdate', $intCurrDate);
? ? }
} else {
? ? //日期不連續(xù),設置連續(xù)天數為1,最近參與時間為當天
? ? $intNum = 1;
? ? $objRedis->HMSET($strRedisKey, 'sequence', $intNum, 'lastdate', $intCurrDate);
}

//do something(根據$intNum發(fā)放金幣等操作)...

 很明顯,這也是一個讀后寫的方法——先獲取最近參與日期,再根據條件修改最近參與日期(定義一二都被滿足了),這個方法在高并發(fā)的時候很有可能會導致連續(xù)天數的錯誤累加。

那么,這個例子如何避免讀后寫呢?
方法其實有很多,這里先舉兩個:

方法1:

通過使定義一或二不成立,從而使得讀后寫的問題不存在。

按日期進行存儲——將redis的key按日期進行劃分,比如用戶ID為123的key從redis_123變?yōu)閞edis_123_20171225。這樣的話,其實相當于避免了讀寫同一份數據。
代碼如下:

$objRedis = new Redis();
//根據用戶ID,生成redis的key
$strCurrRedisKey = 'activity_' . $intUid . '_' . date('Ymd');
//從Hash中獲取最近參與時間
$mixNum ? ? ? ? ?= $objRedis->GET($strCurrRedisKey);

$intNum = 0;//連續(xù)天數
if (is_null($mixNum)) {
? ? //當天還沒被處理過,查找前一天的記錄
? ? $strLastRedisKey = 'activity_' . $intUid . '_' . intval(date("Ymd", strtotime("-1 day")));
? ? $mixLastNum ? ? ?= $objRedis->GET($strLastRedisKey);
? ? //計算連續(xù)天數
? ? $intNum = intval($mixLastNum) + 1;
? ? //設置當天的連續(xù)天數,并給這個key一周的過期時間
? ? $objRedis->SETEX($strCurrRedisKey, 604800, $intNum);
} else {
? ? //今天已經操作了,直接返回
? ? return;
}

//do something(根據$intNum發(fā)放金幣等操作)...

這個思路是通過讀昨天的數據后修改今天的數據,來達到避免對同一份數據讀后寫的目的的(使得定義一不成立,從而消除讀后寫的問題)。
這里雖然在最開始的時候也讀取了今天的數據,但由于最后對今天的數據的修改只依賴于昨天的數據(今天的數據=昨天數據+1),而不依賴于讀到的今天的數據,所以也就沒有讀后寫的問題了(所以也可以看作是使定義二不成立)。

方法2:

限制并發(fā)。

方法一是使定義一或二不成立,從而解決讀后寫的問題。這里就不再在定義一或二上做文章了,下面換一個思路。
讀后寫歸根結底其實還是并發(fā)下才會出現問題。因此這里介紹一個釜底抽薪的方法,限制并發(fā)!
一說到限制并發(fā),可能第一反應就是加鎖,自己在代碼中加鎖當然是一種辦法,但是相對來說成本還是高一些(如何加鎖可以參考我之前的一篇博文《用redis實現悲觀鎖》),這里就不再贅述。
其實讀后寫,最基本也是最簡單的拆分方式是——讀和寫,那么釜底抽薪的辦法就是能不能不讀,只寫!
實現思路就是只用一個key來存儲連續(xù)天數+當前日期,然后使用原子型操作來寫。一說到原子型操作,在redis中第一反應就是incr。那么順著這個思路,我們怎么利用incr來操作呢?
其實關鍵是設計一個存儲方式,滿足既能存放連續(xù)天數,又能存放當前日期,還能使得這個值多次incr而不影響本身數據。這里說下我的設計方法:將一個12位的整數值看作是一個分段有意義的值,連續(xù)天數用最高的2位表示(因業(yè)務自定義),中間8位代表日期(如20171225),最后2位用于計數(無實際意義),比如:

將012017122523拆分成:
01|20171225|23
分別代表:連續(xù)天數|最近參與日期|計數

其中計數,這個字段是為了在利用incr時限制并發(fā)的。
示意代碼如下:

$objRedis ? ?= new Redis();
//根據用戶ID,生成redis的key
$strRedisKey = 'activity_' . $intUid;
//從Hash中獲取最近參與時間
$intVal ? ? ? = intval($objRedis->INCR($strRedisKey));
$intCnt ? ? ? = $intVal % 100;//獲取計數
$intLastDate ?= ($intVal - $intCnt) % 100000000;//獲取最近參與日期
$intNum ? ? ? = intval($intVal / 10000000000);//連續(xù)天數
$intYesterDay = intval(date("Ymd", strtotime("-1 day")));//昨天的日期
$intCurrDate ?= intval(date('Ymd'));//今天的日期

if ($intCurrDate == $intLastDate) {
? ? //今天已經操作了
? ? if ($intCnt > 90) {
? ? ? ? //重置計數,防止超過給定范圍(最大99)
? ? ? ? $objRedis->SET($strRedisKey, $intNum * 10000000000 + $intCurrDate * 100 + 1);
? ? }
? ? return;
} elseif ($intYesterDay == $intLastDate) {
? ? //日期連續(xù),計算連續(xù)天數
? ? $intNum += 1;
} else {
? ? //日期不連續(xù),重置連續(xù)天數
? ? $intNum = 1;
}
//更新連續(xù)天數及當前日期
$objRedis->SET($strRedisKey, $intNum * 10000000000 + $intCurrDate * 100 + 1);

//do something(根據$intNum發(fā)放金幣等操作)...

只要涉及到數據讀、寫,就會有數據一致性問題,mysql中可以通過事務、鎖(FOR UPDATE)等來保證一致性,而redis也可以根據業(yè)務需求設計不同的讀寫方式來實現(redis的事務真心不太好用)。這里拋出兩種redis克服讀后寫問題的思路,希望能起到引玉的作用!

到此這篇關于高并發(fā)下Redis如何保持數據一致性(避免讀后寫)的文章就介紹到這了,更多相關Redis 數據一致性內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Redis過期鍵與內存淘汰策略深入分析講解

    Redis過期鍵與內存淘汰策略深入分析講解

    因為redis數據是基于內存的,然而內存是非常寶貴的資源,然后我們就會對一些不常用或者只用一次的數據進行存活時間設置,這樣才能提高內存的使用效率,下面這篇文章主要給大家介紹了關于Redis中過期鍵與內存淘汰策略,需要的朋友可以參考下
    2022-11-11
  • Centos 7 如何安裝Redis(推薦)

    Centos 7 如何安裝Redis(推薦)

    這篇文章主要介紹了Centos 7 如何安裝Redis,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • CentOS 6.6下Redis安裝配置記錄

    CentOS 6.6下Redis安裝配置記錄

    這篇文章主要介紹了CentOS 6.6下Redis安裝配置記錄,本文給出了安裝需要的支持環(huán)境、安裝redis、測試Redis、配置redis等步驟,需要的朋友可以參考下
    2015-03-03
  • Linux系統(tǒng)下安裝Redis數據庫過程

    Linux系統(tǒng)下安裝Redis數據庫過程

    大家好,本篇文章主要講的是Linux系統(tǒng)下安裝Redis數據庫過程,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • Redis優(yōu)化經驗總結(必看篇)

    Redis優(yōu)化經驗總結(必看篇)

    下面小編就為大家?guī)硪黄猂edis優(yōu)化經驗總結(必看篇)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • 完美解決linux上啟動redis后配置文件未生效的問題

    完美解決linux上啟動redis后配置文件未生效的問題

    今天小編就為大家分享一篇完美解決linux上啟動redis后配置文件未生效的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Redis 5.05 單獨模式安裝及配置方法

    Redis 5.05 單獨模式安裝及配置方法

    這篇文章主要介紹了Redis 5.05 單獨模式安裝,文中通過代碼給大家介紹了Redis 5.0.5 單節(jié)點 安裝配置方法,需要的朋友可以參考下
    2019-10-10
  • Redis核心原理詳細解說

    Redis核心原理詳細解說

    這篇文章主要介紹了Redis核心原理詳細解說,redis利用epoll實現IO多路復用,將連接信息和事件放到隊列中,依次放到文件事件分派器,事件分派器將事件分發(fā)給事件處理器
    2022-07-07
  • Redis教程(十一):虛擬內存介紹

    Redis教程(十一):虛擬內存介紹

    這篇文章主要介紹了Redis教程(十一):虛擬內存介紹,本文講解了虛擬內存簡介、應用場景和配置方法等內容,需要的朋友可以參考下
    2015-04-04
  • Redis過期數據的刪除策略詳解

    Redis過期數據的刪除策略詳解

    Redis 是一個kv型數據庫,我們所有的數據都是存放在內存中的,但是內存是有大小限制的,不可能無限制的增量,這篇文章主要介紹了Redis過期數據的刪除策略,需要的朋友可以參考下
    2023-08-08

最新評論