访问redis

依赖

spring-boot-starter-data-redis

<dependency>   
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

使用与配置

程序只访问1个redis

如程序只需访问一个redis实例,则可以直接使用spring-boot自动创建的配置,无需配置代码,只需在配置文件中添加配置项。

配置文件

只需在配置文件中设置以下属性(部分属性有默认值,可不配)。

# 基本设置
spring.redis.database=0 # 数据库序号,默认为0
spring.redis.url= # 连接地址(如配置则忽略以下主机名、端口和密码的设置),如redis://user:password@example.com:6379
spring.redis.host=localhost # 主机名或地址,默认为localhost
spring.redis.port=6379 # 端口,默认为6379
spring.redis.password= # 密码
spring.redis.ssl=false # 是否开启ssl,默认不开启
# 连接池设置
spring.redis.pool.max-active=8 # 最大连接数,默认为8
spring.redis.pool.max-idle=8 # 最大idle连接数,默认为8
spring.redis.pool.max-wait=-1 # 连接池耗尽时请求最大等待时间(毫秒数),默认为-1(永久等待)
spring.redis.pool.min-idle=0 # 最小idle连接数,默认为0
# 高可用性设置(单redis实例不需配置)
spring.redis.sentinel.master= # sentinel的master
spring.redis.sentinel.nodes= # sentinel的节点列表,逗号分隔的"地址:端口"对
spring.redis.timeout=0 # 连接超时毫秒数
# 集群设置(单redis实例不需配置)
spring.redis.cluster.max-redirects= # 执行命令时最大重定向次数
spring.redis.cluster.nodes= # 集群节点列表,逗号分隔的"地址:端口"对

使用

使用类型为StringRedisTemplate的bean。

@Autowired
private StringRedisTemplate redisTemplate;
共通操作(不区分类型)
使用RestTemplate实例提供的各方法。

Long	delete(Collection<K> keys)
删除keys。
Boolean	delete(K key)
删除key。
Boolean	expire(K key, long timeout, TimeUnit unit)
设置key的有效时间(在经过timeout后失效)。
Boolean	expireAt(K key, Date date)
设置key的有效时间(在date这个时间点后失效)。
Long	getExpire(K key)
获取key的有效时间(秒)。
Long	getExpire(K key, TimeUnit timeUnit)
获取key的有效时间,并转换为以timeUnit为时间单位。
Boolean	hasKey(K key)
判断key是否存在。
Boolean	persist(K key)
删除key的失效时间。
K	randomKey()
Return a random key from the keyspace.
void	rename(K oldKey, K newKey)
将oldKey重命名为newKey。
Boolean	renameIfAbsent(K oldKey, K newKey)
仅当newKey不存在时,将oldKey重命名为newKey。
DataType	type(K key)
返回key的类型。
value类型操作
获得并使用ValueOperation<K, V>实例提供的各方法。

如:

1
redisTemplate.opsForValue().set(key, value);
方法列表:

Integer

append(K key, String value)

拼接value到key的值上。

String

get(K key, long start, long end)

获取key的值在begin和end间的子字符串。

V

get(Object key)

获取key的值。

V

getAndSet(K key, V value)

设置key的值为value,并返回key的旧值。

Boolean

getBit(K key, long offset)

获取key的offset处的位值。

RedisOperations<K,V>

getOperations() 

Double

increment(K key, double delta)

将存储在key下的浮点数递增delta。

Long

increment(K key, long delta)

将存储在key下的整数递增delta。

List<V>

multiGet(Collection<K> keys)

获取多个key的值。

void

multiSet(Map<? extends K,? extends V> map)

使用map中提供的键值对,设置多个key的值为多个value。

Boolean

multiSetIfAbsent(Map<? extends K,? extends V> map)

使用map中提供的键值对,在对应key不存在的前提下设置多个key的值为多个value。

void

set(K key, V value)

设置key的值为value。

void

set(K key, V value, long offset)

将key的值从offset之后的部分覆盖为value。

void

set(K key, V value, long timeout, TimeUnit unit)

设置key的值为value,并设置timeout后失效。

Boolean

setBit(K key, long offset, boolean value)

设置key的值的offset位为value。

Boolean

setIfAbsent(K key, V value)

