HopeHomi脚手架(四)redis、redisson模块

  • Post author:
  • Post category:其他




项目结构

在这里插入图片描述



Redis



RedisSpiModuleImport

基于SPI。在项目启动的时候返回待加载类名

public class RedisSpiModuleImport implements SpiEnvironmentModuleImport {

    @Override
    public String[] readyImportClassName() {
        return new String[]{RedisConfiguration.class.getName()};
    }

}

在base项目,使用selectImports来返回这些类名

public class EnvironmentConfigurationSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        ServiceLoader<SpiEnvironmentModuleImport> load = ServiceLoader.load(SpiEnvironmentModuleImport.class);
        List<String> classNameList = new ArrayList<>();
        load.forEach(n -> {
            String[] classNameArr = n.readyImportClassName();
            if (ArrayUtil.isNotEmpty(classNameArr)) {
                classNameList.addAll(Arrays.asList(classNameArr));
            }
        });
        return classNameList.toArray(new String[0]);
    }
}

在启动类上通过@Import(EnvironmentConfigurationSelector.class)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootApplication
@Import(EnvironmentConfigurationSelector.class)
public @interface HopeSpringBootApplication {

    @AliasFor(
            annotation = SpringBootApplication.class,
            attribute = "scanBasePackages"
    )
    String[] scanBasePackages() default {};
}



@YamlPropertySource(“classpath:/redis.yml”)


读取自定义yaml文件,我在这里面初始化一些连接池配置,可以通过bootstrap.yml和application.yaml覆盖

redis.yaml

spring:
  redis:
    # 在Spring Boot 2.x版本中,RedisTemplate默认是使用Lettuce来连接Redis,而不再使用JedisLettuce是另一个流行的Java Redis客户端,它基于Netty框架进行开发,提供异步和响应式操作支持。相对于JedisLettuce提供了更好的性能和可伸缩性,支持Redis SentinelRedis Cluster,在多线程环境下表现更为优异
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 10
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 1



RedisConfiguration

/**
 * 基于RedisSpiModuleImport加载
 * {@link RedisSpiModuleImport}
 *
 * @author Ledison
 * @date 2023/4/16
 */
@YamlPropertySource("classpath:/redis.yml")
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // key 序列化
        CharsetKeySerialize charsetKeySerialize = new CharsetKeySerialize();
        redisTemplate.setKeySerializer(charsetKeySerialize);
        redisTemplate.setHashKeySerializer(charsetKeySerialize);
        // value 序列化
        ProtoStuffSerialize protostuffSerialize = new ProtoStuffSerialize();
        redisTemplate.setValueSerializer(protostuffSerialize);
        redisTemplate.setHashValueSerializer(protostuffSerialize);

        return redisTemplate;
    }

    @Bean(initMethod = "connectHealth")
    public RedisKit redisKit(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        return new RedisKit(redisTemplate, stringRedisTemplate);
    }
}


通过protoStuff实现RedisTemplate value序列化



通过UTF-8字符串实现RedisTemplate key序列化

/**
 * UTF-8 key序列化
 * @author Ledison
 * @date 2023/4/17
 */
public class CharsetKeySerialize implements RedisSerializer<Object> {

    private final Charset charset;

    public CharsetKeySerialize() {
        this(StandardCharsets.UTF_8);
    }

    public CharsetKeySerialize(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public byte[] serialize(Object obj) throws SerializationException {
        if (obj == null) {
            return null;
        }
        String key = obj.toString();
        return key.getBytes(charset);
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        return new String(bytes, charset);
    }
}

/**
 * protoStuff value序列化
 * @author Ledison
 * @date 2023/4/17
 */
public class ProtoStuffSerialize implements RedisSerializer<Object> {

    private final Schema<BytesWrapper> schema;

    public ProtoStuffSerialize() {
        this.schema = RuntimeSchema.getSchema(BytesWrapper.class);
    }

