Redis基础数据类型
Redis存放的是key-value形式的数据,其中key总是string类型,而value则分为5种类型,如下:
☐ string
☐ hash
☐ list
☐ set
☐ zset
Redis通用命令
通用命令适用于所有redis类型, 其实通用命令,是专门操作key的。
# 心跳命令
ping
# 查看数据库中的key-value数量
dbsize
# 切换数据库
select 2
# 移动键值到其他数据库
move key db
# 查看key对应的value的类型
type key
# 随机返回当前db中的一个key
randomkey
# 修改键名
rename 原名 新名
# 删除键
del key
# 清空当前数据库
flushdb
# 清空所有数据库
flushall
# 查看所有键
keys *
# 查看所有以k开头的键
keys k*
# 设置键的过期时间(秒)
expire 键名 秒数
# 查看键的剩余存活时间(秒)
ttl key
# 取消键的过期时间
persist key
# 判断键是否存在
exists key
String类型
# 设置key-value,如果value中有空格,则整个value要用" "引起来
set key value
get key
# 追加值
append key value
# 批量设置key-value
mset key value key value key value
# 批量获取key对应的value
mget key key key
# 设置key的值,并返回旧value
getset key value
del key
# 设置key-value同时,指定key的过期时间
setex key second value
# 设置key-value,如果key已经存在则该命令无效
setnx key value
# 只有键值存在,才会设置成功(只用于覆盖,不能创建新键值)
set key value xx
# 只有键值不存在,才会设置成功(等价于setnx,只用于创建新键值,不能覆盖旧key)
set key value nx
# 自增1,当value为数字型的字符串时才有效
# 如果key不存在,则创建key,且value默认为0,然后自加为1
incr key
# 自减1,当value为数字型的字符串时才有效
# 如果key不存在,则创建key,且value默认为0,然后自减为-1
decr key
incrby key increment
decrby key decrement
String类型的应用场景
☐ 最常见的就是存储对象的json格式
set user:1 "{'id': 1, 'name': 'andy'}"
set user:2 "{'id': 2, 'name': 'eason'}"
☐ 计数器
# 统计网页浏览次数
incr pageurl
# 统计文章点赞数
incr article:1001
Hash类型
Hash类型的特点是,键值对中的值本身也是一个键值对结构
hset key field value
hmset key field value [field2 value2 ...]
hget key field
hmget key fields
hgetall key
hdel key field
del key 删除整个hash
hlen key
hkeys key
hincrby key field increment (注意,没有hdecriby)
hexists key field
Hash类型应用场景
☐ 存储对象
我们已经知道,string类型也可以存储对象。为何还要使用hash类型来存储对象呢?比如我们要存储一些用户信息如下:
userid | name | age |
1 | andy | 18 |
2 | eason | 19 |
3 | cindy | 20 |
使用string存储用户信息:
set user:1:name andy
set user:1:age 18
优点:每个属性都支持独立的更新操作
缺点:占用更多的键,内存占用量较大。用户信息分散,读取用户的所有信息比较麻烦。
使用string存储对象的序列化形式
set user:1 "{'userid': 1, 'name': andy, 'age': 18}"
优点:便于一次性读取出用户的所有信息,占用内存少。
缺点:更新任何一个属性,都要重新序列化整个对象;读取数据时还要进行反序列化。
使用hash存储用户信息:
hmset user:1 name andy age 18
优点:既可以通过hmget key一次性获取某个用户的所有信息,也可以通过hset key field value来单独更新对象的某个属性。
缺点:hash底层涉及编码转换。
但是在实际开发当中,开发者们经常使用string存对象。因为有框架,框架把那些复杂的操作都封装了。
☐ 实现购物车
# 给购物车中添加商品
hincrby cart:1001 pid:1 1
# 获取购物车中商品数
hlen cart:1001
# 从购物车中删除某个商品
hdel cart:1001 pid:1
☐ 计数器
string类型可以用作计数器,hash也经常作为计数器
# 记录博客文章每月访问量
hincrby article:1 2024-02 1
# 记录商品的赞、踩数量
hincrby pid:1 Good 1
hincrby pid:1 Bad 1
List类型
list底层是双向链表。所以Redis中,List类型的特点是有序,可重复,且增删效率高,查询效率低。
lpush list1 a b c d 在链表头加入元素 --> d c b a
rpush list2 a b c d 在链表尾加入元素 --> a b c d
注意,不同元素之间要用空格隔开,而不是逗号!
linsert list before|after foo bar 在list中foo元素之前|之后插入bar元素
lrange list1 start end
其中start从0开始,查询结果包含start和end
其中start和end也可以写成负数,-1表示倒数第1个元素,-2 表示倒数第2个元素
llen list 获取列表中元素的个数
lindex list 0 获取列表中指定下标的元素
lset list 2 foo 将list中,下标为2的元素内容替换成foo
rpoplpush list list2 将list中的末尾元素弹出,并插入到list2的开头
ltrim list <start> <end> 截取指定范围的值后,再把截取出的值赋给list
lpop 删除并返回链表左侧的元素
rpop 删除并返回链表右侧的元素
blpop key timeout
删除并返回链表左侧的元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,则一直阻塞等待
brpop key timeout
删除并返回链表右侧的元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,则一直阻塞等待
lrem list 0 a 删除list中的所有a元素
lrem list 1 a 从左向右,删除list中的1个a元素
lrem list -2 a 从右向左,删除list中的2个a元素
List应用场景
☐ 实现队列
lpush + rpop
☐ 实现栈
lpush + lpop
☐ 利用List提升网站首页并发量
对于一个网站而言,并发量最高的就是首页了,所以很多网站会把首页数据缓存到redis中。
lpush + ltrim
关于redis中的list数据类型要注意的地方
1. 它是一个双向链表,可以从left或right端添加值
2. 如果键不存在,则创建新的链表
3. 如果键已存在,则新增内容
4. 如果值全部移除,则对应的键也就消失了
5. 操作链表中的头元素和尾元素效率都很高,但是如果操作的是链表中间的元素,效率就很低
6.list可以对数据进行分页操作,通常第一页的数据来自于redis的list,第2页及更多的信息是通过数据库来获取的。
Set类型
Set的特点是,元素无序,且不能重复。无序指的是Set不会以元素插入的顺序存储元素。
sadd key member1 member2...
srem key member1 member2...
# 显示set中的所有元素
smembers key
# 获取set中成员的数量
scard key
# 随机返回set中的一个元素,不删除该元素
srandmember key
# 随机弹出一个元素,该元素会被删除
spop set
# 判断set中是否包含某个元素,返回1表示存在,返回0表示不存在
sismember key member
# 把set中的指定元素,移动到set2中
smove set set2 5
# 差集运算
sdiff key key2
# 交集运算
sinter key1 key2 key3
# 并集运算
sunion key1 key2 key3
# 差集运算,把结果保存在destination 中
sdiffstore destination key key2
# 交集运算,把结果保存在destination 中
sinterstore destination key key2
# 并集运算,把结果保存在destination 中
sunionstore destination key key2
Set类型应用场景
☐ 抽奖逻辑
sadd luckuser u1 u2 u3 u4 u5
spop luckuser
☐ 投票
在朋友圈经常看到好友的投票请求。而每个ip只能投票一次。set类型能够自动去重,很适合完成这类业务。
sadd like:1001 ip1
sadd like:1001 ip2
sadd like:1001 ip3
# 查看1001号选手票数
scard like:1001
☐ 共同好友统计
# andy好友
sadd andy:friend eason
sadd andy:friend cindy
# eason好友
sadd andy:friend andy
sadd andy:friend cindy
# andy和eason的共同好友
sinter andy:focus eason:focus
# andy友的好友,但不是eason的好友,我们称之为集合A
# 如果集合A中的某个用户与eason的共同好友超过3个,则就是eason“可能认识的人”
sdiff andy:focus eason:focus
☐ 黑名单
sadd blacklist ip
Zset类型
Zset是有序集合。其中的元素不能重复,且每个元素都有一个分数,元素可以按照分数排序。
zadd key score value
# 返回指定成员的分数
zscore key member
# 删除集合中指定的成员
zrem key member
# 范围查询
zrange key start end [withscores]
# 获取倒序结果
zrevrange key start stop
# 按照分数范围查询
zrangebyscore key min max
# 给指定的成员增加指定的分数,返回增加以后的分数
zincrby key increment member
# 获取分数在[min, max]之间的成员的个数
zcount key min max
# 返回成员在集合中的排名
zrank key member
# 反转排名
zrevrank key member
# 获取集合数据总量
zcard key
# 计算交集
zinterstore destination number key [key ...]
# 计算并集
zunionstore destination number key [key ...]
Zset应用
☐ 排行榜(热点新闻排行榜、直播打赏排行榜等)
# 2024-02-25当天,id为1001的新闻上线
zadd news:2024-02-25 0 newsId:1001
# 对id为1001的新闻浏览量增加1
zincrby news:2024-02-25 1 newsId:1001
# 获取2024-02-25当天,排在前10的热点新闻
zrevrank key member 0 10
# 汇总7天的新闻浏览量
zinterstore sum 7 news:2024-02-25 news:2024-02-26 news:2024-02-27 news:2024-02-28 news:2024-02-29 news:2024-03-01 news:2024-03-02
☐ 限流
限流是对系统的出入流量进行控制,防止大流量出入,从而导致系统不稳定。
固定窗口的限流方式:假设限流为1秒2000个请求,而在第一秒的最后100毫秒以及第二秒开始的100毫秒都收到2000个请求,就等于在200毫秒的时间内收到4000个请求,并且限流通过。
滑动窗口为固定窗口的改进版:
# 每次请求进来的时候,都执行以下命令,记录当前请求的时间戳
zadd limit 时间戳 随机value
# 统计当前时间戳到前一秒时间戳之内,一共有多少个请求
zcount limit 当前时间戳 一秒前的时间戳
# 如果1秒之内的请求数超过限流的请求数阈值,则拒绝处理请求
redis中的二进制安全
二进制不安全
C语言中使用char数组来保存字符串,且C语言以空字符’\0’作为一个字符串的结束标记。如下,该字符串的内容为“I am a boy!”
I | a | m | a | b | o | y | ! | \0 |
如果在字符串中出现了空字符’\0’,则字符串的内容就变了,以下字符串的内容是“I am”
I | a | m | \0 | a | b | o | y | ! | \0 |
这种限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
所谓的二进制不安全,就是给一些二进制数据赋予了特殊的含义,比如把0这个数据本身,当做了字符串的结束符号,而不是数据本身。
二进制安全
二进制安全,其本质上是将输入作为原始的、无任何特殊格式意义的数据流。这样redis并不会对任何特殊字符进行特殊解释,所以redis是二进制安全的。这样redis就能保存图片、音频、视频、压缩文件这样的二进制数据。而不会造成数据丢失。
虽然数据库一般用于保存文本数据,但使用数据库来保存二进制数据的场景也不少见,因此,为了确保Redis可以适用于各种不同的使用场景,SDS(simple dynamic string )的 API都是二进制安全的(binary-safe),所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据在写入时是什么样的,它被读 取时就是什么样。
这也是我们将SDS的buf属性称为字节数组的原因——Redis不是用这个数组来保存字符,而是用它来保存一系列二进制数据。
例如,使用SDS来保存之前提到的特殊数据格式就没有任何问题,因为SDS使用len属性的值而不是空字符来判断字符串是否结束,如图所示。
redis作为单线程模型为什么性能还很高?
1. 纯内存访问:数据存放在内存中,内存的响应时间是极快的!
2. 采用单线程自然也就避免了线程的上下文切换和所带来的开销。
3. 非阻塞式的I/O操作:redis采用epoll作为I/O多路复用技术的实现,性能很高。