文章目录

  • 分布式锁适用的场景
  • 我们真的需要锁么?
  • 分布式锁解决方案
  • 1.无锁情况
  • 2.JVM 锁
  • 3.基于mysql
  • 4.基于Redis
  • 4.1、手写redis(不推荐使用)
  • 4.2、单节点redisson
  • 4.2.1、小结
  • 4.3、红锁RedLock(多master)
  • 4.3.1、小结
  • 4.4、redis+lua脚本实现分布式锁
  • 4.5、Spring Integration框架 + redis(自定义分布式锁注解)
  • 5.基于zookeeper


分布式锁适用的场景

分布式锁应用场景:服务集群,比如N个订单服务,接受到大量司机的发送的对一个订单的抢单请求。如果是单个服务,可以用jvm锁控制,但是服务集群,jvm 就不行了。因为不在一个jvm中。

JVM的锁,它是单独程序里的锁,解决不了集群中(分布式环境)所需要的锁,这种集群所需要的就是分布式锁。

分布式锁的解决方案_分布式


解决方法,如果想要生效,就要把锁的 “字符串” 提取出来,放在外面(依赖第三方),让三个服务用同一个锁。

我们真的需要锁么?

需要锁的条件:

  1. 多任务环境下(进程,线程)
  2. 任务都对同一共享资源进行写操作。
  3. 对资源的访问是互斥的。

操作周期:

  1. 竞争锁。获取锁后才能对资源进行操作。
  2. 占有锁。操作中。
  3. 其他竞争者,任务阻塞。
  4. 占有锁者,释放锁。继续从1开始。

分布式锁解决方案

简单描述下场景,tbl_order表:订单表,如果抢单成功,order_status由0变为1。

