SpringBoot项目实战 - SpringBoot使用RedisTemplate

一、Redis入门 - 数据类型:5种基础数据类型详解

Redis所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash

1、Redis数据结构简介

Redis基础文章非常多,关于基础数据结构类型,我推荐你先看下官方网站内容 ,然后再看下面的小结

首先对redis来说,所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。
在这里插入图片描述

结构类型结构存储的值结构的读写能力
String字符串可以是字符串、整数或浮点数对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;
List列表一个链表,链表上的每个节点都包含一个字符串对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;
Hash散列包含键值对的无序散列表包含方法有添加、获取、删除单个元素
Set集合包含字符串的无序集合字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等
Zset有序集合和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

2、基础数据结构详解

1、String字符串

String是redis中最基本的数据类型,一个key对应一个value。

String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。

  • 图例
    下图是一个String类型的实例,其中键为hello,值为world
    在这里插入图片描述
  • 命令使用
命令简述使用
GET获取存储在给定键中的值GET name
SET设置存储在给定键中的值SET name value
DEL删除存储在给定键中的值DEL name
INCR将键存储的值加1INCR key
DECR将键存储的值减1DECR key
INCRBY将键存储的值加上整数INCRBY key amount
DECRBY将键存储的值减去整数DECRBY key amount
  • 命令执行
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> get counter
"2"
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> get counter
"3"
127.0.0.1:6379> incrby counter 100
(integer) 103
127.0.0.1:6379> get counter
"103"
127.0.0.1:6379> decr counter
(integer) 102
127.0.0.1:6379> get counter
"102"
  • 实战场景
  1. 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
  2. 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
  3. session:常见方案spring session + redis实现session共享

2、List列表

Redis中的List其实就是链表(Redis用双端链表实现List)。

使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。
在这里插入图片描述

  • 命令使用
命令简述使用
RPUSH将给定值推入到列表右端RPUSH key value
LPUSH将给定值推入到列表左端LPUSH key value
RPOP从列表的右端弹出一个值,并返回被弹出的值RPOP key
LPOP从列表的左端弹出一个值,并返回被弹出的值LPOP key
LRANGE获取列表在给定范围上的所有值LRANGE key 0 -1
LINDEX通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。LINEX key index
  • 使用列表的技巧
    lpush+lpop=Stack(栈)
    lpush+rpop=Queue(队列)
    lpush+ltrim=Capped Collection(有限集合)
    lpush+brpop=Message Queue(消息队列)

  • 命令执行

127.0.0.1:6379> lpush mylist 1 2 ll ls mem
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "mem"
2) "ls"
3) "ll"
4) "2"
5) "1"
127.0.0.1:6379> lindex mylist -1
"1"
127.0.0.1:6379> lindex mylist 10        # index不在 mylist 的区间范围内
(nil)
  • 实战场景
  1. 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
  2. 消息队列

3、Hash散列

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

在这里插入图片描述

  • 命令使用
命令简述使用
HSET添加键值对HSET hash-key sub-key1 value1
HGET获取指定散列键的值HGET hash-key key1
HGETALL获取散列中包含的所有键值对HGETALL hash-key
HDEL如果给定键存在于散列中,那么就移除这个键HDEL hash-key sub-key1
  • 命令执行
127.0.0.1:6379> hset user name1 hao
(integer) 1
127.0.0.1:6379> hset user email1 hao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
127.0.0.1:6379> hget user user
(nil)
127.0.0.1:6379> hget user name1
"hao"
127.0.0.1:6379> hset user name2 xiaohao
(integer) 1
127.0.0.1:6379> hset user email2 xiaohao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
5) "name2"
6) "xiaohao"
7) "email2"
8) "xiaohao@163.com"
  • 实战场景
  1. 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。

4、Set集合

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
在这里插入图片描述

  • 命令使用
命令简述使用
SADD向集合添加一个或多个成员SADD key value
SCARD获取集合的成员数SCARD key
SMEMBERS返回集合中的所有成员SMEMBERS key member
SISMEMBER判断 member 元素是否是集合 key 的成员SISMEMBER key member

其它一些集合操作,请参考这里https://www.runoob.com/redis/redis-sets.html

  • 命令执行
127.0.0.1:6379> sadd myset hao hao1 xiaohao hao
(integer) 3
127.0.0.1:6379> smembers myset
1) "xiaohao"
2) "hao1"
3) "hao"
127.0.0.1:6379> sismember myset hao
(integer) 1
  • 实战场景
  1. 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
  2. 点赞,或点踩,收藏等,可以放到set中实现

5、Zset有序集合

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

  • 图例
    在这里插入图片描述
  • 命令使用
命令简述使用
ZADD将一个带有给定分值的成员添加到有序集合里面ZADD zset-key 178 member1
ZRANGE根据元素在有序集合中所处的位置,从有序集合中获取多个元素ZRANGE zset-key 0-1 withccores
ZREM如果给定元素成员存在于有序集合中,那么就移除这个元素ZREM zset-key member1

更多命令请参考这里 https://www.runoob.com/redis/redis-sorted-sets.html

  • 命令执行
127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1) "xiaohao"
2) "hao"
127.0.0.1:6379> ZSCORE myscoreset hao
"100"
  • 实战场景
  1. 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

二、SpringBoot中RedisTemplate的使用

1、详解 RedisTemplate 的 API

RedisTemplate 为我们操作 Redis 提供了丰富的 API,可以将他们简单进行下归类。

1、常用数据操作

这一类 API 也是我们最常用的一类。

众所周知,redis 存在 5 种数据类型:

字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset)

而 redisTemplate 实现了 RedisOperations 接口,在其中,定义了一系列与 redis 相关的基础数据操作接口,数据类型分别于下来 API 对应:

非绑定key操作

ValueOperations<K, V> opsForValue();
<HK, HV> HashOperations<K, HK, HV> opsForHash();
ListOperations<K, V> opsForList();
SetOperations<K, V> opsForSet();
ZSetOperations<K, V> opsForZSet();

绑定key操作

BoundValueOperations<K, V> boundValueOps(K key);
<HK, HV> BoundHashOperations<K, HK, HV> boundHashOps(K key);
BoundListOperations<K, V> boundListOps(K key);
BoundSetOperations<K, V> boundSetOps(K key);
BoundZSetOperations<K, V> boundZSetOps(K key);
若以 bound 开头,则意味着在操作之初就会绑定一个 key,后续的所有操作便默认认为是对该 key 的操作,算是一个小优化。

2、几种数据结构操作的具体用法

2.1、String类型

设置当前的 key 以及 value 值

redisTemplate.opsForValue().set(key, value)

redisTemplate.opsForValue().set("num","123");

设置当前的 key 以及 value 值并且设置过期时间

redisTemplate.opsForValue().set(key, value, timeout, unit)

redisTemplate.opsForValue().set("num","123",10, TimeUnit.SECONDS);

TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒

将旧的 key 设置为 value,并且返回旧的 key(设置 key 的字符串 value 并返回其旧值)

redisTemplate.opsForValue().getAndSet(key, value);

在原有的值基础上新增字符串到末尾

redisTemplate.opsForValue().append(key, value)

获取字符串的长度

redisTemplate.opsForValue().size(key)

重新设置 key 对应的值,如果存在返回 false,否则返回 true

redisTemplate.opsForValue().setIfAbsent(key, value)

设置 map 集合到 redis

Map valueMap = new HashMap();
valueMap.put(“valueMap1”,“map1”);
valueMap.put(“valueMap2”,“map2”);
valueMap.put(“valueMap3”,“map3”);
redisTemplate.opsForValue().multiSet(valueMap);

如果对应的 map 集合名称不存在,则添加否则不做修改

Map valueMap = new HashMap();
valueMap.put(“valueMap1”,“map1”);
valueMap.put(“valueMap2”,“map2”);
valueMap.put(“valueMap3”,“map3”);
redisTemplate.opsForValue().multiSetIfAbsent(valueMap);

通过 increment(K key, long delta) 方法以增量方式存储 long 值(正值则自增,负值则自减)

