百分百教你学会redis

Posted _雪辉_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了百分百教你学会redis相关的知识,希望对你有一定的参考价值。

一、Redis介绍

1.1 什么是redis?

  Redis:C语言编写,遵循BSD协议,支持多种数据类型。基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
  redis是单进程单线程的键值数据库。阿里将redis改为多IO线程。

1.2 redis为什么这么快?

  1、redis绝大部分操作完全基于内存。
  2、数据结构和操作简单。
  3、采用单线程,避免了上下文切换和竞争。不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
  4、使用多路I/O复用模型(多个网络连接使用一个线程),非阻塞IO;
  5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

1.3 redis的缺点

  缓存雪崩问题。
  缓存穿透问题。
  缓存的并发竞争问题。
  缓存、数据库双写一致性问题。

二、redis单机安装

2.1 安装步骤

安装环境:
  操作系统:CentOS Linux release 7.6.1810 (Core)
  处理器:1C
  内存:2G
  redis版本:4.0.14和5.0.5
1.1下载安装包

wget http://download.redis.io/releases/redis-5.0.5.tar.gz

1.2解压

tar xf redis-5.0.5.tar.gz 

1.3安装
  在安装前编译需要的依赖包

yum -y install gcc automake autoconf libtool make

  进入安装目录进行安装,注意要使用有root权限的账户进行操作,因为在安装过程中需要操作许多系统文件路径,如果没有权限则会安装报错。

cd redis-5.0.5/
make PREFIX=/usr/local/redis/ install
make
安装完成后通过make test进行对编译结果的测试。 
依赖包:yum install -y tcl 
最后如果输出"\\o/ All tests passed without errors!"则安装成功! 

PS:以上步骤完全相同。

2.2 redis工具

  redis-server 启动redis
  redis-cli redis命令行工具
  redis-benchmark 基准测试工具
  redis-check-aof AOF持久化文件检测工具和修复工具
  redis-check-dump RDB持久化文件检测工具和修复工具
  redis-sentinel 启动redis-sentinel

2.3 配置文件介绍

  INCLUDES :Redis只有一个配置文件,如果多个人进行开发维护,那么就需要多个这样的配置文件,这时候多个配置文件就可以在此通过 include /path/to/local.conf 配置进来,而原本的 redis.conf 配置文件就作为一个总闸。如果将此配置写在redis.conf 文件的开头,那么后面的配置会覆盖引入文件的配置,如果想以引入文件的配置为主,那么需要将 include 配置写在 redis.conf 文件的末尾。
  MODULES:自定义模块。
  NETWORK:网络模块。
  GENRAL:普通配置。
  SNAPSHOTTING:持久化配置。
  REPLICATION:复制模块。
  SECURITY:安全模块。
  CLIENTS:客户端模块。
  MEMORY MANAGEMENT:内存相关模块。
  APPEND ONLY MODE:AOF模块。
  LUA SCRIPTING:lua脚本相关模块。
  REDIS CLUSTER:集群模块。

2.4 数据类型

  1、string 字符串
value最大支持512M,建议存储的值小于1M,使用场景:自增自减(微博数,粉丝数),登录信息等。
  2、hash 哈希
哈希有两层,第一层是key:hash集合value,第二层是hashkey:string value。使用场景:部分变更的数据,比如说商品和规格;用户信息。第二层不要超过200个为佳。
  3、list 列表
有序,可重复。使用场景:队列系统。
  4、set 集合
无序,不能重复,比如说交集并集,共同好友和获取某段时间所有数据去重值。values最好小于1M。
  5、zset 有序集合
有序,不能重复。排行榜,带权重的消息队列。
  6、Stream 流(redis5新加入)
hyperloglog,使用得不多。hyperloglog从创建一开始存储12KB的容量。
数据结构
  hyperloglog
使用场景:一般用于统计使用,例如统计页面PV/UV等数据。
  GEO,使用得也不多。用于处理地理位置的信息。
在redis中,GEO可以保存地理位置的信息,并且可以计算地理位置的距离等。场景就在于使用地理位置时,并且需要计算,快速的场景,可使用。

2.5 redis的数据持久化

  Redis提供了将内存数据持久化到硬盘,以及用持久化文件来恢复数据库数据的功能。Redis 支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)。

2.5.1 RDB

  简单的说就是物理备份,记录了某一时间点的快照(数据库中所有键值对数据)。
RDB可以手动/自动触发。
自动触发:save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
手动触发:save、bgsave。Save会阻塞进程。Bgsave会创建子进程操作。创建子进程阶段会阻塞。
  恢复时是将快照文件移到配置文件指定位置,启动时直接读到内存里。,载入期间阻塞。
优势:
  1.RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
  2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
  3.RDB在恢复大数据集时的速度比 AOF 的恢复速度要快。
劣势:
  1、RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
  2、RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
  3、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)。

2.5.2 AOF

  RDB存在一个缺陷,就像物理备份一样,快照后宕机数据会丢失。AOF就是解决这个问题的一个手段。
  AOF类似于逻辑备份,保存的是redis执行的写命令。但是redis不是WAL机制。它进行逻辑处理后存储日志。

