SpringBoot使用Redis分布式缓存

Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

RedisTempate操作Redis

引入依赖

<!--spring boot 2.0以后默认使用lettuce-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lettuce 依赖commons-pool-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>
<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.1</version>
</dependency>

注意:spring-data-redis默认使用的是lettuce,如果想要使用jedis就需要添加一个jedis的依赖。

<!--Jedis客户端, 注意:部分版本有连接泄漏的风险,如2.9.1-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
</dependency>

application.yml配置

实际开发中,jedis和lettuce二选一即可。

server:
  port: 9999

#系统默认连接池,这种方式 redisTemplate 可直接使用默认,在使用的地方直接注入即可
spring:
  redis:
    #单机
    host: 127.0.0.1
    database: 0
    port: 6379
    password:
    #集群
#    cluster:
#      nodes: 192.168.198.155:7000,192.168.198.155:7001,192.168.198.155:7002,192.168.198.155:7003,192.168.198.155:7004,192.168.198.155:7005
#      max-redirects: 3
    timeout: 6000
    #lettuce客户端
#    lettuce:
#      pool:
#        max-active: 50
#        max-idle: 8
#        min-idle: 0
#        max-wait: 1000ms
#      shutdown-timeout: 100ms
    #jedis客户端
    jedis:
      pool:
        max-active: 50
        max-idle: 8
        min-idle: 0
        max-wait: 1000ms

RedisTemplate配置类

@Configuration
public class RedisConfig {

    @Bean(name = "defaultRedisTemplate")
    public RedisTemplate<String, Object> defaultRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

或者:

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean(name = "defaultRedisTemplate")
    public RedisTemplate<String, Object> defaultRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // key采用String的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // value序列化方式采用jackson
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

Spring Boot Data(数据) Redis 中提供了 RedisTemplate 和 StringRedisTemplate;

StringRedisTemplate 是 RedisTemplate 的子类,两个方法基本一致,不同之处在于 操作的数据类型不同:

  • RedisTemplate 两个泛型都是 Object,意味着存储的 key 和 value 都可以是一个对象。
  • StringRedisTemplate 两个泛型都是 String,意味着存储的 的 key 和 value 都只能是字符串。

使用 RedisTemplate 默认是将对象序列化到 Redis 中,所以 放入的对象必须实现对象序列化接口。

两者的 数据是不共通的;也就是说 StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中的数据。

RedisTemplate<String, Object>工具类

下面我们就以RedisTemplate<String, Object> 模板为基础提供一个操作redis的工具类。

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.*;

/**
 * RedisTemplate工具类
 */
@Slf4j
@Component
public class RedisUtil implements SmartInitializingSingleton {

    private static final String KEY_PREFIX = "cacheSys:";

    private static RedisTemplate<String, Object> redisTemplate;

    private RedisUtil() {
    }

    private static final String getCacheKey(String key) {
        return KEY_PREFIX + key;
    }

