Redis中的事務(wù)和Redis樂觀鎖詳解
1 Redis事務(wù)介紹
Redis事務(wù)是一個單獨(dú)的隔離操作:事務(wù)中的所有命令都會序列化、按順序地執(zhí)行。
事務(wù)在執(zhí)行的過程中,不會被其他客戶端發(fā)送來的命令請求所打斷。
- Redis的事務(wù)是通過multi、exec、discard和watch這四個命令來完成的。
- Redis的單個命令都是原子性的,所以這里需要確保事務(wù)性的對象是命令集合。
- Redis將命令集合序列化并確保處于同一事務(wù)的命令集合連續(xù)且不被打斷的執(zhí)行。
- Redis不支持回滾操作
1.1 命令介紹
- multi:用于標(biāo)記事務(wù)塊的開始,Redis會將后續(xù)的命令逐個放入隊列中,然后使用exec原子化執(zhí)行這個命令隊列 。
- exec:執(zhí)行命令隊列
- discard:清除命令隊列
- watch:在執(zhí)行multi之前,先執(zhí)行watch key1 [key2],可以監(jiān)視一個(或多個) key ,如果在事務(wù)執(zhí)行之前這個(或這些) key 被其他命令所改動,那么事務(wù)將被打斷(可以利用Watch特性實(shí)現(xiàn)Redis樂觀鎖)
- unwatch:取消 WATCH 命令對所有 key 的監(jiān)視(如果在執(zhí)行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被執(zhí)行了的話,那么就不需要再執(zhí)行UNWATCH 了)。
1.2 事務(wù)流程
從輸入multi命令開始,輸入的命令都會依次進(jìn)入命令隊列中,但不會執(zhí)行,直到輸入exec命令后,redis會將之前的命令隊列中的命令依次執(zhí)行。
- 組隊的過程中可以通過discard來放棄組隊。
- 如果組隊中某個命令出現(xiàn)了報告錯誤,執(zhí)行時整個的所有隊列都會被取消。
- 如果執(zhí)行階段某個命令報出了錯誤,則只有報錯的命令不會被執(zhí)行,而其他的命令都會執(zhí)行,不會回滾。


