Springboot 2.0.x 结合Redis 分布式锁

1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.properties增加以下配置

## redis配置
spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.password=slo
2.加入配置类
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * <p>redis缓存配置</p>
 */
@Configuration
@EnableCaching
public class RedisCachedConfig extends CachingConfigurerSupport {
	//过期时间1天
//	private Duration timeToLive = Duration.ofMinutes(1);
	private Duration timeToLive = Duration.ofHours(1);
	@Bean
	public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
		//默认1
		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
				.entryTtl(this.timeToLive)
				.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
				.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
				.disableCachingNullValues();
		RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
				.cacheDefaults(config)
				.transactionAware()
				.build();
		return redisCacheManager;
	}
	@Bean(name = "redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		redisTemplate.setKeySerializer(keySerializer());
		redisTemplate.setHashKeySerializer(keySerializer());
		redisTemplate.setValueSerializer(valueSerializer());
		redisTemplate.setHashValueSerializer(valueSerializer());
		return redisTemplate;
	}
	private RedisSerializer<String> keySerializer() {
		return new StringRedisSerializer();
	}
	private RedisSerializer<Object> valueSerializer() {
		return new GenericJackson2JsonRedisSerializer();
	}
}
3.编写Lock工具类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.springframework.data.redis.core.RedisTemplate;

import lombok.Data;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Data
@Component
public class RedisLock {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Resource
	private RedisTemplate<String, Object> redisTemplate;
	/**
	 * 重试时间
	 */
	private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
	/**
	 * 锁的后缀
	 */
	private static final String LOCK_SUFFIX = "_redis_lock";
	/**
	 * 锁的key
	 */
	private String lockKey;

	/**
	 * 锁超时时间,防止线程在入锁以后,防止阻塞后面的线程无法获取锁
	 */
	private int expireMsecs = 2 * 60 * 1000;
	/**
	 * 线程获取锁的等待时间
	 */
	private int timeoutMsecs = 10 * 1000;
	/**
	 * 是否锁定标志
	 */
	private volatile boolean locked = false;


	/**
	 * 封装和jedis方法
	 *
	 * @param key
	 * @return
	 */
	private String get(final String key) {
		Object obj = redisTemplate.opsForValue().get(key);
		return obj != null ? obj.toString() : null;
	}

	/**
	 * 封装和jedis方法
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	private boolean setNX(final String key, final String value) {
		return redisTemplate.opsForValue().setIfAbsent(key, value);
	}

	/**
	 * 封装和jedis方法
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	private String getSet(final String key, final String value) {
		Object obj = redisTemplate.opsForValue().getAndSet(key, value);
		return obj != null ? (String) obj : null;
	}


	//=================================公共方法区============================//

	/**
	 * 初始化参数
	 * 默认锁超时时间 2分钟
	 * 默认获取锁等待时间 10秒
	 *
	 * @param lockKey 锁的key
	 */
	public void init(String lockKey) {
		this.lockKey = lockKey + LOCK_SUFFIX;
	}

	/**
	 * 初始化参数
	 *
	 * @param lockKey      锁的key
	 * @param timeoutMsecs 获取锁的超时时间 单位毫秒 eg:2 * 60 * 1000
	 */
	public void init(String lockKey, int timeoutMsecs) {
		this.lockKey = lockKey + LOCK_SUFFIX;
		this.timeoutMsecs = timeoutMsecs;
	}

	/**
	 * 初始化参数
	 *
	 * @param lockKey      锁的key
	 * @param timeoutMsecs 获取锁的超时时间
	 * @param expireMsecs  锁的有效期 单位毫秒 eg: 10 * 1000
	 */
	public void init(String lockKey, int timeoutMsecs, int expireMsecs) {
		this.lockKey = lockKey + LOCK_SUFFIX;
		this.timeoutMsecs = timeoutMsecs;
		this.expireMsecs = expireMsecs;
	}

	public String getLockKey() {
		return lockKey;
	}

	/**
	 * 获取锁
	 *
	 * @return 获取锁成功返回ture,超时返回false
	 * @throws InterruptedException
	 */
	public synchronized boolean lock() throws InterruptedException {
		int timeout = timeoutMsecs;
		logger.info("准备获取锁{},获取锁超时时间为{}",this.getLockKey(),timeout);
		while (timeout >= 0) {
			long expires = System.currentTimeMillis() + expireMsecs + 1;
			String expiresStr = String.valueOf(expires); // 锁到期时间
			if (this.setNX(lockKey, expiresStr)) {
				locked = true;
				logger.info("成功获取锁{},锁超时时间为{}",this.getLockKey(),expires);
				return true;
			}
			// redis里key的时间
			String currentValue = this.get(lockKey);
			// 判断锁是否已经过期,过期则重新设置并获取
			if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
				// 设置锁并返回旧值
				String oldValue = this.getSet(lockKey, expiresStr);
				// 比较锁的时间,如果不一致则可能是其他锁已经修改了值并获取
				if (oldValue != null && oldValue.equals(currentValue)) {
					logger.info("成功获取锁{},锁超时时间为{}",this.getLockKey(),expires);
					locked = true;
					return true;
				}
			}
			timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
			// 延时
			Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
		}
		logger.warn("获取锁{}失败了!",this.getLockKey());
		return false;
	}

	/**
	 * 释放获取到的锁
	 */
	public void unlock() {
		logger.info("准备释放锁{}",this.getLockKey());
		if (locked) {
			redisTemplate.delete(lockKey);
			logger.info("成功释放锁{}",this.getLockKey());
			locked = false;
		}
	}

}
4.redis缓存数据测试