    /**
     * 判断redis中是否含有该键
     *
     * @param key 键值
     * @return Boolean值 false 没有此键, true 含有此键
     */
    public static boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(getCacheKey(key)) && redisTemplate.getExpire(getCacheKey(key)) > 0;
        } catch (Exception e) {
            log.warn("RedisUtil.hasKey error happened, key={}", key, e);
        }
        return false;
    }

    /**
     * 根据key的前缀获取匹配的key列表
     * 注意:该方法只有在RedisTemplate的key为StringRedisSerializer的配置下才生效
     *
     * @param pattern
     */
    public static Set<String> findKeysByPattern(String pattern) {
        try {
            return redisTemplate.keys(pattern.concat("*"));
        } catch (Exception e) {
            log.warn("RedisUtil.findKeysByPattern error happened, pattern={}", pattern, e);
        }
        return new HashSet<>();
    }

    /**
     * 根据key的前缀删除匹配的key
     * 注意:该方法只有在RedisTemplate的key为StringRedisSerializer的配置下才生效
     *
     * @param pattern
     */
    public static long deleteKeysByPattern(String pattern) {
        try {
            Set<String> keys = findKeysByPattern(pattern);
            if (keys != null && keys.size() > 0) {
                return redisTemplate.delete(keys);
            }
        } catch (Exception e) {
            log.warn("RedisUtil.deleteKeysByPattern error happened, pattern={}", pattern, e);
        }
        return 0;
    }

    /**
     * 过期时间设置
     *
     * @param key           键
     * @param expireMinutes 过期时间
     * @return 返回设置成功
     */
    public static boolean setExpire(String key, long expireMinutes) {
        try {
            return redisTemplate.expire(getCacheKey(key), Duration.ofMinutes(expireMinutes));
        } catch (Exception e) {
            log.warn("RedisUtil.setExpire error happened, key={}", key, e);
        }
        return false;
    }

    public static boolean setExpireByMillis(String key, long expireMillis) {
        try {
            return redisTemplate.expire(getCacheKey(key), Duration.ofMillis(expireMillis));
        } catch (Exception e) {
            log.warn("RedisUtil.setExpireByMillis error happened, key={}", key, e);
        }
        return false;
    }

    public static boolean setExpireBySecond(String key, long expireSeconds) {
        try {
            return redisTemplate.expire(getCacheKey(key), Duration.ofSeconds(expireSeconds));
        } catch (Exception e) {
            log.warn("RedisUtil.setExpireBySecond error happened, key={}", key, e);
        }
        return false;
    }

    public static boolean setExpireByHour(String key, long expireHours) {
        try {
            return redisTemplate.expire(getCacheKey(key), Duration.ofHours(expireHours));
        } catch (Exception e) {
            log.warn("RedisUtil.setExpireByHour error happened, key={}", key, e);
        }
        return false;
    }

    public static boolean setExpireByDay(String key, long expireDays) {
        try {
            return redisTemplate.expire(getCacheKey(key), Duration.ofDays(expireDays));
        } catch (Exception e) {
            log.warn("RedisUtil.setExpireByDay error happened, key={}", key, e);
        }
        return false;
    }

    /**
     * 删除键值
     *
     * @param key 键
     * @return 返回删除结果
     */
    public static boolean delete(String key) {
        try {
            return redisTemplate.delete(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.delete error happened, key={}", key, e);
        }
        return false;
    }

    /**
     * 通过集合中的所有key删除对应的所有值
     *
     * @param keys 集合keys
     * @return 返回boolean值
     */
    public static long delete(Collection<String> keys) {
        try {
            Set<String> waitDeleteKeys = new HashSet<>();
            for (String key : keys) {
                waitDeleteKeys.add(getCacheKey(key));
            }
            return redisTemplate.delete(waitDeleteKeys);
        } catch (Exception e) {
            log.warn("RedisUtil.delete error happened, keys={}", Strings.join(keys, ','), e);
        }
        return 0;
    }

    //-----------------------------对象键值存取---------------------------------------------------------------

    /**
     * 存值
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(getCacheKey(key), value);
        } catch (Exception e) {
            log.warn("RedisUtil.set error happened, key={}", key, e);
        }
    }

    /**
     * 存值
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间
     */
    public static void set(String key, Object value, Duration timeout) {
        try {
            redisTemplate.opsForValue().set(getCacheKey(key), value, timeout);
        } catch (Exception e) {
            log.warn("RedisUtil.set error happened, key={}", key, e);
        }
    }

    /**
     * 获取键对应的值
     *
     * @param key 键
     * @return 返回键对应的值
     */
    public static <T> T get(String key) {
        try {
            return (T)redisTemplate.opsForValue().get(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.get error happened, key={}", key, e);
        }
        return null;
    }

    //-----------------------------List键值存取---------------------------------------------------------------

    /**
     * 根据key存储到list的指定位置
     *
     * @param key   键
     * @param index list中指定索引
     * @param value 值
     */
    public static void lSet(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(getCacheKey(key), index, value);
        } catch (Exception e) {
            log.warn("RedisUtil.lSet error happened, key={}", key, e);
        }
    }

    /**
     * 存储到列表最左侧
     *
     * @param key   键
     * @param value 值
     */
    public static void lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(getCacheKey(key), value);
        } catch (Exception e) {
            log.warn("RedisUtil.lSet error happened, key={}", key, e);
        }
    }

    /**
     * 存储到列表最右
     *
     * @param key   键
     * @param value 值
     */
    public static void lSetR(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(getCacheKey(key), value);
        } catch (Exception e) {
            log.warn("RedisUtil.lSetR error happened, key={}", key, e);
        }
    }

    /**
     * 根据key存储到collection集合
     *
     * @param key
     * @param collection
     */
    public static void lSetMulti(String key, Collection collection) {
        try {
            redisTemplate.opsForList().leftPushAll(getCacheKey(key), collection);
        } catch (Exception e) {
            log.warn("RedisUtil.lSet error happened, key={}", key, e);
        }
    }

    /**
     * 获取对应key的list列表大小
     *
     * @param key 键
     * @return size
     */
    public static long lGetSize(String key) {
        try {
            return redisTemplate.opsForList().size(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.lGetSize error happened, key={}", key, e);
        }
        return 0;
    }

    /**
     * 获取键对应的列表数据
     *
     * @param key 键
     * @return key的值(列表)
     */
    public static <T> List<T> lGet(String key) {
        try {
            return (List<T>) redisTemplate.opsForList().range(getCacheKey(key), 0, -1);
        } catch (Exception e) {
            log.warn("RedisUtil.lGet error happened, key={}", key, e);
        }
        return new ArrayList<>();
    }

    /**
     * 获取键对应的列表数据
     *
     * @param key   键
     * @param start 开始位置
     * @param end   结束位置
     * @return 返回key对应范围内的列表数据
     */
    public static <T> List<T> lGet(String key, long start, long end) {
        try {
            return (List<T>) redisTemplate.opsForList().range(getCacheKey(key), start, end);
        } catch (Exception e) {
            log.warn("RedisUtil.lGet error happened, key={}", key, e);
        }
        return new ArrayList<>();
    }

    //-----------------------------Set(无序)键值存取---------------------------------------------------------------

    /**
     * 存储set类型的数据
     *
     * @param key    键
     * @param values 值,可以是多个
     */
    public static void sSet(String key, Object... values) {
        try {
            redisTemplate.opsForSet().add(getCacheKey(key), values);
        } catch (Exception e) {
            log.warn("RedisUtil.sSet error happened, key={}", key, e);
        }
    }

    /**
     * 获取key对应set类型数据的大小
     *
     * @param key 键
     */
    public static long sGetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.sGetSize error happened, key={}", key, e);
        }
        return 0;
    }

    /**
     * 获取set类型的数据
     *
     * @param key 键
     * @return 返回一个set集合
     */
    public static <T> Set<T> sGet(String key) {
        try {
            return (Set<T>) redisTemplate.opsForSet().members(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.sGet error happened, key={}", key, e);
        }
        return new HashSet<>();
    }

    //-----------------------------ZSet(有序)键值存取---------------------------------------------------------------

    /**
     * 存储有序集合
     *
     * @param key   键
     * @param value 值
     * @param score 排序
     */
    public static boolean zSet(String key, Object value, double score) {
        try {
            return redisTemplate.opsForZSet().add(getCacheKey(key), value, score);
        } catch (Exception e) {
            log.warn("RedisUtil.zSet error happened, key={}", key, e);
        }
        return false;
    }

    /**
     * 存储值
     *
     * @param key 键
     * @param set 集合
     */
    public static long zSet(String key, Set set) {
        try {
            return redisTemplate.opsForZSet().add(getCacheKey(key), set);
        } catch (Exception e) {
            log.warn("RedisUtil.zSet error happened, key={}", key, e);
        }
        return 0;
    }

    /**
     * 获取key指定范围的值
     *
     * @param key   键
     * @param start 开始位置
     * @param end   结束位置
     * @return 返回set
     */
    public static <T> Set<T> zGet(String key, long start, long end) {
        try {
            return (Set<T>) redisTemplate.opsForZSet().range(getCacheKey(key), start, end);
        } catch (Exception e) {
            log.warn("RedisUtil.zGet error happened, key={}", key, e);
        }
        return new HashSet<>();
    }

    /**
     * 获取key对应的所有值
     *
     * @param key 键
     * @return 返回set
     */
    public static <T> Set<T> zGet(String key) {
        try {
            return (Set<T>) redisTemplate.opsForZSet().range(getCacheKey(key), 0, -1);
        } catch (Exception e) {
            log.warn("RedisUtil.zGet error happened, key={}", key, e);
        }
        return new HashSet<>();
    }

    /**
     * 获取对用数据的大小
     *
     * @param key 键
     * @return 键值大小
     */
    public static long zGetSize(String key) {
        try {
            return redisTemplate.opsForZSet().size(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.zGetSize error happened, key={}", key, e);
        }
        return 0;
    }

    //-----------------------------HashMap键值存取---------------------------------------------------------------

    /**
     * 存储hashMap数据
     *
     * @param key     键
     * @param hashKey map的id
     * @param value   值
     */
    public static void hSet(String key, String hashKey, Object value) {
        try {
            redisTemplate.opsForHash().put(getCacheKey(key), hashKey, value);
        } catch (Exception e) {
            log.warn("RedisUtil.hSet error happened, key={}", key, e);
        }
    }

    /**
     * 获取大小
     *
     * @param key 键
     */
    public static long hGetSize(String key) {
        try {
            return redisTemplate.opsForHash().size(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.hGetSize error happened, key={}", key, e);
        }
        return 0;
    }

    /**
     * 获取hashMap数据
     *
     * @param key     键
     * @param hashKey map的id
     * @return 返回值
     */
    public static Object hGet(String key, String hashKey) {
        try {
            return redisTemplate.opsForHash().get(getCacheKey(key), hashKey);
        } catch (Exception e) {
            log.warn("RedisUtil.hGet error happened, key={}", key, e);
        }
        return null;
    }

    /**
     * 删除数据
     *
     * @param key      键
     * @param hashKeys map的id
     * @return 返回Boolean
     */
    public static long hDel(String key, String... hashKeys) {
        try {

            return redisTemplate.opsForHash().delete(getCacheKey(key), hashKeys);
        } catch (Exception e) {
            log.warn("RedisUtil.hDel error happened, key={}", key, e);
        }
        return 0;
    }

    /**
     * 获取map数据
     *
     * @param key 键
     */
    public static <K, V> Map<K, V> hGetMap(String key) {
        try {
            return (Map<K, V>) redisTemplate.opsForHash().entries(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.hGetMap error happened, key={}", key, e);
        }
        return new HashMap<>();
    }

    /**
     * 获取map的keys
     *
     * @param key 键
     */
    public static <T> Set<T> hGetKeys(String key) {
        try {
            return (Set<T>) redisTemplate.opsForHash().keys(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.hGetKeys error happened, key={}", key, e);
        }
        return new HashSet<>();
    }

    /**
     * 获取map的values
     *
     * @param key 键
     */
    public static <T> List<T> hGetValues(String key) {
        try {
            return (List<T>) redisTemplate.opsForHash().values(getCacheKey(key));
        } catch (Exception e) {
            log.warn("RedisUtil.hGetValues error happened, key={}", key, e);
        }
        return new ArrayList<>();
    }


    @Override
    public void afterSingletonsInstantiated() {
        initBean();
    }

    private void initBean() {
        //获取指定bean name的RedisTemplate
        redisTemplate = SpringBeanUtil.getBean("defaultRedisTemplate");
    }
}

分布式锁

(1)在加锁时就要给锁设置一个标识,进程要记住这个标识。当进程解锁的时候,要进行判断,是自己持有的锁才能释放,否则不能释放。可以为key 赋一个随机值,来充当进程的标识。

(2)解锁时要先判断、再释放,这两步需要保证原子性,否则第二步失败的话,就会出现死锁。而获取和删除命令不是原子的,这就需要采用Lua 脚本,通过 Lua 脚本将两个命令编排在一起,而整个Lua脚本的执行过程是原子的。

按照以上思路最终方案如下:

加锁:

set key random-value nx ex seconds

解锁:

if redis.call("get",KEYS[1]) == ARGV[1] then 
    return redis.call("del",KEYS[1]) 
else
    return 0 end

具体Java代码如下:

//分布式锁过期时间 s  可以根据业务自己调节
private static final Long LOCK_REDIS_TIMEOUT = 10L;
//分布式锁休眠 至 再次尝试获取 的等待时间 ms 可以根据业务自己调节
public static final Long LOCK_REDIS_WAIT = 500L;

/**
 * 加锁
 **/
public static boolean getLock(String key, String value) {
    return redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(LOCK_REDIS_TIMEOUT));
}

/**
 * 释放锁
 **/
public static Long releaseLock(String key, String value) {
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
    return redisTemplate.execute(redisScript, Collections.singletonList(key), value);
}

Redisson客户端操作Redis

Redisson是一个高级的分布式协调Redis客户端,能帮助用户在分布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

Redisson对redis的操作基本都进行了封装,并对其进行了扩展,使操作redis更加的简单、方便。再一个需要提到的是redisson是基于NIO的netty框架。

Redisson教程:http://www.voidcc.com/redisson

支持Redis多种连接模式

引入相关的依赖

<!-- redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-22</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <!-- for Spring Data Redis v.2.1.x -->
    <artifactId>redisson-spring-data-21</artifactId>
    <version>3.13.1</version>
</dependency>
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml配置Redisson

spring:
  redis:
    redisson:
      # 指定配置文件
      config: classpath:redisson-cluster.yml
    jedis:
      pool:
        max-active: 8  #最大连接数
        max-wait: -1 #最大阻塞等待时间(负数表示没限制)
        max-idle: 8 #最大空闲

1)单节点配置

redisson-single.yml

# 单节点配置
singleServerConfig:
  # 连接空闲超时,单位:毫秒
  idleConnectionTimeout: 10000
  # 连接超时,单位:毫秒
  connectTimeout: 10000
  # 命令等待超时,单位:毫秒
  timeout: 3000
  # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
  # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
  retryAttempts: 3
  # 命令重试发送时间间隔,单位:毫秒
  retryInterval: 1500
  # 重新连接时间间隔,单位:毫秒
  # reconnectionTimeout: 3000
  # 执行失败最大次数
  # failedAttempts: 3
  # 密码
  password:
  # 单个连接最大订阅数量
  subscriptionsPerConnection: 5
  # 客户端名称
  clientName: null
  #  # 节点地址 redis://ip:port
  address: redis://ip:port
  # 发布和订阅连接的最小空闲连接数
  subscriptionConnectionMinimumIdleSize: 1
  # 发布和订阅连接池大小
  subscriptionConnectionPoolSize: 50
  # 最小空闲连接数
  connectionMinimumIdleSize: 32
  # 连接池大小
  connectionPoolSize: 64
  # 数据库编号
  database: 0
  # DNS监测时间间隔,单位:毫秒
  dnsMonitoringInterval: 5000
# 线程池数量,默认值: 当前处理核数量 * 2
threads: 0
# Netty线程池数量,默认值: 当前处理核数量 * 2
nettyThreads: 0
# 编码
codec: !<org.redisson.codec.JsonJacksonCodec> {}
# 传输模式
transportMode : "NIO"

或程序化配置:

RedissonClient redisson = Redisson.create();
Config config = new Config();
config.useSingleServer().setAddress("192.168.198.154:6379");
RedissonClient redisson = Redisson.create(config);

2)主从配置

redisson-master-slave.yml

#主从模式配置
masterSlaveServersConfig:
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  reconnectionTimeout: 3000
  failedAttempts: 3
  password: null
  subscriptionsPerConnection: 5
  clientName: null
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  slaveSubscriptionConnectionMinimumIdleSize: 1
  slaveSubscriptionConnectionPoolSize: 50
  slaveConnectionMinimumIdleSize: 32
  slaveConnectionPoolSize: 64
  masterConnectionMinimumIdleSize: 32
  masterConnectionPoolSize: 64
  readMode: "SLAVE"
  slaveAddresses:
    - "redis://127.0.0.1:6381"
    - "redis://127.0.0.1:6380"
  masterAddress: "redis://127.0.0.1:6379"
  database: 0
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: "NIO"

或程序化配置:

Config config = new Config();
config.useMasterSlaveServers()
    .setMasterAddress("redis://127.0.0.1:6379")
    .addSlaveAddress("redis://127.0.0.1:6389", "redis://127.0.0.1:6332", "redis://127.0.0.1:6419")
    .addSlaveAddress("redis://127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config);

3)哨兵配置

redisson-sentinel.yml

# 哨兵配置
sentinelServersConfig:
  #如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  #同任何节点建立连接时的等待超时。时间单位是毫秒。
  connectTimeout: 10000
  #等待节点回复命令的时间。该时间从命令发送成功时开始计时。
  timeout: 3000
  #如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
  retryAttempts: 3
  #在一条命令发送失败以后,等待重试发送的时间间隔。时间单位是毫秒。
  retryInterval: 1500
  #当与某个节点的连接断开时,等待与其重新建立连接的时间间隔。时间单位是毫秒。
  reconnectionTimeout: 3000
  #在某个节点执行相同或不同命令时,连续 失败 failedAttempts(执行失败最大次数) 时,该节点将被从可用节点列表里清除,直到 reconnectionTimeout(重新连接时间间隔) 超时以后再次尝试。
  failedAttempts: 3
  password: null
  #每个连接的最大订阅数量。
  subscriptionsPerConnection: 5
  #在Redis节点里显示的客户端名称
  clientName: null
  #WeightedRoundRobinBalancer - 权重轮询调度算法;RoundRobinLoadBalancer - 轮询调度算法;RandomLoadBalancer - 随机调度算法
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  #从节点发布和订阅连接的最小空闲连接数
  slaveSubscriptionConnectionMinimumIdleSize: 1
  #从节点发布和订阅连接池大小
  slaveSubscriptionConnectionPoolSize: 50
  #从节点,每个 从服务节点里用于普通操作(非发布和订阅)的最小保持连接数(长连接)。长期保持一定数量的连接有利于提高瞬时读取反映速度。
  slaveConnectionMinimumIdleSize: 32
  #从节点,每个 从服务节点里用于普通操作(非 发布和订阅)连接的连接池最大容量。连接池的连接数量自动弹性伸缩。
  slaveConnectionPoolSize: 64
  #多从节点的环境里,每个 主节点的最小保持连接数(长连接)。长期保持一定数量的连接有利于提高瞬时写入反应速度。
  masterConnectionMinimumIdleSize: 32
  #主节点的连接池最大容量。连接池的连接数量自动弹性伸缩。
  masterConnectionPoolSize: 64
  #设置读取操作选择节点的模式。 可用值为: SLAVE - 只在从服务节点里读取。 MASTER - 只在主服务节点里读取。 MASTER_SLAVE - 在主从服务节点里都可以读取。
  readMode: "SLAVE"
  #哨兵地址
  sentinelAddresses:
    - "redis://127.0.0.1:26379"
    - "redis://127.0.0.1:26389"
  #主服务器的名称是哨兵进程中用来监测主从服务切换情况的。
  masterName: "mymaster"
  database: 0
#这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。
threads: 0
#这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。
nettyThreads: 0
#Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#TransportMode.NIO;TransportMode.EPOLL(Linux);TransportMode.KQUEUE(macOS)
transportMode: "NIO"

或程序化配置:

Config config = new Config();
config.useSentinelServers()
    .setMasterName("mymaster")
    .addSentinelAddress("redis://127.0.0.1:26389", "redis://127.0.0.1:26379")
    .addSentinelAddress("redis://127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config);

4)集群配置

redisson-cluster.yml

# 集群模式
clusterServersConfig:
  # 如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,
  # 那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。默认10000
  idleConnectionTimeout: 1000
  # 同节点建立连接时的等待超时。时间单位是毫秒。默认10000
  connectTimeout: 1000
  # 等待节点回复命令的时间。该时间从命令发送成功时开始计时。默认3000
  timeout: 1000
  # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。
  # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。
  retryAttempts: 3
  # 在一条命令发送失败以后,等待重试发送的时间间隔。时间单位是毫秒。默认1500
  retryInterval: 1500
  # 失败从节点重连间隔时间
  failedSlaveReconnectionInterval: 3000
  # 失败从节点校验间隔时间
  failedSlaveCheckInterval: 60000
  # 用于节点身份验证的密码。默认null
  password: null
  # 每个连接的最大订阅数量。默认5
  subscriptionsPerConnection: 5
  # 在Redis节点里显示的客户端名称。默认null
  clientName: lizz
  # 在使用多个Redis服务节点的环境里,可以选用以下几种负载均衡方式选择一个节点:
  # org.redisson.connection.balancer.WeightedRoundRobinBalancer - 权重轮询调度算法
  # org.redisson.connection.balancer.RoundRobinLoadBalancer - 轮询调度算法
  # org.redisson.connection.balancer.RandomLoadBalancer - 随机调度算法
  # 默认:RoundRobinLoadBalancer
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  # 用于发布和订阅连接的最小保持连接数(长连接)。Redisson内部经常通过发布和订阅来实现许多功能。
  # 长期保持一定数量的发布订阅连接是必须的。默认1
  subscriptionConnectionMinimumIdleSize: 1
  # 多从节点的环境里,每个从服务节点里用于发布和订阅连接的连接池最大容量。连接池的连接数量自动弹性伸缩。默认50
  subscriptionConnectionPoolSize: 50
  # 多从节点的环境里,每个从服务节点里用于普通操作(非 发布和订阅)的最小保持连接数(长连接)。
  # 长期保持一定数量的连接有利于提高瞬时读取反映速度。默认32
  slaveConnectionMinimumIdleSize: 32
  # 多从节点的环境里,每个从服务节点里用于普通操作(非 发布和订阅)连接的连接池最大容量。连接池的连接数量自动弹性伸缩。默认64
  slaveConnectionPoolSize: 64
  # 多从节点的环境里,每个主节点的最小保持连接数(长连接)。长期保持一定数量的连接有利于提高瞬时写入反应速度。默认32
  masterConnectionMinimumIdleSize: 32
  # 多主节点的环境里,每个主节点的连接池最大容量。连接池的连接数量自动弹性伸缩。 默认64
  masterConnectionPoolSize: 64
  # 设置读取操作选择节点的模式。 可用值为:
  # SLAVE - 只在从服务节点里读取。 默认
  # MASTER - 只在主服务节点里读取。
  # MASTER_SLAVE - 在主从服务节点里都可以读取。
  readMode: "SLAVE"
  # 设置订阅操作选择节点的模式。可用值为:
  # SLAVE - 只在从服务节点里订阅。默认
  # MASTER - 只在主服务节点里订阅。
  subscriptionMode: "SLAVE"
  # 集群节点地址
  nodeAddresses:
    - "redis://192.168.198.155:7000"
    - "redis://192.168.198.155:7001"
    - "redis://192.168.198.155:7002"
    - "redis://192.168.198.155:7003"
    - "redis://192.168.198.155:7004"
    - "redis://192.168.198.155:7005"
  # 对主节点变化节点状态扫描的时间间隔。单位是毫秒。
  scanInterval: 1000
  # ping连接间隔
  pingConnectionInterval: 0
  # 是否保持连接
  keepAlive: false
  #
  tcpNoDelay: false
# 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。默认当前处理核数量 * 2
threads: 8
# 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。
nettyThreads: 8
# Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。
# Redisson提供了多种的对象编码应用,以供大家选择:https://github.com/redisson/redisson/wiki/4.-data-serialization
# 默认 org.redisson.codec.MarshallingCodec,部分编码需要引入编码对于依赖jar包。
codec: !<org.redisson.codec.MarshallingCodec> {}
# 传输模式,默认NIO,可选参数:
# TransportMode.NIO,
# TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux)
# TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS)
transportMode: "NIO"

或程序化配置

Config config = new Config();
config.useClusterServers()
    .setScanInterval(2000) // cluster state scan interval in milliseconds
    .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
    .addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);

