Skip to content

Commit

Permalink
Merge pull request #4 from jsrdxzw/v1.1.0
Browse files Browse the repository at this point in the history
add test result and fix some bugs
  • Loading branch information
jsrdxzw authored Mar 27, 2021
2 parents 20c2aeb + 06e2648 commit 4356171
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 42 deletions.
92 changes: 72 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,34 @@ single redis machine.

![distribute_lock](images/distribute-lock.jpg)

### performance test report
we use our redis kit to compare with their performance in concurrent environment.

1000qps * 10 count

| machine | redisson | redis-kit | redis-kit (preload mode)
| ---- | ---- | ---- | ---- |
| single instance (lock) | 5286ms | 5394ms | 5184ms |
| two instances (lock) | 5854ms | 6620ms | 6184ms |
| single instance (tryLock) | 1271ms | 738ms | 720ms |
| two instances (tryLock) | 2230ms | 1714ms | 1620ms |

*In conclusion, redis-kit is almost as fast as redisson when using lock, but
when using tryLock the redis-kit is faster about 40% than redisson.*


### Import Redis Kit in your project

```xml

<dependency>
<groupId>com.github.jsrdxzw</groupId>
<artifactId>redis-kit-spring-boot-starter</artifactId>
<version>1.0.5</version>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```

Expand All @@ -35,14 +53,16 @@ by default `StringRedisTemplate` which is provided by Spring is used, Of course
yourself.

```java

@Configuration
public class DistributedLockConfiguration{
public class DistributedLockConfiguration {
@Bean
public RedisLockFactory redisLockFactory(StringRedisTemplate redisTemplate){
public RedisLockFactory redisLockFactory(StringRedisTemplate redisTemplate) {
return new DefaultRedisLockFactory(redisTemplate);
}
}
```

use lock in your own business logic code

```java
Expand All @@ -65,8 +85,9 @@ public class UserService {
}
}
```
In the other way, annotations such as `@DistributedLock`, `DistributedTryLock` are also provided, please import the spring aop at the first place
before using annotations.

In the other way, annotations such as `@DistributedLock`, `DistributedTryLock` are also provided, please import the
spring aop at the first place before using annotations.

```xml

Expand Down Expand Up @@ -95,7 +116,34 @@ public class Application {
// @DistributedLock(lockKey = "your key")
@DistributedTryLock(lockKey = "your key", waitTime = 10)
public void method(){
//...
//...
}
```

### Notice
we don't recommend use redis lock with @Transactional because it may cause visibility problems.
```java
@Transactional
public void reduceStock(Long id) {
RedisLock lock = redisLockFactory.getLock("test2");
try {
lock.lock();
SkuStock skuStock = skuStockRepository.getOne(id);
Integer stock = skuStock.getStock();
if (stock > 0) {
log.info("stock is {}", stock);
skuStock.setStock(stock - 1);
skuStockRepository.save(skuStock);
}
longAdder.increment();
log.info("这是第{}个请求, 改之前的stock:{}", longAdder.longValue(), stock);
} finally {
// when lock is released by one client, the other client will
// get redis lock immediately when Transaction may not commit.
// The other client will get old value by using Mysql.
lock.unlock();
}

}
```

Expand All @@ -120,40 +168,44 @@ cache. by default the expired time is `5 minutes`.
public Student methodName(){
}
```
it will remove redis value based on cache principle -- [Cache aside](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)

it is recommended to use @Transactional annotation when modifying
cache values
it will remove redis value based on cache principle
-- [Cache aside](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)

it is recommended to use @Transactional annotation when modifying cache values

```java
@Transactional(rollbackFor = Throwable.class)
@Put(key="xzw")
public Student methodName() {
}
@Put(key = "xzw")
public Student methodName(){
}
```

`@Delete` is same as `@Put`

```java
@Transactional(rollbackFor = Throwable.class)
@Delete(key="xzw")
public void methodName() {
}
@Delete(key = "xzw")
public void methodName(){
}
```

it will delete value from redis

### rate limiter

```java
import org.springframework.beans.factory.annotation.Autowired;
@Autowired
private RateLimit rateLimit;
boolean require = rateLimit.acquire("xzw", 5, 10);
boolean require=rateLimit.acquire("xzw",5,10);
```

it means we allow 5 requests per seconds, and when the request of per second is greater than 5, it will return false

we have provided two algorithms -- token bucket and rolling window.
token bucket algorithm is used by default
we have provided two algorithms -- token bucket and rolling window. token bucket algorithm is used by default

```yaml
# you can change limit algorithm by overriding spring yaml file
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.github.jsrdxzw</groupId>
<artifactId>redis-kit-spring-boot-starter</artifactId>
<version>1.0.5</version>
<version>1.1.0</version>
<description>Redis Kits for distributed environment</description>
<url>https://github.com/jsrdxzw/redis-kit</url>

