redis的分布式锁实现原理是什么?redis分布式锁有哪些命令?

Redis 是缓存型数据库,拥有很高的性能,它能够很轻易地实现分布式锁,并且使用分布式锁能够非常有效的解决程序错乱的问题,那redis的分布式锁实现原理是什么?下面来我们就来给大家讲解一下。

redis的分布式锁实现原理.png

1.通过setnx(lock_timeout)实现,如果设置了锁返回1, 已经有值没有设置成功返回0

2.死锁问题:通过实践来判断是否过期,如果已经过期,获取到过期时间get(lockKey),然后getset(lock_timeout)判断是否和get相同,相同则证明已经加锁成功,因为可能导致多线程同时执行getset(lock_timeout)方法,这可能导致多线程都只需getset后,对于判断加锁成功的线程, 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)过期时间,防止多个线程同时叠加时间,导致锁时效时间翻倍。

redis的分布式锁实现原理是什么?redis分布式锁有哪些命令?.jpg

代码:

/**
* @author yaoxin
* @date 2018/8/13下午5:04
*/
public class RedisLockTest
{
    public static final String url = "jdbc:mysql://127.0.0.1:3306/ly?characterEncoding=UTF-8";
    public static final String name = "com.mysql.jdbc.Driver";
    public static final String user = "root";
    public static final String password = "";
    public static void main(String[] args)
    {
        Integer count = 50;
        while (count > 0)
        {
            count--;
            new Thread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        Jedis jedis = new Jedis("127.0.0.1", 6379);
                        jedis.auth("1234");
                        String lock = lock(jedis);
                        if (lock != null)
                        {
                            Statement statement = null;
                            Connection conn = null;
                            ResultSet resultSet = null;
                            try
                            {
                                Class.forName(name); // 指定连接类型
                                conn = DriverManager.getConnection(url, user, password); // 获取连接
                                statement = conn.createStatement(); // 准备执行语句
                                String querySql = "SELECT id,name,count FROM production WHERE id=2";
                                resultSet = statement.executeQuery(querySql);
                                int count = 0;
                                while (resultSet.next())
                                {
                                    System.out.println(Thread.currentThread()
                                        .getName() + "抢到了锁 id: " + resultSet.getString("id") +
                                        " name: " + resultSet.getString("name") +
                                        " count: " + resultSet.getString("count"));
                                    count = Integer.valueOf(resultSet.getString("count"));
                                }
                                String updateSql = "UPDATE production SET count=" + (count - 1) +
                                    " WHERE id=2";
                                int rows = statement.executeUpdate(updateSql);
                                if (rows > 0)
                                {
                                    System.out.println("更新成功" + Thread.currentThread()
                                        .getName() + " 库存剩余:" + (count - 1));
                                    System.out.println(Thread.currentThread()
                                        .getName() + " === > >开始解锁");
                                    boolean unlock = unlock(jedis, lock);
                                    if (unlock)
                                        System.out.println(Thread.currentThread()
                                            .getName() + " === > >解锁成功");
                                }
                                else
                                {
                                    System.out.println("更新失败" + Thread.currentThread()
                                        .getName());
                                }
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            finally
                            {
                                try
                                {
                                    if (conn != null)
                                        conn.close();
                                    if (statement != null)
                                        statement.close();
                                    if (resultSet != null)
                                        resultSet.close();
                                }
                                catch (Exception e)
                                {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }, "线程" + count)
                .start();
        }
    }
    public static String lock(Jedis jedis)
    {
        try
        {
            while (true)
            {
                String lockTime = Long.valueOf(jedis.time()
                    .get(0)) + 5 + "";
                if (jedis.setnx("lock", lockTime) == 1)
                {
                    jedis.expire("lock", 5);
                    return lockTime;
                }
                String lock = jedis.get("lock");
                if (!StringUtils.isEmpty(lock) && Long.valueOf(lock) < Long.valueOf(jedis.time()
                        .get(0)))
                {
                    String oldLockTime = jedis.getSet("lock", lockTime);
                    if (!StringUtils.isEmpty(oldLockTime) && oldLockTime.equals(lock))
                    {
                        return lockTime;
                    }
                }
                Thread.sleep(100);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    public static boolean unlock(Jedis jedis, String lockTag)
    {
        if (lockTag.equals(jedis.get("lock")))
        {
            jedis.del("lock");
            return true;
        }
        return false;
    }
}

运行结果如下图:

1.jpg

redis分布式锁有哪些命令?

分布式锁的本质其实就是要在 Redis 里面占一个“坑”,当别的进程也要来占时,发现已经有人蹲了,就只好放弃或者稍做等待。这个“坑”同一时刻只允许被一个客户端占据,也就是本着“先来先占”的原则。

1) 常用命令

Redis 分布式锁常用命令如下所示:

SETNX key val:仅当key不存在时,设置一个 key 为 value 的字符串,返回1;若 key 存在,设置失败,返回 0;

Expire key timeout:为 key 设置一个超时时间,以 second 秒为单位,超过这个时间锁会自动释放,避免死锁;

DEL key:删除 key。

上述 SETNX 命令相当于占“坑”操作,EXPIRE 是为避免出现意外用来设置锁的过期时间,也就是说到了指定的过期时间,该客户端必须让出锁,让其他客户端去持有。

但还有一种情况,如果在 SETNX 和 EXPIRE 之间服务器进程突然挂掉,也就是还未设置过期时间,这样就会导致 EXPIRE 执行不了,因此还是会造成“死锁”的问题。为了避免这个问题,Redis 作者在 2.6.12 版本后,对 SET 命令参数做了扩展,使它可以同时执行 SETNX 和 EXPIRE 命令,从而解决了死锁的问题。

直接使用 SET 命令实现,语法格式如下:

SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

EX second:设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。

PX millisecond:设置键的过期时间为毫秒。SET key value PX millisecond 效果等同于 PSETEX key millisecondvalue 。

NX:只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。

XX:只在键已经存在时,才对键进行设置操作。

2) 命令应用

下面进行简单的命令演示:

127.0 .0 .1: 6379 > SETNX WEBNAME www.biancheng.net(integer) 1
127.0 .0 .1: 6379 > EXPIRE WEBNAME 60(integer) 1
127.0 .0 .1: 6379 > GET WEBNAME "www.biancheng.net"
127.0 .0 .1: 6379 > TTL WEBNAME(integer) 33
127.0 .0 .1: 6379 > SET name www.biancheng.net EX 60 NX
OK

我们需要将redis分布式锁命令记住并且合理的使用,这样在实际应用上还能解决死锁的问题!最后大家如果想要了解更多java架构师知识,敬请关注奇Q工具网。

推荐阅读:

java中级工程师证书有用吗?java中级工程师证书怎么考?

qt安装后怎么添加kits?qt安添加kits方法

在json如何处理换行符?json解析有哪些方法?