1.1.1 CAP原理
- C-Consistent 一致性
- A-Availability 可用性
- P-Partition tolerance 分区容忍性
分布式系统的节点往往都是在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个网络断开的场景的专业词汇叫着网络分区。
在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操作无法同步到另外一个节点,所以数据的一致性将无法满足,因为两个分布式节点的数据不再保持一致。除非我们牺牲了可用性,也就是暂停分布式节点服务,在网络分区发生时,不再 提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。
分区容忍性是我们必须需要实现的。一句话概括CAP原理就是--网络分区发生时,一致性和可用性两难全。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

1.1.2 BASE
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。BASE其实是下面三个术语的缩写:
- 基本可用(Basically Available)
- 软状态(Soft state)
- 最终一致(Eventually consistent)
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法
1.2.1 概述
Redis(REmote DIctionary Server,远程字典服务器),是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器。
1.2.2 特点(其他key-value缓存产品也具有)
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master-slave模式的数据备份
1.2.3 Redis的功能
- 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
- 模拟类似于HttpSession这种需要设置过期时间的功能
- 发布,订阅消息系统
- 定时器,计数器
- ...
1.2.4 为什么要使用Redis?
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
1.2.5 Redis的线程模型
参考地址:https://www.javazhiyin.com/22943.html
redis 内部使用文件事件处理器 ,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
1.2.4 安装和使用
分为Linux和Windows安装,可以在网上进行搜索获得安装方法。
- 启动命令:redis-server /目录/redis.conf(一般为了安全性起见,将Redis安装目录下的redis.conf进行拷贝,并进行修改,将daemonize no改为yes)
- 连接客户端:redis-cli
- 指定端口连接:redis-cli -p 6379
- 单实例关闭:redis-cli shutdown
- 多实例关闭:指定端口关闭 redis-cli -p 6379 shutdown
- 默认16个数据库,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
- Dbsize查看当前数据库key的数量
- Flushdb清空当前数据库
- Flushall清空所有的库
- 统一的密码管理
- Redis索引都是从0开始的
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
string是Redis最基本的类型,一个key对应一个value。
string类型是二进制安全的,意味着Redis的string可以包含任何数据,比如JPG图片或者序列化的对象。
string类型是Redis最基本的数据类型,Redis所有的数据结构都是以唯一的key字符串作为名称,然后通过这个唯一的key来获取相应的value数据。不同类型的数据结构的差异就是在于value的结构不一样。

Redis的字符串是动态字符串,是可以修改的字符串,内部结构类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如上图所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间,需要注意的是string类型的值最大能存储512MB。
字符串结构的使用非常广泛,一个常见的用途就是缓存用户信息。将用户信息结构体使用JSON序列化成字符串,然后将序列化的字符串塞入Redis来缓存。同样,取用户信息会经过一次反序列化的过程。
字符串常用的命令如下:
设置指定 key 的值 2 GET key
获取指定 key 的值。 3 GETRANGE key start end
返回 key 中字符串值的子字符 4 GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 5 GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 6 MGET key1 [key2..]
获取所有(一个或多个)给定 key 的值。 7 SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 8 SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 9 SETNX key value
只有在 key 不存在时设置 key 的值。 10 SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 11 STRLEN key
返回 key 所储存的字符串值的长度。 12 MSET key value [key value ...]
同时设置一个或多个 key-value 对。 13 MSETNX key value [key value ...]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 14 PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 15 INCR key
将 key 中储存的数字值增一。 16 INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。 17 INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。 18 DECR key
将 key 中储存的数字值减一。 19 DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。 20 APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
hash是一个键值对集合。是一个string类型的filed和value的映射表,hash特别适合存储对象。
Redis的hash相当于Java语言中的HashMap,它是无序字段,内部实现结构同Java的HashMap也是一致的,同样是数据+链表的二维结构。第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

不同的是,Redis的hash的值只能是字符串,另外它们的rehash方式不一样,因为Java的HashMap在字典很大的时,rehash是个耗时的操作,需要一次性全部rehash。Redis为了提高性能,不堵塞服务,所以采用了渐进式rehash策略。

渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务中以及hash的子指令中,循序渐进地将旧hash的内容一点点迁移到新的hash结构中。
当hash移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
hash结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash可以对用户结构的每个字段单独存储。这样需要获取用户信息可以进行部分获取。而以整个字符串的形式去保存用户信息只能一次性全部读完,这样就会比较浪费网络流量。
hash也有缺点,hash结构存储消耗要高于单个字符串。
hash常用的命令如下:
删除一个或多个哈希表字段 2 HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。 3 HGET key field
获取存储在哈希表中指定字段的值。 4 HGETALL key
获取在哈希表中指定 key 的所有字段和值 5 HINCRBY key field increment
为哈希表 key 中的指定字段的整数值加上增量 increment 。 6 HINCRBYFLOAT key field increment
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 7 HKEYS key
获取所有哈希表中的字段 8 HLEN key
获取哈希表中字段的数量 9 HMGET key field1 [field2]
获取所有给定字段的值 10 HMSET key field1 value1 [field2 value2 ]
同时将多个 field-value (域-值)对设置到哈希表 key 中。 11 HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。 12 HSETNX key field value
只有在字段 field 不存在时,设置哈希表字段的值。 13 HVALS key
获取哈希表中所有值 14 HSCAN key cursor [MATCH pattern] [COUNT count]
迭代哈希表中的键值对。
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
Redis的列表相当于Java语言里面的LinkedList,注意它是链表而不是数组。这意味着list的插入和删除操作非常快,时间复杂度为o(1),但是索引定位很慢,时间复杂度为o(n)。
如果在深入一点,你会发现Redis底层存储还不是一个简单的LinkedList,而是称之为快速链表quicklist的一个结构。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表,它将所有的元素紧挨在一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。因为普通的列表需要的附加指针空间太大了,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。所以Redis将链表和ziplist结合起来组成了quicklist。也就是将ziplist使用双向指针串起来使用。这样既满足可快速的插入删除性能,又不会出现太大的空间冗余。
当列表弹出最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化字符串塞进Redis列表,另一个线程从列表中轮询数据进行处理。
list常用的命令如下:
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 2 BRPOP key1 [key2 ] timeout
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 3 BRPOPLPUSH source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 4 LINDEX key index
通过索引获取列表中的元素 5 LINSERT key BEFORE|AFTER pivot value
在列表的元素前或者后插入元素 6 LLEN key
获取列表长度 7 LPOP key
移出并获取列表的第一个元素 8 LPUSH key value1 [value2]
将一个或多个值插入到列表头部 9 LPUSHX key value
将一个值插入到已存在的列表头部 10 LRANGE key start stop
获取列表指定范围内的元素 11 LREM key count value
移除列表元素 12 LSET key index value
通过索引设置列表元素的值 13 LTRIM key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 14 RPOP key
移除列表的最后一个元素,返回值为移除的元素。 15 RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回 16 RPUSH key value1 [value2]
在列表中添加一个或多个值 17 RPUSHX key value
为已存在的列表添加值
Redis的Set是string类型的无序集合。它是通过HashTable实现实现的。
Redis的集合相当于Java语言里面的HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字段,字典中的所有一个value值都是一个值NULL。
当集合中的最后一个元素移除之后,数据结构自动删除,内存被回收。
set结构可以用来存储活动中奖的用户的ID,因为有去重功能,可以保证同一个用户不会中奖两次。
set常用的命令如下:
向集合添加一个或多个成员 2 SCARD key
获取集合的成员数 3 SDIFF key1 [key2]
返回给定所有集合的差集 4 SDIFFSTORE destination key1 [key2]
返回给定所有集合的差集并存储在 destination 中 5 SINTER key1 [key2]
返回给定所有集合的交集 6 SINTERSTORE destination key1 [key2]
返回给定所有集合的交集并存储在 destination 中 7 SISMEMBER key member
判断 member 元素是否是集合 key 的成员 8 SMEMBERS key
返回集合中的所有成员 9 SMOVE source destination member
将 member 元素从 source 集合 移动到 destination 集合 10 SPOP key
移除并返回集合中的一个随机元素 11 SRANDMEMBER key [count]
返回集合中一个或多个随机数 12 SREM key member1 [member2]
移除集合中一个或多个成员 13 SUNION key1 [key2]
返回所有给定集合的并集 14 SUNIONSTORE destination key1 [key2]
所有给定集合的并集存储在 destination 集合中 15 SSCAN key cursor [MATCH pattern] [COUNT count]
迭代集合中的元素
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zset类似于Java的SortedSet和HashMap的结合体,一方面它是一个set,保证内部value的唯一性,另一方面它可以给一个value赋予一个score,代表这个value的排序权重。它的内部实现用的是一种叫做跳跃列表的数据结构。
zset中最后一个value被移除后,数据结构自动删除,内存被回收。
zset可以用来存粉丝列表,value值是粉丝用户ID,score是关注时间,可以对粉丝列表按关注时间来排序。还可以存储学生的成绩,value值是学生ID,score是他的考试成绩,可以对成绩按分数进行排序就可以得到他的名次。
zset常用命令如下:
向有序集合添加一个或多个成员,或者更新已存在成员的分数 2 ZCARD key
获取有序集合的成员数 3 ZCOUNT key min max
计算在有序集合中指定区间分数的成员数 4 ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment 5 ZINTERSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 6 ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量 7 ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合指定区间内的成员 8 ZRANGEBYLEX key min max [LIMIT offset count]
通过字典区间返回有序集合的成员 9 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
通过分数返回有序集合指定区间内的成员 10 ZRANK key member
返回有序集合中指定成员的索引 11 ZREM key member [member ...]
移除有序集合中的一个或多个成员 12 ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员 13 ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员 14 ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员 15 ZREVRANGE key start stop [WITHSCORES]
返回有序集中指定区间内的成员,通过索引,分数从高到低 16 ZREVRANGEBYSCORE key max min [WITHSCORES]
返回有序集中指定分数区间内的成员,分数从高到低排序 17 ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 18 ZSCORE key member
返回有序集中,成员的分数值 19 ZUNIONSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的并集,并存储在新的 key 中 20 ZSCAN key cursor [MATCH pattern] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)
zset内部的排序功能是通过跳跃列表数据结构来实现的,它的结构非常特殊,也比较复杂。
因为zset要支持随机的插入和删除,所以不好使用数组来表示。先来看一个普通的链表结构:

