在自己实现redis分布式锁的时候,我问了,为什么有些公司采用redisson去实现锁?!
其实理由有很多,个人认为最关键的还是锁的控制。
“拿来主义”,别人已经做好的,拿来用就行了,当然,你要懂原理就更好!
自己实现的锁有很多问题,比如说,锁的过期时间,在高并发情况下,不能保证每次任务进来的时间长短,势必回导致锁的过期时间设置问题。
redisson不光有watch dog 而且还把锁信息放在hash中,hash中的key存储了当前线程id!
下面是模拟并发情况下的,抢占锁资源情况:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
注意自己加上maven包。
application.yml:
server:
port: 8888
spring:
redis:
port: 6379
host: 192.168.100.108
password: 123456
database: 0
timeout: 2000
redisson:
config: classpath:redisson-single.yml
redisson-single.yml:
# 单节点设置
singleServerConfig:
address: redis://192.168.6.166:6379
database: 0
password: 123456
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
clientName: null
# 发布和订阅连接的最小空闲连接数 默认1
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小 默认50
subscriptionConnectionPoolSize: 10
# 单个连接最大订阅数量 默认5
subscriptionsPerConnection: 5
# 最小空闲连接数 默认32,现在暂时不需要那么多的线程
connectionMinimumIdleSize: 4
# connectionPoolSize 默认64,现在暂时不需要那么多的线程
connectionPoolSize: 20
# 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。
threads: 0
# 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: NIO
package com.example.demo.redislock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class DistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
public RLock getLock(String lockId) {
RLock rlock = null;
try{
rlock = redissonClient.getLock(lockId);
rlock.lock(5000, TimeUnit.MILLISECONDS);
//System.out.println("获取锁成功");
}catch (Exception e) {
//System.out.println("获取锁失败!");
unLock(rlock);
}
return rlock;
}
public void unLock(RLock rlock) {
if(rlock != null) {
rlock.unlock();
}
}
public String getValueByKey(String key) {
String value = redisTemplate.opsForValue().get(key);
return value;
}
public void setValueByKey(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
}
package com.example.demo.controller;
import com.example.demo.redislock.DistributedLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/lock")
public class LockController {
private final static String LOCK_ID = "product:";
@Autowired
private DistributedLock distributedLock;
@GetMapping("/{id}")
public void lock(@PathVariable String id) {
//模拟多个资源请求输出库存情况
//goods_num
RLock lock = distributedLock.getLock(LOCK_ID);
if(lock != null) {
System.out.println("-------------线程"+id+"抢到锁------------");
//获取成功
Integer num = Integer
.parseInt(distributedLock.getValueByKey("goods_num"));
if(num > 0) {
distributedLock.setValueByKey("goods_num", Integer.toString(num - 1));
System.out.println("商品数剩余----->" + (num - 1));
} else {
System.out.println("商品销售完----结束了!");
}
distributedLock.unLock(lock);
} else {
//获取失败
System.out.println("没抢到锁");
}
System.out.println("-------------线程"+id+"结束------------");
}
}
以上是部分实现
然后创建多线程模拟并发:
package com.jason.data;
import java.util.concurrent.CountDownLatch;
public class RunnableDemo implements Runnable {
CountDownLatch latch;
int i;
public RunnableDemo(CountDownLatch latch,int i) {
this.latch = latch;
this.i = i;
}
@Override
public void run() {
//http访问get请求
long starTime = 0;
try {
// starTime = System.currentTimeMillis();
System.out.println("开始,第"+i+"个");
latch.await();
// System.out.println("请求开始了");
//JSONObject jsonObject = httpPost("http://192.168.100.43:8080/lua/requestIdfa", "userId=3&bundleName=xxxxxx&country=US&thirdParty=sdfsfs");
// System.out.println("返回的状态码为:"+jsonObject.get("code"));
// System.out.println("返回的状态信息:"+jsonObject.get("desc"));
// System.out.println("返回的内容为:"+jsonObject.get("content"));
HttpClient.doGet("http://localhost:8888/lock/"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.jason.data;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class LockDemo {
public static void main(String[] args) {
//Lock
//多线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(2000, 5000,
2, TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(5000));
//模拟2000人并发请求
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 2000; i++) {
RunnableDemo demo = new RunnableDemo(latch,i);
executor.execute(demo);
}
//计数器減一 所有线程释放 并发访问。
latch.countDown();
}
}
之前通过命令设置 set goods_num 200
库存有200件,我们分别启动,然后截图看效果。
可以看到,并发情况下,到底谁抢到了锁并不确定,但是利用redis单线程原因,可以保证库存到最后都是正确到,事务性良好。其实秒杀功能也是这个原理,
不过比这复杂,当并发特别巨大当时候,需要做负载均衡,限流等一系列操作!
大家想想,redisson分布式锁有什么缺陷?!
后面有空给大家介绍,rocketmq限流等实现!
原文:https://www.cnblogs.com/lzphu/p/12514627.html