分布式专题-01漫谈分布式架构

前言

从今天我们开始进入分布式专题,分布式作为互联网企业技术人员必备的技能之一。也是我们本套分享内容所有专题都是围绕分布式展开的,不可谓不重要~

分布式架构的演进过程

本节重点:

1.了解分布式架构中的相关概念

2.初始分布式架构及意义

3.分布式架构的发展过程和历史

4.分布式架构的演进过程

5.构建分布式架构最重要的因素

分布式架构的发展历史

1946 年情人节(2.14) , 世界上第一台电子数字计算机诞生在美国宾夕法尼亚大学大学,它的名字是:ENIAC; 这台计算机占地 170 平米、重达 30 吨,每秒可进行 5000 次加法运算。第一台电子计算机诞生以后,意味着一个日新月异的 IT 时代的到来。一方面单台计算机的性能每年都在提升:从最早的 8 位 CPU 到现在的 64 位 CPU;从早期的 MB 级内存到现在的 GB 级别内存;从慢速的机械存储到现在的固态 SSD 硬盘存储。

tips:冯诺依曼模型
在这里插入图片描述
ENIAC 之后,电子计算机便进入了 IBM 主导的大型机时代,IBM 大型机之父吉恩.阿姆达尔被认为是有史以来最伟大的计算机设计师之一。1964 年 4 月 7 日,在阿姆达尔的带领下,历时三年,耗费 50 亿美元,第一台 IBM 大型机 SYSTEM/360 诞生。这使得 IBM 在 20 实际 50~60 年代统治整个大型计算机工业,奠定了 IBM 计算机帝国的江山。

2.1 IBM 大型机曾支撑美国航天登月计划

2.2 IBM 主机一直服务于金融等核心行业的关键领域

由于高可靠性和超强的计算能力,即便在 X86 和云计算飞速发

展的情况下,IBM 的大型机依然牢牢占据着一定的高端市场份额

20 世纪 80 年代,在大型机霸主的时代,计算机架构同时向两个方向发展

3.1 以 CISC (微处理器执行的计算机语言指令集) CPU 为架构

的价格便宜的面向个人的 PC

3.2 以 RISC (精简指令集计算机) CPU 为架构的价格昂贵的面

向企业的小型 UNIX 服务器

分布式架构发展的里程碑

大型主机的出现。凭借着大型机超强的计算和 I/O 处理能力、稳定性、安全性等,在很长一段时间内,大型机引领了计算机行业及商业计算领域的发展。而集中式的计算机系统架构也成为了主流。随着计算机的发展,这种架构越来越难以适应人们的需求,比如说

1.由于大型主机的复杂性,导致培养一个能够熟练运维大型主机的人的成本很高

2.大型主机很贵,一般只有土豪(政府、金融、电信)才能用得起

3.单点问题,一台大型主机出现故障,那么整个系统将处于不可用状态。而对于大型机的使用群体来说,这种不可用导致的损失是非常大的

4.科技在进步,技术在进步。PC 机性能不断提升,很多企业放弃大型机改用小型机及普通 PC 来搭建系统架构

阿里巴巴在 2009 年发起了一项"去 IOE"运动

IOE 指的是 IBM 小型机、Oracle 数据库、EMC 的高端存储 2009 年“去 IOE”战略透露,到 2013 年 5 月 17 日最后一台 IBM 小型机在支付宝下线。

为什么要去 IOE?

阿里巴巴过去一直采用的是 Oracle 数据库,并利用小型机和高端存储设备提供高性能的数据处理和存储服务。随着业务的不断发展,数据量和业务量呈爆发性增长,传统的集中式 Oracle 数据库架构在扩展性方面遭遇瓶颈。

传统的商业数据库软件(Oracle,DB2),多以集中式架构为主,这些传统数据库软件的最大特点就是将所有的数据都集中在一个数据库中,依靠大型高端设备来提供高处理能力和扩展性。集中式数据库的扩展性主要采用向上扩展(Scale up)的方式,通过增加 CPU,内存,磁盘等方式提高处理能力。这种集中式数据库的架构,使得数据库成为了整个系统的瓶颈,已经越来越不适应海量数据对计算能力的巨大需求

分布式系统的意义

  1. 升级单机处理能力的性价比越来越低

单机的处理能力主要依靠 CPU、内存、磁盘。通过更换硬件做垂直扩展的方式来提升性能,成本会越来越高。

  1. 单机处理能力存在瓶颈