我们需要这个链表根据score值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可能保持链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办?
跳跃列表的做法是:最下面一层的所有的元素会串起来,然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里在挑选二级代表,再串起来。最终形成金字塔结构。
跳跃列表之所以跳跃,是因为内部的元素可能身兼数职,可以同时处于不同的层中。
定位插入点时,现在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适位置,将新元素插进去。跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。首先L0层肯定是100%,L1层只有50%的概览,L2层只有25%的概览,L3层只有12.5层概览,一直随机到最顶层。绝大数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概览就会越大。
list/set/hash/zset这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists:如果容器不存在,那就创建一个,再进行操作。
- drop if not elements:如果容器里的元素没有了,那么立即删除元素,释放内存。
Redis所有的数据结构都可以设置过期时间,时间到了,Redis就会自动删除相应的对象。需要注意的是过期是以对象为单位的,比如一个hash结构的过期是整个hash对象的过期。
还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后调用了set方法修改了它,它的过期时间会消失。
在Redis的解压目录下有个很重要的配置文件 redis.conf ,关于Redis的很多功能的配置都在此文件中完成的,一般为了不破坏安装的文件,出厂默认配置最好不要去改,所以可以将此文件复制一下,在复制文件中进行更改。
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit。对大小写不敏感。
Redis只有一个配置文件,如果多个人进行开发维护,那么就需要多个这样的配置we年,这时候多个配置文件就可以在此通过includepath olocal.conf配置进来,而原来的redis.conf配置文件就作为一个总阀。
需要注意的是,如果将此配置写在redis.conf文件的开头,那么后面的配置会覆盖引入文件的配置,如果想以引入文件的配置为主,那么需要将include配置写在redis.conf文件的末尾。
redis3.0的爆炸功能是新增了集群,而redis4.0就是在3.0的基础上新增了许多功能,其中这里的 自定义模块配置就是其中之一。通过这里的 loadmodule 配置将引入自定义模块来新增一些功能。
介绍些比较重要的配置,如下:
(1)bind:绑定redis服务器网卡IP,默认为127.0.0.1,即本地回环地址。访问redis服务只能通过本机的客户端连接,而无法通过远程连接。如果bind选项为空的话,那么会接收所有赖在可用网络接口的连接。
(2)port:指定redis运行的端口,默认是6379。由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口。
(3)timeout:设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接。默认值为0,表示不关闭。
(4)tcp-keepalive:单位是秒,表示将周期的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直堵塞,官方给出的建议是300s,如果设置为0,则不会周期性的检测。
具体配置解析如下:
(1)daemonize:设置为yes表示指定Redis以守护进程的方式启动(后台启动),默认值为no。
(2)pidfile:配置PID文件路径。
(3)loglelevel:定义日志级别。默认值为notice,有以下四种取值:
①debug:记录大量日志信息,适用于测试,开发阶段。
②verbose:较多的日志信息
③notice:适量日志信息,使用于生成环境
④warning:仅有部分重要,关键信息才会被记录
(4)logfile:配置log文件地址,默认打印在命名行终端的窗口上
(5)databases:设置数据库的数量,默认的数据库是DB 0。可以在每个连接上使用selec <dbid>命令选择一个不同的数据库,dbid是一个介于0到databases-1的数值。默认值是16,也就是说默认Redis有16个数据库。
这里的配置主要用来做持久化操作,有关持久化的操作后面会介绍。
(1)seve:这里是用来配置触发Redis持久化条件,也就是什么时候将内存中的数据保存在硬盘中。默认如下配置:
当然只是使用Redis的缓存功能,不需要持久化,可以注释掉所有的save行来停用保存功能。可以直接使用一个空字符串来实现停用。
(2)stop-writes-on-bgsave-errror:默认值是yes。当启用了RDB且最后一次后台数据失败,Redis是否停止接受数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难发生了。如果Redis重启了,那么又可以开始接受数据了。如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制。
(3)rdbcompression :默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis可以采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会非常大。
(4)rdbchecksum:默认值是yes。在存储快照后,还可以让Redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望可获取最大的性能提升,可以关闭此功能。
(5)dbfilename:设置快照的文件名,默认是dump.rdb。
(6)dir:设置快照文件的存放位置,这个配置项一定是一个目录,而不是文件名。使用上面的dbfilename作为保存的文件名。
(1)slave-serve-stale-data:默认值是yes。当一个salve与master失去联系,或者复制正在进行的时候,slave可能会有两种表现:
①如果是yes,slave仍会应答客户端请求,但是返回的数据可能是过时的,或者数据可能是空的在第一次同步的时候。
②如果是no,在你执行除了info he slaveof之外的命令时,slave会返回一个"SYNC with master in progress" 的错误。
(2)slave-read-only:配置Redis的Salve实例是否接受写操作,即Slave是否为只读Redis。默认值为yes。
(3)repl-diskless-sync:主从复制是否使用无硬盘复制功能。默认值为no。
(4)repl-diskless-sync-delay:当启用无硬盘备份,服务器等待一段时间后才会通过套接字向从站传送RDB文件,这个等待时间是可配置的。这一点很重要,因为一旦传送开始,就不可能再为一个新到达的从站服务。从站则要排队等待下一次RDB传送。因此服务器等待一段时间以期更多的从站到达。延迟时间以秒为单位,默认为5秒。要关闭这一功能,只需将它设置为0秒,传送会立即启动。
(5)repl-disable-tcp-nodelay:同步之后是否禁用从站上的TCP_NODELAY,如果你选择yes,redis则会使用较少量的TCP包和带宽向从站发送数据。但这会导致在从站增加一点数据的延时。 Linux内核默认配置情况下最多40毫秒的延时。如果选择no,从站的数据延时不会那么多,但备份需要的带宽相对较多。默认情况下我们将潜在因素优化,但在高负载情况下或者在主从站都跳的情况下,把它切换为yes是个好主意。默认值为no。
(1)requirepass:设置redis连接密码
(2)rename-command:命名重命名,对于一些危险的命令例如:
作为服务端redis-server,常常需要禁用以上命令来使服务器更加安全,禁用的具体做法是:
也可以保留命名,但是不能轻易使用,重命名这个命名即可:
这样重启服务器后则需要使用新命令来执行操作,否则服务器会报错unknown command。
(1)maxclients :设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。 描述符数-32(redis server自身会使用一些),如果设置 maxclients为0 。表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。
(1)maxmemory:设置客户端最大并发连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件。 描述符数-32(redis server自身会使用一些),如果设置 maxclients为0 。表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息。
(2)maxmemory-policy:当使用内存达到最大值时,redis使用的清除策略,有以下选择:
① volatile-lru:利用LRU算法移除设置过过期时间的KEY(LRU:最近使用)。
② allkeys-lru:利用LRU算法移除任何key。
③volatile-random:移除设置过过期时间的随机key。
④allkeys-random:移除随机key。
⑤noeviction:不移除任何key,只是返回一个错误,默认选项。
(3)maxmemory-samples:LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存)。随意你可以选择样本大小进行检,redis默认选择3个样本进行检测,你可以通过maxmemory-samples进行设置样本数。
(1)appendonly:默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机的话,会导致可能会有几分钟的数据丢失,根据save策略来进行持久化,Append Only File 是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入appendonly.aof文件,每次启动的时redis会把这个文件的数据读入内存中,先忽略RDB文件,默认值为no。
(2)appendfilename:aof文件名,默认是“appendonly.aof”。
(3)appendsync:aof持久化策略的配置。
①no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
②always表示每次执行都要fsync,以保证数据同步到磁盘。
③ everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
(4)no-appendfsync-on-rewrite:在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。 设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。
(5)auto-aof-rewrite-percentage:默认值为100。aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
(6)auto-aof-rewrite-min-size:64mb。设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。
(7)aof-load-truncated:aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项,出现这种现象 redis宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes。
(1)lua-time-limit:一个lua脚本执行的最大时间,单位为ms。默认值为5000。
(1)cluser-enabled:集群开关,默认是不开启集群模式。
(2)cluster-config-file:集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。 这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件。请确保与实例运行的系统中配置文件名称不冲突。默认配置为nodes-6379.conf。
(3)cluster-node-timeout :可以配置值为15000。节点互连超时的阀值,集群节点超时毫秒数。
(4)cluster-slave-validity-factor :可以配置值为10。在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了, 导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period 如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移。
(5)cluster-migration-barrier :可以配置值为1。master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。
(6)cluster-require-full-coverage:默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。 设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis提供了两种不同的持久化方法来讲数据存储到硬盘里面。一种方法叫快照(snapshotting),它可以将存在于某一时刻的所有数据都写入硬盘中。另一种方法叫只追加文件(append-only file ,AOF),它会在执行写命令时,将被执行的命令复制到硬盘里面。快照是一次全量备份,AOF日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。AOF日志在长期运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个事件就会无比漫长,所以需要定期进行AOF重写,给AOF日志瘦身。
这两种持久化方法既可以同时使用,又可以单独使用,在某些情况下设置可以两种方法都不使用,具体选择哪种持久化方法需要根据用户的数据以及应用来决定。
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。在创建快照之后,用户可以对快照备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本,还可以将快照留在原地以便重启服务器使用。
4.1.1 快照原理
我们知道Redis是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存结构的逻辑读写。
在服务线上请求的同时,Redis还需要进行内存快照,内存快照要求Redis必须进行文件IO操作,可文件IO操作是不能使用多路复用API。这意味着单线程同时在服务线上的请求还要进行文件IO操作,文件IO操作会严重拖垮服务器请求的性能。还有个重要的问题是为了不阻塞线上的业务,就需要边持久化边响应客户端的请求。持久化的同时,内存数据结构还在改变,比如一个大型的hash字典正在持久化,结果一个请求过来把它给删掉了,还没持久化完,这要怎么办?
Redis使用操作系统的COW(Copy On Write)机制来实现快照持久化。
Redis在持久化时会调用fork函数(这个函数有两个返回值,将子进程的PID返回给父进程,在子进程中返回0)产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,它会父进程共享内存里面的代码段和数据段。子进程做数据持久化,它不会改变现存的内存数据结构,它只是对数据结构进行遍历,然后序列化写到磁盘中。但父进程不一样,它必须持续服务客户端请求,然后对内存结构进行不间断的修改。这个时候就会使用操作系统的COW机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当父进程对其中的一个页面进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制页面进行修改。这时的子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长。但是也不会超过原有数据内存的2倍大小,另外一个Redis实例里的冷数据占得比例往往是比较高的,所以很少会出现所有的页面被分离,被分离的往往只有其中一部分的页面。
子进程因为数据没有变化, 它能看到的内存里的数据再进程产生的一瞬间就凝固了,再也不会改变什么了。这也是为什么Redis的持久化叫快照的原因。接下来子进程就可以安心遍历数据了进行序列化写磁盘。
Redis会单独创建一个子进程来进行持久化,会先将数据写到一个临时文件,待持久化结束后,用这个临时文件去替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对数据恢复的完整性不是很敏感的话,RDB比AOF方式更加高效。
4.1.2 配置位置
查看3.6节的配置文件说明。
4.1.3 如何触发RDB快照
- 配置文件中默认的快照配置。
- 客户端可以通过向Redis发送SAVE命令来创建一个快照,接到Save的Redis服务器在快照创建完毕之前将不再响应任何其他命令。Save命令并不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会执行这个命令。
- 客户端可以通过向Redis发送BGSAVE命令来创建一个快照。对于支持BGSAVE命令的平台来说,Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。
- 当Redis通过SHUTDOWM命令接收到关闭服务器请求时,或者接收到标准的TERM信号时,会执行一个SAVE命令。阻塞所有客户端,不再执行客户端发送的任何请求,并在SAVE命令执行完毕之后关闭服务器。
- 当一个服务器连接另一个服务器,并向对方发送SYNC命令开始一次复制的时候,如果主服务器目前没有在执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器将会执行BGSAVE命令。
- 当执行flushall命令,也会产生dump.rdb文件,但是文件是空的,没有意义。
4.1.4 如何恢复RDB快照
将备份文件dump.rdb文件移动到redis安装目录下并启动服务即可(CONFIG GET dir获取目录)。
4.1.5 如何停止快照
动态停止RDB保存规则的方法:redis-cli config set save ""
4.1.6 快照的优缺点
优点:
- 适合大规模数据恢复
- 对数据完整性和一致性要求不是很高
缺点:
- 在一定间隔时间做一次备份,如果Redis意外的关掉的话,就会丢失最后一次快照后的修改
- 创建子进程的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
4.1.7 小总结

