2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
existerSpringBoot utilise RedisTemplate et StringRedisTemplate pour faire fonctionner Redis, nous avons présenté RedisTemplate et comment SpringBoot exploite Redis via RedisTemplate et StringRedisTemplate.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
Donc, puisque SrpingBoot peut faire fonctionner Redis via RedisTemplate, pourquoi Redisson apparaît-il à nouveau ?Documentation chinoise Rddisson
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
À partir de la version spring-boot 2.x, spring-boot-data-redis utilise le client Lettuce pour exploiter les données par défaut.
SpringBoot2之后,默认就采用了lettuce。
Est un client Redis avancé, une couche de communication basée sur les événements basée sur le framework Netty pour une synchronisation thread-safe, une utilisation asynchrone et réactive, prenant en charge les clusters, Sentinel, les pipelines et les encodeurs.
L'API de Lettuce est thread-safe et peut exploiter une seule connexion Lettuce pour effectuer diverses opérations. L'instance de connexion (StatefulRedisConnection) est accessible simultanément entre plusieurs threads.
Couche de communication basée sur les événements basée sur le framework Netty, la méthode est asynchrone, l'API est thread-safe et peut exploiter une seule connexion Redisson pour effectuer diverses opérations.
Il implémente une structure de données Java distribuée et évolutive, ne prend pas en charge les opérations sur les chaînes et ne prend pas en charge les fonctionnalités Redis telles que le tri, les transactions, les pipelines et les partitions.
Fournit de nombreux services d'opérations associés distribués, tels que des verrous distribués, des collections distribuées, et peut prendre en charge les files d'attente différées via Redis.
Résumer: donnez la priorité à l'utilisation de Lettuce, qui nécessite des fonctionnalités distribuées avancées telles que des verrous distribués et des collections distribuées pour y être combinées.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
Avis: Après avoir introduit cette dépendance, il n'est pas nécessaire de la réintroduire
spring-boot-starter-data-redis
,Queredisson-spring-boot-starter
Il a été introduit en interne et exclut les clients Luttuce et Jedis de Redis. Par conséquent, la configuration de Luttuce et Jedis dans application.yaml ne prendra pas effet.
Lorsque nous utilisons Redisson dans un projet, nous utilisons généralement RedissonClient pour les opérations sur les données. Cependant, certains amis peuvent trouver RedissonClient peu pratique à utiliser, ou préférer utiliser RedisTemplate pour les opérations. En fait, les deux peuvent coexister. Catégorie RedisTemplate.faire référence àSpringBoot utilise RedisTemplate et StringRedisTemplate pour faire fonctionner Redis。
Il a été constaté qu'après l'introduction de Redisson par le projet, la fabrique de connexions utilisée au bas de RedisTemplate était également Redisson.
Ajoutez les informations de configuration Redis dans 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
}
}
}
Les étudiants ayant une certaine expérience pensent à Redis lorsqu'ils mentionnent l'utilisation de verrous distribués. Alors, comment Redis implémente-t-il les verrous distribués ?
L'objectif essentiel des verrous distribués est d'occuper une fosse dans Redis (en termes simples, c'est le principe des carottes occupant des fosses). Lorsque d'autres processus veulent également occuper la fosse, ils constatent qu'il y a déjà de gros radis dans la fosse. vous devez abandonner ou réessayer plus tard.
1. Utilisez la commande setNx
La description détaillée de cette commande est (définir si elle n'existe pas). Si la clé spécifiée n'existe pas, elle sera définie (occupant avec succès la fosse, appelez la commande del pour supprimer la clé (libération). la fosse). Par exemple:
# set 锁名 值
setnx distribution-lock locked
// dosoming
del distribution-lock
Mais il y a un problème avec cette commande. S'il y a un problème dans la logique d'exécution, l'instruction del risque de ne pas être exécutée et le verrou deviendra un blocage.
Peut-être que certains amis ont réfléchi à la possibilité de définir un autre délai d'expiration pour cette clé. Par exemple:
setnx distribution-lock locked
expire distribution-lock 10
// dosoming
del distribution-batch
Même après cela, il y a toujours des problèmes avec la logique. Puisque setnx et expire sont deux commandes, si le serveur Redis raccroche entre setnx et expire, expire ne sera pas exécuté, le paramètre de délai d'expiration échouera et le verrou échouera. encore être fixé. Cela deviendra une impasse.
La cause première est que les deux commandes setnx et expire ne sont pas des commandes atomiques.
Et les choses Redis ne peuvent pas résoudre le problème de setnx et expirer, car expire dépend du résultat de l'exécution de setnx. Si setnx ne réussit pas, expire ne doit pas être exécuté. Les choses ne peuvent pas être jugées autrement, donc la méthode setnx+expire pour implémenter des verrous distribués n'est pas une solution optimale.
2. Utilisez la commande setNx Ex
Le problème de setNx+expire a été mentionné ci-dessus. Afin de résoudre ce problème, les responsables de Redis ont introduit les paramètres étendus de la commande set dans la version 2.8, afin que les commandes setnx et expire puissent être exécutées ensemble. Par exemple:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
Logiquement parlant, setNx Ex est déjà une solution optimale et ne fera pas du verrou distribué une impasse.
Mais des problèmes peuvent encore surgir au cours de notre développement. Pourquoi ?
Puisque nous avons fixé un délai d'expiration pour ce verrou au début, que se passe-t-il si le temps d'exécution de notre logique métier dépasse le délai d'expiration défini ? Il y aura une situation où un thread n'a pas terminé son exécution et le deuxième thread peut détenir le verrou distribué.
Par conséquent, si vous utilisez la combinaison setNx Ex, vous devez vous assurer que le délai d'expiration de votre verrou est supérieur au temps d'exécution métier après le verrouillage.
3. Utilisez le mécanisme d'extension automatique du script Lua + Watch Dog
Je peux trouver une grande partie de cette solution en ligne, je n’entrerai donc pas dans les détails ici.
Les commandes setNx et setNx Ex présentées ci-dessus sont des commandes natives fournies par le serveur Redis, et il y a plus ou moins de problèmes afin de résoudre le problème selon lequel la logique métier de la commande setNx Ex est supérieure au délai d'expiration du verrouillage, Redisson fournit des informations internes. Un chien de garde est installé pour surveiller le verrou. Sa fonction est de prolonger en permanence la période de validité du verrou avant la fermeture de l'instance Redisson. Par défaut, le délai d'expiration du verrouillage du contrôle de surveillance est de 30 secondes (c'est-à-dire que le renouvellement est de 30 secondes). Il peut également être spécifié séparément en modifiant Config.lockWatchdogTimeout. Le délai d'expiration initial du verrou est également de 30 secondes par défaut.
// 加锁以后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();
}
}
Nous sommes confrontés au problème de la nécessité de limiter le flux actuel des interfaces ou de la logique métier sous une concurrence élevée. Nous pouvons utiliser l'implémentation RateLimiter basée sur Guaua. En fait, Redisssion a également une fonction de limitation de courant similaire.
RateLimiter est appelé limitation de courant du compartiment de jetons. Ce type de limitation de courant consiste à définir d'abord un compartiment de jetons, à spécifier le nombre de jetons générés au cours d'une certaine période de temps et à obtenir le nombre spécifié de jetons du compartiment de jetons à chaque accès. . Si vous obtenez Si réussi, il est défini comme accès valide.