【Redis】举例让你快速理解!Redis数据结构与命令

Redis

数据存内存C语言实现单线程架构

基于键值对 ,值可以为字符串、哈希、列表、集合、有序集合

键过期功能实现缓存,流水线功能减少网络开销

持久化(数据内存->磁盘)、主从复制(数据多副本)

高可用(故障发现与自动转移)、分布式

*奇数是不稳定版,如4.1,4.3等。偶数稳定,如5.2,5.4jk00…0

4.0.11,6.0.9,6.0.9去看更新文档

主要应用

  1. 缓存:键值过期,淘汰策略
  2. 排行榜:列表与集合
  3. 计数器:计数
  4. 社交网络:订阅和阻塞

启动与停止

服务器:任何目录下均可

redis-server

客户端

redis-cli -h 127.0.0.1 -p 6379

停止

redis-cli shutdown (nosave|save)

关闭前是否生成持久化文件

不要kill -9杀死redis,但可以kill+进程号

数据库级别的部分用法

dbsize输出键的总数
127.0.0.1:6379> dbsize
(integer) 4
select切换数据库

redis默认有16个数据库,不同数据库之间没有管理,可以是相同数据,但如果是使用多个数据库,最好还是用多个不同端口的redis客户端

redis-cli默认是0号数据库

select index

#切到第一个库
select 0 
#切到第16个库
select 15
flushdb&flushall清除数据库

flushdb清除当前数据库

flushall清除所有数据库

键值数目多的时候可能阻塞

键级别的部分用法

set插入与get查看
一个键值对
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
一个列表型
127.0.0.1:6379> rpush mylist a b c d e f g
(integer) 7

get获取的键不存在,返回nil(空)

keys *遍历所有键

*是通配符,?匹配一个,[ ]批评你部分字符,\x做转义

会阻塞

127.0.0.1:6379> keys *
1) "java"
2) "hello"
3) "python"
4) "python"
127.0.0.1:6379> keys h?
1) "hello"
scan遍历所有键

防止阻塞。也可以用模式匹配

一次scan只扫描一个字典中的一部分键,完成keys *的效果要多次scan

scan cursor pattern num

cursor=0表示遍历结束

scan过程中如果有键的变化(增删改),新增的键可能遍历不到

也有hscan、sscan、zscan

127.0.0.1:6379> scan 0
1) "14"
2)  1) "x"
    2) "a"
    3) "new"
    4) "usr:1"
    5) "c"
    6) "z"
    7) "n"
    8) "union"
    9) "key"
   10) "str"
127.0.0.1:6379> scan 14
1) "23"
2)  1) "diff"
    2) "redis"
    3) "num"
    4) "python"
    5) "inter"
    6) "k"
    7) "hello"
    8) "y"
    9) "b"
   10) "wx"
127.0.0.1:6379> scan 23
1) "0"
2) 1) "hi"
   2) "ms"
   3) "mylist"
   4) "usr:2"
   5) "et"
exists判断是否存在键值对
127.0.0.1:6379> exists java
(integer) 1
127.0.0.1:6379> exists c
(integer) 0
del删除键
127.0.0.1:6379> del mylist
(integer) 1
127.0.0.1:6379> exists mylist
(integer) 0
127.0.0.1:6379> del my
(integer) 0

输出删除的个数,不存在会输出0,也可一次删多个

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set b 2
OK
127.0.0.1:6379> set c 3
OK
127.0.0.1:6379> del a b c
(integer) 3
expire设置键过期时间&expireat

xx时间后自动过期,redis不支持二级数据结构内的过期时间,如列表内部元素

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello 10
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 5
127.0.0.1:6379> ttl hello
(integer) -2
127.0.0.1:6379> ttl python
(integer) -1

expireat后面时间用时间戳

ttl查看过期时间&pttl

大于等于0是剩余,-1没设置,-2键不存在,pttl精度更高

