文章目录

一、中间件的定义

中间件一词的由来

中间件这个术语第一次出现是 1968 年在德国加尔米施帕滕基兴举办的 NATO 软件工程大会 结束后发表的一份报告中。

这届大会正式确定了软件工程(Software Engineering)的概念,同时还探讨了软件设计、生产和分发等主题。

中间件的定义

中间件(英语:Middleware),又译中间件、中介层,是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。中间件位于客户机服务器的操作系统之上,管理着计算资源和网络通信。 – 维基百科

什么不是中间件

我们按照类别来看一些经常会遇到的一些不是中间件的概念

- 业务平台不是中间件,业务平台是从服务的视角抽象的能同时支撑多个业务,业务之间的信息能形成交互和增强的平台。

- 营销工具不是中间件,营销工具是直接作用于最终消费者用户的软件或者插件服务。

- 二方/三方工具包不是中间件,二方/三方工具包是在各种场景的程序开发过程中沉淀的一些常用工具类(功能)的集合,包含于软件代码本身。

- SaaS 不是中间件,SaaS(Software as a Service) 更多的是一种软件交付模式,无需用户安装,通过网络在线访问的一种服务模式。

- PaaS 不是中间件,PaaS(Platform as a Service) 将软件研发的平台做为一种服务,提供软件部署平台(强调的是屏蔽系统和软件细节的runtime平台)。

评判关键

从定义可以总结出评判的几个地方

  1. 性质:中间件是软件。

  2. 作用层级:系统软件和应用软件之间、软件各部件之间;管理客户机与系统软件之间的计算资源和网络通信。

  3. 服务对象:中间件为应用软件服务,应用软件为最终用户服务,最终用户并不直接使用中间件。

中间件的好处

中间件能给客户带来什么?

为上层应用软件的开发提供便捷的、开箱即用的服务交互和计算的能力,缩短开发周期;屏蔽底层runtime的差异;节省应用本身的系统资源,减少运行成本。

中间件分类

什么时候使用中间件

基于中间件的定义我们知道中间件是连接软件与系统之间的服务,那么我们什么时候使用了中间件,在哪些地方用到了中间件了。我们不妨假设一个http请求过程来窥视一番。

当你在浏览器中输入一个网址时,它会通过 DNS 解析到目标服务注册的公网IP地址

请求到达目标服务的 web 反向代理服务器 Tengine 之后,经过一定的过滤转发到目标服务A上

服务A通过 RPC框架 Dubbo 请求服务B的结果做中间计算,并且从 Tair 缓存中读取计算因子,计算结果

服务A接着使用 Druid 通过 TDDL 写入计算结果到 MySQL Master 节点然后返回结果

异步过程中 Canal 通过模拟 Binlog 主从复制的原理,迅速将这条 Binlog 消费并下发到消息队列 RocketMQ

服务C通过 RocketMQ 消费到事件之后,通过配置中心 ConfigServer 拉取到的策略进行对应策略的事件处理。

这个过程中我们使用了一系列的中间件来协同各个微服务完成整个流程,如web反向代理服务器 Tengine、RPC框架 Dubbo、缓存 Tair、连接池 Driud、数据库代理层 TDDL、Binlog 同步工具 Canal、消息队列 RocketMQ、配置中心 ConfigServer。

常用基础中间件

- 路由与web服务器:处理和转发其他服务器通信数据的服务器。 如被业界广泛使用的阿里基于 Nginx 研发的 Tengine、阿里内部的集中式路由服务 VipServer

- RPC框架:微服务时代的远程服务调用框架。如grpc, Thrift, 阿里的 HSF, Dubbo, SOFA-RPC

- 消息中间件:支持在分布式系统之间发送和接收消息的软件。 如 Apache kafka, Apache RabbitMQ, NSQ, 阿里孵化开源的 Apache RocketMQ

- 缓存服务: 分布式的高速数据存储层,一般是内存存储。如 阿里 Tair,业界的 Redis, Memcached, Ehcache

- 配置中心:用来统一管理各个项目中所有配置的系统。如 阿里 Nacos、携程 Apollo、百度 Disconf

- 分布式事务:事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 如 阿里 seata、腾讯 DTF

- 任务调度:分布式环境下提供定时、任务编排、分布式跑批等功能的系统。如 阿里 SchedulerX、业界 xxl-job、当当 elastic-job、有赞 TSP

- 数据库层 用于支持弹性扩容和分库分表的 TDDL,数据库连接池 Driud, Binlog 同步的 Canal 等。

中间件云产品

随着云时代的到来,大量公司的业务向云上迁移;为了云上客户能够便捷的使用稳定高效的中间件能力,云厂商开始将自身沉淀的基础中间件能力云化,用于支撑各个云上客户和自身业务的快速生长。

二、中间件的开发

什么是中间件开发?

随着国内软件行业的发展,国内互联网公司规模越来越大,业务越来越复杂,随之使用大量的中间件来提高后台服务性能。由此产生了中间件开发和维护人员。

诚然,在小公司,中间件,例如缓存,MQ,RPC 等服务,极大可能是由业务开发人员自己维护,或者委托第三方云平台运维(支付一些费用)。但,如果后台开发超过 200 人,基本就会组建自己的中间件或者基础架构团队,用于维护后台服务器基础架构和中间件。

更大规模的公司,则由于各种各样的原因(性能,KPI),会自己开发中间件,简称自研。这要求中间件团队需要更多的人员。

中间件开发人员需要哪些素质?

既然需要中间件开发人员,那么中间件开发人员一般从哪里招聘呢?招聘的要求是什么?

通常,一个公司在刚开始组建中间件团队的时候,都会从公司内部挑选精英人才,或者挑选对中间件感兴趣的人才。这时候,可能你没有相关经验,但你仍然有机会参与到中间件开发中。反之,如果你没有中间件开发经验,想通过招聘的方式进入中间件行业,那么相对而言,会有些曲折。

那么,假设,你想从事中间件开发,但,你没有中间件开发经验,且,你的公司也没有组建中间件团队的打算。

该怎么突破?

答: 跳槽。跳槽到别的公司的中间件团队。

这里就涉及到了一个中间件团队需要哪些技能。因为跳槽肯定就要面试,如果你面试的是中间件岗位,那么自然,就需要准备中间件的相关知识。

另外,还有一点,在这个分工明确的时代,即使是中间件,也有很多种类,我这里稍微分一下,可能不是很准确。

  1. 服务治理中间件,例如 RPC 相关中间件,限流熔断,链路追踪,分布式配置中心等等。你可以从 SpringCloud 里找到相关的产品。当然国内也有很多优秀的产品。
  2. 存储中间件,例如缓存,MQ等等,如果存储涉及到分布式(通常都会涉及),那么要求相对较高。
  3. 各种 Proxy,不论是数据库,还是 Cache,还是各种存储,通常单机无法承载海量数据,比较简单的办法就是使用 Proxy 进行代理,让应用透明的使用集群。出于性能考虑,这里通常会使用性能较高的产品,例如 goLang,C++ 等。Java 的长处——开发效率,在这个地方权重不大。
  4. 各种分布式中间件,例如 ZK 这种,这个我个人认为难度是较大的。分布式向来是软件开发中比较困难的一个点。特别是涉及到存储和一致性。
  5. 容器相关,k8s,docker等,容器化已经是大势所趋,其实我也不是很懂😀(听大佬们说的)。

