redis学习


redis学习

redis官网

学习的课程链接

redis是什么

redis(==Re==mote ==Di==ctionary ==S==erver),即远程字典服务

Redis

The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker.

redis是一种基于内存的数据存储程序,经常作为数据库、缓存、流引擎、消息代理等来进行使用。

作为数据库时,是一种典型的NoSQL数据库,NoSQL(Not Only SQL)非关系型数据库。

优点是效率特别高,速度特别快,可用于高速缓存等场景。

下载

推荐在linux环境中进行下载使用

来自官网:

注:下面的|并不是表示管道,而是表示这两句都可以,任选其一进行执行即可

Install on Ubuntu/Debian

You can install recent stable versions of Redis from the official packages.redis.io APT repository.

Prerequisites

If you’re running a very minimal distribution (such as a Docker container) you may need to install lsb-release, curl and gpg first:

1
sudo apt install lsb-release curl gpg

Add the repository to the apt index, update it, and then install:

1
2
3
4
5
6
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update
sudo apt-get install redis

压力测试

下载自带的文件 redis-benchmark 可用于进行压力测试。

启用服务端

1
2
3
4
5
# 直接启用,使用默认配置文件
redis-server
# 使用自定义的配置文件启动
# redis-server [配置文件位置]
redis-server ./redis.conf

默认的IP为本机,端口为6379,即127.0.0.1:6379

默认有16个数据库,为0 - 15

启用客户端

1
2
3
4
5
6
7
# 直接启用,即对话模式
redis-cli
# 仅使用一条命令
# redis-cli [命令]
redis-cli ping
# 指定端口
redis-cli -p 6379

一些基本命令

ping

判断是否与服务端连通

如果连通,则回返回一个PONG

1
2
127.0.0.1:6379> ping
PONG

flushall

删除全部数据库中的所有内容

flushdb

删除当前数据库中的所有内容

keys

使用keys *可以查看所有的key

1
2
3
127.0.0.1:6379> keys *
1) "b"
2) "a"

exists

判断某个键是否存在

1
2
3
4
5
6
7
127.0.0.1:6379> keys *
1) "b"
2) "a"
127.0.0.1:6379> exists a
(integer) 1
127.0.0.1:6379> exists c
(integer) 0

move

将一个键值对移动到另一个数据库中

1
2
3
# move key db
127.0.0.1:6379> move a 1
(integer) 1

expire

对一个键设置过期时间,过了这段时间,这个键就会过期,消失

1
2
3
# expire key seconds
127.0.0.1:6379> expire a 10
(integer) 1

ttl

time to leave,剩余过期时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ttl key
127.0.0.1:6379> expire a 10
(integer) 1
127.0.0.1:6379> ttl a
(integer) 8
127.0.0.1:6379> ttl a
(integer) 6
127.0.0.1:6379> ttl a
(integer) 2
127.0.0.1:6379> ttl a
(integer) 1
127.0.0.1:6379> ttl a
(integer) -2

127.0.0.1:6379> keys *
1) "b"
127.0.0.1:6379> ttl b
(integer) -1

一个大于0的整数表示还剩几秒,-2表示这个键不存在,-1表示这个键没有设置过期时间。

type

查询某个键的数据类型

1
2
3
# type key
127.0.0.1:6379> type b
string

del

删除一个或一些键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# del key [key ...]
127.0.0.1:6379> keys *
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> keys *
1) "c"
2) "b"
127.0.0.1:6379> del b c
(integer) 2
127.0.0.1:6379> keys *
(empty array)

String字符串类型

就是字面意义上的字符串类型

字符串下标是从0开始的

下标-1表示最后一个

set

设置一个键的值

1
2
127.0.0.1:6379> set name shaun
OK

get

查询一个键的值

1
2
3
4
127.0.0.1:6379> get name
"shaun"
127.0.0.1:6379> get age
(nil)

(nil)表示这个键不存在

append

字符串拼接,在一个字符串后面拼接上一段字符串,返回值为拼接之后的字符串的长度

1
2
3
4
5
6
7
# append key value
127.0.0.1:6379> get name
"shaun"
127.0.0.1:6379> append name 2314
(integer) 9
127.0.0.1:6379> get name
"shaun2314"

strlen

查询字符串的长度

1
2
3
# strlen key
127.0.0.1:6379> strlen name
(integer) 9

incr & decr

对于字符串是整数的值,可以使用该命令进行类似于num++num--的操作

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set num 5
OK
# incr key
127.0.0.1:6379> incr num
(integer) 6
127.0.0.1:6379> incr num
(integer) 7
# decr key
127.0.0.1:6379> decr num
(integer) 6
127.0.0.1:6379> decr num
(integer) 5

