锁系列三:分布式锁之redis实现、zookeeper实现

1.分布式锁

分布式锁几乎是在微服务架构下的标配。在前面的系列已经讲过如何通过redisson实现分布式锁(redisson本身已经封装好了,开箱即用),不过得引入redisson的依赖,这里将配合lua脚本自行实现redis的分布式锁。同时还将实现zookeeper版的分布式锁

2.redis的分布式锁实现

2.1Redis依赖引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2RedisLock实现

application.yml配置

spring:
  redis:
    host: ${myurl.redis}
    database: 0
    password: root1234567890

BaseRedisConfig.java

import com.lk.basics.application.component.redis.RedisLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration
public class BaseRedisConfig {
    @Bean
    public RedisLock redisLock(StringRedisTemplate stringRedisTemplate) {
        return new RedisLock(stringRedisTemplate);
    }


    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }
    
}

RedisLock.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 自行实现redis分布式锁,
 * 直接使用的StringRedisTemplate(来自spring-data-redis)。通过配合Lua脚本实现分布式锁
 */
public class RedisLock {

    private static final Logger log = LoggerFactory.getLogger(RedisLock.class);

    public static final long DEFAULT_WAIT_TIME = 5 * 1000L;


    private final StringRedisTemplate stringRedisTemplate;

    private String keyPrefix = "";


    public RedisLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public RedisLock(StringRedisTemplate stringRedisTemplate, String keyPrefix) {
        Objects.requireNonNull(stringRedisTemplate, "stringRedisTemplate require non null");
        this.stringRedisTemplate = stringRedisTemplate;
        if (keyPrefix != null) {
            this.keyPrefix = keyPrefix;
        }
    }

    /**
     * 获取锁并执行任务, 并提供失败后回调方法
     *
     * @param lockKey           锁的key
     * @param task              获取锁后执行的任务
     * @param failedCallback    获取失败 或 执行失败 后的回调, 若获取锁失败,则参数为null. 如果 failedCallback 为 null, 则默认将异常抛出
     * @param waitTimeInMills   获取锁等待时间. <=0 不等待, 单位 ms
     * @param expireTimeInMills 锁超时时间
     */
    public void lockAndDo(String lockKey, Runnable task, Consumer<Exception> failedCallback, long waitTimeInMills, long expireTimeInMills) {
        Objects.requireNonNull(task, "task require non null");

        String lockId = UUID.randomUUID().toString();
        boolean locked = waitLock(lockKey, lockId, waitTimeInMills, expireTimeInMills);

        if (failedCallback == null) {
            failedCallback = getDefaultFailedCallbackThrow(lockKey);
        }

        try {
            if (locked) {
                task.run();
            } else {
                failedCallback.accept(null);
            }
        } catch (Exception e) {
            failedCallback.accept(e);
        } finally {
            unLock(lockKey, lockId);
        }
    }


    /**
     * 获取锁并执行任务, 返回执行后的结果, 或失败后回调后的结果
     *
     * @param lockKey           锁的key
     * @param task              获取锁后执行的任务
     * @param failedCallback    获取失败 或 执行失败 后的回调, 若获取锁失败,则参数为null. 如果 failedCallback 为 null, 则默认将异常抛出
     * @param waitTimeInMills   获取锁等待时间. <=0 不等待, 单位 ms
     * @param expireTimeInMills 锁超时时间
     * @param <T>               执行任务后的返回值
     */
    public <T> T lockAndDo(String lockKey, Callable<T> task, Function<Exception, T> failedCallback, long waitTimeInMills, long expireTimeInMills) {
        Objects.requireNonNull(task, "task require non null");

        String lockId = UUID.randomUUID().toString();
        boolean locked = waitLock(lockKey, lockId, waitTimeInMills, expireTimeInMills);

        if (failedCallback == null) {
            failedCallback = getDefaultFailedCallbackThrowForFunc(lockKey);
        }

        try {
            if (locked) {
                return task.call();
            } else {
                return failedCallback.apply(null);
            }
        } catch (Exception e) {
            return failedCallback.apply(e);
        } finally {
            unLock(lockKey, lockId);
        }
    }