appendfsync:aof持久化策略的配置:
no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;
always表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低;
everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。通常选择 everysec ,兼顾安全性和效率。

  aof日志会随着时间逐渐增大,重放AOF日志会非常耗时。
  Redis提供了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。序列化完成后将操作期间发生的增量AOF日志追加到新AOF日志中,然后替代旧AOF文件。通常redis的持久化由备节点做。但是如果由于网络原因主从出现问题,主库宕机了,那么数据就会丢失。
  AOF 文件重写触发机制:通过 redis.conf 配置文件中的 auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite-min-size:64mb 配置,也就是说默认Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。Redis通过开启子进程进行AOF重放,额外redis还有一个AOF缓冲区,在重写完成后,会将重写期间的写命令应用到新AOF文件中。

2.5.3 RDB&AOF

  RDB会大量丢数据,AOF重放慢,redis在4.0的时候,综合两种日志文件。全量RDB+增量AOF,提升了效率。

2.6 redis的内存

如何查看Redis中内存的消耗情况哪?可以通过 info命令,查看Redis内存消耗的相关指标,从而有助于更好的分析内存。执行命令之后有这么几个重要的指标:


used_memory	Redis分配器分配的内存总量,指Redis存储的所有数据所占的内存
used_memory_human	以可读的形式返回user_memory
used_memory_rss	Redis进程占用的物理内存总量
used_memory_peak	used_memory使用的峰值
used_memory_peak_human	可读格式返回used_memory_peak
used_memory_lua	Lua引擎消耗的内存大小
mem_fragmentation_ratio	used_memory_rss/used_memory比值,内存碎片率
mem_allocator	Redis所使用的内存分配器,默认jemalloc
used_memory:1038744104
used_memory_human:990.62M
used_memory_rss:5811122176
used_memory_peak:4563077088
used_memory_peak_human:4.25G
used_memory_lua:35840
mem_fragmentation_ratio:5.59
mem_allocator:libc

重点需要关注下mem_fragmentation_ratio这个值:
mem_fragmentation_ratio > 1 说明多出来的部分名没有用于数据存储,而是被内存碎片所消耗,相差越大,说明内存碎片率越严重。
mem_fragmentation_ratio < 1 一般出现在Redis内存交换(Swap)到硬盘导致(used_memory > 可用最大内存时,Redis会把旧的和不适用的数据写入到硬盘,这块空间就叫Swap空间),出现这种情况需要格外关注,硬盘速度远远慢于内存,Redis性能就会变得很差,甚至僵死。

1.1、内存消耗的划分
Redis的内存主要包括:对象内存+缓冲内存+自身内存+内存碎片。

1、对象内存
对象内存是Redis内存中占用最大一块,存储着所有的用户的数据。Redis所有的数据都采用的是key-value型数据类型,每次创建键值对的时候,都要创建两个对象,key对象和value对象。key对象都是字符串,value对象的存储方式,五种数据类型–String,List,Hash,Set,Zset。每种存储方式在使用的时候长度、数据类型不同,则占用的内存就不同。

2、缓冲内存
主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区
客户端缓冲:普通的客户端的连接(大量连接),从客户端(主要是复制的时候,异地跨机房,或者主节点下有多个从节点),订阅客户端(发布订阅功能,生产大于消费就会造成积压)
复制积压缓冲:2.8版本之后提供的可重用的固定大小缓冲区用于实现部分复制功能,默认1MB,主要是在主从同步时用到。
AOF缓冲区:持久化用的,会先写入到缓冲区,然后根据响应的策略向磁盘进行同步,消耗的内存取决于写入的命令量和重写时间,通常很小。

3、内存碎片
目前可选的分配器有jemalloc、glibc、tcmalloc默认jemalloc
出现高内存碎片问题的情况:大量的更新操作,比如append、setrange;大量的过期键删除,释放的空间无法得到有效利用
解决办法:数据对齐,安全重启(高可用/主从切换)。

三、redis主备安装

3.1 安装步骤

  与单实例安装区别在于在从库的配置文件中加入一个参数:

slaveof 192.168.0.119  6380   (配置从服务器,写主服务器的地址和端口号)

3.2 redis主备复制原理

1.复制过程
  1.从节点执行 slaveof 命令。
  2.从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制。
  3.从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点。
  4.连接建立成功后,发送ping命令,希望得到pong命令响应,否则会进行重连。
  5.如果主节点设置了权限,那么就需要进行权限验证,如果验证失败,复制终止。
  6.权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。
  7.当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

2.数据间的同步
  上面说的复制过程,其中有一个步骤是“同步数据集”,这个就是现在讲的“数据间的同步”。
  redis 同步有 2 个命令:sync 和 psync,前者是 redis 2.8 之前的同步命令,后者是 redis 2.8 为了优化 sync 新设计的命令。我们会重点关注 2.8 的 psync 命令。
psync 命令需要 3 个组件支持:
  1.主从节点各自复制偏移量
  2.主节点复制积压缓冲区
  3.主节点运行ID
主从节点各自复制偏移量:
  1.参与复制的主从节点都会维护自身的复制偏移量。
  2.主节点在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在info replication中的 master_repl_offset指标中。
  3.从节点每秒钟上报自身的的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量。
  4.从节点在接收到主节点发送的命令后,也会累加自身的偏移量,统计信息在 info replication 中。
  5.通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