incrby & decrby

进行类似于num += inum -= i的操作

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> get num
"5"
# incrby key increment
127.0.0.1:6379> incrby num 10
(integer) 15
127.0.0.1:6379> incrby num 10
(integer) 25
# decrby key increment
127.0.0.1:6379> decrby num 10
(integer) 15
127.0.0.1:6379> decrby num 10
(integer) 5

getrange

功能为substring,获取一个字符串的子串

1
2
3
4
5
6
7
# getrange key start end
127.0.0.1:6379> get name
"shaun2314"
127.0.0.1:6379> getrange name 0 1
"sh"
127.0.0.1:6379> getrange name 0 -1
"shaun2314"

字符串下标是从0开始,startend表示的是一个闭区间

setrange

替换一个字符串的其中的一部分,在此处不能使用-1下标表示最后一个了,且可以向后超出范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# setrange key offset value
127.0.0.1:6379> get name
"shaun2314"
127.0.0.1:6379> setrange name 2 xx
(integer) 9
127.0.0.1:6379> get name
"shxxn2314"
127.0.0.1:6379> setrange name -1 xxx
(error) ERR offset is out of range
127.0.0.1:6379> get name
"shxxn2314"
127.0.0.1:6379> setrange name 9 xxx
(integer) 12
127.0.0.1:6379> get name
"shxxn2314xxx"
127.0.0.1:6379> setrange name 13 j
(integer) 14
127.0.0.1:6379> get name
"shxxn2314xxx\x00j"

setex

set with expire,设置一个带有过期时间的字符串

如果这个key之前存在,则会将其覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
127.0.0.1:6379> keys *
1) "num"
2) "b"
3) "name"
# setex key seconds value
127.0.0.1:6379> setex num 10 9
OK
127.0.0.1:6379> get num
"9"
127.0.0.1:6379> ttl num
(integer) 2
127.0.0.1:6379> get num
(nil)

127.0.0.1:6379> keys *
1) "b"
2) "name"
127.0.0.1:6379> setex num 10 8
OK
127.0.0.1:6379> get num
"8"
127.0.0.1:6379> ttl num
(integer) 5
127.0.0.1:6379> ttl num
(integer) 1
127.0.0.1:6379> get num
(nil)

setnx

set if not exist

如果这个值不存在才会进行设置,如果这个值当时存在,则什么也不做

在分布式锁中经常使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> keys *
1) "a"
2) "name"
127.0.0.1:6379> get a
"1"
# setnx key value
127.0.0.1:6379> setnx a 8
(integer) 0
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> setnx b 9
(integer) 1
127.0.0.1:6379> get b
"9"

mget

一次性获得多个值

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> keys *
1) "b"
2) "a"
3) "name"
# mget key [key ...]
127.0.0.1:6379> mget a
1) "1"
127.0.0.1:6379> mget a b c
1) "1"
2) "9"
3) (nil)

mset

一次性设置多个值

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> keys *
1) "a"
2) "name"
# mset key value [key value ...]
127.0.0.1:6379> mset a 3 b 4 c 5
OK
127.0.0.1:6379> keys *
1) "c"
2) "b"
3) "a"
4) "name"

msetnx

一次性设置多个原本不存在的值

注:该操作为原子操作,若其中有一个值之前存在,则整条命令都不会执行成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> keys *
1) "c"
2) "b"
3) "a"
4) "name"
# msetnx key value [key value ...]
127.0.0.1:6379> msetnx d 8 e 9
(integer) 1
127.0.0.1:6379> keys *
1) "c"
2) "b"
3) "e"
4) "name"
5) "d"
6) "a"
127.0.0.1:6379> msetnx a 7 f 9
(integer) 0
127.0.0.1:6379> keys *
1) "c"
2) "b"
3) "e"
4) "name"
5) "d"
6) "a"
127.0.0.1:6379> get a
"3"

对象

在redis中,可以使用user:1:name这种key的命名方式来表示一个对象的值

user:{id}:{field}

1
2
3
4
5
6
7
8
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> keys *
1) "user:1:name"
2) "user:1:age"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

getset

返回这个键的值,并将其赋予新的值

1
2
3
4
5
6
7
127.0.0.1:6379> keys *
(empty array)
# getset key value
127.0.0.1:6379> getset name zhangsan
(nil)
127.0.0.1:6379> getset name lisi
"zhangsan"

List列表类型

就是列表

无论从哪边,编号都是从0开始,-1表示最后一个节点

lpush

头插法插入一个节点,即在左边插入一个节点