回到之前的话题: 一个中间件开发者需要哪些素质?

  1. 语言基础。从 Java 程序员的角度,基础通常就是:集合,并发,JVM,Netty,IO、NIO(mmap,sendfile)
  2. 计算机基础,由于中间件开发人员经常和 OS 打交道,所以计算机基础也必不可少,例如文件系统(IO/磁盘),进程线程,内存管理。
  3. 网络基础,搞后台的人员,肯定要对网络熟悉了,熟悉在 Linux 下排查网络问题,熟悉 Epoll 原理等。
  4. 分布式相关知识,互联网海量数据背景下,分布式知识必不可少,CAP, Paxos,Raft,zab,2pc,3pc,base等等。最好能根据这些理论写出实现代码。
  5. 熟悉开源实现,即使你是业务开发人员,你也 100% 会接触开源项目,例如 Spring,那么,通常你需要对这种常用的开源代码有深刻的理解,不仅知晓其原理,也领会其设计。从大的角度看,你得看清整个框架的背景,设计和取舍,从小的角度看,你得看清框架的内部实现细节,有哪些有趣的地方(通常这种框架都会进行性能优化)。
  6. 了解行业风向标,中间件行业和业务开发稍有不同,每个中间件的版本升级都会让该领域的开发者们侧目(类似 iPhone 发布会),了解其特性,进而了解行业趋势,最后成为行业引领。

如何成为中间件开发人员?

好,说完了中间件开发人员需要哪些素质,自然,如何成为中间件开发人员,就不言自明了。

说白了,以上 6 个点,都是硬骨头。

  • 对于已经开始工作的人来说,需要平时深刻的积累,说的难听一点,如果你的业务开发任务很重,你很难搞定上门的这些内容。
  • 对于还在上学的同学来说,很爽,你可以用学校(不仅仅指大学,据我所知的大神,通常是初中/小学就开始编程,但这不是必须的)里大把的时间来学习,一个个的搞定这些知识点,和社招不同,如果你的知识达到上面的水平,那么 SP offer 应该是随便拿了 :)

我这里重点和那些平时开发任务不重,想搞中间件的同学聊聊。

我假设你是一个工作 3 年以内的 Java 开发人员,且你可能是培训生,半路出家,科班生,大专生,初中生,且你不在大厂,通常在一个后台开发不超过 200 人的创业公司,title 是 “Java 开发工程师”,并且有一个程序员的梦想,不想 get、set,不想 crud,不想 html 填空,不想和产品同学讨论,也不想和测试同学点点点…(感觉这里会得罪人 =_=||)

你可能想跳槽。

那么你大概需要做以下准备:

  1. 巩固 Java 基础,集合源码,并发源码,JVM 原理,Netty 原理源码,IO 相关(涉及到零拷贝文件存储),这些都是 Java 基础,通常是必须的。
  2. 分布式原理,最起码知晓理论知识,最好能写一个,哪怕参照开源的也行。
  3. 源码,Spring Mybatis Tomcat 等等,这些代码通常是你最先接触的,不妨从这里开始。RPC 中间件相关的,Dubbo,Motan,SOFA,挑一个吧,推荐 SOFA。
  4. 再熟悉熟悉(熟悉指源码和设计)分布式的相关产品,假设你是 Java 开发,推荐 RocketMQ,Apollo 配置中心等等中间件,其实都可以,MQ 相对复杂。
  5. 操作系统,通常,你在研究上面的内容时,会遇到操作系统的疑问,遇到不要绕过,尽量弄明白。
  6. 自己的产品,有就最好了,例如公众号,博客,教学视频,GitHub 项目等等,总之,是拿得出手的东西。
  7. 加大牛好友,了解行业风向标。也许你是一个矜持的人,但从事了这个行业,你有必要和行业里优秀的人学习(看看朋友圈就好)。

三、MySQL和NoSQL

之前写了一篇关于数据库的基础博客:数据库基础、使用C语言构建一个数据库、SQL语言、MySQL

下面来看看NoSQL的含义。

关系型数据库是什么?

关系型数据库建立在关系型数据模型的基础上,是借助于集合代数等数学概念和方法来处理数据的数据库。现实世界中的各种实体以及实体之间的各种联系均可用关系模型来表示,市场上占很大份额的 Oracle、MySQL、DB2 等都是面向关系模型的 DBMS。

关系型数据库基本概念

在关系型数据库中,实体以及实体间的联系均由单一的结构类型来表示,这种逻辑结构是一张二维表。图 1 所示的学生选课系统中,实体和实体间联系在数据库中的逻辑结构可通过图 2 所示。

img

图 1:关系型数据库

img
图 2:学生选课系统数据库逻辑结构

关系型数据库以行和列的形式存储数据,这一系列的行和列被称为表,一组表组成了数据库。图 3 所示的员工信息表就是关系型数据库。

img
图 3:员工信息表

属性说明:

  • 二维表:也称为关系,它是一系列二维数组的集合,用来代表与存储数据对象之间的关系。它由纵向的列和横向的行组成。
  • 行:也叫元组或记录,在表中是一条横向的数据集合,代表一个实体。
  • 列:也叫字段或属性,在表中是一条纵行的数据集合。列也定义了表中的数据结构
  • 主属性:关系中的某一属性组,若它们的值唯一地标识一个记录,则称该属性组为主属性或主键。主属性可以是一个属性,也可以由多个属性共同组成。在图 1-5 中,学号是学生信息表的主属性,但是课程信息表中,学号和课程号共同唯一地标识了一条记录,所以学号和课程号一起组成了课程信息表的主属性。

结构化查询语言

关系型数据库的核心是其结构化的查询语言(Structured Query Language, SQL),SQL 涵盖了数据的查询、操纵、定义和控制,是一个综合的、通用的且简单易懂的数据库管理语言。同时 SQL 又是一种高度非过程化的语言,数据库管理者只需要指出做什么,而不需要指出该怎么做即可完成对数据库的管理。

SQL 可以实现数据库全生命周期的所有操作,所以 SQL 自产生之日起就成了检验关系型数据库管理能力的“试金石”,SQL 标准的每一次变更和完善都引导着关系型数据库产品的发展方向。

SQL 包含以下四个部分。

数据定义语言(DDL)

DDL 包括 CREATE、DROP、ALTER 等动作。在数据库中使用 CREATE 来创建新表,DROP 来删除表,ALTER 负责数据库对象的修改。

例如,创建学生信息表使用以下命令:

CREATE TABLE StuInfo(id int(10) NOT NULL,PRIMARY KEY(id),name varchar(20), female bool,class varchar(20));

数据查询语言(Data Query Language, DQL)

DQL 负责进行数据查询,但是不会对数据本身进行修改。

DQL的语法结构如下:

SELECT FROM 表名1,表2
where 查询条件 #可以组合 and、or、 not、 =、between、and、in、like 等;
group by 分组字段
having (分组后的过滤条件)
order by 排序字段和规则;