单机处理能力存在瓶颈,CPU、内存都会有自己的性能瓶颈,也就是说就算你是土豪不惜成本去提升硬件,但是硬件的发展速度和性能是有限制的。

  1. 稳定性和可用性这两个指标很难达到

单机系统存在可用性和稳定性的问题,这两个指标又是我们必须要去解决的

分布式架构的常见概念

集群

小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群

在这里插入图片描述

分布式

为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群

在这里插入图片描述

节点

节点是指一个可以独立按照分布式协议完成一组逻辑的程序个体。在具体的项目中,一个节点表示的是一个操作系统上的进程。

副本机制

副本(replica/copy)指在分布式系统中为数据或服务提供的冗余。

数据副本指在不同的节点上持久化同一份数据,当出现某一个节点的数据丢失时,可以从副本上读取到数据。数据副本是分布式系统中解决数据丢失问题的唯一手段。

服务副本表示多个节点提供相同的服务,通过主从关系来实现服务的高可用方案

中间件

中间件位于操作系统提供的服务之外,又不属于应用,他是位于应用和系统层之间为开发者方便的处理通信、输入输出的一类软件,能够让用户关心自己应用的部分。

架构的发展过程

一个成熟的大型网站系统架构并不是一开始就设计的非常完美,也不是一开始就具备高性能、高可用、安全性等特性,而是随着用户量的增加、业务功能的扩展逐步完善演变过来的。在这个过程中,开发模式、技术架构等都会发生非常大的变化。而针对不同业务特征的系统,会有各自的侧重点,比如像淘宝这类的网站,要解决的是海量商品搜索、下单、支付等问题;

像腾讯,要解决的是数亿级别用户的实时消息传输;百度所要解决的是海量数据的搜索。每一个种类的业务都有自己不同的系统架构。我们简单模拟一个架构演变过程。

我们以 javaweb 为例,来搭建一个简单的电商系统,从这个系统中来看系统的演变历史;要注意的是,接下来的演示模型,关注的是数据量、访问量提升,网站结构发生的变化, 而不是具体关注业务功能点。其次,这个过程是为了让大家更好的了解网站演进过程中的一些问题和应对策略。假如我们系统具备以下功能:

用户模块:用户注册和管理

商品模块:商品展示和管理

交易模块:创建交易及支付结算

阶段一,单应用架构

在这里插入图片描述
网站的初期也可以认为是互联网发展的早起,我们经常会在单机上跑我们所有的程序和软件。

把所有软件和应用都部署在一台机器上,这样就完成一个简单系统的搭建,这个时候的讲究的是效率

阶段二,应用服务器和数据库服务器分离

随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,在服务器还没有超载的时候,我们应该做好规划,提升网站的负载能力。假如代码层面的优化已经没办法继续提高,在不提高单台机器的性能,增加机器是一个比较好的方式,投入产出比非常高。这个阶段增加机器的主要目的是讲 web 服务器和数据库服务器拆分,这样不仅提高了单机的负载能力,也提高了容灾能力
在这里插入图片描述

阶段三,应用服务器集群

应用服务器负载告警,如何让应用服务器走向集群

随着访问量的继续增加,单台应用服务器已经无法满足需求。在假设数据库服务器还没有遇到性能问题的时候,我们可以增加应用服务器,通过应用服务器集群将用户请求分流到各个服务器中,从而继续提升负载能力。此时多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务
在这里插入图片描述
架构发展到这个阶段,各种问题也会慢慢呈现

1.用户请求由谁来转发到具体的应用服务器

2.用户如果每次访问到的服务器不一样,那么如何维护 session
在这里插入图片描述

阶段四,数据库压力变大,数据库读写分离

架构演变到这里,并不是终点。上面我们把应用层的性能拉上来了,但是数据库的负载也在慢慢增大,那么怎么去提高数据库层面的负载呢?有了前面的思路以后,自然会想到增加服务器。但是假如我们单纯的把数据库一分为二,然后对于后续数据库的请求,分别负载到两台数据库服务器上,那么一定会造成数据库不统一的问题。所以我们一般先考虑读写分离的方式

在这里插入图片描述
这个架构的变化会带来几个问题

1.主从数据库之间的数据同步 ; 可以使用 mysql 自带的 master-slave 方式实现主从复制

2.对应数据源的选择 ; 采用第三方数据库中间件,例如 mycat

阶段五,使用搜索引擎缓解读库的压力