Redisson简单源码分析

当我们导入了redisson-spring-boot-starter 后,其实就是多了几个类

RedissonProperties

这是一个配置文件类,做一个映射,我们配置的config就是配置到这里了

RedissonAutoConfiguration

就是这个类进行配置注入了,并且会帮我们生成2个RedisTemplate,如果我们默认生成了的话就不会生成了。

Redisson API使用

Redisson配置完后,我们可以直接在要任何地方使用:

@Resource
private RedissonClient redissonClient;

RKeys的操作

@Test
public void testRKey() {
    RKeys keys = redissonClient.getKeys();
    //获取所有key值
    Iterable<String> allKeys = keys.getKeys();
    //获取所有模糊key值
    Iterable<String> foundedKeys = keys.getKeysByPattern("key");
    //删除多个key值
    long numOfDeletedKeys = keys.delete("obj1", "obj2", "obj3");
    //删除模糊key值
    long deletedKeysAmount = keys.deleteByPattern("test?");
    //随机获取key
    String randomKey = keys.randomKey();
    //当前多少key数
    long keysAmount = keys.count();
}

RBucket对象桶

Redisson 支持将对象作为value存入redis,被存储的对象事先必须要实现序列化接口Serializable。

实体对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestUser implements Serializable {
    private static final long serialVersionUID = -6400412815805267359L;
    private Integer id;
    private String name;
    private Integer age;
}
/**
 * RBucket 每一个很多方法都存在对象的异步操作方法
 **/
