1.单机模式
关于Redis单节点服务的并发性能,官方提供的数据表示读的速度为110000次/每秒,写的速度是81000次/每秒。当然,官方数据只能代表大概情况,毕竟机器内存大小、网络带宽等因素都会影响到Redis的性能,不过支撑万级并发还是没啥问题的。
1.1 单机性能
Redis自带redis-benchmark工具,可以指定参数对Redis单个服务进行并发压测,得出真实的性能数据,工具参数如下:
序号 | 参数项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器IP或域名 | 127.0.0.1 |
1 | -p | 指定服务器端口 | 6379 |
1 | -s | 指定服务器socket | 无 |
1 | -c | 指定并发连接数 | 50 |
1 | -n | 指定请求数 | 10000 |
1 | -d | 以字节形式指定set、get值的数据大小 | 2 |
1 | -k | keep alive=1 reconncet=0 | 无 |
1 | -r | set、get使用随机key,SADD使用随机值 | 无 |
1 | -P | 通过管道传输请求 | 1 |
1 | -q | 强制退出redis,没懂表达什么的 | 无 |
1 | –csv | 以csv格式输出 | 无 |
1 | -l | 生成循环,永久性执行测试 | 无 |
1 | -t | 仅运行以逗号分隔的测试命令列表 | 无 |
1 | -I | Idle模式,仅打开N个idle连接并等待 | 无 |
先看看官网是什么姿势:
用我阿里云服务器来一波:
没使用管道传输请求时,和Redis官网数据差不多,加上管道参数我这小霸王服务器简直渣渣...
1.2 优缺点
没啥优点,缺点一大堆。首先不具备自动容错和恢复功能,服务器宕机或者Redis服务崩掉后只能手动干预,在恢复期间完全不可用。其次数据没有备份,如果将单机Redis服务作为数据库存储数据,机器磁盘发生损坏或被删库跑路,数据就真的丢失了。
2.主从模式
主从模式是将某一台Redis服务作为主机,其余的一或多个作为备份机,主机每次收到写命令执行成功后会发送给所有备份机进行数据同步。这种模式主要解决单机模式的数据备份问题,并且主从结点的数据几乎一致,可以对集群进行读写分离,提高请求的处理能力。
2.1 集群架构
2.2 主从复制
Redis在2.8版本前主从节点的数据同步是用SYNC实现,主节点需要执行BGSAVE命令生成RDB文件,并且RDB文件的传输也由主线程完成,这意味着整个同步流程中主线程是完全阻塞的,无法处理读写请求,如果RBD文件过大会导致数据同步耗时太久,大量的请求被阻塞。
Redis2.8版本开始采用PSYNC,通过异步+缓冲区的方式同步数据,整个过程如下:
- 从节点启动连接主节点,发送PSYNC命令进行同步数据
- 主节点收到命令后执行BGSAVE向从节点发送快照文件(异步)
- 从节点拿到新的rdb文件替换旧的,载入收到的rdb文件到内存
- 主节点完成自己的rdb加载后开始向从节点发送缓冲区的命令
- 从节点开始同步主节点的缓冲区的命令(此时从节点初始化完成,正常工作)
- 每次主节点收到命令都会发送给从节点同步数据
2.3 读写分离
Redis的读写分离机制是主节点处理所有的写命令,从节点处理所有的读命令,从而减轻主节点的请求压力。然而引用这种机制是要付出一定代价的,首先是数据一致性问题,毕竟主节点的数据同步到从节点需要时间,比如秒杀业务的库存数据,就需要强一致性。其次Redis服务端并不支持这个功能,服务端接收到命令后并不会根据命令类型转发到主节点或从节点,需要客户端自己区分哪些IP的服务是拥有写权限,哪些是只读权限。
单纯的主从模式,从java客户端的角度来实现读写分离,需要创建两个连接bean,一个连接主节点,另一个连接从节点,并根据命令类型分别调用不同bean的API。哨兵模式配置相对较为简单,客户端只需要连接哨兵节点,就可以定位主节点与从节点,然后进行读写分离操作。
读写分离机制更多的是针对mysql这种需要磁盘IO的数据库,因为磁盘IO是比较耗时的操作,对于Redis这种内存IO的数据库很少能达到这种瓶颈。如果你项目中对于Redis仅仅作为缓存使用,换句话说就是读请求数量远大于写请求数量,并且可以接受一定程度的数据一致性问题,可以考虑使用读写分离。
由于Redis的内存管理机制并不能保证某个Key过期后立马被清除,导致在3.2版本以前会把过期数据同步到从节点,在读写分离的场景下会读取到脏数据,这个问题在3.2版本后修复,使用读写分离的一定要注意Redis的版本号!
2.4 优缺点
优点:
- 数据可以保持多个备份,不会出现由于磁盘损坏导致的数据丢失。
- 主节点向从节点同步数据的过程是异步执行,在此期间正常处理读写请求。
- 读写分离机制有效缓解了主节点的读请求压力。
缺点:
- 仍然不支持自动容错和恢复功能,宕机后需要手动干预。
- 主节点宕机如果没来得及同步到从服务器会引起数据不一致。
- 数据全部存储在一个节点中,无法在线扩容。
3.哨兵模式
哨兵模式是启动一个或多个哨兵节点对多个Redis主从节点进行发现并监控,如果主节点宕机将所属的某个从节点升级为主节点,另外哨兵节点之间也会互相监控。哨兵模式的出现是为了解决Redis集群模式下的自动容错和恢复功能。
3.1 集群架构
3.2 哨兵监控原理
哨兵节点启动后,会根据配置文件确认要监控的master节点,并且与每个主节点建立两条连接:
在连接②中,哨兵节点每10秒向主节点、从节点发送INFO命令。由于配置哨兵监控对象时只需要填写主节点信息,因此通过INFO命令可以获取到主节点的所有从节点,达到自动发现新加入的从节点的目的。对于链式主从模式来说,从节点可能还会配置从节点,因此涉及到的所有从节点也会发送INFO命令。
在连接②中,哨兵节点每2秒向主节点、从节点的sentinel:hello频道发布自己的信息。信息内容包括自身的IP、端口、运行ID等,以此来向其他哨兵宣告自己的存在。
在连接②中,哨兵节点每1秒向主节点、从节点以及其他哨兵节点发送ping命令。接收方必须在指定时间内(down-after-milliseconds)作出正确响应,如果down-after-milliseconds值小于1秒时,那么发送频率会提升到每down-after-milliseconds秒发送一次。
在连接①中,哨兵节点订阅监控目标的sentinel:hello频道,从而得知其他哨兵节点的存在。在订阅到消息后会判断消息中的哨兵节点是否为新加入的节点,如果是则与其建立连接②,然后发送ping命令,并通过这种形式实现哨兵集群的相互监控。
3.2 主观/客观下线
哨兵节点会通过ping命令的形式判断节点是否正常运行,当命令在down-after-milliseconds内未做出正确响应,那么哨兵节点会认为其主观下线。主观下线仅表示哨兵自己认为出故障了,但并不一定是节点出现故障,也有可能是哨兵自己网络出现问题导致与被监控节点无法ping通,因此需要借助其他哨兵进一步判断。
哨兵发送命令给其他哨兵节点,询问此故障节点是否主观下线,如果超过指定数量(参数配置)的哨兵都觉得此故障节点主观下线,那么会被整个哨兵集群认为客观下线。如果故障节点的角色是从节点或其他哨兵节点,需要将其踢掉,如果是主节点挂掉了,那么还要进行故障恢复,考虑到故障恢复只能由一个哨兵去完成,因此需要选举出领头哨兵,处理这个事情。
3.3 故障恢复
领头哨兵获取故障主节点的所有从节点,选出优先级最高的从节点,优先级通过replica-priority参数设置。如果出现优先级相同的情况,则选出复制的偏移量最大的节点,因为偏移量越大代表同步的数据越完整,如果到此还存在优先级相同的从节点,那么选择运行ID(启动时自动生成)最小的那个节点。
- 领头哨兵向选中的从节点发送SLAVEOF no one命令,将其升级为主节点
- 故障的原主节点重新启动后,领头哨兵发送SLAVEOF命令,将其变为新master的slave
3.4 优缺点
优点:
- 哨兵模式是主从模式的升级版,主从模式拥有的优点,哨兵模式都有
- 支持自动容错和恢复功能
- 哨兵支持集群相互监控,稳定性高
缺点:
- 所有数据仍然存储在单节点中,很难在线扩容
- 配置太繁琐
4.分片模式
分片模式是将数据集分成几个小部分,存储在不同的服务节点中,在处理读写请求时,通过对key的hash取余定位到具体的节点,然后转发命令到该节点进行处理。分片模式的出现是为了解决主从模式、哨兵模式下,单节点存储瓶颈以及在线扩容的问题。
早期为了应对哨兵模式单节点存储的弱点,需要借助代理中间件在客户端实现分区存储以及对key的定位查询功能,常见的中间件有codis、twemproxy等。直到Redis发布3.0版本后,才开始在服务端支持分片模式,下面只讲述redis自己提供的分片模式。
4.1 集群架构
图中为3个master节点的分片集群,每个master节点拥有一个slave节点,每个节点(包括slave节点)都会实时同步其他所有节点的信息。服务端只有master节点处理请求(如果需要实现读写分离,需要在客户端进行改造),当请求命令涉及的key不属于本节点负责时,会根据实时同步的节点信息进行转发。
4.2 槽位分配
Redis分片模式在存储数据时采用虚拟哈希槽的方式存储,预先分配16384(2^14)个卡槽,集群内各master节点均摊所有卡槽,并对外提供服务:
服务端接收读到写命令后,通过key定位出所在的节点:
Redis在分片设计上,并没有直接使用一致性哈希算法(hash值%节点数,定位节点),而是在中间添加了一层哈希槽的定位计算。这样节点新增或删除后进行数据转移,只需要操作卡槽到对应节点即可,如果采用一致性哈希算法,还需要遍历节点内所有数据并逐个计算,得出哪些数据需要转移的。
另外卡槽的存储概念仅针对master节点,slave节点仅根据master节点同步的数据正常存储。
4.3 节点伸缩
当对集群进行缩容或扩容后,整个集群的master节点数量发生变化,为了保证所有正常节点对哈希槽的分配是均匀的,需要对所有哈希槽进行重新分配并迁移。
例如三个master节点的集群中,新增一个master节点后的哈希槽分配变化:
图中可以看出哈希槽迁移的范围:
- master-0节点的4097~5461哈希槽转移到master-1节点中
- master-1节点的8193~10922哈希槽转移到master-2节点中
- master-2节点的12289~16384哈希槽转移到master-3节点中
4.4 客户端路由
客户端向服务端发送读写命令时,并不知道此次操作涉及的key到底在哪个分片节点上,因此只能请求集群中的任意一个节点(包括从节点),如果响应的是moved重定向异常,则从异常中获取正确的节点信息并再次请求:
在集群正常运行期间,moved重定向机制完全可以解决key的路由访问,当节点正常退出或者有新节点加入集群后,部分卡槽数据进行迁移。此时moved重定向节点的数据已经迁移到别的节点中,仍然会出现查询不到数据的情况,此时节点会返回ask重定向给客户端,再次请求服务端:
上述的路由转发机制可以看出,数据量过多或者集群节点过多时,客户端的请求会产生大量的重定向,对redis的性能造成影响,因此客户端都会对分片节点负责的哈希槽信息进行缓存,从而减少无用的网络请求,当节点伸缩导致负责的哈希槽变化时,客户端也会做出相应的更新。
比如java提供的Jedis客户端,内部JedisCluster类久实现了类似机制。JedisCluster在创建后会从集群中选择一个正常运行的节点,查询哈希槽的分布情况并将映射关系保存到本地,然后为集群的每个节点建立连接池,所有的请求命令都会先去映射缓存查询,然后再调用指定的连接池发送请求命令。
4.5 故障恢复
分片模式并没有哨兵模式那种单独监控节点实现故障恢复,当某个master节点挂掉后,需要借助集群内其他master节点,从故障master的众多slave节点中选举出一个继续工作。
首先有两种情况会导致redis-cluster不可用,也就是集群无法提供服务:
- 超过半数master节点挂掉(无视对应的slave)
- 某个节点的master、slave全挂
在集群可用的情况下,master挂掉后会触发选举,选出新的master节点:
- slave节点发现自己的master变为FAIL
- slave节点先给自己epoch+1,然后请求集群内其他master节点给自己投票,并将事件广播给其他所有节点
- slave节点发起投票后,会等待至少NODE_TIMEOUT*2时间接受投票结果(最少也会2秒)
- master节点接收到投票后响应FAILOVER_AUTH_ACK,并且在NODE_TIMEOUT*2时间内不给同一master的其他slave投票
- slave接收FAILOVER_AUTH_ACK的epoch如果小于自身,直接丢失
- 如果slave接收到半数以上符合条件的FAILOVER_AUTH_ACK,则声明自己选举成功
- 如果在等待的NODE_TIMEOUT2时间内没有赢得选举,放弃本次选举,然后在NODE_TIMEOUT4时间后重新发起选举
- 选举成功后,广播通知集群内其他节点
节点中的epoch全称为currentEpoch,集群中新加入或重启的节点(无论master或slave)currentEpoch值都为0,某节点需要请求其他节点提供协助时(目前只有选举)currentEpoch+1。当节点接收到其他节点的包时(比如slave发起的选举),如果包中的currentEpoch大于自身的currentEpoch,那么会更新为发送者的currentEpoch。这种规则下集群中所有节点的currentEpoch最终会达成一致,也是代表自身在集群中的通信是健康的。
另外slave节点发现master挂掉后,不会立即发起投票,而是通过延迟公式计算出一定延迟时间后在发起:
延迟公式: 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
延迟发送选举是为了确保master挂掉的信息在整个集群内传开(让子弹飞一会),如果没有延迟的情况下可能部分master接收到选举请求时还没有感知到master挂了,造成误判。在0.5秒的基础上增加随机时间,是为了防止多个slave同时发起选举。公式最后的SLAVE_RANK代表slave节点已经从master节点复制的总量的rank,越小代表复制的越新,这个参数理论上可以保证持有最新数据的slave会先发起选举。
4.6 slave自动迁移
Redis为了保证整个集群的高可用,让每个分片都正常提供服务,某些情况下会对slave节点迁移到别的分片下面。假设某个集群拥有3主3从共6个节点,万一某个master对应的slave发生宕机,这时候就不存在高可用这一说,因为master在发生宕机就没有任何节点可以代替它工作,会导致整个集群不可用。
针对这种情况,集群在发现某个master节点没有任何slave节点时,会将其他master节点多余的slave节点迁移过来,继续保证集群的高可用。因此在搭建集群模式时,一定要多部署几台slave节点备用,提高集群的高可用状态。