记上一篇用ZK实现分布式所之后,现在在将redis实现分布式版本补上。相比与ZK的方式简单很多。
先看下,redis分布式是怎么演变的来的,redission如何使用的。
1、导入Redisson 和 StringRedisTemplate的依赖包。
<!--redisson依赖-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.2.13</version>
</dependency>
<!--StringRedisTemplate依赖-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2、使用syn锁安全实现单机版本的商品售卖
public String deductStockOne(){
synchronized (this) {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("商品扣减成功,剩余库存" + realStock + "");
} else {
System.out.println("扣减失败,库存不足");
}
}
return "end";
}
3、简单版本的redis实现分布式锁( 带bug版本的,优化见4)
业务叠加之后,一台服务器解决不了问题,需要部署多台服务器构建分布式系统之后,而synchronized只能锁住当前JVM虚拟机里面的进程代码块。可以用redis中的setnx思想来实现分布式锁,来控制服务访问时不同发服务器对库存的实时监控。
public String deductStockDustriSimp(){
String lockKey = "lockKey";
try {
// Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "yangguo");
// stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
//相当于redis setnx(key,value)
Boolean result = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, "yangguo", 10, TimeUnit.SECONDS);
if(!result){
return "error";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0){
int realStock = stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");
System.out.println("商品扣减成功,剩余库存"+realStock+"");
}else{
System.out.println("扣减失败,库存不足");
}
} finally {
stringRedisTemplate.delete(lockKey);
}
return "end";
}
简单版本的redis之所有会有bug,如图注意几个细节。
例外,bug主要在哪里呢?举个例子两个线程t1,t2,两个线程都需要执行15s时间,但是设置的过期时间是10s,那么t1还没执行完的时候,设置的过期时间到了,那么锁被释放。t2获取到Lock锁执行业务,但是t1此时执行 stringRedisTemplate.delete(lockKey);删除锁的操作。那么这个时候的t2锁被删了。相当于高并发场景下该分布式锁已经失效了。
那么怎么优化呢?
上面的问题主要在于设置的过期时间在线程业务还没跑完时锁就被删除了。怎么解决呢?是否可以在业务代码里面在开启一个子线程,写一个定时器隔几秒时间判断一下锁是否还在,如果在就延长时间直到业务完成在释放锁。
本质上在于设置的过期时间上,判断锁是否存在,如果存在就延长时间直到业务顺利执行。
思路是这样,但是真要自己写个子线程加一个Timer的定时器程序会显的额外复杂。针对这个问题几乎大厂都在用redisson来解决。
4、redis分布式锁实现(正常版)
先看看redis实现分布式原理图:
代码如下:
public String deductStockDist (){
String lockKey = "lockKey";
RLock lock = redisson.getLock(lockKey);
try {
lock.tryLock(30, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0){
int realStock = stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");
System.out.println("商品扣减成功,剩余库存"+realStock+"");
}else{
System.out.println("扣减失败,库存不足");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return "end";
}