分布式锁,即同一个服务被部署成集群,这样就涉及到资源争抢的问题,单机的资源,直接用jvm内部的synchronized的锁就可以
分布式锁的接口
package com.online.taxi.order.service;
public interface GrabService {
/**
* 司机抢单
* @param orderId
* @param driverId
* @return
*/
public String grabOrder(int orderId , int driverId);
}
分布式锁的实现类,里面需要注入需要抢占资源的业务类
package com.online.taxi.order.service.impl;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service("grabRedisLockService")
public class GrabRedisLockServiceImpl implements GrabService {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
OrderService orderService;
@Override
public String grabOrder(int orderId , int driverId){
//生成key
String lock = "order_"+(orderId+"");
/*
* 情况一,如果锁没执行到释放,比如业务逻辑执行一半,运维重启服务,或 服务器挂了,没走 finally,怎么办?
* 加超时时间 如果不设置超时时间,资源一直被占用,不能被其它人抢用,设置了过期时间,就会释放锁
* setnx
*/
// boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"");
// if(!lockStatus) {
// return null;
// }
/*
* 情况二:加超时时间,会有加不上的情况,运维重启
*/
// boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"");
// stringRedisTemplate.expire(lock.intern(), 30L, TimeUnit.SECONDS);
// if(!lockStatus) {
// return null;
// }
/*
* 情况三:超时时间应该一次加,不应该分2行代码,
*
*/
boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"", 30L, TimeUnit.SECONDS);
// 开个子线程,原来时间N,每个n/3,去续上n
if(!lockStatus) {
return null;
}
try {
System.out.println("司机:"+driverId+" 执行抢单逻辑");
boolean b = orderService.grab(orderId, driverId);
if(b) {
System.out.println("司机:"+driverId+" 抢单成功");
}else {
System.out.println("司机:"+driverId+" 抢单失败");
}
} finally {
/**
* 这种释放锁有,可能释放了别人的锁。
*/
// stringRedisTemplate.delete(lock.intern());
/**
* 下面代码避免释放别人的锁
*/
if((driverId+"").equals(stringRedisTemplate.opsForValue().get(lock.intern()))) {
stringRedisTemplate.delete(lock.intern());
}
}
return null;
}
}
设置锁的有效时间,即redis设置key的有效期时间应该》业务执行的时间,简单来说就是程序执行的时间在锁的有效期内
redis 如果直接释放锁,即删除对应的key,这样可能会释放到别人锁,这里有遵循一个原则,谁加的锁谁释放
这里key值肯定是相同的,代表所争抢的资源,这里就是指订单的id
释放锁的方式有两种:争抢的key 相同
1.在value上做文章,可以取出key对应的value,确认是否是自己加的锁,这样就能正确的释放掉锁
2.写一个守护进程,当锁的资源(执行的业务逻辑)执行到有效期的1/3时,如果业务还没有执行完,则将锁续期(将key的有效期延长)至设置的有效期
续期的代码:
package com.online.taxi.order.service;
public interface RenewGrabLockService {
/**
* 续约
* @param key
* @param value
* @param time
*/
public void renewLock(String key , String value , int time);
}
package com.online.taxi.order.service.impl;
import com.online.taxi.order.service.RenewGrabLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RenewGrabLockServiceImpl implements RenewGrabLockService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
@Async
public void renewLock(String key, String value, int time) {
String v = redisTemplate.opsForValue().get(key);
if (v.equals(value)){
int sleepTime = time / 3;
try {
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
redisTemplate.expire(key,time,TimeUnit.SECONDS);
renewLock(key,value,time);
}
}
}
如果直接删除掉key,为什么可能会释放掉别人的锁呢?
当运维重启时或者设置的锁的时间《执行业务逻辑的时间,这时业务还没执行完,锁就释放掉了,多个进程进来,就会释放掉别人的锁。
在service层使用@Async这个注解,需要在启动类上增加@EnableAsync 注解