Redis学习笔记

内容不定期更新,最新内容移步:https://www.upheart.top/

文件说明

可执行文件说明:

redis-server Redis服务器
redis-cli Redis命令行客户端
redis-benchmark Redis性能测试
redis-check-aof AOF文件修复工具
redis-check-dump RDB文件修复工具
redis-sentinel Sentinel服务器(2.8以后)

缓存击穿、雪崩、穿透

缓存穿透:

当利用一个不存在的Key访问缓存的时候,必然访问不到,命中miss,然后我们就会去数据库中查找,当存在一定量的并发,或者有人恶意频繁用不存在的key来访问我们的服务器的时候,一定量的次数后,数据库服务器必然承受不住,崩溃。

解决方案:

1.用一个bit map (布隆过滤器),将所有可能性的key都存储在其中,如果出现一定不存在的key,拦截掉该不可能存在的请求,不让其访问数据库。

2.如果出现访问不存在的key,可以返回一个null值,并存储在缓存中。但是可以设置其过期时间极短。

缓存雪崩:

当我们设置的一些数据key 采用相同的过期时间,当达到一个过期时间临界点的时候,大部分缓存过期,这时候如果一个高并发量的访问过来,就会同时访问数据库,造成数据压力或者崩溃。

解决:

1.可以在原有的失效时间的基础上,加上以个随机值,1-5分钟,这样就可以防止同时过期,造成大量数据库的访问。

2.分级缓存,第一级缓存失效的基础上,访问二级缓存,在二级缓存的失效同时在更新,分级缓存时间不同。

3.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

缓存击穿:

对于某一个热点数据,相对于雪崩,雪崩是多个数据,热点数据在某个时间段失效,高并发下大量访问数据库,造成数据库的压力。

解决:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key

常用命令

DEL命令

看到OK表示插入成功。通过DEL命令我们可以删除一个已经存在的key,如下:

127.0.0.1:6379> DEL k1
(integer) 1

看到(integer) 1表示数据已经删除成功

DUMP命令

DUMP命令可以序列化给定的key,并返回序列化之后的值:

127.0.0.1:6379> DUMP k1
"\x00\x02v1\b\x00\xe6\xc8\\\xe1bI\xf3c"

EXISTS命令

EXISTS命令用来检测一个给定的key是否存在,如下:

127.0.0.1:6379> EXISTS k1
(integer) 1
127.0.0.1:6379> EXISTS k2
(integer) 0
127.0.0.1:6379>

上面的运行结果表示k1存在而k2不存在

TTL命令

TTL命令可以查看一个给定key的有效时间:

127.0.0.1:6379> TTL k1
(integer) -1
127.0.0.1:6379> TTL k2
(integer) -2

-2表示key不存在或者已过期;-1表示key存在并且没有设置过期时间(永久有效)。当然,我们可以通过下面的命令给key设置一个过期时间

EXPIRE命令

EXPIRE命令可以给key设置有效期,在有效期过后,key会被销毁

127.0.0.1:6379> EXPIRE k1 30
(integer) 1
127.0.0.1:6379> TTL k1
(integer) 25
127.0.0.1:6379>

30表示30秒,TTL k1返回25表示这个key的有效期还剩25秒

PERSIST命令

PERSIST命令表示移除一个key的过期时间,这样该key就永远不会过期:

127.0.0.1:6379> EXPIRE k1 60
(integer) 1
127.0.0.1:6379> ttl k1
(integer) 57
127.0.0.1:6379> PERSIST k1
(integer) 1
127.0.0.1:6379> ttl k1
(integer) -1

PEXPIRE命令

PEXPIRE命令的功能和EXPIRE命令的功能基本一致,只不过这里设置的参数是毫秒:

127.0.0.1:6379> PEXPIRE k1 60000
(integer) 1

PTTL命令

PTTL命令和TTL命令基本一致,只不过PTTL返回的是毫秒数:

127.0.0.1:6379> PTTL k1
(integer) 25421

KEYS 命令

KEYS命令可以获取满足给定模式的所有key,比如:

127.0.0.1:6379> KEYS *
1) "k3"
2) "k2"
3) "k1"

KEYS *表示获取所有的KEY,*也可以是一个正则表达式

数据结构

数据类型

Redis中的数据都是以key/value的形式存储的,五大数据类型主要是指value的数据类型,包含如下五种:

STRING

STRING是redis中最基本的数据类型,redis中的STRING类型是二进制安全的,即它可以包含任何数据,比如一个序列化的对象甚至一个jpg图片,要注意的是redis中的字符串大小上限是512M

LIST

LIST是一个简单的字符串列表,按照插入顺序进行排序,我们可以从LIST的头部(LEFT)或者尾部(RIGHT)插入一个元素,也可以从LIST的头部(LEFT)或者尾部(RIGHT)弹出一个元素

HASH

HASH类似于Java中的Map,是一个键值对集合,在redis中可以用来存储对象

SET

SET是STRING类型的无序集合,不同于LIST,SET中的元素不可以重复

ZSET

