Redis总结

简介

Redis“Remote Dictionary Server”(远程字典服务)的缩写。Redis官网对Redis的定义是:“Redis is an open source, BSD licensed, advanced key-value cache and store”,可以看出,Redis是一种键值系统,可以用来缓存或存储数据。

简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

为什么使用Readis

高性能:

假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高并发:

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

Redis数据结构

Redis可以存储键与5种不同数据结构类型之间的映射,分别是STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)、ZSET(有序集合)。有一部分命令对于这5种数据结构是通用的,如DEL、TYPE、RENAME等;但也有一部分命令只能对特定的一种或者两种结构使用。

结构类型结构存储的值结构的读写能力
STRING字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数进行自增或自减操作
LIST一个链表,链表上的每个节点都包含了一个字符串从链表两端推入或弹出元素;根据偏移量对链表进行修剪(trim);读取单个或多个元素;根据值查找或移除元素
SET包含字符串的无序收集器,并且被包含的每个字符串互不相同添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素
HASH包含键值对的无序散列表添加、获取、移除单个键值对;获取所有键值对
ZSET字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素

1.String字符串

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。

常用命令: set,get,decr,incr,mget 等。

  • GET:获取存储在给定键中的值
  • SET:设置存储在给定键中的值
  • DEL:删除存储在给定键中的值(该命令可用于所有类型)
$ redis-cli
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379>

SET命令在执行成功时返回OK;DEL命令在执行成功时将会返回被成功删除的值的数量int;GET命令在尝试得到不存在的值时,将会返回一个nil

2.List列表

list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。

Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。

常用命令: lpush,rpush,lpop,rpop,lrange等。

  • LPUSH(RPUSH)    将给定值推入列表的左端(右端)
  • LPOP(RPOP)         从列表的左端(右端)弹出一个值,并返回被弹出的值
  • LINDEX                  获取列表在给定位置上的单个值
  • LRANGE                获取列表在给定范围上的所有值
$ redis-cli
127.0.0.1:6379> rpush list-key item
(integer) 1
127.0.0.1:6379> rpush list-key item2
(integer) 2
127.0.0.1:6379> rpush list-key item
(integer) 3
127.0.0.1:6379> lrange list-key 0 -1
1) "item"
2) "item2"
3) "item"
127.0.0.1:6379> lindex list-key 1
"item2"
127.0.0.1:6379> lpop list-key 
"item"
127.0.0.1:6379> lrange list-key 0 -1
1) "item2"
2) "item"
127.0.0.1:6379>

RPUSH和LPUSH命令在执行成功后会返回当前列表的长度;列表索引范围从0开始,到-1结束,可以取出列表包含的所有元素;使用LINDEX可以从列表中取出单个元素。

4.Set集合

set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。

当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。

比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能,也就是求交集。

常用命令: sadd,spop,smembers,sunion 等。

  • SADD    将给定元素添加到集合
  • SMEMBERS   返回集合包含的所有元素
  • SISMEMBER  检查给定元素是否存在于集合中
  • SREM   如果给定的元素存在于集合中,那么移除这个元素
$ redis-cli
127.0.0.1:6379> sadd set-key item
(integer) 1
127.0.0.1:6379> sadd set-key item2
(integer) 1
127.0.0.1:6379> sadd set-key item3
(integer) 1
127.0.0.1:6379> sadd set-key item
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item2"
2) "item3"
3) "item"
127.0.0.1:6379> sismember set-key item4
(integer) 0
127.0.0.1:6379> sismember set-key item
(integer) 1
127.0.0.1:6379> srem set-key item2
(integer) 1
127.0.0.1:6379> srem set-key item2
(integer) 0
127.0.0.1:6379> smembers set-key
1) "item3"
2) "item"
127.0.0.1:6379>

SADD命令返回1表示成功添加到集合中,返回0表示该元素已存在于集合中;SMEMBERS命令获取到的元素组成的序列;SREM命令会返回被移除元素的数量。