CREATE TABLE `tbl_order` (
  `order_id` int(8) NOT NULL,
  `order_status` int(8) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tbl_order
-- ----------------------------
INSERT INTO `tbl_order` VALUES ('1', '1');

在测试时,可以用工具apache jmeter用来模拟并发请求

1.无锁情况

controller:

package com.online.taxi.order.controller;


import com.online.taxi.order.service.GrabService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

/** 抢单服务
 * @author :wlw
 */
@RestController
@RequestMapping("/grab-driver")
public class GrabOrderController {

    // 无锁
    @Autowired
    @Qualifier("grabNoLockService")
    // jvm锁
//    @Qualifier("grabJvmLockService")
    // mysql锁
//    @Qualifier("grabMysqlLockService")
    // 手写redis
//    @Qualifier("grabRedisLockService")
    //单个Redisson=redis son
//    @Qualifier("grabRedisRedissonService")
    // 红锁
//    @Qualifier("grabRedisRedissonRedLockLockService")
    // cloud 锁
//    @Qualifier("cloudService")
    // zk 锁
//    @Qualifier("zookeeperService")
    private GrabService grabService;

    @GetMapping("/do/{orderId}")
    public String grabMysql(@PathVariable("orderId") int orderId, int driverId){
        System.out.println("order:"+orderId+",driverId:"+driverId);
        grabService.grabOrder(orderId,driverId);
        return "";
    } 
}

逻辑代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;

@Service("grabNoLockService")
public class GrabNoLockServiceImpl implements GrabService {
	
	@Autowired
	OrderService orderService;
	
	@Override
	public String grabOrder(int orderId, int driverId) {	
		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;
	}
}

结果:

tb_order表中 status设置0,抢单成功把 status改为1

执行jmeter。司机抢单。
结果:
司机:1 执行抢单逻辑
司机:2 执行抢单逻辑
司机:1 抢单成功
司机:3 执行抢单逻辑
司机:2 抢单成功
司机:4 执行抢单逻辑
司机:3 抢单失败
司机:5 执行抢单逻辑
司机:4 抢单失败
司机:6 执行抢单逻辑
司机:5 抢单失败
司机:7 执行抢单逻辑
司机:6 抢单失败
司机:8 执行抢单逻辑
司机:7 抢单失败
司机:8 抢单失败
司机:9 执行抢单逻辑
司机:10 执行抢单逻辑
司机:9 抢单失败
司机:10 抢单失败

1和2 都抢单成功。

2.JVM 锁

逻辑代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;
/**
 * jvm锁-当只有一个服务时有效,但如果启动两个service-order服务就会出错
 */
@Service("grabJvmLockService")
public class GrabJvmLockServiceImpl implements GrabService {   
   @Autowired
   OrderService orderService;
   
   @Override
   public String grabOrder(int orderId, int driverId) {
      String lock = (orderId+"");
      // 如果字符串s在字符串常量池中存在对应字面量,则intern()方法返回该字面量的地址;如果不存在,则创建一个对应的字面量,并返回该字面量的地址
      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;
   }
}
@Qualifier("grabJvmLockService")
只启动一个服务:只有一个成功的

司机:1 执行抢单逻辑
司机:1 抢单成功
司机:10 执行抢单逻辑
司机:10 抢单失败
司机:9 执行抢单逻辑
司机:9 抢单失败
司机:8 执行抢单逻辑
司机:8 抢单失败
司机:7 执行抢单逻辑
司机:7 抢单失败
司机:6 执行抢单逻辑
司机:6 抢单失败
司机:5 执行抢单逻辑
司机:5 抢单失败
司机:4 执行抢单逻辑
司机:4 抢单失败
司机:3 执行抢单逻辑
司机:3 抢单失败
司机:2 执行抢单逻辑
司机:2 抢单失败

只有一个抢单成功

但是:启动两个service-order8004,8005,则有下面情况:有两个司机都抢成功了

8005:
司机:1 执行抢单逻辑
司机:1 抢单成功
司机:9 执行抢单逻辑
司机:9 抢单失败
司机:7 执行抢单逻辑
司机:7 抢单失败
司机:5 执行抢单逻辑
司机:5 抢单失败
司机:3 执行抢单逻辑
司机:3 抢单失败

8004:
司机:2 执行抢单逻辑
司机:2 抢单成功
司机:10 执行抢单逻辑
司机:10 抢单失败
司机:8 执行抢单逻辑
司机:8 抢单失败
司机:6 执行抢单逻辑
司机:6 抢单失败
司机:4 执行抢单逻辑
司机:4 抢单失败

JVM锁问题:无法解决分布式,集群环境的问题。所以要用分布锁

3.基于mysql

sql:

CREATE TABLE `tbl_order` (
  `order_id` int(8) NOT NULL,
  `order_status` int(8) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tbl_order
-- ----------------------------
INSERT INTO `tbl_order` VALUES ('1', '1');

-- ----------------------------
-- Table structure for tbl_order_lock
-- ----------------------------
DROP TABLE IF EXISTS `tbl_order_lock`;
CREATE TABLE `tbl_order_lock` (
  `order_id` int(8) NOT NULL,
  `driver_id` int(8) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

加锁工具类:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import com.online.taxi.order.dao.TblOrderLockDao;
import com.online.taxi.order.entity.TblOrderLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.Data;
/**
 * 工具类-基于mysql数据库实现分布式锁
 * 增加一张表:tbl_order_lock,order_id订单id是主键,加锁就是往表里新增一条数据,TblOrderLock为表实体,冲突就报错,说明加锁失败
 */
@Service
@Data
public class MysqlLock implements Lock {

	@Autowired
	private TblOrderLockDao mapper;
	
	private ThreadLocal<TblOrderLock> orderLockThreadLocal ;

	@Override
	public void lock() {
		// 1、尝试加锁,成功就返回
		if(tryLock()) {
			System.out.println("尝试加锁");
			return;
		}
		// 2.休眠
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 3.递归再次尝试获取锁
		lock();
	}
	
	/**
	 * 	非阻塞式加锁,成功,就成功,失败就失败。直接返回
	 */
	@Override
	public boolean tryLock() {
		try {
			// 往表里新增一条数据,TblOrderLock为表实体,order_id为主键,冲突就报错,说明加锁失败
			// 这样就保证了一个订单对应一把锁
			TblOrderLock tblOrderLock = orderLockThreadLocal.get();
			mapper.insertSelective(tblOrderLock);
			System.out.println("加锁对象:"+orderLockThreadLocal.get());
			return true;
		}catch (Exception e) {
			return false;
		}
	}

	@Override
	public void unlock() {
		mapper.deleteByPrimaryKey(orderLockThreadLocal.get().getOrderId());
		System.out.println("解锁对象:"+orderLockThreadLocal.get());
		orderLockThreadLocal.remove();
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}


	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
}

业务逻辑代码:

import com.online.taxi.order.entity.TblOrderLock;
import com.online.taxi.order.lock.MysqlLock;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * 基于mysql 实现的分布式锁
 */
@Service("grabMysqlLockService")
public class GrabMysqlLockServiceImpl implements GrabService {

    @Autowired
    private MysqlLock lock;

    @Autowired
    OrderService orderService;

    ThreadLocal<TblOrderLock> orderLock = new ThreadLocal<>();

    @Override
    public String grabOrder(int orderId, int driverId) {
        // 生成锁
        // 生成key
        TblOrderLock ol = new TblOrderLock();
        ol.setOrderId(orderId);
        ol.setDriverId(driverId);

        orderLock.set(ol);
        lock.setOrderLockThreadLocal(orderLock);

        // 加锁lock
        lock.lock();

        // 执行业务
        try {
            System.out.println("司机:"+driverId+" 执行抢单逻辑");
            boolean b = orderService.grab(orderId, driverId);
            if(b) {
                System.out.println("司机:"+driverId+" 抢单成功");
            }else {
                System.out.println("司机:"+driverId+" 抢单失败");
            }
        }finally {
            // 释放锁
            lock.unlock();
        }
        // 执行业务
        return null;
    }
}

测试时要恢复数据。tbl_order 中status 为0,tbl_order_lock清空

@Qualifier(“grabMysqlLockService”) 实际用 事件实现。

8005:
加锁对象:TblOrderLock(orderId=1,driverId=6)
尝试加锁
司机:6 执行抢单逻辑
司机:6 抢单成功
解锁对象: TblOrderLock(derId=1,driverId=6)

加锁对象:TblOrderLock(orderId=1,driverId=4)
尝试加锁
司机:4 执行抢单逻辑
司机:4 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=4)

加锁对象:TblOrderLock(orderId=1,driverId=8)
尝试加锁
司机:8 执行抢单逻辑
司机:8 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=8)

加锁对象:TblOrderLock(orderId=1,driverId=10)
尝试加锁
司机:10 执行抢单逻辑
司机:10 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=10)