如果key不存在,则设置key的值为value。

Long

size(K key)

获取key存储值的长度。
hash类型操作
获得并使用<HK, HV> HashOperation<K, HK, HV>实例提供的各方法。

如:

1
redisTemplate.opsForHash().hset(key, hashKey, hashValue);
方法列表:

Long	delete(H key, Object... hashKeys)
删除指定hash的hashKey。
Map<HK,HV>	entries(H key)
获取key存储的整个hash。
HV	get(H key, Object hashKey)
获取key中hash指定hashKey对应的值。
RedisOperations<H,?>	getOperations() 
Boolean	hasKey(H key, Object hashKey)
判断指定hashKey是否存在。
Double	increment(H key, HK hashKey, double delta)
将指定hashKey的值递增delta。
Long	increment(H key, HK hashKey, long delta)
将指定hashKey的值递增delta。
Set<HK>	keys(H key)
获取key的hash键集合。
List<HV>	multiGet(H key, Collection<HK> hashKeys)
获取key中指定hashKeys对应的值。
void	put(H key, HK hashKey, HV value)
设置hashKey对应的值。
void	putAll(H key, Map<? extends HK,? extends HV> m)
利用m提供的数据设置多个hash键对应的值。
Boolean	putIfAbsent(H key, HK hashKey, HV value)
仅当hashKey不存在时设置hashKey对应的值为value。
Cursor<Map.Entry<HK,HV>>	scan(H key, ScanOptions options)
使用Cursor迭代key中hash的所有条目。
Long	size(H key)
获取key中hash的大小。
List<HV>	values(H key)
获取key中hash的hash值集合。
list类型操作
获得并使用ListOperation<K, V>实例提供的各方法。

如:

1
Long value = redisTemplate.opsForList().leftPush(key, value);
方法列表:

RedisOperations<K,V>	getOperations() 
V	index(K key, long index)
获取list在index位置的元素。
V	leftPop(K key)
移除并返回list的第一个元素。
V	leftPop(K key, long timeout, TimeUnit unit)
移除并返回list的第一个元素,无数据时阻塞最多timeout时间。
Long	leftPush(K key, V value)
前插value到list中。
Long	leftPush(K key, V pivot, V value)
插入value到list中pivot前的位置。
Long	leftPushAll(K key, Collection<V> values)
前插values到list中。
Long	leftPushAll(K key, V... values)
前插values到list中。
Long	leftPushIfPresent(K key, V value)
如果list存在,则前插values到list中。
List<V>	range(K key, long start, long end)
获取list在begin和end间的元素。
Long	remove(K key, long count, Object value)
从list删除前count个值为value的元素。
V	rightPop(K key)
删除并返回list中最后一个元素。
V	rightPop(K key, long timeout, TimeUnit unit)
删除并返回list中最后一个元素,无数据时阻塞最多timeout时间。
V	rightPopAndLeftPush(K sourceKey, K destinationKey)
从sourceKey删除最后一个元素,附加到destinationKey,并返回它的值。
V	rightPopAndLeftPush(K sourceKey, K destinationKey, long timeout, TimeUnit unit)
从sourceKey删除最后一个元素,附加到destinationKey,并返回它的值。如果sourceKey无数据时阻塞最多timeout时间。
Long	rightPush(K key, V value)
将value附加到list。
Long	rightPush(K key, V pivot, V value)
将value附加到list中pivot之前。
Long	rightPushAll(K key, Collection<V> values)
将values附加到list。
Long	rightPushAll(K key, V... values)
将values附加到list。
Long	rightPushIfPresent(K key, V value)
仅当list存在时,将value附加到list。
void	set(K key, long index, V value)
设置list在index位置的值为value。
Long	size(K key)
获取list的大小。
void	trim(K key, long start, long end)
将list修剪为只剩start和end间的元素。

程序访问多个redis

如程序需访问多个不同的redis,则需要添加配置类,并在配置文件中添加对应的自定义配置项。

配置类
需编写一个注解为@Configuration的类,并在其中提供创建redisTemplate的方法(带@Bean注解的方法)。相关参数通过@Value注解从配置文件读取。

示例:@Configuration
public class RedisConfiguration {
 
    @Value("${record.redis.host}")
    private String recordRedisHost;
 
