Redis 是缓存型数据库,拥有很高的性能,它能够很轻易地实现分布式锁,并且使用分布式锁能够非常有效的解决程序错乱的问题,那redis的分布式锁实现原理是什么?下面来我们就来给大家讲解一下。
1.通过setnx(lock_timeout)实现,如果设置了锁返回1, 已经有值没有设置成功返回0
2.死锁问题:通过实践来判断是否过期,如果已经过期,获取到过期时间get(lockKey),然后getset(lock_timeout)判断是否和get相同,相同则证明已经加锁成功,因为可能导致多线程同时执行getset(lock_timeout)方法,这可能导致多线程都只需getset后,对于判断加锁成功的线程, 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)过期时间,防止多个线程同时叠加时间,导致锁时效时间翻倍。
代码:
/** * @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; } }
运行结果如下图:
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工具网。
推荐阅读: