7-1、Redis Redisson分布式锁实战(含Spring Boot)

在【Redis Jedis实战(含Spring Boot)】基础上做如下修改:

1、子pom.xml
添加redisson依赖

	
	
	    org.redisson
	    redisson
	    3.10.0
	

注意:redisson-spring-boot-starter与spring-boot-starter-data-redis是有冲突的,redisson-spring-boot-starter中RedissonAutoConfiguration自动配置Bean: redisTemplate、stringRedisTemplate、redisConnectionFactory,spring-boot-starter-data-redis也自动配置了。
这里选择单独引入redisson。

2、Redisson单独实战
RedisUtil.java

package com.java.ashare.redis.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

/**
 * 一、redis自动配置类:RedisRepositoriesAutoConfiguration,可以查看到能配置的属性以及可以使用的bean
 * 		RedisRepositoriesAutoConfiguration源码可知:
 * 			提供了RedisTemplate和StringRedisTemplate两种RedisTemplate的bean
 * 			区别:
 * 			RedisTemplate
 * 				keySerializer、valueSerializer、hashKeySerializer、hashValueSerializer
 * 				使用JdkSerializationRedisSerializer做序列化,用redis客户端查看key-value时显示一串十六进制的字符串,可读性很差
 * 				泛型是,写代码不方便,需要写好多类型转换的代码,不推荐
 * 				
 * 			StringRedisTemplate
 * 				使用StringRedisSerializer做序列化,用redis客户端查看key-value时显示正常的字符串,缺点是key、value必须都是字符串
 * 				如果是对象的话,需要在程序中手动把对象数据转化为json字符串(fastjson)
 * 			
 * 			综上:我们可以自定义bean---RedisTemplate,查看config/RedisConfig
 * 
 * 
 * 二、RedisConnectionFactory实现LettuceConnectionFactory、JedisConnectionFactory
 * 		springboot1.x默认使用JedisConnectionFactory
 * 		springboot2.x默认使用LettuceConnectionFactory,可以通过debug调试查看变量可知
 */
@Component
public class RedisUtil {

	// 自定义redisTemplate
	// private static RedisTemplate redisTemplate;
	// 使用springboot提供的stringRedisTemplate
	private static StringRedisTemplate stringRedisTemplate;
	
	/**
	 * 通过set方法注入redisTemplate(针对静态变量)
	 */
	/*@Autowired
	public void setRedisTemplate(RedisTemplate redisTemplate) {
		
		RedisUtil.redisTemplate = redisTemplate;
	}*/
	@Autowired
	public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
		
		RedisUtil.stringRedisTemplate = stringRedisTemplate;
	}
	
	/**
	 * 获取string操作对象
	 */
	public static ValueOperations valueOperations() {
		
		return RedisUtil.stringRedisTemplate.opsForValue();
	}
	
	/**
	 * 获取hash操作对象
	 */
	public static HashOperations hashOperations() {
		
		return RedisUtil.stringRedisTemplate.opsForHash();
	}
	
	/**
	 * 获取list操作对象
	 */
	public static ListOperations listOperations() {
		
		return RedisUtil.stringRedisTemplate.opsForList();
	}
	
	/**
	 * 获取set操作对象
	 */
	public static SetOperations setOperations() {
		
		return RedisUtil.stringRedisTemplate.opsForSet();
	}
	
	/**
	 * 获取zset操作对象
	 */
	public static ZSetOperations zSetOperations() {
		
		return RedisUtil.stringRedisTemplate.opsForZSet();
	}
}

RedisUtil使用springboot默认提供的stringRedisTemplate(Bean)。

RedisDistributedLock.java

package com.java.ashare.redis.clients.redisson;

import java.util.concurrent.TimeUnit;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import com.java.ashare.redis.utils.RedisUtil;

public class RedisDistributedLock {

	private static RedissonClient redissonClient;
	
	static {
		
		Config config = new Config();
		// 看门狗功能,注意tryLock时不能设置key过期时间,否则此功能无效
		// 当启用watchdog时,它会设置key过期时间为lockWatchdogTimeout,然后底层子线程定时每隔lockWatchdogTimeout/3重新设置key过期时间为lockWatchdogTimeout
		// 要么开启watchdog,这样会影响性能;要么设置key过期时间(watchdog功能无效),但要保证key过期时间大于原子业务操作时间
		config.setLockWatchdogTimeout(60 * 1000);
		
		// 单机
		config.useSingleServer()
			.setAddress("redis://192.168.78.169:6379")
			// .setPassword("123456")
			.setDatabase(0)
			.setTimeout(60 * 1000);
		
		// sentinel哨兵模式-主从
		/* config.useSentinelServers()
			.addSentinelAddress("redis://192.168.78.169:26379", "redis://192.168.78.169:26380", "redis://192.168.78.169:26381")
			.setMasterName("mymaster")
			// .setPassword("123456")
			.setDatabase(0)
			.setTimeout(60 * 1000); */
		
		// cluster集群模式
		/*config.useClusterServers().addNodeAddress("redis://192.168.78.169:6379", "redis://192.168.78.169:6380", "redis://192.168.78.169:6381",
				"redis://192.168.78.169:6382", "redis://192.168.78.169:6383", "redis://192.168.78.169:6384")
			.setPassword("123456")
			.setTimeout(60 * 1000);*/
		
		redissonClient = Redisson.create(config);
	}
	
	/**
	 * redisson实现redis分布式锁,代码简单,隐藏复杂的实现逻辑
	 */
	public static void decrStockRedisson() throws InterruptedException {
		
		/**
		 * 使用hash结构,key=lockKey, field=uuid+threadId, value=1(可重入锁的重入次数)
		 * 可以理解为ReentrantLock在分布式环境下的实现
		 */
		RLock lock = redissonClient.getLock("lockKey");
		boolean isLock = false;
		
		try {
			System.out.println("线程:" + Thread.currentThread().getName() + ",等待获取锁!");
			
			// 30:等待获取锁的超时时间;60:key过期时间
			// isLock = lock.tryLock(30, 60, TimeUnit.SECONDS);
			// 30:等待获取锁的超时时间
			isLock = lock.tryLock(30, TimeUnit.SECONDS);
			
			if(isLock) {
				
				System.out.println("线程:" + Thread.currentThread().getName() + ",获取锁成功!");
				
				// 线程串行进行原子业务操作
				Thread.sleep(600 * 1000);
				int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
				if(stock > 0) {
					int realStock = stock - 1;
					RedisUtil.valueOperations().set("stock", realStock + "");
					System.out.println("扣减成功,剩余库存:" + realStock);
				} else {
					System.out.println("扣减失败,库存不足!");
				}
			} else {
				
				System.out.println("线程:" + Thread.currentThread().getName() + ",获取锁失败!");
			}
		} finally {
			lock.unlock();
		}
	}
	
