分布式锁-快速实战

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题就存在问题,解决方案分布式锁。

下面介绍两种首先分布式锁的方案:

  1. 基于Spring Integration实现分布式锁
  2. 基于redisson实现分布式锁

优缺点:第一种引入简单,使用方便,但只支持重入锁。第二种较第一种麻烦一点点,但支持重入锁、公平锁、读锁、写锁等多种类型。

第一种方案:提供的全局锁目前为以下存储提供了实现
Gemfire
JDBC
Redis
Zookeeper
因为第二种方案基于redis存储,为了方便该方案讲解选择redis作为存储。同时项目都采用springboot方式。

  1. 首先创建springboot项目,添加基本依赖及redis、integration-redis依赖。如果选择其他存储方式,添加对应的integration依赖即可,无需更改业务,非常方便。
  <!--integration-->
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-redis</artifactId>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.9.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
  1. 添加yml配置
spring:
  http:
    encoding:
      charset: UTF-8
      force: true
      enabled: true
  server:
    port: 8080
  mvc:
    static-path-pattern: /**
    resources:
      static-locations: classpath:/static/
  redis:
    database: 1
    host: xx.xx.xx.xx
    port: 6379
    password: xxx
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
    redisson:
      address: "xxxx"
      password: xxx
  1. 创建config配置,RedisLockRegistry的第三个参数设置为上锁以后xxx秒自动解锁的时间,默认60s。
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;

/**
 * 描述:spring-integration-redis锁配置
 *
 * @Auther: gc.x
 * @Date: 2019/10/28
 */
@Configuration
public class RedisLockConfiguration {

    @Bean
    public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory){
        return new RedisLockRegistry(redisConnectionFactory,"spring-integration-redis",60000L);
    }

}

  1. 选择基于注解的形式加锁,先自定义锁注解,然后创建对应的切面

@interface


/**
 * 描述: 基于spring-integration-redis实现分布式锁
 * 只支持重入锁ReentrantLock
 * @Auther: gc.x
 * @Ddate:2019/10/28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockDistributed {

    /**
     * 锁的名称
     * @return
     */
    String name() default "";

    /**
     * 尝试加锁 最多等待时间
     * @return
     */
    int time() default 30;
    /**
     * 自定义业务key
     * @return
     */
    String [] keys() default {};
    /**
     * 上锁以后xxx秒自动解锁
     *
     */
    /**
     *上锁以后xxx秒自动解锁
     *  long leaseTime();
     *  在RedisLockConfiguration里面配置,如果要使用该参数,需要在切面里面new RedisLockRegistry
     * @return
     */
}

Aspect
主要就是拦截自定义的注解,获取数据生成lockname,及竞争锁时间,
redisLockRegistry.obtain方法获取锁,
lock.tryLock方法竞争锁
由于篇幅省略了部分私有方法,不用纠结,结尾会提供完整源码
—getKeyName是获取自定义的作用于参数上的一个注解,可以将传入的参数作为 lockname的一部分,增强锁的力度。
—getName获取完整路径的方法名。

/**
 * 描述:添加了 LzxLockDistributed 注解 的Aop
 *
 * @Auther: gc
 * @Date: 2019/6/18 10:56
 */
@Component
@Aspect
@Slf4j
public class LockAspect {
    /**
     *  锁前缀名
     */
    public static final String LOCK_NAME_PREFIX = "lock";
    /**
     *  分隔符
     */
    public static final String LOCK_NAME_SEPARATOR = ".";

    /**
     * 表达式解析器
     */
    private ExpressionParser parser = new SpelExpressionParser();

    /**
     * 参数解析
     */
    private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    private RedisLockRegistry redisLockRegistry;
  /*  @Autowired
    private RedisConnectionFactory redisConnectionFactory;*/

    @Around(value = "@annotation(lockDistributed)")
    public Object aroundApi(ProceedingJoinPoint point, LockDistributed lockDistributed) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();

        String businessKeyName = getKeyName(point,lockDistributed);
        String lockName = LOCK_NAME_PREFIX+LOCK_NAME_SEPARATOR + getName(lockDistributed.name(), signature) + businessKeyName;
        log.info("lockName===>"+lockName);

       // RedisLockRegistry redisLockRegistry=new RedisLockRegistry(redisConnectionFactory,"spring-integration-redis",lockDistributed.leaseTime());

        Lock lock = redisLockRegistry.obtain(lockName);
        boolean currentThreadLock = false;
        Object proceed = null;
        try{
            currentThreadLock = lock.tryLock(lockDistributed.time(), TimeUnit.SECONDS);
            log.info("获取锁====="+currentThreadLock);
            if (!currentThreadLock) {
                throw new TimeoutException("获取锁资源等待超时");
            }
            proceed = point.proceed();
        }catch (Exception e){
            throw e;
        }finally {
            if(currentThreadLock){
                lock.unlock();
            }

        }