@Test
public void testRBucket() {
    TestUser testUser = new TestUser(1, "harvey", 32);
    TestUser testUser2 = new TestUser(2, "tom", 32);
    //设置
    RBucket<TestUser> bucket = redissonClient.getBucket("user:id:" + testUser.getId());
    bucket.set(testUser);
    //如果已存在值就不存进去,不存在就存进去
    bucket.trySet(testUser);

    //获取
    RBucket<TestUser> bucket2 = redissonClient.getBucket("user:id:" + testUser.getId());
    System.out.println(bucket2.remainTimeToLive());
    System.out.println(bucket2.get());

    //删除
    RBucket<TestUser> bucket3 = redissonClient.getBucket("user:id:" + testUser.getId());
    System.out.println(bucket3.delete());
    System.out.println(bucket3.getAndDelete());

    //批量-获得Buckets
    RBuckets buckets = redissonClient.getBuckets();
    Map<String, TestUser> userMap = new HashMap<>();
    userMap.put("user:id:" + testUser.getId(), testUser);
    userMap.put("user:id:" + testUser2.getId(), testUser2);
    buckets.set(userMap);
    //这里的兼具map的属性
    Map<String, TestUser> bucketsMap = buckets.get("user:id:" + testUser.getId(), "user:id:" + testUser2.getId());
    System.out.println(bucketsMap);
}

