赞
踩
分布式锁(多服务共享锁) 在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问
控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
当在打车软件中,乘客下了订单。多个司机抢单,此时因为单子只有一个,多个司机对此共享资源进行抢,此处应该使用分布式锁;
@GetMapping("/do/{orderId}")
public String grab(@PathVariable("orderId") int orderId, int driverId){
System.out.println("order:"+orderId+",driverId:"+driverId);
//此处调用锁控制层代码
grabService.grabOrder(orderId,driverId);
return "";
}
使用synchronized 不能保证多台服务器只有一个抢成功;因为synchronized 只能锁本服务的资源;多台服务的资源是锁不住的;
@Autowired
OrderService orderService;
@Override
public String grabOrder(int orderId, int driverId) {
String lock = (orderId+"");
synchronized (lock.intern()) {
try {
System.out.println("司机:"+driverId+" 执行抢单逻辑");
//此处调用订单业务代码
boolean b = orderService.grab(orderId, driverId);
if(b) {
System.out.println("司机:"+driverId+" 抢单成功");
}else {
System.out.println("司机:"+driverId+" 抢单失败");
}
} finally {
}
}
return null;
}
这一层就是写的伪代码,后续并不关注他
1:判断一个固定的key在Redis里是否存在,如果这个key存在,说明这把锁正在被使用,反之说明这把锁没有被使用可以被过去
2:如果一个请求获取锁成功,但还没等释放释放锁的时候,这个服务挂掉的改怎么办?这会导致其他的服务永远也无法获取到锁,
此时可以在set key时,设置key的过期时间
3:加超时时间,会有加不上的情况,比如设置key和加过期时间不是原子操作,
此时我们可以将这两个操作变为一个方法,实现原子操作
4:在一个请求释放锁之前,key过期了,锁又被其他请求用了,然后这个请求有释放了锁,导致了释放其他请求的锁,这种情况怎么处理?
那我们可以在sey key 时,value存放针对于这个请求唯一的字符串,在释放锁之前判断是否是我创建的锁,也就是说只能释放自己的锁
5:一个请求还没执行到关键代码,锁就过期(超时)了,这又会导致并发抢锁现象,这种情况怎么处理?
我们可以使用另一个线程(守护线程),来观察这次请求是否结束,如果没有结束延长锁的过期时间(续约)
package com.online.taxi.order.service.impl;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;
import com.online.taxi.order.service.RenewGrabLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author
*/
@Service
public class GrabRedisLockServiceImpl implements GrabService {
//redis调用连接
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
OrderService orderService;
@Autowired
RenewGrabLockService renewGrabLockService;
@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+"", 10L, TimeUnit.SECONDS);
if(!lockStatus) {
return null;
}
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
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());
/**
* 下面代码避免释放别人的锁,通过key对应的value值来判断是否为自己的锁
*/
if((driverId+"").equals(stringRedisTemplate.opsForValue().get(lock.intern()))) {
stringRedisTemplate.delete(lock.intern());
}
}
return null;
}
}
1:写一个异步的守护线程来主动为当前redis的key增加到期时间
@Service
public class RenewGrabLockServiceImpl implements RenewGrabLockService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
@Async
public void renewLock(String key, String value, int time) {
System.out.println("续命"+key+" "+value);
String v = redisTemplate.opsForValue().get(key);
while (StringUtils.isNotBlank(v) && v.equals(value)){
int sleepTime = time / 3;
try {
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
}
}
2:业务代码
@Service
public class GrabRedisLockServiceImpl implements GrabService {
//redis调用连接
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
OrderService orderService;
@Autowired
RenewGrabLockService renewGrabLockService;
@Override
public String grabOrder(int orderId , int driverId){
//生成key
String lock = "order_"+(orderId+"");
boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"", 10L, TimeUnit.SECONDS);
// 开个子线程,原来时间N,每个n/3,去续上n
renewGrabLockService.renewLock(lock.intern(),driverId+"",10);
if(!lockStatus) {
return null;
}
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("司机:"+driverId+" 执行抢单逻辑");
boolean b = orderService.grab(orderId, driverId);
if(b) {
System.out.println("司机:"+driverId+" 抢单成功");
}else {
System.out.println("司机:"+driverId+" 抢单失败");
}
} finally {
/**
* 下面代码避免释放别人的锁,通过key对应的value值来判断是否为自己的锁
*/
if((driverId+"").equals(stringRedisTemplate.opsForValue().get(lock.intern()))) {
stringRedisTemplate.delete(lock.intern());
}
}
return null;
}
}
目前基于Redis实现的分布式锁常用的框架是Redisson,它的使用比较简单,在项目中引入Redisson的依赖,然后基于Redis实现分布式锁的加锁与释放锁,
Redisson 的加锁机制:线程1去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。线程2也去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
Redisson 的还有一个重要的续约机制,只要客户端一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间。
Redisson 加锁工作流程如下:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
@Service
public class GrabRedisRedissonServiceImpl implements GrabService {
@Autowired
RedissonClient redissonClient;
@Autowired
OrderService orderService;
@Override
public String grabOrder(int orderId , int driverId){
//生成key
String lock = "order_"+(orderId+"");
RLock rlock = redissonClient.getLock(lock.intern());
try {
// 此代码默认 设置key 超时时间30秒,过10秒,再延时
rlock.lock();
System.out.println("司机:"+driverId+" 执行抢单逻辑");
boolean b = orderService.grab(orderId, driverId);
if(b) {
System.out.println("司机:"+driverId+" 抢单成功");
}else {
System.out.println("司机:"+driverId+" 抢单失败");
}
} finally {
rlock.unlock();
}
return null;
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。