redisTemplate.opsForValue().increment(key, increment);

批量获取值

public List<String> multiGet(Collection<String> keys) {
    return redisTemplate.opsForValue().multiGet(keys);
}

修改 redis 中 key 的名称

public void renameKey(String oldKey, String newKey) {
    redisTemplate.rename(oldKey, newKey);
}

如果旧值 key 存在时,将旧值改为新值

public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) { 
 return redisTemplate.renameIfAbsent(oldKey, newKey);
}

判断是否有 key 所对应的值,有则返回 true,没有则返回 false

redisTemplate.hasKey(key)

删除单个 key 值

redisTemplate.delete(key)

批量删除 key

redisTemplate.delete(keys) //其中keys:Collection<K> keys

设置过期时间

public Boolean expire(String key, long timeout, TimeUnit unit){
 return redisTemplate.expire(key, timeout, unit);
}
public Boolean expireAt(String key, Date date) { 
 return redisTemplate.expireAt(key, date);
}

返回当前 key 所对应的剩余过期时间

redisTemplate.getExpire(key);

返回剩余过期时间并且指定时间单位

public Long getExpire(String key, TimeUnit unit) {
    return redisTemplate.getExpire(key, unit);
}

查找匹配的 key 值,返回一个 Set 集合类型

public Set<String> getPatternKey(String pattern) { 
    return redisTemplate.keys(pattern);
}

将 key 持久化保存

public Boolean persistKey(String key) {
    return redisTemplate.persist(key);
}

将当前数据库的 key 移动到指定 redis 中数据库当中

public Boolean moveToDbIndex(String key, int dbIndex) {
    return redisTemplate.move(key, dbIndex);
}

2.2、Hash 类型

「Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。」 「Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。」

获取变量中的指定 map 键是否有值,如果存在该 map 键则获取值,没有则返回 null。

redisTemplate.opsForHash().get(key, field)

获取变量中的键值对

public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
}

新增 hashMap 值

redisTemplate.opsForHash().put(key, hashKey, value)

以 map 集合的形式添加键值对

public void hPutAll(String key, Map<String, String> maps) {
    redisTemplate.opsForHash().putAll(key, maps);
}

仅当 hashKey 不存在时才设置

public Boolean hashPutIfAbsent(String key, String hashKey, String value) { 
    return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}

删除一个或者多个 hash 表字段

public Long hashDelete(String key, Object... fields) {
    return redisTemplate.opsForHash().delete(key, fields);
}

查看 hash 表中指定字段是否存在

public boolean hashExists(String key, String field) { 
    return redisTemplate.opsForHash().hasKey(key, field);
}

给哈希表 key 中的指定字段的整数值加上增量 increment

public Long hashIncrBy(String key, Object field, long increment) {
    return redisTemplate.opsForHash().increment(key, field, increment);
}
public Double hIncrByDouble(String key, Object field, double delta) {
    return redisTemplate.opsForHash().increment(key, field, delta);
}

获取所有 hash 表中字段

redisTemplate.opsForHash().keys(key)

获取 hash 表中存在的所有的值

public List<Object> hValues(String key) {
    return redisTemplate.opsForHash().values(key);
}

获取 hash 表中字段的数量

redisTemplate.opsForHash().size(key)

匹配获取键值对,ScanOptions.NONE 为获取全部键对

public Cursor<Entry<Object, Object>> hashScan(String key, ScanOptions options) {
    return redisTemplate.opsForHash().scan(key, options);
}

2.3、List 类型

通过索引获取列表中的元素

redisTemplate.opsForList().index(key, index)

获取列表指定范围内的元素(start 开始位置, 0 是开始位置,end 结束位置, -1返回所有)

redisTemplate.opsForList().range(key, start, end)

存储在 list 的头部,即添加一个就把它放在最前面的索引处

redisTemplate.opsForList().leftPush(key, value)

把多个值存入 List 中(value 可以是多个值,也可以是一个 Collection value)

redisTemplate.opsForList().leftPushAll(key, value)

List 存在的时候再加入

redisTemplate.opsForList().leftPushIfPresent(key, value)