AOF持久化就是以日志的形式来记录每个写操作,将Redis执行过的所有写操作记录下来,只允许追加文件不可以更改文件,Redis启动之初会读取该文件来重构数据。换言之,Redis只要从头到尾重新执行一次AOF文件包含的所有写命令,就可以恢复AOF文件所记录的数据集。
4.2.1 AOF原理
AOF日志存储的是Redis服务器顺序指令序列,AOF日志只记录对内存进行修改的指令记录。
假设AOF日志记录了自Redis实例创建以来的所有修改性指令序列,那么既可以通过对一个空的Redis实例顺序执行所有的指令,也就是重放,来恢复Redis当前实例的内存数据结构的状态。
Redis会在收到客户端修改指令后,先进行参数校验,如果没有问题,就立即将该指令文件存储到AOF日志中,也就是存储到磁盘中,然后再执行指令。这样就是遇到了突发宕机,已经存储到AOF日志的指令进行重放以下就可以恢复到宕机前的状态。
4.2.2 配置位置
参考3.11配置文件说明
4.2.3 AOF正常状态和异常状态下的启动恢复
1.正常状态下:
- 启动:修改3.11配置文件的appendonly为yes
- 将有数据的aof文件复制一份保存到对应的目录(config get dir)
- 恢复:重启Redis重新加载服务
2.异常状态下:
- 启动:修改3.11配置文件的appendonly为yes
- 备份被写坏的aof文件
- 修复:Redis -check-aof--fix进行修复
- 恢复:重启Redis重新加载服务
4.2.4 重写/压缩AOF文件
Redis会不断地将被执行的写命令记录到AOF文件里面,所以随着Redis的不断运行,AOF文件的体积也在不断的增长。在极端情况下,体积不断增大的AOF文件甚至可能会用完硬盘里面的所有可用空间。还有一个问题是,因为Redis在重启之后需要重新执行AOF文件的所有写命令来还原数据集,所以如果AOF文件的体积非常大,那么还原操作的时间就可能会非常长。
为了解决AOF文件体积不断增大的问题用户可以向Redis发送BGREWRITEAOF命令,这个命令会通过移除AOF文件中冗余命令来重写AOF文件,使AOF文件的体积变得尽可能的小。
Redis提供了BGREWRITEAOF指令用于对AOF日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列redis操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到新的AOF日志文件中,追加完毕后就立即代替旧的AOF日志文件,瘦身工作就完成了。
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。
AOF日志是以文件的形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘中的。这就意味这如果机器突然宕机,AOF日志内容还没有来得及刷到磁盘中,这个时候就会出现日志丢失,那么该怎么办?
Linux的glibc提供了fsync(int fd)函数可以将指定we年的内容强制从内核缓存中刷到磁盘。只要Redis进程实时调用fsync函数就可以保证aof日志不丢失。但是fsync是一个磁盘IO操作,运行十分的慢。所以在生产环境的服务器中,Redis通常是每隔1s左右执行一次fsync操作,周期1s是可以配置的。这是在数据安全性和性能之间做了一个折中,在保持高性能的同时,尽可能使得数据少丢失。Redis同样也提供了两种策略,一种是永不fsync-让操作系统来决定合适同步磁盘,很不安全。另一个是来一次指令就fsync一次-非常慢,但是在生产环境中基本不会使用。
重写触发的时机是Redis会记录上次重写AOF时文件的大小,默认配置是当AOF文件大小是上次重写后大小的一倍且文件大于64M时触发。(AOF持久化可以通过设置auto-aof-rewrite-percentage选项和auto-rewrite-min-size选项。上面说的默认配置就是auto-aof-rewrite-percentage 100和auto-rewrite-min-size 64)如果AOF重写执行得太过频繁的话,用户可以考虑将auto-aof-rewrite-percentage选项的值设置到100以上,这样的做法可以让Redis在AOF文件体积变得更大之后才执行重写操作,不过也会让Redis启动还原数据集所需的时间变得更长。
4.2.5 AOF的优缺点
缺点:
- 相同数据集的数据而言aof文件远远大于rdb文件,恢复速度远慢与rdb
- AOF运行的速率要慢与RDB,每秒同步策略效率更好,不同步效率与rdb相同
4.2.6小总结