persist取消设置过期时间&set

*set命令执行时会取消过期时间的设定

type查看格式
127.0.0.1:6379> set str a
OK
127.0.0.1:6379> type str
string
127.0.0.1:6379> set n 012
OK
127.0.0.1:6379> type n
string
127.0.0.1:6379> rpush mylist a b c
(integer) 3
127.0.0.1:6379> type mylist
list
rename重命名&renamenx重命名不覆盖
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> rename hello hi
OK
127.0.0.1:6379> get hi
"world"

127.0.0.1:6379> rename hi hi
OK

#值覆盖
127.0.0.1:6379> set hi sky
OK
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> rename hello hi
OK
127.0.0.1:6379> get hi
"world"

127.0.0.1:6379> set hi sky
OK
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> renamenx hello hi
(integer) 0
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> get hi
"sky"

*如果键值比较大,重命名可能会阻塞redis

randomkey

随机从数据库返回一个键值对

move迁移不同数据库

move key database

dump迁移不同redis

开启了两个客户端连接,非原子性

dump key键值序列化

restore key ttl value复原

migrate原子迁移不同redis

是原子性的,没有开启两个客户端直接传的

单线程

命令从客户端到服务端后,放入队列中,多个客户端同时发送命令,最终执行的顺序不一定

单线程还快的原因
  1. 纯内存访问
  2. 非阻塞I/O,I/O多路复用
  3. 避免线程切换和竞态产生和消耗

数据结构

字符串

包含字符串、数字、二进制(图片音频、视频等),最大不超过512MB

int:8字节

embstr:小于等于39字节

raw:大于等于39字节

object encoding name查询内部编码

设置set&getset
set key value ex seconds px milliseconds nx|xx

ex设置秒级过期时间

px设置毫秒过期时间

nx 当键不存在时,才可以设置成功,等效于setnx

xx 当键存在时才可以设置成功能,等效于setxx

127.0.0.1:6379> getset python p
"redis-py"

getset返回上一个赋值结果

批量操作mset&mget
127.0.0.1:6379> mset a 1 b 2 c 3
OK

127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) (nil)

set&get: n*time_get = n*time_web +n*time_shell

mset&mget: n*time_get = 1*time_web +n*time_shell

计数
自增incr&incrby

不存在时,按0自增,不是整数会报错

127.0.0.1:6379> exists k
(integer) 0
127.0.0.1:6379> incr k
(integer) 1

127.0.0.1:6379> incr hello
(error) ERR value is not an integer or out of range

127.0.0.1:6379> incrby k 4
(integer) 5
自减decr&decrby

by指定增减长度

127.0.0.1:6379> decr k
(integer) 4

127.0.0.1:6379> decrby k 7
(integer) -3
127.0.0.1:6379> get k
"-3"
追加append
127.0.0.1:6379> append hello hello
(integer) 10
127.0.0.1:6379> get hello
"worldhello"
长度strlen
127.0.0.1:6379> strlen k
(integer) 2
127.0.0.1:6379> get k
"-2"
127.0.0.1:6379> strlen hello
(integer) 10
修改setrange
127.0.0.1:6379> set redis pest
OK
127.0.0.1:6379> setrange redis 0 b
(integer) 4
127.0.0.1:6379> get redis
"best"

127.0.0.1:6379> setrange redis 4 !
(integer) 5
127.0.0.1:6379> get redis
"best!"

127.0.0.1:6379> setrange redis 9 b
(integer) 10
127.0.0.1:6379> get redis
"best!\x00\x00\x00\x0

注意下标从0开始,超出长度也可以

截选getrange
127.0.0.1:6379> getrange redis 0 1
"be"

左右闭区间

时间复杂度

del、mset、mget是 o(k),其余均o(1)

使用场景

MySQL做存储层——》 Redis做缓存层——》web服务,MySQL也可以直接返回到web服务层

哈希

