在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1. redis在实际的应用中不仅可以用来缓存数据,在分布式应用开发中,经常被用来当作分布式锁的使用,为什么要用到分布式锁呢? 在分布式的开发中,以电商库存的更新功能进行讲解,在实际的应用中相同功能的消费者是有多个的,假如多个消费者同一时刻要去消费一条数据,假如业务逻辑处理逻辑是查询出redis中的商品库存,而如果第一个进来的消费的消费者获取到库存了,还没进行减库存操作,相对晚来的消费者就获取了商品的库存,这样就导致数据会出错,导致消费的数据变多了。 例如:消费者A和消费者B分别去消费生产者C1和生产者C2的数据,而生产者都是使用同一个redis的数据库的,如果生产者C1接收到消费者A的消息后,先进行查询库存,然后当要进行减库存的时候,因为生产者C2接收到消费者B的消息后,也去查询库存,而因为生产者C1还没有进行库存的更新,导致生产者C2获取到的库存数是脏数据,而不是生产者C1更新后的数据,导致业务出错。 如果不是分布式的应用,可以使用synchronized进行防止库存更新的问题的产生,但是synchronized只是基于JVM层面的,如果在不同的JVM中,就不能实现这样的功能。 @GetMapping("getInt0") public String test() { synchronized (this) { //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * a业务逻辑 * * */ if (productNum > 0) { stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); int productNumNow = productNum - 1; } else { return "product=0"; } int productNumNow = productNum - 1; return "success=" + productNumNow; } } 2.如何使用redis的功能进行实现分布式锁2.1 redis分布式锁思想如果对redis熟悉的话,我们能够想到redis中具有setnx的命令,该命令的功能宇set功能类似,但是setnx的命令在进行存数据前,会检查redis中是否已经存在相同的key,如存在的话就返回false,反之则返回true,因此我们可以使用该命令的功能,设计一个分布式锁。 2.1.1设计思想:
2.1.2 根据上面的设计思想进行代码实现代码片段【1】 @GetMapping("getInt1") public String fubushisuo(){ //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); //如果能够成功的设置lockkey,这说明当前获取到分布式锁 if (!opsForSet){ return "false"; } //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * a业务逻辑 * * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); int productNumNow = productNum - 1; }else { return "product=0"; } //然后进行释放锁 stringRedisTemplate.delete(lockkey); int productNumNow = productNum-1; return "success="+productNumNow; } 2.1.2.1反思代码片段【1】如果使用这种方式,会产生死锁的方式: 优化代码【2】 @GetMapping("getInt2") public String fubushisuo2(){ //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); int productNumNow = 0; //如果能够成功的设置lockkey,这说明当前获取到分布式锁 if (!opsForSet){ return "false"; } try { //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * b业务逻辑 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //然后进行释放锁 stringRedisTemplate.delete(lockkey); } return "success="+productNumNow; } 2.1.2.2反思代码【2】出现问题的情况: 解决方法:加上过期时间,但又服务宕机了,过了设置的时间后,redis会可以把key给删除,这样其他的的服务器就可以正常的进行上锁了。 优化代码【3】 @GetMapping("getInt3") public String fubushisuo3(){ //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; //[01] boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); //设置过期时间为10秒,但是如果使用该命令,没有原子性,可能执行expire前宕机了,而不是设置过期时间, //[02] stringRedisTemplate.expire(lockkey, Duration.ofSeconds(10)); //使用setIfAbsent(lockkey,lockvalue,10,TimeUnit.SECONDS);代码代替上面[01],[02]行代码 Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS); int productNumNow = 0; //如果能够成功的设置lockkey,这说明当前获取到分布式锁 if (!opsForSet){ return "false"; } try { //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * c业务逻辑 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //然后进行释放锁 stringRedisTemplate.delete(lockkey); } return "success="+productNumNow; } 2.1.2.3 反思优化代码【3】出现问题的情况: 解决方法:使用UUID,产生一个随机数,当要进行delete(删除)redis中key时,判断是不是之前自己设置的UUID 代码优化【4】 @GetMapping("getInt4") public String fubushisuo4(){ //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false String lockkey = "yigehaimeirumengdechengxuyuan"; //获取UUID String lockvalue = UUID.randomUUID().toString(); Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS); int productNumNow = 0; //如果能够成功的设置lockkey,这说明当前获取到分布式锁 if (!opsForSet){ return "false"; } try { //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * c业务逻辑 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //进行释放锁 if (lockvalue==stringRedisTemplate.opsForValue().get(lockkey)){ stringRedisTemplate.delete(lockkey); } } return "success="+productNumNow; } 2.1.2.4 反思优化代码【4】出现问题的情况: 解决方法:目前有很多redis的分布式锁的框架,其中redisson用的是比较多的 2.2 使用redisson进行实现分布式锁先添加redisson的maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.1</version> </dependency> redisson的bean配置 @Configuration public class RedissonConfigure { @Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://27.196.106.42:6380").setDatabase(0); return (Redisson) Redisson.create(config); } } 实现分布式锁代码如下 @GetMapping("getInt5") public String fubushisuo5(){ //setIfAbsent的指令功能和redis命令中的setNx功能一样,如果redis中已经存在相同的key,则返回false String lockkey = "yigehaimeirumengdechengxuyuan"; //获取UUID RLock lock = redisson.getLock(lockkey); lock.lock(); int productNumNow = 0; //如果能够成功的设置lockkey,这说明当前获取到分布式锁 try { //获取当前商品的数量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后对商品进行出库操作,即进行减1 /* * c业务逻辑 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { lock.unlock(); } //然后进行释放锁 return "success="+productNumNow; } 从面就能看到,redisson实现分布式锁是非常简单的,只要简单的几条命令就能实现分布式锁的功能的。 到此这篇关于redis分布式锁详解(优化redis分布式锁的过程及Redisson使用)的文章就介绍到这了,更多相关redis分布式锁内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论