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

打印 上一主题 下一主题

主题 940|帖子 940|积分 2820

司机开始接单,乘客填写出发地——目标地,开始下单

service-order模块
  1. @Operation(summary="司机抢单")
  2. @GetMapping("/robNewOrder/{driverId}/{orderId}")
  3. public Result<Boolean> robNewOrder(@PathVariable Long driverId,@PathVariable Long orderId){
  4.         return Result.ok(orderInfoServcie.robNewOrder(driverId,orderId));
  5. }
复制代码
  1. @Override
  2. public Boolean robNewOrder(Long driverId,Long orderId){
  3.        
  4.         /**
  5.                 为了防止数据库压力过大,在saveOrderInfo添加订单的时候
  6.                 需要向redis添加,redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
  7.                                                 "0",RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME,
  8.                                                 TimeUnit.MINUTES);
  9.         */
  10.         if(redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
  11.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  12.         }
  13.         //如果订单存在Redis,则修改订单状态"已经接单"
  14.         //修改条件:根据订单id+司机id
  15.         LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
  16.         wrapper.eq(orderInfo::getId,orderId);
  17.         OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
  18.        
  19.         orderInfo.setStatus(OrderStatus.ACCEPTED.getStats());
  20.         orderInfo.setDriverId(driverId);
  21.         orderInfo.setAccpetTime(new Date());
  22.        
  23.         int rows = orderInfoMapper.updateById(orderInfo);
  24.         if(rows != 1){
  25.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  26.         }
  27.         redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
  28.         return true;
  29. }
复制代码
server-order-client远程界说模块
  1. @GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")
  2. Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId,@PathVariable("orderId") Long orderId);
复制代码
web调用模块
  1. @Operation(summary = "司机抢单")
  2. @GuiguLogin()
  3. @GetMapping("/robNewOrder/{orderId}")
  4. public Result<Boolean> robNewOrder(@PathVariable Long orderId){
  5.        
  6.         Long driverId = AuthContextHolder.getUserId();
  7.         return Result.ok(orderService.robNewOrder(driverId,orderId));
  8. }
复制代码
  1. @Override
  2. public Boolean robNewOrder(Long driverId,Long orderId){
  3.         return orderInfoFeignClient.robNewOrder(driverId,orderId).getData();
  4. }
复制代码
当地锁VS分布式锁


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

司机抢单 update order_info set status=2,driver_id=?,accept_time=> where id=? and status= 1
其中将status订单状态(等待接单)作为版本号来判断
  1. @Override
  2. public Boolean robNewOrder(Long driverId,Long orderId){
  3.        
  4.         /**
  5.                 为了防止数据库压力过大,在saveOrderInfo添加订单的时候
  6.                 需要向redis添加,redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
  7.                                                 "0",RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME,
  8.                                                 TimeUnit.MINUTES);
  9.         */
  10.         if(redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
  11.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  12.         }
  13.         //司机抢单 update order_info set status=2,driver_id=?,accept_time=> where id=? and status= 1
  14.         //其中将status订单状态(等待接单)作为版本号来判断
  15.         LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
  16.         wrapper.eq(OrderInfo::getId,orderId);
  17.         wrapper.eq(OrderInfo::getStatus,OrderStatus.WAITING_ACCEPT.getStatus());//以1作为条件
  18.         OrderInfo orderInfo = new OrderInfo();
  19.         orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());//改成2
  20.         orderInfo.setDriverId(driverId);
  21.         orderInfo.setAcceptTime(new Date());
  22.        
  23.         int rows = orderInfoMapper.updateById(orderInfo,wrapper);
  24.         if(rows != 1){
  25.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  26.         }
  27.         redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
  28.         return true;
  29. }
复制代码
分布式锁Redisson进行对抢单功能的优化

当地锁的范围
  1. @GetMapping("testLock")
  2. public Result testLock(){
  3.        
  4.         testService.testLock();
  5.         return Return.ok();
  6. }
复制代码
使用synchronized解决并发问题,通过jmeter工具实现模仿测试
  1. @Override
  2. public synchronized void testLock(){
  3.        
  4.         String value = redisTemplate.opsForValue().get("num");
  5.         if(StringUtils.isBlank(value)){
  6.                 return;
  7.         }
  8.         int num = Integer.parseInt(value);
  9.         retisTemplate.opsForValue().set("num",String.valueOf(++num));
  10. }
复制代码
通过idea进行对服务的复制,模仿集群场景
通过网关服务进行转发,jmeter进行车市





Redis实现分布式锁


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

  1. @Override
  2. public void testLock(){
  3.        
  4.         //获取锁
  5.         Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock","lock");
  6.        
  7.         //如果为true,表示获取锁成功
  8.         if(ifAbsent){
  9.                 String value = redisTemplate.opsForValue().get("num");
  10.                 if(StringUtils.isBlank(value)){
  11.                         return;
  12.                 }
  13.                 //释放锁
  14.                 redisTemplate.delete("lock");
  15.         }else {
  16.                 try{
  17.                         Thread.sleep(100);
  18.                         this.testLock();
  19.                 }catch(InterrutedException e){
  20.                         e.printStackTrace();
  21.                 }
  22.         }
  23.        
  24.        
  25. }