	/**
	 * 解决redisson实现redis分布式锁在主从模式下存在的问题:
	 * 即master的lockKey没有同步到slave之前就挂掉了,sentine哨兵切换master,此时其他线程就会获得锁,锁失效造成线程不安全。
	 * 
	 * 核心:RedissonRedLock
	 * 针对完全独立的N个redis实例 或 N个sentinel集群 或 N个cluster集群(N建议大于3,3/5)
	 * 如果要获取分布式锁,那么需要向这N个sentinel集群通过EVAL命令执行LUA脚本,至少需要N/2+1个sentinel集群响应成功,才能成功获取到分布式锁
	 * 这样就可以大概率解决主从模式下的问题,除非超过一半以上的sentinel集群都挂掉,这种概率可以排除不计
	 * N个sentinel集群之间是完全独立的,每个sentinel集群包含正常的主从节点,而不是一个sentinel集群包含N个节点。
	 *
	 * 或者
	 * 
	 * 使用zookeeper实现分布式锁可以解决主从不实时同步的问题,zookeeper特性:zookeeper所有节点始终保持实时同步节点信息,健壮性更好,但性能不及redis。
	 */
	public static void decrStockRedissonRedLock() throws InterruptedException {
		
		Config config1 = new Config();
		config1.useSingleServer()
			.setAddress("redis://192.168.78.169:6379")
			.setDatabase(0)
			.setTimeout(60);
		RedissonClient redissonClient1 = Redisson.create(config1);
		
		Config config2 = new Config();
		config2.useSingleServer()
			.setAddress("redis://192.168.78.169:6380")
			.setDatabase(0)
			.setTimeout(60);
		RedissonClient redissonClient2 = Redisson.create(config2);
		
		Config config3 = new Config();
		config3.useSingleServer()
			.setAddress("redis://192.168.78.169:6380")
			.setDatabase(0)
			.setTimeout(60);
		RedissonClient redissonClient3 = Redisson.create(config3);
		
		String lockKey = "lockKey";
		RLock lock1 = redissonClient1.getLock(lockKey);
		RLock lock2 = redissonClient2.getLock(lockKey);
		RLock lock3 = redissonClient3.getLock(lockKey);
		
		RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
		boolean isLock = false;
		
		try {
			isLock = redLock.tryLock(30, 60, TimeUnit.SECONDS);
			
			if(isLock) {
				int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
				if(stock > 0) {
					int realStock = stock - 1;
					RedisUtil.valueOperations().set("stock", realStock + "");
					System.out.println("扣减成功,剩余库存:" + realStock);
				} else {
					System.out.println("扣减失败,库存不足!");
				}
			}
		} finally {
			redLock.unlock();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		
		for(int i = 0; i < 2; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					
					try {
						RedisDistributedLock.decrStockRedisson();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
}

7-1、Redis Redisson分布式锁实战(含Spring Boot)_第1张图片
3、Redisson+SpringBoot实战
RedissonConfig.java

package com.java.ashare.redis.config;

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;

@Configuration
public class RedissonConfig {
	
	@Bean
	public RedissonClient redissonClient() {
		
		Config config = new Config();
		config.useSingleServer()
			.setAddress("redis://192.168.78.169:6379")
			//.setPassword("123456")
			.setDatabase(0)
			.setTimeout(60);
		return Redisson.create(config);
	}
}

RedisDistributedLockApplication.java

package com.java.ashare.redis.distributedlock;

import java.util.concurrent.TimeUnit;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.java.ashare.redis.utils.RedisUtil;

@Component
public class RedisDistributedLockApplication {
	/**
	 * redisson实现分布式锁,代码简单,隐藏复杂的实现逻辑
	 * 这里选择单独引入redisson
	 * 
	 * 与springboot集成简单说明:
	 * 依赖:
	 * 
     *		org.redisson
     *		redisson-spring-boot-starter
	 *		3.10.0
	 * 
	 * 与spring-boot-starter-data-redis二选一,redisson-spring-boot-starter中RedissonAutoConfiguration自动配置Bean: redisTemplate、stringRedisTemplate、redisConnectionFactory,与spring-boot-starter-data-redis中的Bean可能存在冲突
	 * 
	 * 自动配置类:RedissonAutoConfiguration
	 * 
	 * springboot+redisson application.yml说明:
	 * 		spring: 
  	 *			redis: 
     *				host: 192.168.78.169
     *				port: 6379
     *				redisson: 
     * 					config: classpath:redisson-single-dev.yml
	 * 
	 * redisson-single-dev.yml
	 * 		singleServerConfig:		
	 * 			address: redis://47.103.5.190:6379
	 * 			password: 123456
	 *			timeout: 3000
	 *			database: 0
	 * 有哪些属性可以查看属性配置类
     * 
     * redissonClient可以使用redisson-spring-boot-starter默认提供的
	 */
	@Autowired
	private RedissonClient redissonClient;
	public void decrStockRedisson() throws InterruptedException {
		
		RLock lock = redissonClient.getLock("lockKey");
		boolean isLock = false;
		
		try {
			isLock = lock.tryLock(30, 60, TimeUnit.SECONDS);
			
			if(isLock) {
				int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
				if(stock > 0) {
					int realStock = stock - 1;
					RedisUtil.valueOperations().set("stock", realStock + "");
					System.out.println("扣减成功,剩余库存:" + realStock);
				} else {
					System.out.println("扣减失败,库存不足!");
				}
			}
		} finally {
			lock.unlock();
		}
	}
}

你可能感兴趣的:(Redis)