集群 - 数据一致性

引用

CAP理论:在一个分布式系统中,C:一致性,A可用性,P:分区容错性不可兼得

Redis集群

主从数据一致性

过程1:初次全量同步

当一个redis服务器初次向主服务器发送salveof命令时,redis从服务器会进行一次全量同步,同步的步骤如下图所示:
在这里插入图片描述

  • slave服务器向master发送psync命令(此时发送的是psync ? -1)。
  • master接收到psync命令后会进行BGSAVE命令生成RDB文件快照。
  • 生成完后,会将RDB文件发送给slave。
  • slave接收到文件会载入RDB快照,并且将数据库状态变更为master在执行BGSAVE时的状态一致。
  • master会发送保存在缓冲区里的所有写命令,slave执行这些写命令完成同步。

过程2:命令传播

slave已经同步过master了,那么如果后续master进行了写操作,比如说一个简单的set name redis,那么master执行过当前命令后,会将当前命令发送给slave执行一遍,达成数据一致性。

过程3:重新复制

当slave断开重连之后会进行重新同步,重新同步分完全同步和部分同步

首先来看看部分同步大致的走向
在这里插入图片描述

  • 当slave断开重连后,会发送psync 命令给master。
  • master收到psync后会返回+continue回复,表示slave可以执行部分同步了。
  • master发送断线后的写命令给slave
  • slave执行写命令。

实际上当slave发送psync命令给master之后,master还需要根据以下三点判断是否进行部分同步。

先来介绍一下是哪三个方面:

服务器运行ID

每个redis服务器开启后会生成运行ID。

当进行初次同步时,master会将自己的ID告诉slave,slave会记录下来,当slave断线重连后,发现ID是这个master的就会尝试进行部分重同步。当ID与现在连接的master不一样时会进行完整重同步。

复制偏移量

复制偏移量包括master复制偏移量和slave复制偏移量,当初次同步过后两个数据库的复制偏移量相同,之后master执行一次写命令,那么master的偏移量+1,master将写命令给slave,slave执行一次,slave偏移量+1,这样版本就能一致。

复制积压缓冲区

复制积压缓冲区是由master维护的固定长度的先进先出的队列。

当slave发送psync,会将自己的偏移量也发送给master,当slave的偏移量之后的数据在缓冲区还存在,就会返回+continue通知slave进行部分重同步。

当slave的偏移量之后的数据不在缓冲区了,就会进行完整重同步。

结合以上三点,我们又可以总结下:
  • 当slave断开重连后,会发送psync 命令给master。
  • master首先会对服务器运行进行判断,如果与自己相同就进行判断偏移量
  • master会判断自己的偏移量与slave的偏移量是否一致。
  • 如果不一致,master会去缓冲区中判断slave的偏移量之后的数据是否存在。
  • 如果存在就会返回+continue回复,表示slave可以执行部分同步了。
  • master发送断线后的写命令给slave
  • slave执行写命令。

过程总结:主从同步最终流程

在这里插入图片描述

集群中的数据倾斜问题(数据量倾斜&数据访问倾斜)

对于分布式系统而言,整个集群处理请求的效率和存储容量,往往取决于集群中响应最慢或存储增长最快的节点。所以在系统设计和容量规划时,我们尽量保障集群中各节点的“数据和请求分布均衡“。但在实际生产系统中,出现数据容量和请求倾斜(类似Data Skew)问题是比较常见的。
redis分布式集群倾斜问题,主要分为两类:

  • 数据量倾斜:数据存储容量倾斜,数据存储总是落到集群中少数节点;
  • 数据访问倾斜:qps请求倾斜,qps总是落到少数节点。

数据量倾斜

bigkey导致倾斜

什么是 bigkey :我们将含有较大数据或含有大量成员、列表数据的 Key 称之为大Key。

  • 一个 STRING 类型的 Key,它的值为 5MB(数据过大)
  • 一个 LIST 类型的 Key,它的列表数量为 20000 个(列表数量过多)
  • 一个 ZSET 类型的 Key,它的成员数量为 10000 个(成员数量过多)
  • 一个 HASH 格式的 Key,它的成员数量虽然只有 1000 个但这些成员的 value 总大小为 100MB(成员体积过大)

如果某个实例中保存了 bigkey ,那么就有可能导致集群的数据倾斜。

bigkey 存在问题

  • 内存空间不均匀:如果采用切片集群的部署方案,容易造成某些实例节点的内存分配不均匀;
  • 造成网络拥塞:读取 bigkey 意味着需要消耗更多的网络流量,可能会更多 Redis 服务器造成影响;
  • 过期删除:bigkey 不但读写慢,删除也慢,删除过期 bigkey 也比较耗时;
  • 迁移困难:由于数据庞大,备份和还原也容易造成阻塞,操作失败;
如何避免

对于 bigkey 可以从以下两个方面进行处理

1、合理优化数据结构

  • 对较大的数据进行压缩处理;
  • 拆分集合:将大的集合拆分成小集合(如以时间进行分片)或者单个的数据。

2、选择其他的技术来存储 bigkey ;

  • 使用其他的存储形式,考虑是否使用 cdn 或者文档性数据库 MongoDB。
Slot分配不均衡导致倾斜

例如在 Redis Cluster 通过 Slot 来给数据分配实例

  • Redis Cluster 方案采用哈希槽来处理 KEY 在不同实例中的分布,一个切片集群共有 16384 个哈希槽,这些哈希槽类似于数据分区,每个键值都会根据它的 key,被映射到一个哈希槽中;
  • 一个 KEY ,首先会根据 CRC16 算法计算一个16 bit的值;然后,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
  • 然后把哈希槽分配到所有的实例中,例如,如果集群中有N个实例,那么,每个实例上的槽个数为16384/N个。

如果 Slot 分配的不均衡,就会导致某几个实例中数据量偏大,进而导致数据倾斜的发生。

出现这种问题,我们就可以使用迁移命令把这些 Slot 迁移到其它实例上,即可。

Hash Tag导致倾斜

Hash Tag 用于 redis 集群中,其作用是将具有某一固定特征的数据存储到同一台实例上。其实现方式为在 key 中加个 {} ,例如 test{1} 。

使用 Hash Tag 后客户端在计算 key 的 crc16 时,只计算 {} 中数据。如果没使用 Hash Tag ,客户端会对整个 key 进行 crc16 计算。
在这里插入图片描述这样通过 Hash Tag 就可以将某一固定特征数据存储到一台实例上,避免逐个查询集群中实例。

栗如:如果我们进行事务操作或者数据的范围查询,因为 Redis Cluster 和 Codis 本身并不支持跨实例的事务操作和范围查询,当业务应用有这些需求时,就只能先把这些数据读取到业务层进行事务处理,或者是逐个查询每个实例,得到范围查询的结果。

Hash Tag 潜在的问题就是,可能存在大量数据被映射到同一个实例的情况出现,导致集群的数据倾斜,集群中的负载不均衡。

所有当我使用 Hash Tag 的时候就做好评估,我们的业务诉求如果不使用 Hash Tag 可以解决吗,如果不可避免的使用,我们需要评估好数据量,尽量避免数据倾斜的出现。

数据访问倾斜

虽然每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁,这就是数据访问倾斜。

数据量访问倾斜的罪魁祸首就是 Hot Key

切片集群中的 Key 最终会存储到集群中的一个固定的 Redis 实例中。某一个 Key 在一段时间内访问远高于其它的 Key,也就是该 Key 对应的 Redis 实例,会收到过大的流量请求,该实例容易出现过载和卡顿现象,甚至还会被打挂掉。

常见引发热点 Key 的情况:

  • 新闻中的热点事件;

  • 秒杀活动中的,性价比高的商品;

如何发现 Hot Key

1、提现预判;
根据业务经验进行提前预判;

2、在客户端进行收集;
通过在客户端增加命令的采集,来统计发现热点 Key;

3、使用 Redis 自带的命令排查;
使用monitor命令统计热点key(不推荐,高并发条件下会有造成redis 内存爆掉的隐患);

hotkeys参数,redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。但是该参数在执行的时候,如果key比较多,执行起来比较慢。

4、在Proxy层做收集
如果集群架构引入了 proxy,可以在 proxy 中做统计

5、自己抓包评估
Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。自己写程序监听端口,按照RESP协议规则解析数据,进行分析。缺点就是开发成本高,维护困难,有丢包可能性。

Hot Key 如何解决

知道了 Hot Key 如何来应对呢

  • 对 Key 进行分散处理
    有一个热 Key 名字为 Hot-key-test ,可以将其分散为 Hot-key-test1 , Hot-key-test2 …然后将这些 Key 分散到多个实例节点中,当客户端进行访问的时候,随机一个下标的 Key 进行访问,这样就能将流量分散到不同的实例中了,避免了一个缓存节点的过载。
    一般来讲,可以通过添加后缀或者前缀,把一个 hotkey 的数量变成 redis 实例个数 N 的倍数 M,从而由访问一个 redis key 变成访问 N * M 个redis key。 N*M 个 redis key 经过分片分布到不同的实例上,将访问量均摊到所有实例。
const M = N * 2
//生成随机数
random = GenRandom(0, M)
//构造备份新key
bakHotKey = hotKey + “_” + random
data = redis.GET(bakHotKey)
if data == NULL {
    data = GetFromDB()
    redis.SET(bakHotKey, expireTime + GenRandom(0,5))
}
  • 使用本地缓存
    业务端还可以使用本地缓存,将这些热 key 记录在本地缓存,来减少对远程缓存的冲击。
    这里,有个地方需要注意下,热点数据多副本方法只能针对只读的热点数据。如果热点数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会带来额外的开销。
    对于有读有写的热点数据,我们就要给实例本身增加资源了,例如使用配置更高的机器,来应对大量的访问压力。

redis集群出现倾斜的影响

倾斜问题对于redis这类纯内存和单线程服务影响较大,存在以下痛点:

qps集中到少数redis节点,引起少数节点过载,会拖垮整个服务,同时集群处理qps能力不具备可扩展性;

数据容量倾斜,导致少数节点内存爆增,出现OOM Killer和集群存储容量不具备可扩展性;

运维管理变复杂,类似监控告警内存使用量、QPS、连接数、redis cpu busy等值不便统一;

因集群内其他节点资源不能被充分利用,导致redis服务器/容器资源利率低;

增大自动化配置管理难度;单集群节点尽量统一参数配置;

分析完影响,那我们再看生产环境中,导致Redis集群严重“倾斜”的常见原因。

Redis集群倾斜问题的排查方式

排查节点热点key,确定top commands.

当集群因热点key导致集群qps倾斜,需快速定位热点key和top commands。可使用开源工具redis-faina,或有实时redis分析平台更好。

以下是使用redis-faina工具分析,可见两个前缀key的QPS占比基本各为50%, 明显热点key;也能看到auth命令的异常(top commands)。

Overall Stats
========================================
Lines Processed         100000
Commands/Sec            7276.82
 
Top Prefixes
========================================
ar_xxx         49849   (49.85%)
 
Top Keys
========================================
c8a87fxxxxx        49943   (49.94%)
a_r:xxxx           49849   (49.85%)
 
Top Commands
========================================
GET             49964   (49.96%)
AUTH            49943   (49.94%)
SELECT          88      (0.09%)
系统是否使用较大的集合键

系统使用大key导致集群节点容量或qps倾斜,比如一个5kw字段的hash key, 内存占用在近10GB,这个key所在slot的节点的内存容量或qps都很有可能倾斜。

这类集合key每次操作几个字段,很难从proxy或sdk发现key的大小。

可使用redis-cli --bigkeys 分析节点存在的大键。如果需全量分析,可使用redis-rdb-tools(https://github.com/sripathikrishnan/redis-rdb-tools) 对节点的RDB文件全量分析,通过结果size_in_bytes列得到大key的占用内存字节数。

示例使用redis-cli 进行抽样分析:

redis-cli  --bigkeys -p 7000                                 
 
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'key:000000019996' with 1024 bytes
[48.57%] Biggest list   found so far 'mylist' with 534196 items
-------- summary -------
Sampled 8265 keys in the keyspace!
Total key length in bytes is 132234 (avg len 16.00)
 
Biggest string found 'key:000000019996' has 1024 bytes
Biggest   list found 'mylist' has 534196 items
 
8264 strings with 8460296 bytes (99.99% of keys, avg size 1023.75)
1 lists with 534196 items (00.01% of keys, avg size 534196.00)
检查集群每个分片的数据槽分配是否均匀

下面以Redis Cluster集群为例确认集群中,每个节点负责的数据槽位(slots)和key个数。下面demo的部分实例存在不轻度“倾斜”但不严重,可考虑进行reblance.

redis-trib.rb info redis_ip:port
nodeip:port (5e59101a...) -> 44357924 keys | 617 slots | 1 slaves.
nodeip:port (72f686aa...) -> 52257829 keys | 726 slots | 1 slaves.
nodeip:port (d1e4ac02...) -> 45137046 keys | 627 slots | 1 slaves.
---------------------省略------------------------
nodeip:port (f87076c1...) -> 44433892 keys | 617 slots | 1 slaves.
nodeip:port (a7801b06...) -> 44418216 keys | 619 slots | 1 slaves.
nodeip:port (400bbd47...) -> 45318509 keys | 614 slots | 1 slaves.
nodeip:port (c90a36c9...) -> 44417794 keys | 617 slots | 1 slaves.
[OK] 1186817927 keys in 25 masters.
72437.62 keys per slot on average.
系统是否大量使用keys hash tags

在redis集群中,有些业务为达到多键的操作,会使用hash tags把某类key分配同一个分片,可能导致数据、qps都不均匀的问题。可使用scan扫描keyspace是否有使用hash tags的,或使用monitor,vc-redis-sniffer工具分析倾斜节点,是否大理包含有hash tag的key。

是否因为client output buffer异常,导致内存容量倾斜

确认是否有client出现output buffer使用量异常,引起内存过大的问题;比如执行monitor、keys命令或slave同步full sync时出现客户端输入缓冲区占用过大。

这类情况基本redis实例内存会快速增长,很快会出现回落。通过监测client输出缓冲区使用情况;分析见下面示例:

# 通过监控client_longest_output_list输出列表的长度,是否有client使用大量的输出缓冲区.
redis-cli  -p 7000 info clients
# Clients
connected_clients:52
client_longest_output_list:9179
client_biggest_input_buf:0
blocked_clients:0
 
# 查看输出缓冲区列表长度不为0的client。 可见monitor占用输出缓冲区370MB
redis-cli  -p 7000 client list | grep -v "oll=0"
id=1840 addr=xx64598  age=75 idle=0 flags=O obl=0 oll=15234 omem=374930608 cmd=monitor

MySQL与中间件数据一致性解决方案

方案1:延时双删策略

先删除缓存,再更新数据库,休眠一会再次删除缓存:可避免读取缓存中的脏数据

方案2:删除缓存重试机制

不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢?
删除失败会导致脏数据哦~
删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入删除缓存重试机制
在这里插入图片描述

删除缓存重试机制的大致步骤:
写请求更新数据库
缓存因为某些原因,删除失败
把删除失败的key放到消息队列
消费消息队列的消息,获取要删除的key
重试删除缓存操作

方案3:使用canal结合rockemq基于mysql数据库增量日志解析,提供增量数据订阅和消费

工作原理

  • canal模拟mysql slave的交互协议,伪装成为mysql的slave,向mysql master发送dump协议,订阅mysql的binlog二进制文件
  • mysql master收到dump请求,binlog文件发生改变时开始推送增量日志给canal服务器
  • canal服务器将改变的数据转换成json数据发送给canal客户端

在这里插入图片描述

canal架构

server代表一个canal运行实例,对应于一个jvm

instance对应于一个数据队列 (1个server对应1…n个instance)

instance模块:

  • eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)
  • eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)
  • eventStore (数据存储)
  • metaManager (增量订阅&消费信息管理器)
    在这里插入图片描述
    大致的解析过程如下:
  • parse解析MySQL的Bin log,然后将数据放入到sink中
  • sink对数据进行过滤,加工,分发
  • store从sink中读取解析好的数据存储起来
  • 然后自己用设计代码将store中的数据同步写入Redis中就可以了
  • 其中parse/sink是框架封装好的,我们做的是store的数据读取那一步
    在这里插入图片描述

只是这个同步不是实时的,修改数据的不会马上被监听到,我觉得可能跟mysql底层有关,在InnoDB下,当有数据更新的时候,会将数据记录到redo log中,然后更新内存,这时候数据标识已经更新了,当系统空闲或者数据满了的时候,才会把redo log里的数据更新到磁盘中,之前配置的偏移量也跟redo log的工作原理相关,它通过两个指针操作,
在这里插入图片描述

write pos记录当前记录位置, check point是擦除的位置,只有两者之间有空缺才可以写入记录,重合就无法更新记录了,需要擦除一部分记录,也就是要同步一部分数据到磁盘中才可以继续写入。

MySQL主从复制同步延迟解决方案

缓存路由大法

这种方案与中间件的方案流程比较类似,不过改造成本相对较低,不需要增加任何中间件。
在这里插入图片描述

这时流程如下:

写请求发往主库,同时缓存记录操作的 key,缓存的失效时间设置为主从的延时

读请求首先判断缓存是否存在

若存在,代表刚发生过写操作,读请求操作主库
若不存在,代表近期没发生写操作,读请求操作从库
这种方案相对中间件的方案成本较低,但是呢我们此时又引入一个缓存组件,所有读写之间就又多了一步缓存操作。

MySQL同步ES、Redis的同步延迟解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值