建设网站的 成本,怎样在我的世界做汽车视频网站,公司企业网站建设方案书,团购网站 设计方案Redis因为单进程、性能高常被用于分布式锁#xff1b;锁在程序中作用是同步工具#xff0c;保证共享资源在同一时刻只能被一个线程访问。 Java中经常用的锁synchronized、Lock#xff0c;但是Java的锁智能保证单机的时候有效#xff0c;分布式集群环境就无能为力了#xf…Redis因为单进程、性能高常被用于分布式锁锁在程序中作用是同步工具保证共享资源在同一时刻只能被一个线程访问。 Java中经常用的锁synchronized、Lock但是Java的锁智能保证单机的时候有效分布式集群环境就无能为力了这时候需要用到分布式锁。 分布式锁就是分布式项目开发中用到的锁用来控制分布式系统之间同步访问共享资源一般来说分布式锁满足几个特性 1. 互斥性在任何时刻对于同一条数据只有一台应用可以获取到分布式锁
2. 高可用性在分布式场景下一小部分服务器宕机不影响正常使用这种情况就需要将提供分布式锁的服务以集群的方式部署
3. 防止锁超时如果客户端没有主动释放锁服务器会在一段时间之后自动释放锁避免死锁的产生
4. 独占性加锁解锁必须由一台服务器惊醒也就是锁的持有者才可以释放锁
5. 可重入性在同一个节点进程内同一个线程可多次获取锁
实现分布式锁的工具还有db、zookeeper、RedisLockRegistry但操作大致也是加锁、解锁、锁超时。 实现锁的命令
1. setnx(set if not exists),setnx key value设置成功返回1否则返回0 问题为了防止致命的问题key没有过期时间除非手动删除key或者获取锁后设置过期时间不然其他线程永远拿不到锁 解决给key加过期时间让线程获取锁的时候并且设置过期时间 问题加锁、锁超时分两步不是原子性操作可能获取锁成功但设置时间失败 2. setexsetex key seconds value;将值value关联到Key并将Key的生存时间设为seconds以秒为单位。如果key存在setex命令将覆写旧值这两步是原子性会在同一时间完成 3. psetex,psetex key milliseconds value与setex相似以毫秒为单位设置key的生存时间 从Redis 2.6.12版本开始set命令可以通过参数来实现setnx,setex,psetex三个命令相同的效果如set key value nx ex seconds 伪代码工具类实现锁的基础方法
public class RedisLockUtil { private String LOCK_KEY redis_lock; // key的持有时间5ms private long EXPIRE_TIME 5; // 等待超时时间1s private long TIME_OUT 1000; // redis命令参数相当于nx和px的命令合集 private SetParams params SetParams.setParams().nx().px(EXPIRE_TIME); // redis连接池连的是本地的redis客户端 JedisPool jedisPool new JedisPool(127.0.0.1, 6379); /** * 加锁 * * param id * 线程的id或者其他可识别当前线程且不重复的字段 * return */ public boolean lock(String id) { Long start System.currentTimeMillis(); Jedis jedis jedisPool.getResource(); try { for (;;) { // SET命令返回OK 则证明获取锁成功 String lock jedis.set(LOCK_KEY, id, params); if (OK.equals(lock)) { return true; } // 否则循环等待在TIME_OUT时间内仍未获取到锁则获取失败 long l System.currentTimeMillis() - start; if (l TIME_OUT) { return false; } try { // 休眠一会不然反复执行循环会一直失败 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } finally { jedis.close(); } } /** * 解锁 * * param id * 线程的id或者其他可识别当前线程且不重复的字段 * return */ public boolean unlock(String id) { Jedis jedis jedisPool.getResource(); // 删除key的lua脚本 String script if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end; try { String result jedis.eval(script, Collections.singletonList(LOCK_KEY), Collections.singletonList(id)).toString(); return 1.equals(result); } finally { jedis.close(); } }
}
测试demo public class RedisLockDemo { private static RedisLockUtil demo new RedisLockUtil(); private static Integer NUM 101; public static void main(String[] args) { for (int i 0; i 100; i) { new Thread(() - { String id Thread.currentThread().getId() ; boolean isLock demo.lock(id); try { // 拿到锁的话就对共享参数减一 if (isLock) { NUM--; System.out.println(NUM); } } finally { // 释放锁一定要注意放在finally demo.unlock(id); } }).start(); } }
}
//100
//99
//98
//...
一个健全的分布式锁要考虑的方面很多一般使用开源工具zookeepre,db,Redisson等) Redis实现分布式锁的缺陷
客户端长时间阻塞导致锁失效问题
客户端1的到锁因网络问题或gc等原因导致长时间阻塞然后业务程序还没执行完就过期了这时候客户端2也能正常拿到锁可能会导致线程安全问题。 非原子性操作
误删锁
项目中常使用的Redis分布式锁
RedisLockRegistry是 Spring-Integration 集成工具包项目提供的基于 Redis 的分布式锁管理器 基于 Redis 的分布式锁实现主要是依托 get 和 setnx 的方法再包裹一层本地的可重入锁实现。