数据库做读库的话,尝尝对模糊查找效率不是特别好,像电商类的网站,搜索是非常核心的功能,即便是做了读写分离,这个问题也不能有效解决。那么这个时候就需要引入搜索引擎了使用搜索引擎能够大大提高我们的查询速度,但是同时也会带来一些附加的问题,比如维护索引的构建。
在这里插入图片描述

阶段六,引入缓存机制缓解数据库的压力

随着访问量的持续增加,逐渐出现许多用户访问统一部分内容的情

况,对于这些热点数据,没必要每次都从数据库去读取,我们可以

使用缓存技术,比如 memcache、redis 来作为我们应用层的缓存;

另外在某些场景下,比如我们对用户的某些 IP 的访问频率做限制,

那这个放内存中又不合适,放数据库又太麻烦,这个时候可以使用

Nosql 的方式比如 mongDB 来代替传统的关系型数据库

在这里插入图片描述

阶段七,数据库的水平/垂直拆分

我们的网站演进的变化过程,交易、商品、用户的数据都还在同一个数据库中,尽管采取了增加缓存,读写分离的方式,但是随着数据库的压力持续增加,数据库的瓶颈仍然是个最大的问题。因此我们可以考虑对数据的垂直拆分和水平拆分

垂直拆分:把数据库中不同业务数据拆分到不同的数据库

在这里插入图片描述

水平拆分:把同一个表中的数据拆分到两个甚至跟多的数据库中

水平拆分的原因是某些业务数据量已经达到了单个数据库的瓶颈,

这时可以采取讲表拆分到多个数据库中

在这里插入图片描述

阶段八,应用的拆分

随着业务的发展,业务越来越多,应用的压力越来越大。工程规模也越来越庞大。这个时候就可以考虑讲应用拆分,按照领域模型讲我们的用户、商品、交易拆分成多个子系统

在这里插入图片描述

这样拆分以后,可能会有一些相同的代码,比如用户操作,在商品和交易都需要查询,所以会导致每个系统都会有用户查询访问相关操作。这些相同的操作一定是要抽象出来,否则就会是一个坑。所以通过走服务化路线的方式来解决

在这里插入图片描述

那么服务拆分以后,各个服务之间如何进行远程通信呢?

通过 RPC 技术,比较典型的有:webservice、hessian、http、RMI

等等

前期通过这些技术能够很好的解决各个服务之间通信问题,but,互联网的发展是持续的,所以架构的演变和优化还在持续

在这里插入图片描述
成熟的互联网架构模型:
在这里插入图片描述
前面我们讲过经典理论-冯.诺依曼体系,计算机硬件由运算器、控制器、存储器、输入设备、输出设备五大部分组成。不管架构怎么变化,计算机仍没有跳出该体系的范畴;

输入设备的变化

在分布式系统架构中,输入设备可以分两类,第一类是互相连接的多个节点,在接收其他节点传来的信息作为该节点的输入;另一种就是传统意义上的人机交互的输入设备了输出设备的变化

输出和输入类似,也有两种,一种是系统中的节点向其他节点传输信息时,该节点可以看作是输出设备;另一种就是传统意义上的人际交互的输出设备,比如用户的终端

控制器的变化

在单机中,控制器指的是 CPU 中的控制器,在分布式系统中,控制器主要的作用是协调或控制节点之间的动作和行为;比如硬件负载均衡器;LVS 软负载;规则服务器等

运算器

在分布式系统中,运算器是由多个节点来组成的。运用多个节点的计算能力来协同完成整体的计算任务

存储器

在分布式系统中,我们需要把承担存储功能的多个节点组织在一起,组成一个整体的存储器;比如数据库、redis(key-value 存储)

分布式系统的难点

毫无疑问,分布式系统对于集中式系统而言,在实现上会更加

复杂。分布式系统将会是更难理解、设计、构建 和管理的,同时意味着应用程序的根源问题更难发现。

三态

在集中式架构中,我们调用一个接口返回的结果只有两种,成功或者失败,但是在分布式领域中,会出现“超时”这个状态。

分布式事务