上圖說明:
- 1.客戶端1watch user:001 ,
- 2.客戶端1 開啟事務(wù)multi
- 3.客戶端1,執(zhí)行命令set user:001 lisi
- 4.在客戶端1,執(zhí)行exec之前,客戶端2,執(zhí)行set user:001 xiaoming
- 5.客戶端1,執(zhí)行exec出錯,事務(wù)被打斷
命令演示:
127.0.0.1:6379> multi OK 127.0.0.1:6379> set user:001 zhangsan QUEUED 127.0.0.1:6379> set user:002 lisi QUEUED 127.0.0.1:6379> get user:001 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) "zhangsan" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set user:001 xiaoming QUEUED 127.0.0.1:6379> set user:002 xiaozhang QUEUED 127.0.0.1:6379> discard # 使用discard命令取消隊列 OK 127.0.0.1:6379> exec # 執(zhí)行exec報錯 (error) ERR EXEC without MULTI # watch 命令演示 # 客戶端2,在客戶端1執(zhí)行exec之前,執(zhí)行 set user:001 xiaoming,客戶端1的事務(wù)被打斷 127.0.0.1:6379> get user:001 "zhangsan" 127.0.0.1:6379> watch user:001 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set user:001 lisi QUEUED 127.0.0.1:6379> exec # 客戶端1的事務(wù)被打斷 (nil) 127.0.0.1:6379> get user:001 "xiaoming"
2 Redis實(shí)現(xiàn)樂觀鎖
2.1 樂觀鎖與悲觀鎖介紹
悲觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會被阻塞直到它拿到鎖。
傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號等機(jī)制。
樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量。Redis就是利用這種check-and-set機(jī)制實(shí)現(xiàn)事務(wù)的。
2.2 Redis樂觀鎖實(shí)現(xiàn)原理
Redis樂觀鎖的實(shí)現(xiàn),是利用watch命令特性。數(shù)據(jù)進(jìn)行提交更新的時候,對數(shù)據(jù)的沖突與否進(jìn)行檢測,如果發(fā)現(xiàn)沖突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。
Redis通過數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)樂觀鎖,這是樂觀鎖最常用的一種實(shí)現(xiàn)方式。
| 客戶端1 | 客戶端2 |
| age字段初始版本為1 | age字段初始版本為1 |
| watch age multi | |
| set age 25 版本加一,目前數(shù)據(jù)庫版本為2 | |
| set age 30 exec 當(dāng)前操作版本為1,小于數(shù)據(jù)中版本,提交失敗。 |
客戶端2在客戶端1提交事務(wù)之前,對據(jù)庫版本version進(jìn)行更新一次,客戶端1事務(wù)提交的時候?qū)Ρ劝姹咎?,要是此次版本號低于?shù)據(jù)庫當(dāng)前版本號,就會提交失敗。
2.3 Redis樂觀鎖秒殺案例
創(chuàng)建Spring boot項目引入以下依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置文件
spring:
redis:
host: 192.168.235.131
port: 6379
database: 0
connect-timeout: 1800000
password: 123456
lettuce:
pool:
#連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
max-active: 20
#最大阻塞等待時間(負(fù)數(shù)表示沒限制)
max-wait: -1
#連接池中的最大空閑連接
max-idle: 8
#連接池中的最小空閑連接
min-idle: 0
service 代碼
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
//庫存
private final String STOCK_KEY="stock:num";
//秒殺成功的用戶
private final String USER_KEY="success:user";
/**
* Redis樂觀鎖秒殺案例
* @param userId 用戶ID
* @return
*/
@Override
public boolean secKill(String userId) {
Object stockObj = redisTemplate.opsForValue().get(STOCK_KEY);
if (stockObj==null){
log.info("庫存為空,秒殺還未開始!");
return false;
}
int stockNum=Integer.parseInt(stockObj.toString());
if (stockNum<=0){
log.info("庫存為0,秒殺已經(jīng)結(jié)束!");
return false;
}
//判斷當(dāng)前用戶是否已經(jīng)秒殺成功
Boolean member = redisTemplate.opsForSet().isMember(USER_KEY, userId);
if (member){
log.info("您已經(jīng)秒殺成功,不能重復(fù)參與!");
return false;
}
List txList =redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
//監(jiān)聽庫存
operations.watch(STOCK_KEY);
//開啟事務(wù)
operations.multi();
//扣減庫存
operations.opsForValue().decrement(STOCK_KEY);
//把秒殺成功的用戶加入到set集合
operations.opsForSet().add(USER_KEY,userId);
//執(zhí)行事務(wù)
List<Object> result=operations.exec();
return result;
}
});
if (txList==null||txList.size()==0){
log.info("用戶:{},秒殺失敗",userId);
return false;
}
log.info("用戶:{},秒殺成功",userId);
return true;
}
}Controller代碼
/**
* 秒殺
* @return
*/
@RequestMapping("secKill")
public String secKill(){
String userId= UUID.randomUUID().toString();
boolean res = orderService.secKill(userId);
if (res){
return "秒殺成功";
}else {
return "秒殺失敗";
}
}使用linux上的ab進(jìn)行并發(fā)測試:
ab -n 500 -c 100 http://192.168.1.171/order/secKill
到此這篇關(guān)于Redis中的事務(wù)和Redis樂觀鎖詳解的文章就介紹到這了,更多相關(guān)Redis事務(wù)和樂觀鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
分析講解SpringMVC注解配置如何實(shí)現(xiàn)
這篇文章主要介紹了本文要介紹用注解方式代替web.xml與SpringMVC的配置文件,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05
Spring?中的切面與事務(wù)結(jié)合使用完整示例
本文給大家介紹Spring中的切面與事務(wù)結(jié)合使用完整示例,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2025-08-08
Java concurrency集合之ConcurrentSkipListSet_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency集合之ConcurrentSkipListSet的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06
java?Stream流常見操作方法(反射,類加載器,類加載,反射)
這篇文章主要介紹了java?Stream流常見操作方法(反射,類加載器,類加載,反射),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,展開詳細(xì)的內(nèi)容介紹,具有一定參考價值,感興趣的小伙伴可以參考一下2022-06-06
java實(shí)現(xiàn)后臺數(shù)據(jù)顯示在前端
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)后臺數(shù)據(jù)顯示在前端,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-02-02
maven無法依賴spring-cloud-stater-zipkin的解決方案
這篇文章主要介紹了maven無法依賴spring-cloud-stater-zipkin如何解決,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05
IntelliJ IDEA創(chuàng)建maven web項目的圖文步驟(IDEA新手適用)
這篇文章主要介紹了IntelliJ IDEA創(chuàng)建maven web項目的圖文步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Shiro整合Springboot和redis,jwt過程中的錯誤shiroFilterChainDefinition問
這篇文章主要介紹了Shiro整合Springboot和redis,jwt過程中的錯誤shiroFilterChainDefinition問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04

