在互联网大行其道的今天, 各种分布式系统已经司空见惯. 搜索引擎, 电商网站, 微博, 微信, O2O 平台.. 凡是涉及到大规模用户, 高并发访问的, 无一不是分布式. 但不管那种业务, 不管何种分布式系统, 有一些基本的思想还是相通的. 本文将对这些基本思想进行一个梳理汇总.
分拆
系统分拆
微信的架构师说过一句话:"大系统小做". 对于一个大的复杂系统, 首先想到的就是对其分拆, 拆成多个子系统. 每个子系统自己的存储 / Service / 接口层, 各个子系统独立开发, 测试, 部署, 运维.
从团队管理角度讲, 也可以不同团队用自己熟悉的语言体系, 团队之间基于接口进行协作, 职责清晰, 各司其职.
子系统分拆
拆成子系统之后, 子系统内部又可以分层, 分模块. 当然, 这里 "系统","子系统","层","模块" 都只是一个相对概念. 在一个系统里面, 某个模块复杂到一定程度, 会把它抽出来, 单独做成一个系统; 而在初期, 很大简单模块, 可能不回拆分, 集中在一个系统里面.
这就像一个生物组织, 自身是在不断成长, 演化, 有分有合, 不断变化发展的.
存储分拆
Nosql: 对于 Nosql 数据库, 比如 MongoDB, 其天生就是分布式的, 很容易实现数据的分片.
Mysql: 对于 Mysql, 或者其它关系型数据库, 就会设计到分库分表. 而分库分表, 就会涉及到几个关键性的问题: 切分维度, join 的处理, 分布式事务
计算分拆
计算的分拆有 2 种思路:
数据分拆: 一个大的数据集, 拆分成多个小的数据集, 并行计算.
比如大规模数据归并排序
任务分拆: 把一个长的任务, 拆分成几个环节, 各个环节并行计算.
Java 中多线程的 Fork/Join 框架, Hadoop 中的 Map/Reduce, 都是计算分拆的典型框架. 其思路都是相似的, 先分拆计算, 再合并结果.
再比如分布式的搜索引擎中, 数据分拆, 分别建索引, 查询结果再合并.
并发
最常见的就是多线程, 尽可能提高程序的并发度.
比如多次 rpc 顺序调用, 通过异步 rpc 转化为并发调用;
比如数据分片, 你的一个 Job 要扫描全表, 跑几个小时, 数据分片, 用多线程, 性能会加快好几倍.
缓存
缓存大家都不陌生, 遇到性能问题, 大家首先想到的就是缓存. 关于缓存, 一个关键点就是: 缓存的粒度问题.
比如 Tweet 的架构, 缓存的粒度从小到大, 有 Row Cache, Vector Cache, Fragment Cache, Page Cache.
粒度越小, 重用性越好, 但查询需要多次, 需要数据拼装;
粒度越大, 越容易会失效, 任何一个小的地方改动, 都可能造成缓存的失效.
在线计算 vs. 离线计算 / 同步 vs. 异步
在实际的业务需求中, 并不是所有需要都需要完全实时的:
比如内部针对产品, 运营开发的各种报表查询, 分析系统;
比如微博的传播, 我发了一个微博, 我的粉丝延迟几秒才看到, 这是可以接受的, 因为他并不会注意到晚了几秒;
比如搜索引擎的索引, 我发了一篇博客, 可能几分钟之后, 才会被搜索引擎索引到;
比如支付宝转帐, 提现, 也并非这边转出之后, 对方立即收到;
...
这类例子很多. 这种 "非实时也可以接受" 的场景, 就为架构的设计赢得了充分的回旋余地.
因为非实时, 我们就可以做异步, 比如使用消息队列, 比如使用后台的 Job, 周期性处理某类任务;
也因为非实时, 我们可以做读写分离, 读和写不是完全同步, 比如 Mysql 的 Master-Slave.
全量 + 增量
全量 / 增量其实也是在线 / 离线的思路:
比如搜索引擎的全量索引 + 增量索引, 前者是为了吞吐, 后者为了实时;
比如 OceanBase 数据库, 每次更新存在一个小表里面, 定期 merge;
Push vs. Pull
在所有分布式系统中, 都涉及到一个基本问题: 节点之间 (或者 2 个子系统之间) 的状态通知. 比如一个节点状态变更了, 要通知另外一个节点, 都有 2 种策略:
Push: 节点 A 状态变了, push 给节点 B
Pull: 也就是轮询. 节点 B 周期性的去询问节点 A 的状态.
这个问题不光出现在分布式系统中, 可以说是编写代码的一个基本问题. 对应到面向对象的编程中, 也就是常说的 "双向关联" 这种耦合问题.
A 调用 B,B 再回调 A, 这种情形, 在系统开发中经常出现. 再复杂一点, 多个模块之间, 彼此调用, 调用关系跟蜘蛛网一样.
这个问题的出现, 就和 Push/Pull 的策略密切相关:
A 调用 B, 那逻辑就会写在 B 这边; B 调用 A, 逻辑就会写在 A 这边. 所以是采用主动调用的 pull 方式, 还是回调的 push 方式, 会严重影响职责在各个模块或者子系统里面的分配.
批量
批量其实也是在线 / 离线的一种思想, 把实时问题, 转化为一个批量处理的问题, 从而降低对系统吞吐量的压力
比如 Kafka 中的批量发消息;
比如广告扣费系统中, 把多次点击累积在一起扣费;
..
重写轻读 vs 重读轻写
重写轻读, 本质就是 "空间换时间". 你不是计算起来耗时, 延迟高吗, 那我可以提前计算, 然后存储起来. 取的时候, 直接去取.
我们通常对 Mysql 的用法, 都是重读轻写, 写的时候, 简单; 查的时候, 做复杂的 join 计算, 返回结果. 这样做的好处是容易做到数据的强一致性, 不会因为字段冗余, 造成数据的不一致. 但是性能可能就是问题.
而如果采用重写轻读, 怎么做呢? 你不是要看 Feeds 吗, 那就为每个人准备一个 Feeds, 或者说收件箱. 某个人发了微博之后, 把他的微博扩散到所有人的收件箱, 这个扩散是异步的, 在后台扩散. 这样每个人看自己的 Feeds 的时候, 直接去自己的收件箱取就可以了.
读写分离
同样, 对传统的单机 Mysql 数据库, 读和写是完全同步的. 写进去的内容, 立马就可以读到.
但在很多业务场景下, 读和写并不需要完全同步. 这个时候, 就可以分开存储, 写到一个地方, 再异步的同步到另一个地方. 这样就可以实现读写分离.
比如 Mysql 的 Master/Slave 就是个典型, Slave 上面的数据并不是和 Master 实时同步的;
再比如各种报表分析, OLTP/OLAP, 线上 / 线下数据分离, 线上数据定期同步到 Hive 集群, 再做分析.
动静分离
动静分离的典型例子就是网站的前端, 动态的页面, 放在 web 服务器上; 静态的 CSS/jss/img, 直接放到 CDN 上, 这样既提高性能, 也极大的降低服务器压力.
按照这个思路, 很多大型网站都致力于动态内容的静态化, 静态化之后, 就可以很容易的缓存.
冷热分离
比如定期把 mysql 中的历史数据, 同步到 hive
限流
现在很多电商都会有秒杀活动, 秒杀的一个特点就是商品很少, 但短时间内流量暴增, 服务器完全处理不了这么多请求.
应对这类问题的一个基本思路就是限流, 既然处理不了那么多请求, 既然很大人进去了, 也是抢不到的. 那索性不要放那么多人进去.
这个和我们日常生活中, 节假日, 某个景点人数过多, 限制人流量是同样的道理.
服务熔断与降级
服务降级是系统的最后一道保险. 在一个复杂系统内部, 一个系统往往会调用其它很大系统的服务. 在大流量的情况下, 我们可能会在保证主流程能正常工作的情况下, 对其它服务做降级.
所谓降级, 也就是当某个服务不可用时, 干脆就别让其提供服务了, 直接返回一个缺省的结果. 虽然这个服务不可用, 但它不至于让整个主流程瘫痪, 这就可以最大限度的保证核心系统可用.
CAP 理论
上面讲的各种思想, 用一个更大的思想来概括的话, 就是 CAP.
Consistency: 数据一致性, 这个很容易理解, 就是没有脏数据. 我们知道, 在 Mysql 中有一致性的概念, 比如参照完整性约束, 事务等. 但这里的 C 主要特指同 1 份数据的多个备份之间的一致性.
Availability: 可用性有 2 重意思, 一个是说稳定性, 服务可用, 不会挂; 另外一个是性能, 也就是要快, 如果延迟很高, 经常超时, 那和挂了也就区别不大了.
Partition tolerance(分区容错性): 分区, 其实指网络分区. 当你把数据从 1 个物理设备, 分到多个物理设备之后, 设备之间必然是通过网络进行通信. 这就会遇到网络分区, 也就是典型的 "2 将军问题", 网络超时时间不定. 学术上有个词, 叫 "异步通信环境".
以前说 CAP 理论, 说对于一个分布式系统, 上面 3 个, 只能同时满足 2 个. 但这个其实不准确, P 其实一定存在, 是你避免不了的. 能做的, 其实主要是在 C 和 A 之间权衡.
欢迎大家一起交流二零四二八四九二三七
比如拿 Mysql 来说, 它的 C 最强, A 次之, P 最弱. 如果你为了 A, 给数据做冗余, 比如重写轻读, 那 C 就很难保证; 为了 P, 给数据做分库分表, 那就做不了事务;
比如 Nosql,P 最强, 可以很好的做数据拆分, 但 C 就不够, 做不了事务;
比如微博系统, 对 C 的要求降低, 就可以加很多缓存, 提高 A; 数据分片, 提高 P;
而支付, 交易转帐, 对 C 的要求很高, 就不能简单的用 Cache 来提高性能
最终一致性
前面提到, 在分布式系统中, 因为数据的分拆, 服务的分拆, 强一致性就很难保证. 这个时候, 用的最多的就是 "最终一致性".
强一致性, 弱一致性, 最终一致性, 是一致性的几个不同的等级. 在传统的关系型数据库中, 通过事务来保证强一致性.
但在分布式系统中, 通常都会把强一致性折中成最终一致性, 从而变相的解决分布式事务问题.
最终一致性的实现, 通常都需要一个高可靠的消息队列.
来源: http://www.bubuko.com/infodetail-2738580.html