多种方式实现分布式锁

257 阅读4分钟

1.zookeeper方式实现分布式锁

  • 方案一:Curator框架来实现ZooKeeper分布式锁

注意: 下面教程为 模拟高并发情况下生成订单,springboot+mybatis+通用mapper+zookeeper下单并入库

项目github地址 的zhou-cloud-springboot/ zhou-springboot-lock 模块

(1)Curator相关依赖包

        <!--zookeeper分布式锁-->
        <!--注意 zookeeper版本问题,zookeeper为3.4.6-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>

(2)Curator配置类

@Configuration
@ConfigurationProperties(prefix = "curator")
@Data
public class CuratorConfig {
    private int retryCount;
    private int elapsedTimeMs;
    private String connectString;
    private int sessionTimeoutMs;
    private int connectionTimeoutMs;

    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(elapsedTimeMs, retryCount);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(sessionTimeoutMs)
                .retryPolicy(retryPolicy)
                .build();
        return curatorFramework;
    }
}

(3)对应springboot配置文件applciation.yml

#zookeeper分布式锁curator配置
curator:
  connectionTimeoutMs: 5000  # 连接超时时间
  elapsedTimeMs: 5000   #重试间隔时间
  retryCount: 3   #重試次數
  sessionTimeoutMs: 60000  # session超时时间
  connectString: 192.168.91.139:2181,192.168.91.140:2181,192.168.91.140:2181   # zookeeper 地址 如果是单机,只写一台

(4)具体生成订单的实现类

@Slf4j
@Service
public class CuratorDisLockOrderServiceImpl implements OrderService {

    //订单号生成类,时间戳+计数
    private static OrderNumGenerator codeGenerator = new OrderNumGenerator();

    private static String LOCK_PATH = "/distribute-lock";


    @Autowired
    private CuratorFramework curatorFramework;

    @Autowired
    private OrderDao orderDao;

    @Override
    public String createOrder() {
        String orderCode = "";
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, LOCK_PATH);
        try {
            lock.acquire();
            //生成订单编号
            orderCode = codeGenerator.getOrderNumber();
            log.info(Thread.currentThread().getName() + "-->获取锁成功-->生成订单编号:{}", orderCode);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                lock.release();
                log.info(Thread.currentThread().getName() + "-->释放锁成功。");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return orderCode;
    }
}

(4)访问测试controller

@RestController
@Slf4j
@RequestMapping("/zookeeper")
public class OrderController {
    @Autowired
    private OrderService orderService;

    /**
     * 模拟高并发场景,多线程,并发下单
     * (模拟多个用户同时访问)
     *
     * @return
     */
    @RequestMapping("/orderThread")
    public String createOrdertTest() {
        //并发线程数
        int count = 20;
        //循环屏障
        CyclicBarrier cb = new CyclicBarrier(count);
        //模拟高并发场景,多线程,创建订单
        for (int i = 0; i < count; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    log.info(Thread.currentThread().getName() + "--我已经准备好了");
                    try {
                        //等待所有线程启动准备好,才一起往下执行
                        cb.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    //创建订单
                    orderService.createOrder();
                }
            }).start();
        }
        return "ok";
    }
}

这样就可以实现订单号的唯一生成,运用Curator是不是很简单,具体实现 项目github地址 的zhou-cloud-springboot/ zhou-springboot-lock 模块

  • 方案二:原生方案来实现ZooKeeper分布式锁

持续更新中。。。。

2.redis方式实现分布式锁

  • 方案一:Redisson来实现redis分布式锁

    Redisson分布式锁的实现原理

    (1)尝试获取锁和释放锁

      ```
      /**
       * 尝试获取锁
       *
       * @param lockKey
       * @param waitTime  最多等待时间
       * @param leaseTime 上锁后自动释放锁时间
       * @return
       */
      public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
          RLock lock = redissonClient.getLock(lockKey);
          try {
              return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
          } catch (InterruptedException e) {
              return false;
          }
      }
      ```
    
        /**
         * 释放锁
         *
         * @param lockKey
         */
        public static void unlock(String lockKey) {
            RLock lock = redissonClient.getLock(lockKey);
            lock.unlock();
        }
    

    (2)模拟高并发下用户下单

    @Slf4j
    @Service
    public class RedisOrderServiceImpl implements RedisOrderService {
    
        private static OrderNumGenerator codeGenerator = new OrderNumGenerator();
    
        @Autowired
        private OrderDao orderDao;
    
        @Override
        public String createOrder() {
            try {
                //最多等待3秒,20秒后自动解锁
                RedissonUtil.tryLock(RedisConstant.OrderLockConstant.ORDER_LOCK_KEY
                        , TimeUnit.SECONDS, 3, 20);
    
                String orderNumber = codeGenerator.getOrderNumber();
                //重要的是订单号,最好异步去生成订单详情 mq发消息 ,
                Order order = new Order();
                order.setId(orderNumber);
                order.setPrice(new BigDecimal(19));
                order.setCreateTime(new Date());
                orderDao.insert(order);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                RedissonUtil.unlock(RedisConstant.OrderLockConstant.ORDER_LOCK_KEY);
            }
            return "ok";
        }
    }
    

    (3)测试controller ,多线程模拟用户下单

    @RestController
    @Slf4j
    @RequestMapping("/redis")
    public class RedisOrderController {
    
    
        @Autowired
        private RedisOrderService orderService;
    
        /**
         * 模拟高并发场景,多线程,并发下单
         *
         * @return
         */
        @RequestMapping("/orderThread")
        public String createOrdertTest() {
            //并发线程数
            int count = 20;
            //循环屏障
            CyclicBarrier cb = new CyclicBarrier(count);
            //模拟高并发场景,多线程,创建订单
            for (int i = 0; i < count; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        log.info(Thread.currentThread().getName() + "--我已经准备好了");
                        try {
                            //等待所有线程启动准备好,才一起往下执行
                            cb.await();
                        } catch (InterruptedException | BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        //创建订单
                        orderService.createOrder();
                    }
                }).start();
            }
            return "ok";
        }
    }
    

    经测试,发现20个订单号唯一

项目github地址 的zhou-cloud-springboot/ zhou-springboot-lock 模块

  • 方案二:setnx 方法来实现redis分布式锁 持续更新中。。。。

3.数据库方式实现分布式锁

持续更新中。。。。

总结

  • 数据库锁

优点:直接使用数据库,操作简单。

缺点:分布式系统大多数瓶颈都在数据库,使用数据库锁会增加数据库负担。

  • 缓存锁

优点:性能高,实现起来比较方便,在允许偶发的锁失效情况,不影响系统正常使用,建议使用缓存锁。

缺点:通过锁超时机制不是十分可靠,当线程获取锁之后,处理时间过长会导致锁超时,这样就失去了锁的作用。

  • Zookeeper锁

优点:不依靠超时时间来释放锁,可靠性高,系统要求可靠性高,建议采用zookeeper锁。

缺点:性能比不上缓存锁,因为要频繁的创建节点和删除节点。