主节点复制积压缓冲区:
  1.复制积压缓冲区是一个保存在主节点的一个固定长度的先进先出的队列,默认大小 1MB。
  2.这个队列在slave 连接时创建。这时主节点响应写命令时,不但会把命令发送给从节点,也会写入复制缓冲区。
  3.他的作用就是用于部分复制和复制命令丢失的数据补救。通过 info replication 可以看到相关信息。
主节点运行 ID:
  1.每个redis启动的时候,都会生成一个 40 位的运行 ID。
  2.运行 ID 的主要作用是用来识别 Redis 节点。如果使用 ip+port 的方式,那么如果主节点重启修改了RDB/AOF数据,从节点再基于偏移量进行复制将是不安全的。所以,当运行id变化后,从节点将进行全量复制。也就是说,redis 重启后,默认从节点会进行全量复制。
如果在重启时不改变运行ID呢?
  1.可以通过 debug reload 命令重新加载 RDB 并保持运行 ID 不变,从而有效的避免不必要的全量复制。
  2.缺点是:debug reload 命令会阻塞当前 Redis 节点主线程,因此对于大数据量的主节点或者无法容忍阻塞的节点,需要谨慎使用。一般通过故障转移机制可以解决这个问题。
psync 执行流程:
  psync就是增量同步,在2.8实现,在4.0之后使用psync2.解决了主从重新建立连接全量同步的问题。
  为了解决主从角色切换导致的重新全量同步,redis 4.0 引入多另外一个变量 replid2 来存放同步过的主库的replid,同时 replid 在不同角色意义也有写变化。replid 在主库的意义和之前 replid 仍然是一样的,但对于从库来说,replid 表示当前正在同步的主库的 replid 而不再是本身的 replid。replid2 则表示前一个主库的 replid,这个在主从角色切换的时候会用到,默认初始化为全0。

3.全量复制
  全量复制是 Redis 最早支持的复制方式,也是主从第一次建立复制时必须经历的的阶段。触发全量复制的命令是 sync 和 psync。之前说过,这两个命令的分水岭版本是 2.8,redis 2.8 之前使用 sync 只能执行全量不同,2.8 之后同时支持全量同步和部分同步。

流程如下:
  1.发送 psync 命令
  2.主节点根据命令返回 FULLRESYNC
  3.从节点记录主节点 ID 和 offset
  4.主节点 bgsave 并保存 RDB 到本地
  5.主节点发送 RBD 文件到从节点
  6.从节点收到 RDB 文件并加载到内存中
  7.主节点在从节点接受数据的期间,将新数据保存到“复制客户端缓冲区”,当从节点加载 RDB 完毕,再发送过去。(如果从节点花费时间过长,将导致缓冲区溢出,最后全量同步失败)
  8.从节点清空数据后加载RDB 文件,如果RDB文件很大,这一步操作仍然耗时,如果此时客户端访问,将导致数据不一致,可以使用配置slave-server-stale-data 关闭.
  9.从节点成功加载完RDB后,如果开启了AOF,会立刻做 bgrewriteaof。
注意:
  1.如果RDB文件大于6GB,并且是千兆网卡,Redis的默认超时机制(60秒),会导致全量复制失败。可以通过调大repl-timeout参数来解决此问题。
  2.Redis虽然支持无盘复制,即直接通过网络发送给从节点,但功能不是很完善,生产环境慎用。
4.部分复制
  当从节点正在复制主节点时,如果出现网络闪断和其他异常,从节点会让主节点补发丢失的命令数据,主节点只需要将复制缓冲区的数据发送到从节点就能够保证数据的一致性,相比较全量复制,成本小很多。
  1.当从节点出现网络中断,超过了repl-timeout时间,主节点就会中断复制连接。
  2.主节点会将请求的数据写入到“复制积压缓冲区”,默认 1MB。
  3.当从节点恢复,重新连接上主节点,从节点会将 offset 和主节点 id 发送到主节点。
  4.主节点校验后,如果偏移量的数后的数据在缓冲区中,就发送 cuntinue 响应 —— 表示可以进行部分复制。
  5.主节点将缓冲区的数据发送到从节点,保证主从复制进行正常状态。
5.心跳
  主从节点在建立复制后,他们之间维护着长连接并彼此发送心跳命令。
心跳的关键机制如下:
  1.主从都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制相关客户端信息,主节点的连接状态为flags=M,从节点的连接状态是 flags = S。
  2.主节点默认每隔 10 秒对从节点发送 ping 命令,可修改配置repl-ping-slave-period 控制发送频率。
  3.从节点在主线程每隔一秒发送 replconf ack{offset}命令,给主节点上报自身当前的复制偏移量。
  4.主节点收到 replconf 信息后,判断从节点超时时间,如果超过 repl-timeout 60 秒,则判断节点下线。

注意:
  为了降低主从延迟,一般把redis主从节点部署在相同的机房/同城机房,避免网络延迟带来的网络分区造成的心跳中断等情况。
6.异步复制
  主节点不但负责数据读写,还负责把写命令同步给从节点,写命令的发送过程是异步完成,也就是说主节点处理完写命令后立即返回客户度,并不等待从节点复制完成。