按照先进先出的顺序来添加(value 可以是多个值,或者是 Collection var2)

redisTemplate.opsForList().rightPush(key, value)
redisTemplate.opsForList().rightPushAll(key, value)

设置指定索引处元素的值

redisTemplate.opsForList().set(key, index, value)

移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)

redisTemplate.opsForList().leftPop(key)
redisTemplate.opsForList().leftPop(key, timeout, unit)

移除并获取列表最后一个元素

redisTemplate.opsForList().rightPop(key)
redisTemplate.opsForList().rightPop(key, timeout, unit)

从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边

redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey)
redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit)

删除集合中值等于 value 的元素(index=0, 删除所有值等于 value 的元素; index>0, 从头部开始删除第一个值等于 value 的元素; index<0, 从尾部开始删除第一个值等于 value 的元素)

redisTemplate.opsForList().remove(key, index, value)

将 List 列表进行剪裁

redisTemplate.opsForList().trim(key, start, end)

获取当前 key 的 List 列表长度

redisTemplate.opsForList().size(key)

2.4、Set 类型

添加元素

redisTemplate.opsForSet().add(key, values)

移除元素(单个值、多个值)

redisTemplate.opsForSet().remove(key, values)

获取集合的大小

redisTemplate.opsForSet().size(key)

判断集合是否包含 value

redisTemplate.opsForSet().isMember(key, value)

获取两个集合的交集(key 对应的无序集合与 otherKey 对应的无序集合求交集)

redisTemplate.opsForSet().intersect(key, otherKey)

获取多个集合的交集(Collection var2)

redisTemplate.opsForSet().intersect(key, otherKeys)

key 集合与 otherKey 集合的交集存储到 destKey 集合中(其中 otherKey 可以为单个值或者集合)

redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey)

key 集合与多个集合的交集存储到 destKey 无序集合中

redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey)

获取两个或者多个集合的并集(otherKeys 可以为单个值或者是集合)

redisTemplate.opsForSet().union(key, otherKeys)

key 集合与 otherKey 集合的并集存储到 destKey 中(otherKeys 可以为单个值或者是集合)

redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey)

获取两个或者多个集合的差集(otherKeys 可以为单个值或者是集合)

redisTemplate.opsForSet().difference(key, otherKeys)

差集存储到 destKey 中(otherKeys 可以为单个值或者集合)

redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey)

获取集合中的所有元素

redisTemplate.opsForSet().members(key)

随机获取集合中 count 个元素

redisTemplate.opsForSet().randomMembers(key, count)

随机获取集合中的一个元素

redisTemplate.opsForSet().randomMember(key)

遍历 set 类似于 Interator(ScanOptions.NONE 为显示所有的)

redisTemplate.opsForSet().scan(key, options)

2.5、zset 类型

ZSetOperations 提供了一系列方法对有序集合进行操作,添加元素(有序集合是按照元素的 score 值由小到大进行排列)。

redisTemplate.opsForZSet().add(key, value, score)

删除对应的 value,value 可以为多个值

redisTemplate.opsForZSet().remove(key, values)

增加元素的 score 值,并返回增加后的值

redisTemplate.opsForZSet().incrementScore(key, value, delta)

返回元素在集合的排名,有序集合是按照元素的 score 值由小到大排列

redisTemplate.opsForZSet().rank(key, value)

返回元素在集合的排名,按元素的 score 值由大到小排列

redisTemplate.opsForZSet().reverseRank(key, value)

获取集合中给定区间的元素(start 开始位置,end 结束位置, -1 查询所有)

redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end)

按照 Score 值查询集合中的元素,结果从小到大排序

redisTemplate.opsForZSet().reverseRangeByScore(key, min, max)

从高到低的排序集中获取分数在最小和最大值之间的元素

redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end)

根据 score 值获取集合元素数量

redisTemplate.opsForZSet().count(key, min, max)

获取集合的大小

redisTemplate.opsForZSet().size(key)

获取集合中 key、value 元素对应的 score 值

redisTemplate.opsForZSet().score(key, value)

移除指定索引位置处的成员