1
2
3
4
5
6
7
8
9
# lpush key element [element ...]
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lpush list four five
(integer) 5

lrange

从左边开始查找列表中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
# lrange key start stop
127.0.0.1:6379> lrange list 0 0
1) "five"
127.0.0.1:6379> lrange list 0 2
1) "five"
2) "four"
3) "three"
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"

rpush

尾插法插入一个节点,即在右边插入一个节点

lpop & rpop

弹出头节点和弹出尾节点,并返回节点的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# lpop key
# rpop key
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
6) "six"
127.0.0.1:6379> lpop list
"five"
127.0.0.1:6379> lpop list
"four"
127.0.0.1:6379> rpop list
"six"
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"

lindex

获取列表中某一个下标对应的元素的值

1
2
3
4
5
6
7
8
9
10
11
# lindex key index
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lindex list 0
"three"
127.0.0.1:6379> lindex list 2
"one"
127.0.0.1:6379> lindex list 3
(nil)

llen

获取列表的长度

1
2
3
# llen key
127.0.0.1:6379> llen list
(integer) 3

lrem

从左到右移除列表中若干个等于某个值的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# lrem key count element
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "a"
3) "b"
4) "a"
5) "b"
6) "a"
7) "a"
8) "three"
9) "two"
10) "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "a"
3) "b"
4) "a"
5) "b"
6) "a"
7) "a"
8) "three"
9) "two"
127.0.0.1:6379> lrem list 2 two
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "a"
3) "b"
4) "a"
5) "b"
6) "a"
7) "a"
8) "three"
127.0.0.1:6379> lrem list 1 a
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "c"
2) "b"
3) "a"
4) "b"
5) "a"
6) "a"
7) "three"

ltrim

保留列表中的某个区间的节点,删除其他节点

1
2
3
4
5
6
7
8
9
10
11
12
# ltrim key start stop
127.0.0.1:6379> lrange list 0 -1
1) "a0"
2) "a1"
3) "a2"
4) "a3"
5) "a4"
127.0.0.1:6379> ltrim list 2 3
OK
127.0.0.1:6379> lrange list 0 -1
1) "a2"
2) "a3"

rpoplpush

是一个组合命令,移除一个列表中的尾节点,并将这个节点插入到另一个列表的头部

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> lrange list 0 -1
1) "a2"
2) "a3"
127.0.0.1:6379> lrange anotherlist 0 -1
(empty array)
# rpoplpush source destination
127.0.0.1:6379> rpoplpush list anotherlist
"a3"
127.0.0.1:6379> lrange list 0 -1
1) "a2"
127.0.0.1:6379> lrange anotherlist 0 -1
1) "a3"

lset

更新一个列表中某个下标的节点的值

需要保证这个节点是存在的,否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# lset key index element
127.0.0.1:6379> lrange list 0 -1
1) "a0"
2) "a1"
3) "a2"
4) "a3"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
2) "a1"
3) "a2"
4) "a3"
127.0.0.1:6379> lset list 2 temp
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
2) "a1"
3) "temp"
4) "a3"
127.0.0.1:6379> lset list 4 other
(error) ERR index out of range

linsert

将某个值插入到一个列表的某个值的左边或者右边

若列表中不存在该值,则插入失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# linsert key BEFORE|AFTER pivot element
127.0.0.1:6379> lrange list 0 -1
1) "a0"
2) "a1"
3) "a2"
4) "a3"
127.0.0.1:6379> linsert list before a1 a_new
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "a0"
2) "a_new"
3) "a1"
4) "a2"
5) "a3"
127.0.0.1:6379> linsert list after aa aaa
(integer) -1

Set集合类型

无序不重复集合

sadd

插入一个元素

1
2
3
4
5
6
7
# sadd key member [member ...]
127.0.0.1:6379> sadd myset a0
(integer) 1
127.0.0.1:6379> sadd myset a1 a2
(integer) 2
127.0.0.1:6379> sadd myset a0
(integer) 0

smembers

查询一个集合中的所有元素

1
2
3
4
5
# smembers key
127.0.0.1:6379> smembers myset
1) "a1"
2) "a0"
3) "a2"

sismember

查询一个值是否存在于一个集合中

1
2
3
4
5
# sismember key member
127.0.0.1:6379> sismember myset a0
(integer) 1
127.0.0.1:6379> sismember myset a5
(integer) 0

scard

查询一个集合中的元素个数

1
2
3
# scard key
127.0.0.1:6379> scard myset
(integer) 3

srem

移除一个集合中的某个或某些指定的元素