异步复制的步骤很简单,如下:
  1.主节点接受处理命令。
  2.主节点处理完后返回响应结果 。
  3.对于修改命令,异步发送给从节点,从节点在主线程中执行复制的命令。

四、redis哨兵

4.1 什么是哨兵?

  Redis-Sentinel是redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。
它的主要功能有以下几点:
  1、不时地监控redis是否按照预期良好地运行;
  2、如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);
  3、能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
  为了防止sentinel的单点故障,可以对sentinel进行集群化,创建多个sentinel。

4.2 哨兵高可用原理

  ①sentinel集群通过给定的配置文件发现master,启动时会监控master。通过向master发送info信息获得该服务器下面的所有从服务器。
  ②sentinel集群通过命令连接向被监视的主从服务器发送hello信息(每秒一次),该信息包括sentinel本身的ip、端口、id等内容,以此来向其他sentinel宣告自己的存在。
  ③sentinel集群通过订阅连接接收其他sentinel发送的hello信息,以此来发现监视同一个主服务器的其他sentinel;集群之间会互相创建命令连接用于通信,因为已经有主从服务器作为发送和接收hello信息的中介,sentinel之间不会创建订阅连接。
  ④sentinel集群使用ping命令来检测实例的状态,如果在指定的时间内(down-after-milliseconds)没有回复或则返回错误的回复,那么该实例被判为下线。
  ⑤当failover主备切换被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover,即进行failover的sentinel会去获得指定quorum个的sentinel的授权,成功后进入ODOWN状态。如在5个sentinel中配置了2个quorum,等到2个sentinel认为master死了就执行failover。
  ⑥sentinel向选为master的slave发送SLAVEOF NO ONE命令,选择slave的条件是sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前。如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前。如果优先级和下标都相同,就选择进程ID较小的。
  ⑦sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号(config-epoch),当failover执行结束以后,这个版本号将会被用于最新的配置,通过广播形式通知其它sentinel,其它的sentinel则更新对应master的配置。
①到③是自动发现机制:
  以10秒一次的频率,向被监视的master发送info命令,根据回复获取master当前信息。以1秒一次的频率,向所有redis服务器、包含sentinel在内发送PING命令,通过回复判断服务器是否在线。以2秒一次的频率,通过向所有被监视的master,slave服务器发送当前sentinel,master信息的消息。
④是检测机制,⑤和⑥是failover机制,⑦是更新配置机制。
  注意:因为redis采用的是异步复制,没有办法避免数据的丢失。但可以通过以下配置来使得数据不会丢失:min-slaves-to-write 1 、 min-slaves-max-lag 10。一个redis无论是master还是slave,都必须在配置中指定一个slave优先级。要注意到master也是有可能通过failover变成slave的。如果一个redis的slave优先级配置为0,那么它将永远不会被选为master,但是它依然会从master哪里复制数据。

五、redis中间件

  

5.1 中间件产品

Twemproxy

Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。这个方案顺理成章地解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived做高可用方案。这么些年来,Twemproxy是应用范围最广、稳定性最高、最久经考验的分布式中间件。只是,他还有诸多不方便之处。Twemproxy最大的痛点在于,无法平滑地扩容/缩容。这样增加了运维难度:业务量突增,需增加Redis服务器;业务量萎缩,需要减少Redis服务器。但对Twemproxy而言,基本上都很难操作。或者说,Twemproxy更加像服务器端静态sharding。有时为了规避业务量突增导致的扩容需求,甚至被迫新开一个基于Twemproxy的Redis集群。Twemproxy另一个痛点是,运维不友好,甚至没有控制面板。当然,由于使用了中间件代理,相比客户端直接连服务器方式,性能上有所损耗,实测结果降低了20%左右。

Codis

Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。现已广泛用于豌豆荚的各种Redis业务场景,从各种压力测试来看,稳定性符合高效运维的要求。性能更是改善很多,最初比Twemproxy慢20%;现在比Twemproxy快近100%(条件:多实例,一般Value长度)。Codis具有可视化运维管理界面。Codis无疑是为解决Twemproxy缺点而出的新解决方案。因此综合方面会由于Twemproxy很多。目前也越来越多公司选择Codis。Codis引入了Group的概念,每个Group包括1个RedisMaster及至少1个Redis Slave,这是和Twemproxy的区别之一。这样做的好处是,如果当前Master有问题,则运维人员可通过Dashboard“自助式”切换到Slave,而不需要小心翼翼地修改程序配置文件。为支持数据热迁移(Auto Rebalance),出品方修改了Redis Server源码,并称之为Codis Server。Codis采用预先分片(Pre-Sharding)机制,事先规定好了,分成1024个slots(也就是说,最多能支持后端1024个Codis Server),这些路由信息保存在ZooKeeper中。不足之处有对redis源码进行了修改,以及代理实现本身会有的问题。

Redis-cluster

reids-cluster在redis3.0中推出,支持Redis分布式集群部署模式。采用无中心分布式架构。所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.节点的fail是通过集群中超过半数的节点检测失效时才生效.客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可,减少了代理层,大大提高了性能。redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key之间的关系。目前Jedis已经支持Redis-cluster。从计算架构或者性能方面无疑Redis-cluster是最佳的选择方案。

  现在主流主要都是在使用redis-cluster和基于Twemproxy的架构,codis最新版已知支持redis4.0.1。具体待测试。

