# lock **Repository Path**: git_jintian/lock ## Basic Information - **Project Name**: lock - **Description**: 分布式锁 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: http://git.oschina.net/jannal/lock - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-01-08 - **Last Updated**: 2025-01-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 分布式锁 ## 声明 根据https://github.com/adamswanglin/wllock 的思想以及部分代码改造并完善 ## 业务场景 1、防止在分布式环境下数据重复插入的问题,解决思路串行化或者是原子操作 2、秒杀防止超卖 ``` 将以下操作串行化或者作为原子操作 if(查询数据是否存在){ insert 数据 }else{ update 数据 } ``` ## 使用讲解 ### 0、引用pom ```xml cn.jannal.study lock-common 0.0.1-SNAPSHOT ``` 如果使用redis实现分布式锁则还需要引用 ```xml cn.jannal.study lock-distributed-redis 0.0.1-SNAPSHOT ``` 如果使用mysql实现分布式锁则还需要引用 ```xml cn.jannal.study lock-distributed-mysql 0.0.1-SNAPSHOT ``` 并且在数据库中执行如下语句 ```sql DROP TABLE IF EXISTS `global_lock`; CREATE TABLE `global_lock` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `type` varchar(255) COLLATE utf8_bin NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `type_unique` (`type`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; ``` 如果使用zookeeper ```xml cn.jannal.study lock-distributed-zookeeper 0.0.1-SNAPSHOT ``` 如果同时使用redis zookeeper、mysql,则引用以上三个模块的pom ### 1、在业务方法上加入注解@Lock | 注解字段 | 字段描述 | | ------------ | ------------ | | name | 锁的业务名称 | | key | 当前锁的key,目前支持spel表达式 | | localLockMaxWaitTime | 获取本地锁的最大等待时间,默认是0,表示不限制| | distributeLockMaxWaitTime | 获取分布式锁的最大等待时间,默认是0,表示不限制 | | tryTimes | 重试次数,默认是0,表示不重试| |disstributionLockType | 分布式锁实现方式,默认是redis| ### 2、配置 #### 1. 通用配置 ```java #本地锁的等待的最大数 lock.singleWaitThreshold=1000 #系统名称 lock.systemName=test ``` ```java applicationContext-aop-lock.xml AOP配置,设置order设置比较小,也就是此aop在其他aop前执行 ``` ```java 扫描一下包路径 basic.arch.component.lock ``` #### 2、使用redis redis配置,参见lock-distributed-test项目下spring-redis 目录 applicationContext-redis.xml ```java #如果使用redis做分布式锁,则需要执行时间,这是key的过期时间 lock.redis.expire=60000 #key的最大过期时间 lock.redis.expire=60000 #扫描未释放的键的间隔 lock.redis.scan.interval.time=5 ######redis####### redis.host = 192.168.6.102 redis.port = 6379 redis.database = 6 redis.pwd = 123qwe123 redis.timeout = 15000 redis.pool.maxTotal=1024 redis.pool.maxIdle=200 redis.pool.maxWaitMillis=1000 redis.pool.testOnBorrow=true redis.pool.testOnReturn=true #哨兵的配置 redis.master.host=192.168.6.102 redis.master.port=26379 redis.slave1.host=192.168.6.103 redis.slave1.port=26379 redis.slave2.host=192.168.6.104 redis.slave2.port=26379 ``` ```java ``` #### 3、使用mysql 如果使用mysql作为分布式锁,参见lock-distributed-test项目下spring-mysql 目录 ```java #锁持有的最大时间,在未正确删除时宕机,此时要定时去扫描key,防止死锁 lock.mysql.releaselock.maxtime=60 #定时扫描数据库,查询是否有未清理的key,秒为单位 lock.mysql.scan.interval.time=20 ``` #### 4、使用zookeeper 参见lock-distributed-test项目下spring-zookeeper 目录 ``````properties lock.zk.host = 192.168.6.25:2181,192.168.6.25:2182,192.168.6.25:2183 #session超时时间 lock.zk.sessionTimeOut=10000 #连接超时时间 lock.zk.connectionTimeoutMs=1000 #重试次数 lock.zk.retryNTimes=1 lock.zk.sleepMsBetweenRetries=10000 #获取锁的最大等待时间 lock.zk.expire=20000 ``` #### 5、同时使用mysql、redis、zookeeper 参见lock-distributed-test项目下spring-mysql-redis-zookeeper 目录 运行ApplicationMysql_Redis_Zookeeper 启动项目 ### 3、举例 ```java @Lock(name="productStock",key="#productId",distributeLockMaxWaitTime=5000) public void saveOrUpdate(Long productId, Long productNum) { ProductStock productStock = productStockMapper.findByProductId(productId); if (productStock == null) { productStock = new ProductStock(); productStock.setProductId(productId); productStock.setProductNum(productNum); productStock.setVersion(1L); int flag = productStockMapper.insert(productStock); if (flag != 1) { throw new BusinessException("插入失败"); } } else { int flag = productStockMapper.updateProductNumById(productNum, productStock.getId()); if (flag != 1) { throw new BusinessException("更新失败"); } } } ``` ```java key可以通过方法的参数名来进行指定,例如 1、public void saveOrUpdate(Long productId, Long productNum) 如果key="#productId" 2、public void saveOrUpdate(ProductStock productStock) key="#productStock.id" 3、也可以自己使用字符串进行拼接 key="'aa-bb-'+#productStock.id" 最终的锁的key是 lock.systemName-lock注解的name-lock的key,此key必须是唯一的。 系统名称+lock的业务名称+lock的自定义key ``` ### 4、测试 0. lock-distributed-test 是测试项目,可以直接运行 1. 测试mysql分布式锁,则运行ApplicationMysql 类 2. 测试redis分布式锁,则运行ApplicationRedis类 3. 测试sql ```sql DROP TABLE IF EXISTS `product_stock`; CREATE TABLE `product_stock` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_id` bigint(20) NOT NULL, `product_num` bigint(19) NOT NULL DEFAULT '0', `version` bigint(19) NOT NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='商品库存表'; INSERT INTO `product_stock` VALUES ('1', '12', '9', '1'), ('2', '13', '10', '1'); ``` 5. 运行启动类,并增加-Dserver.port=8888 、-Dserver.port=9999 -Dserver.port=8080 分别启动三个实例 6. 使用jmeter导入测试用例(lock-distributed-test/test-jmeter目录下),jmeter版本2.13 7. 修改CSV Data Set Config 的filename路径(测试数据在lock-distributed-test/test-jmeter/lock下) 8. 开启jmeter线程组,三个线程组一组测试