key-value,其中value由多个field(不叫key,等效key)-value构成的集合

命令在字符串的基础上加个h,作用域是field,不是key

内部编码有ziplist(元素个数和所有值大小小于规定值,默认规定512个/ 64字节)和hashtable两种

稀疏存储

hset&hget
127.0.0.1:6379> hset usr:1 name tom
(integer) 1
127.0.0.1:6379> get usr
(nil)
127.0.0.1:6379> hget usr:1 name
"tom"
hlen
127.0.0.1:6379> hset usr:1 city bj
(integer) 1
127.0.0.1:6379> hlen usr:1
(integer) 2
hmset&hmget
127.0.0.1:6379> hmset usr:2 name jerry city sh
OK
127.0.0.1:6379> hmget usr:2 name city
1) "jerry"
2) "sh"
hexists
127.0.0.1:6379> hexists usr:1 age
(integer) 0
127.0.0.1:6379> hexists usr:1 name
(integer) 1
hkeys
127.0.0.1:6379> hkeys usr:1
1) "name"
2) "city"
127.0.0.1:6379> keys *
1) "usr:1"
2) "usr:2"

hvals

127.0.0.1:6379> hvals user:1
(empty list or set)
127.0.0.1:6379> hvals usr:1
1) "tom"
2) "bj"

*没有vals *

hgetall
127.0.0.1:6379> hgetall usr:2
1) "name"
2) "jerry"
3) "city"
4) "sh"
hincrby

*没有hincr

127.0.0.1:6379> hincrby usr:1 age 5
(integer) 5
127.0.0.1:6379> hincr usr:1 age
(error) ERR unknown command `hincr`, with args beginning with: `usr:1`, `age`, 
时间复杂度

hdel、hgetall、hmget、hmset 是o(k)

hkeys、hvals 是 o(n)

其余操作是o(1)

列表

一个列表最多存储232-1个元素

两端有poppush操作

列表元素有序,索引查找

虽然输出是从1号开始,实际下标从0开始,左右闭区间

rpush
127.0.0.1:6379> rpush listk c b a
(integer) 3
lpush
127.0.0.1:6379> lpush listk d
(integer) 4
127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
lrange

如果是大列表获取中间元素的性能会变差

127.0.0.1:6379> lrange listk 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> lrange listk 0 1
1) "c"
2) "b"

127.0.0.1:6379> lrange listk 0 4
1) "d"
2) "c"
3) "b"
4) "A"
5) "a"

127.0.0.1:6379> lrange listk -5 -1
1) "d"
2) "c"
3) "b"
4) "A"
5) "a"
linsert
127.0.0.1:6379> linsert listk before a A
(integer) 5
127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "c"
3) "b"
4) "A"
5) "a"
lindex
127.0.0.1:6379> lindex listk 1
"c"
127.0.0.1:6379> lindex listk -1
"a"
llen
127.0.0.1:6379> llen listk
(integer) 5
lpop
127.0.0.1:6379> lpop listk
"d"
127.0.0.1:6379> lrange listk 0 -1
1) "c"
2) "b"
3) "A"
4) "a"
rpop
127.0.0.1:6379> rpop listk
"a"
127.0.0.1:6379> lrange listk 0 -1
1) "c"
2) "b"
3) "A"
lrem 范围删除

lrem key count value

count = 0 都删

count>0从左删

count<0从右删

127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "d"
3) "d"
4) "d"
5) "c"
6) "b"
7) "A"
127.0.0.1:6379> lrem listk 2 d
(integer) 2
127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "d"
3) "c"
4) "b"
5) "A"
127.0.0.1:6379> lrem listk -1 d
(integer) 1
127.0.0.1:6379> lrange listk 0 -1
1) "c"
2) "d"
3) "c"
4) "b"
5) "A"
6) "a"
7) "a"
8) "a"
ltrim 截取
127.0.0.1:6379> ltrim listk 1 5
OK
127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "c"
3) "b"
4) "A"
5) "a"
lset
127.0.0.1:6379> lset listk 4 Apple
OK
127.0.0.1:6379> lrange listk 0 -1
1) "d"
2) "c"
3) "b"
4) "A"
5) "Apple"
brpop&blpop阻塞

