为什么不用分布式锁
分布式锁属于悲观锁,不利于并发优化
能不能用Redis+Lua
利用Redis+Lua单线程原子性特性,可以解决高并发且无锁,单对于复杂业务逻辑,例如加入数据库业务逻辑判断,Lua非常不友好,且不容易调试
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 thenredis.call('set', KEYS[1],'10')endif tonumber(redis.call('exists', KEYS[2])) == 0 thenredis.call('sadd', KEYS[2],'-1')endif 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 1else return 0 end""";@Autowiredprivate 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类型 用于保存秒杀确权的UIDDefaultRedisScript<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命令展开。
引入依赖
<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
@SpringBootApplication
@EnableRetry
public class RedisClientSideApplication {public static void main(String[] args) {SpringApplication.run(RedisClientSideApplication.class, args);}}
业务逻辑
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 {@Autowiredprivate RedisTemplate redisTemplate;@Retryable(retryFor = IllegalStateException.class, maxAttempts = 2)@Transactionalpublic 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";}
}
控制器
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 {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate SampleService sampleService;@GetMapping("/test")public String testWatch(){sampleService.sa();return "success";}@GetMapping("/setSA")public String setSA(){redisTemplate.opsForValue().set("sa001",300);return "success";}}
测试代码
GET http://localhost:8080/test
GET http://localhost:8080/setSA
执行结果
executing sa()
发现并发冲突:[]
executing sa()
exec执行成功:[true, true]