不要错过的Redis
一、引入介绍
当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化
二、Redis 特点
- 高效性:Redis读取的速度是110000次/s,写的速度是81000次/s
- 原子性:Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
- 支持多种数据结构:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合)
- 稳定性:持久化,主从复制(集群)
- 其他特性:支持过期时间,支持事务,消息订阅
在Redis中存储的时KV类型的数据,上面所说的多种数据结构是对应的V(Value)的支持的数据结构,而为了二进制安全,里面都是存储时字节数组。
1. 常常被问到:Redis 是单线程还是多线程?为什么?
这个问题个人觉得是一个死亡命题,单纯的回答是单线程或者多线程都不是太合适,如果严格的说的话,Redis在处理业务逻辑的时候是单线程的 ,在我们启动Redis Server后会在Linux上有个进程id即Pid,然后这个进程会等待客户端的发送过来的请求,但并不是接收到请求之后,Redis Server就处理这些请求,然后通过clone的方式进行写时复制技术fork出一个新的进程,这个新的进程回去处理Client端的请求。那么Redis Server是怎么工作的呢?我们可以通过strace 命令追踪Server Pid 下的系统调用,发现Server 启动后一直调用一个命令Epoll_wait。
2. 由epoll引出IO的发展史
Epoll_wait是什么?说这个之前要简单描述下IO 的发展史:
2.1 BIO 阻塞式IO
-
最初的时候想法都是很简单,server端一直监听client端的发起的请求,来了一个请求那么server启动线程来处理该请求,此时client阻塞等待server上处理线程的处理结果
- –> 当请求量激增时,server端的压力会变大,client的等待时间也会增加
-
可以用一个伪代码来表示:
while(true){ socket = socket.accept();//阻塞等待一个请求可读 new Thread().run({ //socket todo something... }); }
2.2 伪异步IO
- 开启和销毁一个线程的消耗是很大的,所以可以利用线程池的方式来实现线程的复用,提高资源利用率
- clients的请求会将socket请求封装为task向线程池中提交,处理然会返回结果
- –> 因为线程池维护了任务队列有限,拥有最大线程数的限制,当客户端的并发访问数上升的时候,会造成线程池阻塞
2.3 NIO (同步非阻塞IO)
- 在NIO时socket创建是指定的是No-Block模式,那么Client端在尝试读取server数据的时候,如果数据还未到达那么会返回给client一个“没有数据的错误”,client会结束阻塞等待的状态
while(true){
if(socket.isReadable()){
content = socket.read();
new Thread().run({
//socket todo something...
});
}
}
- –> 假设有1000个连接,同时打进来,你的每次确认连接是否可读都是需要进行请求内核调用的,只有内核才能知道这个连接是否可读,也就是会早就用户态和内核态需要频繁切换。
2.4 多路复用select (同步非阻塞)
能不能不这么频繁的去发生两态切换,类比于数据库的一次批量插入肯定比多次一条一条插入从成本和效率上都好得多。但我们用户态可做不了这个主,要想发生改变,只能是内核做出改变,那怎么做呢?
内核暴露了一个系统的调用函数,select()
摘抄一段函数解释:
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.
意思就是select()提供了对批量的文件描述符的监控,每次select()会阻塞,直到有一个或多个文件描述符准备好可读的时候,这意味着,我们把之前NIO对内核态的调用从一次次,变成了一次批量,每次只需要一次询问,内核会返回一个批次的可用的文件描述符,这样大幅度减少了两态的切换 。、
在内核返回的结果中,包含的是“可以进行read操作的那些描述符”,在Clients拿到后遍历得到这些描述符,然后调用read()方法。
从select()的过程中发现:
- 内核一次需要循环遍历1000次,有点忙
- 每次1000的数据有点多,耗内存
所以能不能传递的参数少点,内核能不能不要盲目遍历?
2.5 Epoll 多路复用(同步非阻塞)
为了优化下select()的问题,所以发展出epoll,首先看下在linux中epoll的解释:
The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll APIcan be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors
可以看出
- epoll_create 创建一个文件描述符
- epoll_ctl 控制epoll_create创建的文件描述符
- epoll_wait 等待一个epoll文件描述符上的IO事件
- 在redis server启动后监听6379(默认)端口产生一个问及那描述符fd6
- server先调用epoll_create 返回一个文件描述符 fd4(举例),再内核中会有对应的空间
- 然后调用epoll_ctl(fd4,fd6,accept) 方法
- 第一个参数 就是之前epoll_create创建的 fd4
- 第二个参数 是server启动时监听端口对应的fd6
- 第三个参数 是对应的事件 此时是 accept (接口客户端的请求)
- 现在基于事件的机制,server 调用epoll_wait 一直等待客户端的连接(更明确的说是等待系统中断,类型是客户端的请求)
- 假如此时一个客户端来了,系统先给一个该请求的文件描述符 fd7 ,epoll_wait的系统调用会监听到这个,当确认连接后,server端会再次调用epoll_ctl(fd4,fd7,read),read是客户端的读请求事件
从整个流程可以看出,epoll_create 只调用的了一次,epoll_ctl从启动到一个client到达一共调用了两次,而epoll_wait则是一直再循环调用,当一个新的client连接上server时,则会先调用epoll_ctl(f4,fdx,xxx),并把fdx对应的事件信息,放入左边的红黑数结构中,fdx放入右边的链表结构中,对比select可以发现内核不需要盲目的遍历、参数也少了,这样放入成本和遍历成本都变少了。
Redis 就是通过epoll这种多路复用的技术,server(单线程)启动后就是一直调用epoll_wait等待client连接。
三、说说Redis的字符串数据结构
对Redis中的string的操作也可以分几种:字符串、数值操作、bitmap,每一种下都有对应的丰富的api,相比于Memcache的单一存储功能,Redis的丰富API提供更好的使用体验。用type + key 命令查看时显示的是 “string”
但是如果用 这个指令的时候返回的结果就不一定都是string。
查看string类型下有哪些api: <help @string>
APPEND key value
summary: Append a value to a key
since: 2.0.0
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7
DECR key
summary: Decrement the integer value of a key by one
since: 1.0.0
DECRBY key decrement
summary: Decrement the integer value of a key by the given number
since: 1.0.0
GET key
summary: Get the value of a key
since: 1.0.0
GETBIT key offset
summary: Returns the bit value at offset in the string value stored at key
since: 2.2.0
GETRANGE key start end
summary: Get a substring of the string stored at a key
since: 2.4.0
GETSET key value
summary: Set the string value of a key and return its old value
since: 1.0.0
INCR key
summary: Increment the integer value of a key by one
since: 1.0.0
INCRBY key increment
summary: Increment the integer value of a key by the given amount
since: 1.0.0
INCRBYFLOAT key increment
summary: Increment the float value of a key by the given amount
since: 2.6.0
MGET key [key ...]
summary: Get the values of all the given keys
since: 1.0.0
MSET key value [key value ...]
summary: Set multiple keys to multiple values
since: 1.0.1
MSETNX key value [key value ...]
summary: Set multiple keys to multiple values, only if none of the keys exist
since: 1.0.1
PSETEX key milliseconds value
summary: Set the value and expiration in milliseconds of a key
since: 2.6.0
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
summary: Set the string value of a key
since: 1.0.0
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0
SETEX key seconds value
summary: Set the value and expiration of a key
since: 2.0.0
SETNX key value
summary: Set the value of a key, only if the key does not exist
since: 1.0.0
SETRANGE key offset value
summary: Overwrite part of a string at key starting at the specified offset
since: 2.2.0
STRLEN key
summary: Get the length of the value stored in a key
since: 2.2.0
3.1 字符串
查看string下
3.2 数值操作
127.0.0.1:6379> set k2 123
OK
127.0.0.1:6379> type k2
string
127.0.0.1:6379> OBJECT encoding k2
"int" --这里是int
127.0.0.1:6379> INCR k2
(integer) 124
127.0.0.1:6379> INCRBY k2 10
(integer) 134
127.0.0.1:6379> DECR k2
(integer) 133
127.0.0.1:6379>
INCR、DECR可以满足秒杀场景
3.3 bitmap
127.0.0.1:6379> SETBIT k1 1 1 -->代表的是 从左边数第二位的值是1 即 01000000
(integer) 0
127.0.0.1:6379> type k1
string
127.0.0.1:6379> OBJECT encoding k1
"raw"
127.0.0.1:6379> STRLEN k1
(integer) 1 --> 一个字节
127.0.0.1:6379> get k1
"@"
127.0.0.1:6379> SETBIT k1 6 1
(integer) 0
127.0.0.1:6379> get k1
"B"
127.0.0.1:6379> SETBIT k1 9 1
(integer) 0
127.0.0.1:6379> STRLEN k1
(integer) 2 --> 两个字节 长度被扩展了
127.0.0.1:6379> get k1 -->此时的 01000010 01000000
"B@"
127.0.0.1:6379> BITCOUNT k1 0 -1 --> 统计 从0到-1(即最后一位) 有多少个1
(integer) 3
127.0.0.1:6379> BITPOS k1 1 1 1 -->
(integer) 9
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> SETBIT k1 1 1
(integer) 0
127.0.0.1:6379> SETBIT k1 7 1
(integer) 0
127.0.0.1:6379> get k1
"A"
127.0.0.1:6379> SETBIT k2 1 1
(integer) 0
127.0.0.1:6379> SETBIT k2 6 1
(integer) 0
127.0.0.1:6379> get k2
"B"
127.0.0.1:6379> BITOP and(操作符&) reskey(结果key) k1 k2
(integer) 1
127.0.0.1:6379> get reskey
"@"
说那么多,说几个个场景:
-
统计任意时间窗口用户的登录情况,如双十一前三天、后三天,一般处理是建立一张表然后记录下用户的登录记录,然后查询。这个方法是可以的但是台麻烦了,表数据会不断变大查询效率也会降低。那么这个时候 bitmap 的作用的效果就来了。绝对的骚操作!
127.0.0.1:6379> SETBIT zhangsan 1 1 --> 第2天登录了 (integer) 0 127.0.0.1:6379> SETBIT zhangsan 2 1 --> 第3天登录了 (integer) 0 127.0.0.1:6379> SETBIT zhangsan 365 1 --> 第366天登录了 (integer) 0 127.0.0.1:6379> STRLEN zhangsan (integer) 46 --> 只是!只是!只是占用了46字节!!! 127.0.0.1:6379> BITCOUNT zhangsan 0 -1 --> 统计一年登录的情况 (integer) 3
-
统计任意时间段内的活跃用户
127.0.0.1:6379> SETBIT 20200101 8 1 --> 用户id为8的用户 20200101登录了 (integer) 0 127.0.0.1:6379> SETBIT 20200102 8 1 --> 用户id为8的用户 20200102登录了 (integer) 0 127.0.0.1:6379> SETBIT 20200102 11 1 --> 用户id为11的用户 20200102登录了 (integer) 0 127.0.0.1:6379> BITOP and andkey 20200101 20200102 (integer) 2 127.0.0.1:6379> get andkey "\x00\x80" 127.0.0.1:6379> BITCOUNT andkey 0 -1 (integer) 1 127.0.0.1:6379> BITOP or orkey 20200101 20200102 (integer) 2 127.0.0.1:6379> BITCOUNT orkey 0 -1 --> 20200101、20200102 统计2天的活跃用户 (integer) 2 127.0.0.1:6379>
四、说说Redis的列表数据结构
Redis中的list底层是一个双向链表,查看下支持哪些命令<help @list>
BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0
BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0
BRPOPLPUSH source destination timeout
summary: Pop a value from a list, push it to another list and return it; or block until one is available
since: 2.2.0
LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0
LINSERT key BEFORE|AFTER pivot value
summary: Insert an element before or after another element in a list
since: 2.2.0
LLEN key
summary: Get the length of a list
since: 1.0.0
LPOP key
summary: Remove and get the first element in a list
since: 1.0.0
LPUSH key value [value ...]
summary: Prepend one or multiple values to a list
since: 1.0.0
LPUSHX key value
summary: Prepend a value to a list, only if the list exists
since: 2.2.0
LRANGE key start stop
summary: Get a range of elements from a list
since: 1.0.0
LREM key count value
summary: Remove elements from a list
since: 1.0.0
LSET key index value
summary: Set the value of an element in a list by its index
since: 1.0.0
LTRIM key start stop
summary: Trim a list to the specified range
since: 1.0.0
RPOP key
summary: Remove and get the last element in a list
since: 1.0.0
RPOPLPUSH source destination
summary: Remove the last element in a list, prepend it to another list and return it
since: 1.2.0
RPUSH key value [value ...]
summary: Append one or multiple values to a list
since: 1.0.0
RPUSHX key value
summary: Append a value to a list, only if the list exists
since: 2.2.0
简单的使用介绍
127.0.0.1:6379> lpush list a b c d e f
(integer) 6
127.0.0.1:6379> LLEN list
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "f"
2) "e"
3) "d"
4) "c"
5) "b"
6) "a"
4.1 在list中可以实现两种数据结构:栈、队列
127.0.0.1:6379> LPOP list --> 栈 后进先出
"f"
127.0.0.1:6379> RPOP list --> 队列 先进先出
"a"
127.0.0.1:6379> LRANGE list 0 -1
1) "e"
2) "d"
3) "c"
4) "b"
127.0.0.1:6379> LINDEX list 2
"c"
127.0.0.1:6379> LSET list 2 x
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "e"
2) "d"
3) "x"
4) "b"
127.0.0.1:6379> LTRIM list 0 -1
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "e"
2) "d"
3) "x"
4) "b"
127.0.0.1:6379> LTRIM list 1 -2
OK
127.0.0.1:6379> LRANGE list 0 -1 --> 删除 2个下标之外的数据
1) "d"
2) "x"
场景: 评论列表 --> 将部分评论信息放到redis,然后利用lpop、rpop 获取
五、说说Redis的哈希数据结构
hash类似map一样
HDEL key field [field ...]
summary: Delete one or more hash fields
since: 2.0.0
HEXISTS key field
summary: Determine if a hash field exists
since: 2.0.0
HGET key field
summary: Get the value of a hash field
since: 2.0.0
HGETALL key
summary: Get all the fields and values in a hash
since: 2.0.0
HINCRBY key field increment
summary: Increment the integer value of a hash field by the given number
since: 2.0.0
HINCRBYFLOAT key field increment
summary: Increment the float value of a hash field by the given amount
since: 2.6.0
HKEYS key
summary: Get all the fields in a hash
since: 2.0.0
HLEN key
summary: Get the number of fields in a hash
since: 2.0.0
HMGET key field [field ...]
summary: Get the values of all the given hash fields
since: 2.0.0
HMSET key field value [field value ...]
summary: Set multiple hash fields to multiple values
since: 2.0.0
HSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate hash fields and associated values
since: 2.8.0
HSET key field value
summary: Set the string value of a hash field
since: 2.0.0
HSETNX key field value
summary: Set the value of a hash field, only if the field does not exist
since: 2.0.0
HSTRLEN key field
summary: Get the length of the value of a hash field
since: 3.2.0
HVALS key
summary: Get all the values in a hash
since: 2.0.0
127.0.0.1:6379> hset lisi name lisi
(integer) 1
127.0.0.1:6379> hset lisi age 19
(integer) 1
127.0.0.1:6379> type lisi
hash
127.0.0.1:6379> hget lisi age
"19"
127.0.0.1:6379> HKEYS lisi
1) "name"
2) "age"
场景:像这样的数据,一个key可以包含各个维度的信息,就像商品的详情页一样,分担数据库的压力
六、说说Redis的集合数据结构
SADD key member [member ...]
summary: Add one or more members to a set
since: 1.0.0
SCARD key
summary: Get the number of members in a set
since: 1.0.0
SDIFF key [key ...]
summary: Subtract multiple sets
since: 1.0.0
SDIFFSTORE destination key [key ...]
summary: Subtract multiple sets and store the resulting set in a key
since: 1.0.0
SINTER key [key ...]
summary: Intersect multiple sets
since: 1.0.0
SINTERSTORE destination key [key ...]
summary: Intersect multiple sets and store the resulting set in a key
since: 1.0.0
SISMEMBER key member
summary: Determine if a given value is a member of a set
since: 1.0.0
SMEMBERS key
summary: Get all the members in a set
since: 1.0.0
SMOVE source destination member
summary: Move a member from one set to another
since: 1.0.0
SPOP key [count]
summary: Remove and return one or multiple random members from a set
since: 1.0.0
SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0
SREM key member [member ...]
summary: Remove one or more members from a set
since: 1.0.0
SSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate Set elements
since: 2.8.0
SUNION key [key ...]
summary: Add multiple sets
since: 1.0.0
SUNIONSTORE destination key [key ...]
summary: Add multiple sets and store the resulting set in a key
since: 1.0.0
127.0.0.1:6379> SADD s1 tom jerry join xxoo ooxx xoxo
(integer) 6
127.0.0.1:6379> SCARD s1
(integer) 6
127.0.0.1:6379> SINTER s1 li
(empty list or set)
127.0.0.1:6379> SINTER s1 tom
(empty list or set)
127.0.0.1:6379> SISMEMBER s1 tom
(integer) 1
127.0.0.1:6379> SMEMBERS s1
1) "tom"
2) "xxoo"
3) "jerry"
4) "join"
5) "xoxo"
6) "ooxx"
127.0.0.1:6379> SRANDMEMBER s1 3 --> 没有重复数据
1) "join"
2) "xoxo"
3) "ooxx"
127.0.0.1:6379> SRANDMEMBER s1 -9 --> 会有重复数据
1) "ooxx"
2) "ooxx"
3) "tom"
4) "xxoo"
5) "tom"
6) "xxoo"
7) "xxoo"
8) "tom"
9) "xxoo"
127.0.0.1:6379> SMEMBERS s1
1) "tom"
2) "xxoo"
3) "jerry"
4) "join"
5) "xoxo"
6) "ooxx"
127.0.0.1:6379> SSCAN s1 0 match x*
1) "0"
2) 1) "xxoo"
2) "xoxo"
127.0.0.1:6379> sadd k1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> SADD k2 3 4 6 7 8
(integer) 5
127.0.0.1:6379> SINTER k1 k2
1) "3"
2) "4"
127.0.0.1:6379> SUNION k1 k2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
127.0.0.1:6379> SUNIONSTORE k3 k1 k2
(integer) 8
127.0.0.1:6379> SMEMBERS k3
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
Redis集合的特征:无序、去重
6.1 抽奖
- 利用SRANDMEMBER来实现抽奖,当每个人只能中一次的话,那么count值要大于0,如果可以重复抽奖的话count值可以小于0
127.0.0.1:6379> SRANDMEMBER s1 3 --> 没有重复数据
1) "join"
2) "xoxo"
3) "ooxx"
127.0.0.1:6379> SRANDMEMBER s1 -9 --> 会有重复数据
1) "ooxx"
2) "ooxx"
3) "tom"
4) "xxoo"
5) "tom"
6) "xxoo"
7) "xxoo"
8) "tom"
9) "xxoo"
6.2 随机事件
- 利用SPOP 随机选中一个人,并且选出后再集合中移除
127.0.0.1:6379> SPOP s1
"xxoo"
127.0.0.1:6379> SMEMBERS s1
1) "jerry"
2) "join"
3) "tom"
4) "xoxo"
5) "ooxx"
127.0.0.1:6379> SPOP s1
"ooxx"
127.0.0.1:6379> SMEMBERS s1
1) "jerry"
2) "join"
3) "tom"
4) "xoxo"
6.3 推荐系统
- 利用SDIFF实现简单差集,实现好用推荐
- 利用SINTER找到 共同好友
127.0.0.1:6379> SADD A B C D
(integer) 3
127.0.0.1:6379> SADD B C E F
(integer) 3
127.0.0.1:6379> SDIFF A B
1) "D"
2) "B"
127.0.0.1:6379> SDIFF B A
1) "E"
2) "F"
127.0.0.1:6379> SINTER A B
1) "C"
七、说说Redis的有序集合数据结构
zset 有序的集合,有序是因为再设置时出了给出元素的名字外,还要对应的分值,然后按照左小右大的顺序,相同分值按字典序
BZPOPMAX key [key ...] timeout
summary: Remove and return the member with the highest score from one or more sorted sets, or block until one is available
since: 5.0.0
BZPOPMIN key [key ...] timeout
summary: Remove and return the member with the lowest score from one or more sorted sets, or block until one is available
since: 5.0.0
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
summary: Add one or more members to a sorted set, or update its score if it already exists
since: 1.2.0
ZCARD key
summary: Get the number of members in a sorted set
since: 1.2.0
ZCOUNT key min max
summary: Count the members in a sorted set with scores within the given values
since: 2.0.0
ZINCRBY key increment member
summary: Increment the score of a member in a sorted set
since: 1.2.0
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Intersect multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0
ZLEXCOUNT key min max
summary: Count the number of members in a sorted set between a given lexicographical range
since: 2.8.9
ZPOPMAX key [count]
summary: Remove and return members with the highest scores in a sorted set
since: 5.0.0
ZPOPMIN key [count]
summary: Remove and return members with the lowest scores in a sorted set
since: 5.0.0
ZRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index
since: 1.2.0
ZRANGEBYLEX key min max [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range
since: 2.8.9
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score
since: 1.0.5
ZRANK key member
summary: Determine the index of a member in a sorted set
since: 2.0.0
ZREM key member [member ...]
summary: Remove one or more members from a sorted set
since: 1.2.0
ZREMRANGEBYLEX key min max
summary: Remove all members in a sorted set between the given lexicographical range
since: 2.8.9
ZREMRANGEBYRANK key start stop
summary: Remove all members in a sorted set within the given indexes
since: 2.0.0
ZREMRANGEBYSCORE key min max
summary: Remove all members in a sorted set within the given scores
since: 1.2.0
ZREVRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index, with scores ordered from high to low
since: 1.2.0
ZREVRANGEBYLEX key max min [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
since: 2.8.9
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score, with scores ordered from high to low
since: 2.2.0
ZREVRANK key member
summary: Determine the index of a member in a sorted set, with scores ordered from high to low
since: 2.0.0
ZSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate sorted sets elements and associated scores
since: 2.8.0
ZSCORE key member
summary: Get the score associated with the given member in a sorted set
since: 1.2.0
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Add multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0
操作演示:
127.0.0.1:6379> ZADD s1 8 apple 2 banana 5 orage
(integer) 3
127.0.0.1:6379> ZRANGE s1 0 -1
1) "banana"
2) "orage"
3) "apple"
127.0.0.1:6379> ZRANGE s1 0 -1 withscores
1) "banana"
2) "2"
3) "orage"
4) "5"
5) "apple"
6) "8"
127.0.0.1:6379> ZINCRBY s1 4 banana
"6"
127.0.0.1:6379> ZRANGE s1 0 -1 withscores
1) "orage"
2) "5"
3) "banana"
4) "6"
5) "apple"
6) "8"
127.0.0.1:6379> ZRANGE s1 0 -1 --> 排名
1) "banana"
2) "orage"
3) "apple"
127.0.0.1:6379> ZSCORE s1 apple
"8"
127.0.0.1:6379> ZRANGE s1 -2 -1
1) "orage"
2) "apple"
127.0.0.1:6379> ZrevRANGE s1 -2 -1
1) "orage"
2) "banana"
127.0.0.1:6379> ZrevRANGE s1 0 1
1) "apple"
2) "orage"
7.1 排行榜
- 比如歌曲通过点击量进行排行,刚出来的都是1,利用ZINCRBY
127.0.0.1:6379> ZADD music 1 dongfengpo 1 xiaochou 1 lige
(integer) 3
127.0.0.1:6379> ZRANGE music 0 -1
1) "dongfengpo"
2) "lige"
3) "xiaochou"
127.0.0.1:6379> ZINCRBY music 1000 dongfengpo
"1001"
127.0.0.1:6379> ZRANGE music 0 -1
1) "lige"
2) "xiaochou"
3) "dongfengpo"
127.0.0.1:6379> ZREVRANGE music 0 -1
1) "dongfengpo" --> 第一名
2) "xiaochou"
3) "lige"
7.2 如何维护有序集合?
redis中维护zset是利用的跳表,Skip List是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间)。基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表(因此得名)。所有操作都以对数随机化的时间进行。Skip List可以很好解决有序链表查找特定值的困难。 例如:
八 持久化
redis为了加快查询速度,数据是放在内存中的,那么如果服务器挂了什么的重启什么的已有的数据怎么办?Redis提供了自己的持久化方案:RDB、AOF
8.1 RDB
8.1.1 方案介绍
Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:
save [seconds] [changes]
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如 save 60 100
--> 会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。可以配置多条save指令,让Redis执行多级的快照保存策略。Redis默认开启RDB快照。也可以通过SAVE或者BGSAVE命令手动触发RDB快照保存。
使用配置需要修改redis的配置文件,重新启动redis服务,每次生成新的dump.rdb都会覆盖掉之前的老的快照
vim redis.conf
save 900 1
save 300 10
save 60 10000
save 5 1
SAVE 和 BGSAVE 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:
· SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
· BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。
8.1.2 方案优点
- 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
- 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。)
- 使用RDB文件进行数据恢复比使用AOF要快很多
8.1.3 方案缺点
- 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
- 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力
8.2 AOF
8.2.1 方案介绍
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:
appendonly yes
AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:
- appendfsync no:不进行fsync,将flush文件的时机交给OS(操作系统)决定,速度最快
- 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,那就只能这样了
- appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
- 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的
- appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
- 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控了
随着AOF不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。
AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:
auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
--> Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite。同时如果增长的大小没有达到64mb,则不会进行rewriten
8.2.2 方案优点
1、 最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
2、 AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复。
3、 AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
8.2.3 方案缺点
1、 AOF文件通常比RDB文件更大
2、 性能消耗比RDB高
3、 数据恢复速度比RDB慢
8.3 合理的持久化策略
- AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响
- AOF + fsync every second是比较好的折中方案,每秒fsync一次
- AOF + fsync never会提供AOF持久化方案下的最优性能使用RDB持久化通常会提供比使用AOF更高的性能
- 但需要注意RDB的策略配置,每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟
- Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝24GB / 4kB * 8 = 48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms
九 哨兵 Sentinel
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
当Server1挂了以后:
哨兵的配置:
vim sentinel.conf
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
#修改bind配置,每台机器修改为自己对应的主机名
bind node01
#配置sentinel服务后台运行
daemonize yes
#修改三台机器监控的主节点,现在主节点是node01服务器
sentinel monitor mymaster node01 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
# 启动哨兵服务
src/redis-sentinel sentinel.conf