六、redis cluster

6.1 安装步骤

  Redis 3.0之后,节点之间通过去中心化的方式,提供了完整的sharding、replication、failover解决方案,称为Redis Cluster。redis cluster相对于单实例来讲仅需要在多台redis实例的配置文件中加入:

cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 15000

cluster-config-file最好使用端口命名,方便区分。
redis4的redis-trib.rb工具依赖ruby脚本语言,需要安装ruby,之后通过gem install redis安装接口。

yum -y install ruby ruby-devel rubygems rpm-build
gem install redis
redis-trib.rb create --replicas 1 192.168.0.118:7000 192.168.0.119:7002 192.168.0.120:7004 192.168.0.118:7001 192.168.0.119:7003 192.168.0.120:7005

redis5.0将redis-trib.rb集成到redis5.0的redis-cli中,不再依赖ruby。

redis-cli --cluster create --cluster-replicas 1 192.168.0.118:8000 192.168.0.119:8002 192.168.0.120:8004 192.168.0.118:8001 192.168.0.119:8003 192.168.0.120:8005

如下则搭建成功:

[root@zj2 bin]# ./redis-trib.rb create --replicas 1 192.168.0.118:7000 192.168.0.119:7002 192.168.0.120:7004 192.168.0.118:7001 192.168.0.119:7003 192.168.0.120:7005
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.0.118:7000
192.168.0.119:7002
192.168.0.120:7004
Adding replica 192.168.0.119:7003 to 192.168.0.118:7000
Adding replica 192.168.0.120:7005 to 192.168.0.119:7002
Adding replica 192.168.0.118:7001 to 192.168.0.120:7004
M: 2963feb267f2f56da76e955f3314ff19aaee436b 192.168.0.118:7000
   slots:0-5460 (5461 slots) master
M: 357c040707bb6e3bae4bee4675945583a380c854 192.168.0.119:7002
   slots:5461-10922 (5462 slots) master
M: a0667bdb9b12fa7501e34e91d98d72eec699e7c0 192.168.0.120:7004
   slots:10923-16383 (5461 slots) master
S: 79f5f35cca5c92162d18d5b0026f473e247f5f1c 192.168.0.118:7001
   replicates a0667bdb9b12fa7501e34e91d98d72eec699e7c0
S: 86681d1ea8868550ddfcffadcecba3c99931bf34 192.168.0.119:7003
   replicates 2963feb267f2f56da76e955f3314ff19aaee436b
S: cc2f9cab4e7560bf46b3a8e17b62fd6dca3efe4a 192.168.0.120:7005
   replicates 357c040707bb6e3bae4bee4675945583a380c854
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join.....
>>> Performing Cluster Check (using node 192.168.0.118:7000)
M: 2963feb267f2f56da76e955f3314ff19aaee436b 192.168.0.118:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
S: 86681d1ea8868550ddfcffadcecba3c99931bf34 192.168.0.119:7003
   slots: (0 slots) slave
   replicates 2963feb267f2f56da76e955f3314ff19aaee436b
M: a0667bdb9b12fa7501e34e91d98d72eec699e7c0 192.168.0.120:7004
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
M: 357c040707bb6e3bae4bee4675945583a380c854 192.168.0.119:7002
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: 79f5f35cca5c92162d18d5b0026f473e247f5f1c 192.168.0.118:7001
   slots: (0 slots) slave
   replicates a0667bdb9b12fa7501e34e91d98d72eec699e7c0
S: cc2f9cab4e7560bf46b3a8e17b62fd6dca3efe4a 192.168.0.120:7005
   slots: (0 slots) slave
   replicates 357c040707bb6e3bae4bee4675945583a380c854
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

6.2 redis cluster原理

  一个Redis Cluster由多个Redis节点组成。不同的节点组服务的数据无交集,每个节点对应数据sharding的一个分片。节点组内部分为主备2类。两者数据准实时一致,通过异步化的主备复制机制保证。一个节点组有且仅有一个master,同时有0到多个slave。只有master对外提供写服务,读服务可由master/slave提供。
  redis cluster 会将数据的全集分散到各主节点,备节点异步同步。各节点之间两两通过Redis Cluster Bus交互,相互获得对方的信息:

1、数据分片(slot)和节点的对应关系;
2、集群中每个节点可用状态;
3、集群结构发生变更时,通过一定的协议对配置信息达成一致。数据分片的迁移、主备切换、单点master的发现和其发生主备关系变更等,都会导致集群结构变化。
4、publish/subscribe(发布订阅)功能,在Cluster版内部实现所需要交互的信息。

  Redis Cluster Bus通过单独的端口进行连接,由于Bus是节点间的内部通信机制,交互的是字节序列化信息。相对Client的字符序列化来说,效率较高。
  Redis Cluster是一个去中心化的分布式实现方案,客户端和集群中任一节点连接,然后通过后面的交互流程,逐渐的得到全局的数据分片映射关系。
  redis cluster中每个节点都保存了集群的配置信息,当集群的数据分片信息发生变更时,redis cluster仍对外提供服务。当急群众某个主节点宕机时,redis cluster会自动发现,并自动故障迁移到该master下的一个从节点。
  不同的节点分片无交集,Redis Cluster 不存在单独的proxy或配置服务器,所以需要将客户端路由到目标的分片。
  redis cluster将所有数据划分到16384个分片。每条数据根据一致性哈希算法映射到16384个分片中的一个。cluster不支持跨节点的单命令(如果涉及到到2个key对应的分片不在一个节点,则执行失败)。为此,Redis引入了HashTag的概念,使得数据分布算法可以根据key 的某一部分进行计算,让相关的2 条记录落到同一个数据分片。
  redis cluster相对于单机redis具备路由语义识别的能力,并且具备路由缓存能力。当Client 访问的key 不在当前Redis 节点的slots中,Redis 会返回给Client 一个moved命令。并告知其正确的路由信息。当Cluster 在数据重新分布过程中时,可以通过ask 命令控制客户端的路由,ask命令和moved 命令的不同在于,moved 会更新Client数据路由,ask 只是重定向新节点,但是后续的相同slot 仍会路由到旧节点。因为数据迁移的过程中,路由会不断变更,所以引入ask。
  在redis cluster中,每个分片对应的节点是确定的。但是在以下情况节点和分片会变更:

1、新的节点作为master加入;
2、某个节点分组需要下线;
3、负载不均衡需要调整slot 分布。

  此时,需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。Redis Cluster 只提供迁移过程中需要的原语,包含下面 2 种:

1、节点迁移状态设置:迁移前标记源/目标节点。
2、key迁移的原子化命令:迁移的具体步骤。

迁移过程:

向节点B发送状态变更命令,将B的对应slot 状态置为importing。
向节点A发送状态变更命令,将A对应的slot 状态置为migrating。
针对A上的slot 的所有key,分别向A 发送migrate 命令,告知A 将对应的key 迁移到B。
当A节点的状态置为migrating 后,表示对应的slot 正在从A迁出,为保证该slot 数据的一致性。A此时提供的写服务和通常状态下有所区别,对于某个迁移中的slot:

如果Client 访问的key 尚未迁出,则正常的处理该key;
如果key已经迁出或者key不存在,则回复Client ASK 信息让其跳转到B处理;
当节点B 状态变成importing 后,表示对应的slot 正在向B迁入。即使B 能对外提供该slot 的读写服务,但是和通常情况下有所区别:

当Client的访问不是从ask 跳转的,说明Client 还不知道迁移。有可能操作了尚未迁移完成的,处在A上面的key,如果这个key 在A上被修改了,则后续会产生冲突。
所以对于该slot 上所有非ask 跳转的操作,B不会进行操作,而是通过moved 让Client 跳转至A执行。
这样的状态控制,保证了同一个key 在迁移之前总是在源节点执行。迁移后总是在目标节点执行,从而杜绝了双写的冲突。迁移过程中,新增加的key 会在目标节点执行,源节点不会新增key。使得迁移有界限,可以在某个确定的时刻结束。

单个key 的迁移过程可以通过原子化的migrate 命令完成。对于A/B的slave 节点,是通过主备复制,从而达到增删数据。

当所有key 迁移完成后,Client 通过 cluster setslot 命令设置B的分片信息,从而包含了迁入的slot。设置过程中会让Epoch自增,并且是Cluster 中的最新值。然后通过相互感知,传播到Cluster 中的其他节点。

6.3 redis cluster崩溃恢复

  redis cluster节点间通过redis cluster bus周期性的ping/pong交互。当某个节点宕机,其他节点发出的ping收不到相应,并且一段时间未收到,则认为该节点故障。后续通过Gossip 发出的PING/PONG消息中,这个节点的故障状态信息会传播到集群的其他节点。
  Redis Cluster 的节点两两通过TCP 保持Redis Cluster Bus连接,当对PING 无反馈时,可能是节点故障,也可能是TCP 链接断开。如果是TCP 断开导致的误报,虽然误报消息会因为其他节点的正常连接被忽略,但是也可以通过一定的方式减少误报。Redis Cluster 通过 预重试机制 排除此类误报:当 NODE_TIMEOUT/2 过去了,但是还未收到响应,则重新连接重发PING 消息,如果对端正常,则在很短的时间内就会有响应。
  对于网络分隔的情况,某个节点(B)并没有故障,但是和A 无法连接,但是和C/D 等其他节点可以正常联通。此时只会有A 将 B 标记为PFAIL 状态,其他节点认为B 正常。此时A 和C/D 等其他节点信息不一致,Redis Cluster 通过故障 确认协议 达成一致。
  集群中每个节点都是Gossip的接收者,A 也会接收到来自其他节点的Gossip 消息,被告知B 是否处于PFAIL 状态。当A收到来气其他master 节点对于 B 的PFAIL 达到一定数量后,会将B的PFAIL 状态升级为 FAIL 状态。表示B 已经确认为故障态,后面会发起slave 选举流程。
  B是A的master,并且B 已经被集群公认是FAIL 状态了,那么A 发起竞选,期望成为新的master。
  如果B 有多个slave (A/E/F)都认知到B 处于FAIL 状态了,A/E/F 可能会同时发起竞选。当B的slave 个数 >= 3 时,很有可能产生多轮竞选失败。为了减少冲突的出现,优先级高的slave 更有可能发起竞选,从而提升成功的可能性。这里的优先级是slave的数据最新的程度,数据越新的(最完整的)优先级越高。
  slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master 收到后回复FAILOVER_AUTH_ACK消息告知是否同意。slave 发送FAILOVER_AUTH_REQUEST前会将currentEpoch 自增,并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST消息中,如果自己未投过票,则回复同意,否则回复拒绝。
  当slave 收到过半的master 同意时,会替代B 成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构。
  当B 恢复可用之后,它手续爱你仍然认为自己是master,但逐渐的通过Gossip 协议得知A 已经替代了自己,然后降级为A的slave。