        return proceed;

    }
}
  1. 测试锁

@RestController
public class DistributedLock {

    @LockDistributed
    @RequestMapping("/redisLockTest")
    public void redisLockTest() {
        System.out.println("获取锁,执行");
        try {
            Thread.sleep(20*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束-------------------->>>>>>>>>>>>>>");
    }
}

第二种方案:提供多种锁实现

  1. 同理导入依赖
  2. 配置yml
  3. 创建config

@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redssionProperties;

    /**
     * 单体的
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "redisson.address")
    public RedissonClient redissonSingle(){
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer().setAddress(redssionProperties.getAddress())
                .setTimeout(redssionProperties.getTimeout())
                .setDatabase(redssionProperties.getDatabase())
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
        if(StringUtils.isNotEmpty(redssionProperties.getPassword())){
            serverConfig.setPassword(redssionProperties.getPassword());
        }
        return Redisson.create(config);
    }

    /**
     * 集群的
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "redisson.masterAddresses")
    public RedissonClient redissonSentinel(){
        Config config = new Config();
        ClusterServersConfig serverConfig = config.useClusterServers().addNodeAddress(redssionProperties.getSentinelAddresses())
                .setTimeout(redssionProperties.getTimeout())
                //设置集群扫描时间
                .setScanInterval(redssionProperties.getScanInterval())
                //主节点线程池数量
                .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
                //从节点线程池数量
                .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());

        if(StringUtils.isNotEmpty(redssionProperties.getPassword())){
            serverConfig.setPassword(redssionProperties.getPassword());
        }
        return Redisson.create(config);
    }
  1. 自定义注解,属性多了锁类型

/**
 * 描述: 基于redisson实现分布式锁
 * 支持多种锁类型
 * @Auther: gc.x
 * @Ddate:2019/10/28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockDistributed2 {

    /**
     * 锁的名称
     * @return
     */
    String name() default "";
    /**
     * 锁类型,默认可重入锁
     * @return
     */
    LockType lockType() default LockType.Reentrant;
    /**
     * 尝试加锁,最多等待时间
     * @return
     */
    long waitTime() default 60;
    /**
     *上锁以后xxx秒自动解锁
     * @return
     */
    long leaseTime() default 60*5;

    /**
     * 自定义业务key
     * @return
     */
    String [] keys() default {};
}

  1. 切面,和方案一类似,主要根据注解上的锁类型获取对应的redisson提供的锁。
    该实现内容较多不贴出来了,结构如下
    在这里插入图片描述
/**
 * @Auther: gc.x
 * @Date: 2019/10/28
 * @Description:
 */
@Aspect
@Component
@Slf4j
public class LockAspect2 {
    /**
     *  锁前缀名
     */
    public static final String LOCK_NAME_PREFIX = "lock";
    /**
     *  分隔符
     */
    public static final String LOCK_NAME_SEPARATOR = ".";

    /**
     * 表达式解析器
     */
    private ExpressionParser parser = new SpelExpressionParser();

    /**
     * 参数解析
     */
    private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    private LockFactory lockFactory;

    @Around(value = "@annotation(lockDistributed2)")
    public Object around(ProceedingJoinPoint joinPoint, LockDistributed2 lockDistributed2) throws Throwable{
        // 获取方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取锁类型
        LockType type= lockDistributed2.lockType();
        String businessKeyName = getKeyName(joinPoint,lockDistributed2);
        String lockName = LOCK_NAME_PREFIX+LOCK_NAME_SEPARATOR + getName(lockDistributed2.name(), signature) + businessKeyName;
        log.info("lockName===>"+lockName);
        long waitTime = lockDistributed2.waitTime();
        long leaseTime =lockDistributed2.leaseTime();
        LockInfo lockInfo = new LockInfo(type,lockName,waitTime,leaseTime);

        ILock ilock = lockFactory.getLock(lockInfo);
        boolean currentThreadLock = false;
        try {
            currentThreadLock = ilock.acquire();
            if (!currentThreadLock) {
                throw new TimeoutException("获取锁资源等待超时");
            }
            return joinPoint.proceed();
        } finally {
            if (currentThreadLock) {
                ilock.release();
            }
        }
    }

  1. 同理测试,结束

附上码云:https://gitee.com/giteeClass/project
欢迎fork

发布了4 篇原创文章 · 获赞 2 · 访问量 1168

猜你喜欢

转载自blog.csdn.net/qq_41460198/article/details/102798454
今日推荐