复制代码
如果开释锁之前,出现了非常,导致无法开释锁
解决方案:以下两种


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

解决方案:


  • UUID防止锁误删
  1. @Override
  2. public void testLock(){
  3.        
  4.         String uuid = UUID.randomUUID().toString();
  5.         //key   value    过期时间   过期时间的单位
  6.         Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
  7.         if(ifAbsent){
  8.                 String value = redisTemplate.opsForValue().get("num");
  9.                 if(StringUtils.isBlank(value)){
  10.                         return;
  11.                 }
  12.                 int num = Integer.parseInt(value);
  13.                 redisTemplate.opsForValue().set("num",String.valueOf(++num));
  14.                
  15.                 //释放锁
  16.                 String redisUUID = redisTemplate.opsForValue().get("lock");
  17.                 if(uuid.equals(redisUUID)){
  18.                         redisTemplate.delete("lock");
  19.                 }
  20.                
  21.         }else{
  22.                 try{
  23.                         Thread.sleep(100);
  24.                         this.testLock();
  25.                 }catch(InterruptedException e){
  26.                         e.printStackTrace();
  27.                 }
  28.         }
  29. }
复制代码
判断开释锁后,锁过期自动开释
当2获取锁,1直接开释2的锁了,不能包管原子性

解决方案:


  • lua脚本
  1. @Override
  2. public void testLock(){
  3.        
  4.         String uuid = UUID.randomUUID().toString();
  5.         //key   value    过期时间   过期时间的单位
  6.         Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
  7.         if(ifAbsent){
  8.                 String value = redisTemplate.opsForValue().get("num");
  9.                 if(StringUtils.isBlank(value)){
  10.                         return;
  11.                 }
  12.                 int num = Integer.parseInt(value);
  13.                 redisTemplate.opsForValue().set("num",String.valueOf(++num));
  14.                
  15.                 //释放锁(通过lua脚本)
  16.                 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
  17.                 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
  18.                 redisScript.setScriptText(script);
  19.                 redisScript.setResultType(Long.class);
  20.                 stringRedisTemplate.execute(redisScript,Arrays.asList("lock"),uuid);
  21.                
  22.         }else{
  23.                 try{
  24.                         Thread.sleep(100);
  25.                         this.testLock();
  26.                 }catch(InterruptedException e){
  27.                         e.printStackTrace();
  28.                 }
  29.         }
  30. }
复制代码
Redisson实现分布式锁


  • 获取锁 redissonClient.getLock(“lock”)
  • 阻塞一直等待直到获取锁,获取锁之后默认过期时间30s lock.lock()
  • 获取到锁,锁过期时间10s lock.lock(10,TimeUnit.SECONDS)
  • 第一个参数获取锁等待时间,第二个参数获取锁过期时间 lock.tryLock(30,10,TimeUnit.SECONDS)
  • 开释锁 lock.unlock()
①、引入依靠redisson
②、设置类
  1. @Data
  2. @Configuration
  3. @ConfigurationProperties(prefix = "spring.data.redis")
  4. public class RedissonConfig{
  5.        
  6.         private String host;
  7.         private String password;
  8.         private String port;
  9.         private int timeout = 3000;
  10.         private static String ADDRESS_PREFIX = "redis://";
  11.         @Bean
  12.         RedissonClient redissonSingle(){
  13.                
  14.                 Config config = new Config();
  15.                 if(!StringUtils.hasText(host)){
  16.                         throw new RuntimeException("host is empty");
  17.                 }
  18.                 SingleServerConfig serverConfig = config.useSingleServer()
  19.                                                                                 .setAddress(ADDRESS_PREFIX + THIS.HOST + ":" + port)
  20.                                                                                 .setTimeout(this.timeout);
  21.                 if(StringUtils.hasText(this.password)){
  22.                         serverConfig.setPassword(this.password);
  23.                 }       
  24.                 return Redisson.create(config);                                       
  25.         }
  26. }
复制代码
③、添加Redisson分布式锁到司机抢单功能
OrderInfoServiceImpl添加分布式锁
  1. @Autowired
  2. private RedissonClient redissonClient;
  3. @Override
  4. public Boolean robNewOrder(Long driverId,Long orderId){
  5.        
  6.         if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)){
  7.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  8.         }
  9.         //创建锁
  10.         RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);
  11.         try{
  12.                 //获取锁  等待时间  过期时间  时间单位
  13.                 boolean flag = lock.tryLock(10,5,TimeUnit.SECONDS);
  14.                
  15.                 if(flag){//获取锁成功
  16.                         LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
  17.                         wrapper.eq(OrderInfo::getId,orderId);
  18.                         OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
  19.                         orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
  20.                         orderInfo.setDriverId(driverId);
  21.                         orderInfo.setAccpetTime(new Date());
  22.                
  23.                         int rows = orderInfoMapper.updateById(orderInfo);
  24.                         if(rows!=1){
  25.                                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  26.                         }
  27.                        
  28.                         redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
  29.                 }
  30.                
  31.         }catch(Exception e){
  32.                 throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
  33.         }finally{
  34.                 //释放锁
  35.                 if(lock.isLocked()){
  36.                         lock.unlock();
  37.                 }
  38.         }
  39.        
  40.         return true;
  41. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表