redis的事務類似于隊列操作,執行過程分為三步:
- 開啟事務
- 入隊操作
- 執行事務
使用到的幾個命令如下:
命令 | 說明 |
---|---|
multi | 開啟一個事務 |
exec | 事務提交 |
discard | 事務回滾 |
watch | 監聽key(s):當監聽一個key(s)時,如果在本次事務提交之前,有其他命令修改了該key的值,那么本地事務就會失效 |
unwatch | 取消監聽key(s) |
下面我們使用一個springboot的代碼操作來說明這幾個命令的含義:
package com.test.spring;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class TestSpringApplication {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping("/exec1")public String test1(){stringRedisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {connection.multi();connection.commands().set("k1".getBytes(),"1".getBytes());connection.commands().set("k2".getBytes(),"2".getBytes());connection.exec();return true;}});return "k1="+stringRedisTemplate.opsForValue().get("k1")+",k2="+stringRedisTemplate.opsForValue().get("k2");}@RequestMapping("/exec2")public String exec2(){try{stringRedisTemplate.execute(new RedisCallback<Boolean>() {private int i=0;@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {connection.multi();connection.commands().set("k1".getBytes(),"11".getBytes());connection.commands().set("k2".getBytes(),"22".getBytes());if(i==0){throw new RedisSystemException("一個異常",new RuntimeException("1"));}connection.exec();return true;}});}catch (Exception e){e.printStackTrace();}return "k1="+stringRedisTemplate.opsForValue().get("k1")+",k2="+stringRedisTemplate.opsForValue().get("k2");}@RequestMapping("/discard")public String discard(){try{stringRedisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {connection.multi();connection.commands().set("k3".getBytes(),"3".getBytes());connection.commands().set("k4".getBytes(),"4".getBytes());connection.discard();connection.exec();return true;}});}catch (Exception e){e.printStackTrace();}return "k3="+stringRedisTemplate.opsForValue().get("k3")+",k4="+stringRedisTemplate.opsForValue().get("k4");}@RequestMapping("/watch1")public String watch1(){//開啟一個線程new Thread(()->{stringRedisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {try {connection.watch("k5".getBytes());connection.multi();connection.commands().set("k5".getBytes(), "5".getBytes());//休眠5秒鐘在提交事務try {Thread.sleep(5000);} catch (InterruptedException e) {}connection.exec();}catch (Exception e){e.printStackTrace();}return true;}});}).start();//開啟一個線程new Thread(()->{stringRedisTemplate.execute(new RedisCallback<Boolean>() {@Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {try {connection.multi();connection.commands().set("k5".getBytes(), "55".getBytes());connection.exec();}catch (Exception e){e.printStackTrace();}return true;}});}).start();return "success";}@RequestMapping("/watch2")public String watch2(){return "k5="+stringRedisTemplate.opsForValue().get("k5");}public static void main(String[] args) {SpringApplication.run(TestSpringApplication.class, args);}}
- exec1
正常流程,使用curl進行測試會返回:k1=1,k2=2 - exec2
我們模擬在事務隊列中發送異常,會發現這段設值不成功,測試返回:k1=1,k2=2 - discard
事務回滾,我們先回滾,再提交,后臺會拋出:ERR EXEC without MULTI錯誤,說明設值失敗 - watch1、watch2
這里我們模擬兩個線程,第一個線程先監聽key,然后等待5秒鐘,但是第二個線程直接去修改這個key,當5秒結束時,第一個線程再去提交事務時,會發現已經失效了,然后我們再通過watch2去查詢值,測試返回:k5=55,說明線程1事務失效
最后再說明一下unwatch,每次操作exec()后,底層會自動調用unwatch,所以我們可以不用顯示去調用unwatch命令。