数据操纵语言(Data Manipulation Language, DML)

DML 负责对数据库对象运行数据访问工作的指令集,以 INSERT、UPDATE、DELETE 三种指令为核心,分别代表插入、更新与删除。

向表中插入数据命令如下:

INSERT 表名 (字段1,字段2,…,字段n,) VALUES (字段1值,字段2值,…,字段n值) where 查询条件;

数据控制语言(Data Control Language, DCL)

DCL 是一种可对数据访问权进行控制的指令。它可以控制特定用户账户对查看表、预存程序、用户自定义函数等数据库操作的权限,由 GRANT 和 REVOKE 两个指令组成。

DCL 以控制用户的访问权限为主,GRANT 为授权语句,对应的 REVOKE 是撤销授权语句。

关系型数据库的优缺点

关系型数据库已经发展了数十年,其理论知识、相关技术和产品都趋于完善,是目前世界上应用最广泛的数据库系统。

关系型数据库的优点

  • 容易理解:二维表结构非常贴近逻辑世界的概念,关系型数据模型相对层次型数据模型和网状型数据模型等其他模型来说更容易理解。
  • 使用方便:通用的 SQL 使用户操作关系型数据库非常方便。
  • 易于维护:丰富的完整性大大减少了数据冗余和数据不一致的问题。关系型数据库提供对事务的支持,能保证系统中事务的正确执行,同时提供事务的恢复、回滚、并发控制和死锁问题的解决。

关系型数据库的缺点

随着各类互联网业务的发展,关系型数据库难以满足对海量数据的处理需求,存在以下不足。

  • 高并发读写能力差:网站类用户的并发性访问非常高,而一台数据库的最大连接数有限,且硬盘 I/O 有限,不能满足很多人同时连接。
  • 对海量数据的读写效率低:若表中数据量太大,则每次的读写速率都将非常缓慢。
  • 扩展性差:在一般的关系型数据库系统中,通过升级数据库服务器的硬件配置可提高数据处理的能力,即纵向扩展。但纵向扩展终会达到硬件性能的瓶颈,无法应对互联网数据爆炸式增长的需求。还有一种扩展方式是横向扩展,即采用多台计算机组成集群,共同完成对数据的存储、管理和处理。这种横向扩展的集群对数据进行分散存储和统一管理,可满足对海量数据的存储和处理的需求。但是由于关系型数据库具有数据模型、完整性约束和事务的强一致性等特点,导致其难以实现高效率的、易横向扩展的分布式架构。

NoSQL数据库的优势有哪些?

数据是当今世界最有价值的资产之一。在大数据时代,人们生产、收集数据的能力大大提升,但是传统的关系型数据库在可扩展性、数据模型和可用性方面已远远不能满足当前的数据处理需求,因此,各种 NoSQL 数据库系统应运而生。

NoSQL 数据库不像关系型数据库那样都有相同的特点,遵循相同的标准。NoSQL 数据库类型多样,可满足不同场景的应用需求,因此取得了巨大的成功。

NoSQL 数据库基本理念是以牺牲事务机制和强一致性机制,来获取更好的分布式部署能力和横向扩展能力,创造出新的数据模型,使其在不同的应用场景下,对特定业务数据具有更强的处理性能。

NoSQL 数据库最初是为了满足互联网的业务需求而诞生的,互联网数据具有大量化、多样化、 快速化等特点。

在信息化时代背景下,互联网数据增长迅猛,数据集合规模已实现从 GB、PB 到 ZB 的飞跃。数据不仅仅是传统的结构化数据,还包含了大量的非结构化和半结构化数据,关系型数据库无法存储此类数据。

因此,很多互联网公司着手研发新型的、非关系型的数据库,这类非关系型数据库统称为 NoSQL 数据库,其主要优势如下。

灵活的数据模型

互联网数据如网站用户信息、地理位置数据、社交图谱、用户产生的内容、机器日志数据以及传感器数据等,正在快速改变着人们的通信、购物、广告、娱乐等日常生活,没有使用这些数据的应用很快就会被用户所遗忘。开发者希望使用非常灵活的数据库,容纳新的数据类型,并且不会被第三方数据提供商的数据结构变化所影响。

关系型数据库的数据模型定义严格,无法快速容纳新的数据类型。例如,若要存储客户的电话号码、姓名、地址、城市等信息,则 SQL 数据库需要提前知晓要存储的是什么。这对于敏捷开发模式来说十分不方便,因为每次完成新特性时,通常都需要改变数据库的模式。

NoSQL 数据库提供的数据模型则能很好地满足这种需求,各种应用可以通过这种灵活的数据模型存储数据而无须修改表;或者只需增加更多的列,无须进行数据的迁移。

可伸缩性强

对企业来说,关系型数据库一开始是普遍的选择。然而,在使用关系型数据库的过程中却遇到了越来越多的问题,原因在于它们是中心化的,是纵向扩展而不是横向扩展的。这使得它们不适合那些需要简单且动态可伸缩性的应用。

NoSQL 数据库从一开始就是分布式、横向扩展的,因此非常适合互联网应用分布式的特性。

在互联网应用中,当数据库服务器无法满足数据存储和数据访问的需求时,只需要增加多台服务器,将用户请求分散到多台服务器上,即可减少单台服务器的性能瓶颈出现的可能性。

自动分片

由于关系型数据库存储的是结构化的数据,所以通常采用纵向扩展,即单台服务器要持有整个数据库来确保可靠性与数据的持续可用性。这样做的代价是非常昂贵的,而且扩展也会受到限制。针对这种问题的解决方案就是横向扩展,即添加服务器而不是扩展单台服务器的处理能力。

NoSQL 数据库通常都支持自动分片,这意味着它们会自动地在多台服务器上分发数据,而不需要应用程序增加额外的操作。

自动复制

NoSQL 数据库支持自动复制。在 NoSQL 数据库分布式集群中,服务器会自动对数据进行备份,即将一份数据复制存储在多台服务器上。因此,当多个用户访问同一数据时,可以将用户请求分散到多台服务器中。

同时,当某台服务器岀现故障时,其他服务器的数据可以提供备份,即 NoSQL 数据库的分布式集群具有高可用性与灾备恢复的能力。

分布式数据库有哪些特点?

大数据需要通过分布式的集群方式来解决存储和访问的问题。分布式系统的核心理念是让多台服务器协同工作,完成单台服务器无法处理的任务,尤其是高并发或者大数据量的任务。

分布式数据库是数据库技术与网络技术相结合的产物,它通过网络技术将物理上分开的数据库连接在一起,进行逻辑层面上的集中管理。

在分布式数据库系统中,一个应用程序可以对数据库进行透明操作,数据库中的数据分别存储在不同的局部数据库中,由不同机器上不同的 DBMS 进行管理,其的体系结构如下图所示。

img

分布式数据处理使用分而治之的办法来解决大规模数据管理问题,它处理数据的基本特点如下。

分布的透明管理

在分布式系统中,数据不是存储在一个场地上,而是存储计算网络的多个场地上。但逻辑上是一个整体,它们被所有用户共享,并由一个 DBMS 统一管理。用户访问数据时无须指出数据存放在哪里,也不需要知道由分布式系统中的哪台服务器来完成。

