моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
существоватьSpringBoot использует RedisTemplate и StringRedisTemplate для работы с Redis.мы представили RedisTemplate и то, как SpringBoot управляет Redis через RedisTemplate и StringRedisTemplate.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
Итак, поскольку SrpingBoot может управлять Redis через RedisTemplate, почему Redisson появляется снова?Rddisson китайская документация
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
Начиная с версии Spring-boot 2.x, Spring-Boot-Data-Redis по умолчанию использует клиент Lettuce для работы с данными.
SpringBoot2之后,默认就采用了lettuce。
Это расширенный клиент Redis, уровень связи, управляемый событиями, на основе платформы Netty для поточно-безопасной синхронизации, асинхронного и реактивного использования, поддерживающий кластеры, Sentinel, конвейеры и кодировщики.
API Lettuce является потокобезопасным и может управлять одним соединением 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. Поэтому настройки Luttuce и Jedis в application.yaml не вступят в силу.
При использовании Redisson в проекте мы обычно используем RedissonClient для операций с данными. Однако некоторые друзья могут найти RedissonClient неудобным в работе или предпочесть использовать RedisTemplate для операций. На самом деле, нам нужно только определить конфигурацию. Категория RedisTemplate.Ссылаться наSpringBoot использует RedisTemplate и StringRedisTemplate для работы с Redis.。
Было обнаружено, что после того, как в проекте появился Redisson, фабрика соединений, используемая в нижней части RedisTemplate, также была Redisson.
Добавьте информацию о конфигурации Redis в файл 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
}
}
}
Студенты с некоторым опытом вспоминают 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 и expire — это две команды, если сервер Redis зависает между setnx и expire, expire не будет выполнен, поэтому установка времени истечения не удалась, и блокировка по-прежнему будет действовать. Это зайдет в тупик.
Основная причина заключается в том, что две команды setnx и expire не являются атомарными командами.
А Redis не может решить проблему setnx и expire, потому что expire зависит от результата выполнения setnx. Если setnx не удался, expire не должен выполняться. О вещах нельзя судить иначе, поэтому метод setnx+expire для реализации распределенных блокировок не является оптимальным решением.
2. Используйте команду setNx Ex.
Проблема setNx+expire была упомянута выше. Чтобы решить эту проблему, представители Redis ввели расширенные параметры команды set в версии 2.8, чтобы команды setnx и expire можно было выполнять вместе. например:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
Логически говоря, setNx Ex уже является оптимальным решением и не приведет к тому, что распределенная блокировка станет взаимоблокировкой.
Но в процессе разработки все равно могут возникнуть проблемы. Почему?
Поскольку мы вначале установили срок действия для этой блокировки, что, если время выполнения нашей бизнес-логики превысит установленный срок действия? Возможна ситуация, когда один поток не завершил выполнение, а второй поток может удерживать распределенную блокировку.
Поэтому, если вы используете комбинацию setNx Ex, вы должны убедиться, что время ожидания вашей блокировки больше, чем время выполнения бизнеса после блокировки.
3. Используйте сценарий Lua + механизм автоматического расширения Watch Dog.
Я могу найти множество подобных решений в Интернете, поэтому не буду здесь вдаваться в подробности.
Команды setNx и setNx Ex, представленные выше, являются собственными командами, предоставляемыми сервером Redis, и существует больше или меньше проблем. Чтобы решить проблему, заключающуюся в том, что бизнес-логика команды setNx Ex превышает время ожидания блокировки, Redisson предоставляет внутреннюю систему. Для мониторинга блокировки устанавливается сторожевой таймер. Его функция — постоянно продлевать срок действия блокировки до закрытия экземпляра Redisson. По умолчанию время ожидания блокировки проверки сторожевого таймера составляет 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();
}
}
Мы столкнулись с проблемой необходимости ограничения текущего потока интерфейсов или бизнес-логики при высоком параллелизме. Мы можем использовать реализацию RateLimiter на основе Guaua. Фактически, Redisssion также имеет аналогичную функцию ограничения тока.
Ограничитель скорости называется ограничением тока корзины токенов. Этот тип ограничения тока предназначен для того, чтобы сначала определить корзину токенов, указать, сколько токенов будет сгенерировано в течение определенного периода времени, и получить указанное количество токенов из корзины токенов каждый раз, когда вы получаете к ней доступ. . Если вы получите В случае успеха, он будет установлен как действительный доступ.