redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列等数据结构,直接使用的分布式锁,以及人物调度器等。
目前有很多项目还在使用jedis的 setNx 充当分布式锁,然而这个锁是有问题的,redisson是java支持redis的redlock的唯一实现, 集成该项目后只需要极少的配置.就能够使用redisson的全部功能. 目前支持 集群模式,云托管模式,单Redis节点模式,哨兵模式,主从模式 配置. 支持 可重入锁,公平锁,联锁,红锁,读写锁 锁定模式。
Redis集群组态的最低要求是必须有三个主节点。集群如何搭建参考之前文章,之前操作后最终是四个master和四个从。
官方地址:https://github.com/redisson/redisson
WIKI:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
redisson-spring-boot-starter:https://gitee.com/ztp/redisson-spring-boot-starter
POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springBoot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springBoot</name> <description>springBoot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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.11.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置application.yml,为了演示Spring的RedisTemplate
server: port: 80 spring: redis: cluster: nodes: - 192.168.3.22:7001 - 192.168.3.22:7002 - 192.168.3.22:7003 - 192.168.3.22:7004 - 192.168.3.22:8001 - 192.168.3.22:8002 - 192.168.3.22:8003 - 192.168.3.22:8004
在resources文件夹,即application.yml同级目录创建redisson.yml,内容参考官网WIKI,把节点更改为自己的
--- clusterServersConfig: idleConnectionTimeout: 10000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 password: null subscriptionsPerConnection: 5 clientName: null loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {} slaveSubscriptionConnectionMinimumIdleSize: 1 slaveSubscriptionConnectionPoolSize: 50 slaveConnectionMinimumIdleSize: 32 slaveConnectionPoolSize: 64 masterConnectionMinimumIdleSize: 32 masterConnectionPoolSize: 64 readMode: "SLAVE" nodeAddresses: - "redis://192.168.3.22:7001" - "redis://192.168.3.22:7002" - "redis://192.168.3.22:7003" - "redis://192.168.3.22:7004" - "redis://192.168.3.22:8001" - "redis://192.168.3.22:8002" - "redis://192.168.3.22:8003" - "redis://192.168.3.22:8004" scanInterval: 1000
创建一个用于获取RedissonClient的管理类,这里仅为演示,实际请用单例模式:
package com.example.springboot.tool; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.stereotype.Component; import org.springframework.util.ResourceUtils; /** * @Auther: Java小强 * @Date: 2022/1/27 - 23:15 * @Decsription: com.example.springboot.tool * @Version: 1.0 */ @Setter @Getter @ToString @Component public class MyRedissonClient { public RedissonClient getRedisson() { RedissonClient redisson = null; // Config config = new Config(); // config.useSingleServer().setAddress("127.0.0.1:6379").setPassword("xrBZgBDp182B3Dl0A"); // RedissonClient redisson = Redisson.create(config); // 默认连接地址 127.0.0.1:6379 // RedissonClient redisson = Redisson.create(); try { Config config = Config.fromYAML(ResourceUtils.getFile("classpath:redisson.yml")); redisson = Redisson.create(config); } catch (Exception e) { e.printStackTrace(); redisson = null; } return redisson; } }
写一个Controller,里面包含了RedisTemplate和Redisson两种工具的使用方式,业务都是模仿扣库存
package com.example.springboot.controller; import com.example.springboot.tool.MyRedissonClient; import org.redisson.api.RBucket; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.client.codec.StringCodec; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @Auther: Java小强 * @Date: 2022/1/27 - 22:28 * @Decsription: com.example.springboot.controller * @Version: 1.0 */ @RestController @RequestMapping("/redis") public class RedisController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private MyRedissonClient myRedissonClient; @GetMapping("/doStockson/{id}") public String doStockson(@PathVariable("id") String id) { String uuid = UUID.randomUUID().toString(); String lockKey = "STOCK_" + id + "_LOCK"; String stockKey = "stock" + id; RedissonClient redisson = myRedissonClient.getRedisson(); RLock lock = redisson.getLock(lockKey); // 加锁以后10秒钟自动解锁 无需调用unlock方法手动解锁 // lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后30秒自动解锁 try { boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { System.out.println("Do My Jobs..."); RBucket<String> bucket = redisson.getBucket(stockKey, StringCodec.INSTANCE); if (null == bucket || "".equals(bucket.get())) { return "error code"; } int stock = Integer.parseInt(bucket.get()); System.out.println("剩余库存:" + stock); if (stock > 0) { stock--; bucket.set(stock + ""); System.out.println("库存扣除成功,剩余:" + stock); } else { System.out.println("库存扣除失败"); } } }catch (Exception exception){ }finally { lock.unlock(); } return "Redisson Job Is Done"; } @GetMapping("/doStock/{id}") public String getStock(@PathVariable("id") String id) { String uuid = UUID.randomUUID().toString(); String lockKey = "STOCK_" + id + "_LOCK"; String stockKey = "stock" + id; // 如果程序30妙也完不成,则要考虑看门狗给key续命问题 Boolean re = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 30, TimeUnit.SECONDS); if (!re) { return "error code"; } try { System.out.println("Do My Jobs..."); String stockStr = stringRedisTemplate.opsForValue().get(stockKey); if (null == stockStr || "".equals(stockStr)) { return "error code"; } int stock = Integer.parseInt(stockStr); System.out.println("剩余库存:" + stock); if (stock > 0) { stock--; stringRedisTemplate.opsForValue().set(stockKey, stock + ""); System.out.println("库存扣除成功,剩余:" + stock); } else { System.out.println("库存扣除失败"); } } finally { if (stringRedisTemplate.opsForValue().get(lockKey).equals(uuid)) { stringRedisTemplate.delete(lockKey); } } return "Redis Job Is Done"; } }
分别访问:
http://localhost/redis/doStock/1001
http://localhost/redis/doStockson/1001
可以打个断点,或者在方法内休眠,在进入Lock后再发一个请求看效果。
Java小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: