Redis五大数据类型
Redis的介绍
Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。它支持多种类型的数据结构,如 字符串(string),散列(hashes),列表(list),集合(sets),有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询。Redis内置了 复制(replocation),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence),并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)
Redis常用类型
- string String
- hash HashMap
- list LinkedList
- set HashSet
- sorted_set TreeSet
redis 数据存储格式
- redis自身是一个Map,其中所有的数据都是采用key:value的形式存储
- 数据类型指的是存储的数据的类型,也就是value部分的类型,key部分永远都是字符串
string
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 存储数据的格式:一个存储空间保存一个数据
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用
string 类型数据的基本操作
- 添加/修改数据
set key value
- 获取数据
get key
- 删除数据
del key
127.0.0.1:6379> set name maomao
OK
127.0.0.1:6379> get name
"maomao"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
- 添加/修改多个数据
mset key1 value1 key2 value2 …
- 获取多个数据
mget key1 key2 …
- 获取数据字符串长度(字符串个数)
strlan key
- 追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
append key value
127.0.0.1:6379> mset k1 a k2 b k3 c
OK
127.0.0.1:6379> mget k1 k2 k3
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> set name maomao
OK
127.0.0.1:6379> strlen name
(integer) 6
127.0.0.1:6379> append hello world
(integer) 5
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> append name zhu
(integer) 9
127.0.0.1:6379> get name
"maomaozhu"
string 类型数据的扩展操作
数据增减操作
业务场景
- 大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键 id 必须保证统一性,不能重复。Oracle 数据库具有 sequence 设定,可以解决该问题,但是 MySQL数据库并不具有类似的机制,那么如何解决?
解决方案
-
设置数值数据增加指定范围的值
incr key
incrby key increment
incrbyfloat key increment -
设置数值数据减少指定范围的值
decr key
decrby key increment
redis本身就是单线程的,所以可以用这种方式解决分布式ID的问题
127.0.0.1:6379> set num 0
OK
127.0.0.1:6379> incr num # 自增1
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"
127.0.0.1:6379> decr num # 自减1
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> decr num
(integer) -1
127.0.0.1:6379> get num
"-1"
127.0.0.1:6379> incrby num 10 # 可以设置步长 指定增量
(integer) 9
127.0.0.1:6379> incrby num 10
(integer) 19
127.0.0.1:6379> decrby num 9
(integer) 10
127.0.0.1:6379> incrbyfloat num 2.5 # 小数
"12.5"
127.0.0.1:6379> incrby num 3.5 # incrby只能是int类型
(error) ERR value is not an integer or out of range
string 作为数值操作
- string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
- redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发
带来的数据影响。 - 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。
9223372036854775807(java中long型数据最大值,Long.MAX_VALUE)
redis 用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性。适用于所有数据库,且支持数据库集群
数据时效性设置
业务场景
- 海选投票,只能通过微信投票,每个微信号每 4 小时只能投1票。
- 电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。
- 新闻网站会出现热点新闻,热点新闻最大的特征是时效性,如何自动控制热点新闻的时效性。
解决方案
-
设置数据具有指定的生命周期
setex key seconds value
psetex key milliseconds value -
设置独立的数据
setnx key value
127.0.0.1:6379> setex tel 10 '123456789' # 设置一个keys的值为num 10秒后过期
OK
127.0.0.1:6379> ttl tel # 生命周期
(integer) 6
127.0.0.1:6379> get tel
"123456789"
127.0.0.1:6379> ttl tel
(integer) 1
127.0.0.1:6379> get tel
(nil)
127.0.0.1:6379> setnx subject 'redis' # 如果key不存在 则创建相应的
(integer) 1
127.0.0.1:6379> setnx subject 'mongodb' # 如果key存在 则创建失败
(integer) 0
127.0.0.1:6379> get subject
"redis"
127.0.0.1:6379> psetex zhu 5000 'zhuzhu' # 毫秒
OK
127.0.0.1:6379> ttl zhu
(integer) 1
127.0.0.1:6379> get zhu
(nil)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k1
"v1"
redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
string类型数据的一些其他操作
-
获取字符串的范围
getrange key start end
-
替换指定范围的字符串
setrange key offset value
127.0.0.1:6379> set name 'hello,maomao'
OK
127.0.0.1:6379> get name
"hello,maomao"
127.0.0.1:6379> getrange name 0 3
"hell"
127.0.0.1:6379> getrange name 0 -1
"hello,maomao"
127.0.0.1:6379> setrange name 2 mmm
(integer) 12
127.0.0.1:6379> get name
"hemmm,maomao"
127.0.0.1:6379> setrange name 0 ilove
(integer) 12
127.0.0.1:6379> getrange name 0 -1
"ilove,maomao"
- 先get再set
getset key value
127.0.0.1:6379> getset db redis # 如果不存在值 则返回 nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongoDB # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongoDB"
- 储存一个对象
set user:1 {name:maomao,age:18} # 设置一个user:1 对象 值为 json字符来保存一个对象!
这里的key是一个巧妙地设计:user:{id}:{filed} , 如此设计在Redis中完全ok
127.0.0.1:6379> mset user:1:name maomao user:1:age 18 user:1:gender nv
OK
127.0.0.1:6379> mget user:1:name user:1:age user:1:gender
1) "maomao"
2) "18"
3) "nv"
string 类型数据操作的注意事项
- 数据操作不成功的反馈与数据正常操作之间的差异
- 表示运行结果是否成功
(integer) 0 → false 失败
(integer) 1 → true 成功 - 表示运行结果值
(integer) 3 → 3 3个
(integer) 1 → 1 1个
- 表示运行结果是否成功
- 数据未获取到
(nil)等同于null - 数据最大存储量
512MB - 数值计算最大范围(java中的long的最大值)
9223372036854775807
string 类型应用场景
主页高频访问信息显示控制,例如B站up主,主页显示粉丝数与,获赞数,播放数
- 在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可
- user: id:110:fans → 2389000
- user: id:110:likes → 9476000
- user: id:110:views → 480000000
- 在redis中以json格式存储up主信息,定时刷新(也可以使用hash类型)
eg: user: id:110 →
{“id”:110,“name”:“王老菊”,“fans”:2389000,“likes”:9476000, “views”:480000000}
127.0.0.1:6379> mset user:id:110:fans 2389000 user:id:110:likes 9476000 user:id:110:views 480000000OK
127.0.0.1:6379> mget user:id:110:fans user:id:110:likes user:id:110:views
1) "2389000"
2) "9476000"
3) "480000000"
127.0.0.1:6379> incr user:id:110:fans
(integer) 2389001
127.0.0.1:6379> incr user:id:110:views
(integer) 480000001
127.0.0.1:6379> incr user:id:110:views
(integer) 480000002
127.0.0.1:6379> mget user:id:110:fans user:id:110:likes user:id:110:views
1) "2389001"
2) "9476000"
3) "480000002"
127.0.0.1:6379> set user:id:120 {id:120,name:wanglaoju,fans:2389000,likes:9476000,views:480000000}OK
127.0.0.1:6379> get user:id:120
"{id:120,name:wanglaoju,fans:2389000,likes:9476000,views:480000000}"
key 的设置约定
- 数据库中的热点数据key命名惯例
表名 :主键名 :主键值 : 字段名
order : id : 294375951 : name
equip : id : 390472345 : type
news : id : 202004150 : title
hash
- 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
关于哈希表参考
大佬的文章
hash存储结构优化
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
hash 类型数据的基本操作
-
添加/修改数据
hset key field value
-
获取数据
hget key field
hgetall key -
删除数据
hdel key field1 [field2]
127.0.0.1:6379> hset user name xiaotian
(integer) 1
127.0.0.1:6379> hset user age 20
(integer) 1
127.0.0.1:6379> hset user gender nan
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "xiaotian"
3) "age"
4) "20"
5) "gender"
6) "nan"
127.0.0.1:6379> hget user name
"xiaotian"
127.0.0.1:6379> hget user gender
"nan"
127.0.0.1:6379> hdel user gender
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "xiaotian"
3) "age"
4) "20"
- 添加/修改多个数据
hmset key field1 value1 field2 value2 …
- 获取多个数据
hmget key field1 field2 …
- 获取哈希表中字段的数量
hlen key
- 获取哈希表中是否存在指定的字段
hexists key field
127.0.0.1:6379> hmget user name age
1) "xiaotian"
2) "20"
127.0.0.1:6379> hmset user name feifei age 22 weight 46
OK
127.0.0.1:6379> hgetall user
1) "name"
2) "feifei"
3) "age"
4) "22"
5) "weight"
6) "46"
127.0.0.1:6379> hlen user # 获取的是field的数量
(integer) 3
127.0.0.1:6379> hexists user age # 判断是否存在指定字段
(integer) 1
127.0.0.1:6379> hexists user height
(integer) 0
hash 类型数据扩展操作
- 获取哈希表中所有的字段名或字段值
hkeys key
hvals key
- 设置指定字段的数值数据增加指定范围的值
hincrby key field increment
hincrbyfloat key field increment
127.0.0.1:6379> hkeys user # 所有的字段名
1) "name"
2) "age"
3) "weight"
127.0.0.1:6379> hvals user # 所有的字段值
1) "feifei"
2) "22"
3) "46"
127.0.0.1:6379> hincrby user age 1
(integer) 23
127.0.0.1:6379> hincrby user weight 10 # 指定增量
(integer) 56
127.0.0.1:6379> hincrby user weight -5 # 指定减量
(integer) 51
hash 类型数据操作的注意事项
- hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的值为(nil)
- 每个 hash 可以存储 2^32 - 1 个键值对
- hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
- hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈
hash 类型应用场景
电商网站购物车设计与实现
业务分析
- 仅分析购物车的redis存储模型
- 添加、浏览、更改数量、删除、清空
- 购物车于数据库间持久化同步
- 购物车于订单间关系
- 提交购物车:读取数据生成订单
- 商家临时价格调整:隶属于订单级别
- 未登录用户购物车信息存储
- cookie存储
解决方案
- 以客户id作为key,每位客户创建一个hash存储结构存储对应的购物车信息
- 将商品编号作为field,购买数量作为value进行存储
- 添加商品:追加全新的field与value
- 浏览:遍历hash
- 更改数量:自增/自减,设置value值 删除商品:删除field
- 清空:删除key
id:001指用户id
g01 g02 指商品id
127.0.0.1:6379> hmset id:001 g01 100 g02 200
OK
127.0.0.1:6379> hmset id:002 g02 10 g04 8 g05 100
OK
127.0.0.1:6379> hset id:001 g03 5
(integer) 1
127.0.0.1:6379> hgetall id:001
1) "g01"
2) "100"
3) "g02"
4) "200"
5) "g03"
6) "5"
127.0.0.1:6379> hdel id:001 g01
(integer) 1
127.0.0.1:6379> hgetall id:001
1) "g02"
2) "200"
3) "g03"
4) "5"
127.0.0.1:6379> hincrby id:001 g03 3
(integer) 8
127.0.0.1:6379> hgetall id:001
1) "g02"
2) "200"
3) "g03"
4) "8"
但目前这样是不够的,当前仅仅是将数据存储到了redis中,并没有起到加速的作用,商品信息还需要二次查询数据库
解决方案
-
每条购物车中的商品记录保存成两条field
-
field1专用于保存购买数量
- 命名格式:商品id:nums
保存数据:数值
- 命名格式:商品id:nums
-
field2专用于保存购物车中显示的信息,包含文字描述,图片地址,所属商家信息等
- 命名格式:商品id:info
保存数据:json
- 命名格式:商品id:info
127.0.0.1:6379> hmset id:003 g01:nums 100 g01:info {diannao,8000}
OK
127.0.0.1:6379> hgetall id:003
1) "g01:nums"
2) "100"
3) "g01:info"
4) "{diannao,8000}"
127.0.0.1:6379> hmset id:004 g01:nums 50 g01:info {diannao,8000}
OK
127.0.0.1:6379> hgetall id:004
1) "g01:nums"
2) "50"
3) "g01:info"
4) "{diannao,8000}"
但这样又出现新的问题 就是g01:info是重复的,因此我们把field2作为独立hash 专门用来保存商品信息的hash
最后再做一个判断 ,如果field有值,则不添加,没有值就添加
hsetnx key field value
127.0.0.1:6379> hset id:004 g01:nums 200
(integer) 0
127.0.0.1:6379> hget id:004 g01:nums
"200"
127.0.0.1:6379> hsetnx id:004 g01:nums 400
(integer) 0
127.0.0.1:6379> hsetnx id:004 g05:nums 400
(integer) 1
127.0.0.1:6379> hgetall id:004
1) "g01:nums"
2) "200"
3) "g01:info"
4) "{diannao,8000}"
5) "g05:nums"
6) "400"
商家抢购活动
双11活动日,iphone商家对iPhone12,iPad Pro, MacBook Pro的5000元、6000元、10000元商品推出抢购活动,每种商
品抢购上限100台
解决方案
- 以商家id作为key
- 将参与抢购的商品id作为field
- 将参与抢购的商品数量作为对应的value
- 抢购时使用降值的方式控制产品数量
127.0.0.1:6379> hmset shop:001 iphone12 100 ipad 100 macbook 100
OK
127.0.0.1:6379> hincrby shop:001 iphone12 -1
(integer) 99
127.0.0.1:6379> hincrby shop:001 macbook -20
(integer) 80
127.0.0.1:6379> hgetall shop:001
1) "iphone12"
2) "99"
3) "ipad"
4) "100"
5) "macbook"
6) "80"