31 事务机制

Redis 提供了 MULTI、EXEC 两个命令来完成事务。

原子性

原子性的要求就是一个事务中的多个操作必须都完成,或者都不完成。

情况1:在执行EXEC命令前,客户端发送的命令有错误,且Redis实例检出了(如语法错误)。
结果:执行EXEC时拒绝执行所有命令,返回事务失败。

情况2:在执行EXEC命令前,命令有错误但Redis实例没有检出(如操作数据类型不匹配)。
结果:Redis会对错误命令报错,但还是会把正确的命令执行完。
影响:事务的原子性无法保证。
注意:Redis没有提供回滚机制。

DISCARD命令:主动放弃事务执行,把暂存的命令队列清空。

情况3:执行EXEC时实例故障,导致事务执行失败。
结果1:如果开启了AOF,会有部分日志记录到AOF中,需要使用redis-check-aof命令,将未完成的事务操作剔除,保证原子性。
结果2:如果没有开启AOF,实例重启后数据无法恢复。

由于 RDB 不会在事务执行时执行,所以 RDB 文件中不会记录只执行了一部分的结果数据。之后用 RDB 恢复实例数据,恢复的还是事务之前的数据。

一致性

数据库中的数据在事务执行前后是一致的。

情况1:命令入队时就报错
结果:事务放弃执行,可以保证一致性。

情况2:命令入队时没报错,执行时报错
结果:错误命令不会执行,可以保证一致性
解析:一致的概念是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据,只要没有执行错误的命令,那么就保证了一致性。

情况3:EXEC命令执行时实例故障
结果1:没有开启RDB或AOF,数据丢失,可以保证一致性。
结果2:使用RDB快照,RDB不会在事务中执行,可以保证一致性。
结果3:使用AOF日志,需要使用redis-check-aof清理事务中已完成的操作,清理后数据一致性。

总结:Redis可以保证事务一致性。

隔离性

数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。

事务的隔离性受并发操作的影响,分为EXEC执行前和执行后两个阶段。

  1. EXEC执行前并发:需要使用WATCH机制保证隔离性;
  2. EXEC执行后并发:可以保证隔离性。

WATCH机制

在事务执行前,监控一个或多个键的值变化情况,当事务调用 EXEC 命令执行时,WATCH 机制会先检查监控的键是否被其它客户端修改了。

  1. 如果修改了,就放弃事务执行,避免事务的隔离性被破坏。
  2. 客户端可以再次执行事务,如果没有并发修改事务数据的操作了,事务就能正常执行,保证了隔离性。

Redis 是用单线程执行命令,EXEC 命令执行后,Redis 会保证先把命令队列中的所有命令执行完。所以,在这种情况下,并发操作不会破坏事务的隔离性。

持久性

数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。

情况1:未开启AOF和RDB,重启数据丢失,没有持久性。
情况2:RDB模式,如果在事务执行后,RDB快照执行前宕机,数据会丢失。
情况3:AOF三种配置都存在数据丢失情况(always会丢失一个事件循环的数据)。
解析:
每次执行客户端命令的时候操作并没有写到aof文件中,只是写到了aof_buf内存当中,当进行下一个事件循环(event_loop)的时候执行beforeSleep之时,才会去fsync到disk中。

结论:Redis无法保证事务的持久性。

32 主从同步与故障切换

Redis 的主从同步机制不仅可以让从库服务更多的读请求,分担主库的压力,而且还能在主库发生故障时,进行主从库切换,提供高可靠服务。

主从数据不一致

主从数据不一致,指的是客户端从库中读取到的值和主库中的最新值不一致。

原因:主从库的命令复制是异步进行的。

  1. 主库收到新的写命令,发送给从库;
  2. 主库在本地执行命令后,向客户端返回结果
  3. 如果从库没有执行命令,主从数据就不一致了

从库滞后执行命令原因:

  1. 网络传输延迟
  2. 从库阻塞(执行集合操作等复杂命令)

解决方案:外部程序监控

  1. Redis的INFO replication命令,可以查看主库接收写命令的进度信息(master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset)
  2. 开发监控程序监控master_repl_offset和slave_repl_offset的差值,超过阈值客户端不再读该从库

读取过期数据

使用Redis主从集群时,有时会读取到过期数据,这是由Redis的过期数据删除策略引起的。

Redis过期数据删除策略:

  • 惰性删除:数据过期后不会立即删除,而是等到有请求读写该数据时进行检查,发现过期时再删除。

    • 优点:减少CPU资源使用。
    • 缺点:占用内存。
  • 定期删除:每隔一段时间(默认100ms)随机选出一些数据判断是否过期,删除过期数据。

情况1:
如果客户端从主库读取过期数据,主库会触发删除操作;
如果客户端从库读取过期数据,从库不会触发删除操作,会返回空值(3.2以上版本)。

情况2:
Redis设置过期时间命令在从库上可能被延后,此时可能读取到过期数据。

过期时间命令:

  • EXPIRE:设置存活时间x秒
  • PEXPIRE:设置存活时间x毫秒
  • EXPIREAT:设置过期时间戳(秒)
  • PEXPIREAT:设置过期时间戳(毫秒)

建议:

  1. 在业务应用中使用 EXPIREAT/PEXPIREAT 命令,把数据的过期时间设置为具体的时间点,避免读到过期数据。
  2. 注意主从节点时钟要一致,让主从节点和相同的NTP(时间)服务器进行时钟同步。

不合理配置项

  1. protected-mode 配置项:限定哨兵能否被其它服务器访问,设置yes时哨兵间无法通信,无法判断主库下线,造成redis不可用。

    1. 注意将protected-mode设置为no
    2. bing配置项设置为其它哨兵实例的IP地址
  2. cluster-node-timeout配置项:设置集群实例响应心跳消息超时时间。如果主从且切换时间较长,会导致实例心跳超时;如切换实例超过半数,会被Redis Cluster判断为异常,导致集群挂掉。

    1. 建议设置为10~20秒
  3. slave-server-stale-data配置项:设置从库能否处理数据读写命令,yes可能处理到过期数据,建议设置为no,在主从失去链接或信息同步时,slave会对除了INFO和SLAVEOF外的所有命令回复"SYNC with master in progress"
  4. slave-read-only配置项:从库能否处理写命令(只读),yes时只能处理读请求,无法处理写请求。

33 脑裂

脑裂指主从集群中,同时有两个主节点,都能接收读写请求,导致不同客户端向不同主节点写入数据,导致数据丢失。

数据丢失原因:
从库升级为主库后,原主库和新主库重新进行全量同步,需要清空本地数据,加载新主库的RDB文件,切换期间原主库写入的数据就丢失了。

查找原因

1.确认数据同步:
主从集群数据丢失最常见原因是主库的数据还没有同步到从库时,主库发生故障,从库升级为主库后,未同步数据丢失。
判断方法:计算master_repl_offset 和 slave_repl_offset 的差值,如果slave小于master,则数据丢失是由于同步未完成导致的。

2.排查客户端操作日志:
如果主从切换后,有客户端仍在和原主库通信,则认为发生了脑裂。

3.脑裂原因:原主库假故障
主库下线原因:超过(quorum配置)的哨兵实例和主库心跳都超时了,判断主库客观下线,哨兵开始执行切换。切换完成后,客户端和新主库进行通信。
主库假故障:主库没有响应哨兵心跳,被判断为客观下线,在没有完成主从切换时,又重新处理请求了,此时客户端仍可以和原主库通信,写入数据。

解决方案

1.限制主库请求处理:

  • min-salves-to-write:设置主库能进行数据同步的最少从库数量;
  • min-slaves-max-lag:设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。

这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。
组合后主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。

建议:从库有 K 个,可以将 min-slaves-to-write 设置为 K/2+1(如果 K 等于 1,就设为 1),将 min-slaves-max-lag 设置为十几秒(例如 10~20s),在这个配置下,如果有一半以上的从库和主库进行的 ACK 消息延迟超过十几秒,我们就禁止主库接收客户端写请求。

CAP

使用 CAP 理论来分析一下课程的内容:

  1. redis 集群允许脑裂存在,其实是一种可用性高的特征,但不保证数据一直。
  2. redis 通过设置两个参数,一定程度上其实是在降低可用性,以提供数据一致性。
  3. 为什么愿意降低可用性?因为那部分的数据会因为主从切换而丢失,所以宁愿不可用。

Redis本身不支持强一致性,因为保证强一致性的代价太大,从CAP角度看,就是放弃C,选择A和P。

min-slaves-to-write 和 min-slaves-max-lag的设置,是为了避免主从数据不一致程度加重。两个参数起作用时,相当于对主库做降级,放弃了A,选择C和P。

35 Codis集群方案

Codis 集群中包含了 4 类关键组件。

  • codis server:这是进行了二次开发的 Redis 实例,其中增加了额外的数据结构,支持数据迁移操作,主要负责处理具体的数据读写请求。
  • codis proxy:接收客户端请求,并把请求转发给 codis server。
  • Zookeeper 集群:保存集群元数据,例如数据位置信息和 codis proxy 信息。
  • codis dashboard 和 codis fe:共同组成了集群管理工具。其中,codis dashboard 负责执行集群管理工作,包括增删 codis server、codis proxy 和进行数据迁移。而 codis fe 负责提供 dashboard 的 Web 操作界面,便于我们直接在 Web 界面上进行集群管理。

工作流程

  1. 先使用 codis dashboard 设置 codis server 和 codis proxy 的访问地址,然后它们开始接收连接。
  2. 当客户端要读写数据时,客户端直接和 codis proxy 建立连接(RESP 交互协议)。
  3. codis proxy 接收到请求,根据映射关系把请求转发给相应的 codis server 进行处理,处理完成后由proxy再返回数据给客户端。

数据分布

  1. Codis 集群一共有 1024 个 Slot(逻辑槽),手动或自动分配给codis server。
  2. 客户端读取数据时,使用CRC32算法计算key的哈希值,对1024取模,对应slot编号。
  3. Slot 和 codis server 的映射关系称为数据路由表(简称路由表),保存在proxy和Zookeeper。
  4. 实例增减时,路由表被修改,dashbaord会把新表发给proxy。

注:Redis Cluster中,路由表在每个实例上保存一份,变化时需要在所有实例间传递。

集群扩容

增加server:
Codis 集群按照 Slot 的粒度进行数据迁移,我们来看下迁移的基本流程。

  1. 在源 server 上,Codis 从要迁移的 Slot 中随机选择一个数据,发送给目的 server。
  2. 目的 server 确认收到数据后,会给源 server 返回确认消息。这时,源 server 会在本地将刚才迁移的数据删除。
  3. 第一步和第二步就是单个数据的迁移过程。Codis 会不断重复这个迁移过程,直到要迁移的 Slot 中的数据全部迁移完成。

同步迁移:数据发送到目的server并执行完的过程中,源server阻塞,无法处理新请求。
异步迁移:

  1. 目的server收到数据后返回ACK,表示迁移完成,源server删除数据,不需要等命令执行完。
  2. 拆分指令:对bigkey中每个元素用一条指令迁移,并设置临时过期时间,迁移失败后会过期删除。

增加proxy:
codis proxy 的访问连接信息都会保存在 Zookeeper 上。
当新增了 proxy 后,Zookeeper 上会有最新的访问列表,客户端也就可以从 Zookeeper 上读取 proxy 访问列表,把请求发送给新增的 proxy。

可靠性

Codis server 其实就是 Redis 实例,只不过增加了和集群操作相关的命令。

Codis 给每个 server 配置从库,并使用哨兵机制进行监控,当发生故障时,主从库可以进行切换,从而保证了 server 的可靠性。

codis proxy 使用 Zookeeper 集群保存路由表,可以充分利用 Zookeeper 的高可靠性保证来确保 codis proxy 的可靠性。

当 codis proxy 发生故障后,直接重启 proxy 就行。重启后的 proxy,可以通过 codis dashboard 从 Zookeeper 集群上获取路由表,然后,就可以接收客户端请求进行转发了。

方案选择

  1. Codis更成熟稳定
  2. 单实例客户端改集群,选Codis可以避免修改业务应用中的客户端
  3. Codis不支持Redis新版本(>3.2.8)命令和数据类型
  4. Codis支持异步迁移,集群迁移比较频繁时优先选择

建议:
当你有多条业务线要使用 Codis 时,可以启动多个 codis dashboard,每个 dashboard 管理一部分 codis server,同时,再用一个 dashboard 对应负责一个业务线的集群管理,这样,就可以做到用一个 Codis 集群实现多条业务线的隔离管理了。


IT小马
1.2k 声望166 粉丝

Php - Go - Vue - 云原生