cas全稱是compare and set,是一種典型的事務(wù)操作。
簡單的說,事務(wù)就是為了存取數(shù)據(jù)庫中同一數(shù)據(jù)時不破壞操作的隔離性和原子性,從而保證數(shù)據(jù)的一致性。
一般數(shù)據(jù)庫,比如MySql是如何保證數(shù)據(jù)一致性的呢,主要是加鎖,悲觀鎖。比如在訪問數(shù)據(jù)庫某條數(shù)據(jù)的時候,會用SELECT FOR UPDATE ,這MySql就會對這條數(shù)據(jù)進行加鎖,直到事務(wù)被提交(COMMIT),或者回滾(ROLLBACK)。如果此時,有其他事務(wù)對被加鎖的數(shù)據(jù)進行寫入,那么該事務(wù)將會被阻塞,直到第一個事務(wù)完成為止。它的缺點在于:持有鎖的事務(wù)運行越慢,等待解鎖的事務(wù)阻塞時間就越長。并且容易產(chǎn)生死鎖(前面有篇文章有講解死鎖)!
本文會介紹三種redis實現(xiàn)cas事務(wù)的方法,并會解決下面的虛擬問題:
維護一個值,如果這個值小于當(dāng)前時間,則設(shè)置為當(dāng)前時間;如果這個值大于當(dāng)前時間,則設(shè)置為當(dāng)前時間+30。簡單的單線程環(huán)境下代碼如下:
# 初始化r = redis.Redis()if not r.exists("key_test"): r.set("key_test", 0)def inc(): count = int(r.get('key_test')) + 30 #1 # 如果值比當(dāng)前時間小,則設(shè)置為當(dāng)前時間 count = max(count, int(time.time())) #2 r.set('key_test', count) #3 return count很簡單的一段代碼,在單線程環(huán)境下可以跑的很歡,但顯然,是無法移植到多線程或者是多進程環(huán)境的(進程A和B同時運行到#1,獲取了相同的count值,然后運行#2#3,會導(dǎo)致count值總共只增加了30)。而為了能在多進程環(huán)境下運行,我們需要引入一些其他的東西。
py-redis本身自帶的事務(wù)操作
redis有這么幾個和事務(wù)相關(guān)的命令,multi,exec,watch。通過這幾個命令,可以實現(xiàn)‘將多個命令打包,然后一次性、按順序執(zhí)行,且不會被終端'。事務(wù)會從MULTI開始,執(zhí)行EXEC后觸發(fā)事件。另外,我們還需要WATCH,watch可以監(jiān)視任意數(shù)量的鍵,當(dāng)在調(diào)用EXEC執(zhí)行事務(wù)時,如果任意一個鍵被修改了,整個事務(wù)不會執(zhí)行。
下邊是使用redis本身的事務(wù)解決cas問題的代碼。
class CasNormal(object): def __init__(self, host, key): self.r = redis.Redis(host) self.key = key if 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)視一個key,如果在執(zhí)行期間被修改了,會拋出WatchError pipe.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_count except WatchError: continue finally: pipe.reset()
代碼也不復(fù)雜,引入了之前說到的multi,exec,watch,如果對事務(wù)操作比較熟悉的同學(xué),可以很容易看出來,這是一個樂觀鎖的操作(咱們假設(shè)沒人競爭來著,每次去拿數(shù)據(jù)的時候都不會上鎖,真有人來改了再說。)樂觀鎖在高并發(fā)的情況下會顯得很無力,文末的性能對比會顯示這個問題。
新聞熱點
疑難解答