Examples of Redis distributed locks used in actual projects

table of Contents

1. Add dependency on pom file:

2. redis lock coding

3. Redis client operation code:

4. Use redis lock code:

Directly on the code:

1. Add dependency on pom file:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.20.RELEASE</version>
</dependency>

 

2. redis lock coding

package com.aep.aep.wcpservice.util;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * Explain:Redis分布式锁
 */
@Component
@Slf4j
public class RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁
     * @param key 唯一标志
     * @param value  当前时间+超时时间 也就是时间戳
     * @return
     */
    public boolean lock(String key,String value){
        if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//对应setnx命令
            //可以成功设置,也就是key不存在
            return true;
        }

        //判断锁超时 - 防止原来的操作异常,没有运行解锁操作  防止死锁
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        //如果锁过期
        if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){//currentValue不为空且小于当前时间
            //获取上一个锁的时间value
            String oldValue =stringRedisTemplate.opsForValue().getAndSet(key,value);//对应getset,如果key存在

            //假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
            //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
            if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) ){
                //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
                return true;
            }
        }
        return false;
    }


    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key,String value){
        try {
            String currentValue = stringRedisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value) ){
                stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key
            }
        } catch (Exception e) {
            log.error("[Redis分布式锁] 解锁出现异常了,{}",e);
        }
    }
}

3. Redis client operation code:

