redis实现分布式锁

目录

  • 1、为什么要使用分布式锁
  • 2、示例
    • 2.1 底层锁
    • 2.2 redis
    • 2.测试

1、为什么要使用分布式锁

为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题

2、示例

利用redis操作的原子性,为资源加锁,判断是否有值,有值缓存,没有返回false,这些判断和操作交给redis来完成,这样保证java中同一时刻不会同时拿到锁(redis操作的原子性)
非阻塞,不会等待锁的一个demo,获取锁失败直接return
这只是底层的锁,关于要用到的时候,编写service的业务逻辑,注意设置过期时间,以免死锁

2.1 底层锁

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.fh.system.dao.RedisOperator;
import com.fh.system.entity.ResultCode;
import lombok.Data;
import lombok.ToString;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Data
@Component
@ToString
public class Lock {
    private String lockKey;
    private Object data;        //锁内数据
    private Long snowflakeId;   //雪花id

    public Lock() {
    }

    public Lock(String lockKey, Object data, Long snowflakeId) {
        this.lockKey = lockKey;
        this.data = data;
        this.snowflakeId = snowflakeId;
    }

    /**
     * 加锁
     */
    public ResultCode<Lock> lockUp(String key, Object data){return lockUp(key, data, null);}

    public ResultCode<Lock> lockUp(String key, Object data, Long timeout) {
        SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(Thread.currentThread().getId());
        Lock lock = new Lock(key, data, snowflakeIdWorker.nextId());
        //直接加锁
        Boolean lockFlag = getDistributedLock(key, JSONObject.toJSONString(lock), timeout);
        if (lockFlag) { //成功
            return ResultCode.getSuccessReturn(lock);
        } else {        //失败
            Lock lockFailure = JSONObject.parseObject(getKey(key), Lock.class);
            lockFailure.setSnowflakeId(null);//抹除锁id
            return ResultCode.getFailureReturn(ResultCode.CODE_RESOURCE_OCCUPY_ERROR, ResultCode.MSG_RESOURCE_OCCUPY_ERROR, lockFailure);
        }
    }

    /**
     * 释放锁
     * @param id 加锁的时候返回的雪花id
     */
    public ResultCode unLock(String key,Long id){
        Boolean aBoolean = releaseDistributedLock(key, id);
        if (aBoolean) {
            return ResultCode.getSuccess();
        } else {
            return ResultCode.getFailure();
        }
    }

    /**
     * 释放锁
     */
    public ResultCode unLock(String key,Lock lock){
        if (lock.getSnowflakeId() == null) {
            return ResultCode.getFailure("锁id不能为空!");
        }
        Boolean aBoolean = releaseDistributedLock(key, lock.getSnowflakeId());
        if (aBoolean) {
            return ResultCode.getSuccess();
        } else {
            return ResultCode.getFailure();
        }
    }



    //缓存中间缓存key值
    @Autowired
    private RedisOperator redisUtil;

    private Boolean getDistributedLock(String key, Object data, Long timeOut) {
        if (timeOut == null) {
            redisUtil.getDistributedLock(key, data);
        }
        return redisUtil.getDistributedLock(key, data, timeOut);
    }
    private Boolean releaseDistributedLock(String key, Long id){
        String data = TypeUtils.castToString(redisUtil.get(key));
        if (StringUtils.isEmpty(data)) {
            return true;//锁已不在,返回true
        }
        Lock lock = JSONObject.parseObject(data, Lock.class);
        if (lock.getSnowflakeId().equals(id)) {
            redisUtil.del(key);
            return true;
        } else {
            return false;
        }
//        return redisUtil.releaseDistributedLock(key, data);
    }
    private String getKey(String key){
        return TypeUtils.castToString(redisUtil.get(key));
    }




}

2.2 redis

需要 spring-data-redis 版本在2.1及以上