redisTemplate.opsForZSet().removeRange(key, start, end)

移除指定 score 范围的集合成员

redisTemplate.opsForZSet().removeRangeByScore(key, min, max)

获取 key 和 otherKey 的并集并存储在 destKey 中(其中 otherKeys 可以为单个字符串或者字符串集合)

redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey)

获取 key 和 otherKey 的交集并存储在 destKey 中(其中 otherKeys 可以为单个字符串或者字符串集合)

redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey)

遍历集合(和 iterator 一模一样)

Cursor<TypedTuple<Object>> scan = opsForZSet.scan("test3", ScanOptions.NONE); while (scan.hasNext()){
ZSetOperations.TypedTuple<Object> item = scan.next();
System.out.println(item.getValue() \+ ":" + item.getScore());
}

3、使用前配置:添加 Redis 依赖

<!-- 配置使用 redis 启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--springboot 集成 junit 起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.1.6.RELEASE</version>
    <scope>test</scope>
</dependency>

Spring Boot 中配置 Redis: application.properties

spring.redis.host=127.0.0.1
spring.redis.port=6379
# Redis 数据库索引(默认为 0)
spring.redis.database=0 
# Redis 服务器连接端口
# Redis 服务器连接密码(默认为空)
spring.redis.password=123456
#连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒) 如果连接超时时间不设置,这要注释掉配置而不能=0,否则会报连接超时错误:Command timed out after no timeout,,有超时时间最后设置为200以上
spring.redis.timeout=300

4、RedisTemplate 的配置

@Configuration
public class RedisConfig {

    /**
     * 默认是JDK的序列化策略,这里配置redisTemplate采用的是Jackson2JsonRedisSerializer的序列化策略
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
        //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);旧版本
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 配置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        //redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        // 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /***
     * stringRedisTemplate默认采用的是String的序列化策略
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
}

5、RedisTemplate

Spring Boot 的 spring-boot-starter-data-redis 为 Redis 的相关操作提供了一个高度封装的 RedisTemplate 类,而且对每种类型的数据结构都进行了归类,将同一类型操作封装为 operation 接口。RedisTemplate 对五种数据结构分别定义了操作,如下所示:

操作字符串:redisTemplate.opsForValue()
操作 Hash:redisTemplate.opsForHash()
操作 List:redisTemplate.opsForList()
操作 Set:redisTemplate.opsForSet()
操作 ZSet:redisTemplate.opsForZSet()
但是对于 string 类型的数据,Spring Boot 还专门提供了 StringRedisTemplate 类,而且官方也建议使用该类来操作 String 类型的数据。那么它和 RedisTemplate 又有啥区别呢?

RedisTemplate 是一个泛型类,而 StringRedisTemplate 不是,后者只能对键和值都为 String 类型的数据进行操作,而前者则可以操作任何类型。
两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中 的数据。
实践

package com.wts.javademo;

import com.alibaba.fastjson.JSON;
import net.minidev.json.JSONArray;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description: RedisTemplateTest
 * @Author: WangTianShun
 * @Date: 2022/4/30 14:53
 * @Version 1.0
 */
//用于指定junit运行环境,是junit提供给其他框架测试环境接口扩展,为了便于使用spring的依赖注入
@RunWith(SpringJUnit4ClassRunner.class)
//用于加载ApplicationContext,启动spring容器
@SpringBootTest
public class RedisTemplateTest {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 操作字符串
     */
    @Test
    public void testString() {
        // 设置值
        stringRedisTemplate.opsForValue().set("String", "wts");
        // 获取值
        String string = stringRedisTemplate.opsForValue().get("String");
        System.out.println("string======" + string);
        // 设置值且设置超时时间
        //TimeUnit.DAYS //天
        //TimeUnit.HOURS //小时
        //TimeUnit.MINUTES //分钟
        //TimeUnit.SECONDS //秒
        //TimeUnit.MILLISECONDS //毫秒
        stringRedisTemplate.opsForValue().set("Middle", "yu", 3, TimeUnit.MINUTES);
        String middle = stringRedisTemplate.opsForValue().get("Middle");
        System.out.println("middle======" + middle);
        // 删除数据
        Boolean isDelete = stringRedisTemplate.delete("String");
        System.out.println(isDelete ? "Yes" : "No");
    }