加锁对象:TblOrderLock(orderId=1,driverId=2)
尝试加锁
司机:2 执行抢单逻辑
司机:2 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=2)

8004:
加锁对象:TblOrderLock(orderId=1,driverId=7)
尝试加锁
司机:7 执行抢单逻辑
司机:7 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=7)
加锁对象:TblOrderLock(orderId=1,driverId=3)
尝试加锁
司机:3 执行抢单逻辑
司机:3 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=3)
加锁对象:TblOrderLock(orderId=1,driverId=1)
尝试加锁
司机:1 执行抢单逻辑
司机:1 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=1)
加锁对象:TblOrderLock(orderId=1,driverId=5)
尝试加锁
司机:5 执行抢单逻辑
司机:5 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=5)
加锁对象:TblOrderLock(orderId=1,driverId=9)
尝试加锁
司机:9 执行抢单逻辑
司机:9 抢单失败
解锁对象: TblOrderLock(derId=1,driverId=9)

问题:

1、如果中间出异常了,如何释放锁,用存储过程(mysql的触发器,超过多长时间的清掉,就像redis的过期时间一样),还是可以解决。

2、mysql 并发是由限制的。不适合高并发场景。mysql的性能比redis差10万倍

压测结果:https://help.aliyun.com/document_detail/150351.html?spm=a2c4g.11186623.6.1463.1e732d02nCMBBa

牛逼点的:https://help.aliyun.com/document_detail/101100.html?spm=5176.11065259.1996646101.searchclickresult.5a6316bcjenDJn

另外一篇可以参考:如何使用MySQL实现分布式锁

4.基于Redis

千万级流量以上的项目,基本上都会用redis。

redis:内存存储的数据结构服务器,内存数据库。可用于:数据库,高速缓存,消息队列。采用单线程模型,并发能力强大。10万并发没问题。

redis相关的分布锁知识:

redis的单进程单线程。

缓存有效期。有效期到就删除数据。

setnx():当key存在,不做任何操作,key不存在,才设置。

4.1、手写redis(不推荐使用)

用到了 stringRedisTemplate 用法,可看:StringRedisTemplate的相关用法 了解其使用方法

业务逻辑代码,简单使用:

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;

/**
 * 手写redis-实现的分布式锁
 */
@Service("grabRedisLockService")
public class GrabRedisLockServiceImpl implements GrabService {

	@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;
//    	}
    	
    	/*
    	 *  情况二:在加超时时间,会有加不上的情况,运维重启
    	 *  set值 和 设置超时时间不能分开写
    	 */
//    	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
		//锁的有效时间是10分钟 三分钟的时候去执行。如果key还在,延时
		renewGrabLockService.renewLock(lock.intern(), driverId+"", 10);
    	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;
    }
}
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;

/**
 * 守护线程-延长锁的有效时间,这只是一个参考,真实开发中不建议这么使用,真实开发中redis本身就支持分布式锁,并有守护线程机制
 */
@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);
        }
    }
}

4.2、单节点redisson

引依赖

<dependency>
			<groupId>org.redisson</groupId>
			<artifactId>redisson</artifactId>
			<version>3.3.2</version>
		</dependency>

配置文件:

spring: 
  application: 
    name: service-order
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    timeout: 2000
    password:
#自定义哨兵
sentinel:
  address:
    - 127.0.0.1:26379
    - 127.0.0.1:26479
    - 127.0.0.1:26579
  masterName: mymaster

配置类:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * redis配置类
 */