Expand Down
24 changes: 16 additions & 8 deletions src/main/java/com/jsrdxzw/redis/lock/AbstractRedisLock.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.UUID;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

Expand All @@ -11,17 +11,17 @@
*/
public abstract class AbstractRedisLock implements RedisLock {

private final ReentrantLock lock;
protected final ReentrantLock lock;
/**
* each machine has own clientId which is in order to avoid to release other client's lock
*/
protected final String clientId;
protected final StringRedisTemplate stringRedisTemplate;
protected final String lockKey;

public AbstractRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey) {
public AbstractRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, String clientId) {
this.lock = new ReentrantLock();
this.clientId = UUID.randomUUID().toString();
this.clientId = clientId;
this.stringRedisTemplate = stringRedisTemplate;
this.lockKey = lockKey;
}
Expand Down Expand Up @@ -76,15 +76,23 @@ public boolean tryLock(long time, TimeUnit timeUnit, int retry) {

@Override
public void unlock() {
if (lock.isHeldByCurrentThread()) {
if (!lock.isHeldByCurrentThread()) {
throw new IllegalStateException("You do not own lock at " + this.lockKey);
}
if (lock.getHoldCount() > 1) {
lock.unlock();
return;
}
try {
removeLockFromRedis();
} finally {
lock.unlock();
}
removeLockFromRedis();
}

private boolean checkReentrantLock(String lockKey) {
String clientId = stringRedisTemplate.opsForValue().get(lockKey);
return clientId != null && !clientId.isEmpty();
String id = stringRedisTemplate.opsForValue().get(lockKey);
return Objects.equals(clientId, id);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/jsrdxzw/redis/lock/DefaultRedisLock.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public final class DefaultRedisLock extends AbstractRedisLock {
" redis.call('DEL', KEYS[1])\n" +
"end";

public DefaultRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit) {
super(stringRedisTemplate, lockKey);
public DefaultRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit, String clientId) {
super(stringRedisTemplate, lockKey, clientId);
this.expireTime = expireTime;
this.expireTimeUnit = expireTimeUnit;
this.obtainRedisScript = new DefaultRedisScript<>(OBTAIN_LOCK, Boolean.class);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/jsrdxzw/redis/lock/PreloadRedisLock.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public final class PreloadRedisLock extends AbstractRedisLock {
REMOVE_REDIS_SCRIPT.setLocation(new ClassPathResource("remove_lock.lua"));
}

public PreloadRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit) {
super(stringRedisTemplate, lockKey);
public PreloadRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit, String clientId) {
super(stringRedisTemplate, lockKey, clientId);
this.expireTime = expireTime;
this.expireTimeUnit = expireTimeUnit;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
* @author xuzhiwei
*/
public abstract class AbstractRedisLockFactory implements RedisLockFactory {
protected final Map<String, RedisLock> redisLockMap = new ConcurrentHashMap<>();
protected static final String CLIENT_ID = UUID.randomUUID().toString();
protected static final Map<String, RedisLock> REDIS_LOCK_MAP = new ConcurrentHashMap<>();

protected final StringRedisTemplate stringRedisTemplate;

Expand All @@ -24,12 +26,7 @@ public RedisLock getLock(String lockKey, long expireTime, TimeUnit expireTimeUni
if (lockKey == null || lockKey.trim().isEmpty()) {
throw new RuntimeException("lockKey can not be empty!");
}
RedisLock redisLock = redisLockMap.get(lockKey);
if (redisLock == null) {
redisLock = createRedisLock(stringRedisTemplate, lockKey, expireTime, expireTimeUnit);
redisLockMap.putIfAbsent(lockKey, redisLock);
}
return redisLock;
return REDIS_LOCK_MAP.computeIfAbsent(lockKey, (key) -> createRedisLock(stringRedisTemplate, key, expireTime, expireTimeUnit));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public DefaultRedisLockFactory(StringRedisTemplate stringRedisTemplate) {

@Override
protected RedisLock createRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit) {
return new DefaultRedisLock(stringRedisTemplate, lockKey, expireTime, expireTimeUnit);
return new DefaultRedisLock(stringRedisTemplate, lockKey, expireTime, expireTimeUnit, CLIENT_ID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public PreloadRedisLockFactory(StringRedisTemplate stringRedisTemplate) {

@Override
protected RedisLock createRedisLock(StringRedisTemplate stringRedisTemplate, String lockKey, long expireTime, TimeUnit expireTimeUnit) {
return new PreloadRedisLock(stringRedisTemplate, lockKey, expireTime, expireTimeUnit);
return new PreloadRedisLock(stringRedisTemplate, lockKey, expireTime, expireTimeUnit, CLIENT_ID);
}
}

0 comments on commit 4356171

Please sign in to comment.