    private Consumer<Exception> getDefaultFailedCallbackThrow(String lockKey) {
        return e -> {
            if (e == null) {
                throw new RuntimeException("获取锁失败: lockKey=" + lockKey);
            }
            throw new RuntimeException(e);
        };
    }

    private <T> Function<Exception, T> getDefaultFailedCallbackThrowForFunc(String lockKey) {
        return e -> {
            if (e == null) {
                throw new RuntimeException("获取锁失败: lockKey=" + lockKey);
            }
            throw new RuntimeException(e);
        };
    }


    public boolean waitLock(String lockKey, String lockId, long expireTimeInMills) {
        return waitLock(lockKey, lockId, DEFAULT_WAIT_TIME, expireTimeInMills);
    }


    /**
     * 阻塞式获取锁
     *
     * @param lockKey           锁的key
     * @param lockId            锁的id, 防止被误删, uuid
     * @param waitTimeInMills   获取锁等待时间, 在该时间内不断重试, <=0 不等待; 单位: ms
     * @param expireTimeInMills 锁超时时间, 单位: ms
     * @return 是否成功获取锁
     */
    public boolean waitLock(String lockKey, String lockId, long waitTimeInMills, long expireTimeInMills) {
        long lastWaitTime = waitTimeInMills;
        while (true) {
            boolean locked = tryLock(lockKey, lockId, expireTimeInMills);
            if (locked) {
                return true;
            }
            if (lastWaitTime <= 0) {
                return false;
            }

            // 平均间隔每100ms 尝试加一次锁,
            long sleepTime = 75 + ThreadLocalRandom.current().nextInt(50);
            lastWaitTime -= sleepTime;
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException ignore) {
            }
        }
    }

    /**
     * 尝试获取锁, 获取失败直接返回
     *
     * @param lockKey           锁的key
     * @param lockId            锁的id, 防止 误删
     * @param expireTimeInMills 锁超时时间, 单位: ms
     * @return 是否获取到锁
     */
    public boolean tryLock(String lockKey, String lockId, long expireTimeInMills) {
        try {
            String key = keyPrefix + lockKey;
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, lockId, expireTimeInMills, TimeUnit.MILLISECONDS);
            return lock != null && lock;
        } catch (Exception e) {
            log.warn("redis lock error key={}, value={}", lockKey, lockId, e);
        }
        return false;
    }

    private static final String UN_LOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] " +
                    "then return redis.call('del', KEYS[1]) " +
                    "else return 0 end";

    private static final Long UN_LOCK_SUCCESS = 1L;


    public boolean unLock(String lockKey, String lockId) {
        try {
            String key = keyPrefix + lockKey;
            DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
            defaultRedisScript.setScriptText(UN_LOCK_SCRIPT);
            defaultRedisScript.setResultType(Long.class);
            Long execute = stringRedisTemplate.execute(defaultRedisScript, Collections.singletonList(key), lockId);
            return UN_LOCK_SUCCESS.equals(execute);
        } catch (Exception e) {
            log.warn("redis unlock error key={}, value={}", lockKey, lockId, e);
        }
        return false;
    }

}

