分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。
在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
使用setnx、getset、expire、del这4个redis命令实现 (推荐学习:Redis视频教程)
setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。 命令格式:SETNX key value;使用:只在键 key 不存在的情况下,将键 key 的值设置为 value 。若键 key 已经存在, 则 SETNX 命令不做任何动作。返回值:命令在设置成功时返回 1 ,设置失败时返回 0 。
getset 命令格式:GETSET key value,将键 key 的值设为 value ,并返回键 key 在被设置之前的旧的value。返回值:如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil 。当键 key 存在但不是字符串类型时,命令返回一个错误。
expire 命令格式:EXPIRE key seconds,使用:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。返回值:设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。
del 命令格式:DEL key [key …],使用:删除给定的一个或多个 key ,不存在的 key 会被忽略。返回值:被删除 key 的数量。
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入门教程栏目进行学习!
以上是redis分布式锁如何实现原理的详细内容。更多信息请关注PHP中文网其他相关文章!