具体类无需关心,只要看注解如何使用即可。

//REDIS缓存调用示例

@Deprecated
@Service
@CacheConfig(cacheNames = "user") //指定cache的名字,这里指定了 caheNames,下面的方法的注解里就可以不用指定 value 属性了
public class UserServiceImpl {

	private Map<Long, User> userMap = new HashMap<Long, User>();

	public UserServiceImpl() {
		User u1=new User();
		u1.setId(1L);
		u1.setName("1111");
		u1.setPassword("11223434");
		User u2=new User();
		u2.setId(2L);
		u2.setName("1111");
		u2.setPassword("11223434");
		User u3=new User();
		u3.setId(3L);
		u3.setName("1111");
		u3.setPassword("11223434");

		userMap.put(1L,u1);
		userMap.put(2L,u2);
		userMap.put(3L,u3);
	}

	@Cacheable()
	@Override
	public List<User> list() {
		System.out.println("querying list.....");
		User[] users = new User[userMap.size()];
		this.userMap.values().toArray(users);
		return Arrays.asList(users);
	}

	@Cacheable(key = "'user:'.concat(#id.toString())")
	@Override
	public User findUserById(Long id) {
		System.out.println("没有调用缓存");
		return userMap.get(id);
	}


	@Cacheable(key = "'user:'.concat(#user.id)")
	@Override
	public void update(User user) {
		System.out.println("没有调用缓存");
		userMap.put(user.getId(), user);
	}

	// 清空cache
	@CacheEvict(key = "'user:'.concat(#id.toString())")
	@Override
	public void remove(Long id) {
		System.out.println("没有调用缓存");
		userMap.remove(id);
	}

	@CacheEvict(key = "'user:'.concat(#id.toString())")
	@Override
	public User upuser(Long id) {
		System.out.println("没有调用缓存");
		User d = userMap.get(id);
		d.setName("0000000000000000000000000000000000000000");
		return d;
	}

	@CachePut(key = "'user:'.concat(#user.id)")
	@Override
	public User saveUser(User user) {
		System.out.println("没有调用缓存");
		userMap.put(user.getId(), user);
		return user;
	}

}
5.Lock嵌入添加示例以及测试

以下无需关注具体业务,只需看lock如何嵌入使用

@Transactional
@Service
public class TestSrvImpl implements TestSrv {
	Logger log = LoggerFactory.getLogger(this.getClass());

	@Resource
	private RedisLock redisLock; //这里自动注入redisLock类

	@Resource
	private UserInfoRepository userInfoRepository;

	@LogAnnotation(moduleName = "测试服务", option = "测试方法")
	public ResponseVO testService(RequestVO requestVO) {
		//获取支付配置相关信息
		ResponseVO response = new ResponseVO(requestVO);
		try {
			log.info("准备获取锁...");
			redisLock.init("Test",20*1000);
			if (redisLock.lock()) {
				log.info("成功获取锁,入参:{}", JSON.toJSONString(requestVO));
				Map<String, Object> resultMap = new HashMap<>();
				resultMap.put("no", "123456");
				resultMap.put("name", "test");
				HandlerStateDTO handlerStateDTO = new HandlerStateDTO();
				response.setBizObj(resultMap);
				if (!HandlerType.isSuccessful(handlerStateDTO)) {
					response.setRetInfo(handlerStateDTO);
				}
				log.info("开始进入睡眠模式");
				Thread.sleep(5000);
				log.info("睡眠模式解除");
				redisLock.unlock();
				log.info("释放锁");
			}
		} catch (Exception e) {
			e.printStackTrace();
			//异常释放锁
			redisLock.unlock();
			response.setRetInfo(HandlerType.UNKNOWN);
			log.error("失败了,{}" + e.toString());
		}
		log.info("结束返回");
		return response;
	}

本样例SDK模拟多线程测试用例如下,自己根据自己的服务调用方式模拟调用即可。本项目采用sdk请求http接口进行调用:

/**
	 * 测试
	 */
	@Test
	public void test() {
		logger.info("开始请求");
		SdkClient sdkClient = new SdkClient(web_pay_url, appId, appSecret, signType, encryptType);
		try {
			Map<String,Object> param = new HashMap<>();
			param.put("name","testUser");
			param.put("id","A000001");
//			// 发起交易
			ResponseVO responseParams = sdkClient.unifyCall("test.service.forfun",version,param);
			logger.info("返回的结果为:"+JSON.toJSONString(responseParams));
			logger.info("其中业务返回为:"+JSON.toJSONString(responseParams.getBizObj()));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
    @Test
	public void test3() throws InterruptedException {
		// 同一时刻最大的并发线程的个数  即并发数
		final int concurrentThreadNum = 2;

		//总共多少线程
		final int countThreadNum = 2;

		ExecutorService executorService = Executors.newCachedThreadPool();
		CountDownLatch countDownLatch = new CountDownLatch(countThreadNum);
		Semaphore semaphore = new Semaphore(concurrentThreadNum);
		for (int i = 0; i< countThreadNum; i++) {
			executorService.execute(()->{
				try {
					semaphore.acquire();
					test();
					semaphore.release();
				} catch (InterruptedException e) {
					logger.error("exception", e);
				}
				countDownLatch.countDown();
			});
		}
		countDownLatch.await();
		executorService.shutdown();
		logger.info("请求完成");
	}
6.基础框架项目

内置spring boot 和接口加解密sdk,redis锁等,下载即可开发具体业务

码云:BaseFramework

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值