Redis因为单进程、性能高常被用于分布式锁;锁在程序中作用是同步工具,保证共享资源在同一时刻只能被一个线程访问。
文章来源:https://www.toymoban.com/news/detail-405094.html
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. setex,setex 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 的方法,再包裹一层本地的可重入锁实现。文章来源地址https://www.toymoban.com/news/detail-405094.html
到了这里,关于分布式锁简介的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!