    /**
     * 操作列表
     */
    @Test
    public void testList() {
        ListOperations listOperations = redisTemplate.opsForList();
        // 往左侧插入一个元素
        listOperations.leftPush("nameList", "mike");
        listOperations.leftPush("nameList", "kin");
        // 往右侧插入一个元素
        listOperations.rightPush("nameList", "dsadk");
        listOperations.rightPush("nameList", "wtss");
        // list 大小
        Long size = listOperations.size("nameList");
        // 遍历整个list
        List nameList = listOperations.range("nameList", 0, size);
        List nameList1 = listOperations.range("nameList", 0, -1);
        System.out.println("遍历list==" + nameList1);
        // 从 List 左侧取出第一个元素,并移除
        Object name1 = listOperations.leftPop("nameList", 200, TimeUnit.MICROSECONDS);
        System.out.println("is chuck:" + name1.equals("kin"));
        //从 List 右侧取出第一个元素,并移除
        Object name2 = listOperations.rightPop("nameList", 200, TimeUnit.MICROSECONDS);
        System.out.println("is chuck:" + name2.equals("wtss"));
    }

    /**
     * 操作 Hash
     */
    @Test
    public void testHash() {
        // 添加泛型方便操作和返回想要的具体类型
        HashOperations<String, String, Integer> hashOp = redisTemplate.opsForHash();
        // hash 中新增元素
        hashOp.put("score", "mike", 0);
        hashOp.put("score", "Jimmy", 1);
        hashOp.put("score", "Kim", 2);
        // 判断指定key对应的Hash中是否存在指定的map键
        Assert.assertTrue(hashOp.hasKey("score", "Kim"));
        // 获取指定key对应的Hash中指定的值
        Integer kim = hashOp.get("score", "Kim");
        System.out.println("kim的分数=" + kim);
        // 获取hash表所有的key集合
        Set<String> keys = hashOp.keys("score");
        System.out.println("keys:" + JSON.toJSONString(keys));
        // 获取"score"对应的hash表map
        Map<String, Integer> maps = hashOp.entries("score");
        System.out.println("maps:" + JSON.toJSONString(maps));
        // 删除指定 key 对应 Hash 中指定键的键值对
        hashOp.delete("score", "Mike");
        // 如果要删除整个hash表,要用redisTemplate.delete("score")方法,否则报错:Fields must not be empty
        // hashOp.delete("score");
        // 删除整个hash表
        redisTemplate.delete("score");
        Map<String, Integer> map1 = hashOp.entries("score");
        System.out.println(JSON.toJSONString(map1));
    }

    /**
     * 操作集合
     */
    @Test
    public void testSet() {
        SetOperations<String, String> setOp = redisTemplate.opsForSet();
        //向集合中添加元素,set元素具有唯一性
        setOp.add("city", "quanzhou", "newyork", "paris", "hongkong", "hongkong");
        Long size = setOp.size("city");
        System.out.println("city的size" + size);
        // 获取集合中的元素
        Set<String> city = setOp.members("city");
        System.out.println(JSON.toJSONString(city));
        // 移除集合中的元素
        Long remove = setOp.remove("city", "paris");
        // 判断是否是集合中的元素
        Boolean isSuccess = setOp.isMember("city", "paris");
        System.out.println("paris in city:" + isSuccess);
        // 移除并返回集合中的随机一个元素
        String city1 = setOp.pop("city");
        System.out.println(city1);
    }