ZSET和SET一样,也是STRING类型的元素的集合,不同的是ZSET中的每个元素都会关联一个double类型的分数,ZSET中的成员都是唯一的,但是所关联的分数可以重复

字符串

APPEND

使用APPEND命令时,如果key已经存在,则会直接在value后追加值,如果key不存在,则会先创建一个value为空字符串的key,然后再追加:

127.0.0.1:6379> APPEND k1 hello
(integer) 5
127.0.0.1:6379> GET k1
"hello"
127.0.0.1:6379> APPEND k1 world
(integer) 10
127.0.0.1:6379> GET k1
"helloworld"

DECR

DECR命令可以实现对value的减1操作,如果key不存在,则key对应的初始值会被置为0,如果key的value不为数字,则会报错,如下:

127.0.0.1:6379> SET k3 19
OK
127.0.0.1:6379> DECR k3
(integer) 18
127.0.0.1:6379> GET k3
"18"
127.0.0.1:6379> SET k4 aa
OK
127.0.0.1:6379> DECR k4
(error) ERR value is not an integer or out of range

DECRBY

DECRBY和DECR类似,不同的是DECRBY可以指定步长,如下:

127.0.0.1:6379> GET k3
"8"
127.0.0.1:6379> DECRBY k3 4
(integer) 4
127.0.0.1:6379> GET k3
"4"

GET

GET命令用来获取对应key的value,如果key不存在则返回nil,如下:

127.0.0.1:6379> GET k5
(nil)

GETRANGE

GETRANGE用来返回key所对应的value的子串,子串由start和end决定,从左往右计算,如果下标是负数,则从右往左计算,其中-1表示最后一个字符,-2是倒数第二个…,如下:

127.0.0.1:6379> SET k1 helloworld
OK
127.0.0.1:6379> GETRANGE k1 0 2
"hel"
127.0.0.1:6379> GETRANGE k1 -3 -1
"rld"

GETSET

GETSET命令可以用来获取key所对应的value,并对key进行重置,如下:

127.0.0.1:6379> SET k1 v1
OK
127.0.0.1:6379> GET k1
"v1"
127.0.0.1:6379> GETSET k1 vv
"v1"
127.0.0.1:6379> GET k1
"vv"

INCR

INCR操作可以对指定key的value执行加1操作,如果指定的key不存在,那么在加1操作之前,会先将key的value设置为0,如果key的value不是数字,则会报错。如下:

127.0.0.1:6379> INCR k2
(integer) 1

INCRBY

INCRBY和INCR功能类似,不同的是可以指定增长的步长,如下:

127.0.0.1:6379> INCRBY k2 99
(integer) 100

INCRBYFLOAT

INCRBYFLOAT命令可以用来增长浮点数,如下:

127.0.0.1:6379> SET k1 0.5
OK
127.0.0.1:6379> INCRBYFLOAT k1 0.33
"0.83"

MGET与MSET

MGET与MSET分别用来批量设置值和批量获取值,如下:

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> MGET k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

SETEX

SETEX用来给key设置value,同时设置过期时间,等效于先给key设置value,再给key设置过期时间,如下:

127.0.0.1:6379> SETEX k1 30 v1
OK
127.0.0.1:6379> TTL k1
(integer) 26
127.0.0.1:6379> GET k1
"v1"

PSETEX

PSETEX的作用和SETEX类似,不同的是,这里设置过期时间的单位是毫秒,如下:

127.0.0.1:6379> PSETEX k1 60000 v1
OK
127.0.0.1:6379> PTTL k1
(integer) 55412

SETNX

SETNX是 SET if Not eXists的简写,SET命令在执行时,如果key已经存在,则新值会覆盖掉旧值,而对于SETNX命令,如果key已经存在,则不做任何操作,如果key不存在,则效果等同于SET命令。如下:

127.0.0.1:6379> SETNX k1 v1
(integer) 1
127.0.0.1:6379> SETNX k1 vv
(integer) 0
127.0.0.1:6379> GET k1
"v1"

MSETNX

MSETNX兼具了SETNX和MSET的特性,但是MSETNX在执行时,如果有一个key存在,则所有的都不会执行,如下:

127.0.0.1:6379> MSETNX k1 v1 k2 v2
(integer) 0

因为k1已经存在,所以k2也没执行成功

SETRANGE

SETRANGE用来覆盖一个已经存在的key的value,如下:

127.0.0.1:6379> set k1 helloworld
OK
127.0.0.1:6379> get k1
"helloworld"
127.0.0.1:6379> SETRANGE k1 5 redis
(integer) 10
127.0.0.1:6379> get k1
"helloredis"

但是如果已经存在的key的value长度小于offset,则不足的地方用0补齐,如下:

127.0.0.1:6379> set k1 helloredis
OK
127.0.0.1:6379> SETRANGE k1 20 --java
(integer) 26
127.0.0.1:6379> GET k1
"helloredis\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00--java"

STRLEN

STRLEN用来计算key的value的长度,如下:

127.0.0.1:6379> STRLEN k1
(integer) 26

BIT相关命令