@Component
public class RedisConfig {

    @Autowired
    RedisSentinelProperties properties;
    //以下为redisson锁,哨兵
//    @Bean(name = "redisson")
//    @Order(1)
//    public Redisson getRedisson(){
//
//        Config config = new Config();
//        config.useSentinelServers()
//                .setMasterName(properties.getMasterName())
//                .addSentinelAddress(properties.getAddress())
//                .setDatabase(0);
//        return (Redisson) Redisson.create(config);
//    }
    //以上为redisson 哨兵锁

    //以下为红锁,独立的三台redis
//    @Bean(name = "redissonRed1")
//    @Primary
//    public RedissonClient redissonRed1(){
//        Config config = new Config();
//        config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
//        return Redisson.create(config);
//    }
//    @Bean(name = "redissonRed2")
//    public RedissonClient redissonRed2(){
//        Config config = new Config();
//        config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0);
//        return Redisson.create(config);
//    }
//    @Bean(name = "redissonRed3")
//    public RedissonClient redissonRed3(){
//        Config config = new Config();
//        config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0);
//        return Redisson.create(config);
//    }
    //以上为红锁

    
    // 单个redis
//    @Bean
//    @ConditionalOnMissingBean(StringRedisTemplate.class)
//    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
//    	StringRedisTemplate redisTemplate = new StringRedisTemplate();
//    	redisTemplate.setConnectionFactory(redisConnectionFactory);
//    	return redisTemplate;
//
//    }
    
    /**
     * 单个redisson
     * @return
     */
    @Bean
    public RedissonClient redissonClient() {
    	Config config = new Config();
    	config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);

    	return Redisson.create(config);
    }

//    @Bean
//    public Redisson redisson(){
//        Config config = new Config();
//        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
//        return (Redisson) Redisson.create(config);
//    }
}

业务逻辑代码:

import java.util.concurrent.TimeUnit;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.online.taxi.order.lock.MysqlLock;
import com.online.taxi.order.lock.RedisLock;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;

/**
 * @author wlw
 * 单个redisson-实现的分布式锁
 */
@Service("grabRedisRedissonService")
public class GrabRedisRedissonServiceImpl implements GrabService {

    // 高版本
	@Autowired
	RedissonClient redissonClient;

	// 低版本
//	@Autowired
//	Redisson redisson;

	@Autowired
	OrderService orderService;
	
    @Override
    public String grabOrder(int orderId , int driverId){
        //生成key
    	String lock = "order_"+(orderId+"");
    	
    	RLock rlock = redissonClient.getLock(lock.intern());

//        RLock lock1 = redisson.getLock(lock.intern());

        try {
    		// 加锁,此代码默认 设置key 超时时间30秒,过10秒,再延时
    		rlock.lock();

//            lock1.lock();
            try {
                TimeUnit.MINUTES.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            lock1.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();
//            lock1.unlock();
        }
        return null;
    }
}

调用:

import com.online.taxi.order.service.GrabService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

/**
 * @author wlw
 */
@RestController
@RequestMapping("/grab")
public class GrabOrderController {

    // 这里通过指定具体service的名字来注入,其对应的实现类上有注解说明:@Service("grabJvmLockService")
    @Autowired
    // 无锁
//    @Qualifier("grabNoLockService")
    // jvm锁
//    @Qualifier("grabJvmLockService")
    // mysql锁
//    @Qualifier("grabMysqlLockService")
    // 手写redis
//    @Qualifier("grabRedisLockService")
    //单个redisson
    @Qualifier("grabRedisRedissonService")
    // 红锁
//    @Qualifier("grabRedisRedissonRedLockLockService")
    private GrabService grabService;
    
    
    @GetMapping("/do/{orderId}")
    public String grab(@PathVariable("orderId") int orderId, int driverId){
        System.out.println("order:"+orderId+",driverId:"+driverId);
        grabService.grabOrder(orderId,driverId);
        return "";
    }
}

测试结果:两台机器,只有一个抢单成功

8004:
order:1, driverId:1
司机:1执行抢单逻辑
order:1, driverld:5
order:1, driverld:9
order:1, driverId:14
order:1, driverld:17
司机:1抢单成功
司机:9执行抢单逻辑
司机:9抢单失败
司机:17执行抢单逻辑
司机:17抢单失败
司机:5执行抢单逻辑
司机:5抢单失败
司机:14执行抢单逻辑
司机:14抢单失败

8005:
order:1, driverld:4
order:1, driverld:8
order:1, driverId:12
order:1, driverId:15
order:1, driverld:19
司机:4执行抢单逻辑
司机:4抢单失败
司机:12执行抢单逻辑
司机:12抢单失败
司机:15执行抢单逻辑
司机:15抢单失败
司机:19执行抢单逻辑
司机:19抢单失败
司机:8执行抢单逻辑
司机:8抢单失败

4.2.1、小结

加锁

SET orderId driverId NX PX 30000

上面的命令如果执行成功,则客户端成功获取到了锁,接下来就可以访问共享资源了;而如果上面的命令执行失败,则说明获取锁失败。

关键:1:同时设置过期时间,2:原子操作

释放锁

关键:判断是不是自己加的锁。另外可以开一个守护线程来延长锁的有效时间。

关注点