1
2
3
4
5
6
7
8
9
10
# srem key member [member ...]
127.0.0.1:6379> smembers myset
1) "a1"
2) "a0"
3) "a2"
127.0.0.1:6379> srem myset a0
(integer) 1
127.0.0.1:6379> smembers myset
1) "a1"
2) "a2"

srandmember

从一个集合中随机取出一个或多个元素,默认是1个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# srandmember key [count]
127.0.0.1:6379> smembers myset
1) "a6"
2) "a2"
3) "a4"
4) "a1"
5) "a0"
6) "a5"
7) "a3"
127.0.0.1:6379> srandmember myset
"a1"
127.0.0.1:6379> srandmember myset
"a0"
127.0.0.1:6379> srandmember myset
"a1"
127.0.0.1:6379> srandmember myset
"a6"
127.0.0.1:6379> srandmember myset 3
1) "a2"
2) "a4"
3) "a1"
127.0.0.1:6379> srandmember myset 3
1) "a6"
2) "a1"
3) "a0"
127.0.0.1:6379> srandmember myset 3
1) "a1"
2) "a0"
3) "a3"

spop

在一个集合中随机移除一个或多个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# spop key [count]
127.0.0.1:6379> smembers myset
1) "a6"
2) "a2"
3) "a4"
4) "a1"
5) "a0"
6) "a5"
7) "a3"
127.0.0.1:6379> spop myset
"a1"
127.0.0.1:6379> spop myset
"a5"
127.0.0.1:6379> spop myset 2
1) "a0"
2) "a2"
127.0.0.1:6379> smembers myset
1) "a6"
2) "a4"
3) "a3"

smove

将一个指定的值移动到另一个集合中

1
2
3
4
5
6
7
8
9
10
11
12
# smove source destination member
127.0.0.1:6379> smembers myset
1) "a6"
2) "a4"
3) "a3"
127.0.0.1:6379> smove myset myset2 a3
(integer) 1
127.0.0.1:6379> smembers myset
1) "a6"
2) "a4"
127.0.0.1:6379> smembers myset2
1) "a3"

sinter & sunion & sdiff

分别表示交集、并集、差集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# sinter key [key ...]
# sunion key [key ...]
# sdiff key [key ...]
127.0.0.1:6379> smembers set1
1) "a1"
2) "a0"
3) "a2"
127.0.0.1:6379> smembers set2
1) "a4"
2) "a3"
3) "a2"
127.0.0.1:6379> sinter set1 set2
1) "a2"
127.0.0.1:6379> sunion set1 set2
1) "a4"
2) "a2"
3) "a1"
4) "a0"
5) "a3"
127.0.0.1:6379> sdiff set1 set2
1) "a0"
2) "a1"

Hash哈希类型

unordered_map

hset

set一个或多个具体的key-value

老版本的hset只能设置一组值

1
2
3
4
5
# hset key field value [field value ...]
127.0.0.1:6379> hset myhash name shaun
(integer) 1
127.0.0.1:6379> hset myhash age 0 some a1
(integer) 2

hget

获得一个hash的一个指定的值

1
2
3
# hget key field
127.0.0.1:6379> hget myhash name
"shaun"

hmset

hset功能一样,老的版本的hset只能设置一组值

1
# hmset key field value [field value ...]

hmget

获取一个hash中的一组或多组值

1
2
3
4
# hmget key field [field ...]
127.0.0.1:6379> hmget myhash name age
1) "shaun"
2) "0"

hgetall

获取一个hash中的所有值

1
2
3
4
5
6
7
8
# hgetall key
127.0.0.1:6379> hgetall myhash
1) "name"
2) "shaun"
3) "age"
4) "0"
5) "some"
6) "a1"

hdel

删除一个hash中的几个或多个指定的值

1
2
3
4
5
6
7
8
9
10
11
12
13
# hdel key field [field ...]
127.0.0.1:6379> hgetall myhash
1) "name"
2) "shaun"
3) "age"
4) "0"
5) "some"
6) "a1"
127.0.0.1:6379> hdel myhash name some
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "age"
2) "0"

hlen

获取一个hash的字段数量

1
2
3
4
5
6
7
8
# hlen key
127.0.0.1:6379> hgetall myhash
1) "age"
2) "0"
3) "name"
4) "shaun"
127.0.0.1:6379> hlen myhash
(integer) 2

hexists

判断一个hash中某个字段是否存在

1
2
3
# hexists key field
127.0.0.1:6379> hexists myhash name
(integer) 1

hkeys & hvals

仅获得一个hash中所有的keyvalue

