一、redisson介绍

redisson实现了分布式和可扩展的java数据结构,支持的数据结构有:List, Set, Map, Queue, SortedSet, ConcureentMap, Lock, AtomicLong, CountDownLatch。并且是线程安全的,底层使用Netty 4实现网络通信。和jedis相比,功能比较简单,不支持排序,事务,管道,分区等redis特性,可以认为是jedis的补充,不能替换jedis。

二、redisson几种锁介绍

1、可重入锁(Reentrant Lock)

基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口

 RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

 // 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

Redisson同时还为分布式锁提供了异步执行的相关方法:

 RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);

2.公平锁(Fair Lock)

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。

 RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

同样的,Fair lock也提供加锁时间

 // 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
fairLock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

 RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);

3.联锁(MultiLock)

基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。

 RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3"); RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 所有的锁都上锁成功才算成功。
lock.lock();
...
lock.unlock();

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

 RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS); // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

4.红锁(Red Lock)

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

 RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3"); RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

 RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS); // 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

5. 读写锁(ReadWriteLock)

基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持自动过期解锁。该对象允许同时有多个读取锁,但是最多只能有一个写入锁。

 RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

6. 闭锁(CountDownLatch)

基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await(); // 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();

三、redisson分布式锁在业务中的应用

1、引入相关pom

        <!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>

2、将redisson交由spring管理,本文采用redisson的集群模式装配,另外可配置哨兵模式,单点等

/**
* redisson客户端参数配置类
*/
@Configuration
@Data
public class RedissonProperties { private int idleConnectionTimeout = 10000; private int pingTimeout = 1000; private int connectTimeout = 10000; private int timeout = 3000; private int retryAttempts = 3; private int retryInterval = 1500; private int reconnectionTimeout = 3000; private int failedAttempts = 3; private int subscriptionsPerConnection = 5; private String clientName = "none"; private int subscriptionConnectionMinimumIdleSize = 64; private int subscriptionConnectionPoolSize = 256; private int slaveConnectionMinimumIdleSize = 64; private int slaveConnectionPoolSize = 256; private int masterConnectionMinimumIdleSize = 64; private int masterConnectionPoolSize = 256; private ReadMode readMode = ReadMode.MASTER; private SubscriptionMode subscriptionMode = SubscriptionMode.MASTER; private int scanInterval = 1000; @Value("${rediscluster.pwd}")
private String password; @Value("${redis.cluster}")
private String nodeAddress; @Value("${redis.cluster1}")
private String nodeAddress1; @Value("${redis.cluster2}")
private String nodeAddress2; }
/**
* 初始化redisson Bean
*
* @author LiJunJun
* @date 2018/10/19
*/
@Configuration
public class RedissonAutoConfiguration { @Autowired
private RedissonProperties redssionProperties; /**
* 集群模式自动装配
*
* @return
*/
@Bean
public RedissonClient redissonClient() { Config config = new Config();
String passWord = redssionProperties.getPassword();
ClusterServersConfig serverConfig = config.useClusterServers();
serverConfig.addNodeAddress(redssionProperties.getNodeAddress(), redssionProperties.getNodeAddress1(), redssionProperties.getNodeAddress2());
serverConfig.setPingTimeout(redssionProperties.getPingTimeout());
serverConfig.setConnectTimeout(redssionProperties.getConnectTimeout());
serverConfig.setTimeout(redssionProperties.getTimeout());
serverConfig.setRetryAttempts(redssionProperties.getRetryAttempts());
serverConfig.setRetryInterval(redssionProperties.getRetryInterval());
serverConfig.setReconnectionTimeout(redssionProperties.getReconnectionTimeout());
serverConfig.setFailedAttempts(redssionProperties.getFailedAttempts());
serverConfig.setSubscriptionsPerConnection(redssionProperties.getSubscriptionsPerConnection());
serverConfig.setClientName(redssionProperties.getClientName());
serverConfig.setSubscriptionConnectionMinimumIdleSize(redssionProperties.getSubscriptionConnectionMinimumIdleSize());
serverConfig.setSubscriptionConnectionPoolSize(redssionProperties.getSubscriptionConnectionPoolSize());
serverConfig.setSlaveConnectionMinimumIdleSize(redssionProperties.getSlaveConnectionMinimumIdleSize());
serverConfig.setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
serverConfig.setMasterConnectionMinimumIdleSize(redssionProperties.getMasterConnectionMinimumIdleSize());
serverConfig.setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize());
serverConfig.setReadMode(redssionProperties.getReadMode());
serverConfig.setSubscriptionMode(redssionProperties.getSubscriptionMode());
serverConfig.setScanInterval(redssionProperties.getScanInterval());
serverConfig.setPassword(StringUtils.isNotBlank(passWord) && !"null".equals(passWord) ? passWord : null);
return Redisson.create(config);
}
}

