1、簡介
Redis類似大多數成熟的資料庫系統一樣,提供了事務機制。Redis的事務機制非常簡單,它沒有嚴格的事務模型,無法像關系型資料庫一樣保證操作的原子性。
Redis事務最大的作用是保證多個指令的串行執行,它可以借助于Redis單線程讀寫的特性,保證Redis事務中的指令不會被事務外的指令打攪,不過要注意它不是原子性的。
完整事務案例:

multi開啟一個事務之後,所有指令都不執行,而是緩存到事務隊列中,直到伺服器接收到exec指令,才開始執行整個事務中的指令。事務全部指令執行完畢後,一次性傳回全部的結果。
使用Redis事務,一個最需要注意的問題是,指令多,網絡開銷高;是以我們一定要結合管道pipeline一起使用,這樣可以将多次網絡io操作壓縮成單次。
2、指令介紹
2.1 簡介
Redis事務相關的指令有五個,分别是MULTI、EXEC、DISCARD、WATCH、UNWATCH
指令 | 指令作用 | 傳回值 |
MULTI | 标記一個事務塊的開始 | 總是傳回 OK |
EXEC | 執行所有事務塊内的指令 | 事務塊内所有指令的傳回值,按指令執行的先後順序排列。當操作被打斷時,傳回空值 nil |
DISCARD | 取消事務,放棄執行事務塊内的所有指令,如果正在使用 WATCH 指令監視某個(或某些) key,那麼取消所有監視,等同于執行指令 UNWATCH | 總是傳回 OK |
WATCH | 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他指令所改動,那麼事務将被打斷 | |
UNWATCH | 取消 WATCH 指令對所有 key 的監視。如果在執行WATCH 指令之後, EXEC 指令或 DISCARD 指令先被執行了的話,那麼就不需要再執行 UNWATCH 了。因為 EXEC 指令會執行事務,是以 WATCH 指令的效果已經産生了;而 DISCARD 指令在取消事務的同時也會取消所有對 key 的監視,是以這兩個指令執行之後,就沒有必要執行 UNWATCH 了 | 總是傳回 OK |
2.2 MULTI(開啟事務)
MULTI用于标記一個事務的開始,事務塊内的多條指令會按照先後順序被放進一個隊列當中,最後由 EXEC 指令原子性(atomic)地執行。MULTI指令總是傳回OK。
2.3 EXEC(執行事務)
EXEC用于執行所有事務塊内的指令,假如某個(或某些) key 正處于 WATCH 指令的監視之下,且事務塊中有和這個(或這些) key 相關的指令,那麼 EXEC 指令隻在這個(或這些) key 沒有被其他指令所改動的情況下執行并生效,否則該事務被打斷(abort)。
2.4 DISCARD(取消事務)
DISCARD用于取消事務,放棄執行事務塊内的所有指令。如果正在使用 WATCH 指令監視某個(或某些) key,那麼取消所有監視,等同于執行指令 UNWATCH 。DISCARD指令總是傳回OK。
2.5 WATCH(監視)
WATCH用于監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他指令所改動,那麼事務将被打斷。這個實作方式也很簡單,WATCH是在事務之間發送的指令,Redis服務在接收到指令時,會記錄下該key對應的值,當Redis服務接收到EXEC指令,需要執行事務時,Redis服務首先會檢查WATCH的key的值,從WATCH之後是否發生改變即可。
注意禁止在MULTI和EXEC之間執行WATCH指令,這會導緻Redis服務響應異常
2.6 UNWATCH
UNWATCH用于取消WATCH指令對所有key的監視。如果在執行 WATCH 指令之後, EXEC 指令或 DISCARD 指令先被執行了的話,那麼就不需要再執行 UNWATCH 了。因為 EXEC 指令會執行事務,是以 WATCH 指令的效果已經産生了;而 DISCARD 指令在取消事務的同時也會取消所有對 key 的監視,是以這兩個指令執行之後,就沒有必要執行 UNWATCH 了。
3、Jedis 使用事務
通過模拟一個簡單的餘額增加的例子,使用Jedis用戶端來使用Redis的事務。
package com.lizba.redis.tx;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.math.BigDecimal;
import java.util.List;
/**
* <p>
* Redis事務demo
* </p>
*
* @Author: Liziba
* @Date: 2021/9/9 23:53
*/
public class TransactionDemo {
private Jedis client;
public TransactionDemo(Jedis client) {
this.client = client;
}
/**
* 添加餘額
*
* @param userId 使用者id
* @param amt 添加餘額
* @return
*/
public BigDecimal addBalance(String userId, BigDecimal amt) {
String key = this.keyFormat(userId);
// 初始使用者餘額為0
client.setnx(key, "0");
while (true) {
client.watch(key);
BigDecimal balance = new BigDecimal(client.get(key)).setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal amount = balance.add(amt);
Transaction tx = client.multi();
tx.set(key, amount.toPlainString());
List<Object> exec = tx.exec();
// 傳回值不為空則證明Redis事務成功
if (exec != null) {
break;
}
}
return new BigDecimal(client.get(key)).setScale(2, BigDecimal.ROUND_HALF_UP);
* 擷取總金額
* @param userId 使用者id
public BigDecimal getAmount(String userId) {
String amt = client.get(keyFormat(userId));
return new BigDecimal(amt);
* Redis key
private String keyFormat(String userId) {
return String.format("balance:%s",userId);
}
測試代碼:
import java.util.concurrent.CountDownLatch;
* 測試Redis事務
* @Date: 2021/9/10 0:03
public class TestTransactionDemo {
private static CountDownLatch count = new CountDownLatch(100);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
Jedis client = new Jedis("192.168.211.109", 6379);
TransactionDemo demo = new TransactionDemo(client);
demo.addBalance("liziba", BigDecimal.TEN);
client.close();
count.countDown();
}).start();
count.await();
Jedis client = new Jedis("192.168.211.109", 6379);
BigDecimal amt = new TransactionDemo(client).getAmount("liziba");
System.out.println(amt.toPlainString());
測試結果:
預期1000,結果1000