Redis中的字符串都是以二进制的方式进行存储的,比如说我执行如下命令:

127.0.0.1:6379> SET k1 a
OK

a对应的ASCII码是97,转换为二进制数据是01100001,我们BIT相关命令都是对这个二进制数据进行操作

GETBIT

GETBIT命令可以返回key对应的value在offset处的bit值,以上文提到的k1为例,a对应的二进制数据是01100001,所以当offset为0时,对应的bit值为0;offset为1时,对应的bit值为1;offset为2时,对应的bit值为1;offset为3时,对应的bit值为0,依此类推….,如下:

127.0.0.1:6379> GETBIT k1 0
(integer) 0
127.0.0.1:6379> GETBIT k1 1
(integer) 1
127.0.0.1:6379> GETBIT k1 2
(integer) 1
127.0.0.1:6379> GETBIT k1 3
(integer) 0
127.0.0.1:6379> GETBIT k1 4
(integer) 0
127.0.0.1:6379> GETBIT k1 5
(integer) 0
127.0.0.1:6379> GETBIT k1 6
(integer) 0
127.0.0.1:6379> GETBIT k1 7
(integer) 1

SETBIT

SETBIT可以用来修改二进制数据,比如a对应的ASCII码为97,c对应的ASCII码为99,97转为二进制是01100001,99转为二进制是01100011,两个的差异在于第六位一个是0一个是1,通过SETBIT命令,我们可以将k1的第六位的0改为1(第六位是从0开始算),如下:

127.0.0.1:6379> SETBIT k1 6 1
(integer) 0
127.0.0.1:6379> GET k1
"c"

此时,k1中存储的字符也就变为了c。SETBIT在执行时所返回的数字,表示该位上原本的bit值

BITCOUNT

BITCOUNT可以用来统计这个二进制数据中1的个数,如下:

127.0.0.1:6379> BITCOUNT k1
(integer) 4

关于BITCOUNT,redis官网上有一个非常有意思的案例:用户上线次数统计。节选部分原文如下:

举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。

这种统计方式最大的好处就是节省空间并且运算速度快。每天占用一个bit,一年也就365个bit,10年也就10*365个bit,也就是456个字节,对于这么大的数据,bit的操作速度非常快

BITOP

BITOP可以对一个或者多个二进制位串执行并(AND)、或(OR)、异或(XOR)以及非(NOT)运算,如下:a对应的ASCII码转为二进制是01100001,c对应的二进制位串是01100011。对这两个二进制位串分别执行AND\OR\XOR的结果如下:

127.0.0.1:6379> set k1 a
OK
127.0.0.1:6379> set k2 c
OK
127.0.0.1:6379> BITOP and k3 k1 k2
(integer) 1
127.0.0.1:6379> get k3
"a"
127.0.0.1:6379> BITOP or k3 k1 k2
(integer) 1
127.0.0.1:6379> get k3
"c"
127.0.0.1:6379> BITOP xor k3 k1 k2
(integer) 1
127.0.0.1:6379> get k3
"\x02"

另外,BITOP也可以执行NOT运算,但是注意参数个数,如下:

127.0.0.1:6379> BITOP not k3 k4
(integer) 1

这里会对k4的二进制位串取反,将取反结果交给k3

BITPOS

BITPOS用来获取二进制位串中第一个1或者0的位置,如下:

127.0.0.1:6379> set k1 a
OK
127.0.0.1:6379> BITPOS k1 1
(integer) 1
127.0.0.1:6379> BITPOS k1 0
(integer) 0

也可以在后面设置一个范围,不过后面的范围是字节的范围,而不是二进制位串的范围

集合

SADD

SADD命令可以添加一个或多个指定的member元素到集合的key中,指定的一个或者多个元素member如果已经在集合key中存在则忽略,如果集合key不存在,则新建集合key,并添加member元素到集合key中。如下:

127.0.0.1:6379> SADD k1 v1 v2 v3 v4
(integer) 4

SREM

SREM命令可以在key集合中移除指定的元素,如果指定的元素不是key集合中的元素则忽略。如果key集合不存在则被视为一个空的集合,该命令返回0。如下:

127.0.0.1:6379> SREM k1 v2
(integer) 1
127.0.0.1:6379> SREM k1 v10
(integer) 0

SISMEMBER

SISMEMBER命令可以返回成员member是否是存储的集合key的成员。如下:

127.0.0.1:6379> SISMEMBER k1 v3
(integer) 1

SCARD

SCARD命令可以返回集合存储的key的基数(集合元素的数量),如下:

127.0.0.1:6379> SCARD k1
(integer) 3

SMEMBERS

SMEMBERS命令可以返回key集合所有的元素,如下:

127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v1"
3) "v3"

SRANDMEMBER

SRANDMEMBER仅需我们提供key参数,它就会随机返回key集合中的一个元素,从Redis2.6开始,该命令也可以接受一个可选的count参数,如果count是整数且小于元素的个数,则返回count个随机元素,如果count是整数且大于集合中元素的个数时,则返回集合中的所有元素,当count是负数,则会返回一个包含count的绝对值的个数元素的数组,如果count的绝对值大于元素的个数,则返回的结果集里会出现一个元素出现多次的情况。如下:

127.0.0.1:6379> SRANDMEMBER k1
"v4"
127.0.0.1:6379> SRANDMEMBER k1 2
1) "v4"
2) "v1"
127.0.0.1:6379> SRANDMEMBER k1 5
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SRANDMEMBER k1 -1
1) "v4"
127.0.0.1:6379> SRANDMEMBER k1 -5
1) "v3"
2) "v1"
3) "v1"
4) "v3"
5) "v3"

SPOP

SPOP命令的用法和SRANDMEMBER类似,不同的是,SPOP每次选择一个随机的元素之后,该元素会出栈,而SRANDMEMBER则不会出栈,只是将该元素展示出来

SMOVE

SMOVE命令可以将member从source集合移动到destination集合中,如下:

127.0.0.1:6379> SMOVE k1 k2 v1
(integer) 1
127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v3"
127.0.0.1:6379> SMEMBERS k2
1) "v1"

SDIFF

SDIFF可以用来返回一个集合与给定集合的差集的元素,如下:

127.0.0.1:6379> SDIFF k1 k2
1) "v4"
2) "v3"

k1中的元素是v3、v4,k2中的元素是v1,差集就是v3、v4

SDIFFSTORE

SDIFFSTORE命令与SDIFF命令基本一致,不同的是SDIFFSTORE命令会将结果保存在一个集合中,如下:

127.0.0.1:6379> SDIFFSTORE key k1 k2
(integer) 2
127.0.0.1:6379> SMEMBERS key
1) "v4"
2) "v3"

SINTER

SINTER命令可以用来计算指定key之间元素的交集,如下:

127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v3"
127.0.0.1:6379> SMEMBERS k2
1) "v1"
2) "v3"
127.0.0.1:6379> SINTER k1 k2
1) "v3"

SINTERSTORE

SINTERSTORE命令和SINTER命令类似,不同的是它会将结果保存到一个新的集合中,如下:

127.0.0.1:6379> SINTERSTORE k3 k1 k2
(integer) 1
127.0.0.1:6379> SMEMBERS k3
1) "v3"

SUNION

SUNION可以用来计算两个集合的并集,如下:

127.0.0.1:6379> SUNION k1 k2
1) "v4"
2) "v1"
3) "v3"

SUNIONSTORE

SUNIONSTORE和SUNION命令类似,不同的是它会将结果保存到一个新的集合中,如下:

127.0.0.1:6379> SUNIONSTORE k4 k1 k2
(integer) 3
127.0.0.1:6379> SMEMBERS k4
1) "v4"
2) "v1"
3) "v3"

有序集合

有序集合类似Sets,但是每个字符串元素都关联到一个叫score浮动数值。里面的元素总是通过score进行着排序,因此它是可以检索的一系列元素

ZADD

ZADD命令可以将所有指定成员添加到键为key的有序集合里面。添加时可以指定多个分数/成员(score/member)对。 如果指定添加的成员已经是有序集合里面的成员,则会更新该成员的分数(scrore)并更新到正确的排序位置。如下:

127.0.0.1:6379> ZADD k1 60 v1
(integer) 1

ZSCORE

ZSCORE命令可以返回有序集key中,成员member的score值。如下:

127.0.0.1:6379> ZSCORE k1 v1
"60"

ZRANGE

ZRANGE命令可以根据index返回member,该命令在执行时加上withscores参数可以连同score一起返回:

127.0.0.1:6379> ZRANGE k1 0 3
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> ZRANGE k1 0 3 withscores
1) "v1"
2) "60"
3) "v2"
4) "70"
5) "v3"
6) "80"
7) "v4"
8) "90"

ZREVRANGE

ZREVRANGE和ZRANGE功能基本一致,不同的是ZREVRANGE是反着来的,如下:

127.0.0.1:6379> ZREVRANGE k1 0 3
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> ZREVRANGE k1 0 3 withscores
1) "v5"
2) "100"
3) "v4"
4) "90"
5) "v3"
6) "80"
7) "v2"
8) "70"

ZCARD

ZCARD命令可以返回key的有序集元素个数。如下:

127.0.0.1:6379> ZCARD k1
(integer) 5

ZCOUNT

ZCOUNT命令可以返回有序集key中,score值在min和max之间(默认包括score值等于min或max)的成员。如下:

127.0.0.1:6379> ZCOUNT k1 60 90
(integer) 4

如果在统计时,不需要包含60或者90,则添加一个 ( 即可,如下:

127.0.0.1:6379> ZCOUNT k1 60 (90
(integer) 3

ZRANGEBYSCORE

ZRANGEBYSCORE命令可以按照score范围范围元素,加上withscores可以连score一起返回。如下:

127.0.0.1:6379> ZRANGEBYSCORE k1 60 80
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> ZRANGEBYSCORE k1 60 80 withscores
1) "v1"
2) "60"
3) "v2"
4) "70"
5) "v3"
6) "80"
127.0.0.1:6379> ZRANGEBYSCORE k1 (60 80 withscores
1) "v2"
2) "70"
3) "v3"
4) "80"