    @Override
    public byte[] serialize(Object object) throws SerializationException {
        if (object == null) {
            return null;
        }
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        try {
            return ProtostuffIOUtil.toByteArray(new BytesWrapper<>(object), schema, buffer);
        } finally {
            buffer.clear();
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (ObjectUtil.isEmpty(bytes)) {
            return null;
        }
        BytesWrapper<Object> wrapper = new BytesWrapper<>();
        ProtostuffIOUtil.mergeFrom(bytes, wrapper, schema);
        return wrapper.getValue();
    }
}
/**
 * redis序列化辅助类.单纯的泛型无法定义通用schema,原因是无法通过泛型T得到Class
 *
 * @author Ledison
 */
public class BytesWrapper<T> implements Cloneable {
    private T value;

    public BytesWrapper() {
    }

    public BytesWrapper(T value) {
        this.value = value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    @Override
    @SuppressWarnings("unchecked")
    public BytesWrapper<T> clone() {
        try {
            return (BytesWrapper) super.clone();
        } catch (CloneNotSupportedException e) {
            return new BytesWrapper<>();
        }
    }
}



RedisKit


/**
 * Redis工具类
 * @author Ledison
 * @date 2023/4/17
 */
public class RedisKit {

    private final RedisTemplate<String, Object> redisTemplate;
    private final StringRedisTemplate stringRedisTemplate;
    private final ValueOperations<String, Object> valueOps;
    private final HashOperations<String, Object, Object> hashOps;
    private final ListOperations<String, Object> listOps;
    private final SetOperations<String, Object> setOps;
    private final ZSetOperations<String, Object> zSetOps;

    public RedisKit(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
        Assert.notNull(redisTemplate, "redisTemplate is null");
        valueOps = redisTemplate.opsForValue();
        hashOps = redisTemplate.opsForHash();
        listOps = redisTemplate.opsForList();
        setOps = redisTemplate.opsForSet();
        zSetOps = redisTemplate.opsForZSet();
    }

    /**
     * 存放 key value 对到 redis。
     */
    public void set(String key, Object value) {
        valueOps.set(key, value);
    }

    /**
     * 存放 key value 对到 redis,并将 key 的生存时间设为 seconds (以秒为单位)。
     * 如果 key 已经存在, SETEX 命令将覆写旧值。
     */
    public void setEx(String key, Object value, Duration timeout) {
        valueOps.set(key, value, timeout);
    }

    /**
     * 存放 key value 对到 redis,并将 key 的生存时间设为 seconds (以秒为单位)。
     * 如果 key 已经存在, SETEX 命令将覆写旧值。
     */
    public void setEx(String key, Object value, Long seconds) {
        valueOps.set(key, value, seconds, TimeUnit.SECONDS);
    }

    /**
     * 返回 key 所关联的 value 值
     * 如果 key 不存在那么返回特殊值 nil 。
     */
    @Nullable
    public <T> T get(String key) {
        return (T) valueOps.get(key);
    }

    /**
     * 获取cache 为 null 时使用加载器,然后设置缓存
     *
     * @param key    cacheKey
     * @param loader cache loader
     * @param <T>    泛型
     * @return 结果
     */
    @Nullable
    public <T> T get(String key, Supplier<T> loader) {
        T value = this.get(key);
        if (value != null) {
            return value;
        }
        value = loader.get();
        if (value == null) {
            return null;
        }
        this.set(key, value);
        return value;
    }