这是一个老生常谈的问题,我们都知道事务就是一些列操作的原子性保证,在单机的情况下,我们能够依靠本机的数据库连接和组件轻易做到事务的控制,但是分布式情况下,业务原子性操作很可能是跨服务的,这样就导致了分布式事务,例如 A 和 B 操作分别是不同服务下的同一个事务操作内的操作,A 调用 B,A 如果可以清楚的知道 B 是否成功提交从而控制自身的提交还是回滚操作,但是在分布式系统中调用会出现一个新状态就是超时,就是 A 无法知道 B 是成功还是失败,这个时候 A 是提交本地事务还是回滚呢?其实这是一个很难的问题,如果强行保证事务一致性,可以采取分布式锁,但是那样会增加系统复杂度而且会增大系统的开销,而且事务跨越的服务越多,消耗的资源越大,性能越低,所以最好的解决方案就是避免分布式事务。

还有一种解决方案就是重试机制,但是重试如果不是查询接口,
必然涉及到数据库的变更,如果第一次调用成功但是没返回成功结果,那调用方第二次调用对调用方来说依然是重试,但是对于被调用方来说是重复调用,例如 A 向 B 转账,A-100,B + 100,这样会导致 A 扣了 100,而 B 增加 200。这样的结果不是我们期望的,因此需在要写入的接口做幂等设计。多次调用和单次调用是一样的效果。通常可以设置一个唯一键,在写入的时候查询是否已经存在,避免重复写入。但是幂等设计的一个前提就是服务是高可用,否则无论怎么重试都不能调用返回一个明确的结果调用方会一直等待,虽然可以限制重试的次数,但是这已经进入了异常状态了,甚至到了极端情况还是需要人肉补偿处理。其实根据 CAP 和 BASE 理论,不可能在高可用分布式情况下做到一致性,一般都是最终一致性保证。

负载均衡

每个服务单独部署,为了达到高可用,每个服务至少是两台机器,因为互联网公司一般使用可靠性不是特别高的普通机器,长期运行宕机概率很高,所以两台机器能够大大降低服务不可用的可能性,这正大型项目会采用十几台甚至上百台来部署一个服务,这不仅是保证服务的高可用,更是提升服务的 QPS,但是这样又带来一个问题,一个请求过来到底路由到哪台机器?路由算法很多,有 DNS 路由,如果 session 在本机,还会根据用户 id 或则 cookie 等信息路由到固定的机器,当然现在应用

服务器为了扩展的方便都会设计为无状态的,session 会保存到专有的 session 服务器,所以不会涉及到拿不到 session 问题。那路由规则是随机获取么?这是一个方法,但是据我所知,实际情况肯定比这个复杂,在一定范围内随机,但是在大的范围也会分为很多个域,例如如果为了保证异地多活的多机房,夸机房调用的开销太大,肯定会优先选择同机房的服务,这个要参考具体的机器分布来考虑。

一致性

数据被分散或者复制到不同的机器上,如何保证各台主机之间的数据的一致性将成为一个难点。

故障的独立性

分布式系统由多个节点组成,整个分布式系统完全出问题的概率是存在的,但是在时间中出现更多的是某个节点出问题,其他节点都没问题。这种情况下我们实现分布式系统时需要考虑得更加全面些

分布式架构设计

主流架构模型-SOA

架构和微服务架构

SOA 全称(Service Oriented Architecture),中文意思为

“面向服务的架构”,他是一种设计方法,其中包含多个服务,服务之间通过相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在与操作系统进程中。各个服务之间通过网络调用

跟 SOA 相提并论的还有一个 ESB(企业服务总线),简单来说 ESB 就是一根管道,用来连接各个服务节点。为了集成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通;
在这里插入图片描述
在这里插入图片描述

SOA 所解决的核心问题

1.系统集成:站在系统的角度,解决企业系统间的通信问题,把原先散乱、无规划的系统间的网状结构,梳理成规整、可治理的系统间星形结构,这一步往往需要引入一些产品,比如 ESB、以及技术规范、服务管理规范;这一步解决的核心问题是【有序】

2.系统的服务化:站在功能的角度,把业务逻辑抽象成可复用、可组装的服务,通过服务的编排实现业务的快速再生,目的:把原先固有的业务功能转变为通用的业务服务,实现业务逻辑的快速复用;这一步解决的核心问题是【复用】

3.业务的服务化:站在企业的角度,把企业职能抽象成可复用、可组装的服务;把原先职能化的企业架构转变为服务化的企业架构,进一步提升企业的对外服务能力;“前面两步都是从技术层面来解决系统调用、系统功能复用的问题”。第三步,则是以业务驱动把一个业务单元封装成一项服务。这一步解决的核心问题是【高效】

微服务架构

微服务架构其实和 SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。

