Redis-redis基础总结

redis简介

redis是一个开源的,C语言编写的、支持网络交互的,可基于内存也可以持久化的key-value数据库,是当前最为热门的非关系型数据库。其官网为redis.io。

redis的安装

  1. 下载redis源码并解压缩
  2. 进入redis源码目录,make编译一下。
  3. make install PREFIX=/usr/local/redis

安装完毕,其目录如下:

./redis-benchmark //用于进行redis性能测试的工具
./redis-check-dump //用于修复出问题的dump.rdb文件
./redis-cli //redis的客户端
./redis-server //redis的服务端
./redis-check-aof //用于修复出问题的AOF文件
./redis-sentinel //用于集群管理

redis的使用

./redis-server redis.conf 可指定配置文件并启动redis服务,默认端口号为6379。可以通过redis-cli或者可视化的客户端如redis management来远程登录操作服务器。

redis的五种数据类型

  1. String

字符串是redis最基本的数据类型,一个key对应一个value.String类型是二进制安全的。也就是说redis的String可以包含任何数据。比如JPG图片或者序列化的对象。String类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M。

基本操作包括get、set、incr、decr

1
2
3
4
5
6
7
8
127.0.0.1:6379> set mynum "2"
OK
127.0.0.1:6379> get mynum
"2"
127.0.0.1:6379> incr mynum
(integer) 3
127.0.0.1:6379> get mynum
"3"

考虑到INCR等指令本身就具有原子操作的特性,可以利用redis的INCR,DECR等指令来实现原子计数,用这个特性来实现业务上的统计技术需求。

  1. Hash

Hash是一个键值对集合,相当于一个key对应一个map,map中还有键值对。使用hash对key进行归类。

基本操作包括hset、hget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34
OK
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "antirez"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
127.0.0.1:6379> HSET user:001 password 12345
(integer) 0
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "antirez"
3) "password"
4) "12345"
5) "age"
6) "34"
  1. List

列表在低层的实现为链表,则其插入和删除的效率非常高。其存在的缺点在于链表中元素的定位比较慢。

其基本操作包括lpush,rpush,lrange等,也就是从左侧插入,右侧插入以及指定一个范围来提取元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> lpush mylist "1"
(integer) 1
127.0.0.1:6379> rpush mylist "2"
(integer) 2
127.0.0.1:6379> lpush mylist "0"
(integer) 3
127.0.0.1:6379> lrange mylist 0 1
1) "0"
2) "1"
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "1"
3) "2

list的使用场景包括:

  • 利用lists来实现一个消息队列,可以确保消息的顺序执行。
  • 利用LRANGE实现分页功能
  1. Set

集合和Java中的集合定义类似,无序,不重合。可以对集合进行添加,删除,取交集,取并集,取差集等。

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 myset "one"
(integer) 1
127.0.0.1:6379> sadd myset "two"
(integer) 1
127.0.0.1:6379> smembers myset
1) "one"
2) "two"
127.0.0.1:6379> sismember myset "one"
(integer) 1
127.0.0.1:6379> sismember myset "three"
(integer) 0
127.0.0.1:6379> sadd yourset "1"
(integer) 1
127.0.0.1:6379> sadd yourset "2"
(integer) 1
127.0.0.1:6379> smembers yourset
1) "1"
2) "2"
127.0.0.1:6379> sunion myset yourset
1) "1"
2) "one"
3) "2"
4) "two"
  1. zset

有序集合,每个元素都关联一个score,据此来进行排序。常用操作有zrange,zadd,zreevrange,zrangebyscore等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> zadd myzset 1 baidu.com
(integer) 1
127.0.0.1:6379> zadd myzset 3 360.com
(integer) 1
127.0.0.1:6379> zadd myzset 2 google.com
(integer) 1
127.0.0.1:6379> zrange myzset 0 -1 with scores
1) "baidu.com"
2) "1"
3) "google.com"
4) "2"
5) "360.com"
6) "3"
127.0.0.1:6379> zrange myzset 0 -1
1) "baidu.com"
2) "google.com"
3) "360.com"

redis持久化

redis提供了两种持久化方案:RDB和AOF。

RDB-Redis Database

  • rdb是将redis某一个时刻的数据持久化到磁盘中,是一种快照式的持久化方法。redis在持久化过程中,会将数据都写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的dump.rdb。保证快照文件试完整可用的,可以随时进行备份。

  • RDB进行持久化过程时,redis会单独fork一个子进程来进行持久化,而主进程不会进行任何的IO操作,从而保证了redis的高性能。

    • fork的作用是复制一个和当前进程一样的进程。新进程的所有数据数值和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
  • RDB快照的触发方法

    • 配置文件中默认的快照配置,cp后重新使用。
    • 命令save、bgsave,其中save时只保存,其他全部阻塞,bgsave会在后台进行快照操作,快照同时还可以响应客户端的请求,可以通过lastsave命令获取最后一次成功执行快照的时间。
    • 使用flushall命令,不过里面是空的,无意义。
  • RDB的优势和劣势

    • 优势:如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常敏感,nameRDB方式要比AOF方式更加的高效。
    • 劣势:RDB的缺点在于如果对于数据完整性要求比较高,则当redis故障时,会丢失掉最后一次快照后的所有数据。而如果持久化频率太高,会增加服务器的多余压力。同时fork时,内存中的数据被复制了一份,内存膨胀需要考虑。

