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类型的特点是,键值对中的值本身也是一个键值对结构

2.1_redis



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存对象。因为有框架,框架把那些复杂的操作都封装了。


☐ 实现购物车


2.1_数据_02



# 给购物车中添加商品

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个请求,并且限流通过。

2.1_redis_03



滑动窗口为固定窗口的改进版:


2.1_redis_04



# 每次请求进来的时候,都执行以下命令,记录当前请求的时间戳

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属性的值而不是空字符来判断字符串是否结束,如图所示。


2.1_数据_05



redis作为单线程模型为什么性能还很高?

1. 纯内存访问:数据存放在内存中,内存的响应时间是极快的!

2. 采用单线程自然也就避免了线程的上下文切换和所带来的开销。

3. 非阻塞式的I/O操作:redis采用epoll作为I/O多路复用技术的实现,性能很高。