二进制流(Binary Stream)

Redisson的分布式RBinaryStream Java对象同时提供了InputStream接口和OutputStream接口的实现。流的最大容量受Redis主节点的内存大小限制。

@Test
public void testBinaryStream() throws IOException {
    RBinaryStream stream = redissonClient.getBinaryStream("stream");
    //设置流内容
    stream.set("name is ".getBytes());

    //写入流, 注意:不是覆盖是写入
    OutputStream outputStream = stream.getOutputStream();
    outputStream.write("victory".getBytes());

    //读取流
    InputStream inputStream = stream.getInputStream();
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    byte[] bytes = new byte[1024];
    int length;
    while ((length = inputStream.read(bytes)) != -1) {
        result.write(bytes, 0, length);
    }
    System.out.println(result.toString());
}

哈希操作

Redisson 支持通过RMap对象来操作哈希数据结构。

@Test
public void testRMap() {
    //哈希操作
    RMap<String, String> rMap = redissonClient.getMap("mapkey");
    // 设置map中key-value
    rMap.put("id", "123");
    rMap.put("name", "赵四");
    rMap.put("age", "50");

    //设置过期时间
    rMap.expire(30, TimeUnit.SECONDS);
    // 通过key获取value
    System.out.println(redissonClient.getMap("mapkey").get("name"));
}