blpop/ brpop key/ key_list timeout

  1. 空表阻塞timeout,timeout=0一直阻塞,timeout时间结束后返回nil
  2. 一旦不为空,立刻弹出返回,即使未到timeout时间,即使该元素并不是本客户端插入的
  3. brpop从左到右,blpop从右到左,有一个能弹出就返回
  4. 多个客户端对同一个键(列表)执行,让最先执行的客户端获得该值,其他客户端阻塞
127.0.0.1:6379> blpop listk 3
1) "listk"
2) "Apple"
127.0.0.1:6379> blpop listk 3
(nil)
(3.04s)
127.0.0.1:6379> 
时间复杂度

linsert、lrem、ltrim、lset、lindex 是 o(n)

rpush、lpush 是o(k)

lrange 是o(start+n)

其余o(1)

使用场景

消息队列:lpush+brpop,阻塞,生产者与消费者问题

有限集合:lpush+ltrim

集合

内部编码:intset(元素都是整型且个数小于规定,默认规定512个),hashtable(其余情况)

sadd

返回添加成功的个数

127.0.0.1:6379> sadd ms a b c
(integer) 3
127.0.0.1:6379> sadd ms a d
(integer) 1
127.0.0.1:6379> sadd ms a b
(integer) 0
smembers

返回是无序的

127.0.0.1:6379> smembers ms
1) "b"
2) "a"
3) "c"
4) "d"
srem

返回删除的个数

127.0.0.1:6379> srem myset a b c
(integer) 3
127.0.0.1:6379> smembers myset
(empty list or set)
scard

计算元素个数

127.0.0.1:6379> scard ms
(integer) 4
sismember
127.0.0.1:6379> sismember ms d
(integer) 1
127.0.0.1:6379> sismember ms e
(integer) 0
srandmember

随机返回几个元素

127.0.0.1:6379> srandmember ms 3
1) "d"
2) "c"
3) "b"
127.0.0.1:6379> srandmember ms 3
1) "d"
2) "a"
3) "b"
spop

随机弹出元素

127.0.0.1:6379> spop ms
"c"
sinter求交集
127.0.0.1:6379> smembers et
1) "a"
2) "f"
3) "k"
4) "h"
127.0.0.1:6379> smembers ms
1) "a"
2) "b"
3) "d"
127.0.0.1:6379> smembers wx
1) "a"
2) "d"
3) "c"
4) "k"
127.0.0.1:6379> sinter wx ms et
1) "a"
sdiff求差集
#ms有,et没有
127.0.0.1:6379> sdiff ms et
1) "d"
2) "b"

#et有,ms没有
127.0.0.1:6379> sdiff et ms
1) "f"
2) "h"
3) "k"

#wx有,ms和et都没有
127.0.0.1:6379> sdiff wx ms et
1) "c"

sunion求并集

并不会改变集合本身,输出并集

127.0.0.1:6379> sunion ms et wx
1) "a"
2) "b"
3) "k"
4) "c"
5) "d"
6) "f"
7) "h"
集合计算结果保存
127.0.0.1:6379> sinterstore inter ms et
(integer) 1
127.0.0.1:6379> smembers inter
1) "a"
127.0.0.1:6379> sunionstore union ms et
(integer) 6
127.0.0.1:6379> smembers union
1) "f"
2) "b"
3) "a"
4) "k"
5) "d"
6) "h"
127.0.0.1:6379> sdiffstore diff ms et
(integer) 2
127.0.0.1:6379> smembers diff
1) "d"
2) "b"
时间复杂度

