【转】10分钟了解redis应用
2024-12-09 11:13:16

本篇是从Redis在docker下的的安装和基本使用这篇文章中切出来的部分。

不过算是redis的基础知识和应用篇,目前还未完全了解,仅仅只是学习记录一下。

正文

目前独立开发项目时,出于节省时间的考虑,我试用了1panel进行部署。

如果你只是想快速完成开发,推荐使用1panel进行快速部署。

基础命令

redis-cli

默认连接:IP 127.0.0.1 端口 6379

1
redis-cli

指定IP端口:

1
redis-cli –h 127.0.0.1p 6379

Redis提供了PING-PONG机制,测试与客户端和服务器链接是否正常

1
redis-cli ping

1
2
3
redis-cli
redis 127.0.0.1:6379>ping
PONG

正常回复

1
2
127.0.0.1:6379>SET test 123
OK

错误回复(以error开头,后面跟着错误信息)

1
2
127.0.0.1:6379>TEST
(error) ERR unknown command 'TEST'

整数回复

1
2
127.0.0.1:6379>INCR test_incr
(integer) 1

字符串回复(最长久的一种回复,双引号包裹)

1
2
127.0.0.1:6379>get test
123

多行字符串回复

1
2
3
127.0.0.1:6379>KEYS *
1) "test_incr"
2) "test"

退出

1
127.0.0.1:6379> exit

关闭

1
127.0.0.1:6379> shutdown

keys

字符串类型是redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。可以存储JSON化的对象、字节数组等。一个字符串类型键允许存储的数据最大容量是512MB。

赋值与取值:

SET key value

GET key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set test1 123
OK
127.0.0.1:6379> set test2 ab
OK
127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> get test1
"123"
127.0.0.1:6379> get test2
"ab"
127.0.0.1:6379> get test3
(nil)
127.0.0.1:6379>

keys通配符

获取符合规则的建名列表。

1
2
3
KEYS *
keys test[_]*
keys t[a-d]

说明:

? 匹配一个字符,例如 keys ?est1

* 匹配任意个(包括0个)字符

[] 匹配括号间的任一字符,例如 keys test[12]。还可以使用“-“表示范围。

例如test[1-3]匹配test1/test2/test3

\x 匹配字符x,用于转义符合,如果要匹配“?“就需要使用?

select

redis默认支持16个数据库,对外都是以一个从0开始的递增数字命名,可以通过参数database来修改默认数据库个数。客户端连接redis服务后会自动选择0号数据库,可以通过select命令更换数据库,例如选择1号数据库:

1
2
3
4
127.0.0.1:6379>SELECT 1
OK
127.0.0.1:6379>GET test
(nil)

说明:

Redis不支持自定义数据库名称。

Redis不支持为每个数据库设置访问密码。

Redis的多个数据库之间不是安全隔离的,FLUSHALL命令会清空所有数据库的数据。

清除屏幕内容

1
clear

exists

判断一个键是否存在。

如果键存在则返回整数类型1,否则返回0。

1
2
3
4
5
6
7
8
127.0.0.1:6379> keys *
1) "test_incr"
2) "test1"
127.0.0.1:6379> exists test1
(integer) 1
127.0.0.1:6379> exists test3
(integer) 0
127.0.0.1:6379>

del

删除键,可以删除一个或者多个键,多个键用空格隔开,返回值是删除的键的个数。

1
2
3
4
5
6
7
127.0.0.1:6379> del test1
(integer) 1
127.0.0.1:6379> del test1
(integer) 0
127.0.0.1:6379> del test1 test_incr
(integer) 1
127.0.0.1:6379>

type

获得键值的数据类型,返回值可能是string(字符串)、hash(散列类型)、list(列表类型)、set(集合类型)、zset(有序集合类型)。

1
2
3
4
5
6
7
127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> type test1
string
127.0.0.1:6379> type test2
string

help

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> help
redis-cli 2.8.19
Type: "help @<group>" to get a list of commands in <group>
"help <command>" for help on <command>
"help <tab>" to get a list of possible help topics
"quit" to exit
127.0.0.1:6379> help type

TYPE key
summary: Determine the type stored at key
since: 1.0.0
group: generic

官网:www.redis.io帮助

flushall

清空所有数据库。

1
2
127.0.0.1:6379> FLUSHALL
OK

flushdb

1
2
127.0.0.1:6379> FLUSHDB
OK

Redis数据类型之字符串

存放的字符串为二进制是安全的。字符串长度支持到512M。

incry/incyby

递增数字INCR key当存储的字符串是整数时,redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> get test1
"123"
127.0.0.1:6379> get test1
"abc"
127.0.0.1:6379> get test2
(nil)
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> keys *
1) "num"
2) "test1"
3) "test"
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379>

