针对现在的大多数项目都是基于微服务的开发,在docker上部署多实例时,就会遇到分布式并发问题。解决分布式并发问题的方案有很多,有基于数据库啊、缓存啊、ZooKeeper啊等等,我就不举例其中的优劣了,很多博客上都有很详细的说明。我在公司也做过几种方案的分布式锁,
这里我重点说下,我在利用Redis做分布式锁的一些心得和体会。
你看!!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(华丽分割线)~~~~~~~~~~~~~~~~~~~~~~~~~~~
1.引入redis jar包
Gradle项目:
compile ('org.springframework.boot:spring-boot-starter-data-redis')
Maven项目:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
2.配置Redis
很多人都喜欢在把Redis的配置写在yml文件里面,这里我不否定这种做法。但是,为了能够更充分的利用Redis针对不同场景的客户端。我这里把Redis配置做成配置类并集群不同客户端配置。
@Configuration
public class JedisPoolConfiguration {
//yml中读取基本配置
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.password}")
private String redisPassword;
@Bean JedisPool jedisPool(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(256);//最大连接数
poolConfig.setMaxWaitMillis(10000);//获取连接10s内拿不到异常
return new JedisPool(poolConfig,
redisHost,
Protocol.DEFAULT_PORT,
Protocol.DEFAULT_TIMEOUT,
redisPassword,
0);
}
@Bean JedisPool couponsPool(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(256);
poolConfig.setMaxWaitMillis(10000);//获取连接10s内拿不到异常
return new JedisPool(poolConfig,
redisHost,
Protocol.DEFAULT_PORT,
Protocol.DEFAULT_TIMEOUT,
redisPassword,
2);
}
...//可以添加特殊配置需求的Redis客户端
}
3.RedisLockHelper(主要是获取锁和解锁)
@Component
public class RedisLockHelper {
@Autowired
private JedisPool jedisPool;
private static final String LOCK_SUCCESS = "OK"; //加锁成功的标识
private static final Long RELEASE_SUCCESS = 1L; //解锁成功标识
private static final String SET_IF_NOT_EXIST = "NX"; //key不存在则set
private static final String SET_WITH_EXPIRE_TIME = "PX";//超时时间
private static final Long tryIntervalMillis = 1000 //轮询间隔毫秒数
private static final int maxTryCount = //最大轮询次数
/**
* 尝试获取分布式锁
* @param Key 锁
* @param expireTime 超时时间
* @return
*/
public String doTryLock( String Key, int expireTime) {
String requestId = UUID.randomUUID().toString();//请求标识(标识,验证解锁)
Jedis jedis = null;
try{
jedis=jedisPool.getResource();
String result = jedis.set(Key, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);//详情请看redis官方API,这里不多介绍
if (LOCK_SUCCESS.equals(result)) {
logger.info("Lock Success");
return requestId;
}
}catch (Exception e){
logger.info("Lock Error",e);
}finally {//使用完后释放资源
if (Objects.nonNull(jedis)) jedis.close();
}
return null;
}
/**
* 轮询尝试获取锁
* @param lockKey 唯一锁
* @param expireTime 超期时间
* @return 成功返回true,超过轮询次数或异常返回false
*/
public String tryLock(String lockKey, int expireTime){
int tryCount = 0;
while (true){
if (++tryCount >= maxTryCount) {
// 获取锁超时
return null;
}
try {
String result = doTryLock(lockKey, expireTime);
if (StringUtils.isNotEmpty(result)) {
return result;
}
Thread.sleep(tryIntervalMillis);//尝试加锁失败,休息
} catch (Exception e) {
logger.error("tryLock interrupted", e);
return null;
}
}
}
/**
* 释放分布式锁
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public void releaseLock(String lockKey, String requestId) {
Jedis jedis = null;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; //Lua脚本 详情:http://redisdoc.com/string/set.html#set
try{
jedis = jedisPool.getResource();
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
logger.info("Unlock success and unlock id :{}",requestId);
}
}catch (Exception e){
logger.error("releaseLock error", e);
}finally {//使用完后释放资源
if (Objects.nonNull(jedis)) jedis.close();
}
}
}
4.Redis分布式锁的场景应用
//创建一个有粒度的key,根据你的具体业务确定
String lockKey = “xxx_xxx”;
String lockValue = redisLockHelper.tryLock(lockKey,"100000"));//第二个参数是过期时间
if (StringUtils.isBlank(lockValue)) {
logger.error(" redisLock failed. Please reset the response time or the number of lock attempts");
throw new BadRequestException("系统太忙,本次操作失败");
// 一般来说,不会走到这一步;如果真的有这种情况,并且在合理设置锁尝试次数和等待响应时间之后仍然处理不过来,可能需要考虑优化程序响应时间或者用消息队列排队执行了
}
try {
//具体要锁的业务:建议粒度越小越好
}catch (Exception e){
//异常处理
}finally {
//解锁
redisLockHelper.releaseLock(lockKey, lockValue);
}
整个Redis分布式锁的使用流程大致就是这样,具体的一些详细API介绍后续会补充。欢迎大佬提出宝贵意见(欢迎加714635093一起讨论)~~~