redis

什么是redis?

Redis是一个内存中数据结构存储,用作数据库,缓存和消息代理

redis的特点

异常快 支持丰富的数据类型 操作都是原子性操作 多实用工具

redis的五个数据结构

String

list 双向链表

hash 数组+链表

set

zset

redis的持久化机制

RDB 快照(snapshotting)

介绍:**快照持久化是Redis默认采用的持久化方式,**Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。

创建快照的办法有如下几种:

  • BGSAVE命令: 客户端向Redis发送 BGSAVE命令 来创建一个快照。对于支持BGSAVE命令的平台来说(基本上所有平台支持,除了Windows平台),Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。

  • SAVE命令: 客户端还可以向Redis发送 SAVE命令 来创建一个快照,接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。

  • save选项: 如果用户设置了save选项(一般会默认设置),比如 save 60 10000,那么从Redis最近一次创建快照之后开始算起,当“60秒之内有10000次写入”这个条件被满足时,Redis就会自动触发BGSAVE命令。

  • SHUTDOWN命令: 当Redis通过SHUTDOWN命令接收到关闭服务器的请求时,或者接收到标准TERM信号时,会执行一个SAVE命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在SAVE命令执行完毕之后关闭服务器。

  • 一个Redis服务器连接到另一个Redis服务器: 当一个Redis服务器连接到另一个Redis服务器,并向对方发送SYNC命令来开始一次复制操作的时候,如果主服务器目前没有执行BGSAVE操作,或者主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE命令