从上面例子可以看出,如果num不存在,则自动会创建,如果存在自动+1。

指定增长系数

语法:INCRBY key increment

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
127.0.0.1:6379> incr num
(integer) 10
127.0.0.1:6379>

decr/decrby

减少指定的整数

DECR key 按照默认步长(默认为1)进行递减

DECRBY key decrement 按照指定步长进行递减

1
2
3
4
5
127.0.0.1:6379> incr num
(integer) 10
127.0.0.1:6379> decr num
(integer) 9
127.0.0.1:6379> decrby num 3

incrbyfloat

整数时,第一次加可以得到正确结果,浮点数后再加浮点就会出现精度问题。

原来下面的例子2.8.7注意在新版本中已经修正了这个浮点精度问题。3.0.7

INCRBYFLOAT key decrement

1
2
3
4
5
6
127.0.0.1:6379> set num 131
(integer) 131
127.0.0.1:6379> incrbyfloat num 0.7
“131.7”
127.0.0.1:6379> incrbyfloat num 0.7
“132.3999999999999999”

append

向尾部追加值。如果键不存在则创建该键,其值为写的value,即相当于SET key value。返回值是追加后字符串的总长度。

语法:APPEND key value

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> keys *
1) "num"
2) "test1"
3) "test"
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> append test "abc"
(integer) 6
127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379>

strlen

字符串长度,返回数据的长度,如果键不存在则返回0。注意,如果键值为空串,返回也是0。

语法:STRLEN key

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379> strlen test
(integer) 6
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> set tnt ""
OK
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> exists tnt
(integer) 1
127.0.0.1:6379>

mset/mget

同时设置/获取多个键值

语法:MSET key value [key value …]

MGET key [key …]

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
127.0.0.1:6379>

Redis有效时间

Expire (设置生效时长-单位秒)

Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置有效时间的(缓存内存是有限的,不可能无限制增加),即到期后数据自动销毁。

语法:EXPIRE key seconds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 10
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 5
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 2
127.0.0.1:6379> ttl bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379>

TTL查看key的剩余时间,当返回值为-2时,表示键被删除。

当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间。

注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。

Persist(取消时长设置)

通过persist让对特定key设置的生效时长失效。

语法:PERSIST key

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 60
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 49
127.0.0.1:6379> persist bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -1
127.0.0.1:6379>

设置新的数据时需要重新设置该key的生存时间,重新设置值也会清除生存时间。

pexpire(单位毫秒)

pexpire 让key的生效时长以毫秒作为计量单位,可应用于秒杀场景。

语法:PEXPIRE key milliseconds

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> pexpire bomb 10000
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 6
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379>

设置生存时间为毫秒,可以做到更精确的控制。

Redis高级中的hash结构

在redis中用的最多的就是hash和string类型。

(1)问题

假设有User对象以JSON序列化的形式存储到redis中,User对象有id、username、password、age、name等属性,存储的过程如下:

保存、更新:

User对象->json(string)->redis

如果在业务上只是更新age属性,其他的属性并不做更新应该怎么做呢?

Redis数据类型之散列类型hash

散列类型存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他类型,也就是说,散列类型不能嵌套其他的数据类型。一个散列类型可以包含最多232-1个字段。

(2)hset/hget

相关命令

1
2
3
4
5
HSET key field value
HGET key field
HMSET key field value [field value…]
HMGET key field [field]
HGETALL key

HSET和HGET赋值和取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379> hset user username chenchen
(integer) 1
127.0.0.1:6379> hget user username
"chenchen"
127.0.0.1:6379> hset user username chen
(integer) 0
127.0.0.1:6379> keys user
1) "user"
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379>
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user address "xi'an"
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
3) "age"
4) "18"
3) "address"
4) "xi'an"
127.0.0.1:6379>

HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。

(3)hincrby

1
2
3
4
127.0.0.1:6379> hdecrby article total 1		#执行会出错
127.0.0.1:6379> hincrby article total -1 #没有hdecrby自减命令
(integer) 1
127.0.0.1:6379> hget article total #获取值

(4)hmset/hmget

HMSET和HMGET设置和获取对象属性

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> hmset person username tony age 18
OK
127.0.0.1:6379> hmget person age username
1) "18"
2) "tony"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>

注意:上面HMGET字段顺序可以自行定义

(5)hexists

属性是否存在

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> hexists killer
(error) ERR wrong number of arguments for 'hexists' command
127.0.0.1:6379> hexists killer a
(integer) 0
127.0.0.1:6379> hexists user username
(integer) 1
127.0.0.1:6379> hexists person age
(integer) 1
127.0.0.1:6379>

(6) hdel

删除属性

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>

(7)hkeys/hvals

只获取字段名HKEYS或字段值HVALS

