当前位置: 首页 > article >正文

乐观锁VS分布式锁实现抢单服务

司机开始接单,乘客填写出发地——目的地,开始下单
在这里插入图片描述

service-order模块

@Operation(summary="司机抢单")
@GetMapping("/robNewOrder/{driverId}/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long driverId,@PathVariable Long orderId){
	return Result.ok(orderInfoServcie.robNewOrder(driverId,orderId));
}
@Override
public Boolean robNewOrder(Long driverId,Long orderId){
	
	/**
		为了防止数据库压力过大,在saveOrderInfo添加订单的时候
		需要向redis添加,redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
						"0",RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME,
						TimeUnit.MINUTES);
	*/
	if(redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}

	//如果订单存在Redis,则修改订单状态"已经接单"
	//修改条件:根据订单id+司机id
	LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(orderInfo::getId,orderId);
	OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
	
	orderInfo.setStatus(OrderStatus.ACCEPTED.getStats());
	orderInfo.setDriverId(driverId);
	orderInfo.setAccpetTime(new Date());
	
	int rows = orderInfoMapper.updateById(orderInfo);
	if(rows != 1){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}
	redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
	return true;
}

server-order-client远程定义模块

@GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")
Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId,@PathVariable("orderId") Long orderId);

web调用模块

@Operation(summary = "司机抢单")
@GuiguLogin()
@GetMapping("/robNewOrder/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long orderId){
	
	Long driverId = AuthContextHolder.getUserId();
	return Result.ok(orderService.robNewOrder(driverId,orderId));
}
@Override
public Boolean robNewOrder(Long driverId,Long orderId){
	return orderInfoFeignClient.robNewOrder(driverId,orderId).getData();
}

本地锁VS分布式锁

在这里插入图片描述

乐观锁进行对抢单功能的优化

司机抢单 update order_info set status=2,driver_id=?,accept_time=> where id=? and status= 1
其中将status订单状态(等待接单)作为版本号来判断

@Override
public Boolean robNewOrder(Long driverId,Long orderId){
	
	/**
		为了防止数据库压力过大,在saveOrderInfo添加订单的时候
		需要向redis添加,redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
						"0",RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME,
						TimeUnit.MINUTES);
	*/
	if(redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}

	//司机抢单 update order_info set status=2,driver_id=?,accept_time=> where id=? and status= 1
	//其中将status订单状态(等待接单)作为版本号来判断
	LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
	wrapper.eq(OrderInfo::getId,orderId);
	wrapper.eq(OrderInfo::getStatus,OrderStatus.WAITING_ACCEPT.getStatus());//以1作为条件

	OrderInfo orderInfo = new OrderInfo();
	orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());//改成2
	orderInfo.setDriverId(driverId);
	orderInfo.setAcceptTime(new Date());
	
	int rows = orderInfoMapper.updateById(orderInfo,wrapper);
	if(rows != 1){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}
	redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
	return true;
}

分布式锁Redisson进行对抢单功能的优化

本地锁的局限

@GetMapping("testLock")
public Result testLock(){
	
	testService.testLock();
	return Return.ok();
}

使用synchronized解决并发问题,通过jmeter工具实现模拟测试

@Override
public synchronized void testLock(){
	
	String value = redisTemplate.opsForValue().get("num");
	if(StringUtils.isBlank(value)){
		return;
	}
	int num = Integer.parseInt(value);
	retisTemplate.opsForValue().set("num",String.valueOf(++num));
}

通过idea进行对服务的复制,模拟集群场景
通过网关服务进行转发,jmeter进行车市
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Redis实现分布式锁

  • 通过redis的setnx命令,具有原子性

在这里插入图片描述

@Override
public void testLock(){
	
	//获取锁
	Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock","lock");
	
	//如果为true,表示获取锁成功
	if(ifAbsent){
		String value = redisTemplate.opsForValue().get("num");
		if(StringUtils.isBlank(value)){
			return;
		}

		//释放锁
		redisTemplate.delete("lock");
	}else {
		try{
			Thread.sleep(100);
			this.testLock();
		}catch(InterrutedException e){
			e.printStackTrace();
		}
	}
	
	
}

如果释放锁之前,出现了异常,导致无法释放锁
解决方案:以下两种

  • 将redisTemplate.delete(“lock”)释放锁放到finally中
  • 为锁设置过期时间 redisTemplate.opsForValue().setIfAbsent(“lock”,“lock”,3,Time.SECONDS);

如果释放的是别人的锁
在这里插入图片描述
解决方案:

  • UUID防止锁误删