package com.aep.aep.wcpservice.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class RedisClient {

  @Autowired
  private StringRedisTemplate template;

  /**
   * 插入键值对
   * @param key   键
   * @param value 值
   */
  public void set(String key, String value) {
    ValueOperations<String, String> ops = template.opsForValue();
    ops.set(key, value);
  }

  /**
   * 插入键值对并设置超时时间
   * @param key    键
   * @param value  值
   * @param time 过期时间, 单位 秒
   */
  public void set(String key, String value, long time) {
    if (time < 1) {
      log.error("过期时间不能小于1");
      return;
    }
    template.opsForValue().set(key, value, time, TimeUnit.SECONDS);
  }

  /**
   * 获取值
   * @param key 键
   * @return 值
   */
  public String get(String key) {
    ValueOperations<String, String> ops = template.opsForValue();
    return ops.get(key);
  }

  /**
   * 判断key是否存在
   * @return true存在 false不存在
   */
  public boolean hasKey(String key) {
    try {
      return template.hasKey(key);
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }

  /**------------------zSet相关操作--------------------------------*/

  /**
   * 添加元素,有序集合是按照元素的score值由小到大排列
   *
   * @param key
   * @param value
   * @param score
   * @return
   */
  public Boolean zAdd(String key, String value, double score) {
    return template.opsForZSet().add(key, value, score);
  }

  /**
   *
   * @param key
   * @param values
   * @return
   */
  public Long zAdd(String key, Set<TypedTuple<String>> values) {
    return template.opsForZSet().add(key, values);
  }

  /**
   *
   * @param key
   * @param values
   * @return
   */
  public Long zRemove(String key, Object... values) {
    return template.opsForZSet().remove(key, values);
  }

  /**
   * 增加元素的score值,并返回增加后的值
   *
   * @param key
   * @param value
   * @param delta
   * @return
   */
  public Double zIncrementScore(String key, String value, double delta) {
    return template.opsForZSet().incrementScore(key, value, delta);
  }

  /**
   * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
   *
   * @param key
   * @param value
   * @return 0表示第一位
   */
  public Long zRank(String key, Object value) {
    return template.opsForZSet().rank(key, value);
  }

  /**
   * 返回元素在集合的排名,按元素的score值由大到小排列
   *
   * @param key
   * @param value
   * @return
   */
  public Long zReverseRank(String key, Object value) {
    return template.opsForZSet().reverseRank(key, value);
  }

  /**
   * 获取集合的元素, 从小到大排序
   *
   * @param key
   * @param start
   *            开始位置
   * @param end
   *            结束位置, -1查询所有
   * @return
   */
  public Set<String> zRange(String key, long start, long end) {
    return template.opsForZSet().range(key, start, end);
  }

  /**
   * 获取集合元素, 并且把score值也获取
   *
   * @param key
   * @param start
   * @param end
   * @return
   */
  public Set<TypedTuple<String>> zRangeWithScores(String key, long start,  long end) {
    return template.opsForZSet().rangeWithScores(key, start, end);
  }

  /**
   * 根据Score值查询集合元素
   *
   * @param key
   * @param min
   *            最小值
   * @param max
   *            最大值
   * @return
   */
  public Set<String> zRangeByScore(String key, double min, double max) {
    return template.opsForZSet().rangeByScore(key, min, max);
  }

  /**
   * 根据Score值查询集合元素, 从小到大排序
   *
   * @param key
   * @param min
   *            最小值
   * @param max
   *            最大值
   * @return
   */
  public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,double min, double max) {
    return template.opsForZSet().rangeByScoreWithScores(key, min, max);
  }

  /**
   *
   * @param key
   * @param min
   * @param max
   * @param start
   * @param end
   * @return
   */
  public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,double min, double max, long start, long end) {
    return template.opsForZSet().rangeByScoreWithScores(key, min, max,
            start, end);
  }

  /**
   * 获取集合的元素, 从大到小排序
   *
   * @param key
   * @param start
   * @param end
   * @return
   */
  public Set<String> zReverseRange(String key, long start, long end) {
    return template.opsForZSet().reverseRange(key, start, end);
  }

  /**
   * 获取集合的元素, 从大到小排序, 并返回score值
   *
   * @param key
   * @param start
   * @param end
   * @return
   */
  public Set<TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {
    return template.opsForZSet().reverseRangeWithScores(key, start,
            end);
  }

  /**
   * 根据Score值查询集合元素, 从大到小排序
   *
   * @param key
   * @param min
   * @param max
   * @return
   */
  public Set<String> zReverseRangeByScore(String key, double min,double max) {
    return template.opsForZSet().reverseRangeByScore(key, min, max);
  }

  /**
   * 根据Score值查询集合元素, 从大到小排序
   *
   * @param key
   * @param min
   * @param max
   * @return
   */
  public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
          String key, double min, double max) {
    return template.opsForZSet().reverseRangeByScoreWithScores(key,
            min, max);
  }

  /**
   *
   * @param key
   * @param min
   * @param max
   * @param start
   * @param end
   * @return
   */
  public Set<String> zReverseRangeByScore(String key, double min,double max, long start, long end) {
    return template.opsForZSet().reverseRangeByScore(key, min, max,
            start, end);
  }

  /**
   * 根据score值获取集合元素数量
   *
   * @param key
   * @param min
   * @param max
   * @return
   */
  public Long zCount(String key, double min, double max) {
    return template.opsForZSet().count(key, min, max);
  }

  /**
   * 获取集合大小
   *
   * @param key
   * @return
   */
  public Long zSize(String key) {
    return template.opsForZSet().size(key);
  }

  /**
   * 获取集合大小
   *
   * @param key
   * @return
   */
  public Long zZCard(String key) {
    return template.opsForZSet().zCard(key);
  }

  /**
   * 获取集合中value元素的score值
   *
   * @param key
   * @param value
   * @return
   */
  public Double zScore(String key, Object value) {
    return template.opsForZSet().score(key, value);
  }

  /**
   * 移除指定索引位置的成员
   *
   * @param key
   * @param start
   * @param end
   * @return
   */
  public Long zRemoveRange(String key, long start, long end) {
    return template.opsForZSet().removeRange(key, start, end);
  }

  /**
   * 根据指定的score值的范围来移除成员
   *
   * @param key
   * @param min
   * @param max
   * @return
   */
  public Long zRemoveRangeByScore(String key, double min, double max) {
    return template.opsForZSet().removeRangeByScore(key, min, max);
  }

  /**
   * 获取key和otherKey的并集并存储在destKey中
   *
   * @param key
   * @param otherKey
   * @param destKey
   * @return
   */
  public Long zUnionAndStore(String key, String otherKey, String destKey) {
    return template.opsForZSet().unionAndStore(key, otherKey, destKey);
  }

  /**
   * 交集
   *
   * @param key
   * @param otherKey
   * @param destKey
   * @return
   */
  public Long zIntersectAndStore(String key, String otherKey,
                                 String destKey) {
    return template.opsForZSet().intersectAndStore(key, otherKey,
            destKey);
  }


}

 

4. Use redis lock code:

Description of scenario 1:

Multi-threaded timing delay task

 

package com.aep.aep.wcpservice.service.pm.impl;

import com.aep.aep.wcpapi.dto.pm.PmDelayDownReq;
import com.aep.aep.wcpapi.response.DtoRespCode;
import com.aep.aep.wcpapi.response.DtoResponse;
import com.aep.aep.wcpservice.common.constants.requirement.Constants;
import com.aep.aep.wcpservice.entity.pm.PmProdAudit;
import com.aep.aep.wcpservice.entity.pm.PmProduct;
import com.aep.aep.wcpservice.mapper.pm.PmProdAuditMapper;
import com.aep.aep.wcpservice.mapper.pm.PmProductMapper;
import com.aep.aep.wcpservice.util.RedisClient;
import com.aep.aep.wcpservice.util.RedisLock;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Set;

