minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
existirSpringBoot usa RedisTemplate e StringRedisTemplate para operar Redis, apresentamos o RedisTemplate e como o SpringBoot opera o Redis por meio de RedisTemplate e StringRedisTemplate.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
Então, como o SrpingBoot pode operar o Redis por meio do RedisTemplate, por que o Redisson aparece novamente?Documentação chinesa do Rddisson
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
A partir da versão spring-boot 2.x, spring-boot-data-redis usa o cliente Lettuce para operar dados por padrão.
SpringBoot2之后,默认就采用了lettuce。
É um cliente Redis avançado, uma camada de comunicação orientada a eventos baseada na estrutura Netty para sincronização segura de threads, uso assíncrono e reativo, com suporte a clusters, Sentinel, pipelines e codificadores.
A API do Lettuce é thread-safe e pode operar uma única conexão do Lettuce para concluir várias operações. A instância de conexão (StatefulRedisConnection) pode ser acessada simultaneamente entre vários threads.
Uma camada de comunicação orientada a eventos baseada na estrutura Netty, o método é assíncrono, a API é thread-safe e pode operar uma única conexão Redisson para concluir várias operações.
Ele implementa uma estrutura de dados Java distribuída e escalonável, não oferece suporte a operações de string e não oferece suporte a recursos do Redis, como classificação, transações, pipelines e partições.
Fornece muitos serviços de operação relacionados distribuídos, como bloqueios distribuídos, coleções distribuídas e pode oferecer suporte a filas de atraso por meio do Redis.
Resumir: Priorize o uso do Lettuce, que requer recursos distribuídos avançados, como bloqueios distribuídos e coleções distribuídas, para combinar com ele.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
Perceber: Depois de introduzir esta dependência, não há necessidade de introduzi-la novamente
spring-boot-starter-data-redis
,Queredisson-spring-boot-starter
Foi introduzido internamente e exclui os clientes Luttuce e Jedis da Redis. Portanto, a configuração de Luttuce e Jedis em application.yaml não terá efeito.
Ao usar o Redisson em um projeto, geralmente usamos o RedissonClient para operações de dados. No entanto, alguns amigos podem achar o RedissonClient inconveniente para operar ou preferir usar o RedisTemplate para operações. Categoria RedisTemplate.referir-seSpringBoot usa RedisTemplate e StringRedisTemplate para operar Redis。
Verificou-se que após a introdução do Redisson no projeto, a fábrica de conexão usada na parte inferior do RedisTemplate também era Redisson.
Adicione informações de configuração do redis em application.yaml.
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
}
}
}
Alunos com alguma experiência pensam em redis quando mencionam o uso de bloqueios distribuídos. Então, como o redis implementa bloqueios distribuídos?
O objetivo essencial dos bloqueios distribuídos é ocupar um caroço no Redis (simplificando, é o princípio da ocupação de caroços por cenouras. Quando outros processos também querem ocupar o caroço, eles descobrem que já existem rabanetes grandes no caroço). você tem que desistir ou tentar novamente mais tarde.
1. Use o comando setNx
A descrição detalhada deste comando é (definir se não existir). Se a chave especificada não existir, ela será definida (ocupando o poço com sucesso. Após a conclusão da execução do negócio, chame o comando del para excluir a chave (liberar). o pit). por exemplo:
# set 锁名 值
setnx distribution-lock locked
// dosoming
del distribution-lock
Mas há um problema com este comando. Se houver um problema na lógica de execução, a instrução del pode não ser executada e o bloqueio se tornará um impasse.
Talvez alguns amigos tenham pensado cuidadosamente que podemos definir outro prazo de validade para esta chave. por exemplo:
setnx distribution-lock locked
expire distribution-lock 10
// dosoming
del distribution-batch
Mesmo depois de fazer isso, ainda há problemas com a lógica, como setnx e expire são dois comandos, se o servidor redis travar entre setnx e expire, expire não será executado e a configuração do tempo de expiração falhará e o bloqueio irá. ainda será definido. Isso se tornará um impasse.
A causa raiz é que os dois comandos setnx e expire não são comandos atômicos.
E as coisas do redis não podem resolver o problema de setnx e expirar, porque expirar depende do resultado da execução de setnx. Se setnx não tiver sucesso, expirar não deve ser executado. As coisas não podem ser julgadas de outra forma, então o método setnx+expire para implementar bloqueios distribuídos não é uma solução ideal.
2. Use o comando setNx Ex
O problema de setNx+expire foi mencionado acima. Para resolver este problema, os funcionários do Redis introduziram os parâmetros estendidos do comando set na versão 2.8, para que os comandos setnx e expire possam ser executados juntos. por exemplo:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
Falando logicamente, setNx Ex já é uma solução ideal e não fará com que o bloqueio distribuído se torne um impasse.
Mas ainda podem surgir problemas durante o nosso desenvolvimento.
Como definimos um prazo de expiração para esse bloqueio no início, e se o tempo de execução de nossa lógica de negócios exceder o prazo de expiração definido? Haverá uma situação em que um thread não concluiu a execução e o segundo thread poderá conter o bloqueio distribuído.
Portanto, se você usar a combinação setNx Ex, deverá garantir que o tempo limite do seu bloqueio seja maior que o tempo de execução do negócio após o bloqueio.
3. Use script lua + mecanismo de extensão automática watch dog
Posso encontrar muitas dessas soluções online, por isso não entrarei em detalhes aqui.
Os comandos setNx e setNx Ex apresentados acima são comandos nativos fornecidos pelo servidor Redis e há mais ou menos problemas. Para resolver o problema de que a lógica de negócios do comando setNx Ex é maior que o tempo limite de bloqueio, Redisson fornece interno. Um watchdog é instalado para monitorar o bloqueio. Sua função é estender continuamente o período de validade do bloqueio antes que a instância Redisson seja fechada. Por padrão, o tempo limite do bloqueio de verificação do watchdog é de 30 segundos (ou seja, a renovação é de 30 segundos). Também pode ser especificado separadamente modificando Config.lockWatchdogTimeout. O tempo de expiração inicial do bloqueio também é de 30 segundos por padrão.
// 加锁以后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();
}
}
Enfrentamos o problema de precisar limitar o fluxo atual de interfaces ou lógica de negócios sob alta simultaneidade. Podemos usar a implementação RateLimiter baseada em Guaua. Na verdade, Redisssion também possui uma função de limitação de corrente semelhante.
RateLimiter é chamado de limitação de corrente do token bucket. Este tipo de limitação de corrente consiste primeiro em definir um token bucket, especificar quantos tokens são gerados dentro de um determinado período de tempo e obter o número especificado de tokens do token bucket cada vez que ele for acessado. Se você obtiver sucesso, será definido como acesso válido.