快照是通过开启子进程方法进行的,它是一个比较耗资源的操作。
1.遍历整个内存,大块写磁盘会加重系统负载
2.AOF的fsync是一个耗时的IO操作,它会降低Redis性能,同时会增加系统IO负担。
所以通常Redis的主节点是不会进行持久化操作,持久化操作主要是在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往是比较充沛。
但是如果出现网络分区,从节点长期连不上主节点,就会出现数据不一致的问题,特别是在网络分区出现的情况下又不小心主节点宕机了,那么数据就会丢失,所以在生产环境中要做好实时监控工作,保证网络畅通或者能快速修复。另外还应该增加一个节点以降低网络分区的概率,只要有一个从节点数据同步正常,数据也就不会轻易丢失。
4.4.1同时开启两种持久化
在这种情况下,当redis重启时会优先载入AOF文件恢复原始数据。因为在这种情况下,AOF文件保存的数据要比RDB文件保存的数据完整。RDB数据不实时,同时使用两者时服务器重启时也只会找AOF文件。
那么可以只用AOF文件吗?建议是不要,RDB更适合备份数据库(AOF在不断变化不好备份)。万一AOF存在bug,有个备份也是好的。
性能建议:因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。
4.4.2 混合持久化
我们知道,在重启Redis时,很少使用rdb来恢复内存状态,因为会丢失大量数据。我们通常使用AOF日志重放,这样在Redis实例很大的情况下,启动需要花费很长的时间。
Redis4.0为了解决这个问题,带来了一个新的持久化选项--混合持久化。将rdb文件的内容和增量的AOF日志文件放在一起。这里的AOF日志不再是全量的日志,而是自持久化开始到持久化结束这段时间发生的增量AOF日志,通常这部分AOF日志很小。
于是在Redis重启的时候,可以先加载rdb的内容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重启效率得到了大幅提升。
Redis的事务可以一次性执行多个命令,本质上是一组命令的集合。一个事务中的所有命令都会被序列化,按顺序串行执行而不会被其他命令插入,不许加塞。