    /**
     * 操作有序集合
     */
    @Test
    public void testZSet() {
        ZSetOperations<String, String> zSetOp = redisTemplate.opsForZSet();
        zSetOp.add("zcity", "beijing", 100);
        zSetOp.add("zcity", "shanghai", 95);
        zSetOp.add("zcity", "guangzhou", 75);
        zSetOp.add("zcity", "shenzhen", 85);
        //获取变量指定区间的元素。0, -1表示全部
        Set<String> zcity = zSetOp.range("zcity", 0, -1);
        System.out.println(JSON.toJSONString(zcity));
        //通过分数返回有序集合指定区间内的成员,其中有序集成员按分数值递增(从小到大)顺序排列
        Set<String> byScore = zSetOp.rangeByScore("zcity", 85, 100);
        System.out.println(JSON.toJSONString(byScore));
        //获取有序集合的成员数
        Long aLong = zSetOp.zCard("zcity");
        System.out.println("zcity size: " + aLong);

        ZSetOperations<String, Integer> zSetOp1 = redisTemplate.opsForZSet();
        zSetOp1.add("board", 1, 100);
        zSetOp1.add("board", 2, 100);
        zSetOp1.add("board", 3, 100);
        zSetOp1.add("board", 4, 100);
        Set<Integer> board = zSetOp1.range("board", 0, -1);
        System.out.println(JSON.toJSONString(board));
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        //less than
        range.lt("3");
        RedisZSetCommands.Limit limit = new RedisZSetCommands.Limit();
        limit.count(1);
        limit.offset(1);
        //用于获取满足非 score 的排序取值。这个排序只有在有相同分数的情况下才能使用,如果有不同的分数则返回值不确定。
        //rangeByLex应用在数值上比较
        Set<Integer> set = zSetOp1.rangeByLex("board", range);
        System.out.println(JSON.toJSONString(set));
        //用于获取满足非 score 的设置下标开始的长度排序取值。
        Set<Integer> setlmt = zSetOp1.rangeByLex("board", range, limit);
        System.out.println(JSON.toJSONString(setlmt));
    }

    /**
     * 分布式锁
     */
    public Boolean lock(String key, String value, Long timeout, TimeUnit timeUnit) {
        Boolean lockStat = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.set(key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")),
                        Expiration.from(timeout, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT));
        return lockStat;
    }

    public Boolean unLock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        boolean unLockStat = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1,
                        key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8"))));
        return unLockStat;
    }