5.Hash哈希

hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。

key=JavaUser293847
value={
  “id”: 1,
  “name”: “SnailClimb”,
  “age”: 22,
  “location”: “Wuhan, Hubei”
}

常用命令: hget,hset,hgetall 等。

  • HSET   在散列里面关联给定的键值对
  • HGET   获取指定散列键的值
  • HGETALL    获取散列包含的所有键值对
  • HDEL   如果给定键存在于散列里面,那么移除这个键
$ redis-cli
127.0.0.1:6379> hset hash-key sub-key1 value1
(integer) 1
127.0.0.1:6379> hset hash-key sub-key2 value2
(integer) 1
127.0.0.1:6379> hset hash-key sub-key1 value1
(integer) 0
127.0.0.1:6379> hgetall hash-key
1) "sub-key1"
2) "value1"
3) "sub-key2"
4) "value2"
127.0.0.1:6379> hdel hash-key sub-key2
(integer) 1
127.0.0.1:6379> hdel hash-key sub-key2
(integer) 0
127.0.0.1:6379> hget hash-key sub-key1
"value1"
127.0.0.1:6379> hgetall hash-key
1) "sub-key1"
2) "value1"
127.0.0.1:6379>

HSET返回一个值来表示给定的键是否已经存在于散列里面;HDEL命令执行后会返回一个值来表示给定的键在移除之前是否存在于散列里面。

5.Sorted Set有序集合

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。

常用命令: zadd,zrange,zrem,zcard等

  • ZADD   将一个带有给定分值的成员添加到有序集合里面
  • ZRANGE 根据元素在有序排列中所处的位置,从有序集合里获取多个元素
  • ZRANGEBYSCORE  获取有序集合在给定分值范围内的所有元素
  • ZREM   如果给定成员存在于有序集合,那么移除这个成员
$ redis-cli
127.0.0.1:6379> zadd zset-key 728 member1
(integer) 1
127.0.0.1:6379> zadd zset-key 982 member0
(integer) 1
127.0.0.1:6379> zadd zset-key 982 member0
(integer) 0
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member1"
2) "728"
3) "member0"
4) "982"
127.0.0.1:6379> zrangebyscore zset-key 0 800 withscores
1) "member1"
2) "728"
127.0.0.1:6379> zrem zset-key member1
(integer) 1
127.0.0.1:6379> zrem zset-key member1
(integer) 0
127.0.0.1:6379> zrange zset-key 0 -1 withscores
1) "member0"
2) "982"
127.0.0.1:6379>

在尝试向有序集合添加元素的时候,ZADD命令会返回新添加元素的数量;ZRANGE命令获取有序集合包含的所有元素,这些元素会按照分值进行排序;ZRANGEBYSCORE命令也可以根据分值来获取有序集合的其中一部分元素;ZREM命令在移除有序集合元素的时候,命令会返回被移除元素的数量。

Redis和数据库的区别

Redis是非关系型数据库NoSQL,MySQL是关系型数据库,是持久化存储的,查询检索的话,会涉及到磁盘IO操作。为了提高性能,可以使用缓存技术,而Redis就是内存数据库,数据存储在内存中(当然也可以进行持久化存储),可以用作缓存数据库。用户首先去Redis查询数据,如果未查询到(即缓存未命中),才去MySQL中查询数据,查询到的数据会更新到缓存数据库中,提供给下次可能进行的查询。提高了数据查询方面的性能。

Redis和memcached都是缓存数据库,可以大大提升高数据量的web访问速度。但是memcached只是提供了简单的数据结构string,而Redis的value可以是string、list、set、hash、sorted set这些,功能更加强大。Redis是内存数据库,数据保存在内存中,访问速度快。MySQL是关系型数据库,功能强大,存储在磁盘中,数据访问速度慢。像memcached,MongoDB,Redis等,都属于Nosql系列。

