本系列是Redis的入门系列第二部分的内容:主要介绍了Redis的数据类型:String、Hash、List、Set以及zset
Redis
Redis数据类型
Redis键key只能为字符串,而value则有很多数据类型
官方命令大全网址
官方指令网址
- String(字符串类型)
- hash(散列类型)
- List(列表类型)
- Set(集合类型)
- SortedSet(有序集合类型,简称zset)
命令不区分大小写,但是key区分大小写
String 类型
最简单的类型,就是普通的key和value。做简单的KV缓存。
实际应用场景
- 缓存功能:利用Redis作为缓存,配合其他数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度,以及降低后端数据库的压力
- 计数器:很多系统会使用Redis作为系统的实时计算器,可以快速实现计数和查询功能。而且最终的数据结果可以按照特定的时间落到数据库或者其他存储介质当中进行永久保存
- 共享用户Session:用户重新刷新一次界面,可能需要重新访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,这种模式下只需要保证Redis的高可用性,每次用户Session的更新和查询都可以快速完成,大大提高效率
指令
- 赋值:
SET key value - 取值:
GET key - 赋值并返回旧值:
GETSET key value
- 数值增减:
- - 前提条件:
- - - 当value是整数数据
- 数值增减都是原子操作
- - 递增数字
INCR key - 增加指定的整数
INCRBY key increment - 递减数字
DECR key - 减少指定的整数
DECRBY key increment - 仅当不存在时赋值(可以实现分布式锁的功能)
setnx key value
举例:
image
- 向尾部追加值:向键值的末尾追加value,如果键值不存在则设置该键为value。最后返回追加后字符串的总长度
APPEND key value - 获取字符串长度:不存在返回0
STRLEN key - 同时设置/获取多个键值
- - MSET key value key value …
- MGET key key …
- 应用场景之自增主键
- - 需求:商品编号采用INCR命令生成
- 实现:定义商品编号key:items:id
image
Hash 类型
Hash叫做散列类型,提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合等其他类型
- 赋值:
- - HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0
- HSET key field value
- 一次赋值多个
- - HMSET key field value [field value …]
- 当字段不存在时赋值
- - 类似于HSET,区别在于如果字段存在,该命令不执行任何操作
- HSETNX key field value
- 取值
- - HGET key field
- 一次获取多个值
- - HMSET key field [field …]
- 获取所有字段的值
- - HGETALL key
image
- 删除字段:可以删除一个或者多个字段、返回值是被删除的字段个数
- - HDEL key field [field …]
- 增加数字
- - HINCRBY key field increment
image
- 判断字段是否存在
- - HEXISTS key field
- 只获取字段名或字段值
- - HKEYS key
- HVALS key
- 获取字段数量
- - HKEN key
- 获取所有字段
- - HGETALL key
- 应用之存储商品信息
- - 注意事项:存在哪些对象数据,特别是对象属性经常发生增删改操作的数据
- 商品信息字段
- - - 【商品id,商品名称,商品描述,商品库存,商品好评】
- - 定义商品信息的key
- - - 商品id为1001的信息在Redis中的key为:【items.1001】
image
List类型
Redis的列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获取列表的某一个片段。
列表类型内容是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0/1,获取越接近两端的元素速度就越快。意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。
可以通过List存储一些列表型的数据结构,类似粉丝列表、文章评论
应用场景
- 消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计
- 文章列表或数据分页展示的内容
指令
- 向列表两端添加元素
- - 向列表左边添加元素
- - - LPUSH key value [value …]
- - 向列表右边添加元素
- - - RPUSH key value [value …]
- 查看列表:获取列表中的某一片段,将返回start、stop之间的所有元素(包括两端的元素),索引从0开始。索引可以是负数,“-1”代表最后一遍的一个元素
- - LRANGE key start stop
image
- 从列表两端弹出元素:先将列表左边的元素从列表中移除,然后返回被移除的元素值
- - LPOP key
- RPOP key
- 获取列表中元素的个数
- - LLEN key
- 删除列表中指定个数的值
- - LREM命令会删除列表中前count个数为value的元素,返回实际删除的元素个数
- 语法:LREM key count value
- - - count>0:从列表左边开始删除
- count<0:出列表右边开始删除
- count=0:删除所有值为value的元素
image
- 获取/设置指定索引的元素值
- - 获取指定索引的元素值
- - - LINDEX key index
- - 设置指定索引的元素值
- - - LSET key index value
- 向列表中插入元素:首先会在列表中从左到右查询值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面
- - LINSERT key BEFORE|AFTER pivot value
- 将元素从一个列表转移到另一个列表中
- - RPOPLPUSH source destination
- 应用之商品评论列表
- - 需求1:用户针对某一商品发表评论,一个商品会被不同的用户进行评论,存储商品评论时,要按时间顺序排序
- 需求2:用户在前端页面查询该商品的评论,需要按照时间顺序降序排序
- 思路:
- - - 使用list存储商品评论信息,key是该商品的id,value是商品评论信息商品编号为1001的商品评论key
image
Set类型
set类型即集合类型,其中的数据是不重复且没有顺序
集合类型 | 列表类型 | |
存储内容 | 至多232-1个字符串 | 至多232-1个字符串 |
有序性 | 否 | 是 |
唯一性 | 是 | 否 |
集合类型的常用操作是向集合中加入或者删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空散列表实现。
Redis还提供了多个集合之间的交集、并集、差集的运算
- 添加元素
- - SADD key member [member …]
- 删除元素
- - SREM key member [member …]
- 获取集合中的所有元素
- - SMEMBERS key
- 判断元素是否在集合中
- - SISMEMBER key member
- 集合运算命令
- - 差集运算
- - - SDIFF key [key …]
- - 交集运算
- - - SINTER key [key …]
- - 并集运算
- - - SUNION key [key …]
- 获取集合中的元素个数
- - SCARD key
- 从集合中弹出一个元素:由于集合是无序的,所有spop命令会从集合中随机选择一个元素弹出
- - SPOP key
SortedSet类型zset
在集合类型的基础上,有序集合为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在集合中,还能获取最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作
有序集合和列表
- 相同:
- - 有序
- 可以获得某一范围的元素
- 区别:
- - 列表是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多时,访问中间数据的速度会变慢
- 有序集合类型是使用散列实现的,所以即使是读取位于中间部分的数据也是很快的
- 列表中不能简单的调整某个元素的位置,但是有序集合可以(改分数)
- 有序集合比列表类型更加消耗内存
实际应用场景
- 排行榜:有序集合经典使用场景
- 做带权重的队列:
指令
- 添加元素:元素已存在会覆盖分数,返回值是新加入到集合中的元素个数
- - ZADD key score member [score member …]
- 获取排名在某个范围的元素列表:按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包括两端的元素)
- - ZRANGE key start stop [WITHSCORES] 需要获取分数在尾部加上WITHSCORES参数
image
- 获取元素的分数
- - ZSCORE key member
- 删除元素:移除有序集key中的一个或多个成员,不存在的成员将被忽略;当key存在但不是有序集类型时,返回错误
- - ZREM key member [member …]
- 获取指定分数范围的元素
- - ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
- 增加某个元素的分数:返回值是更改后的分数
- - ZINCRBY key increment member
- 获取集合中元素的数量
- - ZCARD key
- 获取指定分数范围内的元素的个数
- - ZCOUNT key min max
- 按照排名范围删除元素
- - ZREMRANGEBYRANK key start stop
- 获取元素的排名
- - 从小到大:
- - - ZRANK key member
- - 从大到小
- - - ZREVRANK key member
- 应用之商品销售排行榜
- - 需求:根据商品销售对商品进行排序
- 思路:定义商品销售排行榜(sorted set 集合),key为item:sellsort,分数为商品销售量
image
通用命令
- keys:用于查找所有符合给定模式pattern的key可以使用*
- - keys pattern
但是Redis是单线程的,使用keys会导致线程阻塞一段时间,线上的服务会停顿,直到执行完毕。这个时候可以使用scan指令,scan命令可以无阻塞的提取指定模式的key列表,但是会有一定程度的重复概率,需要在客户端去重。速度较慢
- del
- - del key
- exists:确认一个key是否存在
- - exists key
- expire
EXPIRE key seconds
设置key的生存时间(单位:秒)key 在多少秒后会自动删除
TLL key
查看key剩余的生存时间
PERSIST key
清除生存时间
PEXPIRE key milliseconds
生存时间设置单位为:秒
- image
- rename
- - rename oldkey newkey
- type
- - type key
使用场景
类型 | 使用场景 |
String | 缓存、限流、计数器、分布式锁、分布式Session |
Hash | 存储用户信息、用户主页访问量、组合查询 |
List | 微博关注人时间轴列表、简单队列 |
Set | 赞、踩、标签、好友关系 |
Zset | 排行榜 |
还有简单消息队列,发布订阅实施消息系统等,
String-缓存
1// 1.Cacheable 注解
2// controller 调用 service 时自动判断有没有缓存,如果有就走redis缓存直接返回,如果没有则数据库然后自动放入redis中
3// 可以设置过期时间,KEY生成规则 (KEY生成规则基于 参数的toString方法)
4@Cacheable(value = "yearScore", key = "#yearScore")
5@Override
6public List findBy (YearScore yearScore) {} 7 8// 2.手动用缓存 9if (redis.hasKey(???) {10 return ....11} 1213redis.set(find from DB)...
String-限流|计数器
1// 计数器也是利用单线程incr...等等
2@RequestMapping("/redisLimit")
3public String testRedisLimit(String uuid) {
4 if (jedis.get(uuid) != null) {
5 Long incr = jedis.incr(uuid);
6 if (incr > MAX_LIMITTIME) {
7 return "Failure Request";
8 } else {
9 return "Success Request";
10 }
11 }
12
13 // 设置Key 起始请求为1,10秒过期 -> 实际写法肯定封装过,这里就是随便一写
14 jedis.set(uuid, "1");
15 jedis.expire(uuid, 10);
16 return "Success Request";
17}
String-分布式锁
1/*** 2 * 核心思路: 3 * 分布式服务调用时setnx,返回1证明拿到,用完了删除,返回0就证明被锁,等... 4 * SET KEY value [EX seconds] [PX milliseconds] [NX|XX] 5 * EX second:设置键的过期时间为second秒 6 * PX millisecond:设置键的过期时间为millisecond毫秒 7 * NX:只在键不存在时,才对键进行设置操作 8 * XX:只在键已经存在时,才对键进行设置操作 9 *10 * 1.设置锁11 * A. 分布式业务统一Key12 * B. 设置Key过期时间13 * C. 设置随机value,利用ThreadLocal 线程私有存储随机value14 *15 * 2.业务处理16 * ...17 *18 * 3.解锁19 * A. 无论如何必须解锁 - finally (超时时间和finally 双保证)20 * B. 要对比是否是本线程上的锁,所以要对比线程私有value和存储的value是否一致(避免把别人加锁的东西删除了)21 */
22@RequestMapping("/redisLock")
23public String testRedisLock () {
24 try {
25 for(;;){
26 RedisContextHolder.clear();
27 String uuid = UUID.randomUUID().toString();
28
29 String set = jedis.set(KEY, uuid, "NX", "EX", 1000);
30 RedisContextHolder.setValue(uuid);
31
32 if (!"OK".equals(set)) {
33 // 进入循环-可以短时间休眠
34 } else {
35 // 获取锁成功 Do Somethings....
36 break;
37 }
38 }
39 } finally {
40 // 解锁 -> 保证获取数据,判断一致以及删除数据三个操作是原子的, 因此如下写法是不符合的
41 /*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {42 jedis.del(KEY);43 }*/
44
45 // 正确姿势 -> 使用Lua脚本,保证原子性
46 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
47 Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue()));
48 }
49 return "锁创建成功-业务处理成功";
50}
最后
- 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
- 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。