组件表示一个可以独立更换和升级的单元,就像 PC 中的CPU、内存、显卡、硬盘一样,独立且可以更换升级而不影响其他单元。如果我们把 PC 作为组件以服务的方式构建,那么这台 PC 只需要维护主板和一些必要的外部设备。CPU、内存、硬盘都是以组件方式提供服务,PC 需要调用 CPU 做计算处理,只需要知道 CPU 这个组件的地址即可。

微服务的特征

1.通过服务实现组件化

2.按业务能力来划分服务和开发团队

3.去中心化

4.基础设施自动化(devops、自动化部署)

SOA 和微服务架构的差别

1.微服务不再强调传统 SOA 架构里面比较重的 ESB 企业服务总线,同时 SOA 的思想进入到单个业务系统内部实现真正的组件化

2.Docker 容器技术的出现,为微服务提供了更便利的条件,比如更小的部署单元,每个服务可以通过类似 Node 或者 Spring Boot 等技术跑在自己的进程中。

3.还有一个点大家应该可以分析出来,SOA 注重的是系统集成方面,而微服务关注的是完全分离

领域驱动设计及业务驱动划分

领域驱动设计的概念

领域驱动设计(DDD,Domain-Driven Design),软件开发不是一蹴而就的事情,我们不可能在不了解产品(或行业领域)的前提下进行软件开发,在开发前,通常需要进行大量的业务知识梳理,然后才到软件设计的层面,最后才是开发。而在业务知识梳理的过程中,我们必然会形成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念

为什么需要 DDD

业务初期,功能大都非常简单,普通的 CRUD 就能满

足,此时系统是清晰的。随着产品不断迭代和演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂。各个模块之间彼此关联,甚至到后期连作者都很难说清模块的具体功能意图是啥。导致在修改一个功能时,要追溯到这个功能需要的修改点就需要很长时间,更别提修改带来的不可预知的影响面。 比如说:

在这里插入图片描述
订单服务接口中提供了查询、创建订单相关的接口,也提供了订单评价、支付的接口。同时订单表是个大表,包含了非常多字段。在我们维护代码时,将会导致牵一发而动全身,很可能只是想改下评价相关的功能,却影响到了创建订单的核心路径。虽然我们可以通过测试来保证功能完备性,但当我们在订单领域有大量需求同时并行开发时,改动重叠、恶性循环、疲于奔命修改各种问题。

绝大部分公司都是这样一个状态,然后一般的解决方案是不断的重构系统,让系统的设计随着业务成长也进行不断的演进。通过重构出一些独立的类来存放某些通用的逻辑解决混乱问题,但是我们很难给它一个业务上的含义,只能以技术纬度进行描述,这个带来的问题就是其他人接手这块代码的时候不知道这个的含义或者可以通过修改这块通用逻辑来达到某些需求

领域模型追本溯源

实领域模型本身就不是一个陌生的单词,说直白点,在早期领域模型就是数据库设计. 我们做传统项目的流程或者说包括现在我们做项目的流程,都是首先讨论需求,然后是数据库建模, 在需求逐步确定的过程不断的去更新数据库的设计。接着我们在项目开发阶段,发现有些关系没有建、有些字段少了、有些表结构设计不合理,又在不断的去调整设计。最后上线。在传统项目中,数据库是整个项目的根本,数据模型出来以后后续的开发都是围绕着数据展开;然后形成如下的一个架构
在这里插入图片描述
1.service 很重,所有逻辑处理基本都放在 service 层。

2.POJO()作为 service 层的非常重要的一个实体,会因为不同场景的需求做不同的变化和组合,就会早成 POJO 的几种不同模型(失血、贫血、充血),用来形容领域模型太胖或者太瘦

随着业务变得复杂以后,包括数据结构的变化,那么各个模块就需要进行修改,原本清晰的系统经过不断的演化变得复杂、冗余、耦合度高。后果就非常严重

我们试想一下如果一个软件产品不依赖数据库存储设备,那我们怎么去设计这个软件呢?如果没有了数据存储,那么我们的领域模型就得基于程序本身来设计。那这个就是 DDD 需要去考虑的问题

以抽奖设计为例

BD理解起来有点抽象, 这个有点像设计模式,感觉很有用,但是不知道怎么应用到自己写的代码里面,或者生搬硬套最后看起来又很别扭,那么接下来以一个简单的转盘抽奖案例来分析一下 DDD 的应用

针对功能层面划分边界

