Redis之数据类型和持久化及集群模式

时间:2024-01-28 18:53:06

数据类型

  Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

String(字符串)

  String是redis中最基本的数据类型,一个key对应一个value,它的类型是二进制安全的。意思是 redis 的 string 可以包含任何数据,比如数字,字符串,jpg图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。使用场景

  缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力;

  计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源;

  session:常见方案spring session + redis实现session共享。

  使用命令:get 、 set 、 del 、 incr、 decr 等。如下图:

 

 Hash(哈希)

  Hash,是一个 string 类型的 field 和 value 的映射表,相当于一个Mapmap,即一个键值(key=>value)对集合,指value值本身又是一种键值对结构。hash 特别适合用于存储对象,如 value={{field1,value1},......fieldN,valueN}}。每一个Hash可以存储4294967295个键值对。使用场景

  缓存: 能直观的、相比string更节省空间的维护缓存对象信息,如用户信息,视频信息等,可以存储、读取、修改对象的属性。

  hash的命令都是  h   开头的 hget  、hset 、  hdel 等。如下图:

List(列表)

  List(链表),List 说白了就是链表(redis 使用双端链表实现的 List),可以通过下标取出对应的value值,可以添加一个元素到列表的头部(左边)或者尾部(右边)。Redis的list是由多个字符串值组成的有序可重复(value可以重复)的序列,由于是链表结构,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。list最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。使用场景:lpush+lpop=Stack(栈);lpush+rpop=Queue(队列);Ipush+ltrim=Capped Collection(有限集合);Ipush+brpop=Message Queue(消息队列)。如:

  timeline:例如微博的时间轴,有人发布微博,用lpush加入时间轴,展示新的列表信息。

  list的命令:lpush、rpop、ltrim等。如下图:

 

 Set(集合)

  Set(集合),集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合类型也是用来保存多个字符串的元素,但和列表不同的是set集合中不允许有重复的元素,集合中的元素是无序的,不能通过索引下标获取元素,支持集合间的操作。集合最大的优势是可以从多个集合取交集、并集、差集。使用场景

  标签(tag):给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人,好友推荐的时候根据tag求交集,大于某个threshold(临界值的)就可以推荐;

  共同好友:利用交集求共同好友;

  不可重复性:利用唯一性,可以统计点赞,点踩,收藏,访问网站的所有独立IP等。

  命令都是以s开头的  sset 、srem、scard、smembers、sismember。如下图:

 

 zset(有序集合)

  zset(有序集合),Redis的zset 和 set集合有着必然的联系, 也是string类型元素的集合,且不允许重复的成员。区别是有序集合中的元素是可以排序的,它给每个元素设置一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合中的元素不可以重复,但是score分数可以重复。使用场景

  排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

  消息队列:带权重的消息队列。

  有序集合的命令都是 以  z  开头    zadd 、 zrange、 zscore。如下图:

 

   最后,还有对key通用的操作命令,对所有的数据类型都可以使用,如下图:

 持久化

  Redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,容易发生丢失,而且redis重启后数据就全丢失了,幸好Redis还为我们提供了持久化的机制,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB(Redis DataBase),原理是将Reids在内存中的数据库记录定时dump到磁盘文件中,另外一种是AOF(Append Only File)持久化,原理是将Reids的操作日志以追加的方式写入文件。

  Redis的数据可以保存在磁盘上,这个流程可以分为五个过程:

  (1)客户端向服务端发送写操作(数据在客户端的内存中)。

  (2)数据库服务端接收到写请求的数据(数据在服务端的内存中)。

  (3)服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。

  (4)操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。

  (5)磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。

  这5个过程是在理想条件下一个正常的保存流程,但是在大多数情况下,我们的机器都会有各种各样的故障,这里划分了两种情况:1、Redis数据库发生故障,只要在上面的第三步执行完毕,那么就可以持久化保存,剩下的两步由操作系统替我们完成/2、操作系统发生故障,必须上面5步都完成才可以。

  在这里只考虑了保存的过程可能发生的故障,其实保存的数据也有可能发生损坏,需要一定的恢复机制,不过在这里就不再延伸了。接下来我们主要谈谈Redis如何来实现上面5个保存磁盘的步骤。

RDB持久化

  RDB其实就是把数据以快照的形式保存在磁盘上。快照你可以理解成把当前时刻的数据拍成一张照片保存下来。RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,也是默认的持久化方式,这种方式就是Redis运行过程中,达到了一定时间点或者其他限制(比如手动刷写),启动一个子进程fork整个主进程,在子进程中将redis内存中数据以快照的方式写入到二进制的临时文件中,等这个持久化过程结束后,将这个临时文件替换上次持久化的文件,默认的文件名为dump.rdb,其实就是冷备份。既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,来实现这个过程。对于RDB来说,提供了三种机制:save、bgsave、自动化。

save触发方式

  该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止,也就是说IO类型是同步串行的。执行完成的时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

bgsave触发方式

  执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

自动触发

  自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置:

名称 内容
save 用来配置快照触发的条件,也就是什么时候将内存中的数据保存到硬盘,比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。save ""表示关闭RDB。
stop-writes-on-bgsave-error 当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。默认值为yes,如果最后一次后台保存数据失败了,此时可以让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了;如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制
rdbcompression 对于存储到磁盘中的快照,可以设置是否进行压缩存储。默认值是yes,这需要消耗额外的CPU资源
rdbchecksum 在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。默认值是yes。
dbfilename 存储的RDB数据文件名称,默认为dump.rdb
dir 设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。

  我们可以修改这些配置来实现我们想要的效果。