1
2
3
4
5
6
127.0.0.1:6379> hkeys person
1) "username"
2) "age"
127.0.0.1:6379> hvals person
1) "tony"
2) "18"

(8)hlen

元素个数

1
2
3
4
5
127.0.0.1:6379> hlen user
(integer) 1
127.0.0.1:6379> hlen person
(integer) 2
127.0.0.1:6379>

Redis高级中的list结构

(1)问题

Redis高级中的list结构

Redis的list类型其实就是一个每个子元素都是string类型的双向链表。可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。

有意思的是list的pop操作还有阻塞版本的,当我们[lr]pop一个list对象时,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop可以则可以阻塞,当然可以加超时时间,超时后也会返回nil。为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列。执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在。当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。

(2)lpush

在key对应list的头部添加字符串元素

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> lpush mylist "world"
(integer) 1
redis 127.0.0.1:6379> lpush mylist "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>

其中,Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

(3)rpush

在key对应list的尾部添加字符串元素

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> rpush mylist2 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist2 "world"
(integer) 2
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>

(4)查看list

1
redis 127.0.0.1:6379> lrange mylist3 0 -1

(5)del

1
redis 127.0.0.1:6379> del mylist

(6)linsert

在key对应list的特定位置之前或之后添加字符串元素

1
2
3
4
5
6
7
8
9
10
11
redis 127.0.0.1:6379> rpush mylist3 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist3 "world"
(integer) 2
redis 127.0.0.1:6379> linsert mylist3 before "world" "there"
(integer) 3
redis 127.0.0.1:6379> lrange mylist3 0 -1
1) "hello"
2) "there"
3) "world"
redis 127.0.0.1:6379>

(7)lset

设置list中指定下标的元素值(一般用于修改操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
redis 127.0.0.1:6379> rpush mylist4 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist4 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist4 "three"
(integer) 3
redis 127.0.0.1:6379> lset mylist4 0 "four"
OK
redis 127.0.0.1:6379> lset mylist4 -2 "five"
OK
redis 127.0.0.1:6379> lrange mylist4 0 -1
1) "four"
2) "five"
3) "three"
redis 127.0.0.1:6379>

(8)lrem

从key对应list中删除count个和value相同的元素,count>0时,按从头到尾的顺序删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist5 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist5 2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "foo"
2) "hello"
redis 127.0.0.1:6379>

count<0时,按从尾到头的顺序删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist6 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist6 -2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist6 0 -1
1) "hello"
2) "foo"
redis 127.0.0.1:6379>

count=0时,删除全部

1
2
3
4
5
6
7
8
9
10
11
12
13
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist7 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist7 0 "hello"
(integer) 3
redis 127.0.0.1:6379> lrange mylist7 0 -1
1) "foo"
redis 127.0.0.1:6379>

(9)ltrim

保留指定key 的值范围内的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
redis 127.0.0.1:6379> rpush mylist8 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist8 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist8 "three"
(integer) 3
redis 127.0.0.1:6379> rpush mylist8 "four"
(integer) 4
redis 127.0.0.1:6379> ltrim mylist8 1 -1
OK
redis 127.0.0.1:6379> lrange mylist8 0 -1
1) "two"
2) "three"
3) "four"
redis 127.0.0.1:6379>

(10)lpop

从list的头部删除元素,并返回删除元素

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> lpop mylist
"hello"
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "world"
redis 127.0.0.1:6379>

(11)rpop

从list的尾部删除元素,并返回删除元素:

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> rpop mylist2
"world"
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
redis 127.0.0.1:6379>

(12)llen

返回key对应list的长度:

1
2
3
redis 127.0.0.1:6379> llen mylist5
(integer) 2
redis 127.0.0.1:6379>

(13)index

返回名称为key的list中index位置的元素:

1
2
3
4
5
6
7
8
redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "three"
2) "foo"
redis 127.0.0.1:6379> lindex mylist5 0
"three"
redis 127.0.0.1:6379> lindex mylist5 1
"foo"
redis 127.0.0.1:6379>

(14)rpoplpush

从第一个list的尾部移除元素并添加到第二个list的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list是空或者不存在返回nil:

1
2
rpoplpush lst1 lst1
rpoplpush lst1 lst2

Redis高机中的set结构

Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为232 - 1 (4294967295每个集合可存储40多亿个成员)。

(1)sadd

添加元素,重复元素添加失败,返回0

1
2
3
4
5
6
7
8
127.0.0.1:6379> sadd name tony
(integer) 1
127.0.0.1:6379> sadd name hellen
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 0

(2)smembers

获取内容

1
2
3
4
127.0.0.1:6379> smembers name
1) "hellen"
2) "rose"
3) "tony"

(3)spop

移除并返回集合中的一个随机元素

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> spop internet
"rabbitmq"
127.0.0.1:6379> spop internet
"nginx"
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"

