python实现redis三种cas事务操作
cas全稱是compare and set,是一種典型的事務(wù)操作。
簡單的說,事務(wù)就是為了存取數(shù)據(jù)庫中同一數(shù)據(jù)時(shí)不破壞操作的隔離性和原子性,從而保證數(shù)據(jù)的一致性。
一般數(shù)據(jù)庫,比如MySql是如何保證數(shù)據(jù)一致性的呢,主要是加鎖,悲觀鎖。比如在訪問數(shù)據(jù)庫某條數(shù)據(jù)的時(shí)候,會(huì)用SELECT FOR UPDATE ,這MySql就會(huì)對(duì)這條數(shù)據(jù)進(jìn)行加鎖,直到事務(wù)被提交(COMMIT),或者回滾(ROLLBACK)。如果此時(shí),有其他事務(wù)對(duì)被加鎖的數(shù)據(jù)進(jìn)行寫入,那么該事務(wù)將會(huì)被阻塞,直到第一個(gè)事務(wù)完成為止。它的缺點(diǎn)在于:持有鎖的事務(wù)運(yùn)行越慢,等待解鎖的事務(wù)阻塞時(shí)間就越長。并且容易產(chǎn)生死鎖(前面有篇文章有講解死鎖)!
本文會(huì)介紹三種redis實(shí)現(xiàn)cas事務(wù)的方法,并會(huì)解決下面的虛擬問題:
維護(hù)一個(gè)值,如果這個(gè)值小于當(dāng)前時(shí)間,則設(shè)置為當(dāng)前時(shí)間;如果這個(gè)值大于當(dāng)前時(shí)間,則設(shè)置為當(dāng)前時(shí)間+30。簡單的單線程環(huán)境下代碼如下:
很簡單的一段代碼,在單線程環(huán)境下可以跑的很歡,但顯然,是無法移植到多線程或者是多進(jìn)程環(huán)境的(進(jìn)程A和B同時(shí)運(yùn)行到#1,獲取了相同的count值,然后運(yùn)行#2#3,會(huì)導(dǎo)致count值總共只增加了30)。而為了能在多進(jìn)程環(huán)境下運(yùn)行,我們需要引入一些其他的東西。
py-redis本身自帶的事務(wù)操作
redis有這么幾個(gè)和事務(wù)相關(guān)的命令,multi,exec,watch。通過這幾個(gè)命令,可以實(shí)現(xiàn)‘將多個(gè)命令打包,然后一次性、按順序執(zhí)行,且不會(huì)被終端’。事務(wù)會(huì)從MULTI開始,執(zhí)行EXEC后觸發(fā)事件。另外,我們還需要WATCH,watch可以監(jiān)視任意數(shù)量的鍵,當(dāng)在調(diào)用EXEC執(zhí)行事務(wù)時(shí),如果任意一個(gè)鍵被修改了,整個(gè)事務(wù)不會(huì)執(zhí)行。
下邊是使用redis本身的事務(wù)解決cas問題的代碼。
''' 遇到問題沒人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' class CasNormal(object):def __init__(self, host, key):self.r = redis.Redis(host)self.key = keyif not self.r.exists(self.key):self.r.set(self.key, 0)def inc(self):with self.r.pipeline() as pipe:while True:try:#監(jiān)視一個(gè)key,如果在執(zhí)行期間被修改了,會(huì)拋出WatchErrorpipe.watch(self.key)next_count = 30 + int(pipe.get(self.key))pipe.multi()if next_count < int(time.time()):next_count = int(time.time())pipe.set(self.key, next_count)pipe.execute()return next_countexcept WatchError:continuefinally:pipe.reset()代碼也不復(fù)雜,引入了之前說到的multi,exec,watch,如果對(duì)事務(wù)操作比較熟悉的同學(xué),可以很容易看出來,這是一個(gè)樂觀鎖的操作(咱們假設(shè)沒人競爭來著,每次去拿數(shù)據(jù)的時(shí)候都不會(huì)上鎖,真有人來改了再說。)樂觀鎖在高并發(fā)的情況下會(huì)顯得很無力,文末的性能對(duì)比會(huì)顯示這個(gè)問題。
使用基于redis的悲觀鎖
悲觀鎖,就是很悲觀的鎖,每次拿數(shù)據(jù)都會(huì)假設(shè)別人也要拿,先給鎖起來,用完再把鎖釋放掉。redis本身沒有實(shí)現(xiàn)悲觀鎖,但我們可以先用redis實(shí)現(xiàn)一個(gè)悲觀鎖。
ok,咱們現(xiàn)在有悲觀鎖了,做起事來也有底氣了,根據(jù)上邊的代碼,咱們只要加上@ synchronized注釋就能保證同一時(shí)間只有一個(gè)進(jìn)程在執(zhí)行。下邊是基于悲觀鎖的解決方案。
''' 遇到問題沒人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' lock_conn = redis.Redis("localhost")class CasLock(object):def __init__(self, host, key):self.r = redis.Redis(host)self.key = keyif not self.r.exists(self.key):self.r.set(self.key, 0)@synchronized(lock_conn, "lock", 10)def inc(self):next_count = 30 + int(self.r.get(self.key))if next_count < int(time.time()):next_count = int(time.time())self.r.set(self.key, next_count)return next_count代碼看上去少多了(因?yàn)橐肓藄ynchronized…)
基于lua腳本實(shí)現(xiàn)
上邊兩種方法都是用鎖來實(shí)現(xiàn)的,鎖的實(shí)現(xiàn)總會(huì)出現(xiàn)競爭的問題,區(qū)別無非是出現(xiàn)競爭了咋辦的問題。使用redis lua腳本的實(shí)現(xiàn),可以直接把這個(gè)cas操作當(dāng)成一個(gè)原子操作。
我們知道,redis本身的一系列操作,都是原子操作,且redis會(huì)按順序執(zhí)行所有收到的命令。先看代碼
''' 遇到問題沒人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' class CasLua(object):def __init__(self, host, key):self.r = redis.Redis(host)self.key = keyif not self.r.exists(self.key):self.r.set(self.key, 0)self._lua = self.r.register_script("""local next_count = redis.call('get',KEYS[1]) + ARGV[1]ARGV[2] = tonumber(ARGV[2])if next_count < ARGV[2] thennext_count = ARGV[2]endredis.call('set',KEYS[1],next_count)return tostring(next_count)""")def inc(self):return int(self._lua([self.key], [30, int(time.time())]))這里先注冊(cè)了這個(gè)腳本,后邊可以直接去使用他。關(guān)于redis lua腳本的文章有不少,感興趣的可以去搜搜看,這邊就不贅述了。
性能對(duì)比
這邊的測(cè)試只是一個(gè)非常簡單的測(cè)試(不過還是能看出效果來的),測(cè)試換機(jī)就是自己的開發(fā)機(jī),數(shù)字看個(gè)大小就行了。
分別測(cè)了三種操作在單線程,五個(gè)線程,十個(gè)線程,五十個(gè)線程情況下,進(jìn)行1000次操作各自的表現(xiàn),時(shí)間如下
optimistic Lock pessimistic lock lua 1thread 0.43 0.71 0.35 5thread 5.80 3.10 0.62 10thread 17.80 5.60 1.30 50thread 245.00 29.60 6.50依次是redis本身事務(wù)實(shí)現(xiàn)的樂觀鎖,基于redis實(shí)現(xiàn)的悲觀鎖以及l(fā)ua實(shí)現(xiàn)。
在比較悲觀鎖和樂觀鎖之前,需要先說明一點(diǎn),這邊的測(cè)試對(duì)樂觀鎖不是很公平,樂觀鎖本身就是假設(shè)不會(huì)有很多的并發(fā)的。在單線程情況下,悲觀鎖要差一些。單線程下,不存在競爭關(guān)系,悲觀鎖耗時(shí)長僅因?yàn)槭嵌嗔艘淮蝦edis的網(wǎng)絡(luò)交互。隨著線程的增加,悲觀鎖的性能逐漸變好,畢竟悲觀鎖本身就是為了解決這種高并發(fā)高競爭的環(huán)境而誕生的。在50線程的時(shí)候,樂觀鎖的實(shí)現(xiàn)單次操作的時(shí)間要0.245秒,非常恐怖,如果是生產(chǎn)環(huán)境,幾乎都不能用了。
至于lua的性能,快的不可思議,幾乎就是線性增加。(50線程的情況下,平均的1000次完成時(shí)間是6.5s,換言之,6.5秒內(nèi)執(zhí)行了50 * 1000次cas操作)。
以上測(cè)試都是本地redis,本地測(cè)試,如果redis是遠(yuǎn)端的,網(wǎng)絡(luò)交互時(shí)間會(huì)增加,lua優(yōu)勢(shì)會(huì)更加明顯。
總結(jié)
以上是生活随笔為你收集整理的python实现redis三种cas事务操作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: django orm 常用查询筛选
- 下一篇: Python使用数字与字符串的技巧