为什么不用分布式锁
分布式锁属于悲观锁,不利于并发优化
能不能用Redis+Lua
利用Redis+Lua单线程原子性特性,可以解决高并发且无锁,单对于复杂业务逻辑,例如加入数据库业务逻辑判断,Lua非常不友好,且不容易调试
java
package com.itlaoqi.redislua;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@RestController
public class LuaController {
private static final String LUA_SCRIPT = """
if tonumber(redis.call('exists', KEYS[1])) == 0 then
redis.call('set', KEYS[1],'10')
end
if tonumber(redis.call('exists', KEYS[2])) == 0 then
redis.call('sadd', KEYS[2],'-1')
end
if tonumber(redis.call('get', KEYS[1])) > 0 and tonumber(redis.call('sismember', KEYS[2] , ARGV[1])) == 0 then
redis.call('incrby', KEYS[1],'-1')
redis.call('sadd',KEYS[2],ARGV[1])
return 1
else
return 0
end
""";
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/sk")
public Map secKill(String pid){
Map resp = new HashMap();
String uid = String.valueOf(new Random().nextInt(100000000));
List keys = new ArrayList();
keys.add("P" + pid); //P1010 String类型 用于保存1010产品库存量
keys.add("U" + pid);//U1010 SET类型 用于保存秒杀确权的UID
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT,Long.class);
Long result = redisTemplate.execute(redisScript, keys,uid);
resp.put("uid", uid);
resp.put("result", result);
return resp;
}
}
Spring Retry + Redis Watch实现乐观锁
通过多次重试实现最小程度锁定,开发模式利用Java语言接口
Redis中的事务是指在单个步骤中执行一组命令,围绕着MULTI、EXEC、DISCARD和WATCH命令展开。
引入依赖
java
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
启用spring-retry
java
@SpringBootApplication
@EnableRetry
public class RedisClientSideApplication {
public static void main(String[] args) {
SpringApplication.run(RedisClientSideApplication.class, args);
}
}
业务逻辑
java
package com.itwenqiang.redisclientside;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class SampleService {
@Autowired
private RedisTemplate redisTemplate;
@Retryable(retryFor = IllegalStateException.class, maxAttempts = 2)
@Transactional
public String sa(){
System.out.println("executing sa()");
List execute = (List)redisTemplate.execute(new SessionCallback<List>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
redisTemplate.watch("sa001");
redisTemplate.multi();
redisTemplate.opsForValue().set("pri001",-100);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
redisTemplate.opsForValue().set("sa001",100);
return redisTemplate.exec();
}
});
if(execute.size()==0){
System.out.println("发现并发冲突:" + execute);
throw new IllegalStateException("Retry");
}else{
System.out.println("exec执行成功:" + execute);
}
return "success";
}
}
控制器
java
package com.itwenqiang.redisclientside;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SampleService sampleService;
@GetMapping("/test")
public String testWatch(){
sampleService.sa();
return "success";
}
@GetMapping("/setSA")
public String setSA(){
redisTemplate.opsForValue().set("sa001",300);
return "success";
}
}
测试代码
java
GET http://localhost:8080/test
GET http://localhost:8080/setSA
执行结果
java
executing sa()
发现并发冲突:[]
executing sa()
exec执行成功:[true, true]