    @Test
    public void testLock() {
        String value = UUID.randomUUID().toString();
        Boolean lock = lock("buy", value, 120L, TimeUnit.SECONDS);
        if (!lock) {
            System.out.println("can't lock buy");
        }
        try {
            Thread.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Boolean unLock = unLock("buy", value);
        if (!unLock) {
            System.out.println("can't unlock buy");
        }
    }

    @Test
    public void boundTest() {
        BoundListOperations bound = redisTemplate.boundListOps("bound");
        bound.leftPush("haha");
        bound.rightPush("hehe");
        List list = bound.range(0, -1);
        System.out.println(JSON.toJSONString(list));
    }
}

redis工具类

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 判断 key 是否存在
     *
     * @param key 键
     * @return 如果存在 key 则返回 true,否则返回 false
     */
    public Boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }


    /**
     * 获取 Key 的类型
     *
     * @param key 键
     */
    public String type(String key) {
        DataType dataType = redisTemplate.type(key);
        assert dataType != null;
        return dataType.code();
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return 30
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除 key
     *
     * @param key 键
     */
    public Long delete(String... key) {
        if (key == null || key.length < 1) {
            return 0L;
        }
        return redisTemplate.delete(Arrays.asList(key));
    }

    /**
     * 获取所有的keys
     *
     * @return
     */
    public Set<String> keys() {
        Set<String> keys1 = redisTemplate.keys("*");

        return keys1;
    }

    /**
     * 获取所有的keys得所有的值
     *
     * @param keys
     * @return
     */
    public HashMap<Object, Object> getKeysValue(String keys) {

        Set<String> keys1 = redisTemplate.keys("*");

        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
        for (String s : keys1) {
            Object o = redisTemplate.opsForValue().get(keys + s);
            System.out.println("o=" + o);
            hashMap.put(keys1, o);
        }
        return hashMap;
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {

                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }

        }

    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */

    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return 274
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return 285
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return 295
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)

                expire(key, time);

            return count;

        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> getList(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */

    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */

    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;

        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {

        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;

        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

报错1:

Caused by: io.lettuce.core.RedisCommandExecutionException: ERR Client sent AUTH, but no password is set
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)
	
A:这是因为服务器没有设置密码,客户端设置了密码去连接,导致连接失败。我们在启动服务器时通过命令(config set requirepass "123456")手动设置了密码,一旦Redis服务器关闭后,重新启动加载了Redis启动的配置,因为启动配置菜单(redis-server.exe redis.windows.conf)中没有配置密码,所以重新启动的redis是没有设置密码的,这样就会报错。
解决方法:如果要设置密码则要在Redis启动的配置文件中修改添加;或者如果不设置密码则客户端连接时application.properties不要设置密码连接
# Redis 服务器连接密码(默认为空)
#spring.redis.password=123456

报错2:

org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after no timeout at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
A:这是因为客户端连接时application.properties中设置了连接超时时间为0导致的.
解决方法:
# 连接超时时间(毫秒) 如果连接超时时间不设置,这要注释掉配置而不能=0,否则会报连接超时错误:Command timed out after no timeout,有超时时间最后设置为200以上
spring.redis.timeout=300
org.springframework.data.redis.RedisSystemException: Unknown redis exception; nested exception is java.lang.IllegalArgumentException: Fields must not be empty at org.springframework.data.redis.FallbackExceptionTranslationStrategy.getFallback(FallbackExceptionTranslationStrategy.java:53)

A:如果要删除整个hash表,要用redisTemplate.delete("score")方法,否则报错:Fields must not be empty
解决方法:使用redisTemplate方法,HashOperations的delete是用于删除hash表字段用
		HashOperations<String,String,Integer> hashOp = redisTemplate.opsForHash();
        //hashOp.delete("score");
        redisTemplate.delete("score");

关于 Redis 的几个经典问题

缓存与数据库一致性问题

对于既有数据库操作又有缓存操作的接口,一般分为两种执行顺序。

先操作数据库,再操作缓存。这种情况下如果数据库操作成功,缓存操作失败就会导致缓存和数据库不一致。
第二种情况就是先操作缓存再操作数据库,这种情况下如果缓存操作成功,数据库操作失败也会导致数据库和缓存不一致。
大部分情况下,我们的缓存理论上都是需要可以从数据库恢复出来的,所以基本上采取第一种顺序都是不会有问题的。针对那些必须保证数据库和缓存一致的情况,通常是不建议使用缓存的。
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存雪崩问题
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

缓存击穿问题
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案

  1. 加互斥锁,。当从缓存中获取数据失败时,给当前接口加上锁,从数据库中加载完数据并写入后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。(数据库取数据时加锁)
  2. 使用布隆过滤器。将所有可能存在的数据缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及 DB 挂掉。

缓存并发问题
这里的并发指的是多个 Redis 的客户端同时 set 值引起的并发问题。
解决办法:
把 set 操作放在队列中使其串行化,必须得一个一个执行。

brpop : block right pop 
BRPOP key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot提供了对Redis的支持,可以使用RedisTemplate来操作Redis数据库。RedisTemplateSpring提供的一个对Redis操作的模板类,它封装了Redis的常见操作,可以方便地进行Redis数据的读写。 下面是一个使用RedisTemplate操作Redis的示例: 1. 配置Redis连接信息 在application.properties或application.yml文件中添加Redis连接信息: ``` spring.redis.host=localhost spring.redis.port=6379 ``` 2. 创建RedisTemplateSpring Boot中,可以通过注入RedisConnectionFactory来创建RedisTemplate。 ``` @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } } ``` 3. 使用RedisTemplate操作Redis 通过RedisTemplate可以进行Redis的常见操作,例如: ``` @Autowired private RedisTemplate<String, Object> redisTemplate; // 写入缓存 redisTemplate.opsForValue().set("key", "value"); // 读取缓存 Object value = redisTemplate.opsForValue().get("key"); // 删除缓存 redisTemplate.delete("key"); ``` 除了opsForValue()方法外,RedisTemplate还提供了opsForHash()、opsForList()、opsForSet()、opsForZSet()等方法,分别用于操作Redis的哈希、列表、集合、有序集合等数据结构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值