Summer Blog

数据库总结

0.前言

数据库在软件开发中必不可少,是我日常生活中的小伙伴。狭义上的传统数据库是指关系型数据库,我常用 MySQL 和 Oracle。广义上讲数据库有:关系型、内存型(redis)、搜索引擎型(elasticsearch)、消息队列(kafka),只要是能够存储和获取数据的组件都可以称为数据库。本文是对数据库的一个总结,更好的串联知识,方便复习和回顾。

MySQL

选型

MySQL 是关系型数据库的代表,简单易用,比起 Oracle 和 SQLServer 来说显得很轻量级。选择 MySQL 的原因:开源节约成本;使用广泛,技术成熟,维护成本低;性能不错。一般数据量不大,对 RTO(恢复时间目标)、RPO(恢复点目标) 要求不高,能够承受一定的数据损失时我会选用 MySQL。

部署

MySQL 一般会做高可用部署,常用有:简单的主从架构;MMM;MGR 等,更多。我们公司一开始采用简陋的主从,现在替换成了 MGR。另外,还有读写分离方式部署,专门设置读库减轻主节点压力。

MySQL 做主从复制主要是基于binlog日志完成的。主备之间是有延迟的,造成延迟的原因主要有:主备性能差异;备库执行其他耗时统计分析任务;大事务同步。切换时有两种方式:可靠性优先策略是等到主备延迟小于阈值(比如 3 分钟)将主库改为只读,之后等主备无延迟时,将备库改为可写,然后切换浏览;可用性优先是直接切换,之后通过 binglog 继续补延迟。

架构

MySQL 可分为 service 层和存储引擎层两个部分。

service 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。

SQL 语句执行过程:

连接器建立连接、获取权限 –> 查询缓存(8.0 已废弃) –> 分析器 SQL 语句解析,检查语法错误 –> 优化器确定执行方案(如用什么索引、如何进行表连接) –> 执行器在会验证是否有权限,然后执行语句

MySQL 数据类型

Oracle

实际上很多 MySQL 的设计都是参照 Oracle 进行的。由于公司 Oracle 运维水平高(花钱)所以当追求数据的安全性和稳定性时我会选择 Oracle。

Oracle 语法与 MySQL 的差异

  1. 主键:O 主键通过序列生成;M 有自增主键
  2. 分页:O 分页比较复杂,没有 limit 语法,通过 row num 分页;M 有 limit 分页
  3. 获取当前时间:O SYSDATE;M now()

SQL 优化

性能下降的原因

  1. 没有索引
  2. 索引创建不当
  3. 数据变化,如数据量增大、特征值变化
  4. join 过多
  5. 配置不当

衡量查询的三个指标

  1. 响应时间:最重要,但可能是表面的值。因为它是服务时间+排队时间,可能受当时服务器情况影响
  2. 扫描的行数:非常有用,但不够完美。因为并不是访问所有行的代价都一致
  3. 返回的行数

索引

概念

优化

  1. 若函数或者表达式子作为条件,无法使用索引,要避免
  2. 利用好组合索引,利用最左前缀原则减少索引的创建,尽量使用覆盖索引减少回表,组合索引还需要注意字段的区分度
  3. 当要查询的字段较长时,可创建前缀索引减少索引的消耗,要注意前缀索引的区分度
  4. 主键选择:
    1. 最好是自增,插入时不会做过多的移动,减少页分裂、随机磁盘读写;也能使逻辑相邻的行在物理上也相邻,有利于内存缓存
    2. 尽量小,因为其他索引的叶子节点存的是主键,可以减小其索引大小

表结构

设计范式

  1. 表中的每一列都是不可分割的基本数据项,同一列不能有多个值
  2. 表中的每一行可以被唯一的区分
  3. 表中不包含其他表中一存在的非主键字段

数据字段选取原则

schema 设计中的陷阱

  1. 太多列
  2. 太多的关联:如 EAV 设计,MySQL 限制了每个关联操作最多只能有 61 张表,通常情况下单个查询最好控制在 12 个表以内。
  3. 全能枚举:如 country enum(‘’,’0’,’1’,’2’,’3’….)
  4. 变相枚举:如 is_default enum(‘Y’, ‘N’)
  5. Not invent here 的 null:用其他形式的特定值表示 null,造成不必要的复杂运算

