集群或分布式部署环境--用Redis实现分布式锁

正好学习Redis的高级篇幅-分布式锁,然后发现分布式锁是能解决我现在所处的集群环境问题,先来说下我的问题现场是k8s集群,然后有定时任务是要删除过期时间,那么问题来了,4台集群,任务一到,同时执行定期任务,那么我怎么能让任务只有一台机器执行就可以了呢,啊,想到了,当然都会想到标题麽分布式锁~~~

但是有想过没有,为啥就用分布式锁,为啥不用java自带的锁,什么synchronized,原因是synchronized作用域锁住的是一个进程下的线程,但是我们是集群或者分布式下部署那肯定不是一个java进程,多个进程就肯定不行了,也就是这样才有的分布式锁。

1.Redis分布式锁介绍及设计

按照上面的简述就能明白分布式锁是什么意思了分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一系统的不同主机之间共享了某个资源时,往往通过互斥来防止彼此干扰。

  1. 假如要通过定时任务将数据库的数据刷新到redis中,那么同样的三个server服务可能都会一起执行定时任务,那么需要进行加锁,假如第一个机器任务先进来没有锁,加锁然后进行操作,其他服务器一起执行发现锁住了就不能执行,直到任务锁释放。

1.2.分布式设计目的

可以保证在分布式部署的应用集群中,同一方法在同一操作只能被一台机器上的线程执行

1.3 分布式设计要求

这把锁要是一把可重入锁(避免死锁)

这把锁有高可用的获取锁和释放锁功能

这把锁获取锁和释放锁性能要好

2.Redis分布式锁流程图

  1. 在获取共享资源的时候,只能有一个资源进行操作,所以如果有两台服务器,那么server1获取锁的时候发现正好能获取到,那么在通过获取锁的时候采用超时时间来设置redis,为什么呢,因为可能会有获取锁时间长的操作那么一直不释放,下一次任务也就会被堵塞住了。
  2. 获取成功以后相当于此次任务没有被其他执行过,可以让这台服务器执行,那么执行任务。
  3. 执行完毕千万不要忘了解锁,也就是进行锁的释放,可以把锁释放放入try,catch的finaly里面,这样不管什么情况最终都会走finaly,也就是最终都会释放锁。

 3.分布式代码实战

本示例以springboot框架为示例,整合redis的RedisTemplate,开启一个定时任务实现分布式锁,打成jar包部署两台linux服务器(当然一台机器,部署两个项目,启动两个不同的端口一样能测试),来模拟集群下定时任务问题,当然除了这种场景下还有可以是高并发场景下秒杀,对库存啊,商品啊非常敏感不能出错也是可以使用分布式锁的。那么接下来用代码实现一下

对了,写代码之前说下分布式锁实现其实采用redis的setnx,setnx是什么呢,就是存储数据的时候,如果对应key的value值数据之前没有存储过,就会返回1,就会进行存储,也就代表true成功,如果给的key和value是之前存储过的就会返回0,程序里也就是false

package com.demo.redis.dfdemoredis.schedule;

import com.demo.redis.dfdemoredis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.net.InetAddress;

/**
 * @Author df
 * @Date 2020/5/3 16:55
 * @Version 1.0
 */
@Service
public class LockNxExJob {

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

    @Autowired
    private RedisTemplate redisTemplate;

    private static String LOCK_PREFIX = "prefix_";

    private static int LOCK_EXPIRE = 100;

    @Scheduled(cron = "0/10 * * * * *")
    public void lockJob() {
        // key
        String lock = LOCK_PREFIX + "LockNxExJob";
        try {
            String hostName = InetAddress.getLocalHost().getHostName();
            log.info("当前执行的机器是:" + hostName);
            // setIfAbsent对应的就是setnx操作
            // setnx就是没有存储当前key和value就会存储并且返回true,如果有key和value存在就不存储并且直接返回false
            boolean nxRet = redisTemplate.opsForValue().setIfAbsent(lock, hostName);
            // 获取锁成功
            if (nxRet) {
                // 设置占用
                redisTemplate.opsForValue().set(lock, hostName, 3600);
                log.info("start lock success");
                // 可以操作其他功能
                Thread.sleep(50);
            } else {
                // 获取锁失败
                Object value = redisTemplate.opsForValue().get(lock);
                if (value != null) {
                    log.info("start lock fail,值为:" + value);
                } else {
                    log.info("start lock fail,值为:null");
                }
            }
        } catch (Exception e) {

            log.error("error", e);
        } finally {
            // 释放锁
            redisTemplate.delete(lock);
        }

    }
}

这里我们把释放锁放入了funally里,就算是写了return也会先走finally

还有一个点就是redis存储了锁,但是因为某一台服务器崩溃就没有释放,那么就会造成其他机器任务不能进行访问,解决办法就是加超时

 redisTemplate.expire(lock, 5, TimeUnit.SECONDS);

然后放入我准备好的两台服务器,把jar包放入,启动两台项目,看打印日志

我是这个vm_0_14_centos先启动起来的,所以执行了任务

那么看下另一台因为同时执行没有得到锁,所以一直获取锁失败,,而一直被vm_0_14_centos机器占用

直到vm_0_14_centos那台机器释放,这台机器用获取到了锁

那么这边获取到了,那边机器肯定就获取失败了

这样就实现了,多台机器定时任务执行期间,只有一台能够真正执行

然后我把这个分布式锁应用到我在博客开头说的,定时任务删除数据库过期时间,代码片段如下

 以上就是今天分布式锁的全部内容!

 

猜你喜欢

转载自blog.csdn.net/dfBeautifulLive/article/details/105972460