ZRANK

ZRANK命令可以返回有序集key中成员member的排名。其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,即score值最小的成员排名为0。如下:

127.0.0.1:6379> ZRANK k1 v1
(integer) 0
127.0.0.1:6379> ZRANK k1 v2
(integer) 1

ZREVRANK

ZREVRANK和ZRANK命令功能基本一致,不同的是,ZREVRANK中的排序是从大到小:

127.0.0.1:6379> ZREVRANK k1 v1
(integer) 4
127.0.0.1:6379> ZREVRANK k1 v2
(integer) 3

ZINCRBY

ZINCRBY命令可以为有序集key的成员member的score值加上增量increment。如果key中不存在member,就在key中添加一个member,score是increment(就好像它之前的score是0.0)。如果key不存在,就创建一个只含有指定member成员的有序集合:

127.0.0.1:6379> ZINCRBY k1 3 v1
"63"
127.0.0.1:6379> ZRANGE k1 0 0 withscores
1) "v1"
2) "63"

ZINTERSTORE

ZINTERSTORE命令可以计算给定的numkeys个有序集合的交集,并且把结果放到destination中

在给定要计算的key和其它参数之前,必须先给定key个数(numberkeys)。该命令也可以在执行的过程中给原score乘以weights后再求和,如下:

127.0.0.1:6379> ZADD k2 2 v1
(integer) 1
127.0.0.1:6379> ZADD k2 3 v2
(integer) 1
127.0.0.1:6379> ZADD k2 4 v3
(integer) 1
127.0.0.1:6379> ZADD k3 9 v2
(integer) 1
127.0.0.1:6379> ZADD k3 10 v3
(integer) 1
127.0.0.1:6379> ZADD k3 11 v4
(integer) 1
127.0.0.1:6379> ZINTERSTORE k4 2 k2 k3
(integer) 2
127.0.0.1:6379> ZRANGE k4 0 -1 withscores
1) "v2"
2) "12"
3) "v3"
4) "14"
127.0.0.1:6379> ZINTERSTORE k5 2 k2 k3 weights 3 1
(integer) 2
127.0.0.1:6379> ZRANGE k5 0 -1 withscores
1) "v2"
2) "18"
3) "v3"
4) "22"

ZREM

ZREM命令可以从集合中弹出一个元素,如下:

127.0.0.1:6379> ZRANGE k2 0 -1 withscores
1) "v1"
2) "2"
3) "v2"
4) "3"
5) "v3"
6) "4"
127.0.0.1:6379> ZREM k2 v1
(integer) 1
127.0.0.1:6379> ZRANGE k2 0 -1 withscores
1) "v2"
2) "3"
3) "v3"
4) "4"

ZLEXCOUNT

ZLEXCOUNT命令用于计算有序集合中指定成员之间的成员数量。如下:

127.0.0.1:6379> ZLEXCOUNT k2 - +
(integer) 2
127.0.0.1:6379> ZLEXCOUNT k2 [v2 [v4
(integer) 2

**注意:可以用-+**表示得分最小值和最大值,如果使用成员名的话,一定要在成员名之前加上[

ZRANGEBYLEX

ZRANGEBYLEX 返回指定成员区间内的成员,按成员字典正序排序, 分数必须相同。如下:

127.0.0.1:6379> ZRANGEBYLEX k2 [v2 [v4
1) "v2"
2) "v3"
127.0.0.1:6379> ZRANGEBYLEX k2 - +
1) "v2"
2) "v3"
127.0.0.1:6379>

注意min和max参数的写法和ZLEXCOUNT一致

有序集合原理

Redis有序集合中的元素的编码可以是 ziplist 或者 skiplist

ziplist和skiplist编码选择的标准在于Redis里的元素的数量以及元素成员的长度

当满足以下2个条件时,元素编码为ziplist: 1.有序集合保存的元素数量小于128个 2.有序集合保存的所有元素成员的长度小于64字节

同时当条件不满足的时候,ziplist可以向skiplist转换

ziplist: ziplist编码的有序集合对象使用压缩列表作为底层实现。每个集合使用2个紧挨在一起的压缩列表节点来保存,第一个保存元素的成员,第二个保存元素的分值。压缩列表内的集合按分值从小到大排序,分值较小的元素被放置在靠近表头的位置,分值较大的元素在靠近表尾的位置

skiplist: skiplist编码的有序集合对象使用 zset结构作为底层实现,zset结构同时包含一个字典和一个跳跃表

zset结构的跳跃表按分值从小到大保存了所有集合元素,每个跳跃表都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性保存了元素的分值。 zset里还保存了一个从成员到分值的映射

散列

HSET

HSET命令可以用来设置key指定的哈希集中指定字段的值,如下:

127.0.0.1:6379> HSET k1 h1 v1
(integer) 1

HGET

HGET命令可以用来返回key指定的哈希集中该字段所关联的值,如下:

127.0.0.1:6379> HGET k1 h1
"v1"

HMSET

HMSET命令可以批量设置key指定的哈希集中指定字段的值,如下:

127.0.0.1:6379> HMSET k2 h1 v1 h2 v2 h3 v3
OK

HMGET

HMGET可以批量返回key指定的哈希集中指定字段的值,如下:

127.0.0.1:6379> HMGET k2 h1 h2 h3
1) "v1"
2) "v2"
3) "v3"

