在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害。这个时候就需要用分布式锁了实现分布式锁的方式很多,我们今天就用redis的分布式锁,redisson也是官方比较推荐的。当然我们其实也可以自己用redis的setntx,delete方式自己写一个。
大概有三种:
大部分网站使用的是基于缓存的,有更好的性能,而缓存一般是以集群方式部署,保证了高可用性。
在Redisson中,使用key来作为是否上锁的标志,当通过getLock(String key)方法获得相应的锁之后,这个key即作为一个锁存储到Redis集群中,在接下来如果有其他的线程尝试获取名为key的锁时,便会向集群中进行查询,如果能够查到这个锁并发现相应的value的值不为0,则表示已经有其他线程申请了这个锁同时还没有释放,则当前线程进入阻塞,否则由当前线程获取这个锁并将value值加一,如果是可重入锁的话,则当前线程每获得一个自身线程的锁,就将value的值加一,而每释放一个锁则将value值减一,直到减至0,完全释放这个锁。因为底层是基于分布式的Redis集群,所以Redisson实现了分布式的锁机制。
4.1、pom引入
org.redisson
redisson
3.5.0
4.2、redisson配置
spring
redis:
database: 1
host: 127.0.0.1
port: 6379
#password: 12345678
redisson:
address: redis://127.0.0.1:6379
#password: web2017
4.2、redisson操作锁的工具
package com.example.mybatiesplus.utils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @DESCRIPTION redisson操作锁的工具
* @Author lst
* @Date 2020-05-22 15:00
*/
@Component
public class RedissonUtil {
@Autowired
private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可
/**
* 锁住不设置超时时间(拿不到lock就不罢休,不然线程就一直block)
* @author lst
* @date 2020-5-24 16:23
* @param lockKey
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* leaseTime为加锁时间,单位为秒
* @author lst
* @date 2020-5-24 16:23
* @param lockKey
* @param leaseTime
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return null;
}
/**
* timeout为加锁时间,时间单位由unit确定
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @param unit
* @param timeout
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获取锁
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @param unit
* @param waitTime
* @param leaseTime
* @return boolean
*/
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 通过lockKey解锁
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @return void
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 直接通过锁解锁
* @author lst
* @date 2020-5-24 16:25
* @param lock
* @return void
*/
public void unlock(RLock lock) {
lock.unlock();
}
}
4.3、redisson基本配置类
@Configuration
public class RedissonConfig {
@Value("${redisson.address}")
private String addressUrl;
@Bean
public RedissonClient getRedisson() throws Exception{
RedissonClient redisson = null;
Config config = new Config();
config.useSingleServer()
.setAddress(addressUrl);
redisson = Redisson.create(config);
System.out.println(redisson.getConfig().toJSON().toString());
return redisson;
}
}
4.3、接下来,写一个测试类
package com.example.mybatiesplus.controller;
import com.example.mybatiesplus.result.BaseResponse;
import com.example.mybatiesplus.result.ResultGenerator;
import com.example.mybatiesplus.utils.RedissonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @DESCRIPTION 测试类
* @Author lst
* @Date 2020-05-24
*/
@RestController
@RequestMapping("/test")
@Api(value = "TestController", tags = "测试类")
@Slf4j
public class TestController {
public final static String REDISSON_KEY = "redisson_key";
@Autowired
private RedissonUtil redissonUtil;
/**
* 通过redisson高并发测试
* @author lst
* @date 2020-5-24 16:29
* @param
* @return com.example.mybatiesplus.result.BaseResponse
*/
@GetMapping(value = "/redisson", produces = "application/json; charset=utf-8")
@ApiOperation(value = "通过redisson高并发测试", notes = "通过redisson高并发测试", code = 200, produces = "application/json")
public BaseResponse redisson() {
try{
log.info("============={} 线程访问开始============",Thread.currentThread().getName());
//TODO 尝试获取锁,等待3秒,自己获得锁后一直不解锁则5秒后自动解锁
boolean lock = redissonUtil.tryLock(REDISSON_KEY, TimeUnit.SECONDS, 3L, 5L);
if (lock) {
log.info("线程:{},获取到了锁",Thread.currentThread().getName());
//TODO 获得锁之后可以进行相应的处理 睡一会
Thread.sleep(100);
log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
//redissonUtil.unlock(REDISSON_KEY);
log.info("=============================" + Thread.currentThread().getName());
}
}catch (Exception e){
log.info("错误信息:{}",e.toString());
log.info("线程:{} 获取锁失败",Thread.currentThread().getName());
}
return ResultGenerator.genSuccessResult();
}
}
4.4、使用jmeter测试
第一步:创建线程组
第二步:添加http请求,设置并发数(先测试100)
第二步:添加接口信息
4.5、测试数据
在redissonUtil.unlock(REDISSON_KEY);锁未释放的测试下,可能看到后台日志只有线程26获取到了锁。
将redissonUtil.unlock(REDISSON_KEY);释放开在测试,只要某个抢到锁的线程执行完毕并且释放了锁资源,其他的线程很快就会获取到锁。