Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
existirSpringBoot usa RedisTemplate y StringRedisTemplate para operar Redis, presentamos RedisTemplate y cómo SpringBoot opera Redis a través de RedisTemplate y StringRedisTemplate.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
Entonces, dado que SrpingBoot puede operar Redis a través de RedisTemplate, ¿por qué aparece Redisson nuevamente?Documentación china de Rddisson
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
A partir de la versión spring-boot 2.x, spring-boot-data-redis utiliza el cliente Lettuce para operar los datos de forma predeterminada.
SpringBoot2之后,默认就采用了lettuce。
Es un cliente Redis avanzado, una capa de comunicación basada en eventos basada en el marco Netty para sincronización segura para subprocesos, uso asincrónico y reactivo, compatible con clústeres, Sentinel, canalizaciones y codificadores.
La API de Lettuce es segura para subprocesos y puede operar una única conexión de Lettuce para completar varias operaciones. Se puede acceder a la instancia de conexión (StatefulRedisConnection) simultáneamente entre varios subprocesos.
Una capa de comunicación basada en eventos basada en el marco Netty, el método es asincrónico, la API es segura para subprocesos y puede operar una única conexión de Redisson para completar varias operaciones.
Implementa una estructura de datos Java distribuida y escalable, no admite operaciones de cadenas y no admite funciones de Redis como clasificación, transacciones, canalizaciones y particiones.
Proporciona muchos servicios de operaciones relacionadas distribuidas, como bloqueos distribuidos, colecciones distribuidas y puede admitir colas de retraso a través de Redis.
Resumir: Priorice el uso de Lettuce, que requiere funciones distribuidas avanzadas, como bloqueos distribuidos y colecciones distribuidas, para combinar con Redisson.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
Aviso: Después de introducir esta dependencia, no es necesario volver a introducirla
spring-boot-starter-data-redis
,Esoredisson-spring-boot-starter
Se introdujo internamente y excluye a los clientes Luttuce y Jedis de Redis. Por lo tanto, la configuración de Luttuce y Jedis en application.yaml no tendrá efecto.
Cuando usamos Redisson en un proyecto, generalmente usamos RedissonClient para operaciones de datos. Sin embargo, algunos amigos pueden encontrar inconvenientes para operar RedissonClient o prefieren usar RedisTemplate para operaciones. Categoría RedisTemplate.Referirse aSpringBoot usa RedisTemplate y StringRedisTemplate para operar Redis。
Se descubrió que después de que el proyecto introdujo Redisson, la fábrica de conexiones utilizada en la parte inferior de RedisTemplate también era Redisson.
Agregue información de configuración de Redis en 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
}
}
}
Los estudiantes con cierta experiencia piensan en Redis cuando mencionan el uso de bloqueos distribuidos. Entonces, ¿cómo implementa Redis los bloqueos distribuidos?
El objetivo esencial de las cerraduras distribuidas es ocupar un hoyo en Redis (en pocas palabras, es el principio de que las zanahorias ocupan el hoyo). Cuando otros procesos también quieren ocupar el hoyo, descubren que ya hay rábanos grandes en el hoyo. tienes que rendirte o volver a intentarlo más tarde.
1. Utilice el comando setNx
La descripción detallada de este comando es (establecer si no existe). Si la clave especificada no existe, se configurará (ocupando con éxito el pozo). Una vez completada la ejecución comercial, llame al comando del para eliminar la clave (liberar). el hoyo). Por ejemplo:
# set 锁名 值
setnx distribution-lock locked
// dosoming
del distribution-lock
Pero hay un problema con este comando. Si hay un problema en la lógica de ejecución, es posible que la instrucción del no se ejecute y el bloqueo se convertirá en un punto muerto.
Quizás algunos amigos hayan pensado cuidadosamente que podemos establecer otro tiempo de vencimiento para esta clave. Por ejemplo:
setnx distribution-lock locked
expire distribution-lock 10
// dosoming
del distribution-batch
Incluso después de hacer esto, todavía hay problemas con la lógica. Dado que setnx y expire son dos comandos, si el servidor redis cuelga entre setnx y expire, expire no se ejecutará, por lo que la configuración del tiempo de vencimiento falla y el bloqueo aún está activo. Se convertirá en un punto muerto.
La causa principal es que los dos comandos setnx y expire no son comandos atómicos.
Y las cosas de Redis no pueden resolver el problema de setnx y expire, porque expire depende del resultado de la ejecución de setnx. Si setnx no tiene éxito, expire no debe ejecutarse. Las cosas no se pueden juzgar de otra manera, por lo que el método setnx+expire para implementar bloqueos distribuidos no es una solución óptima.
2. Utilice el comando setNx Ex
El problema de setNx+expire se mencionó anteriormente. Para resolver este problema, los funcionarios de Redis introdujeron los parámetros extendidos del comando set en la versión 2.8, para que los comandos setnx y expire se puedan ejecutar juntos. Por ejemplo:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
Lógicamente hablando, setNx Ex ya es una solución óptima y no provocará que el bloqueo distribuido se convierta en un punto muerto.
Pero todavía pueden surgir problemas durante nuestro desarrollo.
Dado que establecimos un tiempo de vencimiento para este bloqueo al principio, ¿qué pasa si el tiempo de ejecución de nuestra lógica de negocios excede el tiempo de vencimiento establecido? Habrá una situación en la que un subproceso no haya completado la ejecución y el segundo subproceso pueda mantener el bloqueo distribuido.
Por lo tanto, si utiliza la combinación setNx Ex, debe asegurarse de que el tiempo de espera de su bloqueo sea mayor que el tiempo de ejecución empresarial después del bloqueo.
3. Utilice el mecanismo de extensión automática lua script + watch dog
Puedo encontrar muchas de estas soluciones en línea, por lo que no entraré en detalles aquí.
Los comandos setNx y setNx Ex presentados anteriormente son comandos nativos proporcionados por el servidor Redis y existen más o menos problemas. Para resolver el problema de que la lógica empresarial del comando setNx Ex es mayor que el tiempo de espera de bloqueo, Redisson proporciona interna. Se instala un perro guardián para monitorear el bloqueo. Su función es extender continuamente el período de validez del bloqueo antes de que se cierre la instancia de Redisson. De forma predeterminada, el tiempo de espera del bloqueo de verificación del mecanismo de vigilancia es de 30 segundos (es decir, la renovación es de 30 segundos). También se puede especificar por separado modificando Config.lockWatchdogTimeout. El tiempo de vencimiento inicial del bloqueo también es de 30 segundos.
// 加锁以后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();
}
}
Nos enfrentamos al problema de la necesidad de limitar el flujo actual de interfaces o lógica de negocios en condiciones de alta concurrencia. Podemos usar la implementación de RateLimiter basada en Guaua. De hecho, Redisssion también tiene una función de limitación de corriente similar.
RateLimiter se llama limitación de corriente del depósito de tokens. Este tipo de limitación actual consiste en definir primero un depósito de tokens, especificar cuántos tokens se generan dentro de un cierto período de tiempo y obtener la cantidad especificada de tokens del depósito de tokens cada vez que accede a él. Si obtiene Si tiene éxito, se establece como acceso válido.