AOF-Append Only File

  • AOF,只允许对文件追加。AOF采用的持久化方案是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序将指令都执行一遍。AOF的开启可以通过修改redis.conf中的appendonly yes来设置。此时,如果有写操作,都会追加到AOF文件的末尾。默认的AOF持久化策略为每秒种fsync一次,由于向磁盘中写一个指令对系统的压力很小,因此此时redis保持高效运行。而redis重启时根据日志文件顺次执行其指令来完成数据恢复。

  • redis出现故障,丢失的数据也就是这1s没有被同步的数据。同时在追加日志时,恰好遇到磁盘空间满,断电等情况导致日志写入不完整,此时redis提供了redis-check-aof工具来修复日志。

    如果AOF文件出现了被写坏的情况,可以通过以下步骤来修复:

    * 备份被写坏的AOF文件
    * 运行redis-check-aof-fix进行修复
    * 用diff -u来查看两个文件的差异,确认问题点
    * 重启redis,加载修改后的AOF文件
    
  • 重写机制:bgrewriteaof

    • 追加方式会导致AOF文件越来越大,因此redis提供了AOF文件重写机制,也就是当AOF文件的大小超过所设定的阈值时,redis会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。比如,如果我们队一个字符串的值进行了1000次INCR,AOF中应该记录1000条指令,这时候我们完全可以用一个set的值,将其设置为正确的数据。同时AOF重写过程中,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响AOF文件的可用性。

    • 重写原理:

      • 重写时会fork一个新进程来将文件进行重写,先写临时文件然后rename覆盖。重写过程为遍历此时内存中的数据,每条数据有一条对应的set语句。重写AOF文件的操作并不是读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件,类似于快照。
    • 重写触发条件:

      • redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后的大小的一倍且文件大于64M时触发。
  • AOF的优势和劣势

    • AOF的优势:

      • 每秒同步,异步操作,每秒记录,如果1s以内的宕机,有数据丢失
      • 不需要同步
    • AOF的缺陷主要有两个方面:

      • 同样数据规模的情况下,AOF文件要比RDB文件的体积大。
      • AOF方式的恢复速度也要慢于RDB方式。

选择方案

RDB持久化方式能够在指定时间间隔内对你的数据进行快照存储。而AOF持久化方式能记录每次对服务器写的操作,当服务器重启时重新执行这些命令来回复原始数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件不至于过大。

  • 两种方式都开启来提供更加可靠的持久化方案

这种情况下,当redis重启时会优先载入AOF文件来回复原始的数据,因为在通常的情况下AOF文件保存的数据比RDB文件要更加完整。而RDB作为候选方案,对当前内存的数据进行定期的备份,来防范AOF可能存在的BUG。

  • 性能建议

考虑到RDB做数据备份,建议在Slave上持久化RDB文件。

如果这时候Enable AOF,好处是最坏的情况下丢失的数据也不会超过2s,启动脚本简单的load自己的AOF文件就可以看,代价是带来了持续的IO,以及rewrite过程中将产生的新数据写到新文件造成的阻塞难以避免。在磁盘允许的情况下,应该尽量减少AOF重写的频率。

如果不Enable AOF,仅仅依靠主从复制来实现高可靠也是可以的。这样能节省一大笔IO操作,也减少了rewrite时带来的系统资源占用,但是如果master和slave同时坏掉,会丢失十几分钟的数据,启动脚本要比较Master、Slave中的RDB文件,载入比较新的哪一个。

redis的主从复制

redis支持主从同步的,同时支持一主多从以及多级从结构。主从结构的优点在于:

  • 冗余备份
  • 提升读性能。

redis主从结构中,一般推荐关闭主服务器的数据持久化功能,只让从服务器进行数据持久化,这样可以提高主服务器的处理性能,。。

主从架构中,从服务器通常设置为只读模式,这样可以避免从服务器的数据被误修改。但是从服务器仍然可以接受config等指令,不应该将从服务器直接暴露到不安全的网络环境中。如果必须如此,需要考虑给其指令进行重命名,避免误操作。

主从同步的配置方法

  1. 配置的是Slave库。
  2. 从库配置:slaveof 主库ip 主库port, 每次与master断开之后,都需要进行重新连接,除非你配置进redis.conf中的info replication。
  3. 修改配置文件的细节操作:
    • 拷贝多个redis.conf文件
    • 开启daemonize yes
    • pid文件名字
    • 指定端口
    • log文件名称
    • dump.rdb文件名称。