有三个 阶段:
1.开启:以MULTI开启一个事务
2.入队:将多个命令入队到事务中,接收到这些命令并不会立即执行,而是放到等待执行的事务队列中
3.执行:以EXEC命令触发事务执行
所有的指令在exec之前不执行,而是缓存在服务器中的一个事务队列中,服务器一旦受到exec指令,才开始执行整个事务队列,执行完毕后一次性返回所有执行的运行结果。因为Redis的单线程特性,它不用担心自己在执行队列的时候被其他指令打搅,可以保证他们得到“原子性”执行。
Redis为事务提供了一个discard指令,用于丢弃事务缓存队列中的所有指令,在exec之前。
事务的原子性指的是事务要么去全部成功,要么全部失败,那么Redis事务执行时原子性的吗?

上面的例子是事务执行到中间遇到了失败,因为我们不能对一个字符串进行数学运算,事务在遇到指令执行失败后,后面的指令还继续执行。
通过上面的例子,应该明白Redis的事务根本不能算原子性,仅仅满足了事务的隔离性,隔离性中的串行化--当前执行的事务有着不被其他事务打断的权利。
5.3.1 悲观锁/乐观锁/CAS
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
乐观锁的一种典型实现机制(CAS):乐观锁主要就是两个步骤:冲突检测和数据更新。当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程可以更新变量的值,其他的线程都会失败,失败的线程并不会挂起,而是告知这次竞争中失败了,并可以再次尝试。
5.3.2 业务背景
考虑到一个业务背景,Redis中存储了我们信用卡,这张信用卡有初始额度和欠额。我们需要知道的是,这两个操作一定是在一个事务内进行的,操作如下:

但是在这个事务开始之前就请求对这个balance进行改变,那么进行事务成功,这个数额也是具有差异的。
5.3.3 watch的使用
watch会在事务开始之前盯住1个或多个关键变量,当事务执行时,也就是服务器收到exec指令要顺序执行缓存事务队列时,Redis会检查关键变量自watch之后,是否被修改了(包括当前事务所在的客户端)。如果关键变量被人动过了,exec指令就会返回null回复客户端事务执行失败,这个时候客户端一般会选择重试。
下面先来看无加塞篡改,先监控在开启multi:

有加塞篡改:

监控了key,key一旦被修改,后面的事务就失效了。当服务器给exec指令返回一个null回复时,客户端知道了事务执行时失败的,通常客户端(redis-py)都会抛出一个WatchError这种错误,不过也有些语言(Jedis)不会抛出异常,而是通过在exec方法里返回一个null,这样客户端需要检查一个返回结果是否为null来确定事务时候执行失败。
注意事项:
- Redis禁止在multi和exec之间执行watch指令,而必须在multi之前做好盯住关键变量,否则会出错。
- 一旦执行了exec,之前加的监控锁都会被去掉
5.3.4 unwatch

- 单独的隔离操作:事务的所有命令都会序列化,按顺序执行。事务在执行的过程中,不会被其他客户端发送来的请求所打动。
- 没有隔离级别的概念:队列中命令没有提交之前都不会实际的被执行,因为事务提交前任何的命令都不会实际被执行,也就不存在“事务内的查询要看到事务的更新,在事务外查询不能被看到”这个问题。
- 不保证原子性:Redis同一个事务中有一个命令执行失败,其他命令仍会被执行,没有回滚。
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
复制可以让其他服务器拥有一个不断地更新的数据副本,从而使得拥有数据副本的服务器可以用于处理客户端发送的请求。关系数据库通常会使用一个主服务器向多个从服务器发送更新,并使用从服务器来处理所有读请求。Redis也采用了同样的方法来实现自己复制特性,并将其用作扩展性能的一种手段。
在需要扩展读请求的时候,或者需要写入临时数据的时候,用户可以通过设置额外的Redis从服务器来保存数据集的副本。在接收到主服务器发送的数据初始副本之后,客户端每次想主服务器进行写入,从服务器都会实时得到更新。在部署好主服务器之后,客户端就可以向任意一个从服务发送请求了,而不必再像之前一样,总是把每个读请求发送给主服务器(客户端通常会随机选择使用哪个从服务器,从而将负载平均分配到各个从服务器上)。
6.1.1 最终一致
之前了解了CAP原理和BASE原理,在此基础上,Redis的主从复制是异步同步的, 所以分布式的Redis系统并不满足一致性要求,当客户端在Redis的主节点修改了数据后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以Redis满足可用性。
Redis保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态保持一致。如果网络断开了,主从节点的数据将会出现大量不一致,一旦网络恢复,从节点会采用多种策略努力追赶上落后的数据,继续尽力保持和主节点一致。
6.1.2 主从同步
Redis同步支持主从同步和从从同步。salve启动成功连接到master会发送一个sync命令。
1.全量复制
Master接到命令启动后台的存盘进程,同时收集所有接受到用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件给slave,以完成一次完全同步(这个过程会在下面从服务器连接主服务时步骤会详细讲解),而slave服务接收到数据库文件数据后,将其存盘并加载到内存中。但只要是重新连接master,一次完全同步(全量复制)将被自动执行。
2.增量同步:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。
Redis同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存buffer中,然后异步将buffer中的指令同步到从节点,从节点一边执行同步的指令流达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了(偏移量)。
因为内存的buffer是有限的,所以Redis主库不能将所有的指令都记录在内存buffer中。Redis的复制内存buffer是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。如果因为网络状况不好,从节点在短时间无法和主节点进行同步,那么当网络状况恢复时,Redis的主节点那些没有同步的指令在buffer中有可能已经被后续的指令覆盖掉,从节点将无法直接通过指令流来进行同步,这个时候就需要更加复杂的同步机制--快照同步。
3.快照同步
快照同步是一个非常耗费资源的操作,它首先需要在主库进行一次BGSAVE将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送给从节点。从节点将快照文件接收完毕后,立即执行一次全量加载,加载之前首先先将当前内存的数据情况,加载完毕后通知主节点继续进行增量同步(使用配置文件的SLAVEOF配置选项的同步)。
在整个快照同步进行的过程中,主节点的复制buffer还在不停的往前移动,如果快照同步的时间过长或者复制buffer太小,都会导致同步期间的增量指令在复制buffer中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。

