Redis整体
介绍
Redis是一个开源的高性能的key-value存储系统。具有以下特点:
1、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,sorted set,hash等数据结构的存储。
3、Redis支持数据的备份,即master-slave模式的数据备份。
Redis优势:
1、性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
2、丰富的数据类型 – Redis支持二进制案例的 String, List, Hash, Set 及 Sorted Set 数据类型操作。
3、原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
4、丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
数据类型
string、hash、list、set、sorted set
内存管理
首先 Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value,redisObject 最主要的信息如上图所示:type 代表一个 value 对象具体是何种数据类型,encoding 是不同数据类型在 redis 内部的存储方式,比如:type=string 代表 value 存储的是一个普通字符串,那么对应的 encoding 可以是 raw 或者是 int,如果是 int 则代表实际 redis 内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:”123″ “456”这样的字符串。
内存调优
1、首先最重要的一点是不要开启 Redis 的 VM 选项,即虚拟内存功能,这个本来是作为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高,并且我们后续会分析此种持久化策略并不成熟,所以要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled 为 no。
2、其次最好设置下 redis.conf 中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的 Redis 不会因为使用了过多的物理内存而导致 swap,最终严重影响性能甚至崩溃。
3、Redis 为不同数据类型分别提供了一组参数来控制内存使用,我们在前面详细分析过 Redis Hash 是 value 内部为一个 HashMap,如果该 Map 的成员数比较少,则会采用类似一维线性的紧凑格式来存储该 Map,即省去了大量指针的内存开销,这个参数控制对应在 redis.conf 配置文件中下面2项:
hash-max-zipmap-entries 64 hash-max-zipmap-value 512 hash-max-zipmap-entries |
含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的 HashMap。
hash-max-zipmap-value 含义是当 value 这个 Map 内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。
以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap 的优势就是查找和操作的时间复杂度都是 O(1) 的,而放弃 Hash 采用一维存储则是 O(n) 的时间复杂度,如果成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。
内存淘汰策略
Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:
- noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
- allkeys-lru:在主键空间中,优先移除最近未使用的key。
- volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。
- allkeys-random:在主键空间中,随机移除某个key。
- volatile-random:在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除
持久化
redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化。
redis支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是Append-only file(aof)的方式。
snapshotting
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照。也可以命令行的方式让redis进行snapshotting。
快照生成过程大致如下:
- redis调用fork,现在有了子进程和父进程;
- 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照;
- 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。
同时snapshotting也有不足的,因为两次快照操作之间是有时间间隔的,一旦数据库出现问题,那么快照文件中保存的数据并不是全新的,从上次快照文件生成到Redis停机这段时间的数据全部丢掉了。如果业务对数据准确性要求极高的话,就得采用aof持久化机制了。
aof
比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次):
1、appendonly yes //启用aof持久化方式
2、# appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
3、appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
4、# appendfsync no //完全依赖os,性能最好,持久化没保证
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。
bgrewriteaof命令执行过程如下:
- redis调用fork ,现在有父子两个进程;
- 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令;
- 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题;
- 当子进程把快照内容写入以命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件;
- 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
这两种持久化方式有各自的特点,快照相对性能影响不大,但一旦崩溃,数据量丢失较大,而aof数据安全性较高,但性能影响较大,这就得根据业务特点自行选择了。
线程体系
Redis可以说是基于单线程模型的,因为对于客户端的所有读写请求的处理,都由一个主线程串行地处理,因此多个客户端同时对一个键进行写操作不会有并发问题,但是除了客户端读写请求之外还有一些比较耗时的操作,如持久化RDB文件,持久化AOF文件等等,这些操作不能放在主线程里面处理,因此Redis会在适当的时候fork子进程来异步的处理这种任务。除了这些,Redis还有一组异步任务处理线程,用于处理不需要主线程同步处理的工作,总体上Redis的线程体系结构大致如下图:
主从同步
Redis的主从复制功能非常强大,一个master可以拥有多个slave,而一个slave又可以拥有多个slave,如此下去,形成了强大的多级服务器集群架构。下面是关于redis主从复制的一些特点:
1.master可以有多个slave
2.除了多个slave连到相同的master外,slave也可以连接其他slave形成图状结构
3.主从复制不会阻塞master。也就是说当一个或多个slave与master进行初次同步数据时,master可以继续处理client发来的请求。相反slave在初次同步数据时则会阻塞不能处理client的请求。
4.主从复制可以用来提高系统的可伸缩性,我们可以用多个slave 专门用于client的读请求,比如sort操作可以使用slave来处理。也可以用来做简单的数据冗余
5.可以在master禁用数据持久化,只需要注释掉master 配置文件中的所有save配置,然后只在slave上配置数据持久化。
下面介绍下主从复制的过程
当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令。无论是第一次同步建立的连接还是连接断开后的重新连 接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来。后台进程完成写文件 后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上。接着master就会把缓存的命 令转发给slave。而且后续master收到的写命令都会通过开始建立的连接发送给slave。从master到slave的同步数据的命令和从 client发送的命令使用相同的协议格式。当master和slave的连接断开时slave可以自动重新建立连接。如果master同时收到多个 slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave。
redis的主从复制策略是通过其持久化的rdb文件来实现的,其过程是先dump出rdb文件,将rdb文件全量传输给slave,然后再将dump后的操作实时同步到slave中。
- Slave端在配置文件中添加了slaveof指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT;
- Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后阻塞等待master发送回其内存快照文件(最新版的Redis已经不需要让Slave阻塞);
- Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端;
- Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成;
- Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。
从以上的复制过程中可以发现,Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,如果一个master连接多个slave,就会比较影响master性能了。
事务实现原理
Redis事务通常会使用MULTI,EXEC,WATCH等命令来完成,redis实现事务实现的机制与常见的关系型数据库有很大的却别,比如redis的事务不支持回滚,事务执行时会阻塞其它客户端的请求执行。
Redis事务从开始到结束通常会通过三个阶段:1、事务开始,2、命令入队,3、命令执行。
redis > MULTI OK redis > SET "username" "huating" QUEUED redis > SET "password" 161616 QUEUED redis > GET "username" redis > EXEC 1 ) ok 2 ) "huating" 3 ) "huating" |
与事务相关的状态flag
#define REDIS_MULTI ( 1 << 3 ) #define REDIS_DIRTY_EXEC ( 1 << 12 ) #define REDIS_DIRTY_CAS ( 1 << 5 ) |
- 客户端redisClient中有一个名叫flags的成员,标识当前客户端的状态。
- 在声明事务之前,我们可以通过watch命令对一个或多个key进行监视。如果在事务执行之前这些被监视的key被其他命令修改,Redis将redisClient->flags设置为REDIS_DIRTY_CAS标识。
- 使用multi命令可以标识着一个事务的开始,此时redisClient进入事务状态,其flags字段被设置为REDIS_MULTI标识。
- 当客户端进入事务状态后,Redis服务器等待接收一个或多个命令,并把它们放入命令队列中等待执行。如果某条命令在入队过程中发生错误,Redis会将redisClient的flags字段置为REDIS_DIRTY_EXEC标识。
- 最后我们通过exec命令执行事务,该命令将会检查redisClient的flags标识,如果该标识为REDIS_DIRTY_CAS或REDIS_DIRTY_EXEC,则事务执行失败,否则Redis一次性执行事务中的多个命令,并将所有命令的结果集合到回复队列,再作为 exec 命令的结果返回给客户端。
缓存失效机制
1、延迟失效机制:也叫消极失效机制,延迟失效机制即当客户端请求操作某个key的时候,Redis会对客户端请求操作的key进行有效期检查,如果key过期才进行相应的处理
2、主动失效机制:也叫积极失效机制,即服务端定时的去检查失效的缓存,如果失效则进行相应的操作。
与memcached的比较
|
redis
|
memcached
|
---|---|---|
|
redis
|
memcached
|
数据类型 | 支持多种数据类型 | 只支持简单的k-v |
数据持久化支持 | 支持数据持久化 | 全内存模式 |
数据一致性 | 提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断 | 提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题 |
集群 | 服务端分布式 | 客户端分布式 |
内存管理 | 实时分配内存 | 预分配内存 |