这个系统可以划分为运营管理平台和用户使用层,运营平台对于抽奖的配置比较复杂但是操作频率会比较低。而用户对抽奖活动页面的使用是高频率的但是对于配置规则来说是误感知的,根据这样的特点,我们把抽奖平台划分针对 C 端抽奖和 M 端抽奖两个子域

在确认了 M 端领域和 C 端的限界上下文后,我们再对各自上下文内部进行限界上下文的划分,接下来以 C 端用户为例来划分界限上下文

确认基本需求

首先我们要来了解该产品的基本需求

1.抽奖资格(什么情况下会有抽奖机会、抽奖次数、抽奖的活动起始时间)

2.抽奖的奖品(实物、优惠券、理财金、购物卡…)

3.奖品自身的配置,概率、库存、某些奖品在有限的概率下还只能被限制抽到多少次等

4.风控对接, 防止恶意薅羊毛

针对产品功能划分边界
在这里插入图片描述
抽奖上下文是整个领域的核心,负责处理用户抽奖的核心业务。

1.对于活动的限制,我们定义了活动资格的通用语言,将活动开始/

结束时间,活动可参与次数等限制条件都收拢到活动资格子域中。

2.由于 C 端存在一些刷单行为,我们根据产品需求定义了风控上下文,用于对活动进行风控

3.由于抽奖和发放奖品其实可以认为是两个领域,一个负责根据概率去抽奖、另一个负责将选中的奖品发放出去。所以对于这一块也独立出来一个领域

细化上下文
通过上下文划分以后,我们还需要进一步梳理上下文之间的关系,梳理的好处在于:

1.任务更好拆分(一个开发人员可以全身心投入到相关子

域的上下文中),

2.方便沟通,明确自身上下文和其他上下文之间的依赖关

系,可以实现更好的对接

然后是基于上下文的更进一步细化建模,在 DDD 中存在一

些名字定义

实体

当一个对象由其标识(而不是属性)区分时,这种对象称

为实体(Entity)。

值对象

当一个对象用于对事物进行描述而没有唯一标识时,它被

称作值对象

聚合根

聚合根属于实体对象,它是领域对象中一个高度内聚的核心对象。(聚合根具有全局的唯一标识,而实体只有在聚合内部有唯一的本地标识,值对象没有唯一标识,不存在这个值对象或那个值对象的说法)

领域服务

一些重要的领域行为或操作,可以归类为领域服务。它实现了全部业务逻辑并且通过各种校验手段保证业务的正确性。

资源库

资源封装了基础设施来提供查询和持久化聚合操作。这样能够让我们始终关注在模型层面,把对象的存储和访问都委托给资源库来完成。他不是数据库的封装,而是领域层与基础设施之间的桥梁。DDD 关心的是领域内的模型,而不是数据库的操作。

代码设计

详见 mic 在 git 上 2018 年的代码

在实际开发中,我们一般会采用模块来表示一个领域的界限上下文,比如

com.gupaoedu.michael.bussiness.lottery.;//抽奖上下文
com.gupaoedu.michael.bussiness.riskcontrol.;// 风控上下文
com.gupaoedu.michael.bussiness.prize.
;//奖品上下文
com.gupaoedu.michael.bussiness.qualification.;// 活动资格上下文
com.gupaoedu.michael.bussiness.stock.
;//库存上下文*

对于模块内的组织结构,一般情况下我们是按照领域对象、

领域服务、领域资源库、防腐层等组织方式定义的。

com.gupaoedu.michael.bussiness.lottery.domain.valobj.;//领域对象-值对象
com.gupaoedu.michael.bussiness.lottery.domain.entity.;//领域对象-实体
com.gupaoedu.michael.bussiness.lottery.domain.aggregate.
;//领域对象-聚合根
com.gupaoedu.michael.bussiness.lottery.service.;//领域服务
com.gupaoedu.michael.bussiness.lottery.repo.
;//领域资源库*

领域驱动的好处

用 DDD 可以很好的解决领域模型到设计模型的同步、演进最后映射到实际的代码逻辑。总的来说,DDD 有几个好处

1.DDD 能够让我们知道如何抽象出限界上下文上下文以及如何去分而治之

分而治之:把复杂的大规模软件拆分成若干个子模块,每一个模块都能独立运行和解决相关问题。并且分割后各个部分可以组装成为一个整体。
抽象:使用抽象能够精简问题空间,而且问题越小越容易理解,比如说我们要对接支付,我们抽象的纬度应该是支付,而不是具体的微信支付还是支付宝支付