复制数据的透明管理

分布式数据的复制有助于提高性能,更易于协调不同而又冲突的用户需求。同时,当某台服务器出现故障时,此服务器上的数据在其他服务器上还有备份,提高了系统的可用性。

这种多副本的方式对用户来说是透明的,即用户不需要知道副本的存在,由系统统一管理、协调副本的调用。

事务的可靠性

分布式数据处理具有重复的构成,因此消除了单点故障的问题,即系统中一个或多个服务器发送故障不会使整个系统瘫痪,从而提高了系统的可靠性。

但是在分布式系统中,事务是并发的, 即不同用户可能在同一时间对同一数据源进行访问,这就要求系统支持分布式的并发控制,保证系统中数据的一致。

分布式系统可以解决海量数据的存储和访问,但是在分布式环境下,数据库会遇到更为复杂的问题,举例如下。

  • 数据在分布式环境下以多副本方式进行存储,那么,在为用户提供数据访问时如何选择一个副本,或者用户修改了某一副本的数据,如何让系统中每个副本都得到更新。
  • 如果正在更新系统所有副本信息时,某个服务器由于网络或硬、软件功能出现问题导致其发生故障。在这种情况下,如何确保故障恢复时,此服务器上的副本与其他副本一致。

这些问题给分布式数据库管理系统带来了挑战,它们是分布式系统固有的复杂性,但更重要的是对分布数据的管理,控制数据之间的一致性以及数据访问的安全性。

CAP理论是什么?

CAP 理论是针对分布式数据库而言的,它是指在一个分布式系统中,一致性(Consistency, C)、可用性(Availability, A)、分区容错性(Partition Tolerance, P)三者不可兼得。

一致性(C)

一致性是指“all nodes see the same data at the same time”,即更新操作成功后,所有节点在同一时间的数据完全一致。

一致性可以分为客户端和服务端两个不同的视角:

  • 从客户端角度来看,一致性主要指多个用户并发访问时更新的数据如何被其他用户获取的问题;
  • 从服务端来看,一致性则是用户进行数据更新时如何将数据复制到整个系统,以保证数据的一致。

一致性是在并发读写时才会出现的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

可用性(A)

可用性是指“reads and writes always succeed”,即用户访问数据时,系统是否能在正常响应时间返回结果。

好的可用性主要是指系统能够很好地为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。在通常情况下,可用性与分布式数据冗余、负载均衡等有着很大的关联。

分区容错性(P)

分区容错性是指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。分区容错性高指在部分节点故障或出现丢包的情况下,集群系统仍然能提供服务,完成数据的访问。分区容错可视为在系统中采用多副本策略。

相互关系

CAP 理论认为分布式系统只能兼顾其中的两个特性,即出现 CA、CP、AP 三种情况,如图所示。

分布式数据库CAP理论示意图

CA without P

如果不要求 Partition Tolerance,即不允许分区,则强一致性和可用性是可以保证的。其实分区是始终存在的问题,因此 CA 的分布式系统更多的是允许分区后各子系统依然保持 CA。

CP without A

如果不要求可用性,相当于每个请求都需要在各服务器之间强一致,而分区容错性会导致同步时间无限延长,如此 CP 也是可以保证的。很多传统的数据库分布式事务都属于这种模式。

AP without C

如果要可用性高并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了实现高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。

总结

在实践中,可根据实际情况进行权衡,或者在软件层面提供配置方式,由用户决定如何选择 CAP 策略。

CAP 理论可用在不同的层面,可以根据 CAP 原理定制局部的设计策略,例如,在分布式系统中,每个节点自身的数据是能保证 CA 的,但在整体上又要兼顾 AP 或 CP。

ACID原则是什么?

ACID 是关系型数据库的事务机制需要遵守的原则。事务是一个一致和可靠计算的基本单元,由作为原子单元执行的一系列数据库操作组成。数据库库一般在启动时会提供事务机制,包括事务启动、停止、取消或回滚等。

关系型数据库支持事务的 ACID 原则,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),这四种原则保证在事务过程当中数据的正确性,具体描述如下。

原子性(A)

一个事务的所有系列操作步骤被看成一个动作,所有的步骤要么全部完成,要么一个也不会完成。如果在事务过程中发生错误,则会回滚到事务开始前的状态,将要被改变的数据库记录不会被改变。

一致性(C)

一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏,即数据库事务不能破坏关系数据的完整性及业务逻辑上的一致性。

隔离性(I)

主要用于实现并发控制,隔离能够确保并发执行的事务按顺序一个接一个地执行。通过隔离,一个未完成事务不会影响另外一个未完成事务。

持久性(D)

一旦一个事务被提交,它应该持久保存,不会因为与其他操作冲突而取消这个事务。

从事务的四个特性可以看到关系型数据库是要求强一致性的,但是这一点在 NoSQL 数据库中是重点弱化的机制。原因是当数据库保存强一致性时,很难保证系统具有横向扩展和可用性的优势,因此针对分布式数据存储管理只提供了弱一致性的保障,即 BASE 原理

BASE原理与最终一致性

BASE 理论是针对 NoSQL 数据库而言的,它是对 CAP 理论中一致性(C)和可用性(A)进行权衡的结果,源于提出者自己在大规模分布式系统上实践的总结。其核心思想是无法做到强一致性,但每个应用都可以根据自身的特点,采用适当方式达到最终一致性。

基本可用(Basically Available)

基本可用指分布式系统在出现故障时,系统允许损失部分可用性,即保证核心功能或者当前最重要功能可用。

对于用户来说,他们当前最关注的功能或者最常用的功能的可用性将会获得保证,但是其他功能会被削弱。

软状态(Soft-state)

软状态允许系统数据存在中间状态,但不会影响系统的整体可用性,即允许不同节点的副本之间存在暂时的不一致情况。

最终一致性(Eventually Consistent)

最终一致性要求系统中数据副本最终能够一致,而不需要实时保证数据副本一致。例如,银行系统中的非实时转账操作,允许 24 小时内用户账户的状态在转账前后是不一致的,但 24 小时后账户数据必须正确。

最终一致性是 BASE 原理的核心,也是 NoSQL 数据库的主要特点,通过弱化一致性,提高系统的可伸缩性、可靠性和可用性。而且对于大多数 Web 应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是多数分布式数据库产品的方向。

最终一致性可以分为客户端和服务端两个不同的视角。

从客户端的角度看

从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题,最终一致性有以下 5 个变种。

一致性变种说明
因果一致性如果进程 A 通知进程 B 它已更新了一个数据项,那么,进程 B 的后续访问将返回更新后的值,且一次写入将保证取代前一次写入。与进程 A 无因果关系的进程 C 的访问遵守一般的最终一致性规则。
读己之所写(Read-Your-Writes) 一致性当进程 A 自己更新一个数据项之后,它总是访问到更新过的值,且不会看到旧值。这是因果一致性模型的一个特例。
会话(Session)—致性这是上一个模型的实用版本,它把访问存储系统的进程放到会话的上下文中。只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统保证不会延续到新的会话。
单调(Monotonic)读一致性如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值。
单调写一致性系统保证来自同一个进程的写操作顺序执行。

