不要错过的Redis

不要错过的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()的过程中发现:

  1. 内核一次需要循环遍历1000次,有点忙
  2. 每次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

在这里插入图片描述
在这里插入图片描述[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEu6bqt5-1591286839450)(不要错过的Redis.assets/1591115849384.png)]在这里插入图片描述
可以看出

  1. epoll_create 创建一个文件描述符
  2. epoll_ctl 控制epoll_create创建的文件描述符
  3. 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下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0HxMwA5-1591286839453)(不要错过的Redis.assets/1591195658308.png)]

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
"@"

说那么多,说几个个场景:

  1. 统计任意时间窗口用户的登录情况,如双十一前三天、后三天,一般处理是建立一张表然后记录下用户的登录记录,然后查询。这个方法是可以的但是台麻烦了,表数据会不断变大查询效率也会降低。那么这个时候 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
    
  2. 统计任意时间段内的活跃用户

    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

SAVEBGSAVE 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:

· SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。

· BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。

8.1.2 方案优点
  1. 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
  2. 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。)
  3. 使用RDB文件进行数据恢复比使用AOF要快很多
8.1.3 方案缺点
  1. 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
  2. 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力
8.2 AOF
8.2.1 方案介绍

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:

appendonly yes

AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:

  1. appendfsync no:不进行fsync,将flush文件的时机交给OS(操作系统)决定,速度最快
    • 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,那就只能这样了
  2. appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
    • 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的
  3. 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 合理的持久化策略
  1. AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响
  2. AOF + fsync every second是比较好的折中方案,每秒fsync一次
  3. 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 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值