HDEL

HDEL命令可以从key指定的哈希集中移除指定的域,在哈希集中不存在的域将被忽略,如下:

127.0.0.1:6379> HMGET k2 h1 h2 h3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> HDEL k2 h1
(integer) 1
127.0.0.1:6379> HMGET k2 h1 h2 h3
1) (nil)
2) "v2"
3) "v3"

HSETNX

HSETNX命令只在key指定的哈希集中不存在指定的字段时,设置字段的值,如果字段已存在,该操作无效果。如下:

127.0.0.1:6379> HSETNX k2 h3 1
(integer) 0
127.0.0.1:6379> HSETNX k2 h4 1
(integer) 1

HVALS

HVALS命令可以返回key指定的哈希集中所有字段的值,如下:

127.0.0.1:6379> HVALS k2
1) "v2"
2) "v3"
3) "1"

HKEYS

HKEYS命令可以返回key指定的哈希集中所有字段的名字,如下:

127.0.0.1:6379> HKEYS k2
1) "h2"
2) "h3"
3) "h4"

HGETALL

HGETALL命令可以返回key指定的哈希集中所有的字段和值。返回值中,每个字段名的下一个是它的值,所以返回值的长度是哈希集大小的两倍,如下:

127.0.0.1:6379> HGETALL k2
1) "h2"
2) "v2"
3) "h3"
4) "v3"
5) "h4"
6) "1"

HEXISTS

HEXISTS命令可以返回hash里面field是否存在,如下:

127.0.0.1:6379> HEXISTS k2 h3
(integer) 1

HINCRBY

HINCRBY可以增加key指定的哈希集中指定字段的数值。如果key不存在,会创建一个新的哈希集并与key关联。如果字段不存在,则字段的值在该操作执行前被设置为0,HINCRBY支持的值的范围限定在64位有符号整数,如下:

127.0.0.1:6379> HEXISTS k2 h3
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> HGET k2 h4
"1"
127.0.0.1:6379> HINCRBY k2 h4 5
(integer) 6
127.0.0.1:6379> HGET k2 h4
"6"
127.0.0.1:6379> HGET k2 h5
(nil)
127.0.0.1:6379> HINCRBY k2 h5 99
(integer) 99
127.0.0.1:6379> HGET k2 h5
"99"

HINCRBYFLOAT

HINCRBYFLOAT与HINCRBY用法基本一致,只不过这里允许float类型的数据

HLEN

HLEN返回key指定的哈希集包含的字段的数量,如下:

127.0.0.1:6379> HLEN k2
(integer) 4

HSTRLEN

HSTRLEN可以返回hash指定field的value的字符串长度,如果hash或者field不存在,返回0,如下:

127.0.0.1:6379> HSTRLEN k2 h2
(integer) 2

列表

LPUSH

将一个或多个值value插入到列表key的表头,如果有多个value值,那么各个value值按从左到右的顺序依次插入到表头,如下:

127.0.0.1:6379> LPUSH k1 v1 v2 v3
(integer) 3

LRANGE

返回列表key中指定区间内的元素,区间以偏移量start和stop指定,下标(index)参数start和stop都以0为底,即0表示列表的第一个元素,1表示列表的第二个元素,以此类推。我们也可以使用负数下标,以-1表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。如下:

127.0.0.1:6379> LRANGE k1 0 -1
1) "v3"
2) "v2"
3) "v1"

RPUSH

RPUSH与LPUSH的功能基本一致,不同的是RPUSH的中的value值是按照从右到左的顺序依次插入,如下:

127.0.0.1:6379> RPUSH k2 1 2 3 4 5
(integer) 5
127.0.0.1:6379> LRANGE k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

RPOP

RPOP命令可以移除并返回列表key的尾元素。如下:

127.0.0.1:6379> RPOP k2
"5"
127.0.0.1:6379> LRANGE k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"

LPOP

LPOP和RPOP类似,不同的是LPOP移除并返回列表key的头元素,如下:

127.0.0.1:6379> LPOP k2
"1"
127.0.0.1:6379> LRANGE k2 0 -1
1) "2"
2) "3"
3) "4"

LINDEX

LINDEX命令可以返回列表key中,下标为index的元素,正数下标0表示第一个元素,也可以使用负数下标,-1表示倒数第一个元素,如下:

127.0.0.1:6379> LINDEX k2 0
"2"
127.0.0.1:6379> LINDEX k2 -1
"4"

LTRIM

LTRIM命令可以对一个列表进行修剪,即让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除

如下:

127.0.0.1:6379> LRANGE k1 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LTRIM k1 0 1
OK
127.0.0.1:6379> LRANGE k1 0 -1
1) "v3"
2) "v2"

