当前位置:   article > 正文

Redis分布式锁实现

Redis分布式锁实现

1.Controller中创建Redis

  1. //1 获取锁,setnx
  2. //得到一个 uuid 值,作为锁的值
  3. String uuid = UUID.randomUUID().toString();
  4. Boolean lock =
  5. redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
  6. //2 获取锁成功
  7. if (lock) {
  8. //准备删除锁脚本
  9. //String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  10. //DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
  11. //redisScript.setScriptText(script);
  12. //redisScript.setResultType(Long.class);
  13. //写自己的业务-就可以有多个操作了
  14. Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
  15. if (decrement < 0) {//说明这个商品已经没有库存
  16. //说明当前秒杀的商品,已经没有库存
  17. entryStockMap.put(goodsId, true);
  18. //恢复库存为0
  19. redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
  20. //释放锁.-lua为什么使用redis+lua脚本释放锁前面讲过
  21. redisTemplate.execute(script, Arrays.asList("lock"), uuid);
  22. model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
  23. return "secKillFail";//错误页面
  24. }
  25. //释放分布式锁
  26. redisTemplate.execute(script, Arrays.asList("lock"), uuid);
  27. } else {
  28. //3 获取锁失败,返回个信息[本次抢购失败,请再次抢购...]
  29. model.addAttribute("errmsg", RespBeanEnum.SEC_KILL_RETRY.getMessage());
  30. return "secKillFail";//错误页面
  31. }

2.在application.yml同目录下创建lua脚本 

  1. if redis.call('get', KEYS[1]) == ARGV[1] then
  2. return redis.call('del', KEYS[1])
  3. else return 0
  4. end

3.增加配置执行脚本 

  1. package com.hspedu.seckill.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.io.ClassPathResource;
  5. import org.springframework.data.redis.connection.RedisConnectionFactory;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.data.redis.core.script.DefaultRedisScript;
  8. import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
  9. import org.springframework.data.redis.serializer.StringRedisSerializer;
  10. //把session信息提取出来存到redis中
  11. //主要实现序列化, 这里是以常规操作
  12. @Configuration
  13. public class RedisConfig {
  14. //自定义 RedisTemplate对象, 注入到容器
  15. //后面我们操作Redis时,就使用自定义的 RedisTemplate对象
  16. @Bean
  17. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  18. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  19. //设置相应key的序列化
  20. redisTemplate.setKeySerializer(new StringRedisSerializer());
  21. //value序列化
  22. //redis默认是jdk的序列化是二进制,这里使用的是通用的json数据,不用传具体的序列化的对象
  23. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  24. //设置相应的hash序列化
  25. redisTemplate.setHashKeySerializer(new StringRedisSerializer());
  26. redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
  27. //注入连接工厂
  28. redisTemplate.setConnectionFactory(redisConnectionFactory);
  29. System.out.println("测试--> redisTemplate" + redisTemplate.hashCode());
  30. return redisTemplate;
  31. }
  32. //增加执行脚本
  33. @Bean
  34. public DefaultRedisScript<Long> script() {
  35. DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
  36. //设置要执行的lua脚本位置, 把lock.lua文件放在resources
  37. redisScript.setLocation(new ClassPathResource("lock.lua"));
  38. redisScript.setResultType(Long.class);
  39. return redisScript;
  40. }
  41. }

4.Controller装配 RedisScript

@Resource private RedisScript script;

 使用lua脚本的目的,是为了读取到当前程序中的锁,和redis中的锁进行对比。

避免A用户因为锁生效时间超时以后自动删除了A当前用户拿到的锁,进而在操作完成时,主动去删除锁时此时有个B用户正好生成了一把锁,那么A用户删除的是B用户的锁,造成数据不一致。

当前程序的锁就是A用户的锁,同时redis存放的也是A用户的锁,就不会造成锁的误删,从而保证了原子性

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/335800
推荐阅读
相关标签
  

闽ICP备14008679号