1.redisTemplate和stringRedisTemplate的区别是:redisTemplate是带序列 化的,redis的命令是没办法对序列化的东西操作。
比如:redisTemplate.opsForHash().entries()就无法自增。
2.redis的分布式锁lua脚本 我们的lua脚本是写在java中的在发给redis,如果这个lua脚本被频繁的执行,都要发一次rdis执行脚本还不如先缓存到redis,redis在返回一个签名,这个签名是和lua脚本是一一对应的关系。后面在执行lua脚本的时候根据签名执行就好了。
这里需要注意的是lua脚本是不会序列化和反序列化的,如果用redisTemplate操作的元素你是不能用脚本去操作的,但是执行脚本都可以。
lua脚本的缓存
> 缓存脚本:script load "SCRIPT" 返回一个sha签名字符串
> 执行缓存脚本:evalsha "脚本签名" numkeys [key ...] [arg .....]
3.execute里面会有缓存的功能
4.线程安全遇到的几种情况:
4.1:为了防止获得锁和在解锁之间比如断网了什么的意外造成的死锁,所以就设置一个过期时间
4. 2:万一在加完锁和在获得锁之间挂掉了于是就采用了lua脚本
4.3当在解锁的时候刚好过期了,别的线程拿到了这个锁,这个锁被解锁了也就是删除了,于是为了防止解别的锁的情况发生,而且在分布式的情况下加的锁名字必须相同但是用静态的又分不出锁之间的区别,于是就用了value设置成UUID,解锁的时候判断下UUID是否相同,在判断锁和解锁之间也为了线程安全于是也用lua脚本去解锁;
public int miaosha(Integer gid, Integer uid) {
//查询商品库存
//如果够,就扣减库存
//如果不够就返回失败
//扣减成功添加订单
lockUtil.lock("mylock",gid);
//当前线程获得分布式锁
Goods goods = goodsMapper.selectGoodsById(gid);
try {
if (goods.getSave() >= 1){
//库存充足
goodsMapper.jianSave(gid);
//添加订单
Orders orders = new Orders(
null, UUID.randomUUID().toString(), gid, uid, new Date()
);
ordersMapper.insert(orders);
return 1;
}
} finally {
//解锁
lockUtil.unlock("mylock"+gid);
}
return 0;
}
}
4.4: 在封装的时候加锁和解锁必须在同一个线程,于是就把uuid设置到ThreadLocal中
@Component
public class LockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
//加锁的lua脚本
private String lockLua = "--锁的名称\n" +
"local lockName=KEYS[1]\n" +
"--锁的value\n" +
"local lockValue=ARGV[1]\n" +
"--过期时间 秒\n" +
"local timeout=tonumber(ARGV[2])\n" +
"--尝试进行加锁\n" +
"local flag=redis.call('setnx', lockName, lockValue)\n" +
"--判断是否获得锁\n" +
"if flag==1 then\n" +
"--获得分布式锁,设置过期时间\n" +
"redis.call('expire', lockName, timeout)\n" +
"end\n" +
"--返回标识\n" +
"return flag ";
//解锁的lua脚本
private String unLockLua = "--锁的名称\n" +
"local lockName=KEYS[1]\n" +
"--锁的value\n" +
"local lockValue=ARGV[1]\n" +
"--判断锁是否存在,以及锁的内容是否为自己加的\n" +
"local value=redis.call('get', lockName)\n" +
"--判断是否相同\n" +
"if value == lockValue then\n" +
" redis.call('del', lockName)\n" +
" return 1\n" +
"end\n" +
"return 0";
private ThreadLocal<String> tokens = new ThreadLocal<>();
/**
* 加锁
* @return
*/
public void lock(String lockName){
lock(lockName, 30);
}
public void lock(String lockName, Integer timeout){
String token = UUID.randomUUID().toString();
//设置给threadLocal
tokens.set(token);
//分布式锁 - 加锁,锁的value不写在参数里
Long flag = (Long) redisTemplate.execute(new DefaultRedisScript(lockLua, Long.class),
//快速获得list的做法(keys的值 , 可变参数value的值,过期时间)
Collections.singletonList(lockName),
token, timeout + ""
);
System.out.println("获得锁的结果:" + flag);
//设置锁的自旋
if (flag == 0) {
//未获得锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock(lockName, timeout);
}
}
/**
* 解锁
* @return
*/
public boolean unlock(String lockName){
//获得ThreadLocal
String token = tokens.get();
//解锁
Long result = (Long) redisTemplate.execute(new DefaultRedisScript(unLockLua, Long.class),
Collections.singletonList(lockName),
token);//token要获得加锁的但是我们不能把token设置成全局的,这样所有的锁都叫token,那我要加锁的时候
//有特有的token,因为加锁和解锁都是在同一个线程执行,同一个线程传东西可以用ThreadLocal
System.out.println("删除锁的结果:" + result);
return result == 1;
}
}
二:Redisson 底层封装了 Luna 脚本来实现的分布式锁 实现
2.1配置yml
redis:
database: 0
sentinel:
master: mymaster
nodes: 192.168.133.209:26379,192.168.133.204:26379,192.168.133.208:26379
password: sykj-hx123
timeout: 3600ms
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
lettuce:
pool:
max-active: 100
max-idle: 100
min-idle: 50
max-wait: 6000ms
shutdown-timeout: 100ms
2.2 : 配置依赖
<groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.3</version> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.3 : RedissonConfig 配置文件
@Configuration
public class RedissonConfig {
private final static Logger logger = LoggerFactory.getLogger("RedissonConfig");
@Autowired
private RedisProperties redisProperties;
public RedissonConfig() {
}
@Bean
public RedissonClient redissonClient(){
Config config = null;
try {
config = new Config();
if(redisProperties.getSentinel() != null){ //redis哨兵模式
List<String> nodes = redisProperties.getSentinel().getNodes();
logger.info("...nodes ="+ JSONObject.toJSONString(nodes));
List<String> newNodes = new ArrayList<>(nodes.size());
nodes.stream().forEach((index) -> newNodes.add(
index.startsWith("redis://") ? index : "redis://" + index));
logger.info("...newNodes ="+ JSONObject.toJSONString(newNodes));
logger.info("...redisProperties.getTimeout().get(SECONDS)*1000...="+(redisProperties.getTimeout().get(SECONDS)*1000));
SentinelServersConfig serverConfig = config.useSentinelServers()
.addSentinelAddress(newNodes.toArray(new String[0]))
.setMasterName(redisProperties.getSentinel().getMaster())
.setReadMode(ReadMode.SLAVE)
.setTimeout((int)(redisProperties.getTimeout().get(SECONDS)*1000));
// 设置密码
if(StringUtils.isNotBlank(redisProperties.getPassword())){
serverConfig.setPassword(redisProperties.getPassword());
}
// 设置database
if (redisProperties.getDatabase()==0){
serverConfig.setDatabase(redisProperties.getDatabase());
}
}else{ //redis单例
config.useSingleServer().setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort()).setDatabase(redisProperties.getDatabase());
if(StringUtils.isNotBlank(redisProperties.getPassword())){
config.useSingleServer().setPassword(redisProperties.getPassword());
}
}
return Redisson.create(config);
}catch (Exception e){
logger.error("redissonClient", e);
logger.error("...RedissonConfig...出现异常...e ="+e.toString());
}
return null;
}
}
2.4 :实现
@ApiOperation(value = "提交订单")
@PostMapping("/submit")
public WebResult submit(@RequestBody @Valid OrderSubmitReq req) {
TicketModel ticket = ticketManager.getTicket();
logger.info("submit-req|{}", JSON.toJSONString(req));
RedissonUtil.singleExec(LK_ORDER_SUBMIT + ticket.getCompanyCode(), () -> globalTrans.trans(() -> orderSubmit(req, ticket) ));
return WebResultUtils.success();
}
2.5 : 上锁
/**
* 单个执行 在执行中直接抛出异常
* @author hewei
* @date: 2020/11/9 19:12
*/
public static <T> T singleExec(String key, Supplier<T> run){
logger.info("singleExec|{}",key);
RLock lock = redissonClient.getLock(key);
try {
boolean b = lock.tryLock();
if (!b) {
throw new ParamValidException(OPERATION_IN_PROGRESS);
}
return run.get();
}finally {
logger.info("singleExec-finally|{}",key);
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}