sadd、srem 是 o(k),k是元素个数

srandmember 是o(count)

sinter是o(a*b)a是多个集合中元素最少的个数,m是键个数

sunion、sdiff是o(k),k为运算集合的元素个数之和

其余是o(1)

使用场景

标签:给用户添加标签&给标签添加用户(在一个事务执行)、删除用户的标签&删除标签的用户(在一个事务执行)

生成随机数:spop/srandmember

社交需求:sadd+sinter

有序集合

key对应的有序集合中,每个元素由score、member构成,score是排序的指标必须为float。

score可重复,member不可重复

内部编码:ziplist、skiplist(个数超过128个,单个元素大于64字节)

zadd

有nx、xx、ch、incr可以扩展

zadd key 【nx|xx】 【ch】 【incr】 score member

nx的member不存在,xx必须存在(用于修改),ch返回操作结束后元素和分数变化的个数

127.0.0.1:6379> zadd z 40 ada
(integer) 1
127.0.0.1:6379> zadd z 20 tom 23 jan 28 luka
(integer) 3

127.0.0.1:6379> zadd z nx 21 ben
(integer) 1
127.0.0.1:6379> zadd z xx ch 30 ben
(integer) 1
127.0.0.1:6379> zadd z incr 2 ben
"32"
zcard

计算元素个数

127.0.0.1:6379> zcard z
(integer) 5
zscore
127.0.0.1:6379> zscore z ada
"40"
127.0.0.1:6379> zscore z hans
(nil)
zrank&zrevrank

排名,小到大,大到小,返回member的名次

127.0.0.1:6379> zrank z tom
(integer) 0
127.0.0.1:6379> zrevrank z ada
(integer) 0
zrem

返回删除个数

127.0.0.1:6379> zrem z ben
(integer) 1
zincrby
127.0.0.1:6379> zincrby z 10 tom
"30"
zrange&zrevrange

输入的是名次

127.0.0.1:6379> zrange z 0 1
1) "jan"
2) "luka"
127.0.0.1:6379> zrange z 0 -1
1) "jan"
2) "luka"
3) "tom"
4) "ada"
127.0.0.1:6379> zrevrange z 0 1
1) "ada"
2) "tom"
127.0.0.1:6379> zrevrange z 0 1 withscores
1) "ada"
2) "40"
3) "tom"
4) "30"

最后加上withscore会返回分数

zrangebyscore&zrevrangerange

输入的是分数区间,默认闭区间,开区间加个(,或),无穷是±inf

127.0.0.1:6379> zrangebyscore z 20 30 withscores
1) "jan"
2) "23"
3) "luka"
4) "28"
5) "tom"
6) "30"

127.0.0.1:6379> zrangebyscore z (30 +inf withscores
1) "ada"
2) "40"
zcount

个数

127.0.0.1:6379> zcount z 20 30 
(integer) 3
zremrangebyrank

删除某名次到某名次的

127.0.0.1:6379> zremrangebyrank z 2 2
(integer) 1
zremrangebyscore

删除某分数段,也可以用(,),±inf

127.0.0.1:6379> zremrangebyscore z 2 25
(integer) 1
zinterstore

zinterstore destination num key

127.0.0.1:6379> zrange z 0 -1
1) "luka"
2) "momo"
3) "ada"
127.0.0.1:6379> zrange y 0 -1
1) "co"
2) "lily"
3) "momo"
4) "ray"
127.0.0.1:6379> zinterstore new 2 z y
(integer) 1
127.0.0.1:6379> zrange new 0 -1
1) "momo"
zunionstore

zunionstore destination num key

127.0.0.1:6379> zunionstore x 2 z y
(integer) 6
127.0.0.1:6379> zrange x 0 -1
1) "co"
2) "lily"
3) "luka"
4) "ray"
5) "ada"
6) "momo"
时间复杂度

在这里插入图片描述

使用场景

排行榜系统