1
2
3
4
5
6
7
8
# hkeys key
# hvals key
127.0.0.1:6379> hkeys myhash
1) "age"
2) "name"
127.0.0.1:6379> hvals myhash
1) "0"
2) "shaun"

hincrby

使一个hash中的某个值自增

与string中的incrby类似

1
# hincrby key field increment

hsetnx

与string中的setnx类似

1
# hsetnx key field value

Zset有序集合类型

一个有序集合,需要存入一个pair<int, string>记作<score, member>

int用于排序,string用于存内容

zadd

添加一个或多个值

1
2
3
4
5
# zadd key [NX|XX] [CH] [INCR] score member [score member ...]
127.0.0.1:6379> zadd myzset 1 a1
(integer) 1
127.0.0.1:6379> zadd myzset 2 a2 9 a9
(integer) 2

zrange

查找一个zset中某个区间的值,从小到大

1
2
3
4
5
# zrange key start stop [WITHSCORES]
127.0.0.1:6379> zrange myzset 0 -1
1) "a1"
2) "a2"
3) "a9"

zrevrange

从大到小,查找zset中某个区间的值

1
2
3
4
5
# zrevrange key start stop [WITHSCORES]
127.0.0.1:6379> zrevrange myzset 0 -1
1) "a9"
2) "a2"
3) "a1"

zrangebyscore

通过score进行给定区间的从小到大排序

后面的withscores是指会把score值也输出来

1
2
3
4
5
6
7
8
9
10
11
12
13
# zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> zrangebyscore myzset -inf +inf
1) "a1"
2) "a2"
3) "a9"
127.0.0.1:6379> zrangebyscore myzset 0 5
1) "a1"
2) "a2"
127.0.0.1:6379> zrangebyscore myzset 0 5 withscores
1) "a1"
2) "1"
3) "a2"
4) "2"

zrem

移除一个或多个元素

1
2
3
4
5
6
7
8
9
10
# zrem key member [member ...]
127.0.0.1:6379> zrange myzset 0 -1
1) "a1"
2) "a2"
3) "a9"
127.0.0.1:6379> zrem myzset a1
(integer) 1
127.0.0.1:6379> zrange myzset 0 -1
1) "a2"
2) "a9"

zcard

获取有序集合中的元素个数

1
2
3
# zcard key
127.0.0.1:6379> zcard myzset
(integer) 2

zcount

获取指定区间的成员数量

1
2
3
4
5
6
7
# zcount key min max
127.0.0.1:6379> zrevrange myzset 0 -1
1) "a9"
2) "a2"
3) "a1"
127.0.0.1:6379> zcount myzset 0 5
(integer) 2

Geospatial地理位置类型

存每个地方的经纬度

底层实现是使用zset实现的,可以使用zset的相关命令操作该类型

geoadd

添加一个或多个位置

南极和北极无法直接添加

1
2
3
4
5
# geoadd key longitude latitude member [longitude latitude member ...]
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai 106.50 29.53 chongqing
(integer) 2

geopos

获取一个或多个位置的经纬度

1
2
3
4
5
6
7
8
9
# geopos key member [member ...]
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "121.47000163793563843"
2) "31.22999903975783553"

geodist

获取两个位置之间的直线距离

单位:默认为米

  • m,米
  • km,千米
  • ft,英尺
  • mi,英里
1
2
3
4
5
# geodist key member1 member2 [m|km|ft|mi]
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"

georadius

通过给定位置和半径进行查询

count为获取指定数量的位置

withdist表示显示距离

1
2
3
4
5
6
7
8
9
# georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [A
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
127.0.0.1:6379> georadius china:city 110 30 10000 km
1) "chongqing"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> georadius china:city 110 30 10000 km count 1
1) "chongqing"

georadiusbymember

以一个成员位置为中心,查找给定半径内的位置

1
2
3
4
5
6
7
# georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE
127.0.0.1:6379> georadiusbymember china:city chongqing 1000 km
1) "chongqing"
127.0.0.1:6379> georadiusbymember china:city chongqing 10000 km
1) "chongqing"
2) "shanghai"
3) "beijing"

geohash

返回一个位置的经纬度的哈希值

1
2
3
4
# geohash key member [member ...]
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

Hyperloglog基数统计类型

基数:不重复的元素

优点:占用的内存很小

会有误差,但是误差很小,是可以接受的

pfadd

添加一个或多个元素

1
2
3
4
5
# pfadd key element [element ...]
127.0.0.1:6379> pfadd mykey a s d d f g h j k l
(integer) 1
127.0.0.1:6379> pfadd mykey2 z x c v d a a f t w g
(integer) 1

pfcount

统计基数