一主二仆的配置

  • 初始化启动一个Master和两个Slave
  • 查看日志包括主机,备份机的日志,以及info replication
  • 上一个Slave可以是下一个Slave的Master,Slave同样可以接受其他Slave的连接和同步请求,那么该Slave作为了链条中的下一个的Master,可以有效减轻master的写压力。
  • 中途变更转向时会清除之前的数据,重新拷贝最新的slaveof ip port
  • slaveof no one, 使得当前的数据库停止与其他数据库的同步,转成主数据库。

主从复制原理

  1. Slave启动成功连接到master之后会发送一个sync命令。
  2. Master接到命令后,调用bgsave指令来创建一个子进程专门进行数据持久化工作,也就是讲主服务器的数据写入RDB文件中。在数据持久化过程中,主服务器将执行的写指令都缓存在内存中。
  3. bgsave指令完成之后,主服务器会将持久化好的RDB文件发送给服务器,从服务器接到此文件后会将其存储到磁盘上,然后再将读取到内存中。这个动作完成之后,主服务器会将这段时间缓存的写指令以redis协议的格式发送给从服务器。也就是全量复制和增量复制结合使用。
  • 全量复制与增量复制
    • 全量复制:slave服务在接收到数据库文件数据之后,将其存盘并加载到内存中。
    • 增量复制:master继续将新的收集到的修改命令依次传给slave,完成同步。
  • 多个从服务器同时发来SYNC指令,主服务器也只会执行一个bgsave,然后把持久化好的RDB文件发送给多个下游节点。redis2.8之前,从服务器和主服务器断开连接之后,都会进行一次主从之间的全量数据同步,2.8之后,redis支持效率更高的增量同步策略。

  • 主服务器会在内存中维护一个缓冲区,缓冲区中存储着将要发给从服务器的内容。从服务器在与主服务器出现网络瞬间断开之后,从服务器会尝试与主服务器连接,一旦连接成功,从服务器就会把希望同步的主服务器ID和希望请求的数据偏移量位置发送给主服务器。主服务器接收到这种同步请求之后,首先会验证主服务器ID是否和自己的ID匹配,其次会检查请求的偏移量是否存在于自己的缓冲区中,如果两者都满足的话,主服务器会向服务器发送增量内容。

redis事务管理

redis事务处理的基础为MULTI、EXEC、DISCARD、WATCH四个指令。

MULTI:用来组装一个事务,标记事务的开始
EXEC:用来执行一个事务
DISCARD:用来取消一个事务
WATCH:用来监视一些可以,一旦这些key在事务执行前被改变,则取消事务的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
redis> MULTI
OK
redis> INCR user_id
QUEUED
redis> INCR user_id
QUEUED
redis> INCR user_id
QUEUED
redis> PING
QUEUED
redis> EXEC
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG
  • 其中QUEUED表示我们在用MULTI组装事务时,每一个命令都会进入到内存队列中缓存起来,如果出现QUEUED则表示我们这个命令成功的插入了缓存队列,将来执行EXEC时,这些被QUEUED的命令都会被组装成一个事务来执行。

  • 事务执行完成之后,如果redis开启了AOF持久化的话,那么一旦事务被成功执行,事务中的命令就会通过write命令一次性写入到磁盘中,如果写入过程中出现故障则会造成部分命令AOF持久化,此时可以使用redis-check-aof工具来修复这个问题,这个问题会将AOF文件中不完整的信息溢出,确保AOF文件完整可用。

  • 事务遇到的错误分为:

    • 调用EXEC之前的错误:导致某个命令无法成功写入缓冲队列,调用redis会拒绝执行这个事务。

      • 语法错误
      • 内存不足

        1
        2
        3
        4
        5
        6
        7
        8
        127.0.0.1:6379> multi
        OK
        127.0.0.1:6379> haha
        (error) ERR unknown command 'haha'
        127.0.0.1:6379> ping
        QUEUED
        127.0.0.1:6379> exec
        (error) EXECABORT Transaction discarded because of previous errors.
    • 调用EXEC之后的错误:redis会忽略这些错误,而是继续向下执行事务中的其他命令。也就是某一条命令失败并不会影响其他命令的执行。

      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 age 23
      QUEUED
      127.0.0.1:6379> sadd age 15
      QUEUED
      127.0.0.1:6379> set age 29
      QUEUED
      127.0.0.1:6379> exec
      1) OK
      2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
      3) OK
      127.0.0.1:6379> get age
      "29"
    • WATCH指令可以使用类似于乐观锁的效果也就是CAS,WATCH本身作用是监视某些key是否被改动过,只要还没有真正的触发事务,WATCH都会监视指定的key,一旦发现某个key被修改了,在执行EXEC时就会返回nil,表示事务无法触发。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    127.0.0.1:6379> set age 23
    OK
    127.0.0.1:6379> watch age
    OK
    127.0.0.1:6379> set age 24
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set age 25
    QUEUED
    127.0.0.1:6379> get age
    QUEUED
    127.0.0.1:6379> exec
    (nil)
Donate comment here