BLPOP

BLPOP是阻塞式列表的弹出原语。它是命令LPOP的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP命令阻塞。当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。同时,在使用该命令时也需要指定阻塞的时长,时长单位为秒,在该时长内如果没有元素可供弹出,则阻塞结束。返回的结果是key和value的组合,如下:

127.0.0.1:6379> BLPOP k1 10
1) "k1"
2) "v2"
127.0.0.1:6379> BLPOP k1 10
(nil)
(10.03s)

最后,BRPOP、BPOPLPUSH、BRPOPLPUSH都是相应命令的阻塞版本

事务

Redis的事务不是原子性

事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令

事务的生命周期:

事务的创建:使用MULTI开启一个事务

加入队列:在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行

EXEC命令进行提交事务

常用的关于事务的命令有:

MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。

EXEC:执行这个事务内的所有命令

DISCARD:放弃事务,即该事务内的所有命令都将取消

WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)

UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消

在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务

发布订阅

订阅消息的方式如下:

127.0.0.1:6379> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3

这个表示接收c1,c2,c3三个频道传来的消息,发送消息的方式如下:

127.0.0.1:6379> PUBLISH c1 "hello redis!"
(integer) 1

当c1这个频道上有消息发出时,此时在消息订阅控制台可以看到如下输出:

1) "message"
2) "c1"
3) "hello redis!"

在redis中,我们也可以使用模式匹配订阅,如下:

127.0.0.1:6379> PSUBSCRIBE c*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "c*"
3) (integer) 1

此时可以接收到所有以c开头的频道发来的消息

redis中的发布订阅系统在某些场景下还是非常好用的,但是也有一些问题需要注意:由于网络在传输过程中可能会遭遇断线等意外情况,断线后需要进行重连,然而这会导致断线期间的数据丢失

为什么是单线程

Redis 核心就是 如果我的数据全都在内存里,单线程的操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以单线程处理这个事。在内存的情况下,这个方案就是最佳方案

**为什么redis 单线程模型也能效率这么高? **

纯内存操作 核心是基于非阻塞的 IO 多路复用机制 单线程反而避免了多线程的频繁上下文切换问题

为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间

Keys命令的性能问题

SCAN 命令 (以及相关的 SSCAN/HSCAN/ZSCAN,分别用于 SET/HASH/ZSET) 用于增量式的遍历一个集合中的元素。因为其增量特性 (每次使用只返回一小部分元素),所以在生产环境中可以用来替代 KEYS 或 SMEMBERS 命令 (KEYS 或 SMEMBERS 命令可能会因为返回的元素过多而阻塞 redis)

所以在生产环境中即便 redis 服务支持 keys 命令,也应该用 scan 来代替

Keys模糊匹配,请大家在实际运用的时候忽略掉。因为Keys会引发Redis锁,并且增加Redis的CPU占用,情况是很恶劣的

持久化

快照持久化

快照持久化,顾名思义,就是通过拍摄快照的方式实现数据的持久化,redis可以在某个时间点上对内存中的数据创建一个副本文件,副本文件中的数据在redis重启时会被自动加载,我们也可以将副本文件拷贝到其他地方一样可以使用

如何配置快照持久化

redis中的快照持久化默认是开启的,redis.conf中相关配置主要有如下几项:

save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
dbfilename dump.rdb
dir ./

前面三个save相关的选项表示备份的频率,分别表示900秒内至少一个键被更改则进行快照,300秒内至少10个键被更改则进行快照,60秒内至少10000个键被更改则进行快照,stop-writes-on-bgsave-error表示在快照创建出错后,是否继续执行写命令,rdbcompression则表示是否对快照文件进行压缩,dbfilename表示生成的快照文件的名字,dir则表示生成的快照文件的位置,在redis中,快照持久化默认就是开启的

快照持久化操作流程

1.在redis运行过程中,我们可以向redis发送一条save命令来创建一个快照,save是一个阻塞命令,redis在接收到save命令之后,开始执行备份操作之后,在备份操作执行完毕之前,将不再处理其他请求,其他请求将被挂起,因此这个命令我们用的不多。save命令执行如下:

127.0.0.1:6379> SAVE
OK

2.在redis运行过程中,我们也可以发送一条bgsave命令来创建一个快照,不同于save命令,bgsave命令会fork一个子进程,然后这个子进程负责执行将快照写入硬盘,而父进程则继续处理客户端发来的请求,这样就不会导致客户端命令阻塞了。如下:

127.0.0.1:6379> BGSAVE
Background saving started

3.如果我们在redis.conf中配置了如下选项:

save 900 1
save 300 10
save 60 10000

那么当条件满足时,比如900秒内有一个key被操作了,那么redis就会自动触发bgsava命令进行备份。我们可以根据实际需求在redis.conf中配置多个这种触发规则

4.还有一种情况也会触发save命令,那就是我们执行shutdown命令时,当我们用shutdown命令关闭redis时,此时也会执行一个save命令进行备份操作,并在备份操作完成后将服务器关闭