  1. orderId,是我们的key,要锁的目标。
  2. driverId是由我们的司机ID,它要保证在足够长的一段时间内在所有客户端的所有获取锁的请求中都是唯一的。即一个订单被一个司机抢。
  3. NX表示只有当orderId不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。
  4. PX 30000表示这个锁有一个30秒的自动过期时间。当然,这里30秒只是一个例子,客户端可以选择合适的过期时间。
  5. 这个锁必须要设置一个过期时间。 否则的话,当一个客户端获取锁成功之后,假如它崩溃了,或者由于发生了网络分区,导致它再也无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了。antirez在后面的分析中也特别强调了这一点,而且把这个过期时间称为锁的有效时间(lock validity time)。获得锁的客户端必须在这个时间之内完成对共享资源的访问。
  6. 此操作不能分割。
SETNX orderId driverId
EXPIRE orderId 30
虽然这两个命令和前面算法描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。造成死锁。
  1. 必须给key设置一个value。value保证每个线程不一样。如果value在每个线程间一样。会发生 误解锁的问题。
1.客户端1获取锁成功。
2.客户端1在某个操作上阻塞了很长时间。
3.过期时间到了,锁自动释放了。
4.客户端2获取到了对应同一个资源的锁。
5.客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。
之后,客户端2在访问共享资源的时候,就没有锁为它提供保护了。
  1. 释放锁的操作,得释放自己加的锁。
1.客户端1获取锁成功。
2.客户端1访问共享资源。
3.客户端1为了释放锁,先执行'GET'操作获取随机字符串的值。
4.客户端1判断随机字符串的值,与预期的值相等。
5.客户端1由于某个原因阻塞住了很长时间。
6.过期时间到了,锁自动释放了。
7.客户端2获取到了对应同一个资源的锁。
8.客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的锁。
  1. redis故障问题。
    如果redis故障了,所有客户端无法获取锁,服务变得不可用。为了提高可用性。我们给redis 配置主从。当master不可用时,系统切换到slave,由于Redis的主从复制(replication)是异步的,这可能导致丧失锁的安全性。
1.客户端1从Master获取了锁。
2.Master宕机了,存储锁的key还没有来得及同步到Slave上。
3.Slave升级为Master。
4.客户端2从新的Master获取到了对应同一个资源的锁。

客户端1和客户端2同时持有了同一个资源的锁。锁的安全性被打破。

  1. 这个算法中出现的锁的有效时间(lock validity time),设置成多少合适呢?如果设置太短的话,锁就有可能在客户端完成对于共享资源的访问之前过期,从而失去保护;如果设置太长的话,一旦某个持有锁的客户端释放锁失败,那么就会导致所有其它客户端都无法获取锁,从而长时间内无法正常工作。应该设置稍微短一些,如果线程持有锁,开启线程自动延长有效期。
  • 如果在过期时间内,程序没有执行完,是不能让key过期的,所以要延时。
  • 可以开一个守护线程来延长锁的有效时间。守护线程的执行时机:假如锁的有效期是10分钟,当过去了三分钟后,可以去执行守护线程,如果key还在,延时。不过一般不建议自己去写这个守护线程。
断点打在:rlock.lock();
执行完,之后,等着,去redis查看,看过期时间,是不是一直在变,答案:一直在变。到20时,自动加到30.

为了解决9.10问题。antirez设计了Redlock算法

Redis的作者antirez给出了一个更好的实现,称为Redlock,算是Redis官方对于实现分布式锁的指导规范。Redlock的算法描述就放在Redis的官网上:使用 Redis 的分布式锁

4.3、红锁RedLock(多master)

RedLock才真正解决了redis的问题。

redis配置类:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * redis配置类
 * @author wlw
 */
@Component
public class RedisConfig {

    @Autowired
    RedisSentinelProperties properties;
    //以下为redisson锁,哨兵
//    @Bean(name = "redisson")
//    @Order(1)
//    public Redisson getRedisson(){
//
//        Config config = new Config();
//        config.useSentinelServers()
//                .setMasterName(properties.getMasterName())
//                .addSentinelAddress(properties.getAddress())
//                .setDatabase(0);
//        return (Redisson) Redisson.create(config);
//    }
    //以上为redisson 哨兵锁