2.DDD 的限界上下文可以完美匹配微服务的要求

在系统复杂之后,我们都需要用分治来拆解问题。一般有两种方式,技术维度和业务维度。技术维度是类似 MVC 这样,业务维度则是指按业务领域来划分系统。

微服务架构更强调从业务维度去做分治来应对系统复杂度,而 DDD 也是同样的着重业务视角

总结

领域驱动设计其实我们可以简单认为是一种指导思想,是一种软件开发方法,通过 DDD 可以将系统解构更加合理,最终满足高内聚低耦合的本质。在我的观点来看,有点类似数据库的三范式,我们开始在学的时候并不太理解,当有足够的设计经验以后慢慢发现三范式带来的好处。同时我们也并不一定需要严格按照这三范式去进行实践,有些情况下是可以灵活调整。

分布式架构的基本理论 CAP、BASE 以及应用

说 CAP、BASE 理论之前,先要了解下分布式一致性的这个问题

实际上,对于不同业务的产品,我们对数据一致性的要求是不一样的,比如 12306,他要求的是数据的严格一致性,不能说把票卖给用户以后发现没有座位了;比如银行转账,你们通过银行转账的时候,一般会收到一个提示:转账申请将会在 24 小时内到账;实际上这个场景满足的是最终钱只要汇出去了即可,同时以及如果钱没汇出去要保证资金不丢失就行;所以说,用户在使用不同的产品的时候对数据一致性的要求是不一样的

关于分布式一致性问题

在分布式系统中要解决的一个重要问题就是数据的复制。在我们的日常开发经验中,相信很多开发人员都遇到过这样的问题:在做数据库读写分离的场景中,假设客户端 C1 将系统中的一个值 K 由 V1 更新为 V2,但客户端 C2 无法立即读取到 K 的最新值,需要在一段时间之后才能 读取到。这很正常,因为数据库复制之间存在延时。
在这里插入图片描述
所谓的分布式一致性问题,是指在分布式环境中引入数据复制机制之后,不同数据节点之间 可能出现的,并无法依靠计算机应用程序自身解决的数据不一致的情况。简单讲,数据一致性就是指在对一个副本数据进行更新的时候,必须确保也能够更新其他的 副本,否则不同副本之间的数据将不一致。

那么如何去解决这个问题?按照正常的思路,我们可能会想,既然是因为网络延迟导致的问题,那么我们可以把同步动作进行阻塞,用户 2 在查询的时候必须要等到数据同步完成以后再来做。但是这个方案带来的问题是性能会收到非常大的影响。如果同步的数据比较多或者比较频繁,
那么因为阻塞操作可能将导致整个新系统不可用的情况;总结: 所以我们没有办法找到一种能够满足数据一致性、又不影响系统运行的性能的方案,所以这个地方就诞生了一个一致性的级别:

1.强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大

2.弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态

3.最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较用的多的模型

CAP 理论

一个经典的分布式系统理论。CAP 理论告诉我们:一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中两项。CAP 理论

在互联网界有着广泛的知名度,也被称为“帽子理论”,它是由 Eric Brewer 教授在 2000 年举行的 ACM 研讨会提出的一个著名猜想:

一致性(Consistency)、可用性(Availability)、分区容错(Partition-tolerance)三者无法在分布式系统中同时被满足,并且最多只能满足两个!

一致性:所有节点上的数据时刻保持同步

可用性:每个请求都能接收一个响应,无论响应成功或失败

分区容错:系统应该持续提供服务,即时系统内部(某个节点分区)有消息丢失。比如交换机失败、网址网络被分成几个子网,形成脑裂;服务器发生网络延迟或死机,导致某些 server 与集群中的其他机器失去联系

分区是导致分布式系统可靠性问题的固有特性,从本质上来看,CAP 理论的准确描述不应该是从 3 个特性中选取两个,所以我们只能被迫适应,根本没有选择权;

总结一下:CAP 并不是一个普适性原理和指导思想,它仅适用于原子读写的 NoSql 场景中,并不适用于数据库系统。

BASE 理论

从前面的分析中知道:在分布式(数据库分片或分库存在的多个实例上)系统下,CAP 理论并不适合数据库事务(因为更新一些错误的数据而导致的失败,无论使用什么样的高可用方案都是徒劳,因为数据发生了无法修正的错误)。此外 XA 事务虽然保证了数据库在分布式系统下的 ACID (原子性、一致性、隔离性、持久性)特性,但也带来了一些性能方面的代价,对于并发和响应时间要求比较高的电商平台来说,是很难接受的。