1
2
3
# pfcount key [key ...]
127.0.0.1:6379> pfcount mykey
(integer) 9

pfmerge

合并多个统计集合(并集)

1
2
3
4
5
# pfmerge destkey sourcekey [sourcekey ...]
127.0.0.1:6379> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 14

bitmap位图类型

按位存储,只有01两个状态

setbit

设置某个位的值

采用偏移量的下标进行定位

1
2
3
4
5
6
7
8
9
# setbit key offset value
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0

getbit

获取某个位的值

1
2
3
# getbit key offset
127.0.0.1:6379> getbit sign 2
(integer) 0

bitcount

获取一个bitmap中1的个数

也可以指定区间

1
2
3
# bitcount key [start end]
127.0.0.1:6379> bitcount sign
(integer) 3

事务

一组命令的集合

注:Redis单条命令具有原子性,但是事务不保证原子性

有一个队列,可以将各条命令加入到队列中

直到输入执行命令之后才会开始执行

一次性、顺序性、排他性

执行一个事务的时候不会被其他命令干扰

流程:

  • 开启事务
  • 命令入队
  • 执行事务

multi

开启事务

exec

执行事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# multi -
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> get a
QUEUED
127.0.0.1:6379> strlen a
QUEUED
# exec -
127.0.0.1:6379> exec
1) OK
2) OK
3) "1"
4) (integer) 1

discard

放弃事务,队列中的命令不会执行

1
2
3
4
5
6
7
8
9
10
11
# discard -
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set s 2
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> keys *
(empty array)

出错

  • 编译型异常

    代码编写有问题,命令使用错误,此时,事务中的所有命令都不会执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set a 1
    QUEUED
    127.0.0.1:6379> set s 2
    QUEUED
    127.0.0.1:6379> set c
    (error) ERR wrong number of arguments for 'set' command
    127.0.0.1:6379> set d 3
    QUEUED
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get a
    (nil)
  • 运行时异常

    出现了语法性错误,此时,其他命令会正常执行,错误命令会抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    127.0.0.1:6379> set str hello
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> incr str
    QUEUED
    127.0.0.1:6379> set a 1
    QUEUED
    127.0.0.1:6379> set s 2
    QUEUED
    127.0.0.1:6379> get a
    QUEUED
    127.0.0.1:6379> exec
    1) (error) ERR value is not an integer or out of range
    2) OK
    3) OK
    4) "1"
    127.0.0.1:6379> get s
    "2"

    此时是因为对字符串进行incr操作而产生异常,其他命令都成功运行。

使用watch实现乐观锁

  • 悲观锁:很悲观,认为什么时候都会出问题,无论做什么都会加锁
  • 乐观锁:很乐观,认为什么时候都不会出问题,不会上锁,只是在更新数据的时候会去判断一下在此期间是否有人修改过这个数据,如判断version

watch & unwatch

监视

监视一个值,在修改时会去判断在此期间是否有其他人修改多这个数据,如果有人修改过,则不会进行修改

监视的那个值是调用watch时的值,如果在调用watch之后当前进程对这个键的值进行修改,则事务也不会执行

watch是一次性的,在调用一个事务之后,watch即会失效,需要调用unwatch取消监视,再重新调用watch进行监视

只能用于事务

1
# watch key [key ...]

example

开两个线程,记为线程1和线程2

按顺序执行下列操作

线程1:

1
2
3
4
5
6
127.0.0.1:6379> set all 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch all
OK

线程2:

1
2
3
4
127.0.0.1:6379> get all
"100"
127.0.0.1:6379> incrby all 100
(integer) 200

线程1:

1
2
3
4
5
6
7
8
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby all 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
(nil)

此时,线程1就会执行失败,因为在此期间值all被线程2修改了

失败的解决方法

  1. 先解锁,即取消所有监视
  2. 再开启监视,即调用watch
  3. 再重新开启事务
  4. 如果失败,重新开始第1步,直到执行成功

持久化之RDB(Redis DataBase)

在手动调用或指定的时间间隔内将内存中的数据集快照写入磁盘,即Snapshot快照,恢复时是直接将快照文件读取到内存中

默认使用RDB进行持久化

存储位置以及自动存储时间间隔等配置可以通过配置文件进行更改,默认存储文件为dump.rdb

1
2
3
4
5
6
7
8
# 默认时间间隔
save 900 1
save 300 10
save 60 10000

# 默认存储位置
# The filename where to dump the DB
dbfilename dump.rdb

过程:

redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等持久化过程结束,再使用这个临时文件替换上次持久化好的文件。

整个过程中主进程是不会进行任何IO操作,确保了极高的性能。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB就会比AOF方式更高效。