3、业务代码中的应用。此处使用的是悲观锁,即必须拿到锁之后才能继续往下执行,也可使用乐观锁,tryLock,利用重试去获取锁

    /**
* redissonClient
*/
@Resource
private RedissonClient redissonClient; /**
* 减库存
*
* @param trace 请求流水
* @param stockManageReq(stockId、decrNum)
* @return -1为失败,大于-1的正整数为减后的库存量,-2为库存不足无法减库存
*/
@Override
@ApiOperation(value = "减库存", notes = "减库存")
@RequestMapping(value = "/decrByStock", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public int decrByStock(@RequestHeader(name = "Trace") String trace, @RequestBody StockManageReq stockManageReq) { long startTime = System.currentTimeMillis(); LOGGER.reqPrint(Log.CACHE_SIGN, Log.CACHE_REQUEST, trace, "decrByStock", JSON.toJSONString(stockManageReq)); int res = 0;
String stockId = stockManageReq.getStockId();
Integer decrNum = stockManageReq.getDecrNum(); // 添加分布式锁
RLock stockLock = null; try {
if (null != stockId && null != decrNum) { stockId = PREFIX + stockId; // 添加分布式锁
stockLock = redissonClient.getFairLock(stockId); stockLock.lock(); // redis 减库存逻辑
String vStock = redisStockPool.get(stockId);
long realV = 0L;
if (StringUtils.isNotEmpty(vStock)) {
realV = Long.parseLong(vStock);
}
//库存数 大于等于 要减的数目,则执行减库存
if (realV >= decrNum) {
Long v = redisStockPool.decrBy(stockId, decrNum);
res = v.intValue();
} else {
res = -2;
} stockLock.unlock();
}
} catch (Exception e) {
LOGGER.error(trace, "decr sku stock failure.", e);
res = -1;
} finally {
if (stockLock != null && stockLock.isLocked() && stockLock.isHeldByCurrentThread()) {
stockLock.unlock();
}
LOGGER.respPrint(Log.CACHE_SIGN, Log.CACHE_RESPONSE, trace, "decrByStock", System.currentTimeMillis() - startTime, String.valueOf(res));
}
return res;
}

四、ab压测及结果分析
同样的,我们以5000的请求量100的并发量来压、tps在330左右,相对于zk做分布式锁来看,提升了10倍的性能,但仍然不能满足我们的要求

 五、总结

redisson提供了丰富的分布式锁实现机制,并且使用起来相对比较简单方便,具体选用哪种锁,可以根据业务来选择,但在高并发的情况下,性能还是有些差强人意,下一篇,我们使用redis的watch来实现分布式锁。

【连载】redis库存操作,分布式锁的四种实现方式[二]--基于Redisson实现分布式锁的更多相关文章

  1. 【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁

    一.背景 在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以 ...

  2. 【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁

    一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发 ...

  3. 【转载】Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍

    转载地址:http://blog.csdn.net/truong/article/details/46711045 关键字:Redis的Java客户端Jedis的八种调用方式(事务.管道.分布式…)介 ...

  4. 【连载】redis库存操作,分布式锁的四种实现方式[四]--基于Redis lua脚本机制实现分布式锁

    一.redis lua介绍 Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向 ...

  5. redis分布式锁的几种实现方式,以及Redisson的配置和使用

    最近在开发中涉及到了多个客户端的对redis的某个key同时进行增删的问题.这里就会涉及一个问题:锁 先举例在分布式系统中不加锁会出现问题: redis中存放了某个用户的账户余额 ,例如100 (用户 ...

  6. 分布式锁的两种实现方式(基于redis和基于zookeeper)

    先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制.从实现角度来看,主要有两种方式:基于redis的方式和基于zookeeper的方式,下面分别简单介 ...

  7. Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍

    jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...

  8. 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)

    原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...

  9. Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

随机推荐

  1. Vue.js:模版语法

    ylbtech-Vue.js:模版语法 1.返回顶部 1. Vue.js 模板语法 Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据. Vu ...

  2. windows-x64下安装python3.6

    1.下载python3安装包,注意要安装windowsx64的exe安装包.python-3.6.2-amd64.exe 2.设置window的环境变量:C:\Users\您设置的windows用户名 ...

  3. 微信小程序之工具js封装与使用

    工具库的创建与使用 创建一个common文件夹 在common文件夹中创建一个utils文件夹 在utils文件夹中创建util.js // 工具 function tool() { console. ...

  4. zabbix的sendEmail配置

    zabbix的sendEmail配置 [root@hongquan scripts]# yum install sendmail[root@hongquan soft]# tar xvzf sendE ...

  5. 关于 Mybatis的原生连接池 和 DBCP 连接池

    一 遇到的问题:  项目用的play框架,数据库DB2, 持久化框架是Mybatis, 连接池用的是Mybatis原生的,遇到的问题是:有时候抛出如下异常: play.api.UnexpectedEx ...

  6. java - 只输出不含中文标点符号的中文

    String a ="12dss显示,‘:()中文只"; StringBuffer b = new StringBuffer(); for(int i = 0;i<a.len ...

  7. Python函数的进阶

    一  函数的动态参数 *agrs  位置参数动态传参 *args  接收多个位置参数 def func(*args): print(args) func("女儿国","西 ...

  8. classmethod VS staticmethod

  9. shelve和hashlib模块

    一.shelve模块 shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式. 注意: shelve模块封装了pickle模块,,允许 ...

  10. Spring总结四:IOC和DI 注解方式

    首先我们要了解注解和xml配置的区别: 作用一样,但是注解写在Bean的上方来代替我们之前在xml文件中所做的bean配置,也就是说我们使用了注解的方式,就不用再xml里面进行配置了,相对来说注解方式 ...