在单点的程序中我们可以使用synchronized 和 lock来实现,但是在集群环境下。因为程序所在空间不同。已经不能使用这两个来实现锁了。
1. 如何在集群架构中像所有服务共享一个锁。
使用redis分布式锁来实现。 setnx。
将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,返回 0。 不存在 设置值并返回1
springboot 1.5.10.RELEASE
1. 项目中增加redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. application.yml 增加redis配置信息
#redis配置数据
redis:
hostName: 123.206.20.217
port: 6379
database: 0
password:
pool:
maxTotal: 1000
maxWaitMillis: 1000
minEvictableIdleTimeMillis: 300000
numTestsPerEvictionRun: 1024
timeBetweenEvictionRunsMillis: 30000
testOnBorrow: true
testWhileIdle: true
timeout: 5000
3. 创建redis配置实例
package com.admin.system.config.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class RedisConfig {
@Bean(name = "jedisPoolConfig")
@ConfigurationProperties(prefix = "redis.pool")
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig config = new JedisPoolConfig();
return config;
}
@Bean(name = "jedisConnectionFactory")
@ConfigurationProperties(prefix = "redis")
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
JedisConnectionFactory factory = new JedisConnectionFactory(jedisPoolConfig);
return factory;
}
@Bean(name = "redisTemplate" )
public RedisTemplate<?, ?> getRedisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate<?, ?> redisTemplate = new StringRedisTemplate(jedisConnectionFactory);
/**
* key序列化类型
*/
redisTemplate.setKeySerializer(new StringRedisSerializer());
/**
* value序列化类型
*/
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
4. 示例
package com.admin.system.modular.sys.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class RedisImpl {
@Autowired
public RedisTemplate redisTemplate;
@Scheduled(cron = "* * * * * ?")
public void test() {
// 模仿多服务
System.out.println("======================================================");
new Thread(() -> redisLock("线程1")).start();
new Thread(() -> redisLock("线程2")).start();
new Thread(() -> redisLock("线程3")).start();
new Thread(() -> redisLock("线程4")).start();
}
public void redisLock(String threadName) {
boolean bol = getLock("aa");
if (bol) {
try {
// 执行业务
System.out.println(threadName + "执行业务");
} finally {
unlock("aa");
}
} else {
// 并发执行了
System.out.println("有服务器正在执行此次无需再次执行");
}
}
// 获得锁 ture 获得锁 false 没有获得锁
public Boolean getLock(String key) {
// 创建事物 防止命令执行一半
redisTemplate.multi();
redisTemplate.execute(
(RedisCallback<Boolean>) conn -> {
try {
conn.setNX(redisTemplate.getStringSerializer().serialize(key),
redisTemplate.getStringSerializer().serialize(key));
} finally {
// 关闭释放链接
conn.close();
}
return null;
});
// 防止死锁。 10秒后删除key
redisTemplate.expire(key, 10, TimeUnit.SECONDS);
List<Object> list = redisTemplate.exec();
return (Boolean) list.get(0);
}
// 释放锁
private void unlock(String key) {
redisTemplate.delete(key);
}
}
输出
======================================================
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
线程4执行业务
有服务器正在执行此次无需再次执行
======================================================
有服务器正在执行此次无需再次执行
线程2执行业务
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
======================================================
有服务器正在执行此次无需再次执行
线程3执行业务
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
======================================================
有服务器正在执行此次无需再次执行
线程2执行业务
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
======================================================
线程4执行业务
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
有服务器正在执行此次无需再次执行
springboot 2.2.9.RELEASE
yml redis配置
spring:
application:
# 服务名
name: advanced
#redis配置数据
redis:
host: 101.37.152.195
port: 6379
database: 10
password:
timeout: 5000
jedis:
pool:
max-idle: 10
max-wait:
min-idle: 0
server:
# 服务端口
port: 9001
RedisLock
package com.advanced.config.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author 刘志强
* @date 2020/8/10 11:36
* redis分布式锁
*/
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 获得锁 ture 获得锁 false 没有获得锁
*
* @param key
* @param time 过期时间
* @return
*/
public Boolean getLock(String key, Long time) {
return stringRedisTemplate.opsForValue().setIfAbsent(key, key, time, TimeUnit.SECONDS);
}
/**
* 释放锁
*
* @param key
*/
public void unLock(String key) {
stringRedisTemplate.delete(key);
}
}
使用
@GetMapping("fbs")
@ResponseBody
public String fbs() throws InterruptedException {
boolean bol = redisLock.getLock("a", 10L);
if (bol) {
System.out.println("获得锁");
Thread.sleep(5000L);
redisLock.unLock("a");
return "获得锁";
} else {
System.out.println("获取锁失败");
Thread.sleep(1000);
return fbs();
}
}
Redisson官方文档 - 8. 分布式锁和同步器