    //以下为红锁
    //三台独立的redis 
    @Bean(name = "redissonRed1")
    @Primary
    public RedissonClient redissonRed1(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed2")
    public RedissonClient redissonRed2(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed3")
    public RedissonClient redissonRed3(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0);
        return Redisson.create(config);
    }
    //以上为红锁

    
    // 单个redis
//    @Bean
//    @ConditionalOnMissingBean(StringRedisTemplate.class)
//    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
//    	StringRedisTemplate redisTemplate = new StringRedisTemplate();
//    	redisTemplate.setConnectionFactory(redisConnectionFactory);
//    	return redisTemplate;
//
//    }
    
    /**
     * 单个redisson
     * @return
     */
//    @Bean
//    public RedissonClient redissonClient() {
//    	Config config = new Config();
//    	config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
//
//    	return Redisson.create(config);
//    }

//    @Bean
//    public Redisson redisson(){
//        Config config = new Config();
//        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
//        return (Redisson) Redisson.create(config);
//    }
}

业务逻辑:

import com.online.taxi.order.constant.RedisKeyConstant;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;

import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 红锁
 */
@Service("grabRedisRedissonRedLockLockService")
public class GrabRedisRedissonRedLockLockServiceImpl implements GrabService {

    // 和配置类里的对应
    @Autowired
    @Qualifier("redissonRed1")
    private RedissonClient redissonRed1;
    @Autowired
    @Qualifier("redissonRed2")
    private RedissonClient redissonRed2;
    @Autowired
    @Qualifier("redissonRed3")
    private RedissonClient redissonRed3;
    
    @Autowired
	OrderService orderService;

    @Override
    public String grabOrder(int orderId , int driverId){
        //生成key
        String lockKey = (RedisKeyConstant.GRAB_LOCK_ORDER_KEY_PRE + orderId).intern();
        //redisson锁 哨兵
//        RLock rLock = redisson.getLock(lockKey);
//        rLock.lock();

        //redisson锁 单节点
//        RLock rLock = redissonRed1.getLock(lockKey);

        //红锁 RedissonRedLock
        RLock rLock1 = redissonRed1.getLock(lockKey);
        RLock rLock2 = redissonRed2.getLock(lockKey);
        RLock rLock3 = redissonRed3.getLock(lockKey);
        RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3);

        try {
            // 加锁, 此代码默认 设置key 超时时间30秒,过10秒,再延时
            rLock.lock();
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			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;
    }
}

调用:

@RestController
@RequestMapping("/grab")
public class GrabOrderController {

    // 红锁
    @Qualifier("grabRedisRedissonRedLockLockService")
    private GrabService grabService;
    
    @GetMapping("/do/{orderId}")
    public String grab(@PathVariable("orderId") int orderId, int driverId){
        System.out.println("order:"+orderId+",driverId:"+driverId);
        grabService.grabOrder(orderId,driverId);
        return "";
    }
}

结果:两台机器,只有一个抢单成功

8004:
order:1, driverId:3
order:1, driverld:9
order:1, driverId:1
司机:9执行抢单逻辑
order:1, driverId: 13
order:1, driverId: 17
司机:9抢单成功
司机:3执行抢单逻辑
司机:3抢单失败
司机:13执行抢单逻辑
司机:13抢单失败
司机:17执行抢单逻辑
司机:17抢单失败
司机:1执行抢单逻辑
司机:1抢单失败

8005:
order:1, driverId:7
order:1, driverld:5
order:1, driverId:11
order:1, driverId:15
order:1, driverId:19
司机:5执行抢单逻辑
司机:5抢单失败
司机:7执行抢单逻辑
司机:7抢单失败
司机:15执行抢单逻辑
司机:15抢单失败
司机:11执行抢单逻辑
司机:11抢单失败
司机:19执行抢单逻辑
司机:19抢单失败

debug

断点达到:rLock.lock()
执行完后,看结果,发现如果是3个redis节点,则有2个节点中 设置了值。

4.3.1、小结

加锁目的:对共享资源做互斥访问。

因此antirez提出了新的分布式锁的算法Redlock,它基于N个完全独立的Redis节点(通常情况下N可以设置成5)。

运行Redlock算法的客户端依次执行下面各个步骤,来完成 获取锁 的操作:

  1. 获取当前时间(毫秒数)。
  2. 按顺序依次向N个Redis节点执行 获取锁 的操作。这个获取操作跟前面基于单Redis节点的 获取锁 的过程相同,包含value: driverId ,也包含过期时间(比如 PX 30000 ,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个 获取锁 的操作还有一个超时时间(time out),它要远小于锁的有效时间(应该是几十毫秒量级),如果获取锁的操作时间超过了锁的有效时间,认为加锁失败。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。
  3. 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。
  4. 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。
  5. 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起 释放锁 的操作(即Lua脚本)。

当然,上面描述的只是 获取锁 的过程,而 释放锁 的过程比较简单:客户端向所有Redis节点发起 释放锁 的操作,不管这些节点当时在获取锁的时候成功与否。

问题:

由于N个Redis节点中的大多数能正常工作就能保证Redlock正常工作,因此理论上它的可用性更高。我们前面讨论的单Redis节点的分布式锁在failover的时候锁失效的问题,在Redlock中不存在了,但如果有节点发生崩溃重启,还是会对锁的安全性有影响的。具体的影响程度跟Redis对数据的持久化程度有关。

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

  1. 客户端1成功锁住了A, B, C, 获取锁 成功(但D和E没有锁住)。
  2. 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
  3. 节点C重启后,客户端2锁住了C, D, E, 获取锁 成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

在默认情况下,Redis的AOF持久化方式是每秒写一次磁盘(即执行fsync),因此最坏情况下可能丢失1秒的数据。为了尽可能不丢数据,Redis允许设置成每次修改数据都进行fsync,但这会降低性能。当然,即使执行了fsync也仍然有可能丢失数据(这取决于系统而不是Redis的实现)。所以,上面分析的由于节点重启引发的锁失效问题,总是有可能出现的。为了应对这一问题,antirez又提出了 延迟重启 (delayed restarts)的概念。也就是说,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

关于Redlock还有一点细节值得拿出来分析一下:在最后 释放锁 的时候,antirez在算法描述中特别强调,客户端应该向所有Redis节点发起 释放锁 的操作。也就是说,即使当时向某个节点获取锁没有成功,在释放锁的时候也不应该漏掉这个节点。这是为什么呢?设想这样一种情况,客户端发给某个Redis节点的 获取锁 的请求成功到达了该Redis节点,这个节点也成功执行了 SET操作,但是它返回给客户端的响应包却丢失了。这在客户端看来,获取锁的请求由于超时而失败了,但在Redis这边看来,加锁已经成功了。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些Redis节点同样发起请求。实际上,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。

与分布式锁有关的面试题:

秒杀。 并发读、并发写比较多的场景。例如多个司机抢一个订单。多个服务抢一个资源。

秒杀的前提:已有的交易系统功能要完善,稳定。

秒杀的特点:短时间内并发过高。

要注意的点:

  • 准:不多卖,不少卖(分布式锁控制库存)
  • 例如,100个库存,分布式锁就是对这100个库存加锁,服务来抢,要先获取锁,如果获取不到,阻塞,直到抢到。
  • 那怎么提高阻塞的效率?可以选择分段,100个分成10份,每次加锁的时候,选取其中一段加锁,效率提升10倍。
  • 快:不能因为服务的堆积,影响到性能。(服务响应快)
  • 稳:服务的可用性
  • 请求路径短(减少链路)
  • 依赖少,(少依赖别的服务,例如百度app抢红包,打开app之请求了10个接口,正常都是100多个)
  • 不要单点
  • 动静分离,一些资源可以采用静态资源,例如图片之类的,不需要去请求服务
  • 热点数据,提前识别热点数据的到来,提前扩容
  • 削峰,消息队列
  • 网络(cdn转发)、cpu(程序并发)、内存(缓存)、存储(mysql)

4.4、redis+lua脚本实现分布式锁

执行lua脚本的操作是原子的。

适用场景:不使用redisson框架,只使用简单的redis的api的操作。

脚本:

--- set key的脚本:
--- 获取key
local key = KEYS[1]
--- 获取value
local val = KEYS[2]
--- 获取一个参数
local expire = ARGV[1]
--- 如果redis找不到这个key就去插入
if redis.call("get", key) == false then
    --- 如果插入成功,就去设置过期值
    if redis.call("set", key, val) then
        --- 由于lua脚本接收到参数都会转为String,所以要转成数字类型才能比较
        if tonumber(expire) > 0 then
            --- 设置过期时间
            redis.call("expire", key, expire)
        end
        return true
    end
    return false
else
    return false
end
--- 删除key的脚本
if redis.call("get",KEYS[1])==ARGV[1] then
  return redis.call("del",KEYS[1])
else
  return 0
end

lua配置类:读取lua脚本

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

/**
 * lua配置类-读取lua脚本文件
 */
@Configuration
public class LuaConfiguration {
    @Bean(name = "set")
    public DefaultRedisScript<Boolean> redisScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/lock-set.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

    @Bean(name = "del")
    public DefaultRedisScript<Boolean> redisScriptDel() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/lock-del.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

控制层:通过调用对应的路径,可往redis里设值、删除值,对应的就是加锁与解锁

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

/**
 * lua的控制层
 */
@RestController
public class LuaLockController {
    /**
     * 这两个就是配置类(LuaConfiguration)配置的
     */
    @Resource(name = "set")
    private DefaultRedisScript<Boolean> redisScript;
    @Resource(name = "del")
    private DefaultRedisScript<Boolean> redisScriptDel;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加锁
     * @return
     */
    @GetMapping("/lua")
    public ResponseEntity lua() {
        // key value
        List<String> keys = Arrays.asList("testLua", "hello lua");
        // args参数是过期时间
        // 这三个参数对应 脚本的lock-set.lua中三个参数
        Boolean execute = stringRedisTemplate.execute(redisScript, keys, "100");
        return null;
    }

    /**
     * 解锁
     * @return
     */
    @GetMapping("/lua-del")
    public ResponseEntity luaDel() {
        List<String> keys = Arrays.asList("testLua");
        Boolean execute = stringRedisTemplate.execute(redisScriptDel, keys,"hello lua");
        return null;
    }
}

4.5、Spring Integration框架 + redis(自定义分布式锁注解)

Spring Integration提供了基于Spring的EIP(Enterprise Integration Patterns,企业集成模式的实现。Spring Integration主要解决的问题是不同系统之间交互的问题,通过异步消息驱动来达到系统交互时系统之间的松耦合。

引依赖:

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-integration</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

自定义注解与对应的切面类:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 分布式锁注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {

    String value() default "";

    int time() default 30;
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * 切面,分布式锁注解具体逻辑
 */
@Component
@Aspect
@Slf4j
public class LockAop {

    private WebApplicationContext webApplicationContext;

    public LockAop(WebApplicationContext webApplicationContext) {
        this.webApplicationContext = webApplicationContext;
    }

    @Pointcut("@annotation(com.online.taxi.order.annotation.DistributedLock)")
    private void apiAop(){

    }

    @Around("apiAop()")
    public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
        // 获取使用了这个注解的方法与对应参数
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Object[] args = point.getArgs();
        System.out.println("args[0]:"+args[0]);
        DistributedLock lockDistributed = method.getAnnotation(DistributedLock.class);


        RedisLockRegistry redisLockRegistry = (RedisLockRegistry) webApplicationContext.getBean(lockDistributed.value());
        // 加锁
        Lock lock = redisLockRegistry.obtain(signature.getName()+"_"+args[0]);
        boolean b = false;
        for (int i =0 ; i<3;i++){
            b = lock.tryLock(lockDistributed.time(), TimeUnit.SECONDS);
            if(b){
                break;
            }else {
                continue;
            }
        }
        log.info("获取锁:"+b);
        Object proceed = null;
        try{
            // 执行方法
            proceed = point.proceed();
        }catch (Exception e){
            throw e;
        }finally {
            try{
                // 解锁
                lock.unlock();
            }catch (Exception e){
//                log.error(e.getMessage(),e);
                System.out.println("没加过锁");
            }
        }

        return proceed;
    }
}

配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;

@Configuration
public class RedisLockConfiguration {
  @Bean
  public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
    return new RedisLockRegistry(redisConnectionFactory, "order_lock");
  }
}

使用刚刚的注解(司机抢单业务逻辑):

import com.online.taxi.order.annotation.DistributedLock;
import com.online.taxi.order.annotation.DistributedLock;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author wlw
 * 使用分布式锁注解来实现抢单
 */
@Service("cloudService")
public class CloudServiceImpl implements GrabService {

    @Autowired
    OrderService orderService;

    @Override
    @DistributedLock(value = "redisLockRegistry", time = 10)
    public String grabOrder(int orderId, int driverId) {

        System.out.println("司机:" + driverId + " 执行抢单逻辑");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 修改订单状态
        boolean b = orderService.grab(orderId, driverId);
        if (b) {
            System.out.println("司机:" + driverId + " 抢单成功");
        } else {
            System.out.println("司机:" + driverId + " 抢单失败");
        }

        return null;
    }
}

调用:

import com.online.taxi.order.service.GrabService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

/** 抢单服务
 * @author :wlw
 */
@RestController
@RequestMapping("/grab-driver")
public class GrabOrderController {

    @Autowired
    // 分布式锁注解
    @Qualifier("cloudService")

    private GrabService grabService;

    @GetMapping("/do/{orderId}")
    public String grabMysql(@PathVariable("orderId") int orderId, int driverId){
        System.out.println("order:"+orderId+",driverId:"+driverId);
        grabService.grabOrder(orderId,driverId);
        return "";
    }
}

5.基于zookeeper

可参考:ZooKeeper 分布式锁案例