优劣势

  优势

  (1)RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复,适合大规模的数据恢复且对数据完整性和一致性要求不高。

  (2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

  (3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快,启动效率会更高。。

  劣势

  (1)RDB快照是一次全量备份,当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。

  (2)fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑;同时子进程写入的时候主进程不能进行任何的IO操作。

  (3)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF持久化

  RDB的持久化是全量备份,每次备份都比较耗时,有时候我们可以用一种更加高效的方式:AOF。AOF工作机制就是将每次写入操作写入日志,redis会将每一个收到的写命令都通过write函数追加到文件中,通俗的理解就是日志记录,在每次恢复的时候重新执行一次日志文件中的写入操作就可以了。

  AOF的方式虽然高效,但同时带来了另一个问题:持久化文件会变的越来越大。为了压缩aof的持久化文件,redis提供了bgrewriteaof命令,将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

  AOF的配置参数如下:

名称 内容
appendfsync 同步的机制:Always,表示实时同步,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好;everysec,表示每秒同步,异步操作,如果一秒内宕机,有数据丢失;no,表示不同步
no-appendfsync-on-rewrite 重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
auto-aof-rewrite-min-size 设置重写的基准值
auto-aof-rewrite-percentage 设置重写的基准值百分比
优劣势 

  优势

  (1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。

  (2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。

  (3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。

  (4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。

  劣势

  (1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大,恢复速度慢于RDB。

  (2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的。

  (3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。

  Redis的持久化机制就是这两种,二者选择的标准,就是看我们的系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些最终一致( eventually consistent)的意思了。通常我们会结合使用,毕竟Redis是可集群部署的。

 

 集群

  Redis有三种集群方式:主从复制,哨兵模式和集群模式。

主从复制模式

  通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上

  在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。部署的时候,主数据库不用配置,从redis的conf文件中可以加载从数据库的信息,也可以在启动时,使用 redis-server --port 6380 --slaveof 127.0.0.1 6379,从数据库一般是只读,可以改为可写,但写入的数据很容易被主同步没,所以还是只读就可以。如果硬盘效率低将会影响复制性能,2.8之后可以设置无硬盘复制,repl-diskless-sync yes。

主从复制的原理

  (1)从服务器启动时连接主服务器,并发送SYNC命令; 

  (2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; 

  (3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; 

  (4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; 

  (5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; 

  (6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;(从服务器初始化完成)

  (7)主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。

  (8)当出现断开重连后,2.8之后的版本会将断线期间的命令传给从数据库,也就是增量复制。
  主从复制是乐观复制,当客户端发送写执行给主,主执行完立即将结果返回客户端,并异步的把命令发送给从,从而不影响性能。也可以设置至少同步给N个从服务器后,主服务器才可以写。
 
优缺点
  优点
  (1)主从复制,主机会自动将数据同步到从机,可以进行读写分离;
  (2)为了分担Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成;
  (3)Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力;
  (4)Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求;
  (5)Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。
  缺点:
  (1)Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复;
  (2)主服务器宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性;
  (3)Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

哨兵模式

  当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个:

  (1)监控主服务器和从服务器是否正常运行。 

  (2)主服务器出现故障时自动将从服务器转换为主服务器。

  配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。如下配置,就是1主2从1哨兵,
redis-server --port 6379 
redis-server --port 6380 --slaveof 192.168.0.167 6379 
redis-server --port 6381 --slaveof 192.168.0.167 6379
#哨兵配置文件 sentinel.conf 
 sentinel monitor mymaster 192.168.0.167 6379  1
#这里的1代表1个哨兵

  这样哨兵就能监控主6379和从6380、6381,一旦6379挂掉,哨兵就会在2个从中选择一个作为主,根据优先级选,如果一样就选个id小的,当6379再起来就作为从存在。

哨兵模式的原理
   

  (1)每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令;

  (2)如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN);

  (3)如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态;

  (4)当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN);

  (5)在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令;

  (6)当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次;

  (7)若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

优缺点
  优点
  (1)哨兵模式是基于主从模式的,所有主从复制的优点,哨兵模式都具有;
  (2)主从服务可以自动切换,系统更健壮,可用性更高。
  缺点:
  (1)Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

 

Redis-Cluster集群

  Redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。使用集群,只需要将每个数据库节点的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行,使用集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选。每个实例的配置参数如下:

cluster-enabled yes  --开启集群
cluster-config-file nodes-6382.conf --集群配置文件名,每个实例配置的要不同,redis会根据文件名自动新建

集群模式的原理

  Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、1、2、3……16382、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster中的每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在Redis Cluster中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。

  Redis集群的运行,redis安装目录的src执行命令:./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385。

  Redis-Cluster采用无中心结构,它的特点如下:

  (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;

  (2)节点的fail是通过集群中超过半数的节点检测失效时才生效;

  (3)客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

   在redis的每一个节点上,都有这么两个东西,一个是插槽(slot)可以理解为是一个可以存储两个数值的一个变量这个变量的取值范围是:0-16383。还有一个就是cluster我个人把这个cluster理解为是一个集群管理的插件。当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

   为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。