(4)scard

获取成员个数

1
2
127.0.0.1:6379> scard name
(integer) 3

(5)smove

移动一个元素到另外一个集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6379> sadd internet amoeba nginx redis
(integer) 3
127.0.0.1:6379> sadd bigdata hadopp spark rabbitmq
(integer) 3
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
3) "rabbitmq"
127.0.0.1:6379> smove bigdata internet rabbitmq
(integer) 1
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
127.0.0.1:6379>

(6)sunion

并集

1
2
3
4
5
6
7
127.0.0.1:6379> sunion internet bigdata
1) "redis"
2) "nginx"
3) "rabbitmq"
4) "amoeba"
5) "hadopp"
6) "spark"

Redis数据持久化的两种模式(重点)

(1)简介

Redis中为了保证在系统宕机(类似进程被杀死)情况下,能更快的进行故障恢复,设计了两种数据持久化方案,分别为rdb和aof。

Rdb方式是通过手动(save-阻塞式或bgsave-异步)或周期性方式保存redis中key/value的一种机制,Rdb方式一般为redis的默认数据持久化方式.

Aof方式是通过记录写操作日志的方式,记录redis数据的一种持久化机制,这个机制默认是没有开启的.

(2)rdb和aof比较

rdb aof
fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。 save,bgsave,shutdown, slave 命令会触发这个操作。粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。 把写操作指令,持续的写到一个类似日志文件里。(类似于从postgresql等数据库导出sql一样,只记录写操作) 粒度较小,crash(宕机)之后,只有crash之前没有来得及做日志的操作,这些数据是没办法恢复。

两种区别就是,一个是持续的用日志记录写操作,crash(崩溃)后利用日志恢复;一个是平时写操作的时候不触发写,只有手动提交save命令,或者是shutdown关闭命令时,才触发备份操作。

选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 最终一致性(eventually consistent)的意思了。

Redis事务管理(重点)

(1)背景

大多数数据库的事务控制,假如是乐观锁的方式,一般都是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。

Redis也采用类似的机制,使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视。

(2)基本概念

redis是单线程(但是在6.0中真正引用多线程的应用),提交命令时,其它命令无法插入其中,轻松利用单线程实现了事务的原子性。那如果执行多个redis命令呢?自然就没有事务保证,于是redis有下列相关的redis命令来实现事务管理。

multi 开启事务

exec 提交事务

discard 取消事务

watch 监控,如果监控的值发生变化,则提交事务时会失败

unwatch 去掉监控

Redis保证一个事务中的所有命令要么都执行,要么都不执行(原子性)。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

(3)exec提交事务

例如:模拟转账,王有200,张有700,张给王转100。过程如下:

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
127.0.0.1:6379> set w 200
OK
127.0.0.1:6379> set z 700
OK
127.0.0.1:6379> mget w z
1) "200"
2) "700"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby z 100
QUEUED #注意此命令根本没有执行,而是把其放在一个队列中
127.0.0.1:6379> incrby w 100
QUEUED
127.0.0.1:6379> mget w z
QUEUED
127.0.0.1:6379> get w #同时,这些相关的变量也不能再读取
QUEUED
127.0.0.1:6379> get z
QUEUED
127.0.0.1:6379> exec
1) (integer) 600
2) (integer) 300
3) 1) "300"
2) "600"
4) "300"
5) "600"
127.0.0.1:6379> mget w z
1) "300"
2) "600"
127.0.0.1:6379>

(4)如果有错误指令,自动取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> mget w z
1) "300"
2) "600"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> get w
QUEUED
127.0.0.1:6379> set w 100
QUEUED
127.0.0.1:6379> abc
(error) ERR unknown command 'abc'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget w z #可以看出数据并未变化
1) "300"
2) "600"
127.0.0.1:6379>

(5)discard取消事务

注意redis事务太简单,没有回滚,而只有取消。

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> mget z w
1) "600"
2) "300"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby z 100
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get z
"600"
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

(6)秒杀抢票事务处理

客户端1:

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> set money 0
OK
127.0.0.1:6379> watch ticket #乐观锁,对值进行观察,改变则事务失败
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> incrby money 100
QUEUED

客户端2:还没等客户端1提交事务,此时客户端2把票买到了。

1
2
3
4
127.0.0.1:6379> get ticket
"1"
127.0.0.1:6379> decr ticket
(integer) 0

客户端1:

1
2
3
4
5
127.0.0.1:6379> exec
(nil) #执行事务,失败
127.0.0.1:6379> get ticket
"0"
127.0.0.1:6379> unwatch #取消监控

结语

关于redis的基础知识及应用,是上篇分离出来的。

因为我个人不怎么用JAVA,所以把java的相关内容移除了。

参考

Redis在docker下的的安装和基本使用