微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
使用redis的五个注意事项
http://blog.nosqlfan.com/html/3705.html下面内容来源于Quora上的一个提问,问题是使用Redis需要避免的五个问题。而回答中超出了五个问题的范畴,描述了五个使用Redis的注意事项。如果你在使用或者考虑使用Redis,可能你可以学习一下下面的一些建议,避免一下提到的问题。1.使用key值前缀来作命名空间虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。所以用前缀作为命名空间可能会更明智一点。另外,在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现,直接在代码里写前缀的做法要严格避免,这样可维护性实在太差了。2.创建一个类似 ”registry” 的key用于标记key使用情况为了更好的管理你的key值的使用,比如哪一类key值是属于哪个业务的,你通常会在内部wiki或者什么地方创建一个文档,通过查询这个文档,我们能够知道Redis中的key都是什么作用。与之结合,一个推荐的做法是,在Redis里面保存一个registry值,这个值的名字可以类似于 __key_registry__ 这样的,这个key对应的value就是你文档的位置,这样我们在使用Redis的时候,就能通过直接查询这个值获取到当前Redis的使用情况了。3.注意垃圾回收Redis是一个提供持久化功能的内存数据库,如果你不指定上面值的过期时间,并且也不进行定期的清理工作,那么你的Redis内存占用会越来越大,当有一天它超过了系统可用内存,那么swap上场,离性能陡降的时间就不远了。所以在Redis中保存数据时,一定要预先考虑好数据的生命周期,这有很多方法可以实现。比如你可以采用Redis自带的过期时间为你的数据设定过期时间。但是自动过期有一个问题,很有可能导致你还有大量内存可用时,就让key过期去释放内存,或者是内存已经不足了key还没有过期。如果你想更精准的控制你的数据过期,你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,你可以轻松地找到最老的数据,并且从最老的数据开始进行删除,一直删除到你的空间足够为止。4.设计好你的Sharding机制Redis目前并不支持Sharding,但是当你的数据量超过单机内存时,你不得不考虑Sharding的事(注意:Slave不是用来做Sharding操作的,只是数据的一个备份和读写分离而已)。所以你可能需要考虑好数据量大了后的分片问题,比如你可以在只有一台机器的时候就在程序上设定一致性hash机制,虽然刚开始所有数据都hash到一台机器,但是当你机器越加越多的时候,你就只需要迁移少量的数据就能完成了。5.不要有个锤子看哪都是钉子当你使用Redis构建你的服务的时候,一定要记住,你只是找了一个合适的工具来实现你需要的功能。而不是说你在用Redis构建一个服务,这是很不同的,你把Redis当作你很多工具中的一个,只在合适使用的时候再使用它,在不合适的时候选择其它的方法。
redis集群搭建
1.  Redis Cluster的架构图。                   (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.         (2)节点的fail是通过集群中超过半数的节点检测失效时才生效.         (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。         (4)redis-cluster把所有的物理节点映射到[0-16383]slot上(哈希槽),cluster 负责维护Redis 集群中内置了 16384 个哈希槽,当需要在Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。2.  redis-cluster投票:容错                   (1)领着投票过程是集群中所有master参与,如果半数以上master节点与其中一个master节点通信超时(cluster-node-timeout),认为当前master节点挂掉.         (2):什么时候整个集群不可用(cluster_state:fail)?          a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态. ps : redis-3.0.0.rc1加入cluster-require-full-coverage参数,默认关闭,打开集群兼容部分失败.        b:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态.        ps:当集群不可用时,所有对集群的操作做都不可用,收到((error)CLUSTERDOWN The cluster is down)错误。 3.  安装环境直接在虚拟机模拟就行4.  集群搭建        本次为实验教程,所以在一台虚拟机中进行搭建,跟在多台真机上搭建其实没有什么区别,只要保证网络通信ok就可以了!        我们在几台机器上通过端口号的不同,搭建一个伪集群。在一个服务器上创建多个redis实例。端口号如下所示主节点:127.0.0.1:7001 127.0.0.1:7002127.0.0.1:7003从节点:127.0.0.1:7004127.0.0.1:7005127.0.0.1:7006在/usr/local下创建redis-cluster目录,其下创建redis01、redis02。。redis06目录,如下:接下来我们将redis安装到   redis01中,然后make编译一下,会发现在src文件中出现:安装完以后我们在将Redis编译目录中的redis.Conf文件复制到redis01目录下,将src中的绿色文件复制到redis01目录下。如下:然后我们将redis01文件夹的文件分别复制到redis02……redis06文件夹中。同时将redis源码目录src下的redis-trib.rb拷贝到redis-cluster目录下。接着修改每个文件夹下的配置文件, 修改每个文件夹下的配置文件,有三点需要修改,每个配置文件都要配置自己的端口号,不能重复。          准备好以上工作以后,我们分别启动每个redis进行的实例。启动完毕以后我们输入命令ps ax|grep redis ,查看实例是否启动,出现如下所以图片,表示所有的实例都启动了。实例启动完以后,我们要开始创建集群,在redis-cluter文件夹下执行如下命令。 <span "font-size:18px;"> ./redis-trib.rbcreate --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004127.0.0.1:7005 127.0.0.1:7006。lt;/span><p><span "font-size:18px;">>>> Creating cluster</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7001: OK</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7002: OK</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7003: OK</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7004: OK</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7005: OK</span></p><p><span "font-size:18px;">Connecting to node 127.0.0.1:7006: OK</span></p><p><span "font-size:18px;">>>> Performing hash slotsallocation on 6 nodes...</span></p><p><span "font-size:18px;">Using 3 masters:</span></p><p><span "font-size:18px;">127.0.0.1:7001</span></p><p><span "font-size:18px;">127.0.0.1:7002</span></p><p><span "font-size:18px;">127.0.0.1:7003</span></p><p><span "font-size:18px;">Adding replica 127.0.0.1:7004 to 127.0.0.1:7001</span></p><p><span "font-size:18px;">Adding replica 127.0.0.1:7005 to 127.0.0.1:7002</span></p><p><span "font-size:18px;">Adding replica 127.0.0.1:7006 to 127.0.0.1:7003</span></p><p><span "font-size:18px;">M: 5a8523db7e12ca600dc82901ced06741b3010076127.0.0.1:7001</span></p><p><span "font-size:18px;"> slots:0-5460 (5461 slots) master</span></p><p><span "font-size:18px;">M: bf6f0929044db485dea9b565bb51e0c917d20a53127.0.0.1:7002</span></p><p><span "font-size:18px;"> slots:5461-10922 (5462 slots) master</span></p><p><span "font-size:18px;">M: c5e334dc4a53f655cb98fa3c3bdef8a808a693ca127.0.0.1:7003</span></p><p><span "font-size:18px;"> slots:10923-16383 (5461 slots) master</span></p><p><span "font-size:18px;">S: 2a61b87b49e5b1c84092918fa2467dd70fec115f127.0.0.1:7004</span></p><p><span "font-size:18px;"> replicates 5a8523db7e12ca600dc82901ced06741b3010076</span></p><p><span "font-size:18px;">S: 14848b8c813766387cfd77229bd2d1ffd6ac8d65127.0.0.1:7005</span></p><p><span "font-size:18px;"> replicates bf6f0929044db485dea9b565bb51e0c917d20a53</span></p><p><span "font-size:18px;">S: 3192cbe437fe67bbde9062f59d5a77dabcd0d632127.0.0.1:7006</span></p><p><span "font-size:18px;"> replicates c5e334dc4a53f655cb98fa3c3bdef8a808a693ca</span></p><p><span "font-size:18px;">Can I set the above configuration? (type'yes' to accept): <strong><span "color:lime;">yes</span></strong></span></p><p><span "font-size:18px;">>>> Nodes configuration update
redis系列--你真的入门了吗?redis4.0入门~
前言redis作为nosql家族中非常热门的一员,也是被大型互联网公司所青睐,无论你是开发、测试或者运维,学习掌握它总会为你的职业生涯增色添彩。当然,你或多或少已经了解redis,但是你是否了解其中的某些细节,本片文章将详细介绍redis基础,后续也会介绍其高级部分如、持久化、复制、集群等内容,希望对你有所帮助。自redis3.0发布已经3年了,redis目前官方提供的redis稳定版本是4.0,以下示例均在4.0版本上进行。一、redis简介概述redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value类型的数据库,并提供多种语言的API。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步,redis在3.0版本推出集群模式。特点、优势k、v键值存储以及数据结构存储(如列表、字典)所有数据(包括数据的存储)操作均在内存中完成单线程服务(这意味着会有较多的阻塞情况),采用epoll模型进行请求响应,对比nginx支持主从复制模式,更提供高可用主从复制模式(哨兵)去中心化分布式集群丰富的编程接口支持,如Python、Golang、Java、php、Ruby、Lua、Node.js 功能丰富,除了支持多种数据结构之外,还支持事务、发布/订阅、消息队列等功能支持数据持久化(AOF、RDB)对比memcachememcache是一个分布式的内存对象缓存系统,并不提供持久存储功能,而redis拥有持久化功能memcache数据存储基于LRU(简单说:最近、最少使用key会被剔除),而redis则可以永久保存(服务一直运行情况下)memcache是多线程的(这是memcache优势之一),也就意味着阻塞情况少,而redis是单线程的,阻塞情况相对较多两者性能上相差不大memcache只支持简单的k、v数据存储,而redis支持多种数据格式存储。memcache是多线程、非阻塞IO复用网络模型,而redis是单线程IO复用模型二、开始源码部署yum install gcc -y #安装C依赖wget http://download.redis.io/redis-stable.tar.gz #下载稳定版本tar zxvf redis-stable.tar.gz #解压cd redis-stablemake PREFIX=/opt/app/redis install #指定目录编译,也可以不用指定make installmkdir /etc/redis #建立配置目录cp redis.conf /etc/redis/6379.conf # 拷贝配置文件cp utils/redis_init_script /etc/init.d/redis #拷贝init启动脚本针对6.X系统chmod a+x /etc/init.d/redis #添加执行权限vi /etc/redis/6379.conf #修改配置文件:bind 0.0.0.0 #监听地址maxmemory 4294967296 #限制最大内存(4G):daemonize yes #后台运行####启动与停止/etc/init.d/redis start/etc/init.d/redis stop查看是否成功安装#执行客户端工具redis-cli#输入命令info127.0.0.1:6379> info# Serverredis_version:4.0.10redis_git_sha1:00000000redis_git_dirty:0redis_build_id:cf83e9c690dbed33redis_mode:standaloneos:Linux 2.6.32-642.el6.x86_64 x86_64arch_bits:64multiplexing_api:epoll二进制文件说明redis安装完成后会有以下可执行文件(window下是exe文件)生成,下面是各个文件的作用。redis-server #Redis服务器和Sentinel服务器,启动时候可使用--sentinel指定为哨兵redis-cli #Redis命令行客户端redis-benchmark #Redis性能测试工具redis-check-aof #AOF文件修复工具redis-check-dump #RDB文件检测工具redis-sentinel #Sentinel服务器,4.0版本已经做了软链接到redis-server三、配置详解redis所有的配置参数都可以通过客户端通过“CONFIG GET 参数名” 获取,参数名支持通配符,如*代表所有。所得结果并按照顺序分组,第一个返回结果是参数名,第二个结果是参数对应的值。除了查看配置还可以使用CONFIG SET修改配置,写入配置文件使用CONFIG REWRITE,使用时是需要注意某些关于服务配置参数慎重修改,如bind。配置参数以及解释,需要注意的是,有些配置是4.0.10新增的,有些配置已经废除,如vm相关配置,和集群相关配置在集群篇章在进行补充。logfile#日志文件位置及文件名称bind 0.0.0.0#监听地址,可以有多个 如bind 0.0.0.0 127.0.0.1daemonize yes#yes启动守护进程运行,即后台运行,no表示不启用pidfile /var/run/redis.pid# 当redis在后台运行的时候,Redis默认会把pid文件在在/var/run/redis.pid,也可以配置到其他地方。# 当运行多个redis服务时,需要指定不同的pid文件和端口port 6379# 指定redis运行的端口,默认是6379unixsocket#sock文件位置unixsocketperm#sock文件权限timeout 0# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接, 0是关闭此设置loglevel debug# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verboselogfile ""# 日志文件配置,默认值为stdout,标准输出,若后台模式会输出到/dev/nullsyslog-enabled# 是否以syslog方式记录日志,yes开启no禁用,与该配置相关配置syslog-ident 和syslog-facility local0 分别是指明syslog的ident和facilitydatabases 16#配置可用的数据库个数,默认值为16,默认数据库为0,数据库范围在0-(database-1)之间always-show-logo yes #4.0以后新增配置#是否配置日志显示redis徽标,yes显示no不显示################################ 快照相关配置 #################################save 900 1save 300 10save 60 10000#配置快照(rdb)促发规则,格式:save <seconds> <changes>#save 900 1 900秒内至少有1个key被改变则做一次快照#save 300 10 300秒内至少有300个key被改变则做一次快照#save 60 10000 60秒内至少有10000个key被改变则做一次快照dbfilename dump.rdb#rdb持久化存储数据库文件名,默认为dump.rdbstop-write-on-bgsave-error yes#yes代表当使用bgsave命令持久化出错时候停止写RDB快照文件,no则代表继续写rdbchecksum yes#开启rdb文件校验dir "/etc"#数据文件存放目录,rdb快照文件和aof文件都会存放至该目录################################# 复制相关配置参数 #################################slaveof <masterip> <masterport>#设置该数据库为其他数据库的从数据库,设置当本机为slave服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步masterauth <master-password>#主从复制中,设置连接master服务器的密码(前提master启用了认证)slave-serve-stale-data yes# 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求# 2) 如果slave-serve-stale-data是指为no,除了INFO和SLAVOF命令之外的任何请求都会返回一个错误"SYNC with master in progress"repl-ping-slave-period 10#从库会按照一个时间间隔向主库发送PING命令来判断主服务器是否在线,默认是10秒repl-timeout 60#设置主库批量数据传输时间或者ping回复时间间隔超时时间,默认值是60秒# 一定要确保repl-timeout大于repl-ping-slave-periodrepl-backlog-size 1mb#设置复制积压大小,只有当至少有一个从库连入才会释放。slave-priority 100#当主库发生宕机时候,哨兵会选择优先级最高的一个称为主库,从库优先级配置默认100,数值越小优先级越高min-slaves-to-write 3min-slaves-max-lag 10#设置某个时间断内,如果从库数量小于该某个值则不允许主机进行写操作,以上参数表示10秒内如果主库的从节点小于3个,则主库不接受写请求,min-slaves-to-write 0代表关闭此功能。################################## 安全相关配置 ###################################requirepass#客户端连接认证的密码,默认为空,即不需要密码,若配置则命令行使用AUTH进行认证maxclients 10000# 设置同一时间最大客户端连接数,4.0默认10000,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,# 如果设置 maxclients 0,表示不作限制。# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息maxmemory 4gb#设置最大使用的内存大小maxmemory-policy noeviction#设置达到最大内存采取的策略:# volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )# allkeys-lru -> 利用LRU算法移除任何key# volatile-random -> 移除设置过过期时间的随机key# allkeys-&
redis系列--redis4.0深入持久化
前言在之前的博文中已经详细的介绍了redis4.0基础部分,并且在memcache和redis对比中提及redis提供可靠的数据持久化方案,而memcache没有数据持久化方案,本篇博文将详细介绍redis4.0所提供的持久化方案:RDB持久化和AOF持久化以及redis4.0新特性混合持久化。这里将从原理到配置以及相关实践进行说明,希望能对你有所帮助。一、RDB持久化简介RDB持久化方式是通过快照(snapshotting)完成的,当符合一定条件时,redis会自动将内存中所有数据以二进制方式生成一份副本并存储在硬盘上。当redis重启时,并且AOF持久化未开启时,redis会读取RDB持久化生成的二进制文件(默认名称dump.rdb,可通过设置dbfilename修改)进行数据恢复,对于持久化信息可以用过命令“info Persistence”查看。快照文件位置RDB快照文件存储文件位置由dir配置参数指明,文件名由dbfilename指定,如下: 快照触发条件RDB生成快照可自动促发,也可以使用命令手动触发,以下是redis触发执行快照条件,后续会对每个条件详细说明:客户端执行命令save和bgsave会生成快照;根据配置文件save m n规则进行自动快照;主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照;客户端执行数据库清空命令FLUSHALL时候,触发快照;客户端执行shutdown关闭redis时,触发快照; save命令触发客户端执行save命令,该命令强制redis执行快照,这时候redis处于阻塞状态,不会响应任何其他客户端发来的请求,直到RDB快照文件执行完毕,所以请慎用。实践操作:首先使用info Persistence查看最近一次持久化时间:此时我们执行save命令,并再次查看最新快照保存时间已经是最新一次时间:当然你也可以直接查看RDB数据文件目录下的RDB文件最新时间: bgsave命令触发bgsave命令可以理解为background save即:“后台保存”。当执行bgsave命令时,redis会fork出一个子进程来执行快照生成操作,需要注意的redis是在fork子进程这个简短的时间redis是阻塞的(此段时间不会响应客户端请求,),当子进程创建完成以后redis响应客户端请求。其实redis自动快照也是使用bgsave来完成的。为了能清楚了解bgsave工作过程,以下将图文详细描述其工作过程:对上述过程描述:客户端执行bgsave命令,redis主进程收到指令并判断此时是否在执行bgrewriteaof(AOF文件重新过程,后续会讲解),如果此时正好在执行则bgsave直接返回,不fork子进程,如果没有执行bgrewriteaof重写AOF文件,则进入下一个阶段;主进程调用fork方法创建子进程,在创建过程中redis主进程阻塞,所以不能响应客户端请求;子进程创建完成以后,bgsave命令返回“Background saving started”,此时标志着redis可以响应客户端请求了;子经常根据主进程的内存副本创建临时快照文件,当快照文件完成以后对原快照文件进行替换;子进程发送信号给redis主进程完成快照操作,主进程更新统计信息(info Persistence可查看),子进程退出; 实践操作:执行bgsave查看日志,能看到6MB文件内存副本写到了磁盘上,同时打印“Background saving terminated with success”代表文件bgsave操作完成。此时我们查看统计信息最后一次RDB保存时间已经更新:  save m n规则触发 save m n规则说明:在指定的m秒内,redis中有n个键发生改变,则自动触发bgsave。该规则默认也在redis.conf中进行了配置,并且可组合使用,满足其中一个规则,则触发bgsave,在上篇博文也进行了解释,如下:以save 900 1为例,表明当900秒内至少有一个键发生改变时候,redis触发bgsave操作。 实践操作:我们改变一个键,满足save 900 1 :此时查看redis日志,会发现redis立即响应开始bgsave操作: FLUSHALL触发flushall命令用于清空数据库,请慎用,当我们使用了则表明我们需要对数据进行清空,那redis当然需要对快照文件也进行清空,所以会触发bgsave。实践操作:日志: shutdown触发shutdown命令触发就不用说了,redis在关闭前处于安全角度将所有数据全部保存下来,以便下次启动会恢复。实践操作:可以使用客户端连入执行shutdown命令,也可以直接使用脚本关闭redis,这里我使用init脚本(系统centos6.X)。查看日志: 主从触发在redis主从复制中,从节点执行全量复制操作,主节点会执行bgsave命令,并将rdb文件发送给从节点,该过程会在复制篇中进行阐述。 故障恢复上面提及到过,当redis意外崩溃或者关闭再次启动时,此时AOF持久化未开启时(默认未开启),将使用RDB快照文件恢复数据。下面我们停用redis服务来模拟故障情况,让再启动redis服务:观察日志会发现,启动时候load RDB文件。 RDB持久化配置save m n#配置快照(rdb)促发规则,格式:save <seconds> <changes>#save 900 1 900秒内至少有1个key被改变则做一次快照#save 300 10 300秒内至少有300个key被改变则做一次快照#save 60 10000 60秒内至少有10000个key被改变则做一次快照#关闭该规则使用svae “”dbfilename dump.rdb#rdb持久化存储数据库文件名,默认为dump.rdbstop-write-on-bgsave-error yes#yes代表当使用bgsave命令持久化出错时候停止写RDB快照文件,no表明忽略错误继续写文件。rdbchecksum yes#在写入文件和读取文件时是否开启rdb文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。dir "/etc/redis"#数据文件存放目录,rdb快照文件和aof文件都会存放至该目录,请确保有写权限rdbcompression yes#是否开启RDB文件压缩,该功能可以节约磁盘空间  二、AOF持久化简介当redis存储非临时数据时,为了降低redis故障而引起的数据丢失,redis提供了AOF(Append Only File)持久化,从单词意思讲,将命令追加到文件。AOF可以将Redis执行的每一条写命令追加到磁盘文件(appendonly.aof)中,在redis启动时候优先选择从AOF文件恢复数据。由于每一次的写操作,redis都会记录到文件中,所以开启AOF持久化会对性能有一定的影响,但是大部分情况下这个影响是可以接受的,我们可以使用读写速率高的硬盘提高AOF性能。与RDB持久化相比,AOF持久化数据丢失更少,其消耗内存更少(RDB方式执行bgsve会有内存拷贝)。 开启AOF默认情况下,redis是关闭了AOF持久化,开启AOF通过配置appendonly为yes开启,我们修改配置文件或者在命令行直接使用config set修改,在用config rewrite同步到配置文件。通过客户端修改好处是不用重启redis,AOF持久化直接生效。 AOF持久化过程 redisAOF持久化过程可分为以下阶段:1.追加写入redis将每一条写命令以redis通讯协议添加至缓冲区aof_buf,这样的好处在于在大量写请求情况下,采用缓冲区暂存一部分命令随后根据策略一次性写入磁盘,这样可以减少磁盘的I/O次数,提高性能。2.同步命令到硬盘当写命令写入aof_buf缓冲区后,redis会将缓冲区的命令写入到文件,redis提供了三种同步策略,由配置参数appendfsync决定,下面是每个策略所对应的含义:no:不使用fsync方法同步,而是交给操作系统write函数去执行同步操作,在linux操作系统中大约每30秒刷一次缓冲。这种情况下,缓冲区数据同步不可控,并且在大量的写操作下,aof_buf缓冲区会堆积会越来越严重,一旦redis出现故障,数据丢失严重。always:表示每次有写操作都调用fsync方法强制内核将数据写入到aof文件。这种情况下由于每次写命令都写到了文件中, 虽然数据比较安全,但是因为每次写操作都会同步到AOF文件中,所以在性能上会有影响,同时由于频繁的IO操作,硬盘的使用寿命会降低。everysec:数据将使用调用操作系统write写入文件,并使用fsync每秒一次从内核刷新到磁盘。 这是折中的方案,兼顾性能和数据安全,所以redis默认推荐使用该配置。3.文件重写(bgrewriteaof)当开启的AOF时,随着时间推移,AOF文件会越来越大,当然redis也对AOF文件进行了优化,即触发AOF文件重写条件(后续会说明)时候,redis将使用bgrewriteaof对AOF文件进行重写。这样的好处在于减少AOF文件大小,同时有利于数据的恢复。为什么重写?比如先后执行了“set foo bar1 set foo bar2 set foo bar3” 此时AOF文件会记录三条命令,这显然不合理,因为文件中应只保留“set foo bar3”这个最后设置的值,前面的set命令都是多余的,下面是一些重写时候策略:重复或无效的命令不写入文件过期的数据不再写入文件多条命令合并写入(当多个命令能合并一条命令时候会对其优化合并作为一个命令写入,例如“RPUSH list1 a RPUSH list1 b" 合并为“RPUSH list1 a b” )重写触发条件 AOF文件触发条件可分为手动触发和自动触发:手动触发:客户端执行bgrewriteaof命令。自动触发:自动触发通过以下两个配置协作生效:auto-aof-rewrite-min-size: AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。auto-aof-rewrite-percentage:当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。redis开启在AOF功能开启的情况下,会维持以下三个变量记录当前AOF文件大小的变量aof_current_size。记录最后一次AOF重写之后,AOF文件大小的变量aof_rewrite_base_size。增长百分比变量aof_rewrite_perc。每次当serverCron(服务器周期性操作函数)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的AOF重写操作:没有BGSAVE命令(RDB持久化)/AOF持久化在执行;没有BGREWRITEAOF在进行;当前AOF文件大小要大于server.aof_rewrite_min_size的值;当前AOF文件大小和最后一次重写后的大小之间的比率等于或者大于指定的增长百分比(auto-aof-rewrite-percentage参数)重写过程AOF文件重写过程与RDB快照bgsave工作过程有点相似,都是通过fork子进程,由子进程完成相应的操作,同样的在fork子进程简短的时间内,redis是阻塞的,以下图文说明其重写过程: 过程说明:aof_rewrite_buf 代表重写缓冲区      aof_buf代表写写命令存放的缓冲区1.开始bgrewriteaof,判断当前有没有bgsave命令(RDB持久化)/bgrewriteaof在执行,倘若有,则这些命令执行完成以后在执行。2.主进程fork出子进程,在这一个短暂的时间内,redis是阻塞的。3.主进程fork完子进程继续接受客户端请求,所有写命令依然写入AOF文件缓冲区并根据appendfsync策略同步到磁盘,保证原有AOF文件完整和正确。由于fork的子进程仅仅只共享主进程fork时的内存,因此Redis使用采用重写缓冲区(aof_rewrite_buf
redis系列--主从复制以及redis复制演进
 一、前言在之前的文章已经详细介绍了redis入门基础已经持久化相关内容包括redis4.0所提供的混合持久化。通过持久化功能,Redis保证了即使在服务器宕机情况下数据的丢失非常少。但是如果这台服务器出现了硬盘故障、系统崩溃等等,不仅仅是数据丢失,很可能对业务造成灾难性打击。为了避免单点故障通常的做法是将数据复制多个副本保存在不同的服务器上,这样即使有其中一台服务器出现故障,其他服务器依然可以继续提供服务。当然Redis提供了多种高可用方案包括:主从复制、哨兵模式的主从复制、以及集群。本文将详细介绍Redis从2.6以到4.0提供复制方案的演进,也包括:主从复制、复制原理以及相关实践。二、主从复制简介在主从复制中,数据库分为两类,一类是主库(master),另一类是同步主库数据的从库(slave)。主库可以进行读写操作,当写操作导致数据变化时会自动同步到从库。而从库一般是只读的(特定情况也可以写,通过参数slave-read-only指定),并接受来自主库的数据,一个主库可拥有多个从库,而一个从库只能有一个主库。这样就使得redis的主从架构有了两种模式:一类是一主多从如下图1,二类是“链式主从复制”--主->从->主-从如下图2。对于一主多从的复制架构不必多说,这里解释下链式主从复制:如上图2,主库A的数据会同步到从库B和从库C,而B又是从库D和从库E的主库,所以B的数据会同步到从库D和从库E。如果向B中写数据,数据只能同步到D和E中,所以对于这种架构数据的一致性将不能保持,也不推荐这种架构。 搭建配置主从由于没有过多的机器,这里将使用一台机器上启动多个redis实例实现主从复制。对于redis来说搭建主从非常容易,引用官网一句话来说:there is a very simple to use and configure leader follower (master-slave) replication。本次实践分别以 10.1.210.68:6379 作为主,两个从服务器分别是 10.1.210.69:6380 和 10.1.210.69:6381。搭建步骤:将redis.conf文件拷贝三份,名称最好有显示的区别,我这里采用名字为 6379.conf、 6380.conf、 6381.conf;分别修改三个文件的ip(默认127.0.0.1可以不用修改)、端口(尽量和配置文件一致)、pid文件,日志文件,持久化数据目录(dir)、后台运行(daemonize yes);使用启动命令脚本启动每个redis服务;设置主从关系、验证主从同步;示例:步骤一:#建立三个redis目录mkdir -p /opt/db/{redis6379,redis6380,redis6381}#从源码中拷贝配置文件cp redis-stable/redis.conf /opt/db/redis6379/6379.confcp redis-stable/redis.conf /opt/db/redis6380/6380.confcp redis-stable/redis.conf /opt/db/redis6381/6381.conf步骤二:修改配置项如下:找到对应的参数修改即可,下面是每个配置文件修改部分、本机器IP地址是10.1.210.69;daemonize yes #修改redis为后台运行模式pidfile /var/run/redis_6379.pid #修改运行的redis实例的pid,不能重复logfile "/opt/db/redis6379/6379.log" #指明日志文件dir "/opt/db/redis6379" #工作目录,存放持久化数据的目录bind 10.1.210.69 #监听地址,如果是单机多个示例可以不用修改port 6379 #监听端口,保持和配置文件名称端口一致6379.confdaemonize yes #修改redis为后台运行模式pidfile /var/run/redis_6380.pid #修改运行的redis实例的pid,不能重复logfile "/opt/db/redis6380/6380.log" #指明日志文件dir "/opt/db/redis6380" #工作目录,存放持久化数据的目录bind 10.1.210.69 #监听地址,如果是单机多个示例可以不用修改port 6380 #监听端口,保持和配置文件名称端口一致6380.confdaemonize yes #修改redis为后台运行模式pidfile /var/run/redis_6381.pid #修改运行的redis实例的pid,不能重复logfile "/opt/db/redis6379/6381.log" #指明日志文件dir "/opt/db/redis6381" #工作目录,存放持久化数据的目录bind 10.1.210.69 #监听地址,如果是单机多个实例可以不用修改使用127.0.0.1port 6381 #监听端口,保持和配置文件名称端口一致6381.conf步骤三:启动每个redis实例redis-server /opt/db/redis6379/6379.confredis-server /opt/db/redis6380/6380.confredis-server /opt/db/redis6381/6381.conf步骤四:设置主从关系,当然你可以直接指明从库配置文件直接使用slaveof <masterip> <masterport>指定,这里我在用客户端修改,分别使用客户端redis-cli命令连入端口为6380、6381的redis。连入6380数据库,使用redis-cli -h 10.1.210.69 -p 6380,其中-h代表ip地址,-p代表端口,并执行slaveof 10.1.210.69 6379,并写入配置文件config rewrite,如下:同样我们在从库6381执行相同操作:此时我们在使用info Replication 查看相关主从信息: 同时,还可以测试主从功能,在6379上创建key,在从库查看:主库:从库: 三、复制原理 了解redis复制原理对日后运维有很大帮助,包括如何规划节点,如何处理节点故障,redis复制过程可分为三个阶段:复制初始化阶段数据同步阶段命令传播阶段 复制初始化阶段当执行完slaveof  masterip  port 命令时候,从库根据指明的master节点ip和port向主库发起socket连接,主库收到socket连接之后将连接信息保存,此时连接建立;当socket连接建立完成以后,从库向主库发送ping命令,以确认主库是否可用,此时的结果返回如果是PONG则代表主库可以用,否则可能出现超时或者主库此时在处理其他任务阻塞那么此时从库将断开socket连接,然后进行重试;如果主库连接设置了密码,则从库需要设置masterauth参数,此时从库会发送auth命令,命令格式为“auth + 密码”进行密码验证,其中密码为masterauth参数配置的密码,需要注意的是如果主库设置了密码验证,从库未配置masterauth参数则报错,socket连接断开。当身份验证完成以后,从节点发送自己的监听端口,主库保存其端口信息,此时进入下一个阶段:数据同步阶段。数据同步阶段主库和从库都确认对方信息以后,便可开始数据同步,此时从库向主库发送psync命令(需要注意的是redis4.0版本对2.8版本的psync做了优化,后续会进行说明),主库收到该命令后判断是进行增量复制还是全量复制,然后根据策略进行数据的同步,当主库有新的写操作时候,此时进入复制第三阶段:命令传播阶段。命令传播阶段当数据同步完成以后,在此后的时间里主从维护着心跳检查来确认对方是否在线,每隔一段时间(默认10秒,通过repl-ping-slave-period参数指定)主节点向从节点发送PING命令判断从节点是否在线,而从节点每秒1次向主节点发送REPLCONF ACK命令,命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量,作用一是汇报自己复制偏移量,主节点会对比复制偏移量向从节点发送未同步的命令,作用二在于判断主节点是否在线,从库接送命令并执行,最终实现与主库数据相同。乐观复制redis采用量乐观复制策略,容忍在一定时间内主从数据内容是不同的,但是两者的数据最终会同步。 四、redis复制演进sync&psync1&psync2从redis2.6到4.0开发人员对其复制流程进行逐步的优化,以下是演进过程:redis版本<=2.6<2.8,复制采用sync命令,无论是第一次主从复制还是断线重连进行复制都采用全量复制;2.8<=redis版本<4.0,复制采用psync,从redis2.8开始,redis复制从sync过渡到psync,这一特性主要添加了redis在断线重新时候可使用部分复制;redis版本>=4.0,也采用psync,相比与2.8版本的psync优化了增量复制,这里我们称为psync2,2.8版本的psync称为psync1。以下将分别说明各个版本的复制演进。sync在redis2.6以及以前的版本,复制采用sync命令,当一个从库启动后,会向主库发送sync命令,主库收到sync命令后执行bgsave后台保存RDB快照(该过程在上一篇已经详细介绍),同时将保存快照的将快照保存期间接受的写命令保存到缓冲队列。当快照完成以后,主库将快照文件已经缓存的所有命令发送给从库,从库接受到快照文件并载入,再将执行主库发送的命令,也就是上面我们介绍的复制初始化阶段和数据同步阶段,其后就是命令增量同步,最终主库与从库保持数据一直。当从库在某些情况断线重连(如从库重启、由于网络原因主从连接超时),重复上述过程,进行数据同步。由此可见,redis2.6版本以及2.6以前复制过程全部采用全量复制。sync虽然解决了数据同步问题,但是在数据量比较大情况下,从库断线从来依然采用全量复制机制,无论是从数据恢复、宽带占用来说,sync所带来的问题还是很多的。于是redis从2.8开始,引入新的命令psync。psync1在redis2.8版本,redis引入psync命令来进行主从的数据同步,这里我们称该命令为psync1。psync1实现依赖以下三个关键点:1.offset(复制偏移量):主库和从库分别各自维护一个复制偏移量(可以使用info replication查看),用于标识自己复制的情况,在主库中代表主节点向从节点传递的字节数,在从库中代表从库同步的字节数。每当主库向从节点发送N个字节数据时,主节点的offset增加N,从库每收到主节点传来的N个字节数据时,从库的offset增加N。因此offset总是不断增大,这也是判断主从数据是否同步的标志,若主从的offset相同则表示数据同步量,不通则表示数据不同步。以下图示分别代表某个时刻两个主从的同步情况(以下是4.0版本截图): 2.replication backlog buffer(复制积压缓冲区):复制积压缓冲区是一个固定长度的FIFO队列,大小由配置参数repl-backlog-size指定,默认大小1MB。需要注意的是该缓冲区由master维护并且有且只有一个,所有slave共享此缓冲区,其作用在于备份最近主库发送给从库的数据。在主从命令传播阶段,主节点除了将写命令发送给从节点外,还会发送一份到复制积压缓冲区,作为写命令的备份。除了存储最近的写命令,复制积压缓冲区中还存储了每个字节相应的复制偏移量(如下图),由于复制积压缓冲区固定大小先进先出的队列,所以它总是保存的是最近redis执行的命令。 3.run_id(服务器运行的唯一ID) 每个redis实例在启动时候,都会随机生成一个长度为40的唯一字符串来标识当前运行的redis节点,
redis系列--深入哨兵集群
一、前言在之前的系列文章中介绍了redis的入门、持久化以及复制功能,如果不了解请移步至redis系列进行阅读,当然我也是抱着学习的知识分享,如果有什么问题欢迎指正,也欢迎大家转载。而本次将介绍哨兵集群相关知识,包括哨兵集群部署、哨兵原理、相关配置、故障转移等内容,正因为redis有了哨兵机制,而在很多企业(包括笔者自身的公司)采用的是哨兵模式下的redis主从。二、哨兵(Sentinel)简介 哨兵(后文统称sentinel)是官方推荐的的高可用(HA)解决方案。在之前的文章中介绍过redis的主从高可用解决方案,这种方案的缺点在于当master故障时候,需要手动进行故障恢复,而sentinel是一个独立运行的进程,它能监控一个或多个主从集群,并能在master故障时候自动进行故障转移,更为理想的是sentinel本身是一个分布式系统,其分布式设计思想有点类似于zookeeper,当某个时候Master故障后,sentinel集群采用Raft算法来选取Leader,故障转移由Leader完成。而对于客户端来说,操作redis的主节点,我们只需要询问sentinel,sentinel返回当前可用的master,这样一来客户端不需要关注的切换而引发的客户端配置变更。一个典型的sentinel架构如下图:  sentinel的主要功能:监控(Monitoring): sentinel 会不断地检查Master和Slave是否运作正常。通知(Notification):当被监控的某个Redis实例出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。自动故障迁移(Automatic failover):当一个Master不能正常工作时,sentinel)会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。配置中心(Configuration provider):如果故障转移发生了,sentinel会返回新的master地址。三、Sentinel集群部署环境规划本次部署过程中将分别部署三个哨兵节点来监控一主二从的redis集群,主从的搭建过程可以参考笔者博文《redis系列--主从复制以及redis复制演进》,以下是环境规则:哨兵节点:10.1.210.32:26379、10.1.210.33:26379、10.1.210.34:26379redis实例:10.1.210.69:6379(主)、10.1.210.69:6380(从)、10.1.210.69:6381(从)安装配置sentinel安装与redis安装过程一致,请参考redis系列文章,而在源码中,redis提供了参考配置示例sentinel.conf(可以使用命令grep -E -v ^# sentinel.conf 查看),如下:port 26379dir /tmpsentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 180000配置说明:sentinel monitor mymaster 127.0.0.1 6379 2 这行配置代表sentinel监控的master名字叫做mymaster(可以自己取),地址是127.0.0.1,端口是6379。最后一个2代表当sentinel集群中有2个sentinel认为master故障时候才判定master真正不可用。官方把该参数称为quorum,在后续选举领头哨兵时候会用到,在下文将进行介绍。sentinel down-after-milliseconds mymaster 30000sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了(subjectively down, 也简称为SDOWN)。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒,在这里表示30秒时间内master不回应PONG则主观不可用。sentinel parallel-syncs mymaster 1该配置表明在发生failover主备切换时候,最多允许多少个slave同时同步新的master。这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态。sentinel failover-timeout mymaster 180000failover-time超时时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failover失败,单位毫秒。不难发现关于sentinel的配置都是固定格式如下:sentinel <option_name> <master_name> <option_value> 运行Sentinel启动sentinel的方式有两种,两种方式都必须指定配置文件:#第一种(推荐)redis-sentinel /path/to/sentinel.conf#第二种redis-server /path/to/sentinel.conf --sentinel以下是笔者三个节点的配置文件,并同时拷贝到三个节点进行启动:bind 10.1.210.32 #IP地址port 26379 #端口dir /opt/db/redis # 数据存储目录daemonize yes #后台运行logfile /opt/db/redis/sentinel.log #日志sentinel monitor mymaster 10.1.210.69 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 18000010.1.210.32bind 10.1.210.33 #IP地址port 26379 #端口dir /opt/db/redis # 数据存储目录daemonize yes #后台运行logfile /opt/db/redis/sentinel.log #日志sentinel monitor mymaster 10.1.210.69 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 18000010.1.210.33bind 10.1.210.34 #IP地址port 26379 #端口dir /opt/db/redis # 数据存储目录daemonize yes #后台运行logfile /opt/db/redis/sentinel.log #日志sentinel monitor mymaster 10.1.210.69 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 18000010.1.210.34通过redis-sentinel /opt/db/redis/sentinel.conf启动每个sentinel,以下是启动日志(可以发现sentinel自动通过master发现slave和其他sentinel): 此时,一个sentinel集群就搭建完成。 sentinel相关命令和redis一样,sentinel可以通过客户端使用命令操作,例如查看master状态SENTINEL masters,示例:以下是所有命令以及解释:SENTINEL masters #列出所有被监视的master,以及当前master状态SENTINEL master <master name> #列出指定的masterSENTINEL slaves <master name> #列出给定master的所有slave以及slave状态SENTINEL sentinels <master name> #列出监控指定的master的所有sentinelSENTINEL get-master-addr-by-name <master name> #返回给定master名字的服务器的IP地址和端口号SENTINEL reset <pattern> #重置所有匹配pattern表达式的master状态SENTINEL failover <master name> #当msater失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移,但是它会给其他sentinel发送一个最新的配置,其他sentinel会根据这个配置进行更新SENTINEL ckquorum <master name> #检查当前sentinel的配置能否达到故障切换master所需的数量,此命令可用于检测sentinel部署是否正常,正常返回okSENTINEL flushconfig #强制sentinel将运行时配置写入磁盘,包括当前sentinel状态 四、Sentinel原理SDOWN和ODOWN在介绍sentinel原理之前,需要了解的两个概念SDOWN和ODOWN:SDOWN:全拼Subjectively Down,称为主观下线,指的是单个sentinel对redis实例作出的下线状态判断。ODOWN:全拼Objectively Down,称为客户端下线,指多个 Sentinel 实例在对同一个redis做出 SDOWN 判断,并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的redis实例下线判断。(一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的redis实例已下线。)从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。这个时间在配置中通过master-down-after-milliseconds参数配置。当sentinel发送PING后,以下回复之一都被认为是合法的:PING replied with +PONG.PING replied with -LOADING error.PING replied with -MASTERDOWN error.其它任何回复(或者根本没有回复)都是不合法的。从SDOWN切换到ODOWN不需要任何一致性算法,只需要一个gossip协议:如果一个sentinel收到了足够多的sentinel发来消息告诉它某个master已经down掉了,SDOWN状态就会变成ODOWN状态。如果之后master可用了,这个状态就会相应地被清理掉。真正进行failover需要一个授权的过程,这个授权的过程即是leader选取过程,但是所有的failover都开始于一
Redis持久化实践及灾难恢复模拟
Redis持久化实践及灾难恢复模拟源地址:http://heylinux.com/archives/1932.html另一篇:Redis主从自动failoverhttp://ylw6006.blog.51cto.com/470441/1086455/ 参考资料:Redis Persistence http://redis.io/topics/persistenceGoogle Groups https://groups.google.com/forum/?fromgroups=#!forum/redis-db一、对Redis持久化的探讨与理解目前Redis持久化的方式有两种: RDB 和 AOF首先,我们应该明确持久化的数据有什么用,答案是用于重启后的数据恢复。Redis是一个内存数据库,无论是RDB还是AOF,都只是其保证数据恢复的措施。所以Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。RDB就是Snapshot快照存储,是默认的持久化方式。可理解为半持久化模式,即按照一定的策略周期性的将数据保存到磁盘。对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。下面是默认的快照设置:1 save 900 1 #当有一条Keys数据被改变时,900秒刷新到Disk一次2 save 300 10 #当有10条Keys数据被改变时,300秒刷新到Disk一次3 save 60 10000 #当有10000条Keys数据被改变时,60秒刷新到Disk一次   Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的。当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件。这样在任何时候出现故障,Redis的RDB文件都总是可用的。同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。第一次Slave向Master同步的实现是:Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发送给各个Slave。但不管什么原因导致Slave和Master断开重连都会重复以上两个步骤的过程。Redis的主从复制是建立在内存快照的持久化基础上的,只要有Slave就一定会有内存快照发生。可以很明显的看到,RDB有它的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的。从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。AOF(Append-Only File)比RDB方式有更好的持久化性。由于在使用AOF持久化方式时,Redis会将每一个收到的写命令都通过Write函数追加到文件中,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。对应的设置参数为:$ vim /opt/redis/etc/redis_6379.conf1 appendonly yes #启用AOF持久化方式2 appendfilename appendonly.aof #AOF文件的名称,默认为appendonly.aof3 # appendfsync always #每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。4 appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。5 # appendfsync no #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐。   AOF的完全持久化方式同时也带来了另一个问题,持久化文件会变得越来越大。比如我们调用INCR test命令100次,文件中就必须保存全部的100条命令,但其实99条都是多余的。因为要恢复数据库的状态其实文件中保存一条SET test 100就够了。为了压缩AOF的持久化文件,Redis提供了bgrewriteaof命令。收到此命令后Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件,以此来实现控制AOF文件的增长。由于是模拟快照的过程,因此在重写AOF文件时并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件。对应的设置参数为:$ vim /opt/redis/etc/redis_6379.conf1 no-appendfsync-on-rewrite yes #在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。2 auto-aof-rewrite-percentage 100 #当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。3 auto-aof-rewrite-min-size 64mb #当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。   到底选择什么呢?下面是来自官方的建议:通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用RDB。很多用户仅使用了AOF,但是我们建议,既然RDB可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用RDB。因此,我们希望可以在未来(长远计划)统一AOF和RDB成一种持久化模式。在数据恢复方面:RDB的启动时间会更短,原因有两个:一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在CPU消耗上要远小于AOF日志的加载。二、灾难恢复模拟既然持久化的数据的作用是用于重启后的数据恢复,那么我们就非常有必要进行一次这样的灾难恢复模拟了。据称如果数据要做持久化又想保证稳定性,则建议留空一半的物理内存。因为在进行快照的时候,fork出来进行dump操作的子进程会占用与父进程一样的内存,真正的copy-on-write,对性能的影响和内存的耗用都是比较大的。目前,通常的设计思路是利用Replication机制来弥补aof、snapshot性能上的不足,达到了数据可持久化。即Master上Snapshot和AOF都不做,来保证Master的读写性能,而Slave上则同时开启Snapshot和AOF来进行持久化,保证数据的安全性。首先,修改Master上的如下配置:$ sudo vim /opt/redis/etc/redis_6379.conf1 #save 900 1 #禁用Snapshot2 #save 300 103 #save 60 1000045 appendonly no #禁用AOF   接着,修改Slave上的如下配置:$ sudo vim /opt/redis/etc/redis_6379.conf01 save 900 1 #启用Snapshot02 save 300 1003 save 60 100000405 appendonly yes #启用AOF06 appendfilename appendonly.aof #AOF文件的名称07 # appendfsync always08 appendfsync everysec #每秒钟强制写入磁盘一次09 # appendfsync no1011 no-appendfsync-on-rewrite yes #在日志重写时,不进行命令追加操作12 auto-aof-rewrite-percentage 100 #自动启动新的日志重写过程13 auto-aof-rewrite-min-size 64mb #启动新的日志重写过程的最小值   分别启动Master与Slave$ /etc/init.d/redis start启动完成后在Master中确认未启动Snapshot参数redis 127.0.0.1:6379> CONFIG GET save1) "save"2) ""然后通过以下脚本在Master中生成25万条数据:dongguo@redis:/opt/redis/data/6379$ cat redis-cli-generate.temp.sh01 #!/bin/bash0203 REDISCLI="redis-cli -a slavepass -n 1 SET"04 ID=10506 while(($ID<50001))07 do08 INSTANCE_NAME="i-2-$ID-VM"09 UUID=`cat /proc/sys/kernel/random/uuid`10 PRIVATE_IP_ADDRESS=10.`echo "$RANDOM % 255 + 1" | bc`.`echo "$RANDOM % 255 + 1" |bc`.`echo "$RANDOM % 255 + 1" | bc`11 CREATED=`date "+%Y-%m-%d %H:%M:%S"`1213 $REDISCLI vm_instance:$ID:instance_name "$INSTANCE_NAME"14 $REDISCLI vm_instance:$ID:uuid "$UUID"15 $REDISCLI vm_instance:$ID:private_ip_address "$PRIVATE_IP_ADDRESS"16 $REDISCLI vm_instance:$ID:created "$CREATED"1718 $REDISCLI vm_instance:$INSTANCE_NAME:id "$ID"1920 ID=$(($ID+1))21 done   dongguo@redis:/opt/redis/data/6379$ ./redis-cli-generate.temp.sh在数据的生成过程中,可以很清楚的看到Master上仅在第一次做Slave同步时创建了dump.rdb文件,之后就通过增量传输命令的方式给Slave了。dump.rdb文件没有再增大。dongguo@redis:/opt/redis/data/6379$ ls -lhtotal 4.0K-rw-r--r-- 1 root root 10 Sep 27 00:40 dump.rdb而Slave上则可以看到dump.rdb文件和AOF文件在不断的增大,并且AOF文件的增长速度明显大于dump.rdb文件。dongguo@redis-slave:/opt/redis/data/6379$ ls -lhtotal 24M-rw-r--r-- 1 root root 15M Sep 27 12:06 appendonly.aof-rw-r--r-- 1 root root 9.2M Sep 27 12:06 dump.rdb等待数据插入完成以后,首先确认当前的数据量。redis 127.0.0.1:6379> info01 redis_version:2.4.1702 redis_git_sha1:0000000003
Redis configuration
官方2.6配置如下:# Redis configuration file example# Note on units: when memory size is needed, it is possible to specify# it in the usual form of 1k 5GB 4M and so forth:## 1k => 1000 bytes# 1kb => 1024 bytes# 1m => 1000000 bytes# 1mb => 1024*1024 bytes# 1g => 1000000000 bytes# 1gb => 1024*1024*1024 bytes## units are case insensitive so 1GB 1Gb 1gB are all the same.# By default Redis does not run as a daemon. Use 'yes' if you need it.# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.daemonize no# When running daemonized, Redis writes a pid file in /var/run/redis.pid by# default. You can specify a custom pid file location here.pidfile /var/run/redis.pid# Accept connections on the specified port, default is 6379.# If port 0 is specified Redis will not listen on a TCP socket.port 6379# If you want you can bind a single interface, if the bind option is not# specified all the interfaces will listen for incoming connections.## bind 127.0.0.1# Specify the path for the unix socket that will be used to listen for# incoming connections. There is no default, so Redis will not listen# on a unix socket when not specified.## unixsocket /tmp/redis.sock# unixsocketperm 755# Close the connection after a client is idle for N seconds (0 to disable)timeout 0# TCP keepalive.## If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence# of communication. This is useful for two reasons:## 1) Detect dead peers.# 2) Take the connection alive from the point of view of network# equipment in the middle.## On Linux, the specified value (in seconds) is the period used to send ACKs.# Note that to close the connection the double of the time is needed.# On other kernels the period depends on the kernel configuration.## A reasonable value for this option is 60 seconds.tcp-keepalive 0# Specify the server verbosity level.# This can be one of:# debug (a lot of information, useful for development/testing)# verbose (many rarely useful info, but not a mess like the debug level)# notice (moderately verbose, what you want in production probably)# warning (only very important / critical messages are logged)loglevel notice# Specify the log file name. Also 'stdout' can be used to force# Redis to log on the standard output. Note that if you use standard# output for logging but daemonize, logs will be sent to /dev/nulllogfile stdout# To enable logging to the system logger, just set 'syslog-enabled' to yes,# and optionally update the other syslog parameters to suit your needs.# syslog-enabled no# Specify the syslog identity.# syslog-ident redis# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.# syslog-facility local0# Set the number of databases. The default database is DB 0, you can select# a different one on a per-connection basis using SELECT <dbid> where# dbid is a number between 0 and 'databases'-1databases 16################################ SNAPSHOTTING ################################### Save the DB on disk:## save <seconds> <changes>## Will save the DB if both the given number of seconds and the given# number of write operations against the DB occurred.## In the example below the behaviour will be to save:# after 900 sec (15 min) if at least 1 key changed# after 300 sec (5 min) if at least 10 keys changed# after 60 sec if at least 10000 keys changed## Note: you can disable saving at all commenting all the "save" lines.## It is also possible to remove all the previously configured save# points by adding a save directive with a single empty string argument# like in the following example:## save ""save 900 1save 300 10save 60 10000# By default Redis will stop accepting writes if RDB snapshots are enabled# (at least one save point) and the latest background save failed.# This will make the user aware (in an hard way) that data is not persisting# on disk properly, otherwise chances are that no one will notice and some# distater will happen.## If the background saving process will start working again Redis will# automatically allow writes again.## However if you have setup your proper monitoring of the Redis server# and persistence, you may want to disable this feature so that Redis will# continue to work as usually even if there are problems with disk,# permissions, and so forth.stop-writes-on-bgsave-error yes# Compress string objects using LZF when dump .rdb databases?# For default that's set to 'yes' as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.# This makes the format more resistant to corruption but there is a performance# hit to pay (around 10%) when saving and loading RDB files, so you can disable it# for maximum performances.## RDB files created with checksum disabled have a checksum of zero that will# tell the loading code to skip the check.rdbchecksum yes# The f
Redis运行流程源码解析--转载
http://blog.nosqlfan.com/html/4007.htmlhttp://www.searchdatabase.com.cn/showcontent_62166.htm导读:本文分析源码基于Redis 2.4.7 stable版本,对Redis运行流程,命令处理的内部实现进行了深入讲解。关键词:Redis 源码 运行流程 框架 概述Redis通过定义一个 struct redisServer 类型的全局变量server 来保存服务器的相关信息(比如:配置信息,统计信息,服务器状态等等)。启动时通过读取配置文件里边的信息对server进行初始化(如果没有指定配置文 件,将使用默认值对sever进行初始化),初始化的内容有:起监听端口,绑定有新连接时的回调函数,绑定服务器的定时函数,虚拟内存初始化,log初始 化等等。启动初始化服务器配置先来看看redis 的main函数的入口Redis.c:1694int main(int argc, char **argv) {    time_t start;    initServerConfig();    if (argc == 2) {        if (strcmp(argv[1], "-v") == 0 ||            strcmp(argv[1], "--version") == 0) version();        if (strcmp(argv[1], "--help") == 0) usage();        resetServerSaveParams();        loadServerConfig(argv[1]);    } else if ((argc > 2)) {        usage();    } else {        ...    }    if (server.daemonize) daemonize();    initServer();    ...initServerConfig初始化全局变量 server 的属性为默认值。如果命令行指定了配置文件, resetServerSaveParams重置对落地备份的配置(即重置为默认值)并读取配置文件的内容对全局变量 server 再进行初始化 ,没有在配置文件中配置的将使用默认值。如果服务器配置成后台执行,则对服务器进行 daemonize。initServer初始化服务器,主要是设置信号处理函数,初始化事件轮询,起监听端口,绑定有新连接时的回调函数,绑定服务器的定时函数,初始化虚拟内存和log等等。创建服务器监听端口。Redis.c:923    if (server.port != 0) {        server.ipfd= anetTcpServer(server.neterr,server.port,server.bindaddr);        if (server.ipfd == ANET_ERR) {            redisLog(REDIS_WARNING, "Opening port %d: %s",                server.port, server.neterr);            exit(1);        }    }anetTcpServer创建一个socket并进行监听,然后把返回的socket fd赋值给server.ipfd。事件轮询结构体定义先看看事件轮询的结构体定义Ae.h:88/* State of an event based program */typedef struct aeEventLoop {    int maxfd;    long long timeEventNextId;    aeFileEvent events[AE_SETSIZE]; /* Registered events */    aeFiredEvent fired[AE_SETSIZE]; /* Fired events */    aeTimeEvent *timeEventHead;    int stop;    void *apidata; /* This is used for polling API specific data */    aeBeforeSleepProc *beforesleep;} aeEventLoop;maxfd是最大的文件描述符,主要用来判断是否有文件事件需要处理(ae.c:293)和当使用select 来处理网络IO时作为select的参数(ae_select.c:50)。timeEventNextId 是下一个定时事件的ID。events[AE_SETSIZE]用于保存通过aeCreateFileEvent函数创建的文件事件,在sendReplyToClient函数和freeClient函数中通过调用aeDeleteFileEvent函数删除已经处理完的事件。fired[AE_SETSIZE]用于保存已经触发的文件事件,在对应的网络I/O函数中进行赋值(epoll,select,kqueue),不会对fired进行删除操作,只会一直覆盖原来的值。然后在aeProcessEvents函数中对已经触发的事件进行处理。timeEventHead 是定时事件链表的头,定时事件的存储用链表实现。Stop 用于停止事件轮询处理。apidata 用于保存轮询api需要的数据,即aeApiState结构体,对于epoll来说,aeApiState结构体的定义如下:typedef struct aeApiState {    int epfd;    struct epoll_event events[AE_SETSIZE];} aeApiState;beforesleep 是每次进入处理事件时执行的函数。创建事件轮询Redis.c:920  server.el = aeCreateEventLoop();Ae.c:55aeEventLoop *aeCreateEventLoop(void) {    aeEventLoop *eventLoop;    int i;    eventLoop = zmalloc(sizeof(*eventLoop));    if (!eventLoop) return NULL;    eventLoop->timeEventHead = NULL;    eventLoop->timeEventNextId = 0;    eventLoop->stop = 0;    eventLoop->maxfd = -1;    eventLoop->beforesleep = NULL;    if (aeApiCreate(eventLoop) == -1) {        zfree(eventLoop);        return NULL;    }/* Events with mask == AE_NONE are not set. So let's initialize * the vector with it. */    for (i = 0; i < AE_SETSIZE; i++)        eventLoop->events[i].mask = AE_NONE;    return eventLoop;}绑定定时函数和有新连接时的回调函数redis.c:973aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);if (server.ipfd > 0 &&    aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");aeCreateTimeEvent创建定时事件并绑定回调函数serverCron,这个定时事件第一次是超过1毫秒就有权限执行,如果其他事件的处理时间比较长,可能会出现超过一定时间都没执行情况。这里的1毫秒只是超过后有可执行的权限,并不是一定会执行。第一次执行后,如果还要执行,是由定时函数的返回值确定的,在processTimeEvents(ae.c:219)中,当调用定时回调函数后,获取定时回调函数的返回值,如果返回值不等于-1,则设置定时回调函数的下一次触发时间为当前时间加上定时回调函数的返回值,即调用间隔时间。serverCron的返回值是100ms,表明从二次开始,每超过100ms就有权限执行。(定时回调函数serverCron用于更新lru时钟,更新服务器的状态,打印一些服务器信息,符合条件的情况下对hash表进行重哈希,启动后端写AOF或者检查后端写AOF或者备份是否完成,检查过期的KEY等等)aeCreateFileEvent创建监听端口的socket fd的文件读事件(即注册网络io事件)并绑定回调函数acceptTcpHandler。进入事件轮询初始化后将进入事件轮询Redis.c:1733    aeSetBeforeSleepProc(server.el,beforeSleep);    aeMain(server.el);    aeDeleteEventLoop(server.el);设置每次进入事件处理前会执行的函数beforeSleep。进入事件轮询aeMain。退出事件轮询后删除事件轮询,释放事件轮询占用内存aeDeleteEventLoop(不过没在代码中发现有执行到这一步的可能,服务器接到shutdown命令时通过一些处理后直接就通过exit退出了,可能是我看错了,待验证)。事件轮询函数aeMain看看aeMain的内容Ae.c:382void aeMain(aeEventLoop *eventLoop) {    eventLoop->stop = 0;    while (!eventLoop->stop) {        if (eventLoop->beforesleep != NULL)            eventLoop->beforesleep(eventLoop);        aeProcessEvents(eventLoop, AE_ALL_EVENTS);    }}每次进入事件处理前,都会调用设置的beforesleep,beforeSleep函数主要是处理被阻塞的命令和根据配置写AOF。aeProcessEvents处理定时事件和网络io事件。启动完毕,等待客户端请求到进入事件轮询函数后,redis的启动工作就做完了,接下来就是等待客户端的请求了。接收请求新连接到来时的回调函数在绑定定时函数和有新连接时的回调函数中说到了绑定有新连接来时的回调函数acceptTcpHandler,现在来看看这个函数的具体内容Networking.c:427void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {    int cport, cfd;    char cip[128];    REDIS_NOTUSED(el);    REDIS_NOTUSED(mask);    REDIS_NOTUSED(privdata);    cfd = anetTcpAccept(server.neterr, fd, cip, &cport);    if (cfd == AE_ERR) {        redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);        return;    }    redisLog(REDIS_VE
What is GDB?
Redis debugging guideRedis is developed with a great stress on stability: we do our best with every release to make sure you'll experience a very stable product and no crashes. However even with our best efforts it is impossible to avoid all the critical bugs with 100% of success.When Redis crashes it produces a detailed report of what happened, however sometimes looking at the crash report is not enough, nor it is possible for the Redis core team to reproduce the issue independently: in this scenario we need help from the user that is able to reproduce the issue.This little guide shows how to use GDB to provide all the informations the Redis developers will need to track the bug more easily.What is GDB?GDB is the Gnu Debugger: a program that is able to inspect the internal state of another program. Usually tracking and fixing a bug is an exercise in gathering more informations about the state of the program at the moment the bug happens, so GDB is an extremely useful tool.GDB can be used in two ways:It can attach to a running program and inspect the state of it at runtime.It can inspect the state of a program that already terminated using what is called a core file, that is, the image of the memory at the time the program was running.From the point of view of investigating Redis bugs we need to use both this GDB modes: the user able to reproduce the bug attaches GDB to his running Redis instance, and when the crash happens, he creates the core file that the in turn the developer will use to inspect the Redis internals at the time of the crash.This way the developer can perform all the inspections in his computer without the help of the user, and the user is free to restart Redis in the production environment.Compiling Redis without optimizationsBy default Redis is compiled with the -O2 switch, this means that compiler optimizations are enabled. This makes the Redis executable faster, but at the same time it makes Redis (like any other program) harder to inspect using GDB.It is better to attach GDB to Redis compiled without optimizations using the make noopt command to compile it (instead of just using the plain make command). However if you have an already running Redis in production there is no need to recompile and restart it if this is going to create problems on your side. Even if by a lesser extent GDB still works against executables compiled with optimizations.It is great if you make sure to recompile Redis with make noopt after the first crash, so that the next time it will be simpler to track the issue.You should not be concerned with the loss of performances compiling Redis without optimizations, it is very unlikely that this will cause problems in your environment since it is usually just a matter of a small percentage because Redis is not very CPU-bound (it does a lot of I/O to serve queries).Attaching GDB to a running processIf you have an already running Redis server, you can attach GDB to it, so that if Redis will crash it will be possible to both inspect the internals and generate a core dump file.After you attach GDB to the Redis process it will continue running as usually without any loss of performance, so this is not a dangerous procedure.In order to attach GDB the first thing you need is the process ID of the running Redis instance (the pid of the process). You can easily obtain it using redis-cli:$ redis-cli info | grep process_idprocess_id:58414In the above example the process ID is 58414.Login into your Redis server.(Optional but recommended) Start screen or tmux or any other program that will make sure that your GDB session will not be closed if your ssh connection will timeout. If you don't know what screen is do yourself a favour and Read this articleAttach GDB to the running Redis server typing:gdb <path-to-redis-executable> <pid>For example: gdb /usr/local/bin/redis-server 58414GDB will start and will attach to the running server printing something like the following:Reading symbols for shared libraries + done0x00007fff8d4797e6 in epoll_wait ()(gdb)At this point GDB is attached but your Redis instance is blocked by GDB. In order to let the Redis instance continue the execution just type continue at the GDB prompt, and press enter.(gdb) continueContinuing.Done! Now your Redis instance has GDB attached. You can wait for... the next crash :)Now it's time to detach your screen / tmux session, if you are running GDB using it, pressing the usual Ctrl-a a key combination.After the crashRedis has a command to simulate a segmentation fault (in other words a bad crash) using the DEBUG SEGFAULT command (don't use it against a real production instance of course ;). So I'll use this command to crash my instance to show what happens in the GDB side:(gdb) continueContinuing.Program received signal EXC_BAD_ACCESS, Could not access memory.Reason: KERN_INVALID_ADDRESS at address: 0xffffffffffffffffdebugCommand (c=0x7ffc32005000) at debug.c:220220 *((char*)-1) = 'x';As you can see GDB detected that Redis crashed, and was able to show me even the file name and line num
redis High Availability---Redis Sentinel翻译
注意,本文档为最新(11月21日),旧版本的可以参考:http://redis.io/topics/sentinel-old 不鼓励使用旧版本的文档。Redis Sentinel是一个用来管理Redis服务器的系统,它主要工作有三种:1. 监控。Sentinel一直检测主从redis服务器是否正常工作。2. 通知。当一个被监控的redis服务器出错时,Sentinel可以通过api来通知系统管理员或者别的系统。3. 自动灾备。当主redis实例不能正常工作时,Sentinel会启动一个灾备进程,从而使一个从redis实例设置为主redis服务器,别的从Redis服务器设置为新的主redis的从服务器,然后通知使用redis服务器的应用系统来连接新的主redis服务器地址。 Redis Sentinel是一个分布式系统,这通常意味着你想在你的设备上运行多个Sentinel处理程序。Sentinel处理程序应该做到:1. 当一个主服务器宕机时可以检测到的协议;2.可以执行灾备,改变别的Redis服务器配置。Redis Sentinel原意为一个单机可运行程序,但实际上只是redis服务器的一种特殊执行模式,可以通过使用选项--sentinel来执行。注意:Redis Sentinel推荐应用在在redis 2.8.0或者以上版本。 获取Sentinel    当前,在github中,Sentinel是Redis非稳定版本分支的一部分。编译redis,你需要克隆非稳定版本进行编译。编译完成后,你可以在src目录下看到一个名为redis-sential可执行文件。同样,在下一段落中,你也可以直接使用redis-server可执行文件,以Sentinel模式 启动。启动Sentinel    两种方式:    1. redis-sentinel /path/to/sentinel.conf    2. redis-server /path/to/sentinel.conf --sentinel两种启动方式是一样的,但请注意,sentinel配置文件必须指定,因为配置文件会保存当前的运行状态,当需要重启时可以重新加载此配置文件。如果没有配置文件,Sentinel将拒绝启动或者不能向制定路径写入文件。 配置Sentinel  Redis发布的源代码版本中包含一个名为sentinel.conf的文件,该文件是一个配置实例。一个典型的最小配置文件如下所示:sentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 60000sentinel failover-timeout mymaster 180000sentinel parallel-syncs mymaster 1sentinel monitor resque 192.168.1.3 6380 4sentinel down-after-milliseconds resque 10000sentinel failover-timeout resque 180000sentinel parallel-syncs resque 5--休息一下 马上回来。    
Redis: under the hood---转载
http://pauladamsmith.com/articles/redis-under-the-hood.html#redis-under-the-hoodHow does the Redis server work?I was curious to learn more about Redis’s internals, so I’ve been familiarizing myself with the source, largely by reading and jumping around in Emacs. After I had peeled back enough of the onion’s layers, I realized I was trying to keep track of too many details in my head, and it wasn’t clear how it all hung together. I decided to write out in narrative form how an instance of the Redis server starts up and initializes itself, and how it handles the request/response cycle with a client, as a way of explaining it to myself, hopefully in a clear fashion. Luckily, Redis has a nice, clean code base that is easy to read and follow along. Armed with a TAGS file, my $EDITOR, and GDB, I set out to see how it all works under the hood. (Incidentally, I was working with the Redis code base as of commit b4f2e41. Of course, internals such as I outline below are subject to change. However, the broad architecture of the server is unlikely to change very much, and I tried to keep that in mind as I went along.)This article examines server startup and takes a high-level view of the request/response processing cycle. In a subsequent article, I’ll dive in to greater detail and trace a simple SET/GET command pair as they make their way through Redis.Redis: under the hoodStartupBeginning global server state initializationSetting up command tableLoading config fileinitServer()Shared objectsShared integersEvent loopDatabasesTCP socketServer cronRegistering connection handler with event loopOpening the AOFBack up to main()Restoring dataEvent loop setupEntering the event loopProcessing a request & returning a responseHandling a new connectionReading a command from a clientExecuting the command and respondingSummaryNext time — tracing a SET and GETStartupLet’s begin with the main() function in redis.c.Beginning global server state initializationFirst, initServerConfig() is called. This partially initializes a variable server, which has the type struct redisServer, that serves as the global server state.// redis.h:338struct redisServer {pthread_t mainthread;int port;int fd;redisDb *db;// ...};// redis.c:69struct redisServer server; /* server global state */There are a huge number of members in this struct, but they generally fall into the following categories:general server statestatisticsconfiguration from config filereplicationsort parametersvirtual memory config, state, I/O threads, & statszip structureevent loop helperspub/subFor example, this struct includes members that map to options in the configuration file (usually named redis.conf) such as the port the server listens on and how verbose logging should be, pointers to linked lists of connected clients and slave Redis servers as well as the Redis database(s) itself, and counters for statistics like the number of commands processed since startup.initServerConfig() provides default values for the members that can be configured by the user via the redis.conf config file.Setting up command tableThe next thing main() does is sort the table of Redis commands. These are defined in a global variable readonlyCommandTable which is an array of struct redisCommands.// redis.c:70struct redisCommand *commandTable;struct redisCommand readonlyCommandTable[] = {{"get",getCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},{"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0},{"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0},{"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0},{"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},// ...};// redis.h:458typedef void redisCommandProc(redisClient *c);// ...struct redisCommand {char *name;redisCommandProc *proc;int arity;int flags;// ...};A redisCommand struct keeps track of its name — the mnemonic, i.e., “get” — a pointer to the actual C function that performs the command, the command’s arity, command flags such as whether it returns a bulk response, and a number of VM-specific members.)The read-only table is ordered in source code so that commands are grouped by category, such as string commands, list commands, set commands, etc., to make it easier for a programmer to scan the table for similar commands. The sorted table of commands is pointed to by the global variable commandTable, and is used to lookup Redis commands with a standard binary search (lookupCommand(), which returns a pointer to a redisCommand).Loading config filemain() moves on to processing command-line options given by the user starting up the redis-server executable. Currently, there is only a single argument that Redis takes — aside from the usual version -vand help -h flags — which is a path to a config file. If the path is given, Redis loads the config file and overrides any defaults already set by initServerConfig() by calling loadServerConfig(). This function is fairly straightforward, looping over each line in the config file and converting values that match directive names to appropr
Linux环境进程间通信二: 信号--转载
http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.htmlhttp://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 一、信号及信号来源信号本质信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号来源信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。 回页首二、信号的种类可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通信(一):管道及有名管道》的附1中列出了系统所支持的所有信号。1、可靠信号与不可靠信号"不可靠信号"Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。信号可能丢失,后面将对此详细阐述。 因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。"可靠信号"随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。2、实时信号与非实时信号早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。 回页首三、进程对信号的响应进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。 回页首四、信号的发送发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。1、kill() #include <sys/types.h> #include <signal.h> int kill(pid_t pid,int signo) 参数pid的值信号的接收进程pid>0进程ID为pid的进程pid=0同一个进程组的进程pid<0 pid!=-1进程组ID为 -pid的所有进程pid=-1除发送进程自身外,所有进程ID大于1的进程Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。Kill()最常用于pid>0时的信号发送,调用成功返回 0; 否则,返回 -1。 注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一,其实很简单,参阅内核源码kernal/signal.c即可,上表中的规则是参考red hat 7.2。2、raise() #include <signal.h> int raise(int signo) 向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。3、sigqueue() #include <sys/types.h> #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval val) 调用成功返回 0;否则,返回 -1。sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。typedef union sigval {int sival_int;void *sival_ptr;}sigval_t; sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针,稍后将阐述)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。4、alarm() #include <unistd.h> unsigned int alarm(unsigned int seconds) 专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。 返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。5、setitimer() #include <sys/time.h> int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); setitimer()比alarm功能强大,支持3种类型的定时器:ITIMER_REAL: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;Setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例,结构itimerval形式见附录1。第三个参数可不做处理。Setitimer()调用成功返回0,否则返回-1。6、abort() #include <stdlib.h> void abort(void);向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。 回页首五、信号的安装(设置信号关联动作)如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优
深入redis内部--实现双向链表
数据结构的应用--Adlist.h定义1.节点结构typedef struct listNode {    struct listNode *prev;  //前向节点    struct listNode *next; //后向节点    void *value;              //该节点的值} listNode;2.双向链表结构typedef struct list {    listNode *head;              //头节点    listNode *tail;                //尾节点    void *(*dup)(void *ptr); //复制函数    void (*free)(void *ptr);   //释放函数    int (*match)(void *ptr, void *key);  //匹配函数,查找节点使用    unsigned long len;          //双向链表的长度即节点的个数} list;3.双向链表遍历器typedef struct listIter {    listNode *next;   //下一个节点    int direction;} listIter;  方向定义   #define AL_START_HEAD 0  //向前查找   #define AL_START_TAIL 1    //向后查找4.宏定义函数#define listLength(l) ((l)->len)#define listFirst(l) ((l)->head)#define listLast(l) ((l)->tail)#define listPrevNode(n) ((n)->prev)#define listNextNode(n) ((n)->next)#define listNodeValue(n) ((n)->value)#define listSetDupMethod(l,m) ((l)->dup = (m))#define listSetFreeMethod(l,m) ((l)->free = (m))#define listSetMatchMethod(l,m) ((l)->match = (m))#define listGetDupMethod(l) ((l)->dup)#define listGetFree(l) ((l)->free)#define listGetMatchMethod(l) ((l)->match)5.定义函数list *listCreate(void); //创建一个新的链表。该链表可以使用AlFree()方法释放。                              //但使用AlFree()方法前需要释放用户释放私有节点的值。                             //如果没有创建成功,返回null;创建成功则返回指向新链表的指针。void listRelease(list *list);  //释放整个链表,此函数不会执行失败。调用zfree(list *list)方法,定义在Zmalloc.c中。list *listAddNodeHead(list *list, void *value); //向链表头部中增加一个节点list *listAddNodeTail(list *list, void *value);    //向链表尾部增加一个节点list *listInsertNode(list *list, listNode *old_node, void *value, int after);//向某个节点位置插入节点 after为方向void listDelNode(list *list, listNode *node);//从链表上删除特定节点,调用者释放特定私用节点的值。                                                           //该函数不会执行失败listIter *listGetIterator(list *list, int direction);//返回某个链表的迭代器。                                                                 //迭代器的listNext()方法会返回链表的下个节点。direction是方向                                                                //该函数不会执行失败。listNode *listNext(listIter *iter);               void listReleaseIterator(listIter *iter);            //释放迭代器的内存。list *listDup(list *orig);                               //复制整个链表。当内存溢出时返回null,成功时返回原链表的一个备份                                                               //不管该方法是否执行成功,原链表不会改变。listNode *listSearchKey(list *list, void *key);  //从特定的链表查找key。成功则返回第一个匹配节点的指针                                                                //如果没有匹配,则返回null。listNode *listIndex(list *list, long index);     //序号从0开始,链表的头的索引为0.1为头节点的下个节点。一次类推。                                                        //负整数用来表示从尾部开始计数。-1表示最后一个节点,-2倒数第二个节点                                                         //如果超过链表的索引,则返回nullvoid listRewind(list *list, listIter *li) {    li->next = list->head;    li->direction = AL_START_HEAD;}void listRewindTail(list *list, listIter *li) {    li->next = list->tail;    li->direction = AL_START_TAIL;}void listRotate(list *list);                 //旋转链表,移除尾节点并插入头部。  
深入redis内部--实现字符串
redis字符串的定义和实现在Ssd.h和Ssd.c中。1.定义typedef char *sds; //本质是字符char的指针2.字符串的操作sds sdsnew(const char *init) {    size_t initlen = (init == NULL) ? 0 : strlen(init);    return sdsnewlen(init, initlen);}调用sdsnewlen,看一下该函数的实现sds sdsnewlen(const void *init, size_t initlen) {    struct sdshdr *sh;    if (init) {        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);    } else {        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);    }    if (sh == NULL) return NULL;    sh->len = initlen;    sh->free = 0;    if (initlen && init)        memcpy(sh->buf, init, initlen);    sh->buf[initlen] = '';    return (char*)sh->buf;}该函数使用redis自定义函数的zmalloc或者zcalloc分配内存(以后章节会专门深入这些函数)。3.创建字符串使用了另一个结构(红色标注)struct sdshdr {    int len;    int free;    char buf[];};从上面的函数可以得出创建字符串时,返回的是一个字符数组的指针。该结构和字符串的关系如下:static inline size_t sdslen(const sds s) {    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));    return sh->len;}static inline size_t sdsavail(const sds s) {    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));    return sh->free;} 4. 操作字符串的方法sds sdsnewlen(const void *init, size_t initlen);      //实际真正创建字符串的函数,使用sdsnew创建字符串时该函数被调用。sds sdsnew(const char *init);                             //创建字符串函数sds sdsempty(void);                                        //创建一个长度为0的空字符串size_t sdslen(const sds s);                              //计算字符串的长度 sds sdsdup(const sds s);                                //复制字符串void sdsfree(sds s);                                        //从内存中释放字符串,调用zfree方法size_t sdsavail(const sds s);                                       //计算字符串的可用长度(字符数组中还没有使用的长度)sds sdsgrowzero(sds s, size_t len);                              //增加字符串的内存,并使用''填充sds sdscatlen(sds s, const void *t, size_t len);              //字符串拼接常量实现函数,使用sdscat时调用该函数sds sdscat(sds s, const char *t);                                  //字符串拼接常量函数                sds sdscatsds(sds s, const sds t);                                //字符串拼接字符串函数sds sdscpylen(sds s, const char *t, size_t len);              //字符串复制的执行函数sds sdscpy(sds s, const char *t);                                 //将字符串t复制到s中,若s空间不足,使用sdsgrowzero增加空间sds sdscatvprintf(sds s, const char *fmt, va_list ap);    //类似sdscatprintf,但参数不是变长的,是固定的#ifdef __GNUC__                                                       //如果是linux平台,使用GNU编译器sds sdscatprintf(sds s, const char *fmt, ...)    __attribute__((format(printf, 2, 3)));                      //format (archetype, string-index, first-to-check) 告诉编译器,按照printf, scanf, strftime                                                                             //或strfmon的参数表格式规则对该函数的参数进行检查。“archetype”指定是哪种风格;                                                                             //“string-index”指定传 入函数的第几个参数是格式化字符串;“first-to-check”                                                                            //指定从函数的第几个参数开始按上述规则进行检查。#elsesds sdscatprintf(sds s, const char *fmt, ...);            //向参数s添加字符串,实例:s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).#endifsds sdstrim(sds s, const char *cset);                     //从左边或者后边移除制定的字符串cset,                                                                          * Example:                                                                          * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");                                                                          * s = sdstrim(s,"A. :");                                                                          * printf("%sn", s);                                                                          * Output will be just "Hello World".sds sdsrange(sds s, int start, int end);           //从字符串s中截取制定索引位置的子字符串。void sdsupdatelen(sds s);                            //更新字符串的长度。应用实例: 如果没有sdsupdatelen,则结果为2,有则结果为6                                                                   * s = sdsnew("foobar");                                                                   * s[2] = '';                                                                   * sdsupdatelen(s);                                                                  * printf("%dn", sdslen(s));void sdsclear(sds s);                                     //将字符串的buf设置为''int sdscmp(const sds s1, const sds s2);        //使用memcpy()比较两个字符串,如果s1>s2则结果为1,s1=s2则结果为0,s1<s2则为-1sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);//使用sep分隔符分割s,返回字符串数组void sdsfreesplitres(sds *tokens, int count); //tokens为null时不执行任何操作,否则清空sdssplitlen()函数返回的结果。void sdstolower(sds s);                             //将字符串的每个字符转换为小写形式void sdstoupper(sds s);                           //将字符串的每个字符转换为大写形式sds sdsfromlonglong(long long value);                  //当数字太大的时候,直接将数字转化为字符串.sds sdscatrepr(sds s, const char *p, size_t len);   //添加引用字符串sds *sdssplitargs(const char *line, int *argc);       //反向解析,可以做命令行解析sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);                         /* Modify the string substituting all the occurrences of the set of                        * characters specified in
深入redis内部--字典实现
redis的字典定义和实现在dict.h和dict.c文件中。1.字典结构typedef struct dict {dictType *type; //定义了字典需要的函数void *privdata;dictht ht[2]; //哈希表结构int rehashidx; //下一个需要扩容的字典编号,若rehashidx == -1 则不会进行重新散列。int iterators; //当前正在运行的迭代器数目} dict;其中涉及到数据结构,如下所示: 1.1 字典类型,包含了一系列字典所需要用到的函数typedef struct dictType {unsigned int (*hashFunction)(const void *key); //hash函数void *(*keyDup)(void *privdata, const void *key); //键复制void *(*valDup)(void *privdata, const void *obj); //值复制int (*keyCompare)(void *privdata, const void *key1, const void *key2); //key比较void (*keyDestructor)(void *privdata, void *key); //key析构void (*valDestructor)(void *privdata, void *obj); //value析构} dictType;1.2 哈希表结构,每个字典有两个哈希表。当哈希表扩容时实现散列。typedef struct dictht {dictEntry **table;unsigned long size; //桶的大小,是2的指数unsigned long sizemask; //sizemask=size-1,方便取模(i%sizemask 开放链地址法处理hash冲突)。unsigned long used; //哈希表中的记录数} dictht;1.3 dictEntry为字典的条目,其定义如下:typedef struct dictEntry {void *key; // 键union { //值的共用体void *val;uint64_t u64;int64_t s64;} v;struct dictEntry *next;} dictEntry;2. 字典的遍历--字典遍历器typedef struct dictIterator {dict *d;int table, index, safe;dictEntry *entry, *nextEntry;long long fingerprint; /* unsafe iterator fingerprint for misuse detection */} dictIterator; 注意:当safe=1时,该遍历器是安全的,即字典可以在遍历的同时执行dictAdd, dictFind, 和别的函数。否则遍历器是不安全的,遍历时只能执行dictNext()。   迭代器提供了遍历字典中所有元素的方法,通过dicGetIterator()获得迭代器后,使用dictNext(dictIterator *)获得下一个元素。遍历的过程,先从ht[0]开始,依次从第一个桶table[0]开始遍历桶中的元素,然后遍历table[1],'*** ,table[size],若正在扩容,则会继续遍历ht[1]中的桶。遍历桶中元素时,依次访问链表中的每一个元素。3.宏定义函数#define dictFreeVal(d, entry)if ((d)->type->valDestructor)(d)->type->valDestructor((d)->privdata, (entry)->v.val)#define dictSetVal(d, entry, _val_) do {if ((d)->type->valDup)entry->v.val = (d)->type->valDup((d)->privdata, _val_);elseentry->v.val = (_val_);} while(0)#define dictSetSignedIntegerVal(entry, _val_)do { entry->v.s64 = _val_; } while(0)#define dictSetUnsignedIntegerVal(entry, _val_)do { entry->v.u64 = _val_; } while(0)#define dictFreeKey(d, entry)if ((d)->type->keyDestructor)(d)->type->keyDestructor((d)->privdata, (entry)->key)#define dictSetKey(d, entry, _key_) do {if ((d)->type->keyDup)entry->key = (d)->type->keyDup((d)->privdata, _key_);elseentry->key = (_key_);} while(0)#define dictCompareKeys(d, key1, key2)(((d)->type->keyCompare) ?(d)->type->keyCompare((d)->privdata, key1, key2) :(key1) == (key2))#define dictHashKey(d, key) (d)->type->hashFunction(key)#define dictGetKey(he) ((he)->key)#define dictGetVal(he) ((he)->v.val)#define dictGetSignedIntegerVal(he) ((he)->v.s64)#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)#define dictIsRehashing(ht) ((ht)->rehashidx != -1)4. 字典提供的api,有字典的创建,增加、删除、修改记录,还有迭代器(前面已经介绍)和自动扩容(下面介绍)。dict *dictCreate(dictType *type, void *privDataPtr);int dictExpand(dict *d, unsigned long size);int dictAdd(dict *d, void *key, void *val);dictEntry *dictAddRaw(dict *d, void *key);int dictReplace(dict *d, void *key, void *val);dictEntry *dictReplaceRaw(dict *d, void *key);int dictDelete(dict *d, const void *key);int dictDeleteNoFree(dict *d, const void *key);void dictRelease(dict *d);dictEntry * dictFind(dict *d, const void *key);void *dictFetchValue(dict *d, const void *key);int dictResize(dict *d);dictIterator *dictGetIterator(dict *d);dictIterator *dictGetSafeIterator(dict *d);dictEntry *dictNext(dictIterator *iter);void dictReleaseIterator(dictIterator *iter);dictEntry *dictGetRandomKey(dict *d);void dictPrintStats(dict *d);unsigned int dictGenHashFunction(const void *key, int len);unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);void dictEmpty(dict *d);void dictEnableResize(void);void dictDisableResize(void);int dictRehash(dict *d, int n);int dictRehashMilliseconds(dict *d, int ms);void dictSetHashFunctionSeed(unsigned int initval);unsigned int dictGetHashFunctionSeed(void);5.外部定义变量/* 哈希表类型*/extern dictType dictTypeHeapStringCopyKey;extern dictType dictTypeHeapStrings;extern dictType dictTypeHeapStringCopyKeyValue; 6. 自动扩容    Redis使用标识dict_can_resize来记录字典是否可以扩容,可以使用dictEnableResize()方法和dictDisableResize()来改变此标识。使用dictResize()来扩容,但需要首先判断是否允许扩容及是否正在扩容。若可以扩容,则调用dictExpand()扩容,然后调用dictRehashMilliseconds()启动扩容,并指定扩容过程中记录的copy速度。请看程序:   6.1 dictResize()/* Resize the table to the minimal size that contains all the elements,* but with the invariant of a USED/BUCKETS ratio near to <= 1 */int dictResize(dict *d){int minimal;if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;minimal = d->ht[0].used;if (minimal < DICT_HT_INITIAL_SIZE)minimal = DICT_HT_INITIAL_SIZE;return dictExpand(d, minimal);}   6.2 dictExpand()/* Expand or create the hash table */int dictExpand(dict *d, unsigned long size){dictht n; /* the new hash table */unsigned long realsize = _dictNextPower(size);/* the size is invalid if it is smaller than