/**
 * @Author :yangshilei
 * @Date :2020/5/19 17:58
 * @Description:
 */
@Slf4j
@Component
public class PmDelayOffProdTask implements Runnable{

    private static final String PM_DELAY_OFF_PROD = "pm_delay_off_prod";

    @Autowired
    private RedisClient redisClient;
    @Autowired
    private RedisLock lock;

    @Override
    @Transactional
    public void run() {
        log.info("进入延迟下架商品线程");
        // redis排序队列中查找有无到期的数据
        double min = 0D;
        long time = new Date().getTime();
        double max = (double)time;
        Long timestamp = System.currentTimeMillis() + 60000;
        try {
            // redis上锁
            lock.lock("pm_delay_off_task",timestamp.toString());
            String flag = redisClient.get("pm_delay_prod_flag");
            if("true".equals(flag)){
                log.info("有存在的key,结束");
                return;
            }
            redisClient.set("pm_delay_prod_flag","true",60);

            // 具体的业务逻辑代码
            Set<String> prodList =
                    redisClient.zRangeByScore(PM_DELAY_OFF_PROD, min, max);
            if(null != prodList && !prodList.isEmpty()){
                log.info("共有到期下架的商品个数==={}",prodList.size());
                for(String item : prodList){
                    JSONObject jsonObject = JSONObject.parseObject(item);
                    PmDelayDownReq request = JSONObject.toJavaObject(jsonObject, PmDelayDownReq.class);
                    log.info("转成对象==={}",request.toString());

                    // 将商品进行下架操作,后续业务逻辑代码省略
                    ......

                }
                Long aLong = redisClient.zRemoveRangeByScore(PM_DELAY_OFF_PROD, min, max);
                log.info("移除成员个数==={}",aLong);
            }
            log.info("线程执行结束");
        }finally {

            // redis释放锁
            lock.unlock("pm_delay_off_task",timestamp.toString());
        }

    }

   

}

 

Description of scenario 2 :

In a distributed deployment project, there are timing tasks. In order to prevent multiple deployed services from executing timing tasks at the same time, a redis distributed lock is added;

package com.aep.aep.wcpservice.service.pm.impl;

import com.aep.aep.wcpapi.dto.pm.PmAdvanceOrderDto;
import com.aep.aep.wcpapi.dto.pm.PmResult;
import com.aep.aep.wcpservice.mapper.PmAdvanceOrderMapper;
import com.aep.aep.wcpservice.util.RedisClient;
import com.aep.aep.wcpservice.util.RedisLock;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;

/**
 * @Author :yangshilei
 * @Date :2020/4/15 10:04
 * @Description:
 */
@Slf4j
@Component
public class ScheduledTask {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RedisLock lock;
    @Autowired
    private RedisClient redisClient;


    @Scheduled(cron  = "0 0/30 * * * ?" )
    @Transactional
    public void pmAdvanceOrder(){
        log.info("进入防疫专区订单状态更新定时任务,每30分钟执行一次");
        Long timestamp = System.currentTimeMillis() + 60000;

        try {
            // 1.生产环境是多服务部署,需要保证只能有一个任务在执行,故加锁。
            lock.lock("pm_advance_order_syn", timestamp.toString());
            String f = redisClient.get("pm_order_syn_flag");
            if("true".equals(f)) {
                return;
            }
            redisClient.set("pm_order_syn_flag","true", 1800);

            // 2.业务逻辑处理代码省略
            
        }finally {
            // 释放锁
            lock.unlock("pm_advance_order_syn", timestamp.toString());
        }
    }



}

 

There is a hidden danger in the use of the above distributed locks, that is, what if the locked application suddenly hangs during half of its execution?

This is a problem that is easy to encounter in production. The solution is to set a timeout period for the lock. This timeout period must be evaluated from the perspective of the response timeout period set by the business code execution time and the gateway response timeout.

Of course, there are some timing tasks that do not need to consider the timeout of the gateway response. However, with the increase of business data, the execution time may become longer and longer, and the time set at the beginning may no longer meet the demand for later business execution time. In this case, the way I think of is to use a cache or database to record the execution time from the start to the end of each business. If it is greater than the default value, it will be used as the new default value. The basis for the timeout period of a lock.

If there is any better way, you are welcome to comment and add.

Guess you like

Origin blog.csdn.net/qq_37488998/article/details/112709979