最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫)

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

“讀后寫”

通常意義上我們說(shuō)讀后寫是指針對(duì)同一個(gè)數(shù)據(jù)的先讀后寫,且寫入的值依賴于讀取的值。

關(guān)于這個(gè)定義要拆成兩部分來(lái)看,一:同一個(gè)數(shù)據(jù);二:寫依賴于讀。(記住這個(gè)拆分,后續(xù)會(huì)用到,記為定義一、定義二)只有當(dāng)這兩部分都成立時(shí),讀后寫的問(wèn)題才會(huì)出現(xiàn)。

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

舉個(gè)栗子:

存在A、B兩個(gè)進(jìn)程,同時(shí)操作下面這段代碼:

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

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

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

對(duì)于這段代碼其實(shí)很好解決,尤其是如果key的值本身沒(méi)有意義的時(shí)候:

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

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

但是,如果這個(gè)key如果是有意義的呢,那就不能隨意改變,這種情況我們?cè)撛趺崔k?

詳細(xì)說(shuō)明

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

例子如下:
有一個(gè)活動(dòng),需要根據(jù)用戶連續(xù)參與天數(shù)進(jìn)行發(fā)獎(jiǎng),規(guī)則如下:

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

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

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

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

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

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

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

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

方法1:

通過(guò)使定義一或二不成立,從而使得讀后寫的問(wèn)題不存在。

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

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

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

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

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

方法2:

限制并發(fā)。

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

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

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

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

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

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

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

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

相關(guān)文章

  • Spring Boot 項(xiàng)目集成Redis的方式詳解

    Spring Boot 項(xiàng)目集成Redis的方式詳解

    這篇文章主要介紹了Spring Boot 項(xiàng)目集成Redis的方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧,需要的朋友可以參考下
    2021-08-08
  • Redis實(shí)現(xiàn)和數(shù)據(jù)庫(kù)的數(shù)據(jù)同步

    Redis實(shí)現(xiàn)和數(shù)據(jù)庫(kù)的數(shù)據(jù)同步

    本文介紹了Redis與傳統(tǒng)數(shù)據(jù)庫(kù)數(shù)據(jù)同步的幾種常見(jiàn)方法,包括CacheAside、WriteThrough、WriteBehind,以及如何通過(guò)分布式事務(wù)、樂(lè)觀鎖、數(shù)據(jù)過(guò)期策略和消息隊(duì)列來(lái)解決數(shù)據(jù)一致性問(wèn)題,每種方法都有其適用場(chǎng)景和優(yōu)缺點(diǎn),需要根據(jù)具體需求進(jìn)行選擇
    2025-01-01
  • Redis哨兵改集群的方法實(shí)現(xiàn)

    Redis哨兵改集群的方法實(shí)現(xiàn)

    Redis作為一個(gè)開源的鍵值存儲(chǔ)系統(tǒng),廣泛應(yīng)用于各種場(chǎng)景,如緩存和消息隊(duì)列,為了提高可用性和擴(kuò)展性,可以將Redis哨兵架構(gòu)改為集群架構(gòu),本文就來(lái)介紹一下,感興趣的可以了解一下
    2024-09-09
  • redis.conf中使用requirepass不生效的原因及解決方法

    redis.conf中使用requirepass不生效的原因及解決方法

    本文主要介紹了如何啟用requirepass,以及啟用requirepass為什么不會(huì)生效,從代碼層面分析了不生效的原因,以及解決方法,需要的朋友可以參考下
    2023-07-07
  • Redis安裝及基本數(shù)據(jù)類型

    Redis安裝及基本數(shù)據(jù)類型

    這篇文章主要介紹了Redis安裝及基本數(shù)據(jù)類型,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • Redis實(shí)現(xiàn)鎖續(xù)期的項(xiàng)目實(shí)踐

    Redis實(shí)現(xiàn)鎖續(xù)期的項(xiàng)目實(shí)踐

    本文介紹了使用Redis實(shí)現(xiàn)分布式鎖的續(xù)期,包括使用Lua腳本、Redlock算法和Redisson客戶端等方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • Redis之RedisTemplate配置方式(序列和反序列化)

    Redis之RedisTemplate配置方式(序列和反序列化)

    這篇文章主要介紹了Redis之RedisTemplate配置方式(序列和反序列化),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Web-ssrfme:redis 未授權(quán)訪問(wèn)攻擊的問(wèn)題解決

    Web-ssrfme:redis 未授權(quán)訪問(wèn)攻擊的問(wèn)題解決

    本文主要介紹了Web-ssrfme:redis 未授權(quán)訪問(wèn)攻擊的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • Redis哨兵機(jī)制的使用詳解

    Redis哨兵機(jī)制的使用詳解

    文章講解了Redis哨兵機(jī)制的基本原理、主庫(kù)和從庫(kù)自動(dòng)切換的過(guò)程、如何減少誤判、哨兵集群的組成和通信機(jī)制,以及哨兵在故障發(fā)生時(shí)如何選舉Leader進(jìn)行主從切換
    2025-01-01
  • Redis分布式鎖的使用和實(shí)現(xiàn)原理詳解

    Redis分布式鎖的使用和實(shí)現(xiàn)原理詳解

    這篇文章主要給大家介紹了關(guān)于Redis分布式鎖的使用和實(shí)現(xiàn)原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評(píng)論

五台县| 榆社县| 赤峰市| 盐津县| 呼玛县| 石泉县| 彩票| 莱芜市| 波密县| 景泰县| 丹凤县| 洪洞县| 铁岭市| 石家庄市| 凤台县| 海门市| 余姚市| 廊坊市| 台湾省| 夏河县| 浠水县| 错那县| 禹城市| 耒阳市| 遂宁市| 常山县| 且末县| 南澳县| 通道| 定安县| 微博| 南城县| 图木舒克市| 老河口市| 竹山县| 大兴区| 岳西县| 公安县| 会东县| 广宗县| 金湖县|