# 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线程组,三个线程组一组测试