首页 > 代码库 > [redis]redis概述

[redis]redis概述

Redis是一个开源支持网络基于内存、可持久化日志型、key-value键值对数据库,使用ANSI C编写。并提供多种语言的API

特性

速度快

Redis使用标准C编写实现,而且将所有数据加载到内存中,所以速度非常快。官方提供的数据表明,在一个普通的Linux机器上,Redis读写速度分别达到81000/s110000/s

持久化

由于所有数据保持在内存中,所以对数据的更新将异步地保存到磁盘上,Redis提供了一些策略来保存数据,比如根据时间或更新次数。

数据结构

可以将Redis看做“数据结构服务器”。目前,Redis支持5种数据结构。

自动操作

Redis对不同数据类型的操作是自动的,因此设置或增加key值,从一个集合中增加或删除一个元素都能安全的操作。

支持多种语言

Redis支持多种语言,诸如Ruby, Python, Twisted Python, PHP, Erlang, Tcl, Perl, Lua, Java, Scala, Clojure等。

主-从复制

Redis支持简单而快速的主-从复制。官方提供了一个数据,Slave21秒即完成了对Amazon网站10G key set的复制。

Sharding

很容易将数据分布到多个Redis实例中,但这主要看该语言是否支持。目前支持Sharding功能的语言只有PHPRubyScala

性能

当数据依赖不再需要,Redis这种基于内存的性质,与在执行一个事务时将每个变化都写入硬盘的数据库系统相比就显得执行效率非常高。写与读操作速度没有明显差别。

数据类型

Redis的外围由一个键、值映射的字典构成。与其他非关系型数据库主要不同在于:Redis值的类型不仅限于字符串,还支持这些抽象数据类型:string,list,set,zset,hash.值的类型决定了值本身支持的操作。Redis支持不同无序、有序的列表,无序、有序的集合间的交集、并集等高级服务器端原子操作。

string字符串

stringredis最基本的类型,而且string类型是二进制安全的。意思是redisstring可以包含任何数据。比如jpg图片或者序列化的对象。

从内部实现来看其实string可以看作byte数组,最大上限是1G字节。string类型的值也可视为integer,从而可以让“incr”命令族操作,这种情况下,该integer的值限制在64位有符号数

listsetzset中包含的独立的元素类型都是string类型

list双向链表

redislist类型其实就是一个每个子元素都是string类型的双向链表所以[lr]push[lr]pop命令的算法时间复杂度都是O(1),另外list会记录链表的长度,所以llen操作也是O(1).可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list可以用作栈,也可以用作队列。

list的最大长度是2^32-1个元素,约等于4亿个。

set无序不重复集合

set就是redis string的无序集合,不允许有重复元素set的操作有交集、并集、差集等

set的最大元素数是2^32-1,约等于4亿。

zset有序不重复集合

zsetset的一个升级版本,set的基础上增加了一个顺序属性,这一属性在添加修改元素时可以指定,每次指定后zset会自动安照指定值重新调整顺序。可以理解为一张表,一列存value,一列存顺序。操作中的key理解为zset的名字。

zset的最大元素数是2^32-1,约等于4亿。

对于已经有序的zset,仍然可以使用sort命令,通过指定asc|desc参数对其进行排序。

hast表

键、值都为字符串的哈希表(hash)。redis Hash类型对数据域和值提供了映射,这一结构很方便表示对象。

Hash中可以只保存有限的几个“域”,而不是将所有的“域”作为key,这可以节省内存

同步

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。从盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助 

主从复制

Redis的复制功能是完全建立在基于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你的系统内存容量规划——物理内存使用量不要超过3/5

配置slave服务器很简单,只需要在配置文件中加入如下配置

slaveof 192.168.1.1 6379  #指定masterip和端口

 

下面是关于redis主从复制的一些特点

  • lmaster可以有多个slave
  • 除了多个slave连到相同的master外,slave也可以连接其他slave形成图状结构
  • 主从复制不会阻塞master。也就是说当一个或多个slavemaster进行初次同步数据时,master可以继续处理client发来的请求。相反slave在初次同步数据时则会阻塞不能处理client的请求。
  • 主从复制可以用来提高系统的可伸缩性,我们可以用多个slave 专门用于client的读请求,比如sort操作可以使用slave来处理。也可以用来做简单的数据冗余
  • 可以在master禁用数据持久化,只需要注释掉master 配置文件中的所有save配置,然后只在slave上配置数据持久化。

 

主从复制的过程

  1. 当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令。
  2. 无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来。
  3. 后台进程完成写文件后,master就发送文件给slaveslave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上。
  4. 接着master就会把缓存的命令转发给slave。而且后续master收到的写命令都会通过开始建立的连接发送给slave
  5. masterslave的同步数据的命令和从client发送的命令使用相同的协议格式。当masterslave的连接断开时slave可以自动重新建立连接。如果master同时收到多个slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave 

Redis复制流程在SlaveMaster端各自是一套状态机流转,涉及的状态信息是:

Slave 端:

  • REDIS_REPL_NONE
  • REDIS_REPL_CONNECT
  • REDIS_REPL_CONNECTED 

Master端:

  • REDIS_REPL_WAIT_BGSAVE_START
  • REDIS_REPL_WAIT_BGSAVE_END
  • REDIS_REPL_SEND_BULK
  • REDIS_REPL_ONLINE

整个状态机流程过程如下:

 

  1. Slave端在配置文件中添加了slave of指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT。 
  2. Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后等待master发送回其内存快照文件。 
  3. Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端。 
  4. Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成。 
  5. Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。 

 

Redis复制机制的缺陷:Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题

持久化

定时快照方式(snapshot)

快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。可以配置redisn秒内如果超过mkey被修改就自动做快照,下面是默认的快照保存配置

  • save 900 1  #900秒内如果超过1key被修改,则发起快照保存
  • save 300 10 #300秒内容如超过10key被修改,则发起快照保存
  • save 60 10000

client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有 client的请求,这种方式会阻塞所有client请求,所以不推荐使用;每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式

保存过程如下

  1. redis调用fork,现在有子进程和父进程。
  2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数 据是fork时刻整个数据库的一个快照。
  3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

缺点:是定时快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。 

基于语句追加方式(aof)

aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要 通过fsync函数强制os写入到磁盘的时机。

有三种方式如下(默认是:每秒fsync一次)

  • appendonly yes              //启用aof持久化方式
  • # appendfsync always    //每次收到写命令就立即写盘,最慢,但保证完全的持久化,不推荐使用
  • appendfsync everysec    //每秒钟强制写入磁盘一次,推荐
  • # appendfsync no    //完全依赖os,性能最好,持久化没保证

持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。 

保存过程如下

  1. redis调用fork ,现在有父子两个进程
  2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
  3. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
  4. 当子进程把快照内容以命令方式写入临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
  5. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

缺点:log文件体积过大时系统重启恢复数据非常慢,几十G的数据可能要几小时才能加载完;每条命令都要写log,读写性能会有所下降

[redis]redis概述