Redis所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash
Redis基础文章非常多,关于基础数据结构类型,我推荐你先看下官方网站内容 ,然后再看下面的小结
1、String字符串
String是redis中最基本的数据类型,一个key对应一个value。
-
使用列表的技巧
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列) -
命令执行
- 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
- 消息队列
3、Hash散列
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
- 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。
4、Set集合
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
其它一些集合操作,请参考这里https://www.runoob.com/redis/redis-sets.html
- 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
- 点赞,或点踩,收藏等,可以放到set中实现
5、Zset有序集合
- 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
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.1、String类型
设置当前的 key 以及 value 值
redisTemplate.opsForValue().set(key, value)
设置当前的 key 以及 value 值并且设置过期时间
redisTemplate.opsForValue().set(key, value, timeout, unit)
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);
批量获取值
修改 redis 中 key 的名称
如果旧值 key 存在时,将旧值改为新值
判断是否有 key 所对应的值,有则返回 true,没有则返回 false
删除单个 key 值
批量删除 key
设置过期时间
返回当前 key 所对应的剩余过期时间
返回剩余过期时间并且指定时间单位
查找匹配的 key 值,返回一个 Set 集合类型
将 key 持久化保存
将当前数据库的 key 移动到指定 redis 中数据库当中
2.2、Hash 类型
「Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。」 「Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。」
获取变量中的指定 map 键是否有值,如果存在该 map 键则获取值,没有则返回 null。
获取变量中的键值对
新增 hashMap 值
以 map 集合的形式添加键值对
仅当 hashKey 不存在时才设置
删除一个或者多个 hash 表字段
查看 hash 表中指定字段是否存在
给哈希表 key 中的指定字段的整数值加上增量 increment
获取所有 hash 表中字段
获取 hash 表中存在的所有的值
获取 hash 表中字段的数量
匹配获取键值对,ScanOptions.NONE 为获取全部键对
2.3、List 类型
通过索引获取列表中的元素
获取列表指定范围内的元素(start 开始位置, 0 是开始位置,end 结束位置, -1返回所有)
存储在 list 的头部,即添加一个就把它放在最前面的索引处
把多个值存入 List 中(value 可以是多个值,也可以是一个 Collection value)
List 存在的时候再加入
按照先进先出的顺序来添加(value 可以是多个值,或者是 Collection var2)
设置指定索引处元素的值
移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
移除并获取列表最后一个元素
从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边
删除集合中值等于 value 的元素(index=0, 删除所有值等于 value 的元素; index>0, 从头部开始删除第一个值等于 value 的元素; index<0, 从尾部开始删除第一个值等于 value 的元素)
将 List 列表进行剪裁
获取当前 key 的 List 列表长度
2.4、Set 类型
添加元素
移除元素(单个值、多个值)
判断集合是否包含 value
获取两个集合的交集(key 对应的无序集合与 otherKey 对应的无序集合求交集)
获取多个集合的交集(Collection var2)
key 集合与 otherKey 集合的交集存储到 destKey 集合中(其中 otherKey 可以为单个值或者集合)
key 集合与多个集合的交集存储到 destKey 无序集合中
获取两个或者多个集合的并集(otherKeys 可以为单个值或者是集合)
key 集合与 otherKey 集合的并集存储到 destKey 中(otherKeys 可以为单个值或者是集合)
获取两个或者多个集合的差集(otherKeys 可以为单个值或者是集合)
差集存储到 destKey 中(otherKeys 可以为单个值或者集合)
获取集合中的所有元素
随机获取集合中 count 个元素
随机获取集合中的一个元素
遍历 set 类似于 Interator(ScanOptions.NONE 为显示所有的)
2.5、zset 类型
ZSetOperations 提供了一系列方法对有序集合进行操作,添加元素(有序集合是按照元素的 score 值由小到大进行排列)。
删除对应的 value,value 可以为多个值
增加元素的 score 值,并返回增加后的值
返回元素在集合的排名,有序集合是按照元素的 score 值由小到大排列
返回元素在集合的排名,按元素的 score 值由大到小排列
获取集合中给定区间的元素(start 开始位置,end 结束位置, -1 查询所有)
按照 Score 值查询集合中的元素,结果从小到大排序
从高到低的排序集中获取分数在最小和最大值之间的元素
根据 score 值获取集合元素数量
获取集合中 key、value 元素对应的 score 值
移除指定索引位置处的成员
移除指定 score 范围的集合成员
获取 key 和 otherKey 的并集并存储在 destKey 中(其中 otherKeys 可以为单个字符串或者字符串集合)
获取 key 和 otherKey 的交集并存储在 destKey 中(其中 otherKeys 可以为单个字符串或者字符串集合)
遍历集合(和 iterator 一模一样)
Spring Boot 中配置 Redis: application.properties
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 中 的数据。
实践
redis工具类
报错1:
报错2:
关于 Redis 的几个经典问题
缓存与数据库一致性问题
对于既有数据库操作又有缓存操作的接口,一般分为两种执行顺序。
先操作数据库,再操作缓存。这种情况下如果数据库操作成功,缓存操作失败就会导致缓存和数据库不一致。
第二种情况就是先操作缓存再操作数据库,这种情况下如果缓存操作成功,数据库操作失败也会导致数据库和缓存不一致。
大部分情况下,我们的缓存理论上都是需要可以从数据库恢复出来的,所以基本上采取第一种顺序都是不会有问题的。针对那些必须保证数据库和缓存一致的情况,通常是不建议使用缓存的。
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存雪崩问题
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
缓存击穿问题
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案
- 加互斥锁,。当从缓存中获取数据失败时,给当前接口加上锁,从数据库中加载完数据并写入后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。(数据库取数据时加锁)
- 使用布隆过滤器。将所有可能存在的数据缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及 DB 挂掉。