5.还有一种特殊情况也会触发bgsave命令,就是在主从备份的时候。当从机连接上主机后,会发送一条sync命令来开始一次复制操作,此时主机会开始一次bgsave操作,并在bgsave操作结束后向从机发送快照数据实现数据同步

快照持久化的缺点

快照持久化有一些缺点,比如save命令会发生阻塞,bgsave虽然不会发生阻塞,但是fork一个子进程又要耗费资源,在一些极端情况下,fork子进程的时间甚至超过数据备份的时间。定期的持久化也会让我们存在数据丢失的风险,最坏的情况我们可能丢失掉最近一次备份到当下的数据,具体丢失多久的数据,要看我们项目的承受能力,我们可以根据项目的承受能力配饰save参数

AOF持久化

AOF持久化是将被执行的命令写到aof文件末尾,在恢复时只需要从头到尾执行一遍写命令即可恢复数据,AOF在redis中默认也是没有开启的,需要我们手动开启,开启方式如下:

打开redis.conf配置文件,修改appendonly属性值为yes,如下:

appendonly yes

另外几个和AOF相关的属性如下:

appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

这几个属性的含义分别如下:

1.appendfilename表示生成的AOF备份文件的文件名
2.appendfsync表示备份的时机,always表示每执行一个命令就备份一次,everysec表示每秒备份一次,no表示将备份时机交给操作系统
3.no-appendfsync-on-rewrite表示在对aof文件进行压缩时,是否执行同步操作
4.最后两行配置表示AOF文件的压缩时机

同时为了避免快照备份的影响,我们将快照备份关闭,关闭方式如下:

save ""
# save 900 1
# save 300 10
# save 60 10000

此时,当我们在redis中进行数据操作时,就会自动生成AOF的配置文件appendonly.aof

我们在项目中首选everysec,always选项会严重降低redis性能
使用everysec,最坏的情况下我们可能丢失1秒的数据

AOF文件的重写与压缩

AOF备份有很多明显的优势,当然也有劣势,那就是文件大小。随着系统的运行,AOF的文件会越来越大,甚至把整个电脑的硬盘填满,AOF文件的重写与压缩机制可以在一定程度上缓解这个问题

当AOF的备份文件过大时,我们可以向redis发送一条bgrewriteaof命令进行文件重写,如下:

127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
(0.71s)

bgrewriteaof的执行原理和我们上面说的bgsave的原理一致,因此bgsave执行过程中存在的问题在这里也一样存在

bgrewriteaof也可以自动执行,自动执行时间则依赖于auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置,auto-aof-rewrite-percentage 100表示当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据,同时还要求AOF文件的大小至少要大于64M(auto-aof-rewrite-min-size 64mb)

最佳实践

1.如果redis只做缓存服务器,那么可以不使用任何持久化方式

2.同时开启两种持久化方式,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整;RDB的数据不完整时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段

3.因为RDB文件只用作后备用途,建议只在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则

4.如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值

5.如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个

集群

集群原理

原理如下:

1.所有的Redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
2.节点的fail是通过集群中超过半数的节点检测失效时才生效
3.客户端与Redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
4.Redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster (簇)负责维护node<->slot<->value。Redis集群中内置了16384个哈希槽,当需要在Redis集群中放置一个key-value时,Redis先对key使用crc16算法算出一个结果,然后把结果对 16384 求余数,这样每个key都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

怎么样投票

投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超过cluster-node-timeout设置的时间,认为当前master节点挂掉

怎么样判定节点不可用

1.如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态
2.如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态,当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误

主从复制

复制原理

每一个master都有一个replication ID,这是一个较大的伪随机字符串,标记了一个给定的数据集

每个master也持有一个偏移量,master将自己产生的复制流发送给slave时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新slave的状态

复制偏移量即使在没有一个slave连接到master时,也会自增,所以基本上每一对给定的Replication ID, offset都会标识一个master数据集的确切版本

简单来说,就是以下几个步骤:

1.slave启动成功连接到master后会发送一个sync命令。  
2.Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令。  
3.在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。  
4.全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。  
5.增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。  
6.但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。  

哨兵模式

当主机宕机时,就会发生群龙无首的情况,如果在主机宕机时,能够从从机中选出一个来充当主机,那么就不用我们每次去手动重启主机了,那就是哨兵模式

所谓的哨兵模式,其实并不复杂,我们还是在我们前面的基础上来搭建哨兵模式。假设现在我的master是6379,两个从机分别是6380和6381,两个从机都是从6379上复制数据,在redis目录下打开sentinel.conf文件,做如下配置:

sentinel monitor mymaster 127.0.0.1 6379 1

其中mymaster是给要监控的主机取的名字,随意取,后面是主机地址,最后面的1表示有多少个sentinel认为主机挂掉了,就进行切换,输入如下命令启动哨兵:

redis-sentinel sentinel.conf

6379挂掉之后,redis内部重新举行了选举,6380重新上位。此时,如果6379重启,也不再是扛把子了,只能屈身做一个slave了

你可能感兴趣的:(数据库)