现在Redis没有过期清空Map中的某个entry的功能,只能是清空Map所有的entry。Redission提供了这种功能:

RMapCache<String, TestUser> map = redissonClient.getMapCache("anyMap");
// ttl = 10 minutes,
map.put("key1", new TestUser(), 10, TimeUnit.MINUTES);
// ttl = 10 minutes, maxIdleTime = 10 seconds
map.put("key1", new TestUser(), 10, TimeUnit.MINUTES, 10, TimeUnit.SECONDS);
// ttl = 3 seconds
map.putIfAbsent("key2", new TestUser(), 3, TimeUnit.SECONDS);
// ttl = 40 seconds, maxIdleTime = 10 seconds
map.putIfAbsent("key2", new TestUser(), 40, TimeUnit.SECONDS, 10, TimeUnit.SECONDS);

列表操作

Redisson 支持通过RList对象来操作列表数据结构。

@Test
public void testRList() {
    //字符串操作
    RList<TestUser> rList = redissonClient.getList("listkey");

    TestUser student1 = new TestUser();
    student1.setId(1);
    student1.setName("张三");
    student1.setAge(18);
    rList.add(student1);

    TestUser student2 = new TestUser();
    student2.setId(2);
    student2.setName("李四");
    student2.setAge(19);
    rList.add(student2);

    //设置过期时间
    rList.expire(30, TimeUnit.SECONDS);
    // 通过key获取value
    System.out.println(redissonClient.getList("listkey"));

}

