摘要:當(dāng)一個(gè)事務(wù)執(zhí)行完畢之后,才會(huì)處理其他客戶端的命令。延遲執(zhí)行事務(wù)有助于提升性能因?yàn)樵趫?zhí)行事務(wù)的過(guò)程中,會(huì)延遲執(zhí)行已入隊(duì)的命令直到客戶端發(fā)送命令為止。
Redis的基本事務(wù)(basic transaction)需要用到MULTI命令和EXEC命令,這種事務(wù)可以讓一個(gè)客戶端在不被其他客戶端打斷的情況下執(zhí)行多個(gè)命令。被NULTI命令和EXEC命令包圍的所有命令會(huì)一個(gè)接一個(gè)地執(zhí)行,直到所有命令都執(zhí)行完畢為止。當(dāng)一個(gè)事務(wù)執(zhí)行完畢之后,Redis才會(huì)處理其他客戶端的命令。
當(dāng)Redis從一個(gè)客戶端那里接收到MULTI命令時(shí),Redis會(huì)將這個(gè)客戶端之后發(fā)送的所有命令都放入到一個(gè)隊(duì)列里面,直到這個(gè)客戶端發(fā)送EXEC命令為止,然后Redis就會(huì)在不被打斷的情況下,一個(gè)接一個(gè)地執(zhí)行存儲(chǔ)在隊(duì)列里面的命令。從語(yǔ)義上來(lái)說(shuō),Redis事務(wù)在Python客戶端上面是由流水線(pipeline)實(shí)現(xiàn)的:對(duì)連接對(duì)象調(diào)用pipeline()方法將創(chuàng)建一個(gè)事務(wù),在一切正常的情況下,客戶端會(huì)自動(dòng)地使用MULTI和EXEC包裹起用戶輸入的多個(gè)命令。為了減少Redis與客戶端之間的通信往返次數(shù),提升執(zhí)行多個(gè)命令時(shí)的性能,Python的Redis客戶端會(huì)存儲(chǔ)起事務(wù)包含的多個(gè)命令,然后在事務(wù)執(zhí)行時(shí)一次性地將所有命令都發(fā)送給Redis。
在Python中使用事務(wù)來(lái)處理命令的并行執(zhí)行問(wèn)題:
def trans(): pipeline = conn.pipeline() # 創(chuàng)建事務(wù)型流水線對(duì)象 pipeline.incr("trans:") # 把針對(duì)"trans:"計(jì)數(shù)器的自增操作放入隊(duì)列 time.sleep(.1) # 等待100ms pipeline.incr("trans:", -1) # 把針對(duì)"trans:"計(jì)數(shù)器的自減操作放入隊(duì)列 print pipeline.execute()[0] # 執(zhí)行被事務(wù)包裹的命令,并打印自增操作的執(zhí)行結(jié)果 if 1: for i in xrange(3): # 啟動(dòng)3個(gè)線程來(lái)執(zhí)行被事務(wù)包裹的自增、休眠和自減3個(gè)操作 threading.Thread(target=trans).start() time.sleep(.5) # 等待500ms,讓操作有足夠的時(shí)間完成 # 打印結(jié)果: 1 1 1
Redis要在接收到EXEC命令之后,才會(huì)執(zhí)行哪些位于MULTI和EXEC之間的入隊(duì)命令。
上述這種簡(jiǎn)單的事務(wù)在EXEC命令被調(diào)用之前不會(huì)執(zhí)行任何實(shí)際操作,所以用戶將沒(méi)辦法根據(jù)讀取到的數(shù)據(jù)來(lái)做決定。這種方式無(wú)法以一致的形式讀取數(shù)據(jù)將導(dǎo)致某一類型的問(wèn)題變得難以解決,除此之外,因?yàn)樵诙鄠€(gè)事務(wù)同時(shí)處理同一個(gè)對(duì)象時(shí)通常需要用到二階提交(two-phase commit), 所以如果事務(wù)不能以一致的形式讀取數(shù)據(jù),那么二階提交將無(wú)法實(shí)現(xiàn),從而導(dǎo)致一些原本可以成功執(zhí)行的事務(wù)執(zhí)行失敗。
延遲執(zhí)行事務(wù)有助于提升性能
因?yàn)镽edis在執(zhí)行事務(wù)的過(guò)程中,會(huì)延遲執(zhí)行已入隊(duì)的命令直到客戶端發(fā)送EXEC命令為止。包括python客戶端在內(nèi)的很多Redis客戶端都會(huì)等到事務(wù)包含的所有命令都出現(xiàn)了之后,才一次性地將MULTI命令、要在事務(wù)中執(zhí)行的一系列命令,以及EXEC命令全部發(fā)送給Redis,然后等待直到接收到所有命令的回復(fù)為止。這種“一次性發(fā)送多個(gè)命令,然后等待所有回復(fù)出現(xiàn)”的做法通常被稱為流水線(pipeline),它可以通過(guò)減少客戶端與Redis服務(wù)器之間的網(wǎng)絡(luò)通信次數(shù)來(lái)提升Redis在執(zhí)行多個(gè)命令時(shí)的性能。
在用戶使用WATCH命令對(duì)鍵進(jìn)行監(jiān)視之后,直到用戶執(zhí)行EXEC命令的這段時(shí)間,如果有其他客戶端搶先對(duì)任何被監(jiān)視的鍵進(jìn)行了替換、更新或刪除等操作,那么當(dāng)用戶嘗試執(zhí)行EXEC命令的時(shí)候,事務(wù)將失敗并返回一個(gè)錯(cuò)誤(之后選擇重試事務(wù)或者放棄事務(wù))。
UNWATCH命令可以在WATCH命令執(zhí)行之后、MULTI命令執(zhí)行之前對(duì)連接進(jìn)行重置(reset);同樣地,DISCARD命令也可以在MULTI命令執(zhí)行之后、EXEC命令執(zhí)行之前對(duì)連接進(jìn)行重置。這也就是說(shuō),用戶在使用WATCH監(jiān)視一個(gè)或多個(gè)鍵,接著使用MULTI開(kāi)始一個(gè)新的事務(wù),并將多個(gè)命令入隊(duì)到事務(wù)隊(duì)列之后,仍然可以通過(guò)發(fā)送DISCARD命令來(lái)取消WATCH命令并清空所有已入隊(duì)命令。
將商品放到市場(chǎng)上銷售:
def list_item(conn, itemid, sellerid, price): inventory = "inventory:%s"%sellerid # 商家包裹 item = "%s.%s"%(itemid, sellerid) end = time.time() + 5 pipe = conn.pipeline() while time.time() < end: try: pipe.watch(inventory) # 監(jiān)視商家包裹發(fā)生的變化 if not pipe.sismember(inventory, itemid): # 檢查商家是否仍然持有將要被銷售的商品 pipe.unwatch() return None pipe.multi() pipe.zadd("market:", item, price) # 將出售的商品添加到買賣市場(chǎng) pipe.srem(inventory, itemid) pipe.execute() # 執(zhí)行execute沒(méi)有引發(fā)WatchError異常,說(shuō)明事務(wù)執(zhí)行成功,并且對(duì)包裹鍵的監(jiān)視也已經(jīng)結(jié)束 return True except redis.exceptions.WatchError: # 商家的包裹已經(jīng)發(fā)生變化,重試 pass return False
購(gòu)買商品:
def purchase_item(conn, buyerid, itemid, sellerid, lprice): buyer = "users:%s"%buyerid seller = "users:%s"%sellerid item = "%s.%s"%(itemid, sellerid) inventory = "inventory:%s"%buyerid end = time.time() + 10 pipe = conn.pipeline() while time.time() < end: try: pipe.watch("market:", buyer) # 對(duì)商品買賣市場(chǎng)以及買家的個(gè)人信息進(jìn)行監(jiān)視 # 檢查購(gòu)買的商品價(jià)格是否發(fā)生變化,以及賣家是否有足夠的錢購(gòu)買 price = pipe.zscore("market:", item) funds = int(pipe.hget(buyer, "funds")) if price != lprice or price > funds: pipe.unwatch() return None pipe.multi() pipe.hincrby(seller, "funds", int(price)) pipe.hincrby(buyer, "funds", int(-price)) pipe.sadd(inventory, itemid) pipe.zrem("market:", item) pipe.execute() return True except redis.exceptions.WatchError: pass return False
加鎖有可能造成長(zhǎng)時(shí)間的等待,所以Redis為了盡可能地減少客戶端的等待時(shí)間,并不會(huì)在執(zhí)行WATCH命令時(shí)對(duì)數(shù)據(jù)進(jìn)行加鎖。相反,Redis只會(huì)在數(shù)據(jù)已經(jīng)被其他客戶端搶先修改了的情況下,通知執(zhí)行WATCH命令的客戶端,這種做法稱為樂(lè)觀鎖(optimistic locking),而關(guān)系型數(shù)據(jù)庫(kù)實(shí)際執(zhí)行的加鎖操作則被稱為悲觀鎖(pessimistic locking)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/44556.html
摘要:如何使用操作詳解簡(jiǎn)介是一個(gè)開(kāi)源許可的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),它可以用作數(shù)據(jù)庫(kù)緩存和消息中間件。解決辦法是即使查出的對(duì)象為空,也放入緩存時(shí)間設(shè)短一點(diǎn)。緩存雪崩,是指在某一個(gè)時(shí)間段,緩存集中過(guò)期失效。 如何使用StringRedisTemplate操作Redis詳解 Redis簡(jiǎn)介 Redis 是一個(gè)開(kāi)源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),它可以用作數(shù)據(jù)庫(kù)、緩存和消息中間件。支...
摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語(yǔ)言和等其他語(yǔ)言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問(wèn)到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過(guò)的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語(yǔ)言和Java、python等其他語(yǔ)言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...
摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語(yǔ)言和等其他語(yǔ)言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問(wèn)到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過(guò)的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語(yǔ)言和Java、python等其他語(yǔ)言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...
閱讀 3569·2021-10-18 13:30
閱讀 3012·2021-10-09 09:44
閱讀 2036·2019-08-30 11:26
閱讀 2431·2019-08-29 13:17
閱讀 815·2019-08-29 12:17
閱讀 2314·2019-08-26 18:42
閱讀 578·2019-08-26 13:24
閱讀 3013·2019-08-26 11:39