上述最终一致性的不同方式可以进行组合,例如,单调读一致性和“读己之所写”一致性就可以组合实现。从实践的角度来看,这两者的组合读取自己更新的数据,一旦读取到最新的版本,就不会再读取旧版本,对基于此架构上的程序开发来说,会减少很多额外的烦恼。

从服务器的角度看

从服务端来看,如何尽快地将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验度非常重要的方面。

分布式数据系统有以下特性:

  • N 为数据复制的份数。
  • W 为更新数据时需要进行写操作的节点数。
  • R 为读取数据的时候需要读取的节点数。

如果 W+R>N,写的节点和读的节点重叠,则是强一致性。例如,对于典型的一主一备同步复制的关系型数据库(N=2, W=2,R=1),则不管读的是主库还是备库的数据,都是一致的。

如果 W+R≤N,则是弱一致性。例如,对于一主一备异步复制的关系型数据库(N=2,W=1,R=1),如果读的是备库,则可能无法读取主库已经更新过的数据,所以是弱一致性。

对于分布式系统,为了保证高可用性,一般设置 N≥3。设置不同的N、W、R 组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。

如果N=W 且 R=1,则任何一个写节点失效,都会导致写失败,因此可用性会降低。但是由于数据分布的 N 个节点是同步写入的,因此可以保证强一致性。

如果 N=R 且 W=1,则只需要一个节点写入成功即可,写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性。在这种情况下,如果 W<(N+1)/2,并且写入的节点不重叠,则会存在写冲突。

NoSQL数据库有哪些?

关系型数据库产品很多,如 MySQL、Oracle、Microsoft SQL Sever 等,但它们的基本模型都是关系型数据模型。NoSQL 并没有统一的模型,而且是非关系型的。

常见的 NoSQL 数据库包括键值数据库、列族数据库、文档数据库和图形数据库,其具体分类和特点如表所示。

分类相关产品应用场景数据模型优点缺点
键值数据库RedisMemcached、Riak内容缓存,如会话、配置文件、参数等; 频繁读写、拥有简单数据模型的应用<key,value> 键值对,通过散列表来实现扩展性好,灵活性好,大量操作时性能高数据无结构化,通常只被当做字符串或者二进制数据,只能通过键来查询值
列族数据库Bigtable、HBase、Cassandra分布式数据存储与管理以列族式存储,将同一列数据存在一起可扩展性强,查找速度快,复杂性低功能局限,不支持事务的强一致性
文档数据库MongoDB、CouchDBWeb 应用,存储面向文档或类似半结构化的数据<key,value> value 是 JSON 结构的文档数据结构灵活,可以根据 value 构建索引缺乏统一查询语法
图形数据库Neo4j、InfoGrid社交网络、推荐系统,专注构建关系图谱图结构支持复杂的图形算法复杂性高,只能支持一定的数据规模

NoSQL 数据库并没有一个统一的架构,两种不同的 NoSQL 数据库之间的差异程度,远远超过两种关系型数据库之间的不同。

可以说,NoSQL 数据库各有所长,一个优秀的 NoSQL 数据库必然特别适用于某些场合或者某些应用,在这些场合中会远远胜过关系型数据库和其他的 NoSQL 数据库。

常见的 NoSQL 数据库分为以下几种。

1) 键值数据库

这一类数据库主要会使用到一个散列表,这个表中有一个特定的键和一个指针指向特定的数据。

键值模型对于 IT 系统来说,其优势在于简单、易部署。键值数据库可以按照键对数据进行定位,还可以通过对键进行排序和分区,以实现更快速的数据定位。

2) 列族数据库

列族数据库通常用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列,如图所示。

img

此列族数据库表中由两行组成,每一行都有关键字 Row Key,每一行由多个列族组成,即 Column-Family-1 和 Column-Family-2,而每个列族由多个列组成。

3) 文档数据库

文档数据库的灵感来自 Lotus Notes 办公软件,它与键值数据库类似。该类型的数据模型是版本化的文档,文档以特定的格式存储,如 JSON。

文档数据库可以看作键值数据库的升级版,允许之间嵌套键值,如图所示。

img

文档数据库比键值数据库的查询效率更高, 因为文档数据库不仅可以根据键创建索引,同时还可以根据文档内容创建索引。

4) 图形数据库

图形数据库来源于图论中的拓扑学,以节点、边及节点之间的关系来存储复杂网络中的数据,如图所示。

img

这种拓扑结构类似 E-R 图,但在图形模式中,关系和节点本身就是数据,而在 E-R 图中,关系描述的是一种结构。

内存数据库是什么?

内存数据库主要是把磁盘的数据加载到内存中进行相应操作。

与直接读取磁盘数据相比,内存的数据读取速度要高出几个数量级,因此,将数据保存在内存中能够极大地提高应用的性能。

内存数据库改变了磁盘数据管理的传统方式,基于全部数据都在内存中的特点重新设计了体系结构,并且在数据缓存、快速算法、并行操作方面也进行了相应的升级,因此,其数据处理速度一般比传统数据库的数据处理速度快几十倍。

内存数据库的最大特点是其应用数据常驻内存中,即活动事务只与实时内存数据库的内存进行数据交流。

常见的内存数据库有 Memcached、Redis、SQLite、Microsoft SQL Server Compact 等。

Memcached入门教程

Memcached 是 LiveJournal 旗下 Danga Interactive 公司的布拉德•菲茨帕特里克(BradFitzpatric)开发的一款内存数据库,现在已被应用于 Facebook、LiveJournal 等公司用于提高 Web 服务质量。

目前这款软件流行于全球各地,经常被用来建立缓存项目,并以此分担来自传统数据库的并发负载压力。

Memcached 可以轻松应对大量同时出现的数据请求,而且它拥有独特的网络结构,在工作机制方面,它还可以在内存中单独开辟新的空间,建立 HashTable,并对 HashTable 进行有效的管理。

我们有时会见到 Memcache 和 Memcached 两种不同的说法,为什么会有两种名称?

其实 Memcache 是这个项目的名称,而 Memcached 是它服务器端的主程序文件名。一个是项目名称,另一个是主程序文件名。

为什么要使用 Memcached

由于网站的高并发读写和对海量数据的处理需求,传统的关系型数据库开始出现瓶颈。

对数据库的高并发读写

关系型数据库本身就是个庞然大物,处理过程非常耗时(如解析 SQL 语句、事务处理等)。如果对关系型数据库进行高并发读写(每秒上万次的访问),数据库系统是无法承受的。

对海量数据的处理

对于大型的 SNS 网站(如 Twitter、新浪微博),每天有上千万条的数据产生。对关系型数据库而言,如果在一个有上亿条数据的数据表中查找某条记录,效率将非常低。

使用 Memcached 就能很好地解决以上问题。

多数 Web 应用都将数据保存到关系型数据库中(如 MySQL ),Web 服务器从中读取数据并在浏览器中显示。但随着数据量的增大,访问的集中,关系型数据库的负担就会加重,岀现响应缓慢、网站打开延迟时间长等问题。