Web应用中一般采用MySQL+Redis的方式,Web应用每次先访问Redis,如果没有找到数据,才去访问MySQL,如下:

                    âMySQL+Redisâçå¾çæç´¢ç»æ

Redis的关键技术

1、基础事务和回滚机制

Redis事务基本概念

Redis中的事务(transaction)是一组命令的集合,是一个原子操作。事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行。事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次执行这些命令。

Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入。

与传统事务分不同

Redis的事务和传统的关系型数据库不同,在关系型数据库中,用户首先向数据库发送一个BEGIN信号,然后执行各个相互一致的读写操作,最后,用户发送COMMIT来确认之前的操作,或者发送ROLLBACK来放弃之前的操作,回滚到之前的状态。

在Redis中也有简单的方法可以处理一连串的读写操作,使用特殊命令MULTI为开始,然后传入一连串用户的操作,最后以EXEC结束,但这种做法,实际上是在用户执行EXEC之前,客户端缓存保留所有命令,在EXEC之后一次性把所有命令发送到服务器执行,然后等待直到接收所有的命令回复为止,所以用户没办法根据读取到的数据来做决定是否ROLLBACKCOMMIT。但也正是因为它一次性传输所有命令的方式,可以将N条命令造成的N次网络往返,浓缩到1次请求网络往返,所以性能得到了提高。

所以Redis获得的是高并发时的吞吐量和快速响应的高性能,失去的是关系数据库事务提供的回滚(rollback)功能。为此开发者必须在事务执行出错后自己收拾剩下的摊子(将数据库复原回事务执行前的状态等,这里我们一般采取日志记录然后业务补偿的方式来处理,但是一般情况下,在Redis做的操作不应该有这种强一致性要求的需求,我们认为这种需求为不合理的设计)。

Redis事务管理

Redis的事务管理是通过事务队列QUEUE实现的,在Redis中开始事务是 multi 命令,执行事务是 exec 命令,在这两个命令之间的其他命令将被加入到事务队列中,知道exec命令的出现,才会一次性的发送队列里的全部命令去执行,而在执行的时候就不允许再插入任何命令了,这就是Redis的事务机制。

所以在exec命令出现事务执行,队列的所有命令都会执行,即使有出现错误的命令,也会继续执行,并且无法回滚。如下:

redis>MULTI
OK
redis>SET key 1
QUEUED
redis>SADD key 2
QUEUED
redis>SET key 3
QUEUED
redis>EXEC
1) OK
2) (error) ERR Operation against a key holding the wrong kind of value
3) OK
redis>GET key
"3"

运行错误指在命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前Redis是无法发现的,所以在事务里这样的命令是会被Redis接受并执行的。如果事务里的一条命令出现了运行错误,事务里其他的命令依然会继续执行(包括出错命令之后的命令),如上代码可见虽然SADD key 2出现了错误,但是SET key 3依然执行了。

Redis事务的“回滚”:

经过上述分析,我们知道Redis是通过命令队列实现事务的,所以事务一旦执行是不可能再返回到之前的状态的。但是Redis提供了一种伪回滚的命令 discard 命令,discard命令可在exec命令之前执行,使用discard命令回导致从multi命令开始的事务命令全部取消,事务队列清空且事务结束,若此时再执行exec命令则会出错,这样就形成了一种类似回滚的“伪回滚”机制。

但其实说是回滚,倒不如说命令根本就没执行,只是在队列中等待被取消了而已。

Redis事务监控

上文提到,Redis的事务是队列实现的,那么它是如何保证多并发下数据的一致性呢?答案是使用 watch 命令监控事务。

watch命令可以决定事务是执行还是回滚(discard),一般而言,可以在multi命令开始事务之前使用watch命令监控某些键值对,watch命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改watch监控的键值)。

                

watch命令的实现原理:

我们知道,在关系型数据库(如MySQL)中,使用的是悲观锁来保证事务的原子性。即客户端在执行写操作时,悲观地认为一定会产生冲突,所以直接对该数据进行加锁,以保证自身一定能执行成功。

悲观锁的缺点是当多个客户端修改同一条数据时会进行锁竞争和释放,没有获得额锁的客户端需要等待锁释放,如果排在前面的客户端执行非常慢,则会使后续客户端的等待时间变长。

而在Readis中,watch监控采取的是乐观锁(CAS)的机制实现的,客户端在每次修改操作时,乐观的以为不会出现冲突,执行之后,如果产生了冲突,则再次重新执行,直到成功为止。

乐观锁的缺点是当多个客户端修改同一条数据时,可能会导致多个客户端不断失败并重试,耗费大量时间。

从上述watch命令的执行描述中野可以看出,它是很适合乐观锁的比较与交换CAS机制的,当一组处于监控状态下的键值对被改变了,那么事务就回滚,不再执行,因为值的改变说明已有别的线程执行过了。

既然是使用的CAS机制,就避免不了"ABA"问题的产生,这是CAS机制的一个设计缺陷,常用的方法是对缓存的持久对象(POJO)加入字段 version版本值,每一次操作该对象都给 version值加1,这样可保证CAS能及时发现对象是否被操作过,关于乐观锁CAS和悲观锁的更多知识,可见这篇文章

需要注意的是,由于WATCH命令的作用只是当被监控的键值被修改后阻止之后一个事务的执行,而不能保证其他客户端不修改这一键值,所以在一般的情况下我们需要在EXEC执行失败后重新执行整个函数。

执行EXEC命令后会取消对所有键的监控,如果不想执行事务中的命令也可以使用UNWATCH命令来取消监控。

2、Redis常用技术

Redis超时命令

作为一个缓存数据库,Redis的内存是很有限的,也是很珍贵的,因此我们必须合理的利用其内存空间;我们知道,缓存数据库的目的是存储常用的数据以便更快的读取,但是如何判断哪些是常用的数据呢?

Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。

我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。Redis执行删除的方式主要有3种:定时删除惰性删除定期删除。

定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达时,由事件处理器自动执行键的删除操作。

定时删除策略对内存是最友好的: 因为它保证过期键会在第一时间被删除, 过期键所消耗的内存会立即被释放。

这种策略的缺点是, 它对 CPU 时间是最不友好的: 因为删除操作可能会占用大量的 CPU 时间 —— 在内存不紧张、但是 CPU 时间非常紧张的时候 (比如说,进行交集计算或排序的时候), 将 CPU 时间花在删除那些和当前任务无关的过期键上, 这种做法毫无疑问会是低效的。

惰性删除 :放任键过期不管,但是在每次从 dict 字典中取出键值时,要检查键是否过期,如果过期的话,就删除它,并返回空;如果没过期,就返回键值。

惰性删除对 CPU 时间来说是最友好的: 它只会在取出键时进行检查, 这可以保证删除操作只会在非做不可的情况下进行 —— 并且删除的目标仅限于当前处理的键, 这个策略不会在删除其他无关的过期键上花费任何 CPU 时间。

惰性删除的缺点是, 它对内存是最不友好的: 如果一个键已经过期, 而这个键又仍然保留在数据库中, 那么 dict 字典和 expires 字典都需要继续保存这个键的信息, 只要这个过期键不被删除, 它占用的内存就不会被释放。

定期删除:每隔一段时间,对 expires 字典进行检查,删除里面的过期键。

定期删除是这两种策略的一种折中

  • 它每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对CPU时间的影响。
  • 另一方面,通过定期删除过期键,它有效地减少了因惰性删除而带来的内存浪费。

