概念:在分布式环境下,保证某个资源在同一时间访问时,只有一个线程能访问到资源;
分布式锁的实现方式:
1.基于数据库实现:
innodb行锁;
排他锁:一次只允许一个线程访问,只有等一个线程执行完后才会有下个线程进入;
乐观锁:
版本号控制;在数据库中加一个Version字段;
基于数据库主键实现分布式锁;
基于数据库索引实现分布式锁;
乐观锁的缺点:常见的ABA问题;
2.基于Redis实现:
出现的问题:
原子性问题:设置超时时间
if(jedis.setnx(lock_stock,1) == 1){ //获取锁
expire(lock_stock,5000) //设置锁超时
try {
业务代码需要10秒执行完毕
} finally {
jedis.del(lock_stock) //释放锁
}
}
死锁问题:(没有释放锁/没有删除锁);删除锁判断一下是不是自己的锁;把value设置uuid
//A-uuid:1 redis:123-1
//B-uuid:2 redis:123-2
String uuid = UUID.randomUUID().toString();
if(jedis.set('123', uuid,"NX","EX",5) == 1){ //获取锁并设置超时
try {
//业务代码
} finally {
String lockValue = jedis.get("123"); //获取锁的值
if(lockValue.equals(uuid)){ //判断是不是自己的锁
jedis.del("123") //释放锁
}
}
}
释放锁的原子性问题:lua脚本保证原子性;
String uuid = UUID.randomUUID().toString();
if(jedis.set('123',uuid,"NX","EX",5) == 1){ //获取锁并设置超时
try {
业务代码
} finally {
//lua脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
if
redis.call('get', '123') == '1111111'
then
return redis.call('del', '123')
else
return 0 end
//执行脚本
jedis.eval(script, Collections.singletonList("123"),Collections.singletonList(uuid));
}
}
redis提供的redis命令,setnx,比如三个线程同时进入一个业务,A线程进去了,只有A线程执行完释放锁后,其他两个线程才会执行;只有key不存在才会存value返回1,key不存在不会存value返回0;
但是会有原子性的问题,如果代码没有执行完就出错了,那么数据的一致性如何保证呢?
此时用Redisson来操作;
Redission是什么?
是Java用来操作redis的工具包,Redisson的宗旨是让用户redis的操作分离,为了是让用户更注重处理业务代码;
Redission常见的锁:
可重入锁;线程可重新进入的锁;
联锁;在分布式场景下使用,必须等其他几个线程都上锁之后,主线程才能拿到锁;
红锁;和联锁一样,唯一区别就是其他几个线程只需要大部分上锁就可以了;
读写锁;只有写完之后,读的线程才能执行;
闭锁(重点):如下图,CountdownLatch里面有一个初始值,就是线程的数量,线程每执行完一个就会减一,只有当CountdownLatch里面的值为零的时候,await方法才会放行,否则在await方法出等待;
信号量:并发场景下,把数据库里的库存先拿到缓存里面预热,然后下单在缓存里进行库存扣减;
公平锁:保持先进先出的原则,和队列一样;
3.基于zookeeper实现(临时顺序节点+监听):
zookeeper:解决分布式服务,保证数据一致性的协调解决方案;是层次型的目录树的数据结构;
原理图:
四种节点分类:
1.持久化节点:
2.持久化顺序节点:
3.临时节点:
4.临时顺序化节点(重点):
使用原理图:
主要实现原理:临时顺序节点+监听机制实现的;