redis事务原理,使用,详解
生活随笔
收集整理的這篇文章主要介紹了
redis事务原理,使用,详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
聲明:本博客內容來自《Redis深度歷險》一書
? ? ??
為了確保連續多個操作的原子性,一個成熟的數據庫通常都會有事務支持,Redis 也不例外。Redis 的事務使用非常簡單,不同于關系數據庫,我們無須理解那么多復雜的事務模型,就可以直接使用。不過也正是因為這種簡單性,它的事務模型很不嚴格,這要求我們不能像使用關系數據庫的事務一樣來使用
Redis 事務的基本使用
每個事務的操作都有 begin、commit 和 rollback begin 指示事務的開始 commit 指示事務的提交 rollback 指示事務的回滾 它大致的形式如下 begin();
try
{command1();command2();....commit();
}
catch(Exception e)
{rollback();
} Redis 在形式上看起來也差不多,分別是 multi/exec/discard。multi 指示事務的開始,exec 指示事務的執行,discard 指示事務的丟棄 > multi
OK
> incr books
QUEUED
> incr books
QUEUED
> exec
(integer) 1
(integer) 2 上面的指令演示了一個完整的事務過程,所有的指令在 exec 之前不執行,而是緩存在服務器的一個事務隊列中,服務器一旦收到 exec 指令,才開執行整個事務隊列,執行完畢后一次性返回所有指令的運行結果。因為 Redis 的單線程特性,它不用擔心自己在執行隊列的時候被其它指令打攪,可以保證他們能得到的「原子性」執行。QUEUED 是一個簡單字符串,同 OK 是一個形式,它表示指令已經被服務器緩存到隊列里了 原子性 事務的原子性是指要么事務全部成功,要么全部失敗,那么 Redis 事務執行是原子性的么? 下面我們來看一個特別的例子。 > multi
OK
> set books iamastring
QUEUED
> incr books
QUEUED
> set poorman iamdesperate
QUEUED
> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
> get books
"iamastring"
> get poorman
"iamdesperate 上面的例子是事務執行到中間遇到失敗了,因為我們不能對一個字符串進行數學運算,事務在遇到指令執行失敗后,后面的指令還繼續執行,所以 poorman 的值能繼續得到設置 到這里,你應該明白Redis 的事務根本不能算「原子性」,而僅僅是滿足了事務的「隔離性」,隔離性中的串行化——當前執行的事務有著不被其它事務打斷的權利 discard(丟棄) Redis 為事務提供了一個 discard 指令,用于丟棄事務緩存隊列中的所有指令,在 exec 執行之前 > get books
(nil)
> multi
OK
> incr books
QUEUED
> incr books
QUEUED
> discard
OK
> get books
(nil) 我們可以看到 discard 之后,隊列中的所有指令都沒執行,就好像 multi 和 discard 中間的所有指令從未發生過一樣 優化 上面的 Redis 事務在發送每個指令到事務緩存隊列時都要經過一次網絡讀寫,當一個事務內部的指令較多時,需要的網絡 IO 時間也會線性增長。所以通常 Redis 的客戶端在執行事務時都會結合 pipeline 一起使用,這樣可以將多次 IO 操作壓縮為單次 IO 操作。比如我們在使用 Python 的 Redis 客戶端時執行事務時是要強制使用 pipeline 的 pipe = redis.pipeline(transaction=true)
pipe.multi()
pipe.incr("books")
pipe.incr("books")
values = pipe.execute() Watch 考慮到一個業務場景,Redis 存儲了我們的賬戶余額數據,它是一個整數。現在有兩個并發的客戶端要對賬戶余額進行修改操作,這個修改不是一個簡單的 incrby 指令,而是要對余額乘以一個倍數。Redis 可沒有提供 multiplyby 這樣的指令。我們需要先取出余額然后在內存里乘以倍數,再將結果寫回 Redis 這就會出現并發問題,因為有多個客戶端會并發進行操作。我們可以通過 Redis 的分布式鎖來避免沖突,這是一個很好的解決方案 但是分布式鎖是一種悲觀鎖,那是不是可以使用樂觀鎖的方式來解決沖突呢? Redis 提供了這種 watch 的機制,它就是一種樂觀鎖。有了 watch 我們又多了一種可以用來解決并發修改的方法。 watch 的使用方式如下 while True:do_watch()commands()multi()send_commands()try:exec()breakexcept WatchError:continue watch 會在事務開始之前盯住 1 個或多個關鍵變量,當事務執行時,也就是服務器收到了 exec 指令要順序執行緩存的事務隊列時,Redis 會檢查關鍵變量自 watch 之后,是否被修改了 (包括當前事務所在的客戶端)。如果關鍵變量被人動過了,exec 指令就會返回 null 回復告知客戶端事務執行失敗,這個時候客戶端一般會選擇重試 > watch books
OK
> incr books # 被修改了
(integer) 1
> multi
OK
> incr books
QUEUED
> exec # 事務執行失敗
(nil) 當服務器給 exec 指令返回一個 null 回復時,客戶端知道了事務執行是失敗的,通常客戶端 (redis-py) 都會拋出一個 WatchError 這種錯誤,不過也有些語言 (jedis) 不會拋出異常,而是通過在 exec 方法里返回一個 null,這樣客戶端需要檢查一下返回結果是否為 null來確定事務是否執行失敗 注意事項 Redis 禁止在 multi 和 exec 之間執行 watch 指令,而必須在 multi 之前做好盯住關鍵變量,否則會出錯 接下來我們使用 Python 語言來實現對余額的加倍操作 import redis
def key_for(user_id):return "account_{}".format(user_id)
def double_account(client, user_id):key = key_for(user_id)while True:client.watch(key)value = int(client.get(key))value *= 2 # 加倍pipe = client.pipeline(transaction=True)pipe.multi()pipe.set(key, value)try:pipe.execute()break # 總算成功了except redis.WatchError:continue # 事務被打斷了,重試return int(client.get(key)) # 重新獲取余額
client = redis.StrictRedis()
user_id = "abc"
client.setnx(key_for(user_id), 5) # setnx 做初始化
print double_account(client, user_id) 下面我們再使用 Java 語言實現一遍 import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TransactionDemo {public static void main(String[] args) {Jedis jedis = new Jedis();String userId = "abc";String key = keyFor(userId);jedis.setnx(key, String.valueOf(5)); # setnx 做初始化System.out.println(doubleAccount(jedis, userId));jedis.close();}public static int doubleAccount(Jedis jedis, String userId) {String key = keyFor(userId);while (true) {jedis.watch(key);int value = Integer.parseInt(jedis.get(key));value *= 2; // 加倍Transaction tx = jedis.multi();tx.set(key, String.valueOf(value));List<Object> res = tx.exec();if (res != null) {break; // 成功了}}return Integer.parseInt(jedis.get(key)); // 重新獲取余額}public static String keyFor(String userId) {return String.format("account_{}", userId);}
} 我們常常聽說 Python 的代碼要比 Java 簡短太多,但是從這個例子中我們看到 Java的代碼比 python 的代碼也多不了多少,大約只多出 50% 思考題 為什么 Redis 的事務不能支持回滾?
總結
以上是生活随笔為你收集整理的redis事务原理,使用,详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 令牌桶限流之redis-cell的安装,
- 下一篇: goland设置Terminal