集合操作

Redisson 支持通过RSet对象来操作集合数据结构。

@Test
public void testRSet() {
    //字符串操作
    RSet<TestUser> rSet = redissonClient.getSet("setkey");

    TestUser student1 = new TestUser();
    student1.setId(1);
    student1.setName("张三");
    student1.setAge(18);
    rSet.add(student1);

    TestUser student2 = new TestUser();
    student2.setId(2);
    student2.setName("李四");
    student2.setAge(19);
    rSet.add(student2);

    //设置过期时间
    rSet.expire(30, TimeUnit.SECONDS);
    // 通过key获取value
    System.out.println(redissonClient.getSet("setkey"));
}

有序集合操作

Redisson 支持通过RSortedSet对象来操作有序集合数据结构,在使用对象来存储之前,实体对象必须先实现Comparable接口,并重写比较逻辑,否则会报错。

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestUser implements Serializable, Comparable<TestUser> {
    private static final long serialVersionUID = -6400412815805267359L;
    private Integer id;
    private String name;
    private Integer age;

    @Override
    public int compareTo(TestUser obj) {
        return this.getId().compareTo(obj.getId());
    }
}
@Test
public void testRSortedSet() {
    //有序集合操作
    RSortedSet<TestUser> sortSetkey = redissonClient.getSortedSet("sortSetkey");

    TestUser student1 = new TestUser();
    student1.setId(1);
    student1.setName("张三");
    student1.setAge(18);
    sortSetkey.add(student1);

    TestUser student2 = new TestUser();
    student2.setId(2);
    student2.setName("李四");
    student2.setAge(19);
    sortSetkey.add(student2);

    // 通过key获取value
    System.out.println(redissonClient.getSortedSet("sortSetkey"));
}

布隆过滤器

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

Redisson 支持通过RBloomFilter对象来操作布隆过滤器.

@Test
public void testRBloomFilter() {
    RBloomFilter rBloomFilter = redissonClient.getBloomFilter("seqId");
    // 初始化预期插入的数据量为10000和期望误差率为0.01
    rBloomFilter.tryInit(10000, 0.01);
    // 插入部分数据
    rBloomFilter.add("100");
    rBloomFilter.add("200");
    rBloomFilter.add("300");
    //设置过期时间
    rBloomFilter.expire(30, TimeUnit.SECONDS);
    // 判断是否存在
    System.out.println(rBloomFilter.contains("300"));
    System.out.println(rBloomFilter.contains("200"));
    System.out.println(rBloomFilter.contains("999"));
}

分布式自增ID

ID 是数据的唯一标识,传统的做法是利用 UUID 和数据库的自增 ID。

但由于 UUID 是无序的,不能附带一些其他信息,因此实际作用有限。

随着业务的发展,数据量会越来越大,需要对数据进行分表,甚至分库。分表后每个表的数据会按自己的节奏来自增,这样会造成 ID 冲突,因此这时就需要一个单独的机制来负责生成唯一 ID,redis 原生支持生成全局唯一的 ID。

@Test
public void testId() {
    final String lockKey = "aaaa";
    //通过redis的自增获取序号
    RAtomicLong atomicLong = redissonClient.getAtomicLong(lockKey);
    //设置过期时间
    atomicLong.expire(30, TimeUnit.SECONDS);
    // 获取值
    System.out.println(atomicLong.incrementAndGet());
}

分布式锁

Redisson 最大的亮点,也是使用最多的功能,就是提供了强大的分布式锁实现,特点是:使用简单、安全!

@Test
public void testLock() {
    //获取锁对象实例
    final String lockKey = "abc";
    RLock rLock = redissonClient.getLock(lockKey);

    try {
        //尝试5秒内获取锁,如果获取到了,最长60秒自动释放
        boolean res = rLock.tryLock(5L, 60L, TimeUnit.SECONDS);
        if (res) {
            //成功获得锁,在这里处理业务
            System.out.println("获取锁成功");
        }
    } catch (Exception e) {
        System.out.println("获取锁失败,失败原因:" + e.getMessage());
    } finally {
        //无论如何, 最后都要解锁
        if(rLock.isLocked() && rLock.isHeldByCurrentThread()){
            rLock.unlock();
        }
    }
}

以上是单机环境下的分布式锁实现逻辑,如果是集群环境下,应该如何处理呢?

Redisson 提供RedissonRedLock操作类,也被称为红锁,实现原理简单的总结有以下几点:

  • 如果有多个 redis 集群的时候,当且仅当从大多数(N/2+1,比如有3个 redis 节点,那么至少有2个节点)的 Redis 节点都取到锁,并且获取锁使用的总耗时小于锁失效时间时,锁才算获取成功
  • 如果获取失败,客户端会在所有的 Redis 实例上进行解锁操作
  • 集群环境下,redis 服务器直接不存在任何复制或者其他隐含的分布式协调机制,否则会存在实效的可能
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.3.111:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.3.112:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.3.113:6379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);

//获取多个 RLock 对象
final String lockKey = "abc";
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);

//根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

try {
    //尝试5秒内获取锁,如果获取到了,最长60秒自动释放
    boolean res = redLock.tryLock(5L, 60L, TimeUnit.SECONDS);
    if (res) {
        //成功获得锁,在这里处理业务
        System.out.println("获取锁成功");

    }
} catch (Exception e) {
    System.out.println("获取锁失败,失败原因:" + e.getMessage());
} finally {
    //无论如何, 最后都要解锁
    redLock.unlock();
}