所以务必配置一个合适的复制buffer大小参数,避免快照复制的死循环。
当从节点刚刚加入到集群时,它必须先要进行一次快照同步,同步完成后再继续进行增量同步
4.无盘复制
主节点在进行快照同步时,会进行很重的文件IO操作,特别是对于非SSD磁盘进行存储时,快照会对系统的负载产生较大影响。。特别是当系统正在进行AOF的fsync操作时如果发生快照,fsync将会被推迟执行,这就会严重影响主节点的服务效率。
所以从Redis2.8.18版开始支持无盘复制。所谓无盘复制是指主服务器直接通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点会一边遍历内存,一边将序列化的内容发送给从节点,从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。
6.2.1 实验
1.配从库不配主库(拷贝多个redis.conf文件,并修改daemonize为yes,然后修改PID名,端口号,log文件名字和Dumb.rdb名字)。

2.从库配置:slaveof 主库IP 主库端口 (要注意的是每次与主库断开后,都需要重新连接,除非你配置redis.conf文件)
3.Info replication
主机:

从机1:

从机2:

6.2.2 从服务器连接主服务器时的步骤
通过使用上表中的方法,Redis在复制期间也会尽可能处理接收到的命令请求,但是,如果主从服务器之间的网络带宽不足,或者主服务器没有足够的内存来创建子进程和创建记录写命令的缓冲区,那么Redis处理命令请求的效率就会受到影响。因此,尽管这并不是必须的,但在实际中最好还是让主服务只使用50%~65%的内存,留下30%~45%的内存用于执行BGSAVE命令和创建写命令的缓冲区。
设置从服务器的步骤非常简单,用户既可以通过配置选项SLAVEOF host port来将一个Redis服务器设置为从服务器,又可以通过向运行中的Redis服务器发送SLAVEOF命令来将其设置为从服务器。如果用户使用的是SLAVEOF配置选项,那么Redis在启动时首先会载入当前可用的任何快照文件或者AOF文件,然后连接主服务器并执行上表的复制过程。如果用户使用的是SLAVEOF命令,那么Redis会立即尝试连接主服务器,并在连接成功之后,开始执行上表所示的复制过程。
注意事项:
- 从服务器在进行同步时,会清空自己的所有数据。
- Redis不支持主主复制
6.2.3 一主二仆
基本的设置如上图的实验,6370模拟主机,6380和6381模拟从机
1.切入点问题,slave1、slave2是从头开始复制还是从切入点开始复制?
在设置从机前,主机先存放一个数据:
![]()
然后设置从机连接主机,然后获得从机数据:

从上面的实验就可以知道,从机是从头开始复制主机数据。
2 从机是否可以写?set可否?

从机只能读取主机数据,不能写。
3 主机shutdown后情况如何?从机是上位还是原地待命


主机突然宕机,从机原地待命。
4 主机又回来了后,主机新增记录,从机还能否顺利复制?


重启主机,在主机中设置数据,从机仍然可以获得。
5 其中一台从机down后情况如何?依照原有它能跟上大部队吗?

从机宕机,重启后,从机将变成与之间主机无关的服务器。在上面说过,从机于主机断开,都需要重新连接。
6.2.4 薪火相传
上一个slave可以是下一个slave的master,slave同样可以接受其他slave的连接和同步请求,那么slave作为下一个master,可以减轻master的写压力。
从服务器对从服务器进行复制在操作上和从服务器对主服务器进行复制的唯一区别在于,如果从服务器X拥有从服务器Y,那么当从服务器执行从服务器连接主服务器时的步骤4时,它将断开从服务器Y的连接,导致从服务器Y需要重新连接并重新同步。
当读请求的重要性明显高过写请求的重要性,并且读请求的数量远远超于写请求的重要性,并且读请求的数量远远超过一台Redis服务器可以处理的范围时,用户就需要添加新的从服务器来处理请求。随着负载不断上升,主服务器可能会无法快速地更新所有从服务器,或者因为重新连接和重新同步从服务器而导致系统超载。为了缓解这个问题,用户可以创建一个由Redis主从节点组成的中间层来分担主服务器的复制工作,如下图所示。

尽管主从服务器之间并不一定像图中所示的一个树状结构,但是理解这种树状结构对Redis复制是可行的并且是合理的。在之前的持久化节中介绍过,AOF持久化的同步选项可以控制数据丢失的时间长度:通过将每个写命令同步到硬盘里面,用户可以不损失任何数据(除非系统崩溃或者硬盘驱动器损坏),但这种做法会对服务器的性能造成影响;另一方面,如果用户将同步的频率设置为每秒一次,那么服务器的性能将回到正常水平,但故障可能会造成1s的数据丢失。通过同时使用复制和AOF持久化,可以将数据持久化到多台机器上。
为了将数据保存到多台机器上面,用户首先需要为主服务器设置多个从服务器,然后对每个从服务器设置appendonly yes选项和appendfsync everysec选项(如果有需要的话,也可以对主服务进行相同的设置),这样的话,用户就可以让多台服务器以每秒一次的频率将数据同步在硬盘上。但这还只是第一步:因为用户还必须等待主服务器发送的写命令达到从服务器,并在执行后续操作之前,检查数据是否已经被同步到硬盘里面。
6.2.5 反客为主
slaveof no one:使当前数据库停止与其他数据库的同步,转为主数据库。
目前讲的Redis还只是主从方案,最终一致性。如果主节点凌晨2点突发宕机怎么办?等运维手工进行从主切换,然后再通知所有的程序吧地址统统改一遍再重新上线?毫无疑问,这样的人工效率太低了,所以我们必须有一个高可用的方案来抵抗节点故障,当故障发生时可以自动进行从主切换,程序可以不用重启。Redis官方提供了这样的一种方案--Redis Sentinel(哨兵)。