优点: (1)RDB文件是紧凑的二进制文件,比较适合做冷备,全量复制的场景。RDB做会生成多个文件,每个文件都代表了某一个时刻的Redis完整的数据快照;RDB这种多个数据文件的方式,非常适合做冷备,因为大量的一个个的文件,可以每隔一定的时间,复制出来;可以将这种完整的数据文件发送到一些远程的云服务、分布式存储上进行安全的存储,以预定好的备份策略来定期备份Redis中的数据; AOF也可以做冷备,只有一个文件,但是你可以写个脚本,每隔一定时间,去copy一份这个文件出来,相对比较麻烦,不推荐; 问题:RDB做冷备,优势在哪儿呢? 由Redis去控制固定时长生成快照文件的事情,比较方便;AOF,还需要自己写一些脚本去做这个事情,各种定时;RDB数据做冷备,在最坏的情况下,提供数据恢复的时候,速度比AOF快; (2)相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复Redis进程,更加快速; 问题:为什么恢复的时候RDB比AOF快? AOF,存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的; RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可; RDB的时候,Redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可; (3)RDB对Redis对外提供的读写服务,影响非常小,可以让Redis保持高性能,因为Redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可;RDB每次写,都是直接写Redis内存,只是在一定的时候,才会将数据写入磁盘中;AOF,每次都是要写文件的,虽然可以快速写入os cache中,但是还是有一定的时间开销的,速度肯定比RDB略慢一些; (4)RDB使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了Redis的高性能 ; ** 缺点 (1)如果想要在Redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。 一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦Redis进程宕机,那么会丢失最近5分钟的数据; 这个问题,也是RDB最大的缺点,就是不适合做第一优先的恢复方案,如果你依赖RDB做第一优先恢复方案,会导致数据丢失的比较多; (2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒; 一般不要让RDB的间隔太长,否则每次生成的RDB文件太大了,对Redis本身的性能可能会有影响的; (3)RDB无法实现实时或者秒级持久化 RDB是间隔一段时间进行持久化,如果持久化之间Redis发生故障,会发生数据丢失。

AOF**(append-only file)**

介绍开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。

在Redis的配置文件中存在三种同步方式,它们分别是:


appendfsync always     #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no      #让操作系统决定何时进行同步

1、appendfsync always 可以实现将数据丢失减到最少,不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令,十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项,因为这会明显降低固态硬盘的使用寿命。

**2、**为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 ** 3、appendfsync no 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。

优点 ** (1)AOF可以更好的保护数据不丢失。 一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据,Redis进程挂了,最多丢掉1秒钟的数据; (2)AOF日志文件以append-only模式写入,写入性能比较高 AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修; (3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。 因为在rewrite log的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。 (4)适合做灾难性的误删除紧急恢复 AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据; ** 缺点 ** (1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大,恢复速度慢; 我们可以简单的认为AOF就是日志文件,此文件只会记录“变更操作”(例如:set/del等),如果server中持续的大量变更操作,将会导致AOF文件非常的庞大,意味着server失效后,数据恢复的过程将会很长; 事实上,一条数据经过多次变更,将会产生多条AOF记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为AOF持久化模式还伴生了“AOF rewrite”。 (2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的; 如果你要保证一条数据都不丢,也是可以的,AOF的fsync设置成没写入一条数据,fsync一次,那就完蛋了,Redis的QPS大降; (3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来 所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

redis的六种淘汰策略

当前版本,Redis3.0支持的策略包括: noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数例外,如del) allkeys-lru:所有key通用,优先删除最近最少使用的(less recently used,LRU)key。 allkeys-random:所有key通用,随机删除一部分key. volatile-random:只限于设置了expire的部分,删除一部分expire的key. volatile-ttl:只限于设置了expire的部分,优先删除剩余时间(time to live,TTL)短的key. 如果没有设置expire的key,不满足先决条件(prerequisites),那么volatile-lru,volatile-random和volatile-ttl策略的行为, 和noeviction(不删除策略)基本上一致。

淘汰策略的使用场景

如果分为热数据和冷数据,推荐使用allkeys-lru策略,也就是,其中一部分key经常被读写,如果不确定具体的业务特征, 那么allkeys-lru是一个很好的选择。 如果需要循环读写所有的key,或者各个key的访问频率差不多,可以使用allkeys-random策略,即读写所有的元素的概率差不多。 假如要让Redis根据ttl来筛选需要删除的key,请使用volatile-ttl策略。 volatile-lru和volatile-random策略主要应用场景是:既有缓存,又有持久key的实例中,一般来说,像这类场景,应该使用两个单独的Redis实例。 值得一提的是,设置expire会消耗额外的内存,所以使用allkeys-lru策略,可以更高效的利用内存,因为这样就可以不再设置过期时间了。

驱逐的内部实现

驱逐过程可以这样理解: 1、客户端执行一个命令,导致Redis中的数据增加,占用更多的内存。 2、Redis检查内存使用量,如果超出maxmemory限制,根据策略清除部分key. 3、继续执行下一条命令,以此类推。 在这个过程中,内存使用量会不断的达到limit值,然后超过,然后删除部分key,使用量又下降到limit值之下。 如果某个命令导致大量内存使用(比如通过新key保存一个很大的set),在一段时间内,可能内存的使用量会明显超过maxmemory限制。 近似LRU算法 Redis使用的并不是完全LRU算法,自动驱逐的key,并不一定是最满足LRU特征的那个,而是通过近似LRU算法, 抽取少量的key样本,然后删除其中访问时间最古老的那个key. 驱逐算法,从Redis3.0开始得到了巨大的优化,使用pool(池子)来作为候选, 这大大提升了算法的效率,也更接近真实的LRU算法。

redis集群

主从复制

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 主节点(master),后者称为 从节点(slave)。且数据的复制是 单向 的,只能由主节点到从节点。Redis 主从复制支持 主从同步 和 从从同步 两种,后者是 Redis 后续版本新增的功能,以减轻主节点的同步负担。

主从复制的主要作用:

  • 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

  • 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 (实际上是一种服务的冗余)。

  • 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。

  • 高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的 基础,因此说主从复制是 Redis 高可用的基础。

哨兵模式

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。 ** **

哨兵模式的主要作用

  • 监控(Monitoring): 哨兵会不断地检查主节点和从节点是否运作正常。

  • 自动故障转移(Automatic failover): 当 主节点 不能正常工作时,哨兵会开始 自动故障转移操作,它会将失效主节点的其中一个 从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

  • 配置提供者(Configuration provider): 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。

  • 通知(Notification): 哨兵可以将故障转移的结果发送给客户端。

**

集群模式

上图 展示了 Redis Cluster 典型的架构图,集群中的每一个 Redis 节点都 互相两两相连,客户端任意 直连 到集群中的 任意一台,就可以对其他 Redis 节点进行 读写 的操作。

基本原理

Redis 集群中内置了 16384 个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个 集群的配置信息,当客户端具体对某一个 key 值进行操作时,会计算出它的一个 Hash 值,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量 大致均等 的将哈希槽映射到不同的节点。 再结合集群的配置信息就能够知道这个 key 值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的 MOVED 命令来进行一个跳转,告诉客户端去连接这个节点以获取数据:

GET x
-MOVED 3999 127.0.0.1:6381

MOVED 指令第一个参数 3999 是 key 对应的槽位编号,后面是目标节点地址,MOVED 命令前面有一个减号,表示这是一个错误的消息。客户端在收到 MOVED 指令后,就立即纠正本地的 槽位映射表,那么下一次再访问 key 时就能够到正确的地方去获取了。

集群的主要作用

  1. 数据分区: 数据分区 (或称数据分片) 是集群最核心的功能。集群将数据分散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍持久化和主从复制时都有提及,例如,如果单机内存太大,bgsave 和 bgrewriteaof 的 fork 操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……

  2. 高可用: 集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。

问题

linux redis WARNING overcommit_memory is set to 0! 解决方案

现象

公司的redis有时background save db不成功,通过log发现下面的告警,很可能由它引起的:

[13223] 17 Mar 13:18:02.207 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

于是通过搜索,也有人跟我遇到同样的问题,基本可以确定是由它引起的。

内核参数overcommit_memory

它是 内存分配策略

可选值:0、1、2。 0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。 1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。 2, 表示内核允许分配超过所有物理内存和交换空间总和的内存

什么是Overcommit和OOM

Linux对大部分申请内存的请求都回复"yes",以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存。这种技术叫做Overcommit。当linux发现内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。

当oom-killer发生时,linux会选择杀死哪些进程?选择进程的函数是oom_badness函数(在mm/oom_kill.c中),该函数会计算每个进程的点数(0~1000)。点数越高,这个进程越有可能被杀死。每个进程的点数跟oom_score_adj有关,而且oom_score_adj可以被设置(-1000最低,1000最高)。

解决方法

很简单,按提示的操作(将vm.overcommit_memory 设为1)即可:

有三种方式修改内核参数,但要有root权限:

(1)编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p 使配置文件生效

(2)sysctl vm.overcommit_memory=1

(3)echo 1 > /proc/sys/vm/overcommit_memory