缺点:在最后一次持久化之后的数据可能会丢失

save

即手动调用RDB存储

1
2
3
# save -
127.0.0.1:6379> save
OK

可以从server段的输出内容看到保存的情况

1
2
160:M 13 Jul 2023 15:50:33.914 * DB saved on disk
160:M 13 Jul 2023 15:51:48.780 * DB saved on disk

保存的触发机制

  • 在配置文件中的配置的情况满足的情况下,会自动触发保存
  • 手动调用save
  • 执行flushall命令
  • 退出redis,也会触发保存

持久化之AOF(Append Only File)

以日志的形式来记录每个写操作,将所有的写操作历史记录保存到一个文件中

恢复的时候就是把这个文件中的命令全部再执行一遍

默认是不开启的

1
2
3
4
5
6
7
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly no

no改为yes即可开启

文件位置

1
2
3
# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

默认为appendonly.aof

保存时间间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no

默认为everysec,即每秒保存

如果这个文件有错误,如被恶意修改,此时,redis是启动不起来的,需要修复这个appendonly.aof文件

redis给我们提供了一个工具

1
redis-check-aof --fix appendonly.aof

如果恢复成功,则可正常启动

当两种持久化都开启时,启动redis时会优先载入aof方法的文件

订阅发布

就类似于B站中的关注功能,可以订阅up主,up主可以发布动态,所有的粉丝都会收到动态(消息)

subscribe

订阅一个或多个频道

1
2
3
4
5
6
# subscribe channel [channel ...]
127.0.0.1:6379> subscribe shaun
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "shaun"
3) (integer) 1

执行完这个命令之后就会开始读取推送的信息

publish

发布内容(发布动态)

1
2
3
4
5
# publish channel message
127.0.0.1:6379> publish shaun hello
(integer) 1
127.0.0.1:6379> publish shaun world
(integer) 1

之后订阅这个频道的所有客户端都会收到这个消息

1
2
3
4
5
6
7
8
9
10
11
12
# 订阅这个频道的客户端
127.0.0.1:6379> subscribe shaun
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "shaun"
3) (integer) 1
1) "message"
2) "shaun"
3) "hello"
1) "message"
2) "shaun"
3) "world"

主从复制

指的是:将一台redis服务器上的数据,复制到其他redis服务器上。前者称为主节点(master/leader),后者称为从节点(slave/follower)

数据的复制时单向的,只能从主节点到从节点

master以写为主,slave以读为主

默认情况下,每台redis服务器都是主节点

一个主节点可以有零个或多个从节点,但一个从节点只能有一个主节点

主要作用

  • 数据冗余:实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复:主节点出现问题时,可以由从节点提供服务
  • 负载均衡:在主从复制的基础上,配合读写分离,可以主节点提供写服务,从节点提供读服务,分担服务器负载。可以大大提高redis服务器的并发量
  • 高可用(集群)基石:主从复制是哨兵和集群能够实施的基础

配置

只需要配置从节点,主节点不需要配置,因为每个节点都默认是主节点

查看当前节点的信息

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> info replication
# Replication
role:master # 角色 master
connected_slaves:0 # 连接的从机的数量
master_replid:fcd48402e5c2c4779693d9999a84c3476ed22749
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

example

一主两从,在此使用本机进行配置

首先创建3个配置文件,这里以端口进行命名

1
2
shaun@DESKTOP-JJRTDBP:~/redis/colony$ ls
redis_master_6379.conf redis_slave_6380.conf redis_slave_6381.conf

之后需要对这三个配置文件进行对应的更改

主要注意的几个配置项:

  • bind,连接的主机地址
  • port,连接的端口
  • pidfile,pid文件
  • logfile,日志文件
  • dbfilename,使用rdb进行持久化时需要有自己的dump.rdb文件,不能重复

redis_master_6379.conf

1
2
3
4
5
bind 127.0.0.1 ::1
port 6379
pidfile /var/run/redis/redis-server_6379.pid
logfile ./log/redis-server_6379.log
dbfilename dump_6379.rdb

redis_slave_6380.conf

1
2
3
4
5
bind 127.0.0.1 ::1
port 6380
pidfile /var/run/redis/redis-slave_6380.pid
logfile ./log/redis-slave_6380.log
dbfilename dump_6380.rdb

redis_slave_6381.conf

1
2
3
4
5
bind 127.0.0.1 ::1
port 6381
pidfile /var/run/redis/redis-slave_6381.pid
logfile ./log/redis-slave_6381.log
dbfilename dump_6381.rdb

配置好之后分别开启这几个服务端

