分布式锁使用
分布式锁特点
1、互斥性
和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
2、可重入性
同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
3、锁超时
和本地锁一样支持锁超时,加锁成功之后设置超时时间,以防止线程故障导致不释放锁,防止死锁。
4、高效,高可用
加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
redission是基于redis的,redis的故障就会导致redission锁的故障,因此redission支持单节点redis、reids主从、reids集群
5、支持阻塞和非阻塞
和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。
7.5.2 锁的分类的总结
1、乐观锁与悲观锁
- 乐观锁
- 悲观锁
2、可重入锁和非可重入锁
- 可重入锁:当在一个线程中第一次成功获取锁之后,在此线程中就可以再次获取
- 非可重入锁
3、公平锁和非公平锁
- 公平锁:按照线程的先后顺序获取锁
- 非公平锁:多个线程随机获取锁
4、阻塞锁和非阻塞锁
-
阻塞锁:不断尝试获取锁,直到获取到锁为止
-
非阻塞锁:如果获取不到锁就放弃,但可以支持在一定时间段内的重试
——在一段时间内如果没有获取到锁就放弃
Redission的使用
1、获取锁——公平锁和非公平锁
//获取公平锁
RLock lock = redissonClient.getFairLock(skuId);
//获取非公平锁
RLock lock = redissonClient.getLock(skuId);
2、加锁——阻塞锁和非阻塞锁
//阻塞锁(如果加锁成功之后,超时时间为30s;加锁成功开启看门狗,剩5s延长过期时间)
lock.lock();
//阻塞锁(如果加锁成功之后,设置自定义20s的超时时间)
lock.lock(20,TimeUnit.SECONDS);
//非阻塞锁(设置等待时间为3s;如果加锁成功默认超时间为30s)
boolean b = lock.tryLock(3,TimeUnit.SECONDS);
//非阻塞锁(设置等待时间为3s;如果加锁成功设置自定义超时间为20s)
boolean b = lock.tryLock(3,20,TimeUnit.SECONDS);
3、释放锁
lock.unlock();
4、应用示例
//公平非阻塞锁
RLock lock = redissonClient.getFairLock(skuId);
boolean b = lock.tryLock(3,TimeUnit.SECONDS);
分布式锁框架-Redisson
Redisson是基于
Redis+看门狗机制
的分布式锁框架
Redisson介绍
Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作
在SpringBoot应用中使用Redisson
-
添加依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
-
配置yml
redisson: addr: singleAddr: host: redis://192.168.0.1:6379 password: 123456 database: 0
-
配置RedissonClient
@Configuration public class RedissonConfig { @Value("${redisson.addr.singleAddr.host}") private String host; @Value("${redisson.addr.singleAddr.password}") private String password; @Value("${redisson.addr.singleAddr.database}") private int database; @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer() .setAddress(host) .setPassword(password) .setDatabase(database); return Redisson.create(config); } }
-
在秒杀业务实现中注入RedissonClient对象
@Service public class OrderServiceImpl2 implements OrderService { @Autowired private ProductMapper productMapper; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedissonClient redissonClient; @Override public ResultVO orderHandle(Long userId, Integer productId, Integer num) { //构造锁 RLock lock = redissonClient.getLock("product_" + productId); //加锁:阻塞锁 lock.lock(); try { //1.查询商品(Product)库存 Product product = productMapper.selectById(productId); //2.校验库存(√) stock>num if (product.getProductStock() >= num) { System.out.println("-----------生成订单");//3.生成/保存订单 System.out.println("-----------生成订单快照"); //4.保存订单快照 int newStock = product.getProductStock() - num; //5.扣减库存 product.setProductStock(newStock); int i = productMapper.updateById(product); System.out.println("------------订单提交完成!"); return new ResultVO(0, "提交订单成功!", "orderId"); } else { return new ResultVO(1, "库存不足", null); } }catch (Exception e){ e.printStackTrace(); } finally { //释放锁 lock.unlock(); } return null; } }
Redisson工作原理
Redis的setnx操作特性
看门狗
线程
Redisson工作原理图 |
---|
![]() |
Redisson使用扩展
7.4.1 Redisson单机连接
-
application.yml
redisson: addr: singleAddr: host: redis://192.168.0.1:6379 password: 123456 database: 0
-
RedissonConfig
@Configuration public class RedissonConfig { @Value("${redisson.addr.singleAddr.host}") private String host; @Value("${redisson.addr.singleAddr.password}") private String password; @Value("${redisson.addr.singleAddr.database}") private int database; @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer() .setAddress(host) .setPassword(password) .setDatabase(database); return Redisson.create(config); } }
Redisson集群连接
-
application.yml
redisson: addr: cluster: hosts: redis://192.168.0.1:6370,...,redis://192.168.0.1:6373 password: 123456
-
RedissonConfig——RedissonClient对象
@Configuration public class RedissonConfig { @Value("${redisson.addr.cluster.hosts}") private String hosts; @Value("${redisson.addr.cluster.password}") private String password; /** * 集群模式 * @return */ @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useClusterServers().addNodeAddress(hosts.split("[,]")) .setPassword(password) .setScanInterval(2000) .setMasterConnectionPoolSize(10000) .setSlaveConnectionPoolSize(10000); return Redisson.create(config); } }
Redisson主从连接
-
application.yml
redisson: addr: masterAndSlave: masterhost: redis://192.168.0.1:6370 slavehosts: redis://192.168.0.1:6371,redis://192.168.0.1:6372 password: 123456 database: 0
-
RedissonConfig — RedissonClient
@Configuration public class RedissonConfig3 { @Value("${redisson.addr.masterAndSlave.masterhost}") private String masterhost; @Value("${redisson.addr.masterAndSlave.slavehosts}") private String slavehosts; @Value("${redisson.addr.masterAndSlave.password}") private String password; @Value("${redisson.addr.masterAndSlave.database}") private int database; /** * 主从模式 * @return */ @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useMasterSlaveServers() .setMasterAddress(masterhost) .addSlaveAddress(slavehosts.split("[,]")) .setPassword(password) .setDatabase(database) .setMasterConnectionPoolSize(10000) .setSlaveConnectionPoolSize(10000); return Redisson.create(config); } }