springboot中加分佈式redis鎖

分佈式redis鎖,spring-boot-starter-data-redis,RedisTemplatehtml

公司聊天的聊天系統,近期出現多個客服併發接待同一個客戶的記錄,經排查,是因爲代碼加的同步鎖在集羣環境下不適用,java

咱們的客服系統是2條服務器,redis中緩存session,故考慮經過加redis分佈式鎖來解決該問題.redis

根據實際狀況,只針對單臺redis實例,代碼邏輯先獲取鎖再執行操做.spring

代碼體系用的是spring-boot-starter-data-redis,因此訪問redis的客戶端就是 RedisTemplate<String, String> redisTemplate緩存

具體的加鎖和解鎖方法,用到了lua腳原本操做,儘量的保證操做的原子性.服務器

腳本封裝入 DefaultRedisScript對象中,再使用RedisTemplate來執行,官方介紹 session

6.11. Redis Scripting

 https://docs.spring.io/spring-data/redis/docs/1.6.2.RELEASE/reference/html/併發

 

lua腳本操做redis相關命令理解,請參考  http://redisdoc.com/string/index.html分佈式

1,獲取鎖lua腳本 spring-boot

    redis.call('set', KEYS[1], ARGV[1],ARGV[2],ARGV[3],ARGV[4]) if redis.call('get',KEYS[1]) == ARGV[1] then return true else return false end

    參數對應 set( key,reqId,NX,PX,時間毫秒)

    原子操做 set NX參數,不存在key則設置key-value, PX 並同時設置key超時毫秒數,這裏爲了返回參數判斷,加入get 獲取key的值是否等於我設置的值,是則獲取鎖成功.

    注意 expireTime 是 int類型的,RedisTemplate 底層要轉字符串的,因此入參是記得加 String.valueOf(expireTime),不然會出現轉換異常,固然只是我在處理得時候遇到了,特別說下.

    固然這是個人蔘考實現,要有更好建議,還請提出.

2,釋放鎖lua腳本

    if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return true else return false end     

    參數對應 get( key) == reqId 是true則直接刪除key釋放鎖

 

這個實現參考了另外一篇博客,那個是用Jedis作客戶端而我在RedisTemplate底層調用中沒找到類似功能的方法,因此才改變思路都用lua腳本操做,後面附博客連接。

3,貼上代碼以下

package com.xxxxx.kefu.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;

public class RedisLockTool {

/**不存在則設置k - v 值*/
private static final String SET_IF_NOT_EXIST = "NX";
/**設置毫秒超時*/
private static final String SET_WITH_EXPIRE_TIME = "PX";

/**
* 嘗試獲取分佈式鎖
*/
public static boolean getLock(RedisTemplate<String, String> redisTemplate, String lockKey, String requestId, int expireTime) {
    String script = "redis.call('set', KEYS[1], ARGV[1],ARGV[2],ARGV[3],ARGV[4]) if redis.call('get',KEYS[1]) == ARGV[1] then return true else return false end";
    DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>(script,Boolean.class);
    Boolean result = redisTemplate.execute(redisScript,Collections.singletonList(lockKey), requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,String.valueOf(expireTime));
    if (result != null && result) {
         return true;
    }
    return false;
}

/**
* 釋放分佈式鎖
*/
public static boolean unLock(RedisTemplate<String, String> redisTemplate, String lockKey, String requestId) {
         String script = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return true else return false end"; 
         DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>(script,Boolean.class);
         Boolean ok = redisTemplate.execute(redisScript,Collections.singletonList(lockKey),requestId);
         if(ok != null && ok){
               return true;
         }else{
          return false;
        }
   }
}

 

4,參考博客

      https://yq.aliyun.com/articles/307547

      裏面指出了,其餘實現方法的一些問題,在某些狀況下不能確保操做原子性,看了理解以後,我發現同事以前用的redis鎖在刪除鎖時沒有判斷客戶端id,也就是裏面指出的錯誤示例,極端狀況下會出現。

     參考改造出了這個實現,我的以爲是沒有什麼大問題的,若有發現不妥之處,還請指教.

 

5,查閱的其餘連接

     https://docs.spring.io/spring-data/redis/docs/1.6.2.RELEASE/reference/html/   RedisTemplate調用lua腳本示例

     http://redisdoc.com/string/index.html   lua腳本操做redis 示例

     http://ifeve.com/redis-lock/  官方redis文檔,提到單實例和多實例的分佈式解決方案

相關文章
相關標籤/搜索