其他

  1. 特定写法:
    1. count:按照效率排序的话,count(字段) < count(id) < count(1) =. count(*)
    2. join:被驱动表上有索引时会使用Index Nested-Loop Join,驱动表扫描,被驱动表走索引树。小表做驱动表。被驱动表上没有索引时会使用Block Nested-Loop Join,将驱动表存入缓存中,被驱动表依次访问判断是否可以作为返回。当缓存足够大时是一样的,当缓存不够大时小表做驱动表更好。
    3. order by:尽量用上索引,如果是覆盖索引更好;如果需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大sort_buffer_size参数,来避免用到磁盘临时表;
  2. 批量操作:事务提交需要写日志,批处理减小日志的性能损耗
  3. 绑定变量,节约解析时间
  4. 减少长事务

总结

可以从几个方面来说 SQL 优化,索引优化、表结构、查询时的注意点、数据库本身的注意点。

索引方面

  1. 查询使用索引,如果查询中有函数或者运算就不会使用索引,要避免和注意
  2. 注意索引的区分度
  3. 利用好组合索引,最左前缀原则,可以创建覆盖索引减少回表
  4. 对于字段较长的,可以创建前缀索引
  5. 主键索引选择自增主键更好,小;物理的相邻和逻辑的相邻一致,减少随机读写

表结构方面

  1. 注意字段类型的选择,小、简单、NULL 避免
  2. 表列不要过多,表关联不要过的
  3. 避免一些常见的反模式:全能枚举(枚举值过多)、变相枚举(bool)、日期不用日期类型用字符串

