各中间件的横向对比
内容太多,持续更新。建议收藏
可靠性问题
如何保证单机可靠性
| 中间件 | 如何保证单机可靠性 |
|---|
| 通用 | 1. 使用 O_Append 格式打开文件可以保证多进程, 多线程追加写入都是安全的. (NFS系统除外) |
| Kafka | kafka默认情况下, 单机不保证可靠性, 只负责写入内存, 由OS负责磁盘刷新机制。kafka 可配置日志写入级别,支持fsync写入 |
| mysql | 使用redolog , 默认刷盘机制为同步刷。redolog会使用两阶段提交落盘, 当节点故障后redolog和binlog可实现崩溃恢复 |
| ElasticSearch | 使用translog 保证, 刷新磁盘可控制。节点故障后, 可以使用translog进行崩溃恢复.控制translog大小,避免崩溃恢复时间过长。和mysql类似. 但是由于es不存在binlog,所以不需要两阶段提交 |
| zookeeper | master节点每次接受请求的时候 先写事务日志, 同时向从节点广播该决议, 从节点也是先写事务日志. 等半数都成功后, commit 数据到内存中。事务日志可保证 数据可靠性, 参数配置提供了同步和异步刷盘机制, 默认是同步刷盘 |
| redis | AOF 日志追加写入 |
| hbase | |
部分写失败
写入磁盘过程中OS突然宕机, 造成部分写失败。只有 Mysql会更新原数据文件所以有这个问题, 其他中间件都是写新文件,所以不存在部分写问题
| 中间件 | 是否考虑部分写 |
|---|
| kafka | 每次都追加写入 log末尾。不用考虑部分写问题 |
| mysql | double write机制。由于redolog是增量日志, 崩溃时如果页已经不完整, 是不能使用增量日志的, mysql采用将脏页率先 顺序写入磁盘, 然后再次写数据页方式, 如果数据页部分写失败, 可以使用备份恢复 |
| ElasticSearch | es每次都是新增一个segment, 不存在文件更新问题, 所以当segment新文件写失败, 只需要使用translog恢复就可以了. 而且es可以通过备份节点恢复数据!!! |
| zookeeper | zookeeper 的快照发生在 事务日志堆积过多的时候, 此时 zk 会选择全量备份数据到磁盘, 所以不存在部分写问题. |
| redis | |
| hbase | |
如何保证数据分布式可靠性
| 中间件 | 如何保证数据分布式可靠性 |
|---|
| kafka | follower副本同步数据, 由producer负责写入的可靠性, 可选择忽略写入成功, 只保证leader成功, 和ISR列表都成功. 失败producer则再次重试(非常明智的选择, 把一致性选择前置到生产者) |
| mysql | binlog可以实现数据复制. 但是有一定的延迟. 所以mysql崩溃后, 从节点可能丢数据。binlog在事务commit时 追加写入 |
| ElasticSearch | 每次写入请求都是一个同步写入, 即ES主分片会同步请求到备份上. 但是备份同步失败, 此次请求也是成功的. 客户端无法感知备份失败, 此时如果主节点挂了, 也没写磁盘,所以会丢数据. |
| zookeeper | |
| redis | |
| hbase | |
| |
崩溃如何恢复数据
| 中间件 | 崩溃如何恢复数据 |
|---|
| kafka | |
| mysql | |
| ElasticSearch | |
| zookeeper | |
| redis | |
| hbase | |
| |
WAL 日志叫什么
| 中间件 | 如何保证数据分布式可靠性 |
|---|
| kafka | Log |
| mysql | redolog |
| ElasticSearch | translog (transaction) |
| zookeeper | transaction log |
| redis | AOF read only file |
| hbase | WAL日志写入分为四个级别 1. 无 WAL 2. 异步 WAL 3 同步刷到 HDFS 内存 4, 同步刷新到 HDFS磁盘 |
WAL 日志何时刷新
WAL 日志一般 可以设置 同步flush 进磁盘 也可以异步。异步一般会定时刷新进磁盘
如果 WAL 文件如果有最大限制, 会在文件满了刷新进磁盘. 也可能定义请求量, 例如每 1w 次请求刷新一次LOG。
刷新日志也要考虑 磁盘写入毛刺问题, 避免瞬间大量刷新数据到磁盘, 要定时+IO限速(例如美团就对 kafka 的刷磁盘做了限速,解决毛刺).
hbase 也对日志写入做限速. 合并 hFile 也会限速.(合并写入速度上下限[10M, 20M 之间] 可根据磁盘性能做更改)
Es 同样也做了段合并时磁盘写入的限速. (同样做了合并线程数, 如果 hdd 使用 1 个线程,ssd 可以设置大一些)
为什么可靠性问题这么重要
如果出现数据丢失如何解决呢?
如果出现一个分片不可用, 导致客户端雪崩?
如果数据不一致, 导致查询结果不一致呢?
总结
mysql的数据可靠性问题包括redolog 是否同步写磁盘、binlog是否同步写磁盘、如果数据页还在buffer pool, mysql崩溃如何、如果数据页还在buffer pool或者os cache, 数据崩溃怎么办、数据页回写磁盘写到一半断电如何处理、mysql master 磁盘故障怎么办
一致性问题
如何保证一致性
| 中间件 | 如何保证一致性 |
|---|
| kafka | kafka 的consumer只会消费 已经同步到副本的数据, 而且只有leader分区提供读写,所以consumer的读取一致性, 可以保证. 写入数据暂时不一致 |
| mysql | mysql写入时无法做到 同步写入. 无法保证主从数据强一致性. |
| ElasticSearch | 两个地方无法保证一致性。search主从分片无法保证搜索一致性, 因为各个节点 refresh 创建新分段 是完全独立的。写入过程没有强一致性保证, 全部读写成功, leader不关心备份写入失败. |
| zookeeper | zookeeper保证的是最终一致性, 无法保证同一时刻, 客户端可看见已知数据.但保证client看到已成功的数据一定不会丢失, (半数写成功), 此刻一致性, 代表客户端可保证最终得到一致的数据视图。原子性:更新操作要么成功要么失败,不存在中间状态。顺序一致性: 来自同一客户端的更新将按顺序进行。及时性:客户端会及时获取ZooKeeper最新状态。不一致之处,由于半数以上提交即可成功,主从节点 某一个时刻, 读取数据是可能不一致的. |
| redis | |
| hbase | |
| |
一致性问题
| 中间件 | 一致性问题 |
|---|
| kafka | 主副本同步数据不一致.但是不会影响 consumer端消费 |
| mysql | 1. 主从数据不一致 因为 binlog 异步复制。2. 避免 mysql 存储大数据对象,导致带宽被占满 |
| ElasticSearch | 主从数据由于写入不同步导致一致性 。refresh 不同步导致搜索结果不一致。refresh 后再内存中的数据没有及时刷新到磁盘, 导致主从磁盘数据不一致。可指定主分片查询 |
| zookeeper | |
| redis | |
| hbase | |
| |
如何主从数据同步
| 中间件 | 如何主从数据同步 |
|---|
| kafka | producer可以同步等待复制全部完成。ISR列表定期+通知拉取消息。半异步复制 |
| mysql | binlog 全异步复制 |
| ElasticSearch | 每次写入请求会同步数据、半异步复制 |
| zookeeper | 半异步复制 |
| redis | 主从同步 采用。全异步复制 |
| hbase | |
| |
实时性
| 中间件 | 实时性 |
|---|
| kafka | ISR follower同步副本后, 才会被消费 |
| mysql | 实时 |
| ElasticSearch | 近实时 |
| zookeeper | |
| redis | |
| hbase | |
| |
隔离性
| 中间件 | 隔离性 |
|---|
| kafka | 完全独立 |
| mysql | 事务锁竞争 |
| ElasticSearch | 乐观版本控制 |
| zookeeper | |
| redis | |
| hbase | |
| |
读写优化
数据写入优化
| 中间件 | 数据写入优化 |
|---|
| kafka | 追加写入 +异步刷新磁盘+ 分区 |
| mysql | 写入内存+ redolog 异步追加磁盘写 |
| ElasticSearch | 使用分片。写入内存+异步新建分段(耗费cpu+批量聚合)+ 定时刷新分段到磁盘。translog追加磁盘写 |
| zookeeper | |
| redis | |
| hbase | |
| |
数据写入速度
| 中间件 | 数据写入速度 |
|---|
| kafka | 写内存+并发写 |
| mysql | 受限于 写锁, 存在热点问题. 插入不受影响 |
| ElasticSearch | 受限于内存建索引速度+ 并发写入 |
| zookeeper | 同步串行写 |
| redis | 串行写(redis cluster可并发) |
| hbase | 完全并发写(更新+删除+新增都是 插入新纪录) |
| |
数据读优化
| 中间件 | 数据读优化 |
|---|
| kafka | 由于kafka的消息基于分区消费, 不需要对消息额外的处理, 可以使用零拷贝技术, 直接发送到网卡。无从库,但有分区 |
| mysql | 索引+ buffer 缓冲池。查询走从库 |
| ElasticSearch | 分片 副本可读。segement只读属性, 可以方便加载进pagecache (mmap后可不用上下文切换), es使用mmap读取索引文件, pageCache 最好要占用OS一半以上内存。(mysql和es的数据从pagecache检索到 需要用户态处理下, 所以无法使用零拷贝。mysql select,分页, Es 对数据聚合处理) 路由+ 指定搜索主节 |
| zookeeper | zookeeper 将DataTree 数据维护在ConcurrentHashMap中, 其中path是key, dataNote是value, 这样每次读取请求都是访问用户态内存. |
| redis | |
| hbase | |
| |
存储文件的创建方式
| 中间件 | 存储文件的创建方式 |
|---|
| kafka | 追加写入日志可能采用预分配机制,kafka segemnt日志文件大小固定, 完全可能采用了预分配机制 |
| mysql | 追加写入日志采用预分配机制。性能对比 采用预分配高并发下可快10倍 。好处: 提高速度, 减少磁盘碎片化, 不足: 磁盘利用率不高。mysql innodb redo采用了预分配。binlog则是追加写入,没有分割文件 |
| ElasticSearch | es 中translog 可能采用了预分配机制. |
| zookeeper | zookeeper创建事务日志文件时, 会预分配磁盘空间,这样防止当连续空间不足时, 磁盘重新寻道。文件系统为文件分配磁盘常见: 连续分配、链接分配和索引分配. (一个文件系统一般使用一种方式)连续分配磁盘空间, 可保证高并发写时, 磁盘不会为了分配新空间, 重新寻道 |
| redis | |
| hbase | |
| |
读写性能如何
| 中间件 | 读写性能如何 |
|---|
| kafka | 写入高吞吐, 分片数足够多,撑 10w+写入 |
| mysql | Tps 5000 qps 就有压力了(看索引和查询数据量) |
| ElasticSearch | |
| zookeeper | 写入串行,读取比较快完全是内存访问,但客户端太多,心跳难以维持。不能应用用户端流量。 |
| redis | 读写都很快10ms以下。 redis cluster 超高并发千万w+读取,10w+写入,和集群规模有关 |
| hbase | 写入 10ms 以下, 大规模 1w+写入不在话下,读取 100ms+ 级别 |
故障处理
| 中间件 | 故障处理 |
|---|
| kafka | |
| mysql | |
| ElasticSearch | |
| zookeeper | |
| redis | |
| hbase | Hbase regionServer可能会挂掉, 挂掉后该分片不可用, 恢复时间要看WAL 日志有多少需要恢复。客户端为了避免雪崩可以考虑1. hbase 设置备份集群, 当挂掉后, client 直接访问备份集群 2. client 使用 hystrix 做熔断降级,避免读接口雪崩。3.写入可以使用 redis 或者 mysql 做一层备份的 |
数据清理
| 中间件 | 隔离性 |
|---|
| kafka | 淘汰策略 |
| mysql | 基于时间, 默认七天. 也可以基于日志大小 |
| ElasticSearch | |
| zookeeper | |
| redis | |
| hbase | |
| | ## 隔离性 |
集群管理(是否主从模式、无主模式)
如何选主
| 中间件 | 隔离性 |
|---|
| kafka | 使用Zookeeper轩主。 控制器 Controller选举zk 指定路径下创建临时节点, 谁成功谁是主节点。分区 Leader 选主由Controller Leader 控制, 根据 ISR 集合, 调用配置的分区选择算法 选举 leader 。2. ISR 中有幸存的分区, 选择幸存的ISR 没有幸存的, 选择其他幸存的(此时会选中不在 ISR 中的幸存者, 会丢失一部分数据)都没有幸存的 支持配置 选择 ISR 中第一个复活的选择第一个复活的 ,默认配置 会丢数据 |
| mysql | 主从模式 |
| ElasticSearch | bully 选举 |
| zookeeper | ZAB 一致性协议选主 |
| redis | redis cluster 无主。redis 哨兵 选 主从 |
| hbase | |
| |
选举算法
| 中间件 | 隔离性 |
|---|
| kafka | 依赖zookeeper选举 |
| mysql | 无,依赖于其他系统选主,需要进行存活检测选主 |
| ElasticSearch | bully |
| zookeeper | zab |
| redis | redis sentinel raft算法。redis cluster |
| hbase | |
| | ## 隔离性 |
何时选主
如何避免脑裂
元数据管理
数据分片
如何分片
| 中间件 | 如何分片 |
|---|
| kafka | 基于partition. 默认 1 个分区, |
| mysql | 有分区, 创建表时指定分区策略, 使用较少, 主要使用分表 |
| ElasticSearch | 创建索引时指定分片数. 分片数应该基于数据量设置。数据量过大时, 可以考虑索引别名. 创建多个索引优化查询 |
| zookeeper | |
| redis | |
| hbase | |
如何自动分片
| 中间件 | 如何自动分片 |
|---|
| kafka | 不会自动分片 |
| mysql | 不会自动分片 |
| ElasticSearch | 使用别名来替代分片 |
| zookeeper | |
| redis | |
| hbase | Hbase 定义了每个 Region 文件数大小, 超过后自动分片.hbase.hregion.max |
客户端如何发送请求到指定的master节点
| 中间件 | 隔离性 |
|---|
| kafka | 客户端每次初始化时会从任意节点获取 元数据信息。缓存下来将指定分区的消息发送到指定的 节点一旦出现错误 "非分区首领的错误响应", 重新更新元数据到本地重定向等通知也会更新缓存分区信息 + 错误重定向 + 重试 |
| mysql | |
| ElasticSearch | es 节点会转发请求 到 master 分片 |
| zookeeper | follower 节点会转发写请求到 leader |
| redis | smart redis 客户端每次初始化会从集群拉取所有的槽和节点映射。当节点发送到错误的节点,redis 节点会返回 moved 重定向, 此时客户端会重新拉取元数据信息. 重试N次直到 成功缓存槽信息+ 错误重定向+ 重试 |
| hbase | |
分片的叫法
| 中间件 | 分片的叫法 |
|---|
| kafka | 分区partition |
| mysql | mysql分区表 |
| ElasticSearch | Shard |
| zookeeper | 无 |
| redis | redis cluster模式下叫 Slot(槽) |
| hbase | Region |
扩缩容过程
| 中间件 | 隔离性 |
|---|
| kafka | 直接修改分片数量即可, 不需要迁移数据, 因为 kafka 基本上不存在根据分区获取消息的场景. |
| mysql | 分表 |
| ElasticSearch | Es 扩容分片操作很重量级. 有三种实现1. split 将一个表的分片扩容到 2,3,4 倍,但是需要停止写入。2. reindex 重新将一个索引数据重建到新索引,新索引分片数增加即可。需要保证新旧分片双写. 同时不存在 delete 操作 |
| zookeeper | 很少扩容 |
| redis | 扩容需要迁移分片和数据 |
| hbase | 分为 RegionServer 扩容和 Region 扩容。RegionServer 扩容时由于 region 存储和计算分离, 只是 meta 信息的更改而已。hbase region 会自动分裂和扩容, 扩容发生时, 不影响使用, hfile 不会立即分裂, 子 region 使用 reference 文件引用父 region 的 hfile 文件。 读取时不受影响,写入直接归并进新的hFile 文件。最终将会在major campaction时合并所有的 hFile 文件,此时引用文件也会被合并. |