我这里只能在root权限下才能成功开启服务

可以使用这个命令查看进程信息

1
2
3
4
5
shaun@DESKTOP-JJRTDBP:~/redis/colony$ ps -ef | grep redis
root 556 9 0 11:16 ? 00:00:00 redis-server 127.0.0.1:6379
root 576 9 0 11:17 ? 00:00:00 redis-server 127.0.0.1:6380
root 582 9 0 11:17 ? 00:00:00 redis-server 127.0.0.1:6381
shaun 588 281 0 11:17 pts/3 00:00:00 grep --color=auto redis

启动之后,可以启动客户端连接服务端,并使用info replication查看主机状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:f6a6b4b59c7cbc6225a04babaca578309cc7dcdc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# 6380
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_replid:fd20fbf6c2d1d6acd1ba342cb19d993d27d9d637
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# 6381
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_replid:fd5c40ad7c157b8dddc52524b9e33944f55fc707
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

默认三个服务端都是主节点,需要我们去配置从节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# slaveof host port
# 在从节点中使用该命令,表示作谁的从节点

# 6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:10
master_sync_in_progress:0
slave_repl_offset:140
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d9254af02b5bc2603e53c14cfbe20602f7b10374
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:140
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:140

# 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:770
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d9254af02b5bc2603e53c14cfbe20602f7b10374
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:770
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:771
repl_backlog_histlen:0

可以在从节点中看到主节点的信息

配置好之后,查看主节点的info replication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=798,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=798,lag=1
master_replid:d9254af02b5bc2603e53c14cfbe20602f7b10374
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:798
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:798

也可以在主节点中看到两个从节点的信息

以上是使用命令进行配置的,实际上应该是需要在配置文件中进行配置

在配置文件中,有REPLICATION字段

1
# replicaof <masterip> <masterport>

在此进行配置即可

主节点可以进行读和写的操作,但是从节点只能进行读操作,无法进行写操作

只要从机连接到主机,就会触发一次全量复制,即主节点会把所有数据传输到从节点一次。之后会进行增量复制,即每次写操作都会向从节点传输一次。

宕机

主节点宕机之后手动配置的流程

这时候需要将一个从节点变成主节点

首先执行slaveof no one来将一个从节点变成主节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6380
127.0.0.1:6380> slaveof no one
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_replid:ad9330d14b3efb752103cb0cdbddb565fbf1f7c7
master_replid2:d9254af02b5bc2603e53c14cfbe20602f7b10374
master_repl_offset:3494
second_repl_offset:3495
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3494

此时,其他节点就可以手动连接到这个新的主节点了

哨兵(自动选举老大)

image-20230714143537673

哨兵是一个独立的进程,哨兵会通过发送命令的方式,检查redis服务器是否有响应,以此来判断服务器是否出现故障。

程序redis-sentinel为哨兵

需要先配置哨兵的配置文件,之后启动这个程序即可自动开始监控

我这里电脑上下载的没有redis-sentinel,无法进行实际练习

缓存穿透

概念

image-20230714151324601

用户查询一个数据,如果redis缓存中没有,就会持续向持久层数据库发起查询,发现也没有,即查询失败。如果很多用户都同时持续发生这种情况,就会给持久层数据库造成很大的压力,即缓存穿透

解决方法

  • 布隆过滤器

    布隆过滤器是一种数据结构,对所有可能的查询的参数以hash形式存储,在控制层进行校验,如果一个查询不符合,则会丢弃,以此来避免对底层存储系统的查询压力

  • 缓存空对象

    当存储层不命中后,即使返回的是空对象也缓存起来,并设置一个过期时间,之后再访问这个数据就会从缓存中进行获取,保护了后端数据源

    缺点:存储空值也会消耗空间,空间浪费;即使有过期时间,也有可能会存在缓存层和存储层的数据会有一段时间窗口的不一致

缓存击穿

概念

有一个非常热的热点,不停的扛着大并发,并且这个大并发集中对一个点进行访问,当缓存中的这个key过期的一瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个点击穿一样,如果服务器没有扛住,就会出现宕机了

解决方法

  • 设置热点数据永不过期
  • 使用分布式锁

缓存雪崩

概念

指某一个时间段,缓存中的数据集中过期,或者缓存服务器宕机,断电

此时所有的请求都会到达底层存储

解决方法

  • redis高可用

    即多增几台服务器

  • 限流降级

    如停掉一些其他服务,保证这个服务可用

  • 数据预热

    在正式部署之前,把可能的数据先访问一遍,以加载到缓存中,手动设置不同的过期时间


文章作者: Shaun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Shaun !
评论
  目录