6.4 读写分离

  对于读写分离的场景,应用对于某些读请求允许舍弃一定的数据一致性,以换取更高的吞吐量。此时希望将读请求交给slave处理,以分担master的压力。
  通过分片映射关系,某个slot一定对应着一个master节点。Client 通过moved 命令,也只会路由到各个master中。即使Client将请求直接发送到slave上,也会回复moved 到master去处理。
  为此,Redis Cluster引入了readonly命令。Client向slave发送该命令后,不再moved到master处理,而是自己处理,这成为slave的readonly模式。通过readwrite命令,可以将slave的readonly模式重置。

6.5 redis单点故障解决

  当集群中,有一个从宕机,如果其他主库有多余一个从节点,那么会拿出一个从节点放到这个单主下面。集群中只需要保持2*master+1个节点,就可以保持任一节点宕机时,故障转移后继续高可用。

6.6 增删节点及集群可用性

6.7.1添加节点

  首先搭建redis新实例,配置文件和其他节点相同,启动新节点。

添加主节点
redis-trib.rb add-node 192.168.0.120:7006 192.168.0.118:7000

192.168.10.219:6378是新增的节点
192.168.10.219:6379集群任一个旧节点

  添加节点成功后,节点中并不会分配哈希槽。
分配哈希槽

# redis-trib.rb reshard 192.168.10.219:6378 //下面是主要过程  
  
How many slots do you want to move (from 1 to 16384)? 1000 //设置slot数1000  
What is the receiving node ID? 03ccad2ba5dd1e062464bc7590400441fafb63f2 //新节点node id  
Please enter all the source node IDs.  
 Type 'all' to use all the nodes as source nodes for the hash slots.  
 Type 'done' once you entered all the source nodes IDs.  
Source node #1:all //表示全部节点重新分配
Do you want to proceed with the proposed reshard plan (yes/no)? yes //确认重新分  

添加从节点

redis-trib.rb add-node --slave --master-id 03ccad2ba5dd1e062464bc7590400441fafb63f2 192.168.10.220:6385 192.168.10.219:6379  

删除节点

删除从节点
redis-trib.rb del-node 192.168.127.130:7007 991ed242102aaa08873eb9404a18e0618a4e37bd
删除主节点
# redis-trib.rb reshard 192.168.10.219:6378 //取消分配的slot,下面是主要过程  
  
How many slots do you want to move (from 1 to 16384)? 1000 //被删除master的所有slot数量  
What is the receiving node ID? 5d8ef5a7fbd72ac586bef04fa6de8a88c0671052 //接收6378节点slot的master  
Please enter all the source node IDs.  
 Type 'all' to use all the nodes as source nodes for the hash slots.  
 Type 'done' once you entered all the source nodes IDs.  
Source node #1:03ccad2ba5dd1e062464bc7590400441fafb63f2 //被删除master的node-id  
Source node #2:done   
Do you want to proceed with the proposed reshard plan (yes/no)? yes 
redis-trib.rb del-node 192.168.10.219:6378 '03ccad2ba5dd1e062464bc7590400441fafb63f2' 

七、redis5新特性

  redis5.0总共增加了19项新特性,如下:

  1. 新的流数据类型(Stream data type)
  2. 新的 Redis 模块 API:定时器、集群和字典 API(Timers, Cluster and Dictionary APIs)
  3. RDB 现在可存储 LFU 和 LRU 信息
  4. redis-cli 中的集群管理器从 Ruby(redis-trib.rb)移植到了 C 语言代码。执行 redis-cli --cluster help 命令以了解更多信息
  5. 新的有序集合(sorted set)命令:ZPOPMIN/MAX 和阻塞变体(blocking variants)
  6. 升级 Active defragmentation 至 v2 版本
  7. 增强 HyperLogLog 的实现
  8. 更好的内存统计报告
  9. 许多包含子命令的命令现在都有一个 HELP 子命令
  10. 客户端频繁连接和断开连接时,性能表现更好
  11. 许多错误修复和其他方面的改进
  12. 升级 Jemalloc 至 5.1 版本
  13. 引入 CLIENT UNBLOCK 和 CLIENT ID
  14. 新增 LOLWUT 命令
  15. 在不存在需要保持向后兼容性的地方,弃用 “slave” 术语
  16. 网络层中的差异优化
  17. Lua 相关的改进:
    • 将 Lua 脚本更好地传播到 replicas / AOF
    • Lua 脚本现在可以超时并在副本中进入 -BUSY 状态
  18. 引入动态的 HZ(Dynamic HZ) 以平衡空闲 CPU 使用率和响应性
  19. 对 Redis 核心代码进行了重构并在许多方面进行了改进