    /**
     * 删除给定的一个 key
     * 不存在的 key 会被忽略。
     */
    public Boolean del(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除给定的多个 key
     * 不存在的 key 会被忽略。
     */
    public Long del(String... keys) {
        return del(Arrays.asList(keys));
    }

    /**
     * 删除给定的多个 key
     * 不存在的 key 会被忽略。
     */
    public Long del(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }

    /**
     * 查找所有符合给定模式 pattern 的 key 。
     * KEYS * 匹配数据库中所有 key 。
     * KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
     * KEYS h*llo 匹配 hllo 和 heeeeello 等。
     * KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
     * 特殊符号用 \ 隔开
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 同时设置一个或多个 key-value 对。
     * 如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。
     * MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
     */
    public void mSet(Object... keysValues) {
        Map<Object, Object> objectObjectHashMap = CollectionUtil.toMap(keysValues);
        Map<String, Object> res = new HashMap<>();
        objectObjectHashMap.forEach((k, v) -> {
            res.put(StrUtil.str(k, Charset.defaultCharset()), v);
        });
        valueOps.multiSet(res);
    }

    /**
     * 返回所有(一个或多个)给定 key 的值。
     * 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
     */
    public List<Object> mGet(String... keys) {
        return mGet(Arrays.asList(keys));
    }

    /**
     * 返回所有(一个或多个)给定 key 的值。
     * 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。因此,该命令永不失败。
     */
    public List<Object> mGet(Collection<String> keys) {
        return valueOps.multiGet(keys);
    }

    /**
     * 将 key 中储存的数字值减一。
     * 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
     * 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
     * 本操作的值限制在 64 位(bit)有符号数字表示之内。
     * 关于递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
     */
    public Long decr(String key) {
        return stringRedisTemplate.opsForValue().decrement(key);
    }

    /**
     * 将 key 所储存的值减去减量 decrement 。
     * 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。
     * 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
     * 本操作的值限制在 64 位(bit)有符号数字表示之内。
     * 关于更多递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
     */
    public Long decrBy(String key, long longValue) {
        return stringRedisTemplate.opsForValue().decrement(key, longValue);
    }

    /**
     * 将 key 中储存的数字值增一。
     * 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
     * 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
     * 本操作的值限制在 64 位(bit)有符号数字表示之内。
     */
    public Long incr(String key) {
        return stringRedisTemplate.opsForValue().increment(key);
    }

    /**
     * 将 key 所储存的值加上增量 increment 。
     * 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
     * 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
     * 本操作的值限制在 64 位(bit)有符号数字表示之内。
     * 关于递增(increment) / 递减(decrement)操作的更多信息,参见 INCR 命令。
     */
    public Long incrBy(String key, long longValue) {
        return stringRedisTemplate.opsForValue().increment(key, longValue);
    }

    /**
     * 获取记数器的值
     */
    public Long getCounter(String key) {
        return Long.valueOf(String.valueOf(valueOps.get(key)));
    }

    /**
     * 检查给定 key 是否存在。
     */
    public Boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 从当前数据库中随机返回(不删除)一个 key 。
     */
    public String randomKey() {
        return redisTemplate.randomKey();
    }

    /**
     * 将 key 改名为 newkey 。
     * 当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。
     * 当 newkey 已经存在时, RENAME 命令将覆盖旧值。
     */
    public void rename(String oldkey, String newkey) {
        redisTemplate.rename(oldkey, newkey);
    }

    /**
     * 将当前数据库的 key 移动到给定的数据库 db 当中。
     * 如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
     * 因此,也可以利用这一特性,将 MOVE 当作锁(locking)原语(primitive)。
     */
    public Boolean move(String key, int dbIndex) {
        return redisTemplate.move(key, dbIndex);
    }

    /**
     * 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
     * 在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。
     */
    public Boolean expire(String key, long seconds) {
        return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
     * 在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。
     */
    public Boolean expire(String key, Duration timeout) {
        return expire(key, timeout.getSeconds());
    }

    /**
     * EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
     */
    public Boolean expireAt(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
     */
    public Boolean expireAt(String key, long unixTime) {
        return expireAt(key, new Date(unixTime));
    }

    /**
     * 这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。
     */
    public Boolean pexpire(String key, long milliseconds) {
        return redisTemplate.expire(key, milliseconds, TimeUnit.MILLISECONDS);
    }

    /**
     * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
     * 当 key 存在但不是字符串类型时,返回一个错误。
     */
    public <T> T getSet(String key, Object value) {
        return (T) valueOps.getAndSet(key, value);
    }

    /**
     * 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
     */
    public Boolean persist(String key) {
        return redisTemplate.persist(key);
    }

    /**
     * 返回 key 所储存的值的类型。
     */
    public String type(String key) {
        return redisTemplate.type(key).code();
    }

    /**
     * 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
     */
    public Long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 这个命令类似于 TTL 命令,但它以毫秒为单位返回 key 的剩余生存时间,而不是像 TTL 命令那样,以秒为单位。
     */
    public Long pttl(String key) {
        return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
    }

    /**
     * 将哈希表 key 中的域 field 的值设为 value 。
     * 如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。
     * 如果域 field 已经存在于哈希表中,旧值将被覆盖。
     */
    public void hSet(String key, Object field, Object value) {
        hashOps.put(key, field, value);
    }

    /**
     * 同时将多个 field-value (域-值)对设置到哈希表 key 中。
     * 此命令会覆盖哈希表中已存在的域。
     * 如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。
     */
    public void hMset(String key, Map<Object, Object> hash) {
        hashOps.putAll(key, hash);
    }

    /**
     * 返回哈希表 key 中给定域 field 的值。
     */
    public <T> T hGet(String key, Object field) {
        return (T) hashOps.get(key, field);
    }

    /**
     * 返回哈希表 key 中,一个或多个给定域的值。
     * 如果给定的域不存在于哈希表,那么返回一个 nil 值。
     * 因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
     */
    public List hmGet(String key, Object... fields) {
        return hmGet(key, Arrays.asList(fields));
    }

    /**
     * 返回哈希表 key 中,一个或多个给定域的值。
     * 如果给定的域不存在于哈希表,那么返回一个 nil 值。
     * 因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
     */
    public List hmGet(String key, Collection<Object> hashKeys) {
        return hashOps.multiGet(key, hashKeys);
    }

    /**
     * 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
     */
    public Long hDel(String key, Object... fields) {
        return hashOps.delete(key, fields);
    }

    /**
     * 查看哈希表 key 中,给定域 field 是否存在。
     */
    public Boolean hExists(String key, Object field) {
        return hashOps.hasKey(key, field);
    }

    /**
     * 返回哈希表 key 中,所有的域和值。
     * 在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。
     */
    public Map hGetAll(String key) {
        return hashOps.entries(key);
    }

    /**
     * 返回哈希表 key 中所有域的值。
     */
    public List hVals(String key) {
        return hashOps.values(key);
    }

    /**
     * 返回哈希表 key 中的所有域。
     * 底层实现此方法取名为 hfields 更为合适,在此仅为与底层保持一致
     */
    public Set<Object> hKeys(String key) {
        return hashOps.keys(key);
    }

    /**
     * 返回哈希表 key 中域的数量。
     */
    public Long hLen(String key) {
        return hashOps.size(key);
    }

    /**
     * 为哈希表 key 中的域 field 的值加上增量 increment 。
     * 增量也可以为负数,相当于对给定域进行减法操作。
     * 如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
     * 如果域 field 不存在,那么在执行命令前,域的值被初始化为 0 。
     * 对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
     * 本操作的值被限制在 64 位(bit)有符号数字表示之内。
     */
    public Long hIncrBy(String key, Object field, long value) {
        return hashOps.increment(key, field, value);
    }

    /**
     * 为哈希表 key 中的域 field 加上浮点数增量 increment 。
     * 如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。
     * 如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。
     * 当以下任意一个条件发生时,返回一个错误:
     * 1:域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型)
     * 2:域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)
     * HINCRBYFLOAT 命令的详细功能和 INCRBYFLOAT 命令类似,请查看 INCRBYFLOAT 命令获取更多相关信息。
     */
    public Double hIncrByFloat(String key, Object field, double value) {
        return hashOps.increment(key, field, value);
    }

    /**
     * 返回列表 key 中,下标为 index 的元素。
     * 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,
     * 以 1 表示列表的第二个元素,以此类推。
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     * 如果 key 不是列表类型,返回一个错误。
     */
    public <T> T lIndex(String key, long index) {
        return (T) listOps.index(key, index);
    }

    /**
     * 返回列表 key 的长度。
     * 如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
     * 如果 key 不是列表类型,返回一个错误。
     */
    public Long lLen(String key) {
        return listOps.size(key);
    }

    /**
     * 移除并返回列表 key 的头元素。
     */
    public <T> T lPop(String key) {
        return (T) listOps.leftPop(key);
    }

    /**
     * 将一个或多个值 value 插入到列表 key 的表头
     * 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,
     * 对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,
     * 这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
     * 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
     * 当 key 存在但不是列表类型时,返回一个错误。
     */
    public Long lPush(String key, Object... values) {
        return listOps.leftPush(key, values);
    }

    /**
     * 将列表 key 下标为 index 的元素的值设置为 value 。
     * 当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
     * 关于列表下标的更多信息,请参考 LINDEX 命令。
     */
    public void lSet(String key, long index, Object value) {
        listOps.set(key, index, value);
    }

    /**
     * 根据参数 count 的值,移除列表中与参数 value 相等的元素。
     * count 的值可以是以下几种:
     * count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
     * count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
     * count = 0 : 移除表中所有与 value 相等的值。
     */
    public Long lRem(String key, long count, Object value) {
        return listOps.remove(key, count, value);
    }

    /**
     * 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
     * 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     * <pre>
     * 例子:
     * 获取 list 中所有数据:cache.lrange(listKey, 0, -1);
     * 获取 list 中下标 1 到 3 的数据: cache.lrange(listKey, 1, 3);
     * </pre>
     */
    public List lRange(String key, long start, long end) {
        return listOps.range(key, start, end);
    }

    /**
     * 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
     * 举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
     * 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     * 当 key 不是列表类型时,返回一个错误。
     */
    public void lTrim(String key, long start, long end) {
        listOps.trim(key, start, end);
    }

    /**
     * 移除并返回列表 key 的尾元素。
     */
    public <T> T rPop(String key) {
        return (T) listOps.rightPop(key);
    }

    /**
     * 将一个或多个值 value 插入到列表 key 的表尾(最右边)。
     * 如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如
     * 对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,
     * 等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。
     * 如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
     * 当 key 存在但不是列表类型时,返回一个错误。
     */
    public Long rPush(String key, Object... values) {
        return listOps.rightPush(key, values);
    }

    /**
     * 命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
     * 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
     * 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
     */
    public <T> T rPopLPush(String srcKey, String dstKey) {
        return (T) listOps.rightPopAndLeftPush(srcKey, dstKey);
    }

    /**
     * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
     * 假如 key 不存在,则创建一个只包含 member 元素作成员的集合。
     * 当 key 不是集合类型时,返回一个错误。
     */
    public Long sAdd(String key, Object... members) {
        return setOps.add(key, members);
    }

    /**
     * 移除并返回集合中的一个随机元素。
     * 如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER 命令。
     */
    public <T> T sPop(String key) {
        return (T) setOps.pop(key);
    }

    /**
     * 返回集合 key 中的所有成员。
     * 不存在的 key 被视为空集合。
     */
    public Set sMembers(String key) {
        return setOps.members(key);
    }

    /**
     * 判断 member 元素是否集合 key 的成员。
     */
    public boolean sIsMember(String key, Object member) {
        return setOps.isMember(key, member);
    }

    /**
     * 返回多个集合的交集,多个集合由 keys 指定
     */
    public Set sInter(String key, String otherKey) {
        return setOps.intersect(key, otherKey);
    }

    /**
     * 返回多个集合的交集,多个集合由 keys 指定
     */
    public Set sInter(String key, Collection<String> otherKeys) {
        return setOps.intersect(key, otherKeys);
    }

    /**
     * 返回集合中的一个随机元素。
     */
    public <T> T sRandMember(String key) {
        return (T) setOps.randomMember(key);
    }

    /**
     * 返回集合中的 count 个随机元素。
     * 从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数:
     * 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。
     * 如果 count 大于等于集合基数,那么返回整个集合。
     * 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
     * 该操作和 SPOP 相似,但 SPOP 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。
     */
    public List sRandMember(String key, int count) {
        return setOps.randomMembers(key, count);
    }

    /**
     * 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。
     */
    public Long sRem(String key, Object... members) {
        return setOps.remove(key, members);
    }

    /**
     * 返回多个集合的并集,多个集合由 keys 指定
     * 不存在的 key 被视为空集。
     */
    public Set sUnion(String key, String otherKey) {
        return setOps.union(key, otherKey);
    }

    /**
     * 返回多个集合的并集,多个集合由 keys 指定
     * 不存在的 key 被视为空集。
     */
    public Set sUnion(String key, Collection<String> otherKeys) {
        return setOps.union(key, otherKeys);
    }

    /**
     * 返回一个集合的全部成员,该集合是所有给定集合之间的差集。
     * 不存在的 key 被视为空集。
     */
    public Set sDiff(String key, String otherKey) {
        return setOps.difference(key, otherKey);
    }

    /**
     * 返回一个集合的全部成员,该集合是所有给定集合之间的差集。
     * 不存在的 key 被视为空集。
     */
    public Set sDiff(String key, Collection<String> otherKeys) {
        return setOps.difference(key, otherKeys);
    }

    /**
     * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,
     * 并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
     */
    public Boolean zAdd(String key, Object member, double score) {
        return zSetOps.add(key, member, score);
    }

    /**
     * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,
     * 并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。
     */
    public Long zAdd(String key, Map<Object, Double> scoreMembers) {
        Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
        scoreMembers.forEach((k, v) -> {
            tuples.add(new DefaultTypedTuple<>(k, v));
        });
        return zSetOps.add(key, tuples);
    }

    /**
     * 返回有序集 key 的基数。
     */
    public Long zCard(String key) {
        return zSetOps.zCard(key);
    }

    /**
     * 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
     * 关于参数 min 和 max 的详细使用方法,请参考 ZRANGEBYSCORE 命令。
     */
    public Long zCount(String key, double min, double max) {
        return zSetOps.count(key, min, max);
    }

    /**
     * 为有序集 key 的成员 member 的 score 值加上增量 increment 。
     */
    public Double zIncrBy(String key, Object member, double score) {
        return zSetOps.incrementScore(key, member, score);
    }

    /**
     * 返回有序集 key 中,指定区间内的成员。
     * 其中成员的位置按 score 值递增(从小到大)来排序。
     * 具有相同 score 值的成员按字典序(lexicographical order )来排列。
     * 如果你需要成员按 score 值递减(从大到小)来排列,请使用 ZREVRANGE 命令。
     */
    public Set zRange(String key, long start, long end) {
        return zSetOps.range(key, start, end);
    }

    /**
     * 返回有序集 key 中,指定区间内的成员。
     * 其中成员的位置按 score 值递减(从大到小)来排列。
     * 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
     * 除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
     */
    public Set zRevrange(String key, long start, long end) {
        return zSetOps.reverseRange(key, start, end);
    }

    /**
     * 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
     * 有序集成员按 score 值递增(从小到大)次序排列。
     */
    public Set zRangeByScore(String key, double min, double max) {
        return zSetOps.rangeByScore(key, min, max);
    }

    /**
     * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。
     * 排名以 0 为底,也就是说, score 值最小的成员排名为 0 。
     * 使用 ZREVRANK 命令可以获得成员按 score 值递减(从大到小)排列的排名。
     */
    public Long zRank(String key, Object member) {
        return zSetOps.rank(key, member);
    }

    /**
     * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。
     * 排名以 0 为底,也就是说, score 值最大的成员排名为 0 。
     * 使用 ZRANK 命令可以获得成员按 score 值递增(从小到大)排列的排名。
     */
    public Long zRevrank(String key, Object member) {
        return zSetOps.reverseRank(key, member);
    }

    /**
     * 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
     * 当 key 存在但不是有序集类型时,返回一个错误。
     */
    public Long zRem(String key, Object... members) {
        return zSetOps.remove(key, members);
    }

    /**
     * 返回有序集 key 中,成员 member 的 score 值。
     * 如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
     */
    public Double zScore(String key, Object member) {
        return zSetOps.score(key, member);
    }

    private void initConnectHealth() {
        // 检查连接状态
        redisTemplate.hasKey("health");
    }
}



Redisson

分布式锁,提供单节点、主从、哨兵、集群四种模式的配置



RedissonConfiguration

/**
 * @author Ledison
 * @date 2023/4/16
 */
@ConditionalOnProperty(value = "spring.redis.redisson.enable", havingValue = "true")
@ConditionalOnClass(RedissonClient.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonConfiguration {

    @Autowired
    private RedissonProperties redissonProperties;

    @Bean
    public RedissonClient redissonClient() {
        RedissonProperties.Mode mode = redissonProperties.getMode();
        Config config;
        switch (mode) {
            case single:
                config = singleConfig();
                break;
            case cluster:
                config = clusterConfig();
                break;
            case sentinel:
                config = sentinelConfig();
                break;
            case master:
                config = masterSlaveConfig();
                break;
            default:
                config = new Config();
                break;
        }
        return Redisson.create(config);
    }

    /**
     * 主从模式
     *
     * @return
     */
    private Config masterSlaveConfig() {
        Config config = new Config();
        MasterSlaveServersConfig serversConfig = config.useMasterSlaveServers();
        serversConfig.setMasterAddress(redissonProperties.getMasterAddress());
        serversConfig.addSlaveAddress(redissonProperties.getSlaveAddress());
        String password = redissonProperties.getPassword();
        if (StringUtil.isNotBlank(password)) {
            serversConfig.setPassword(password);
        }
        serversConfig.setDatabase(redissonProperties.getDatabase());
        serversConfig.setMasterConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setMasterConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setSlaveConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setSlaveConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setIdleConnectionTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setConnectTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setTimeout(redissonProperties.getTimeout());
        return config;
    }

    /**
     * 哨兵模式
     *
     * @return
     */
    private Config sentinelConfig() {
        Config config = new Config();
        SentinelServersConfig serversConfig = config.useSentinelServers();
        serversConfig.setMasterName(redissonProperties.getMasterName());
        serversConfig.addSentinelAddress(redissonProperties.getSentinelAddress());
        String password = redissonProperties.getPassword();
        if (StringUtil.isNotBlank(password)) {
            serversConfig.setPassword(password);
        }
        serversConfig.setDatabase(redissonProperties.getDatabase());
        serversConfig.setMasterConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setMasterConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setSlaveConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setSlaveConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setIdleConnectionTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setConnectTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setTimeout(redissonProperties.getTimeout());
        return config;
    }

    /**
     * 集群模式
     *
     * @return
     */
    private Config clusterConfig() {
        Config config = new Config();
        ClusterServersConfig serversConfig = config.useClusterServers();
        serversConfig.addNodeAddress(redissonProperties.getNodeAddress());
        String password = redissonProperties.getPassword();
        if (StringUtil.isNotBlank(password)) {
            serversConfig.setPassword(password);
        }
        serversConfig.setMasterConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setMasterConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setSlaveConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setSlaveConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setIdleConnectionTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setConnectTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setTimeout(redissonProperties.getTimeout());
        return config;
    }

    /**
     * 单节点模式
     *
     * @return
     */
    private Config singleConfig() {
        Config config = new Config();
        SingleServerConfig serversConfig = config.useSingleServer();
        serversConfig.setAddress(redissonProperties.getAddress());
        String password = redissonProperties.getPassword();
        if (StringUtil.isNotBlank(password)) {
            serversConfig.setPassword(password);
        }
        serversConfig.setDatabase(redissonProperties.getDatabase());
        serversConfig.setConnectionPoolSize(redissonProperties.getPoolSize());
        serversConfig.setConnectionMinimumIdleSize(redissonProperties.getIdleSize());
        serversConfig.setIdleConnectionTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setConnectTimeout(redissonProperties.getConnectionTimeout());
        serversConfig.setTimeout(redissonProperties.getTimeout());
        return config;
    }

}



RedissonProperties

@ConfigurationProperties("spring.redis.redisson")
@Data
public class RedissonProperties {

    /**
     * 是否开启:默认为:false,便于生成配置提示。
     */
    private Boolean enabled = Boolean.FALSE;
    /**
     * 单机配置:redis 服务地址
     */
    private String address = "redis://127.0.0.1:6379";
    /**
     * 密码配置
     */
    private String password;
    /**
     * db
     */
    private Integer database = 0;
    /**
     * 连接池大小
     */
    private Integer poolSize = 20;
    /**
     * 最小空闲连接数
     */
    private Integer idleSize = 5;
    /**
     * 连接空闲超时,单位:毫秒
     */
    private Integer idleTimeout = 60000;
    /**
     * 连接超时,单位:毫秒
     */
    private Integer connectionTimeout = 3000;
    /**
     * 命令等待超时,单位:毫秒
     */
    private Integer timeout = 10000;
    /**
     * 集群模式,单机:single,主从:master,哨兵模式:sentinel,集群模式:cluster
     */
    private Mode mode = Mode.single;
    /**
     * 主从模式,主地址
     */
    private String masterAddress;
    /**
     * 主从模式,从地址
     */
    private String[] slaveAddress;
    /**
     * 哨兵模式:主名称
     */
    private String masterName;
    /**
     * 哨兵模式地址
     */
    private String[] sentinelAddress;
    /**
     * 集群模式节点地址
     */
    private String[] nodeAddress;

    public enum Mode {
        /**
         * 集群模式,单机:single,主从:master,哨兵模式:sentinel,集群模式:cluster
         */
        single,
        master,
        sentinel,
        cluster
    }
}



项目配置

spring:
  redis:
    redisson:
      enable: true
      mode: single
      address: redis://150.158.24.165:36380
      password: 123456



项目使用

@RestController
@RequestMapping("redis")
@AllArgsConstructor
public class RedisController {

    private final StringRedisTemplate redisTemplate;
    private final RedisKit redisKit;
    private final RedissonClient redissonClient;

    @RequestMapping("test")
    public void run() {
        redisKit.set("a", "ccwewe");
        Object a = redisKit.get("a");
        System.out.println(a);
        redisTemplate.opsForHash().put("vv", "b", "c");
    }

    @RequestMapping("redisson")
    public void redisson() {
        String lockKey = "lockKey";
        RLock lock = redissonClient.getLock("myredisson");
        try {
//            boolean b = lock.tryLock(2,20, TimeUnit.SECONDS);
            lock.lock();
            System.out.println("获取到锁");
        } finally {
            lock.unlock();
        }
    }
}



其他说明

  1. 如果通过redisson.lock()来获取锁,会通过看门狗机制来续锁,续锁逻辑是用netty下的Timeout定时器来实现的,默认锁时间是30秒,定时器每锁时间/3也就是10秒执行一次。来判断是否还持有锁,如果还持有锁就重置锁时间为30秒。从而避免业务执行时间大于锁时间而导致的锁被释放了的问题。
  2. 如果是通过tryLock(long waitTime, long leaseTime, TimeUnit unit)来获取锁,则不会执行续锁操作。



版权声明:本文为qq_36382225原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。