因此,使用 Memcached 的主要目的是通过自身内存中缓存关系型数据库的查询结果,减少数据库自身被访问的次数,以提高动态 Web 应用的速度,增强网站架构的并发能力和可扩展性。

通过在事先规划好的系统内存空间中临时缓存数据库中的各类数据,以达到减少前端业务服务对关系型数据库的直接高并发访问,从而达到提升大规模网站集群中动态服务的并发访问能力。

Web 服务器读取数据时先读 Memcached 服务器,若 Memcached 没有所需的数据,则向数据库请求数据,然后 Web 再把请求到的数据发送到 Memcached,如下图所示。

img

Redis是什么?

Redis 是一个开源的、高性能的、键值对内存数据库。它通过提供多种键值数据类型来满足不同场景下的存储需求,并借助许多高层级的接口使其可以胜任如缓存、队列系统等不同的角色。

本小节将介绍 Redis 的历史和特性,以使读者能够快速地对 Redis 有一个全面的了解。

Redis 历史与发展

2008 年,意大利的一家创业公司 Merzia 推出了一款基于 MySQL 的网站实时统计系统 —— LLOOGG,然而没过多久,该公司的创始人萨尔瓦托•桑菲利普(Salvatore Sanfilippo)便对这个系统的性能感到失望,于是他决定亲自为 LLOOGG 量身定做一个数据库,并于 2009 年开发完成, 这个数据库就是Redis。

不过萨尔瓦托并不满足只将 Redis 用于 LLOOGG 这一款产品,而是希望让更多的人使用它,于是萨尔瓦托将 Redis 开源发布,并开始和 Redis 的另一名主要的代码贡献者皮特•诺德胡斯(Pieter Noordhuis)一起继续着 Redis 的开发,直到今天。

萨尔瓦托自己也没有想到,在短短的几年时间内,Redis 就拥有了庞大的用户群体。Hacked News 在 2012 年发布了一份数据库的使用情况调查,结果显示有近 12% 的公司在使用 Redis。

国内如新浪微博和知乎,国外如 GitHub、Stack Overflow、Flickr、暴雪和 Instagram,都是 Redis 的用户。现在使用 Redis 的用户越来越多,大多数的互联网公司都使用 Redis 作为公共缓存。

VMware 公司从 2010 年开始赞助 Redis 的开发,萨尔瓦托和皮特也分别于同年的 3 月和 5 月加入 VMware,全职开发 Redis。

Redis 特性

存储结构

有过脚本语言编程经验的读者对字典(或称映射、关联数组)数据结构一定很熟悉,如在代码 dict[“key”]=“value” 中,“diet”是一个字典结构变量,字符串“key”是键名,而“value”是键值,在字典中可以获取或设置键名对应的键值,也可以删除一个键。

Redis 是 Remote Dictionary Server(远程字典服务器)的缩写,它以字典结构存储数据,并允许其他应用通过 TCP 读写字典中的内容。同大多数脚本语言中的字典一样,Redis 字典中的键值除了可以是字符串,还可以是其他数据类型。

到目前为止,Redis 支持的键值数据类型有:字符串类型、散列类型、列表类型、集合类型和有序集合类型。

这种字典形式的存储结构与常见的 MySQL 等关系数据库的二维表形式的存储结构有很大的差异。举个例子,在程序中使用 post 变量存储了一篇文章的数据(包括标题、正文、阅读量和标签),如下所示:

post["title"] = "Hello World!"
post["content"] = "Blablabla..."
post["views"] = 0
post["tags"] = ["PHP","Ruby","Node.js"]

现在希望将这篇文章的数据存储在数据库中,并且要求可以通过标签检索岀文章。

如果使用关系数据库存储,一般会将其中的标题、正文和阅读量存储在一个表中,而将标签存储在另一个表中,然后使用第三个表连接文章和标签表。需要查询时还需要连接三个表,不是很直观。

而 Redis 字典结构的存储方式和对多种键值数据类型的支持使得开发者可以将程序中的数据直接映射到 Redis 中,数据在 Redis 中的存储形式和其在程序中的存储方式非常相似。

使用 Redis 的另一个优势是其对不同的数据类型提供了非常方便的操作方式,如使用集合类型存储文章标签,Redis 可以对标签进行如交集、并集等集合运算操作。

内存存储与持久化

Redis 数据库中的所有数据都存储在内存中。由于内存的读写速度远高于硬盘,因此 Redis 在性能上与其他基于硬盘存储的数据库相比有非常明显的优势。在一台普通的笔记本电脑上,Redis 可以在一秒内读写超过 10 万个键值。

将数据存储在内存中也有问题,例如,程序退出后内存中的数据会丢失。不过 Redis 提供了对持久化的支持,即可以将内存中的数据异步写入硬盘中,同时不影响其继续提供服务。

功能丰富

Redis 虽然是作为数据库开发的,但由于其提供了丰富的功能,越来越多的人将其用作缓存、 队列系统等。Redis 可谓是名副其实的多面手。

Redis 可以为每个键设置生存时间(Time To Live, TTL),生存时间到期后键会自动被删除。 这一功能配合出色的性能让 Redis 可以作为缓存系统来使用,而且由于 Redis 支持持久化和丰富的数据类型,使其成为另一个非常流行的缓存系统 Memcached 的有力竞争者。

讨论 Redis 和 Memcached 的优劣一直是一个热门的话题。由于 Redis 是单线程模型,而 Memcached 支持多线程,所以在多核服务器上后者的性能更高一些。

然而,前面已经介绍过,Redis 的性能已经足够优异,在绝大部分场合下其性能都不会成为瓶颈。所以在使用时更应该关心的是二者在功能上的区别,如果需要用到高级的数据类型或持久化等功能,Redis 将会是 Memcached 很好的替代品。

作为缓存系统,Redis 还可以限定数据占用的最大内存空间,在数据达到空间限制后可以按照一定的规则自动淘汰不需要的键。

除此之外,Redis 的列表类型键可以用来实现队列,支持阻塞式读取,并且可以很容易地实现一个高性能的优先级队列。同时,在更高层面上,Redis 还支持“发布/订阅”的消息模式,用户可以基于此构建聊天室等系统。

简单稳定

如果一个工具使用起来太复杂,即使它的功能再丰富也很难吸引人。Redis 直观的存储结构使其通过程序与 Redis 交互十分简单。在 Redis 中使用命令来读写数据,命令语句之于 Redis 就相当于 SQL 之于关系数据库。

例如,在关系数据库中要获取 posts 表内 id 为 1 的记录的 title 字段的值,可以使用如下 SQL 语句实现:

SELECT title FROM posts WHERE id=1 LIMIT 1

相对应的,在 Redis 中要读取键名为 post:1 的散列类型键的 title 字段的值,可以使用如下命令语句实现:

HGET post : 1 title

其中,HGET 就是一个命令。Redis 提供了 100 多个命令,但是常用的只有十几个,并且每个命令都很容易记住。

Redis 提供了几十种不同编程语言的客户端库,这些库都封装了 Redis 的命令,这样在程序中与 Redis 进行交互变得很容易。

有些库还提供了可以将编程语言中的数据类型直接以相应的形式存储到 Redis 中(如将数组直接以列表类型存入 Redis)的简单方法,使用起来非常方便。

