1. 程式人生 > >老張開發 springboot下Redistemplate實現併發鎖

老張開發 springboot下Redistemplate實現併發鎖

謹以此記錄學習redis併發鎖學習筆記:

      基於傳統的單機模式下的併發鎖,已遠遠不能滿足當下高併發大負載的情況,當下常用的併發處理如下

       1、使用synchronized關鍵字

        2、select    for update   樂觀鎖

        3、使用redis實現同步鎖

方案一 適合單機模式,

方案二 雖然滿足多節點服務例項但 對變更操作的吞吐量有影響

方案三 基於redis nosql資料庫  在效率與橫向擴充套件方面都大大優於前兩種方案

redis  單執行緒   在自身設計上一定程度可避免想成不安全  再者其效率高於關係型資料庫

本次實現鎖機制  基於redis 的兩個指令 查詢 redis.cn  網站  

指令一:SETNX key value

key設定值為value,如果key不存在,這種情況下等同SET命令。 當key存在時,什麼也不做。SETNX是”SET if Not eX

ists”的簡寫。

返回值

Integer reply, 特定值:

  • 1 如果key被設定了
  • 0 如果key沒有被設定

例子

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

 指令二:GETSET key value

自動將key對應到value並且返回原來key對應的value。如果key存在但是對應的value不是字串,就返回錯誤。

返回值

bulk-string-reply: 返回之前的舊值,如果之前Key不存在將返回nil

例子

redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis> 

 步驟一:引入依賴

 <!-- 配置redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>

步驟二: 配置redis連線

public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("redis異常:key=[{}]", key, exception);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("redis異常:key=[{}]", key, exception);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.error("redis異常:key=[{}]", key, exception);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("redis異常:", exception);
            }
        };
    }
}

 

 程式碼demo:將Redistemplate 注入需要使用的地方

public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * 加鎖
     * @param key   鍵
     * @param value 當前時間 + 超時時間
     * @return 是否拿到鎖
     */
    public boolean lock(String key, String value) {
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        String currentValue = redisTemplate.opsForValue().get(key);
        //如果鎖過期
        if (!StringUtils.isEmpty(currentValue)
                && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            //是否已被別人搶佔
            return StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue);
        }
        return false;
    }

    /**
     * 解鎖
     *
     * @param key   鍵
     * @param value 當前時間 + 超時時間
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("redis解鎖異常");
        }
    }
}

使用demo:

 private void getJsapiTicketCatch(String appid, Map<String, String> resultMap) {
        long expr = 90 * 60 * 1000L;
        long ex = 5 * 1000L;
        String value = String.valueOf(System.currentTimeMillis() + ex);
        boolean lock = redisLock.lock(appid, value);
        //獲取鎖
        if (lock) {
            String tick = redisTemplate.opsForValue().get("zqs"+appid);
            if (StringUtils.isNotBlank(tick)) {
                resultMap.put("tick",tick);
            } else {
                //token 通過appid
                BigAuthorizationInfo authorInfoByAppidService = authorizedService.getAuthorInfoByAppidService(appid);
                if (authorInfoByAppidService == null
                        || StringUtils.isBlank(authorInfoByAppidService.getAuthorizer_access_token())) {
                    resultMap.put("tick",null);
                }else {
                    String token = authorInfoByAppidService.getAuthorizer_access_token();
                    String jsapiTicket = WeShareUtils.GetJsapiTicket(token);
                    if (StringUtils.isBlank(jsapiTicket)) {
                        resultMap.put("tick",null);
                    }else {
                        resultMap.put("tick",jsapiTicket);
                        redisTemplate.opsForValue().set("zqs"+appid, jsapiTicket, expr,TimeUnit.MILLISECONDS);
                    }
                }
            }
            //釋放鎖
            redisLock.unlock(appid, value);
        }