Redis Sentinel集群一般是由3~5个节点组成,这样挂了个别节点集群还可以正常运行。
它负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点转换为主节点。客户端来连接集群时,会首先连接Sentinel,通过sentinel来查询主节点的地址,然后再去连接主节点记性数据交互。当主节点发生故障时,客户端会重新向Sentinel要地址,sentinel会将最新的主节点地址告诉客户端。如此应用程序将无需重启即可自动完成节点切换。比如上图的主节点挂掉后,集群将可能自动调整为下图所示结构。

从这张图中我们可以看到主节点挂掉了,原先的主从复制也断开了,客户端和损坏的主节点也断开了。从节点被提升为新的主节点,其他从节点开始和新的主节点建立复制关系。客户端通过新的主节点继续进行交互。Sentinel会持续监控已经挂掉了主节点,待它恢复后,集群会调整为下面这张图。

此时原先挂掉的主节点现在变成了从节点,从新的主节点那里建立复制关系。
6.3.1 消息丢失
Redis主从采用异步复制,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分的未同步消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。Sentinel无法保证数据完全不丢失,但是也尽可能保证数据少丢失。它有两个选项可以限制主从延迟过大。
第一个参数表示主节点必须有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性。
何为正常复制,何为异常复制?这个就是由第二个参数控制的,它的单位是秒,表示10s没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开,要么一直没有给反馈。
6.3.2 Sentinel的使用
上面举例是Sentinel集群,在下面的基本使用的是单个哨兵。
1.主从复制,还是上面的实验一样,6379为主机,6380和6381为从机。
2.在redis安装目录下(或者是自定义的Redis目录下)新建sentinel.conf问价,这个文件名绝对不能写错
3.配置哨兵,填写内容:sentinel monitor 被监控的数据库名字(自己起名字)127.0.0.1 6379 1(这里的1表示主机挂掉后slave投票看谁称为成为主机)。
4.启动哨兵:Redis-sentinel /目录/sentinel.conf
问题:如果之前的master重启回来,会发生双Master冲突?

从图中可以看出,原Master重启回来会变成现任Master的从机。
复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
进程的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。一般来说,发布和订阅的特点是订阅者(listener)负责订阅频道(channel),发送者(publisher)负责向频道发送二进制字符串消息。每当有消息被发送给定频道时,都会收到消息。


先订阅后发布后才能收到消息:
1 可以一次性订阅多个,SUBSCRIBE c1 c2 c3
2 消息发布,PUBLISH c2 hello-redis
3 订阅多个,通配符*, PSUBSCRIBE new*
4 收取消息, PUBLISH new1 redis2015
- 和Redis的稳定性有关。对于旧版的redis来说,如果一个客户端订阅了某个或某些频道,但它读取消息的速度却不够快的话,那么不断积压的消息就会使得Redis输出缓存区的体积变得越来越大,这可能会导致Redis的速度变慢,甚至直接崩溃。也可能导致Redis被操作系统强制杀死,甚至导致操作系统本身不可用。新版的Redis不会出现这种问题,因为它会自动断开不符合client-output-buffer-limit pubsub配置选项要求的订阅客户端。
- 和数据传输的可靠性有关。任何网络系统在执行操作时都可能会遇上断线情况,而断线产生的连接错误通常会使得网络连接两端中的其中一端进行重新连接。但是,如果客户端在执行订阅操作的过程中断线,那么客户端将丢失在断线期间发送的所有消息,因此依靠频道来接收消息的用户可能对Redis提供的PUBLISH命令和SUBSCRIBE命令的语义感到失望。
操作环境:Eclipse
jar包:
8.2.1 实验架构

8.2.2 测试联通性
输出结果:
![]()
8.2.3 测试key和数据类型
8.2.4 事务
开启一个事务:
加锁:
8.2.5 主从复制
获取Jedis实例要从JedisPool中获取,用完Jedis实例需要返回给JedisPool。如果Jedis在使用过程出错也需要还给JedisPool。
8.3.1 实验
JedisPool的配置,这个类有获取和释放Jedis实例的方法:
测试:
8.3.2 配置
JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的(如上图实验所示)。
(1)maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。
(2)maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
(3)whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作;默认有三种。
- WHEN_EXHAUSTED_FAIL --> 表示无jedis实例时,直接抛出NoSuchElementException;
- WHEN_EXHAUSTED_BLOCK --> 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;
- WHEN_EXHAUSTED_GROW --> 则表示新建一个jedis实例,也就说设置的maxActive无用;
(4)maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;
(5)testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
(6)testOnReturn:return 一个jedis实例给pool时,是否检查连接可用性(ping());
(7)testWhileIdle:如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
(8)timeBetweenEvictionRunsMillis:表示idle object evitor两次扫描之间要sleep的毫秒数;
(9)numTestsPerEvictionRun:表示idle object evitor每次扫描的最多的对象数;
(10)minEvictableIdleTimeMillis:表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
(11)softMinEvictableIdleTimeMillis:在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idle time驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义;
(12)lifo:borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),如果为False,则表示FIFO队列;
参考文章:
Redis详解(二)------ redis的配置文件介绍
《Redis实战》
《Redis深度历险:核心原理和应用实践》
到此这篇redis哨兵模式连接命令(redis哨兵连接数设置)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/haskellbc/29660.html