redis简介
redis是一个开源的,C语言编写的、支持网络交互的,可基于内存也可以持久化的key-value数据库,是当前最为热门的非关系型数据库。其官网为redis.io。
redis的安装
- 下载redis源码并解压缩
- 进入redis源码目录,make编译一下。
- 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的五种数据类型
- String
字符串是redis最基本的数据类型,一个key对应一个value.String类型是二进制安全的。也就是说redis的String可以包含任何数据。比如JPG图片或者序列化的对象。String类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M。
基本操作包括get、set、incr、decr
|
|
考虑到INCR等指令本身就具有原子操作的特性,可以利用redis的INCR,DECR等指令来实现原子计数,用这个特性来实现业务上的统计技术需求。
- Hash
Hash是一个键值对集合,相当于一个key对应一个map,map中还有键值对。使用hash对key进行归类。
基本操作包括hset、hget。
|
|
- List
列表在低层的实现为链表,则其插入和删除的效率非常高。其存在的缺点在于链表中元素的定位比较慢。
其基本操作包括lpush,rpush,lrange等,也就是从左侧插入,右侧插入以及指定一个范围来提取元素。
|
|
list的使用场景包括:
- 利用lists来实现一个消息队列,可以确保消息的顺序执行。
- 利用LRANGE实现分页功能
- Set
集合和Java中的集合定义类似,无序,不重合。可以对集合进行添加,删除,取交集,取并集,取差集等。
|
|
- zset
有序集合,每个元素都关联一个score,据此来进行排序。常用操作有zrange,zadd,zreevrange,zrangebyscore等等。
|
|
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等指令,不应该将从服务器直接暴露到不安全的网络环境中。如果必须如此,需要考虑给其指令进行重命名,避免误操作。
主从同步的配置方法
- 配置的是Slave库。
- 从库配置:slaveof 主库ip 主库port, 每次与master断开之后,都需要进行重新连接,除非你配置进redis.conf中的info replication。
- 修改配置文件的细节操作:
- 拷贝多个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, 使得当前的数据库停止与其他数据库的同步,转成主数据库。
主从复制原理
- Slave启动成功连接到master之后会发送一个sync命令。
- Master接到命令后,调用bgsave指令来创建一个子进程专门进行数据持久化工作,也就是讲主服务器的数据写入RDB文件中。在数据持久化过程中,主服务器将执行的写指令都缓存在内存中。
- 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在事务执行前被改变,则取消事务的执行
|
|
其中QUEUED表示我们在用MULTI组装事务时,每一个命令都会进入到内存队列中缓存起来,如果出现QUEUED则表示我们这个命令成功的插入了缓存队列,将来执行EXEC时,这些被QUEUED的命令都会被组装成一个事务来执行。
事务执行完成之后,如果redis开启了AOF持久化的话,那么一旦事务被成功执行,事务中的命令就会通过write命令一次性写入到磁盘中,如果写入过程中出现故障则会造成部分命令AOF持久化,此时可以使用redis-check-aof工具来修复这个问题,这个问题会将AOF文件中不完整的信息溢出,确保AOF文件完整可用。
事务遇到的错误分为:
调用EXEC之前的错误:导致某个命令无法成功写入缓冲队列,调用redis会拒绝执行这个事务。
- 语法错误
内存不足
12345678127.0.0.1:6379> multiOK127.0.0.1:6379> haha(error) ERR unknown command 'haha'127.0.0.1:6379> pingQUEUED127.0.0.1:6379> exec(error) EXECABORT Transaction discarded because of previous errors.
调用EXEC之后的错误:redis会忽略这些错误,而是继续向下执行事务中的其他命令。也就是某一条命令失败并不会影响其他命令的执行。
1234567891011121314127.0.0.1:6379> multiOK127.0.0.1:6379> set age 23QUEUED127.0.0.1:6379> sadd age 15QUEUED127.0.0.1:6379> set age 29QUEUED127.0.0.1:6379> exec1) OK2) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) OK127.0.0.1:6379> get age"29"WATCH指令可以使用类似于乐观锁的效果也就是CAS,WATCH本身作用是监视某些key是否被改动过,只要还没有真正的触发事务,WATCH都会监视指定的key,一旦发现某个key被修改了,在执行EXEC时就会返回nil,表示事务无法触发。
1234567891011121314127.0.0.1:6379> set age 23OK127.0.0.1:6379> watch ageOK127.0.0.1:6379> set age 24OK127.0.0.1:6379> multiOK127.0.0.1:6379> set age 25QUEUED127.0.0.1:6379> get ageQUEUED127.0.0.1:6379> exec(nil)