redisTemplate.opsForValue().setIfAbsent(lockKey, data, expireTime, TimeUnit.SECONDS);//否者没有outtime这个参数
		<dependency>
            <groupId>org.springframework.datagroupId>
            <artifactId>spring-data-redisartifactId>
            <version>2.1.0.RELEASEversion>
        dependency>
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisOperator {
    @Autowired
    RedisTemplate redisTemplate;
    /**
     * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
     *
     * @param key
     * @return
     */
    public long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 实现命令:expire 设置过期时间,单位秒
     *
     * @param key
     * @return
     */
    public void expire(String key, long timeout) {
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:INCR key,增加key一次
     *
     * @param key
     * @return
     */
    public long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 实现命令:DEL key,删除一个key
     *
     * @param key
     */
    public void del(String key) {
        redisTemplate.delete(key);
    }

    // String(字符串)

    /**
     * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
     *
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
     *
     * @param key
     * @param value
     * @param timeout
     *            (以秒为单位)
     */
    public void set(String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令:GET key,返回 key所关联的字符串值。
     *
     * @param key
     * @return value
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // Hash(哈希表)

    /**
     * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
     *
     * @param key
     * @param field
     * @param value
     */
    public void hset(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
     *
     * @param key
     * @param field
     * @return
     */
    public Object hget(String key, String field) {
        return (String) redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
     *
     * @param key
     * @param fields
     */
    public void hdel(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // List(列表)

    /**
     * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long lpush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 实现命令:LPOP key,移除并返回列表 key的头元素。
     *
     * @param key
     * @return 列表key的头元素。
     */
    public Object lpop(String key) {
        return (String)redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后,列表的长度。
     */
    public long rpush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 加锁
     * @param lockKey 锁的key
     * @param data 竞争者id
     * @param expireTime 锁超时时间,超时之后锁自动释放
     * @return
     */
    public boolean getDistributedLock(String lockKey, Object data, Long expireTime) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, data, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 加锁
     * @param lockKey 锁的key
     * @param data 竞争者id
     * @return
     */
    public boolean getDistributedLock(String lockKey, Object data) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, data);
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 锁持有者id(这个示例demo中没有用到,锁中存储的不是用户id,为了更加通用,Lock的data类型为object)
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = (Long) redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList(lockKey), requestId);
        return RELEASE_SUCCESS.equals(result);
    }

}

2.测试

起三个线程,演示锁的抢占、解锁

import com.fh.system.entity.ResultCode;
import com.fh.system.service.tools.impl.DebugLockService;
import com.fh.system.util.Lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;

/**
 * 锁
 *
 * @author Administrator
 * @date 2022/11/16/016 19:19
 */

@RestController
@RequestMapping(("/debugLock"))
public class DebugLockController {
    @Autowired
    private Lock lock;

    @RequestMapping("/test1")
    public void test1() throws InterruptedException {
        System.out.println("进入接口...");
        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    // 代码段1
                    ResultCode<Lock> resultCode1 = lock.lockUp("ziyuan1", "张三");
                    System.out.println("代码块1:" + resultCode1);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unLock("ziyuan1", resultCode1.getData().getSnowflakeId());//释放资源
                }, ForkJoinPool.commonPool()),
                CompletableFuture.runAsync(() -> {
                    // 代码段2,在 张三 获取资源锁住的时候尝试获取
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ResultCode resultCode2 = lock.lockUp("ziyuan1", "李四");
                    System.out.println("代码块2:" + resultCode2);
                }, ForkJoinPool.commonPool()),
                CompletableFuture.runAsync(() -> {
                    // 代码段3,在 张三 释放资源后再尝试获取
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ResultCode resultCode3 = lock.lockUp("ziyuan1", "王五");
                    System.out.println("代码块3:" + resultCode3);
                }, ForkJoinPool.commonPool()));
    }

	//三个线程同时尝试获取锁,模拟并发
	@RequestMapping("/test2")
    public void test2() throws InterruptedException {
        System.out.println("进入接口...");
        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    // 代码段1
                    ResultCode<Lock> resultCode1 = lock.lockUp("ziyuan2", "张三", 60l * 10);
                    System.out.println("代码块1:" + resultCode1);

                }, ForkJoinPool.commonPool()),
                CompletableFuture.runAsync(() -> {
                    // 代码段2,在 张三 获取资源锁住的时候尝试获取
                    ResultCode<Lock> resultCode1 = lock.lockUp("ziyuan2", "李四", 60l * 10);
                    System.out.println("代码块2:" + resultCode1);

                }, ForkJoinPool.commonPool()),
                CompletableFuture.runAsync(() -> {
                    // 代码段3,在 张三 释放资源后再尝试获取
                    ResultCode<Lock> resultCode1 = lock.lockUp("ziyuan2", "王五", 60l * 10);
                    System.out.println("代码块3:" + resultCode1);

                }, ForkJoinPool.commonPool()));
    }



}

效果1:
代码块1最先执行获取锁的操作,还没有释放锁的时候,代码块2尝试获取锁,得到失败的结果
代码块1释放锁后,代码块3尝试获取锁,获取成功
在这里插入图片描述
效果2:
三个线程同时尝试获取锁,模拟并发,跑三次(每次跑完释放锁的过程省略)
只有一个线程能够获取锁成功…
redis实现分布式锁_第1张图片

你可能感兴趣的:(springcloud,redis,分布式,java)