Redis的这三种删除方式虽然可以在一定程度上避免内存的浪费和对CPU的影响,但仍然没有彻底的解决突发性的高并发问题;试想一下,如果很短的时间内,Redis的缓存中有巨大量数据新增(比如微博热搜、淘宝双11等),而且这些数据并没有超过限制的时间,超时命令机制无法删除它们,最终就会导致Redis内存块耗尽。解决这个问题,Redis采用的是内存淘汰机制

Redis内存淘汰 (内存回收) 机制

内存淘汰的过程

我们可以通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能,我们可以通过这个值来设置内存淘汰算法

  1. 客户端发起了需要申请更多内存的命令(如set)。
  2. Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略(就是配置的maxmemory这个值)来淘汰内存(key),从而换取一定的内存。
  3. 如果上面都没问题,则这个命令执行成功。

maxmemory为0的时候表示我们对Redis的内存使用没有限制。

内存淘汰策略

Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰(仅仅是超时的)
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰(剩余存活时间最短)
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(不仅仅是超时的
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. noeviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,读正常进行,新写入操作会报错。

这些策略适用的场景:

  • allkeys-lru:如果我们的应用对缓存的访问符合幂律分布(也就是存在相对热点数据),或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。
  • allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。
  • volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction。

Redis持久化机制

很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。

Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)

快照(RDB)持久化:

1、配置

为了使用持久化的功能,我们需要先知道该如何开启持久化的功能,即如何进行RDB的持久化配置。

# 时间策略
save 900 1
save 300 10
save 60 10000

# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /home/work/app/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes

# 是否压缩
rdbcompression yes

# 导入时是否检查
rdbchecksum yes

配置其实非常简单,这里说一下持久化的时间策略具体是什么意思。

  • save 900 1    表示900s内如果有1条是写入命令,就触发产生一次快照,可以理解为就进行一次备份

  • save 300 10  表示300s内有10条写入,就产生快照

下面的类似,那么为什么需要配置这么多条规则呢?因为Redis每个时段的读写请求肯定不是均衡的,为了平衡性能与数据安全,我们可以自由定制什么情况下触发备份。所以这里就是根据自身Redis写入情况来进行合理配置。

stop-writes-on-bgsave-error yes 这个配置也是非常重要的一项配置,这是当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。如果自己的业务有完善的监控系统,可以禁止此项配置, 否则请开启。

关于压缩的配置 rdbcompression yes ,建议没有必要开启,毕竟Redis本身就属于CPU密集型服务器,再开启压缩会带来更多的CPU消耗,相比硬盘成本,CPU更值钱。

当然如果你想要禁用RDB配置,也是非常容易的,只需要在save的最后一行写上:save ""

2、原理

在Redis中RDB持久化的触发分为两种:自己手动触发Redis定时触发

针对RDB方式的持久化,手动触发可以使用:

  • save:会阻塞当前Redis服务器,直到持久化完成,线上应该禁止使用。
  • bgsave:该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候

而自动触发的场景主要是有以下几点:

  • 根据我们的 save m n 配置规则自动触发;
  • 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave
  • 执行 debug reload 时;
  • 执行 shutdown时,如果没有开启AOF,也会触发。

由于 save 基本不会被使用到,我们重点看看 bgsave 这个命令是如何完成RDB的持久化的。

这里注意的是 fork 操作会阻塞,导致Redis读写性能下降。我们可以控制单个Redis实例的最大内存,来尽可能降低Redis在fork时的事件消耗。以及上面提到的自动触发的频率减少fork次数,或者使用手动触发,根据自己的机制来完成持久化。

只追加文件(AOF)持久化:

1、配置

# 是否开启aof
appendonly yes

# 文件名称
appendfilename "appendonly.aof"

# 同步方式
appendfsync everysec

# aof重写期间是否同步
no-appendfsync-on-rewrite no

# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载aof时如果有错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes

同步方式 appendfsync everysec 它其实有三种模式:

  • always:把每个写命令都立即同步到aof,很慢,但是很安全
  • everysec:每秒同步一次,是折中方案
  • no:redis:不处理交给OS来处理,非常快,但是也最不安全