2.3使用

    @Resource
    private RedisLock redisLock;
    
    @GetMapping("/redisLock/lock")
    public String redisLockTest() throws InterruptedException {
        //加锁并处理业务
        String redisLockKey = "redisLock_key";
        long waitTimeInMills = 30 * 1000L;
        long expireTimeInMills = 30 * 1000L;
        redisLock.lockAndDo(redisLockKey,
                () -> {
                    log.info("开始====》完成一些业务逻辑");
                    try {
                        Thread.sleep(1000 * 10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("已经====》完成上述业务逻辑");
                }
                , e -> log.error("There have some error", e)
                , waitTimeInMills
                , expireTimeInMills);

        return "执行正常";
    }

3.zookeeper的分布式锁实现

3.1zookeeper相关依赖引入

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
</dependency>

3.2配置

application.yml

zookeeper:
  address: ${myurl.zk}     #zookeeper Server地址,如果有多个,使用","隔离。例如 ip1:port1,ip2:port2,ip3:port3
  retryCount: 5               #重试次数
  elapsedTimeMs: 5000         #重试间隔时间
  sessionTimeoutMs: 30000     #Session超时时间
  connectionTimeoutMs: 10000  #连接超时时间

ZookeeperProperties.java

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "zookeeper")
public class ZookeeperProperties {

    /** 重试次数 */
    private int retryCount;

    /** 重试间隔时间 */
    private int elapsedTimeMs;

    /**连接地址 */
    private String address;

    /**Session过期时间 */
    private int sessionTimeoutMs;

    /**连接超时时间 */
    private int connectionTimeoutMs;

}

ZookeeperConfig.java

import com.lk.basics.infrastructure.component.ZookeeperProperties;
import org.apache.curator.retry.RetryNTimes;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.curator.framework.CuratorFrameworkFactory;

@Configuration
public class ZookeeperConfig {

    /**
     * 创建 CuratorFramework 对象并连接 Zookeeper
     *
     * @param zookeeperProperties 从 Spring 容器载入 zookeeperProperties Bean 对象,读取连接 ZK 的参数
     * @return CuratorFramework
     */
    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework(ZookeeperProperties zookeeperProperties) {
        return CuratorFrameworkFactory.newClient(
                zookeeperProperties.getAddress(),
                zookeeperProperties.getSessionTimeoutMs(),
                zookeeperProperties.getConnectionTimeoutMs(),
                new RetryNTimes(zookeeperProperties.getRetryCount(),
                        zookeeperProperties.getElapsedTimeMs()));
    }

}

3.3ZkLock实现

ZkLock.java

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ZkLock {

    @Autowired
    CuratorFramework curatorFramework;

    /**
     * 节点名称
     */
    public static final String NODE_PATH = "/lock-space/%s";

    /**
     * 尝试获取分布式锁
     *
     * @param key        分布式锁 key
     * @param expireTime 超时时间
     * @param timeUnit   时间单位
     * @return 超时时间单位
     */
    public InterProcessMutex tryLock(String key, int expireTime, TimeUnit timeUnit) {
        try {
            InterProcessMutex mutex = new InterProcessMutex(curatorFramework, String.format(NODE_PATH, key));
            boolean locked = mutex.acquire(expireTime, timeUnit);
            if (locked) {
                log.info("申请锁(" + key + ")成功");
                return mutex;
            }
        } catch (Exception e) {
            log.error("申请锁(" + key + ")失败,错误:{}", e);
        }
        log.warn("申请锁(" + key + ")失败");
        return null;
    }

    /**
     * 释放锁
     *
     * @param key          分布式锁 key
     * @param lockInstance InterProcessMutex 实例
     */
    public void unLock(String key, InterProcessMutex lockInstance) {
        try {
            lockInstance.release();
            log.info("解锁(" + key + ")成功");
        } catch (Exception e) {
            log.error("解锁(" + key + ")失败!");
        }
    }

}

3.3使用

    @Resource
    private ZkLock zkLock;

    @GetMapping("/lock")
    public String redissonLock() throws InterruptedException {
        String zkLockKey = "redisLock_key";
        //尝试加锁
        final InterProcessMutex lock = zkLock.tryLock(zkLockKey, 30, TimeUnit.SECONDS);
        //成功获取到锁
        if (lock != null) {
            // 业务代码
            log.info("开始====》完成一些业务逻辑");
            Thread.sleep(1000 * 10);
            log.info("已经====》完成上述业务逻辑");
            zkLock.unLock(zkLockKey, lock);//解锁
        }
        return "执行正常";
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是lk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值