    @Value("${record.redis.port:6379}")
    private int recordRedisPort;
 
    @Value("${kpi.redis.host}")
    private String kpiRedisHost;
 
    @Value("${kpi.redis.port:6379}")
    private int kpiRedisPort;
 
    @Value("${record.redis.pool.max-active:8}")
    private int recordRedisMaxTotal;
 
    @Value("${kpi.redis.pool.max-active:8}")
    private int kpiRedisMaxTotal;
 
    @Bean(name = "recordRedisTemplate")
    public StringRedisTemplate recordRedisTemplate()
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(createConnectionFactory(recordRedisHost, recordRedisPort, recordRedisMaxTotal));
        return template;
    }
 
    @Bean(name = "kpiRedisTemplate")
    public StringRedisTemplate kpiRedisTemplate()
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(createConnectionFactory(kpiRedisHost, kpiRedisPort, kpiRedisMaxTotal));
        return template;
    }
 
    private JedisConnectionFactory createConnectionFactory(String host, int port, int poolMaxActive) {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new JedisPoolConfig());
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setPort(port);
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(poolMaxActive);
        jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }
 
}
# 本例中"record"和"kpi"是对两个redis的命名,分别表示用于录音和kpi指标的redis,使用中需按实际功能命名,以提高可读性
record.redis.host=192.168.150.233
record.redis.port=6379
record.redis.pool.max-active=10
kpi.redis.host=192.168.150.233
kpi.redis.port=6379
kpi.redis.pool.max-active=10
    
    使用
与访问1个redis实例时基本相同,只需按名称限定方式(@Qualifier注解)获取对应的StringRedisTemplate实例,进行操作即可。

示例(与上文中的配置类示例配合):
@Qualifier("recordRedisTemplate")
@Autowired
private StringRedisTemplate recordRedisTemplate;
  
@Qualifier("kpiRedisTemplate")
@Autowired
private StringRedisTemplate kpiRedisTemplate;

一.原子递增计数

private Long incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
        if (increment == 0 && liveTime > 0) {
            entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
        }
        return increment;
    }

    public Long transferCount(String batch) {
        String key = "count:" + batch;
        Long value = 0L;
        try {
            value = incr(key, 1);
        } catch (Exception e) {
            log.error("Redis连接异常,异常为e={}", e);
        }
        return value;
    }

二.查找key

public static List<String> keys(StringRedisTemplate redisTemplate, String pattern) {
    List<String> list = new LinkedList<String>();
    try {
        RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build();
        Cursor cursor = redisConnection.scan(options);
        while (cursor.hasNext()) {
            list.add(new String((byte[]) cursor.next()));
        }
        cursor.close();
    } catch (Exception e) {
        e.printStackTrace();
        // 异常时返回null,指示调用方重试
        return null;
    }
    return list;
}

三.有大量串行操作时,请使用pipeline

如果程序中有爆发性的大量串行redis操作,如某个web刷新页面,需要从redis读取超过100个key的值,请使用pipeline来提高响应速度。
原理:使用pipeline时,客户端一次发送多个命令,而redis按顺序处理每个命令后,先把结果缓存在本地内存,直到所有命令都处理完成,再一次性把结果返回给客户端。通过这种批量处理的方式,减少了数据包在客户端和redis之间往返的次数,降低了总时延,提高了吞吐量。
注意:使用pipeline时,redis需要在服务端缓存结果,从而耗费一定的额外内存,该内存的大小与pipeline内的命令数成正比,因此一次pipeline的命令数不宜过大,建议不超过10000。通过抓包实测,Spring Boot(或底层的jedis)自动会将大的pipeline切分为多个pipeline,因此不需在代码中手工控制pipeline大小。

List<RedisHashOp> redisHashOpList = ...;
try {
    redisTemplate.executePipelined(new RedisCallback<Object>() {
 
        @Override
        public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
             
            for (RedisHashOp redisHashOp : redisHashOpList) {
                redisConnection.hSet(redisHashOp.getKey().getBytes(), redisHashOp.getHashKey().getBytes(), redisHashOp.getValue().getBytes());
            }
            return null;
        }
    });
} catch (Exception e) {
    e.printStackTrace();
}