@Override
public void testLock(){
	
	String uuid = UUID.randomUUID().toString();

	//key   value    过期时间   过期时间的单位
	Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
	if(ifAbsent){
		String value = redisTemplate.opsForValue().get("num");
		if(StringUtils.isBlank(value)){
			return;
		}
		int num = Integer.parseInt(value);
		redisTemplate.opsForValue().set("num",String.valueOf(++num));
		
		//释放锁
		String redisUUID = redisTemplate.opsForValue().get("lock");
		if(uuid.equals(redisUUID)){
			redisTemplate.delete("lock");
		}
		
	}else{
		try{
			Thread.sleep(100);
			this.testLock();
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

判断释放锁后,锁过期自动释放
当2获取锁,1直接释放2的锁了,不能保证原子性
在这里插入图片描述
解决方案:

  • lua脚本
@Override
public void testLock(){
	
	String uuid = UUID.randomUUID().toString();

	//key   value    过期时间   过期时间的单位
	Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
	if(ifAbsent){
		String value = redisTemplate.opsForValue().get("num");
		if(StringUtils.isBlank(value)){
			return;
		}
		int num = Integer.parseInt(value);
		redisTemplate.opsForValue().set("num",String.valueOf(++num));
		
		//释放锁(通过lua脚本)
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
		String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
		redisScript.setScriptText(script);
		redisScript.setResultType(Long.class);
		stringRedisTemplate.execute(redisScript,Arrays.asList("lock"),uuid);
		
	}else{
		try{
			Thread.sleep(100);
			this.testLock();
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

Redisson实现分布式锁

  • 获取锁 redissonClient.getLock(“lock”)
  • 阻塞一直等待直到获取锁,获取锁之后默认过期时间30s lock.lock()
  • 获取到锁,锁过期时间10s lock.lock(10,TimeUnit.SECONDS)
  • 第一个参数获取锁等待时间,第二个参数获取锁过期时间 lock.tryLock(30,10,TimeUnit.SECONDS)
  • 释放锁 lock.unlock()

①、引入依赖redisson

②、配置类

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
public class RedissonConfig{
	
	private String host;
	private String password;
	private String port;

	private int timeout = 3000;
	private static String ADDRESS_PREFIX = "redis://";

	@Bean
	RedissonClient redissonSingle(){
		
		Config config = new Config();
		if(!StringUtils.hasText(host)){
			throw new RuntimeException("host is empty");
		}
		SingleServerConfig serverConfig = config.useSingleServer()
										.setAddress(ADDRESS_PREFIX + THIS.HOST + ":" + port)
										.setTimeout(this.timeout);
		if(StringUtils.hasText(this.password)){
			serverConfig.setPassword(this.password);
		}	
		return Redisson.create(config);					
	}
}

③、添加Redisson分布式锁到司机抢单功能

OrderInfoServiceImpl添加分布式锁

@Autowired
private RedissonClient redissonClient;

@Override
public Boolean robNewOrder(Long driverId,Long orderId){
	
	if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}

	//创建锁
	RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);

	try{
		//获取锁  等待时间  过期时间  时间单位
		boolean flag = lock.tryLock(10,5,TimeUnit.SECONDS);
		
		if(flag){//获取锁成功
			LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
			wrapper.eq(OrderInfo::getId,orderId);
			OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
			orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
			orderInfo.setDriverId(driverId);
			orderInfo.setAccpetTime(new Date());
		
			int rows = orderInfoMapper.updateById(orderInfo);
			if(rows!=1){
				throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
			}
			
			redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
		}
		
	}catch(Exception e){
		throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
	}finally{
		//释放锁
		if(lock.isLocked()){
			lock.unlock();
		}
	}
	
	return true;
}

http://www.kler.cn/a/586625.html

相关文章:

  • windows安装两个或多个JDK,并实现自由切换
  • git证书文件路径错误
  • GitHub 汉化插件,GitHub 中文化界面安装全教程_最新
  • linux root丢失修改密
  • 【spring-boot-starter-data-neo4j】创建结点和查找结点操作
  • 用tiptap搭建仿google-docs工具栏
  • JavaScript基础篇:五、 流程控制语句
  • java学习笔记2
  • 告别XML模板的繁琐!Word文档导出,easy!
  • Kubernetes 单节点集群搭建
  • tcpdump剖析:入门网络流量分析实战指南
  • Ubuntu从源代码编译安装QT
  • 进程间通信--匿名管道
  • 【蓝桥杯】雪地工程核弹引爆控制器最小数量计算
  • Pytorch实现之最小二乘梯度归一化设计
  • 在离线情况下如何使用 Python 翻译文本
  • 【JVM】性能监控与调优概述篇
  • 基于RAGFlow本地部署DeepSpeek-R1大模型与知识库:从配置到应用的全流程解析
  • Spring Boot 中 BootstrapRegistryInitializer 的作用与示例
  • Ubuntu中为curl和Docker配置代理