一般情况下都采用 everysec 配置,这样可以兼顾速度与安全,最多损失1s的数据。

aof-load-truncated yes 如果该配置启用,在加载时发现aof尾部不正确是,会向客户端写入一个log,但是会继续执行,如果设置为 no ,发现错误就会停止,必须修复后才能重新加载。

2、原理

AOF的整个流程大体来看可以分为两步,一步是命令的实时写入(如果是 appendfsync everysec 配置,会有1s损耗),第二步是对aof文件的重写。

对于增量追加到文件这一步主要的流程是:命令写入 --->  追加到aof_buf  ---> 同步到aof磁盘。那么这里为什么要先写入buf在同步到磁盘呢?如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能。

aof重写是为了减少aof文件的大小,可以手动或者自动触发,关于自动触发的规则请看上面配置部分。fork的操作也是发生在重写这一步,也是这里会对主进程产生阻塞。

手动触发: bgrewriteaof自动触发 就是根据配置规则来触发,当然自动触发的整体时间还跟Redis的定时任务频率有关系。

对于上图的解释:

  • 在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;因此它依然会写入旧的AOF file中,如果重写失败,能够保证数据不丢失。
  • 为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个buf,防止新写的file丢失数据。
  • 重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。
  • AOF文件直接采用的文本协议,主要是兼容性好、追加方便、可读性高可认为修改修复。

从持久化中恢复数据

其实想要从这些文件中恢复数据,只需要重新启动Redis即可。

启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB。优先加载AOF是因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。

Redis主从同步原理

和MySQL主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,另外也是为了保证HA。Redis主从复制可以根据是否是全量分为全量同步增量同步

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下: 
  1)从服务器连接主服务器,发送SYNC命令; 
  2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; 
  3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; 
  4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; 
  5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; 
  6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; 

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis的同步策略是:主从刚刚连接的时候,进行全量同步;全量同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步

Redis的哨兵机制

Redis-Sentinel也就是哨兵机制,是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。

它的主要功能有以下几点

  • 不时地监控redis是否按照预期良好地运行;
  • 如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);
  • 能够进行自动切换(进行主备切换)。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。

Sentinel支持集群,很显然,只使用单个sentinel进程来监控redis集群是不可靠的,当sentinel进程宕掉后(sentinel本身也有单点问题,single-point-of-failure)整个集群系统将无法按照预期的方式运行。所以有必要将sentinel集群,这样有几个好处:

  • 即使有一些sentinel进程宕掉了,依然可以进行redis集群的主备切换;
  • 如果只有一个sentinel进程,如果这个进程运行出错,或者是网络堵塞,那么将无法实现redis集群的主备切换(单点问题);
  • 如果有多个sentinel,redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。

看到这,我们会想,Redis提供的哨兵机制Zookeeper的应用场景类似,利用ZK也可以监控服务器运行状态,并在有宕机情况发生时完成master选举,从而进行故障转移。哨兵机制的运行过程:

 如上图所示,Redis为了保证高可用性,采用Master-slave形式部署,采用AOF或RDB进行持久化,采用集群culster机制来分布式存储。多个哨兵,不仅同时监控主从数据库,而且哨兵之间互为监控,并且哨兵不会存在单点问题。

哨兵会监控主从Redis服务器是否正常,当发现有问题时,会进行通知,并进行故障转移。当监控到master节点宕机后,会进行maser选举出新的master节点,并进行主从切换,并将其他节点作为新选举出的master节点的slave。哨兵Sentinel用到的分布式一致性算法是Raft分布式算法

 

 

参考文章:

 《Redis设计与实现》以及其官方网站:http://redisbook.com/index.html

  https://snailclimb.gitee.io/javaguide/#/database/Redis

  https://blog.csdn.net/qtyl1988/article/details/39553339

  https://blog.csdn.net/u013679744/article/details/79203933

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值