내 연락처 정보
우편메소피아@프로톤메일.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
존재하다SpringBoot는 RedisTemplate과 StringRedisTemplate을 사용하여 Redis를 작동합니다., RedisTemplate을 소개하고 SpringBoot가 RedisTemplate 및 StringRedisTemplate을 통해 Redis를 작동하는 방법을 소개했습니다.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
그렇다면 SrpingBoot는 RedisTemplate을 통해 Redis를 동작시킬 수 있는데 왜 Redisson이 다시 나타나는 걸까요?Rddisson 중국어 문서
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
spring-boot 2.x 버전부터 spring-boot-data-redis는 기본적으로 Lettuce 클라이언트를 사용하여 데이터를 운용합니다.
SpringBoot2之后,默认就采用了lettuce。
스레드로부터 안전한 동기화, 비동기 및 반응적 사용, 지원 클러스터, Sentinel, 파이프라인 및 인코더를 위한 Netty 프레임워크를 기반으로 하는 이벤트 기반 통신 계층인 고급 Redis 클라이언트입니다.
Lettuce의 API는 스레드로부터 안전하며 단일 Lettuce 연결을 작동하여 다양한 작업을 완료할 수 있습니다. 연결 인스턴스(StatefulRedisConnection)는 여러 스레드에서 동시에 액세스할 수 있습니다.
Netty 프레임워크를 기반으로 하는 이벤트 중심 통신 계층으로, 메서드는 비동기식이고 API는 스레드로부터 안전하며 단일 Redisson 연결을 작동하여 다양한 작업을 완료할 수 있습니다.
분산되고 확장 가능한 Java 데이터 구조를 구현하고 문자열 작업을 지원하지 않으며 정렬, 트랜잭션, 파이프라인 및 파티션과 같은 Redis 기능을 지원하지 않습니다.
분산 잠금, 분산 컬렉션 등 다양한 분산 관련 운영 서비스를 제공하며 Redis를 통해 지연 대기열을 지원할 수 있습니다.
요약하다: 분산 잠금, 분산 컬렉션 등 고급 분산 기능이 필요한 Lettuce를 우선적으로 사용하여 결합하세요.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
알아채다: 이 종속성을 도입한 후에는 다시 도입할 필요가 없습니다.
spring-boot-starter-data-redis
,저것redisson-spring-boot-starter
내부적으로 도입되었으며 Redis의 Luttuce 및 Jedis 클라이언트는 제외됩니다. 따라서 application.yaml의 Luttuce 및 Jedis 구성은 적용되지 않습니다.
프로젝트에서 Redisson을 사용할 때 일반적으로 데이터 작업에 RedissonClient를 사용합니다. 그러나 일부 친구는 작업에 RedissonClient를 사용하는 것을 선호하거나 작업에 RedisTemplate을 사용하는 것을 선호할 수 있습니다. 실제로 두 가지 구성을 함께 정의하면 됩니다. RedisTemplate 카테고리입니다.인용하다SpringBoot는 RedisTemplate과 StringRedisTemplate을 사용하여 Redis를 작동합니다.。
프로젝트에서 Redisson을 도입한 이후 RedisTemplate 하단에 사용된 연결 팩토리도 Redisson인 것으로 확인되었습니다.
application.yaml에 Redis 구성 정보를 추가합니다.
spring:
data:
redis:
mode: master
# 地址
host: 30.46.34.190
# 端口,默认为6379
port: 6379
# 密码,没有不填
password: ''
# 几号库
database: 1
sentinel:
master: master
nodes: 30.46.34.190
cluster:
nodes: 30.46.34.190
lettuce:
pool:
# 连接池的最大数据库连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 50
# 连接池中的最小空闲连接
min-idle: 8
@Configuration
@EnableConfigurationProperties({RedisProperties.class})
public class RedissonConfig {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
@Value("${spring.data.redis.mode}")
private String redisMode;
private final RedisProperties redisProperties;
public RedissonConfig(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
/**
* 逻辑参考 RedissonAutoConfiguration#redisson()
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson(@Autowired(required = false) List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers) throws IOException {
Config config = new Config();
config.setCheckLockSyncedSlaves(false);
int max = redisProperties.getLettuce().getPool().getMaxActive();
int min = redisProperties.getLettuce().getPool().getMinIdle();
switch (redisMode) {
case "master": {
SingleServerConfig singleConfig = config.useSingleServer()
.setAddress(REDIS_PROTOCOL_PREFIX + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
singleConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
singleConfig.setConnectionPoolSize(max);
singleConfig.setConnectionMinimumIdleSize(min);
}
break;
case "sentinel": {
String[] nodes = convert(redisProperties.getSentinel().getNodes());
SentinelServersConfig sentinelConfig = config.useSentinelServers()
.setMasterName(redisProperties.getSentinel().getMaster())
.addSentinelAddress(nodes)
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
sentinelConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
sentinelConfig.setMasterConnectionPoolSize(max);
sentinelConfig.setMasterConnectionMinimumIdleSize(min);
sentinelConfig.setSlaveConnectionPoolSize(max);
sentinelConfig.setSlaveConnectionMinimumIdleSize(min);
}
break;
case "cluster": {
String[] nodes = convert(redisProperties.getCluster().getNodes());
ClusterServersConfig clusterConfig = config.useClusterServers()
.addNodeAddress(nodes)
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
clusterConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
clusterConfig.setMasterConnectionMinimumIdleSize(min);
clusterConfig.setMasterConnectionPoolSize(max);
clusterConfig.setSlaveConnectionMinimumIdleSize(min);
clusterConfig.setSlaveConnectionPoolSize(max);
}
break;
default:
throw new IllegalArgumentException("无效的redis mode配置");
}
if (redissonAutoConfigurationCustomizers != null) {
for (RedissonAutoConfigurationCustomizer customizer : redissonAutoConfigurationCustomizers) {
customizer.customize(config);
}
}
return Redisson.create(config);
}
private String[] convert(List<String> nodesObject) {
List<String> nodes = new ArrayList<String>(nodesObject.size());
for (String node : nodesObject) {
if (!node.startsWith(REDIS_PROTOCOL_PREFIX)) {
nodes.add(REDIS_PROTOCOL_PREFIX + node);
} else {
nodes.add(node);
}
}
return nodes.toArray(new String[0]);
}
}
@Component
public class RedissonService {
@Resource
protected RedissonClient redissonClient;
public void redissonExists(String key){
RBucket<String> rBucketValue = redissonClient.getBucket(key, StringCodec.INSTANCE);
if (rBucketValue.isExists()){
String value = rBucketValue.get();
// doSomething
} else {
// doElseSomething
}
}
}
경험이 있는 학생들은 분산 잠금 사용을 언급할 때 Redis를 생각합니다. 그러면 Redis는 분산 잠금을 어떻게 구현합니까?
분산 잠금의 핵심 목표는 Redis에서 구덩이를 점유하는 것입니다(간단히 말하면 당근이 구덩이를 점유하는 원리입니다). 포기하거나 나중에 다시 시도해야 합니다.
1. setNx 명령을 사용하십시오
이 명령에 대한 자세한 설명은 (존재하지 않는 경우 설정)입니다. 지정된 키가 존재하지 않는 경우 설정됩니다(피트 점유 성공). 해당 키를 삭제하려면 del 명령을 호출하십시오. 구덩이). 예를 들어:
# set 锁名 值
setnx distribution-lock locked
// dosoming
del distribution-lock
하지만 이 명령에는 문제가 있습니다. 실행 논리에 문제가 있으면 del 명령이 실행되지 않고 잠금이 교착 상태가 됩니다.
어쩌면 일부 친구들은 이 키에 대해 또 다른 만료 시간을 설정할 수 있다고 신중하게 생각했을 수도 있습니다. 예를 들어:
setnx distribution-lock locked
expire distribution-lock 10
// dosoming
del distribution-batch
이렇게 한 후에도 여전히 로직에 문제가 있습니다. setnx와 만료가 두 가지 명령이기 때문에 redis 서버가 setnx와 만료 사이에 끊기면 만료가 실행되지 않고 만료 시간 설정이 실패하며 잠금이 해제됩니다. 여전히 교착 상태가 됩니다.
근본 원인은 setnx 및 만료 두 명령이 원자성 명령이 아니기 때문입니다.
그리고 Redis에서는 setnx 및 만료 문제를 해결할 수 없습니다. 만료는 setnx의 실행 결과에 따라 달라지기 때문입니다. setnx가 성공하지 못하면 만료가 실행되어서는 안 됩니다. 그렇지 않으면 상황을 판단할 수 없으므로 분산 잠금을 구현하는 setnx+expire 방법은 최적의 솔루션이 아닙니다.
2. setNx Ex 명령을 사용하십시오.
setNx+expire 문제는 위에서 언급한 바 있습니다. 이 문제를 해결하기 위해 Redis 관계자는 버전 2.8에서 set 명령의 확장 매개변수를 도입하여 setnx 및 만료 명령을 함께 실행할 수 있도록 했습니다. 예를 들어:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
논리적으로 말하면, setNx Ex는 이미 최적의 솔루션이며 분산 잠금이 교착 상태가 되는 원인이 되지 않습니다.
하지만 개발 과정에서 여전히 문제가 발생할 수 있습니다. 왜일까요?
처음에 이 잠금에 대한 만료 시간을 설정했기 때문에 비즈니스 로직의 실행 시간이 설정된 만료 시간을 초과하면 어떻게 될까요? 한 스레드가 실행을 완료하지 않았고 두 번째 스레드가 분산 잠금을 보유할 수 있는 상황이 발생합니다.
따라서 setNx Ex 조합을 사용하는 경우 잠금 시간 초과가 잠금 후 비즈니스 실행 시간보다 길어야 합니다.
3. lua 스크립트 + 감시 자동 확장 메커니즘 사용
이 솔루션은 온라인에서 많이 찾을 수 있으므로 여기서는 자세히 설명하지 않겠습니다.
위에 소개된 setNx 및 setNx Ex 명령은 Redis 서버에서 제공하는 기본 명령으로 setNx Ex 명령의 비즈니스 로직이 잠금 시간 제한보다 크다는 문제를 해결하기 위해 Redisson에서는 내부적으로 제공합니다. 잠금을 모니터링하기 위해 감시 장치가 설치됩니다. 그 기능은 Redisson 인스턴스가 닫히기 전에 잠금의 유효 기간을 지속적으로 연장하는 것입니다. 기본적으로 Watchdog 확인 잠금 시간 제한은 30초입니다(즉, 갱신은 30초입니다). 또한 Config.lockWatchdogTimeout을 수정하여 별도로 지정할 수도 있습니다. 잠금의 초기 만료 시간도 기본적으로 30초입니다.
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
@Resource
RedissonClient redissonClient;
@GetMapping("/testDistributionLock")
public BaseResponse<String> testRedission(){
RLock lock = redissonClient.getLock("redis:distributionLock");
try {
boolean locked = lock.tryLock(10, 3, TimeUnit.SECONDS);
if(locked){
log.info("获取锁成功");
Thread.sleep(100);
return ResultUtils.success("ok" );
}else{
log.error("获取锁失败");
return ResultUtils.error(ErrorCode.SYSTEM_ERROR);
}
} catch (InterruptedException e) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"出异常了");
} finally {
lock.unlock();
}
}
우리는 높은 동시성에서 현재 인터페이스 흐름이나 비즈니스 로직을 제한해야 하는 문제에 직면해 있습니다. 실제로 Redisssion에도 유사한 전류 제한 기능이 있습니다.
RateLimiter를 토큰 버킷 전류 제한이라고 합니다. 이러한 유형의 전류 제한은 먼저 토큰 버킷을 정의하고, 특정 기간 내에 생성되는 토큰 수를 지정하고, 액세스할 때마다 토큰 버킷에서 지정된 수의 토큰을 얻는 것입니다. 을 획득한 경우 성공하면 유효한 액세스로 설정됩니다.