一、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. 摆花 (DP动态规划)

    2012_p3 摆花 (flower.cpp/c/pas) 时间限制: 1 Sec  内存限制: 128 MB提交: 17  解决: 10[提交][状态][讨论版][命题人:外部导入] 题目描述 3. ...

  2. Vue.js:表单

    ylbtech-Vue.js:表单 1.返回顶部 1. Vue.js 表单 这节我们为大家介绍 Vue.js 表单上的应用. 你可以用 v-model 指令在表单控件元素上创建双向数据绑定. v-mo ...

  3. springboot成神之——basic auth和JWT验证结合

    本文介绍basic auth和JWT验证结合 目录结构 依赖 config配置文件WebSecurityConfig filter过滤器JWTLoginFilter filter过滤器JWTAuthe ...

  4. Python Twisted系列教程11:改进诗歌下载服务器

    作者:dave@http://krondo.com/your-poetry-is-served/ 译者:杨晓伟(采用意译) 你可以从这里从头阅读这个系列. 诗歌下载服务器 到目前为止,我们已经学习了大 ...

  5. 从javascript的循环问题来看待闭包本质

    第一次接触这个问题还是在我刚开始学js的时候,当时就是一头雾水,时隔一年多了,突然又想起了这个问题,在这个春气盎然的周末,我就坐下来研究下并把结果和大家分享下: 先看代码:demo.html < ...

  6. spring cloud微服务搭建(一)

    martin fowler大神提出微服务的概念后,各种微服务的技术满天飞,现在用的比较多的是spring cloud和阿里的dubbo,由于dubbo 在16年10月份就停止更新了,不过好像前些天又更 ...

  7. linux多线程默认栈大小和最大线程数

    linux的线程栈大小可以使用ulimit -s查看,对于ubunt 2.6的内核线程栈的默认大小为8M,如下: shine@shine-bupt:~/Program/C$ ulimit -s 819 ...

  8. Tarjan的强连通分量算法

    Tarjan算法用于寻找图G(V,E)中的所有强连通分量,其时间复杂度为O(|V|+|E|). 所谓强连通分量就是V的某个极大子集,其中任意两个结点u,v在图中都存在一条从u到v的路径. Tarjan ...

  9. linux tcpdump补充

    If they are going across the loopback interface, you may have to tell tcpdump to read that interface ...

  10. Codeforces 919F——A Game With Numbers

    转自大佬博客:https://www.cnblogs.com/NaVi-Awson/p/8405966.html; 题意 两个人 Van♂ 游戏,每人手上各有 8'>88 张牌,牌上数字均为 [ ...