分布式读写锁ReadWriteLock

读写锁是针对于读写操作的互斥锁。它与普通的互斥锁最大的不同是,它可以分别针对读操作和写操作进行锁定和解锁操作。

  • 读锁:可以同时读, 但不允许写
  • 写锁:只允许一个写, 其他的不允许读也不允许写
//获取读写锁
RReadWriteLock rwlock = redissonClient.getReadWriteLock("anyRWLock");

//加锁
rwlock.readLock().lock();
rwlock.writeLock().lock();

// Lock time-to-live support
// releases lock automatically after 10 seconds
// if unlock method not invoked
rwlock.readLock().lock(10, TimeUnit.SECONDS);
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// Wait for 100 seconds and automatically unlock it after 10 seconds
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
//....

//释放锁
rwlock.readLock().unlock();
rwlock.writeLock().unlock();

整长型累加器(LongAdder)

基于Redis的Redisson分布式整长型累加器(LongAdder)采用了与java.util.concurrent.atomic.LongAdder类似的接口。通过利用客户端内置的LongAdder对象,为分布式环境下递增和递减操作提供了很高得性能。据统计其性能最高比分布式AtomicLong对象快 12000 倍。完美适用于分布式统计计量场景。

RLongAdder atomicLong = redissonClient.getLongAdder("myLongAdder");
atomicLong.add(12);
atomicLong.increment();
atomicLong.decrement();
atomicLong.sum();

当不再使用整长型累加器对象的时候应该自行手动销毁,如果Redisson对象被关闭(shutdown)了,则不用手动销毁。 

RLongAdder atomicLong = ...
atomicLong.destroy();

限流器(RateLimiter)

基于Redis的分布式限流器(RateLimiter)可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。该算法不保证公平性。除了同步接口外,还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RRateLimiter rateLimiter = redissonClient.getRateLimiter("myRateLimiter");
// 初始化
// 最大流速 = 每1秒钟产生10个令牌
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

CountDownLatch latch = new CountDownLatch(2);
limiter.acquire(3);
// ...

Thread t = new Thread(() -> {
    limiter.acquire(2);
    // ...        
});

分布式Semaphore

 Redisson 信号量可以用来做限流处理。

@Test
public void testRSemaphore() {
    //和锁一样  随便指定一个名字 只要名字相同获取的就是同一个信号量
    RSemaphore park = redissonClient.getSemaphore("park");
    //初始化RSemaphore之前,最好调用delete()方法,否则若已存在相同的信号量名,调用trySetPermits设置许可无论如何都不成功
    park.delete();
    //初始化RSemaphore,需要调用trySetPermits()设置许可数量:设置成功,返回true,否则返回false
    boolean success = park.trySetPermits(1);
    //park.acquire();//阻塞式方法  获取成功执行下面  获取不成功就一直在这一句卡住
    //占成功了 redis里这个信号量的值就会 减一
    boolean b = park.tryAcquire();
    if (b) {
        System.out.println("当前可用的许可数为:" + park.availablePermits());
        //执行业务
        System.out.println("park =>信号量有值 获取成功  可以执行业务");
        //执行成功后释放
        park.release();//执行这一句  信号量的值加一
        System.out.println("当前可用的许可数为:" + park.availablePermits());
    } else {
        System.out.println("park => 没有获取到信号量");
    }
}

分布式CountDownLatch

RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// in other thread or other JVM
RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
latch.countDown();

分布式Topic

@Test
public void testRTopic() {
    // 订阅此主题,当主题发布的时候进行调用回调函数
    RTopic topic = redissonClient.getTopic("anyTopic", new SerializationCodec());
    /**
         * * @param 消息类型
         * * @param 回调函数 
         *   回调函数第一个参数 为主题名称,第二个参数为 主题消息(消息内容)
         */
    topic.addListener(String.class, new MessageListener<String>() {
        @Override
        public void onMessage(CharSequence topic, String s) {
            System.out.println("主题频道为:" + topic);
            System.out.println("消息为:" + s);
        }
    });

    //发布主题
    publicRTopic();
}

public void publicRTopic() {
    //发布主题和订阅主题一般是在不同的JVM上操作的
    RTopic topic = redissonClient.getTopic("anyTopic", new SerializationCodec());
    // 发布主题
    TestUser testUser = new TestUser();
    testUser.setId(1);
    testUser.setName("harvey");
    testUser.setAge(33);
    topic.publish(testUser.toString());
}

分布式队列RBlockingQueue

还支持Queue, Deque, Blocking Deque。

RBlockingQueue<TestUser> queue = redisson.getBlockingQueue("anyQueue");
queue.offer(new TestUser());
TestUser obj = queue.peek();
TestUser someObj = queue.poll();
TestUser ob = queue.poll(10, TimeUnit.MINUTES);

延迟队列RDelayedQueue

延迟队列主要是用于处理延时任务,与定时任务存在的区别如下所示:

  • 定时任务有明确的触发时间,触发周期,而延时任务没有确定的时间,是随着业务去订立延时任务;
  • 定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务;

使用场景:

  • 生成订单30分钟未支付,则自动取消;
  • 生成订单60秒后,给用户发短信;
  • 邮件延时发送;

 也可以采用 RocketMQ 实现延时任务;

还有更多的功能,大家网上找一找,练习练习。

 

posted @ 2023-10-29 10:31  残城碎梦  阅读(269)  评论(0编辑  收藏  举报