isHooky 2020-04-10
Redisson是一个在Redis的基础上实现的Java驻内存数据网格。它几乎提供了Redis所有工具,不仅封装Redis底层数据结构,而且还提供了很多Java类型映射。Redisson支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构。Redisson除了普通分布式锁还支持 联锁(MultiLock),读写锁(ReadWriteLock),公平锁(Fair Lock),红锁(RedLock),信号量(Semaphore),可过期性信号量(PermitExpirableSemaphore)和闭锁(CountDownLatch)等。
Redisson 虽然功能强大但是它依然不能解决分布式锁有可能锁不住的情况,这不是Redisson或者Redis的问题(目前遇到这种问题只能人工干预)。本篇主要是平时工作中使用对Redisson分布式锁的封装
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.9.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.2</version> </dependency>
redis: server: database: 0 host: redis的ip地址 maxIdle: 500 maxTotal: 50 maxWaitMillis: 10000 minEvictableIdleTimeMillis: 60000 minIdle: 10 numTestsPerEvictionRun: 10 password: yiwei-redis-666 port: redis的端口号 testOnBorrow: true testOnReturn: true testWhileIdle: true timeBetweenEvictionRunsMillis: 30000 timeOut: 2000
import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component public class JedisProperties { @Value("${redis.server.host}") private String host; @Value("${redis.server.port}") private int port; @Value("${redis.server.password}") private String password; @Value("${redis.server.maxTotal}") private int maxTotal; @Value("${redis.server.maxIdle}") private int maxIdle; @Value("${redis.server.minIdle}") private int minIdle; @Value("${redis.server.maxWaitMillis}") private int maxWaitMillis; @Value("${redis.server.timeOut}") private int timeOut; @Value("${redis.server.testOnBorrow}") private boolean testOnBorrow; @Value("${redis.server.testOnReturn}") private boolean testOnReturn; @Value("${redis.server.testWhileIdle}") private boolean testWhileIdle; @Value("${redis.server.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${redis.server.numTestsPerEvictionRun}") private int numTestsPerEvictionRun; @Value("${redis.server.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${redis.server.database}") private int database; }
import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import redis.clients.jedis.JedisPoolConfig; import javax.annotation.Resource; @Slf4j @Configuration @EnableCaching public class JedisConfig { @Resource private JedisProperties prop; @Bean(name = "jedisPoolConfig") public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(prop.getMaxTotal()); config.setMaxIdle(prop.getMaxIdle()); config.setMinIdle(prop.getMinIdle()); config.setMaxWaitMillis(prop.getMaxWaitMillis()); config.setTestOnBorrow(prop.isTestOnBorrow()); config.setTestOnReturn(prop.isTestOnReturn()); config.setTestWhileIdle(prop.isTestWhileIdle()); config.setNumTestsPerEvictionRun(prop.getNumTestsPerEvictionRun()); config.setMinEvictableIdleTimeMillis(prop.getMinEvictableIdleTimeMillis()); config.setTimeBetweenEvictionRunsMillis(prop.getTimeBetweenEvictionRunsMillis()); return config; } @Bean(name ="jedisConnectionFactory") public JedisConnectionFactory jedisConnectionFactory(){ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setPort(prop.getPort()); redisStandaloneConfiguration.setHostName(prop.getHost()); redisStandaloneConfiguration.setPassword(RedisPassword.of(prop.getPassword())); redisStandaloneConfiguration.setDatabase(prop.getDatabase()); return new JedisConnectionFactory(redisStandaloneConfiguration); } @Bean(name ="redisTemplate") public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(jedisConnectionFactory); return template; } }
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; @Configuration public class RedissonConfig { @Resource private JedisProperties prop; @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer().setAddress("redis://" + prop.getHost() + ":" + prop.getPort()) .setPassword(prop.getPassword()).setDatabase(prop.getDatabase()); return Redisson.create(config); } }
分布式锁主要采用,模板模式,应为它天生就有业务骨架属性。当我们使用1.8以上的JDK时,针对模板模式,使用方简化了很多操作。只是需要注意定义模板方法时要定义成函数接口
public interface VoidHandle { /** * 业务处理 */ void execute(); } public interface ReturnHandle<T> { /** * 业务处理 * @return */ T execute(); }
@Slf4j @Service public class RedisLock { @Autowired private RedissonClient redissonClient; /** * 分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 */ @Transactional(rollbackFor = Exception.class) public void lock(String lockName, Object businessId, VoidHandle handle) { RLock rLock = getLock(lockName, businessId); try { rLock.lock(); log.info("业务ID{},获取锁成功", businessId); handle.execute(); } finally { rLock.unlock(); } } /** * 带返回值分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 * @param <T> 返回值 * @return */ @Transactional(rollbackFor = Exception.class) public <T> T lock(String lockName, Object businessId, ReturnHandle<T> handle) { RLock rLock = getLock(lockName, businessId); try { rLock.lock(); log.info("业务ID{},获取锁成功", businessId); return handle.execute(); } finally { rLock.unlock(); } } /** * 分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 */ @Transactional(rollbackFor = Exception.class) public void tryLock(String lockName, Object businessId, VoidHandle handle) { RLock rLock = getLock(lockName, businessId); if (!rLock.tryLock()) { log.info("业务ID{},获取锁失败,返回", businessId); return; } try { log.info("业务ID{},获取锁成功", businessId); handle.execute(); } finally { rLock.unlock(); } } /** * 带返回值分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 * @param <T> 返回值 * @return */ @Transactional(rollbackFor = Exception.class) public <T> T tryLock(String lockName, Object businessId, ReturnHandle<T> handle) { RLock rLock = getLock(lockName, businessId); if (!rLock.tryLock()) { log.info("业务ID{},获取锁失败,返回null", businessId); return null; } try { log.info("业务ID{},获取锁成功", businessId); return handle.execute(); } finally { rLock.unlock(); } } /** * 分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 */ @Transactional(rollbackFor = Exception.class) public void tryLockException(String lockName, Object businessId, VoidHandle handle) { RLock rLock = getLock(lockName, businessId); if (!rLock.tryLock()) { log.info("业务ID{},获取锁失败,抛异常处理", businessId); throw new RuntimeException("处理中"); } try { log.info("业务ID{},获取锁成功", businessId); handle.execute(); } finally { rLock.unlock(); } } /** * 带返回值分布式锁实现 * @param lockName 锁名称 * @param businessId 业务ID * @param handle 业务处理 * @param <T> 返回值 * @return */ @Transactional(rollbackFor = Exception.class) public <T> T tryLockException(String lockName, Object businessId, ReturnHandle<T> handle) { RLock rLock = getLock(lockName, businessId); if (!rLock.tryLock()) { log.info("业务ID{},获取锁失败,抛异常处理", businessId); throw new RuntimeException("处理中"); } try { log.info("业务ID{},获取锁成功", businessId); return handle.execute(); } finally { rLock.unlock(); } } /** * 获取锁 * @param lockName * @param businessId * @return */ private RLock getLock(String lockName, Object businessId) { log.info("获取分布式锁lockName:{},businessId:{}", lockName, businessId); if (StringUtils.isEmpty(lockName)) { throw new RuntimeException("分布式锁KEY为空"); } if (StringUtils.isEmpty(businessId)) { throw new RuntimeException("业务ID为空"); } String lockKey = lockName + businessId.toString(); return redissonClient.getLock(lockKey); } }
@Autowired private RedisLock redisLock; @Test public void test() { redisLock.tryLock("order:pay:", 1, () -> { // 业务逻辑 }); Boolean payResult = redisLock.tryLock("order:pay:", 2, () -> { // 业务逻辑 return true; }); Integer payResult2 = redisLock.tryLock("order:pay:", 2, () -> { // 业务逻辑 return 0; }); String payResult3 = redisLock.tryLock("order:pay:", 2, () -> { // 业务逻辑 return ""; }); }
测试方法没有粘类,不过已经可以看出用起来还是超方便的。