Redis 使用 C 语言开发,代码量只有 3 万多行。这降低了用户通过修改 Redis 源代码来使之更适合自己项目需要的门槛。对于希望“榨干”数据库性能的开发者而言,这无疑具有很大的吸引力。

Redis 是开源的,有将近 100 名开发者为 Redis 贡献了代码。良好的开发氛围和严谨的版本发布机制使得 Redis 的稳定版本的性能非常可靠,如此多的公司选择使用 Redis 也可以印证这 一点。

Memcached 与 Redis 比较

Memcached 与 Redis 的比较见下表。

数据库CPU内存利用率持久性数据结构工作环境
Memcached支持多核简单Linux/Windows
Redis单核低(压缩比 Memcached 高)有(硬盘存储,主从同步)复杂Linux

四、分布式系统

什么是分布式系统

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算存储任务。其目的是利用更多的机器,处理更多的数据

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

在很多文章中,主要讲分布式系统分为分布式计算(computation)与分布式存储(storage)。计算与存储是相辅相成的,计算需要数据,要么来自实时数据(流数据),要么来自存储的数据;而计算的结果也是需要存储的。在操作系统中,对计算存储有非常详尽的讨论,分布式系统只不过将这些理论推广到多个节点罢了。

那么分布式系统怎么将任务分发到这些计算机节点呢,很简单的思想,分而治之,即分片(partition)。对于计算,那么就是对计算任务进行切换,每个节点算一些,最终汇总就行了,这就是MapReduce的思想;对于存储,更好理解一下,每个节点存一部分数据就行了。当数据规模变大的时候,Partition是唯一的选择,同时也会带来一些好处:

(1)提升性能和并发,操作被分发到不同的分片,相互独立

(2)提升系统的可用性,即使部分分片不能用,其他分片不会受到影响

理想的情况下,有分片就行了,但事实的情况却不大理想。原因在于,分布式系统中有大量的节点,且通过网络通信。单个节点的故障(进程crash、断电、磁盘损坏)是个小概率事件,但整个系统的故障率会随节点的增加而指数级增加,网络通信也可能出现断网、高延迟的情况。在这种一定会出现的“异常”情况下,分布式系统还是需要继续稳定的对外提供服务,即需要较强的容错性。最简单的办法,就是冗余或者复制集(Replication),即多个节点负责同一个任务,最为常见的就是分布式存储中,多个节点复杂存储同一份数据,以此增强可用性与可靠性。同时,Replication也会带来性能的提升,比如数据的locality可以减少用户的等待时间。

下面这种来自Distributed systems for fun and profit 的图形象生动说明了Partition与Replication是如何协作的。

img

Partition和Replication是解决分布式系统问题的一记组合拳,很多具体的问题都可以用这个思路去解决。但这并不是银弹,往往是为了解决一个问题,会引入更多的问题,比如为了可用性与可靠性保证,引用了冗余(复制集)。有了冗余,各个副本间的一致性问题就变得很头疼,一致性在系统的角度和用户的角度又有不同的等级划分。如果要保证强一致性,那么会影响可用性与性能,在一些应用(比如电商、搜索)是难以接受的。如果是最终一致性,那么就需要处理数据冲突的情况。CAP、FLP这些理论告诉我们,在分布式系统中,没有最佳的选择,都是需要权衡,做出最合适的选择。

分布式系统挑战

分布式系统需要大量机器协作,面临诸多的挑战:

第一,异构的机器与网络

分布式系统中的机器,配置不一样,其上运行的服务也可能由不同的语言、架构实现,因此处理能力也不一样;节点间通过网络连接,而不同网络运营商提供的网络的带宽、延时、丢包率又不一样。怎么保证大家齐头并进,共同完成目标,这四个不小的挑战。

第二,普遍的节点故障:

虽然单个节点的故障概率较低,但节点数目达到一定规模,出故障的概率就变高了。分布式系统需要保证故障发生的时候,系统仍然是可用的,这就需要监控节点的状态,在节点故障的情况下将该节点负责的计算存储任务转移到其他节点

第三,不可靠的网络

节点间通过网络通信,而网络是不可靠的。可能的网络问题包括:网络分割、延时、丢包、乱序。

相比单机过程调用,网络通信最让人头疼的是超时:节点A向节点B发出请求,在约定的时间内没有收到节点B的响应,那么B是否处理了请求,这个是不确定的,这个不确定会带来诸多问题,最简单的,是否要重试请求,节点B会不会多次处理同一个请求。

总而言之,分布式的挑战来自不确定性,不确定计算机什么时候crash、断电,不确定磁盘什么时候损坏,不确定每次网络通信要延迟多久,也不确定通信对端是否处理了发送的消息。而分布式的规模放大了这个不确定性,不确定性是令人讨厌的,所以有诸多的分布式理论、协议来保证在这种不确定性的情况下,系统还能继续正常工作。

而且,很多在实际系统中出现的问题,来源于设计时的盲目乐观,觉得这个、那个应该不会出问题。Fallacies_of_distributed_computing很有意思,介绍了分布式系统新手可能的错误的假设:

The network is reliable.
Latency is zero.
Bandwidth is infinite.
The network is secure.
Topology doesn’t change.
There is one administrator.
Transport cost is zero.
The network is homogeneous.

刘杰在《分布式系统原理介绍》中指出,处理这些异常的最佳原则是:在设计、推导、验证分布式系统的协议、流程时,最重要的工作之一就是思考在执行流程的每个步骤时一旦发生各种异常的情况下系统的处理方式及造成的影响。

分布式系统特性与衡量标准

