• 从零开发短视频电商 分布式锁-基于数据库实现


    方式一 for update悲观锁方式

    • select * from xxx for update 悲观锁方式。

      • 首先要开启事务
      • 在for update语句中where条件字段上创建索引,因为是通过索引来加锁的,否则会锁整个表。
      • 这个在微服务中,使用不多,因为事务中很可能嵌套了第三方调用,不符合规范。

      缺点:会导致数据库锁占用很长时间,同时会长时间占用数据库连接。

    方式二 单独lock表乐观锁方式

    锁示例

    @Component
    public class Example {
        @Autowired
        private Lock lock;
    
        public void test() {
            // 尝试获取锁
            LLock llock = lock.acquire(key, Duration.ofSeconds(10));
            if (Objects.isNull(llock)) {
                throw new BusinessException("其他人正在处理中,请稍后重试");
            }
            try {
                // 业务代码
            } finally {
                // 解除锁
                lock.release(llock);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    数据表结构

    CREATE TABLE `distribute_lock` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
      `lock_key` varchar(50) NOT NULL COMMENT '锁记录key',
      `token` varchar(50) NOT NULL COMMENT '锁的token,防止误删其他人的锁',
      `thread_id` varchar(50) NOT NULL COMMENT '获取锁的线程id',
      `expire` bigint(20) NOT NULL COMMENT '锁的失效时间,时间戳',
      PRIMARY KEY (`id`),
      UNIQUE KEY `distribute_lock_UN` (`lock_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    tips:表名切勿使用lock,使用关键字语法会报错。在oracle里,resource也是关键字,mysql里不是。

    整体

    后边会再实现个redis版本的。

    public interface Lock {
        /**
         * 尝试获取锁,
         *
         * @param key        锁定的key
         * @param expiration 锁定的时间
         * @return 锁定失败返回null,成功返回LLock锁对象
         */
        LLock acquire(String key, Duration expiration);
        /**
         * 释放锁
         *
         * @param lock   锁对象
         * @return 是否成功
         */
        boolean release(LLock lock);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    加锁

    String token = fastSimpleUUID();
    int row = INSERT ignore INTO distribute_lock (lock_key, token, expire, thread_id) VALUES (?, ?, ?, ?);
    if (row == 0) {
        return null;
    }
    // 续约线程
    ScheduledFuture scheduledFuture = scheduleLockRefresh(refresh)
    return new LLock(key,token,scheduledFuture);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    解锁

    客户端设置的锁,必须由自己解开。即会check key和token。

    cancelSchedule(lock.getScheduledFuture());
    DELETE FROM distribute_lock WHERE lock_key = ? AND token = ?;
    
    • 1
    • 2

    续租

    当客户端发现在锁的租期内无法完成操作时,就需要延长锁的持有时间,进行续租(renew)。同解锁一样,客户端应该只能续租自己持有的锁。续租需要每个锁后台定时任务时间建议是锁的1/3去续租。

    UPDATE distribute_lock SET expire = ? WHERE lock_key = ? AND token = ?;
    
    • 1

    整体代码在:https://gitee.com/lakernote/easy-admin/tree/master/src/main/java/com/laker/admin/framework/lock

    总结

    优点

    • 实现简单,没有redis的集群和主从问题。

    缺点

    • 性能方面来说没有redis好。

    参考:

    • https://github.com/alturkovic/distributed-lock
  • 相关阅读:
    java毕业设计LIS检验系统2021mybatis+源码+调试部署+系统+数据库+lw
    基于SSM的疫苗预约接种平台
    数据结构前瞻
    总结List三种实现类
    前端页面的性能测试
    acwing算法基础之基础算法--高精度乘法算法
    浅谈Vue中render函数
    JVM学习-JMM
    修改Linux系统的主机名
    电子产品量产工具-软件架构-显示系统
  • 原文地址:https://blog.csdn.net/abu935009066/article/details/127649438