八、redis存在的问题及解决办法

8.1 redis双写一致性问题

问题:一致性的问题是分布式系统中很常见的问题。一致性一般分为两种:强一致性和最终一致性,当我们要满足强一致性的时候,Redis也无法做到完美无瑕,因为数据库和缓存双写,肯定会出现不一致的情况,Redis只能保证最终一致性。
解决:我们如何保证最终一致性呢?
  第一种方式是给缓存设置一定的过期时间,在缓存过期之后会自动查询数据库,保证数据库和缓存的一致性。
  如果不设置过期时间的话,我们首先要选取正确的更新策略:先更新数据库再删除缓存。但我们删除缓存的时候也可能出现某些问题,所以需要将要删除的缓存的key放到消息队列中去,不断重试,直到删除成功为止。

8.2 缓存雪崩问题

问题:在执行代码的时候将很多缓存的失效时间设定成一样,接着这些缓存在同一时间都会实效,然后都会重新访问数据库更新数据,这样会导致数据库连接数过多、压力过大而崩溃。
解决:
  设置缓存过期时间的时候加一个随机值。
  设置双缓存,缓存1设置缓存时间,缓存2不设置,1过期后直接返回缓存2,并且启动一个进程去更新缓存1和2。

8.3 缓存穿透问题

问题: 缓存穿透是指一些非正常用户(黑客)故意去请求缓存中不存在的数据,导致所有的请求都集中到到数据库上,从而导致数据库连接异常。
解决方法:
  利用互斥锁。缓存失效的时候,不能直接访问数据库,而是要先获取到锁,才能去请求数据库。没得到锁,则休眠一段时间后重试。
采用异步更新策略。无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
  提供一个能迅速判断请求是否有效的拦截机制。比如利用布隆过滤器,内部维护一系列合法有效的key,迅速判断出请求所携带的Key是否合法有效。如果不合法,则直接返回。

8.4 缓存的并发竞争问题

问题:
  缓存并发竞争的问题,主要发生在多线程对某个key进行set的时候,这时会出现数据不一致的情况。
  比如Redis中我们存着一个key为amount的值,它的value是100,两个线程同时都对value加100然后更新,正确的结果应该是变为300。但是两个线程拿到这个值的时候都是100,最后结果也就是200,这就导致了缓存的并发竞争问题。
解决方法:
  如果多线程操作没有顺序要求的话,我们可以设置一个分布式锁,然后多个线程去争夺锁,谁先抢到锁谁就可以先执行。这个分布式锁可以用zookeeper或者Redis本身去实现。
  可以利用Redis的incr命令。当我们的多线程操作需要顺序的时候,我们可以设置一个消息队列,把需要的操作加到消息队列中去,严格按照队列的先后执行命令。

九、redis的过期策略

  Redis随着数据的增多,内存占用率会持续变高,我们以为一些键到达设置的删除时间就会被删除,但是时间到了,内存的占用率还是很高,这是为什么呢?  
  Redis采用的是定期删除和惰性删除的内存淘汰机制。

9.1 定期删除

定期删除和定时删除是有区别的:
  定时删除是必须严格按照设定的时间去删除缓存,这就需要我们设置一个定时器去不断地轮询所有的key,判断是否需要进行删除。但是这样的话cpu的资源会被大幅度地占据,资源的利用率变低。所以我们选择采用定期删除,。
  定期删除是时间由我们定,我们可以每隔100ms进行检查,但还是不能检查所有的缓存,Redis还是会卡死,只能随机地去检查一部分缓存,但是这样会有一些缓存无法在规定时间内删除。这时惰性删除就派上用场了。

9.2 惰性删除

  举个简单的例子:中学的时候,平时作业太多,根本做不完,老师说下节课要讲这个卷子,你们都做完了吧?其实有很多人没做完,所以需要在下节课之前赶紧补上。
  惰性删除也是这个道理,我们的这个值按理说应该没了,但是它还在,当你要获取这个key的时候,发现这个key应该过期了,赶紧删了,然后返回一个’没有这个值,已经过期了!’。
但是,如果这个key一直不访问,那么它会一直滞留,也是不合理的,这就需要我们的内存淘汰机制了。
###9.3redis的内存淘汰机制
  reids的内存淘汰机制有六种:

noeviction:当内存不足以容纳新写入数据时,新写入操作会报错.
  allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(最常用)
  allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
  volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
  volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
  volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

相关配置:

# maxmemory-policy allkeys-lru

以上是关于百分百教你学会redis的主要内容,如果未能解决你的问题,请参考以下文章

教你快速学会 Python 函数基础知识

手把手教你学会用Spring AOP

20几行代码,用C++让微信地球转起来,太酷了!视频手把手教你,零基础人人学会

图文并茂教你学会使用 IntelliJ IDEA 进行远程调试

不会用github?全中文CSDN代码托管平台值得你拥有!手把手教你学会使用!!

手把手教你学会希尔排序,很简单!