透明性:使用分布式系统的用户并不关心系统是怎么实现的,也不关心读到的数据来自哪个节点,对用户而言,分布式系统的最高境界是用户根本感知不到这是一个分布式系统,在《[Distributed Systems Principles and Paradigms](http://barbie.uta.edu/~jli/Resources/MapReduce&Hadoop/Distributed Systems Principles and Paradigms.pdf)》一书中,作者是这么说的:

A distributed system is a collection of independent computers that appears to its users as a single coherent system.

可扩展性:分布式系统的根本目标就是为了处理单个计算机无法处理的任务,当任务增加的时候,分布式系统的处理能力需要随之增加。简单来说,要比较方便的通过增加机器来应对数据量的增长,同时,当任务规模缩减的时候,可以撤掉一些多余的机器,达到动态伸缩的效果

可用性与可靠性:一般来说,分布式系统是需要长时间甚至7*24小时提供服务的。可用性是指系统在各种情况对外提供服务的能力,简单来说,可以通过不可用时间与正常服务时间的必知来衡量;而可靠性而是指计算结果正确、存储的数据不丢失。

高性能:不管是单机还是分布式系统,大家都非常关注性能。不同的系统对性能的衡量指标是不同的,最常见的:高并发,单位时间内处理的任务越多越好;低延迟:每个任务的平均时间越少越好。这个其实跟操作系统CPU的调度策略很像

一致性:分布式系统为了提高可用性可靠性,一般会引入冗余(复制集)。那么如何保证这些节点上的状态一致,这就是分布式系统不得不面对的一致性问题。一致性有很多等级,一致性越强,对用户越友好,但会制约系统的可用性;一致性等级越低,用户就需要兼容数据不一致的情况,但系统的可用性、并发性很高很多。

组件、理论、协议

假设这是一个对外提供服务的大型分布式系统,用户连接到系统,做一些操作,产生一些需要存储的数据,那么在这个过程中,会遇到哪些组件、理论与协议呢

用一个请求串起来

用户使用Web、APP、SDK,通过HTTP、TCP连接到系统。在分布式系统中,为了高并发、高可用,一般都是多个节点提供相同的服务。那么,第一个问题就是具体选择哪个节点来提供服务,这个就是负载均衡(load balance)。负载均衡的思想很简单,但使用非常广泛,在分布式系统、大型网站的方方面面都有使用,或者说,只要涉及到多个节点提供同质的服务,就需要负载均衡。

通过负载均衡找到一个节点,接下来就是真正处理用户的请求,请求有可能简单,也有可能很复杂。简单的请求,比如读取数据,那么很可能是有缓存的,即分布式缓存,如果缓存没有命中,那么需要去数据库拉取数据。对于复杂的请求,可能会调用到系统中其他的服务。

承上,假设服务A需要调用服务B的服务,首先两个节点需要通信,网络通信都是建立在TCP/IP协议的基础上,但是,每个应用都手写socket是一件冗杂、低效的事情,因此需要应用层的封装,因此有了HTTP、FTP等各种应用层协议。当系统愈加复杂,提供大量的http接口也是一件困难的事情。因此,有了更进一步的抽象,那就是RPC(remote produce call),是的远程调用就跟本地过程调用一样方便,屏蔽了网络通信等诸多细节,增加新的接口也更加方便。

一个请求可能包含诸多操作,即在服务A上做一些操作,然后在服务B上做另一些操作。比如简化版的网络购物,在订单服务上发货,在账户服务上扣款。这两个操作需要保证原子性,要么都成功,要么都不操作。这就涉及到分布式事务的问题,分布式事务是从应用层面保证一致性:某种守恒关系。

上面说道一个请求包含多个操作,其实就是涉及到多个服务,分布式系统中有大量的服务,每个服务又是多个节点组成。那么一个服务怎么找到另一个服务(的某个节点呢)?通信是需要地址的,怎么获取这个地址,最简单的办法就是配置文件写死,或者写入到数据库,但这些方法在节点数据巨大、节点动态增删的时候都不大方便,这个时候就需要服务注册与发现:提供服务的节点向一个协调中心注册自己的地址,使用服务的节点去协调中心拉取地址。

从上可以看见,协调中心提供了中心化的服务:以一组节点提供类似单点的服务,使用非常广泛,比如命令服务、分布式锁。协调中心最出名的就是chubby,zookeeper。

回到用户请求这个点,请求操作会产生一些数据、日志,通常为信息,其他一些系统可能会对这些消息感兴趣,比如个性化推荐、监控等,这里就抽象出了两个概念,消息的生产者与消费者。那么生产者怎么讲消息发送给消费者呢,RPC并不是一个很好的选择,因为RPC肯定得指定消息发给谁,但实际的情况是生产者并不清楚、也不关心谁会消费这个消息,这个时候消息队列就出马了。简单来说,生产者只用往消息队列里面发就行了,队列会将消息按主题(topic)分发给关注这个主题的消费者。消息队列起到了异步处理、应用解耦的作用。

上面提到,用户操作会产生一些数据,这些数据忠实记录了用户的操作习惯、喜好,是各行各业最宝贵的财富。比如各种推荐、广告投放、自动识别。这就催生了分布式计算平台,比如Hadoop,Storm等,用来处理这些海量的数据。

最后,用户的操作完成之后,用户的数据需要持久化,但数据量很大,大到按个节点无法存储,那么这个时候就需要分布式存储:将数据进行划分放在不同的节点上,同时,为了防止数据的丢失,每一份数据会保存多分。传统的关系型数据库是单点存储,为了在应用层透明的情况下分库分表,会引用额外的代理层。而对于NoSql,一般天然支持分布式。

一个简化的架构图

下面用一个不大精确的架构图,尽量还原分布式系统的组成部分(不过只能体现出技术,不好体现出理论)

img

概念与实现

那么对于上面的各种技术与理论,业界有哪些实现呢,下面进行简单罗列。

当然,下面的这些实现,小部分我用过,知其所以然;大部分听说过,知其然;还有一部分之前闻所未闻,分类也不一定正确,只是从其他文章抄过来的。罗列在这里,以便日后或深或浅的学习。

  • 负载均衡:

Nginx:高性能、高并发的web服务器;功能包括负载均衡、反向代理、静态内容缓存、访问控制;工作在应用层

LVS: Linux virtual server,基于集群技术和Linux操作系统实现一个高性能、高可用的服务器;工作在网络

  • webserver:

Java:Tomcat,Apache,Jboss

Python:gunicorn、uwsgi、twisted、webpy、tornado

  • service:

SOA、微服务、spring boot,django

  • 容器:

docker,kubernetes

  • cache:

memcache、redis等

  • 协调中心:

zookeeper、etcd等

zookeeper使用了Paxos协议Paxos是强一致性,高可用的去中心化分布式。zookeeper的使用场景非常广泛,之后细讲。

  • rpc框架:

grpc、dubbo、brpc

dubbo是阿里开源的Java语言开发的高性能RPC框架,在阿里系的诸多架构中,都使用了dubbo + spring boot

  • 消息队列:

kafka、rabbitMQ、rocketMQ、QSP

消息队列的应用场景:异步处理、应用解耦、流量削锋和消息通讯

  • 实时数据平台:

storm、akka

  • 离线数据平台:

hadoop、spark

PS: apark、akka、kafka都是scala语言写的,看到这个语言还是很牛逼的

  • dbproxy:

cobar也是阿里开源的,在阿里系中使用也非常广泛,是关系型数据库的sharding + replica 代理

  • db:

mysql、oracle、MongoDB、HBase

  • 搜索:

elasticsearch、solr

  • 日志:

rsyslog、elk、flume

总结

写这篇文章,我曾在网络上搜索过“如何学习分布式系统”,但实话说,没有很认同的答案。也许,这确实是一个难以回答的问题。于是,我想自己写出一个答案,但写完这篇文章,感觉自己的回答也很混乱,也没有说清楚,不过对我自己还是有一些指导意义的,比如,理清了分布式系统中会遇到的各种技术、理论、协议,以及通过一个例子展示他们是如何协作的,接下来就是各个击破了。

网上的诸多回答,上来就是看各种论文,google三大件、paxos什么的,个人觉得不是很实用。更好的过程,是先有一个整体的把握,然后自己思考会有什么问题,带着问题去寻求答案,在寻求答案的时候再去看论文。

另外,也有很多人提到,掌握好计算机基础知识,如操作系统计算网络,对学习分布式系统是大有裨益的,这一点我很赞同。分布式系统解决问题的思路是早就有的,很多都是前人研究透的问题,思想都是相同的。比如函数式编程中的map reduce之于Hadoop的MapReduce,比如磁盘存储的raid之于Partition与Replication,比如IPC之于消息队列。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