eBay 尝试了另外一条完全不同的路,放宽了数据库事务的 ACID 要求,提出了一套名为 BASE 的新准则。BASE 全称是 Basically available,soft-state,Eventually Consistent.

系统基本可用、软状态、数据最终一致性。相对于 CAP 来说,它大大降低了我们对系统的要求。

Basically available(基本可用),在分布式系统出现不可预知的故障时,允许瞬时部分可用性

1.比如我们在淘宝上搜索商品,正常情况下是在 0.5s 内返回查询结果,但是由于后端的系统故障导致查询响应时间变成了 2s

2.再比如数据库采用分片模式,100W 个用户数据分在 5 个数据库实例上,如果破坏了一个实例,那么可用性还有 80%,也就是 80%的用户都可以登录,系统仍然可用
3.电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现

soft-state(软状态). 表示系统中的数据存在中间状态,并且这个中间状态的存在不会影响系统的整体可用性,也就是表示系统允许在不同节点的数据副本之间进行数据同步过程中存在延时;比如订单状态,有一个待支付、支付中、支付成功、支付失败, 那么支付中就是一个中间状态,这个中间状态在支付成功以后,在支付表中的状态同步给订单状态之前,中间会存在一个时间内的不一致。

Eventually consistent(数据的最终一致性),表示的是所有数据副本在一段时间的同步后最终都能达到一个一直的状态,因此最终一致性的本质是要保证数据最终达到一直,而不需要实时保证系统数据的强一致

BASE 理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性

分布式架构下的高可用设计

避免单点故障

a)负载均衡技术(failover/选址/硬件负载/

软件负载 / 去中心化的软件负载( gossip(redis-cluster)))

b)热备(linux HA)

c)多机房(同城灾备、异地灾备)

应用的高可用性

a)故障监控(系统监控(cpu、内存)/链路监控/日志监控) 自动预警

b)应用的容错设计、(服务降级、限流)自我保护能力

c)数据量(数据分片、读写分离)

分布式架构下的可伸缩设计

垂直伸缩 提升硬件能力

水平伸缩 增加服务器

加速静态内容访问速度的 CDN

CDN 是 Content Delivery Network 的缩写,表示的是内容分发网络。CDN 的作用是把用户需要的内容分发到离用户最近的地方,这样可以是用户能够快熟获取所需要的内容。 CDN 其实就是一种网络缓存技术,能够把一些相对稳定的资源放到距离最终用户较近的地方,一方面可以节省整个

广域网的贷款消耗,另外一方面可以提升用户的访问速度,改进用户体验。我们一般会把静态的文件(图片、脚本、静态页面)放到 CDN 中

在这里插入图片描述

1.当用户点击网站页面上的内容 URL,经过本地 DNS 系统解析,DNS系统会最终将域名的解析权交给 CNAME 指向的 CDN 专用 DNS 服务器

2.CDN 的 DNS 服务器将 CDN 的全局负载均衡设备 IP 地址返回用户

3.用户向 CDN 的全局负载均衡设备发起内容 URL 访问请求

4.CDN 全局负载均衡设备根据用户 IP 地址,以及用户请求的内容 URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。

5.区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户 IP 地址,判断哪一台服务器距用户最近;根据用户所请求的 URL 中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的 IP 地址

6.局负载均衡设备把服务器的 IP 地址返回给用户

用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。

什么情况下用 CDN

最适合的是那些不会经常变化的内容,比如图片,JS 文件, CSS 文件,图片文件包括程序模板中的,CSS 文件中用到的背景图片,还有就是作为网站内容组成部分的那些图片,都可以;

灰度发布

发布的时候一般会采用灰度发布,也就是会对新应用进行分批发布,逐步扩大新应用在整个及群众的比例直到最后全部完成。灰度发布是针对新引用在用户体验方面完全无感知。

灰度发布系统的作用在于,可以根据自己的配置,来将用户的流量导到新上线的系统上,来快速验证新的功能修改,而一旦出问题,也可以马上的恢复,简单的说,就是一套 A/BTest 系统.
在这里插入图片描述

总结

下节预告:

分布式架构的基础

  • 分布式架构基石- TCP通信协议
  • 分布式系统的基础HTTP及HTTPS协议
  • 分布式架构原理之序列化与反序列化