特殊写法

  1. count:按照效率排序的话,count(字段) < count(id) < count(1) =. count(*)
  2. join
    1. 被驱动表上有索引时会使用Index Nested-Loop Join,驱动表扫描,被驱动表走索引树。
    2. 小表做驱动表。被驱动表上没有索引时会使用Block Nested-Loop Join,将驱动表存入缓存中,被驱动表依次访问判断是否可以作为返回。当缓存足够大时是一样的,当缓存不够大时小表做驱动表更好。
  3. order by
    1. 尽量用上索引,如果是覆盖索引更好;
    2. 如果需要统计的数据量不大,尽量只使用内存临时表;
    3. 也可以通过适当调大sort_buffer_size参数,来避免用到磁盘临时表;
  4. group by
    1. 如果对group by没有排序要求,要在语句后加order by null
    2. 尽量让group by过程用上索引,用explain确认没有使用临时表(Using temporary or Using filesort
    3. 如果group by需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size参数,来避免用到磁盘临时表;
    4. 如果数据量实在太大,使用SQL_BIG_RESULT这个提示,来告诉优化器直接使用排序算法得到group by的结果

数据库

  1. 绑定变量,节约解析时间
  2. 批量操作:事务提交需要写日志,批处理减小日志的性能损耗
  3. 减少长事务

MySQL 查询计划关注的值:

  1. id越大越先执行,相同 id 顺序执行
  2. select_type查询类型:
    1. simple:简单查询
    2. primary: 使用主键
    3. derive:衍生表
    4. subquery:子查询
    5. union\union result:合并
  3. table查询表,partition查询分区
  4. type连接类型(访问类型),最好到最差:
    1. system:表中只有一行记录
    2. const:只查询一次
    3. eq_ref:唯一性索引扫描
    4. ref:查找条件列使用了索引而且不为主键和 unique。语句最好能达到这种情况
    5. range:索引范围查询
    6. index:全量索引扫描
    7. all:全表
  5. possible_keys:可能用到的索引
  6. keys:实际用到的索引
  7. key_len:使用索引的长度
  8. ref:索引使用的具体值,常量或者是其他列的值
  9. rows:扫描行数
  10. filter:用到的行数与扫描行数的百分例,越大越好,最大 100
  11. extra:额外信息
    1. using filesort:使用文件排序
    2. using temporary:使用临时表
    3. using index:使用索引
    4. using where:使用查询条件
    5. using join buffer:使用链接缓存
    6. impossible value:条件永远不会达成

事务

事务特性:

  1. 原子(atomicity),事务最终状态只有两种,全部成功或全部不执行。
  2. 一致性(consistency),事务操作前后,数据满足完整性约束,数据库保持一致状态。
  3. 隔离性(isolation),当多个事务并发使用相同数据时,不会互相干扰。
  4. 持久性(durability),事务执行后数据被永久保存下来。

隔离级别:

  脏读 不可重复读 幻读
read uncommitted Y Y Y
read committed N Y Y
repeatable read(default) N N Y
serialized N N N

可重复读事务隔离的实现:每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。当系统里没有比这个回滚日志更早的 read-view 的时候,才会删除回滚,所以长事务是有害的。

按不同的方式分类锁可以划分为:

  1. 粒度:行所、表锁、服务器锁
  2. 功能:共享锁(读锁)、排他锁(写锁)、意向锁(简化加行级别锁后,再加别的锁时的检查)

避免死锁:

  1. 如果事务涉及多个表,操作复杂,尽可能一次性锁定所有资源
  2. 如果一次需要更新一个表中的很多数据,可以直接加表锁
  3. 不同事务并发读写多张数据表,可以约定访问表的顺序,降低死锁发生的概率
  4. 设置合适的锁等待时间,MySQL InnoDB 中用innodb_lock_wait_timeout
  5. 把最可能发生冲突的语句放在事务的最后

维护

数据迁移

迁移应该是在线的迁移,也就是在迁移的同时还会有数据的写入;数据应该保证完整性,也就是说在迁移之后需要保证新的库和旧的库的数据是一致的;迁移的过程需要做到可以回滚,这样一旦迁移的过程中出现问题,可以立刻回滚到源库不会对系统的可用性造成影响。

  1. 双写方案:
    1. 将新库作为旧库的从库,进行同步
    2. 改造业务代码,写入旧库同时,写入新库。可以异步写,失败做记录
    3. 数据校验,提前准备脚本校验,必须经过完整测试
    4. 灰度切换,如果遇到问题,立刻切换会旧库,减少影响
    5. 观察监控,对比新旧库的区别
  2. 级联同步:比较适合数据从自建机房向云上迁移的场景,因为迁移上云最担心云上的环境和自建机房的环境不一致,会导致数据库在云上运行时因为参数配置或者硬件环境不同出现问题。具体是自建机房准备一个备库,在云上环境上准备一个新库,通过级联同步的方式在自建机房留下一个可回滚的数据库。
  3. MQ 消息传递完成迁移

ElasticSearch

ElasticSearch 是一个高性能的分布式搜索引擎。它存储数据的最小单位是 Document(文档),同一种类型的文档放在一个 Index(索引)中,Index 被存储在 Shard 上,Shard 分为 Primary Shard(负责写入数据) 和 Replica Shard,它们会分布在不同的 Node 上,提高数据的高可用。

倒排索引

倒排索引是维护的单词到文档 id 的关系,分为两个部分:

  1. 单词词典:记录文档中的所有单词,记录单词到倒排列表的关系。可以用 B+树实现
  2. 倒排列表:由倒排索引项组成(文档 ID、词频、位置(Position)、偏移(Offset)

设计优点:

  1. API 设计的好,简洁易用
  2. 分布式存储,每个索引可以设置分区和备份,防止数据的丢失
  3. 写操作会被转发到主分区,但备份可以进行读操作的计算,增加效率
  4. 一个搜索会在多台机器上分布式的进行,提升搜索效率
  5. 删除和更新,都是先标记为逻辑删除,再新增数据接在后面,可以保证一定的顺序存储,提升读取效率
  6. ES 的倒排索引不可变
    • 好处是:不需加锁,可以一直放在缓存中,也可以整块压缩节约 io 和 cpu
    • 坏处是:修改需要重新构建索引

工作过程

  1. 写入:客户端发送请求给任意一个节点,它就是协调节点,由协调节点找到有主分区所在节点,将请求转发给主分区节点,主分区节点处理这个请求,并将结果同步给备份节点,同步达到设置的同步策略后,返回完成给协调节点,协调节点将结果返回给客户端。
  2. 读取:客户端发送请求给任意一个节点,它就是协调节点,协调节点根据 hash 算出该 doc id 所在的分区号,在负载均衡的方式选择一个主/备份分区执行查询,查询结果返回给协调节点,在返回给客户端。
  3. 搜索:客户端发送请求给任意一个节点,它就是协调节点,协调节点将请求发给该索引的所有分区(负载均衡方式随机发向一个主分区或副本),这打他些节点执行搜索将 doc id 返回给协调节点,协调节点汇总排序后,通过 doc id 再拉取完整数据,最终返回给客户端。
  4. 替换,删除内部逻辑
    1. 替换时会把之前的数据设置为逻辑删除
    2. 部分更新时,与替换一样,会将先前的记录设置为逻辑删除,创建一个新的 document。内部会自动使用乐观锁
    3. 删除命令也是逻辑删除,当数据越来越多时会自动清理逻辑删除过的数据
  5. 写入内部逻辑:数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。数据写入 segment file 之后,同时就建立好了倒排索引。

最佳实践

  1. 索引创建
    1. 单个 document 不要过大,会带来 IO、缓存压力
    2. 分片数量和 data 节点数量保持一致,最好每个主分片都设置一个副本
    3. 对于时序数据存储,索引最好按照时间序列进行创建,可以方便删除数据、降低故障范围、减小检索粒度
    4. 集群中的索引不要过多,最好不要超过 200 个
  2. 文档维护
    1. 使用批量操作 bulk api
  3. 搜索优化:
    1. 不要返回很大的结果集。es 被设计为搜索引擎,非常擅长返回满足查询的前几个 document。es 并不擅长返回所有数据。如果一定要这么做需要使用scroll api
    2. 使用filter效率更高,不需要评分只是过滤的场景,使用filter更高效

Redis

Redis 是一个 Key-Value 类型的内存数据库,通常我们用它来做缓存。现在有一些对性能要求高的系统,也会用 Redis 作为完全的数据来源。

Redis 为什么这么快

  1. C 语言效率更高
  2. 内存数据库,避免 I/O
  3. 单线程避免了上下文切换资源竞争
  4. I/O 多路复用技术处理网络 socket 连接
  5. 对象压缩,如果对象小,一维结构内存小于二维结构。ziplist 紧凑型字节数组,inset 紧凑型整数数组

数据类型

类型 名称 应用
string 字符串 分布式锁,设置 key-value-ttl,考虑超时和可重入性
list 列表 异步消息队;利用 lrange 命令,做基于 Redis 的分页功能列
set 集合 利用 Set 的交集、并集、差集等操作
zset 有序集合 延迟队列,score 为延迟时间;限流器,score 为加入时间,移除加入时间超过与之的成员,zset 大小超过限流时拒绝请求
hash 字典  
setbit 位图 位图不是特殊的数据类型,而是将字符串看出 byte 数组后按位操作。用于记录 1/0 数据节约使用空间
hyperloglog   不精确的去重计数方案,标准误差 0.81%
bloom filter 布隆过滤器 检查一个值是否在集合里,有一定误差。当判断一个值存在时,可能不存在,但判断一个值不存在,就一定不存在
redis cell 限流器 使用漏斗算法
geo hash 地理模块 原理:将二维地理映射到一维 zset 中进行排序

高可用

  1. 客户端高可用:制定一些数据分片和数据读写的策略
  2. 集群 redis cluster,Redis 官方集群支持,去中心化的集群,每个节点管理一部分 key,共同组成一个对等集群:
    1. 主从同步:
      1. 增量同步:主节点将执行的命令存在 buffer 中,buffer 是循环记录的,然后异步的将指令同步到从节点,从节点执行命令并更新自己同步的偏移量
      2. 快照同步:主节点生成 rdb 快照,从节点同步 rdb 快照,完成后再同步复制 buffer。这个操作也可以不生成 rdb,主节点直接将内存中的数据通过网络传输给从节点
    2. 细节
      1. 将所有数据划分为 16384 槽位,每个节点负责一部分槽位
      2. 客户端也会存一份槽位映射信息,可以计算槽位应在哪个节点,直接调用
      3. 若客户端槽位信息和真实槽位不一致,Redis 会返回 MOVED 指令和错误信息,客户端需要重试并纠正自己的槽位映射
      4. 动态扩容将槽位发生变化的 key 迁移至新的槽位;当客户端请求迁移中的 key 时,Redis 会返回 ASKING 指令和错误信息,客户端需要重试
      5. Cluster 中的节点支持主从,若主节点故障,会将从节点提升为主节点
      6. 节点下线使用 Gossip 协议,一个节点发现连接不到某个节点,广播这个信息,集群中的大部分节点承认后,再广播下线信息使所有节点承认下线
    3. 一致性 hash:使用一致性 hash 可以保障在发生变化时(动态扩容),对数据移动更少。客户端缓存集群的槽位,当集群发生变化时,客户端请求计算的槽位就会不一致,此时会返回重新加载槽位的命令给客户端
  3. 代理 codis

缓存读写策略

  1. Cache Aside(旁路缓存)策略:在更新数据时不更新缓存,而是删除缓存中的数据,在读取数据时,发现缓存中没数据,则从数据库中读取数据,更新到缓存中。
    1. 问题:
      1. 很小几率的缓存不一致
      2. 数据库主从同步延迟,造成新写入的数据没有写入从库,查询不到,写不进缓存
    2. 解决:
      1. 写数据库同时,加锁写入缓存
      2. 写数据库同时,写缓存,设置较小的缓存过期时间
  2. Read/Write Through(读穿 / 写穿)策略:这个策略的核心原则是用户只与缓存打交道,由缓存和数据库通信,写入或者读取数据。
    1. Write Through 的策略:先查询要写入的数据在缓存中是否已经存在,如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,如果缓存中数据不存在,我们把这种情况叫做“Write Miss(写失效)”。可以选择两种“Write Miss”方式:一个是“Write Allocate(按写分配)”,做法是写入缓存相应位置,再由缓存组件同步更新到数据库中;另一个是“No-write allocate(不按写分配)”,做法是不写入缓存中,而是直接更新到数据库中。
    2. Read Through 策略:先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库中同步加载数据。
    3. 问题:Write Through 策略中写数据库是同步的,这对于性能来说会有比较大的影响,因为相比于写缓存,同步写数据库的延迟就要高很多了。
  3. Write Back(写回)策略:这个策略的核心思想是在写入数据时只写入缓存,并且把缓存块儿标记为“脏”的。而脏块儿只有被再次使用时才会将其中的数据写入到后端存储中。

缓存穿透

请求穿透缓存,直接访问数据库。三种解决方案:

  1. 回种空值:当我们从数据库中查询到空值或者发生异常时,我们可以向缓存中回种一个空值。但是因为空值并不是准确的业务数据,并且会占用缓存的空间,所以我们会给这个空值加一个比较短的过期时间,让空值在短时间之内能够快速过期淘汰。建议在使用的时候应该评估一下缓存容量是否能够支撑。如果需要大量的缓存节点来支持,那么就无法通过通过回种空值的方式来解决,这时可以考虑使用布隆过滤器。
  2. 布隆过滤器:把集合中的每一个值按照提供的 Hash 算法算出对应的 Hash 值,然后将 Hash 值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从 0 改成 1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则则认为不在集合中。两个由于 hash 碰撞有关的缺陷:
    1. 它在判断元素是否在集合中时是有一定错误几率的,比如它会把不是集合中的元素判断为处在集合中
    2. 不支持删除元素
  3. 极热点缓存穿透可以通过后台加载、设置分布式锁控制穿透的数量

缓存迁移

  1. 利用副本,将新服务设置成现有服务的副本,应用连接这个副本,如果副本上找不到结果,会去源服务商找。
  2. 缓存预热,先切部分流量到新服务,等新服务足够多的命中率后切换

过期策略

  1. 主节点
    1. 维护一个设置了过期时间的 key 的字典
    2. 定时过期随机扫描 key 删除过期 key
    3. 惰性检查获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西
  2. 从节点:从节点并不会执行过期扫描,会同步主节点删除key时写入AOFdel命令

消息中间件

作用

  1. 解耦:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
  2. 消峰添谷:起到缓冲流量的作用
  3. 异步:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
  4. 消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;

缺点

系统可用性降低、系统复杂度提高、一致性问题

丢消息

  1. 丢消息定位:消息丢失定位是丢消息问题最先要解决的,如果需要让业务有感知后在解决,显然是不合理的。可以根据消息的有序性来验证是否丢消息。 在发送端和消费端添加拦截器,生成连续序号和验证序号连续。注意根据使用的中间件合理选择生成和验证方式。
  2. 发送阶段
    1. 原因:生产者发送消息时网络错误,以为发送给了 MQ,实际 MQ 没有接收到
    2. 解决:这种情况设置正确的消息重传次数和失败处理,另外也可以设置消息同步到多个副本才算成功。
  3. 存储阶段
    1. 原因:MQ 仅接收到消息,还未来得及写入磁盘保存;只写入到 leader 没有同步到 follower 时,leader 挂了
    2. 解决:设置消息同步到多个副本才算成功
  4. 消费阶段
    1. 原因:消费者已经提交了确认,但消费途中没有处理完就 down 了
    2. 解决:关闭自动提交确认,手动保证处理完成后提交

重复消费(仅消费一次)

避免重复消费,需要保证消息的生产、消费过程的幂等性:

消息积压

  1. 监控:
    1. 使用消息队列提供的工具,通过监控消息的堆积来完成,如kafka-consumer-groups.shJMX
    2. 生成监控消息的方式来监控消息的延迟情况,具体是生成一个时间戳消息,消费者消费到此消息时判断时间是否大于阈值,发送报警
  2. 减少的方法:
    1. 优化消费性能
    2. 增加消费者数量(Kafka 消费者的数量和分区数一样,需要同时增加分区数和消费者数)

高可用保证

  1. RabbitMQ:主从。镜像集群中每一个节点可以拥有一份全量数据
  2. Kafka:集群模式。集群有多个 broker,topic 可以划分为多个 partition,每个 partition 分布在不同的 broker,同时可以设置 partition 的 replica 副本数,replica 会选举出 leader 负责读写数据,leader 会负责把数据同步给 follower。

Kafka 设计优点

  1. Kafka 使用硬盘存储,但做了很多优化的设计使存储性能很高
    1. 顺序存储,所有的消息在文件后面追加
    2. 通过维护一个 offset 来实现顺序访问
  2. IO 采用 0 拷贝技术,减少了从内核空间读取到用户缓存,再从用户缓存输出到网络流的时间。直接从页缓存写入网络流
  3. 网络带宽上的设计考虑,会对消息做压缩,减少带宽消耗。也可以设置消息批量发送,减少网络请求次数
  4. 分布式存储设计,有备份和主分区,保证消息不丢失。用户通过 offset 也能从宕机事故中快速恢复

选型对比

特性 RabbitMQ RocketMQ Kafka
单机吞吐量 万级,比 RocketMQ、Kafka 低一个数量级 10 万级,支撑高吞吐 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic 数量对吞吐量的影响   topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源
时效性 微秒级,这是 RabbitMQ 的一大特点,延迟最低 ms 级 延迟在 ms 级以内
可用性 高,基于主从架构实现高可用 非常高,分布式架构 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性 基本不丢 经过参数优化配置,可以做到 0 丢失 同 RocketMQ
功能支持 基于 erlang 开发,并发能力很强,性能极好,延时很低 MQ 功能较为完善,还是分布式的,扩展性好 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用

Zookeeper

ZooKeeper 作为一个分布式的协调服务框架,主要用来解决分布式集群中,应用系统需要面对的各种通用的一致性问题。它提供了一个可以保证一致性的分布式的存储系统,数据的组织方式类似于 UNIX 文件系统的树形结构。ZooKeeper 本身可以部署为一个集群,集群的各个节点之间可以通过选举来产生一个 Leader,选举遵循半数以上的原则,所以一般集群需要部署奇数个节点。

特点

ZAB 协议

注意事项

  1. 不要在 Zookeeper 中写入大量数据,它只适合存放少量数据,当写入超过百兆,性能和稳定性会严重下降。 2.

通用实践

分布式锁

数据库

优点:直接使用数据库,直观容易理解。

缺点:会需要做额外的过期、重入、阻塞策略,另外数据库性能也需要考虑。

方式:

INSERT INTO lock_table(lock_name) VALUES ('lock1'); -- 加锁
DELETE FROM lock_table WHERE lock_name = 'lock1'; -- 解锁

问题与解决:

Redis

优点:性能更好,有成熟的中间件

缺点:单节点实现的话有可用性问题

成熟实现:Redisson

方式:

问题与解决:

Zookeeper

优点:无单节点问题,只要半数以上机器存活就可对外服务;可以持有锁任意长时间,也可自动释放锁;可阻塞,监听节点变化,然后判断自己的节点是否是最小的获取锁;可重入,直接对比节点和自己是否一样

缺点:性能不如缓存;需要对 zk 有一定了解

成熟实现:Curator

方式:


comments powered by Disqus