- 主流架构模型-SOA架构和微服务架构
- 领域驱动设计及业务驱动划分。
- 分布式架构的基本理论CAP、BASE以及应用
- 什么是分布式架构下的高可用设计
- 分布式架构下的可伸缩设计
- 构建高性能的分布式架构
SOA架构和微服务架构
面向服务的架构,是架构模型,不是解决方案,是一种设计方法
在这种方法下,有多个服务,而服务之间是相互依赖的或者通过一定的通讯机制去完成通讯的。
在这个服务中,每个服务独立存在,而且可以单独运行和发布
ESB企业服务治理总线
ESB是一个管道,他是连通各个服务节点来继承不同系统,不同协议的一个服务,可以简单的理解为,它做了一个消息转换和消息路由的工作。让不同的服务去互连互通。
前端一个页面上的信息来自不同的系统,或者说不同的应用的话,那么我们通过各个接口把数据展示出来,可能还是基于不同的协议,http协议,或者数据格式是json,XML不同的操作,如果前端要展示来自五个后台的数据。???
我们后台有多个应用,客户端所展示的数据是基于多个协议去做成的。客户端不可能去跟每一个服务去做调用,每个客户端,每一个节点所做的事情是不一样的。这时候服务端相互通信去进行一个扩展,暴露给客户端一个接口,客户端只调用一个接口就可以得到这些信息,而不需要一个一个去调用。这时候后端的程序就会存在相互的调用,后端的服务通讯怎么去做,各个服务之间就存在彼此的依赖关系,这个相互依赖关系可能会很复杂,彼此之间可能是相互调用,或者说是彼此调用,他可能是一个网状的结构。这是一个复杂的应用,一个电商平台的数据可能来自几百个服务,这都有可能。这时候我们后台可能存在成百个上千个这样的服务,我们后台架构就会变得非常的庞大,和复杂。
SOA的架构
所以在这样的一个结构下,我们要做一个调整,所以第一步是SOA架构,他是一个理论模型,或者是一个设计的思想,这时候我们的整个的交互就会变得很清晰,我们整个架构模型的就会变得很清楚,我们把原本杂乱和没有规划的系统,梳理成了一个新型的且可治理的新型结构最大的核心就是ESB。ESB里边引入了服务的路由和协议的转换,我们的一些其它的一些信息都会在ESB里边做一个处理,然后我们还会引入一些协议规范,和服务治理规范,去解决这样一个问题。
SOA解决的问题:
有序
复用(系统的服务化)
站在功能的角度,把我们的业务逻辑抽象成可以复用可以组装的服务,通过服务编排,实现业务的快速再生和可复用
目的:把原先的固有的业务功能做成通用的可复用的服务。
比如说:用户服务,订单模块,支付模块,他就是一个模块化,它只关注它这个业务领域,它可以把跟他领域相关的功能去对外提供一个服务,而这个服务我可以做成通用的,可复用的。
高效(业务的服务化)
站在企业的角度,把一些企业职能,职能化的架构抽象成可复用,可以组装的服务的架构,提升企业的对外服务能力
微服务架构
我们现在一直再说微服务,不太能区分微服务和SOA有什么关系。
简单理解:是SOA的一个持续的升华。
我们很难说清楚他们之间的一个关系,但是我们可以通过他们解决方案的领域和关注的一些点去看一些他们的区别。
微服务注重:
业务需要彻底的组件化和服务化。
以前我们的单个业务要拆分成多个可以独立开发运行和部署的程序,那么这样的一个小程序细细的拆分以后它们之间相互协作,相互通讯去完成一个交付和集成,这就是微服务架构。
组件化:
以PC机为例,可以组装一个品牌机,我们如果想要升级我们的电脑可以去更换我们的CPU,更换我们的显卡,更换我们的内存,更换我们其中的任意一个组件不会影响其它的一个单元的,这就是完全独立。
在微服务架构里边,需要我们对我们整个服务架构进行组件化的分解,然后各个组件之间进行组件化的分解,各个组件是一个彼此独立的进程,彼此之间通过http协议或者其他协议进行一个通讯。这就是微服务表达的一个组件化。
举个例子,我们把一个PC机当成一个组件化的服务去构建的话,这台PC里边,这个PC电脑只需要关注维护主板的一些内容,和一些必要的外设接入,这两个,而中间的CPU,内存,硬盘都是以组件的方式去提供, 比如说我们的电脑要发起一个计算的操作,一定会去调用我们CPU,内存等去协作,那知道CPU的地址,内存的地址,就可以发起一个处理,这就是组件化带来的好处。
微服务化有几个特征:
组件化
按照业务能力划分服务和开发团队
去中心化(不再关心ESB的企业总线)
比如说我们现在常说的区块链,就是我们去中心化带来的一门技术。
基础设施的自动化(devops)
比如说我们的自动化部署和我们的自动化运维,以及我们的自动化测试,就是我们的自动化
微服务和SOA的区别
最直观:去掉了ESB
微服务重点在于它不在关注ESB企业总线,而是深入到每一个业务单元,去真正的实现独立化!!!
最重要的区别:
SOA注重的是系统集成方面的问题
微服务关注的是完全独立,完全分离!!!
领域驱动设计及业务驱动划分
领域驱动设计是一种思想
它的概念跟我们的数据库三范式、设计模式是类似的。
我们最开始学习数据库,有三范式,列不可分,一件事,与主键相关,但是我们在运用数据库的时候,我们并不能很深入的沿用以及使用三范式,当有足够的经验去积累之后,就会发现你设计的东西就是满足数据库三范式的。但是在某些场进下,我不需要去依赖三范式,比如说冗余这块,有时候我们为了性能问题,去去做一些冗余的处理。设计模式很抽象,你很难真正把设计模式用在你的代码里边,如果你强制把设计模式用在你的代码里边,并不能保证它的一个扩展性,伸缩性。但是当你有一个很好的经验以后,你就不需要对这两个东西去进行一个解释,解释很难去理解,但是基于这样一个理念,多去实践,多取观察,这是一个慢慢提升的点。
领域驱动设计叫:DDD
正常来说(Domain Driver Development)也有人叫(Debug Driver Development)
软件开发不是一步到位的事情,我们不可能在不了解一个专业领域的时候就能够进行软件的开发,我们在开发软件产品的时候必须去了解相应的业务知识。这叫需求的一个确定,我们现在做互联网产品,先有一个需求,然后需求评审出来以后有个PRD,PRD产品文档出来以后在去跟开发去核对这个需求有没有问题,然后再去做开发层面上的任务分解。
所以这样一个业务知识的梳理,还有PRD的过程,实际上,我们要去做一些领域知识的一些梳理,领域就是我们现在说的。比如说我们要做一个互联网金融的产品,理财类的产品,不可能老板说我要做一个,你明天就要上线,后天就要投入使用,你就屁颠屁颠的去做。他这种不像我们以前的那种传统的管理系统那么简单,基于开发一个OA或者一个CRM,告诉你应该做个什么东西,基本上都有可能是系统做完以后,文档后边再补的,这种系统不会很依赖于复杂的业务知识,但是在这种大型的互联网金融产品里边,他会基于非常复杂的业务知识系统,所以我们没有办法在没有了解这个领域之前,去做出一个符合市场需求的一个产品。所以我们必须知道这个软件产品是什么!!!那我们要知道它是什么的时候,就需要了解这样一个专业领域的专业知识,这就是领域!!
还说我们做一个金融产品,那真正最熟悉这一块的就是线下去负责这一块金融领域的专家,专业人员,比如说我们要买基金,我是怎么样的过程,我需要去开户,然后去购买,然后告诉你期限,让你签协议,然后你购买成功以后多久,他是一个什么的状态的过程,这些业务知识,就是专业知识。这些专业知识你必须去线下去获得,你不能说你不了解去做这个东西。这个就叫领域。
我们要做这样要做这样一个产品,一定是基于这个领域去做,那么这个软件产品,我们必须要跟它的服务业务领域保持完全一致。甚至说,这个软件应该是这个领域的映射,所以
软件产品最关心它领域中最核心的概念和元素并且进行精确地实现他们的关系。所以这个软件需要对这个领域去建模。
DDD:
我们开始去做一个软件产品的时候,都不是很复杂,CRUD就能够满足我们简单的需求。(管他什么设计,老子就是一把梭,)因为这个时候系统很简单,逻辑很清晰,逻辑很简单,随着产品不断演化和迭代,这个业务会变得越来越复杂,在这个复杂变化的过程中,我们的各个系统之间也会变得越来越复杂和冗余,所以说各个模块之间也会存在一个非常大的关联,甚至到后期,你去维护这样一个功能的时候,就算是你自己开发的,你都很难去追溯到你要修改的这个点,甚至说,你改了这个点以后,引起的对其它的影响,你都可能都意识不到,这就是我们没有早期规划的时候所带来的一个负面影响。
早期开发的问题:
早期我们代码开发都是基于数据库的设计。当一个表中字段比较多,然后这个服务就存在多个维度的服务,这个服务不断变化,然后这个服务的逻辑越来越复杂,然后你改变其中一点就会对其它功能产生不可预期的变化。
业务存在一定的耦合,然后随着不断地重构系统,不断的拆分数据库,不断的重构我们的服务,不断的挖坑,填坑!!!
如图,这个服务就很不单纯!!!
早期的设计
我们现在的是基于数据库去设计的,然后生成你的代码,然后service调用你的dao层,然后controller调用service层。
我们的代码都是基于数据库去写的,然后就会出现问题,然后不断的优化,不断的重构代码,然后会存在非常大的问题:
Service层会很重
我们的controller会去做参数校验,就是一个请求过来以后,controller层调用service层,service层返回结果,然后返回到页面,controller其实就是一个转发功能,它没有业务逻辑,而所有的业务逻辑应该是在service层,就我们很多人说的逻辑应该下沉,不应该放到controller,controller不应该有很多业务逻辑,最后导致service层很重。
实体类的变化
我们封装简单对象从service层传到dao层,然后我们的这个对象根据不同场景的需求去做不同场景的变化。
打个比方,我们有一个USER表,有的时候User里边需要存一些关系怎么办?表里边没有这个字段,但是我后边要改啊,这个尸体就会随着这个service不断的变化和组合。最后POJO就会变得非常的复杂。
领域模型。
实体的三个状态:
充血:非常复杂(还有一些逻辑)
失血:非常单纯(只存在field, get , set)
贫血:介于充血和失血之间
在这样的一个模型下就会存在这样的一个结构,那这个结构就会很危险,导致充血的原因是就是我们在使用这个模型的时候因为不同的场景去做不同的变化,这就带来了一个非常隐患的坑,后续我们要去调整的话,要去做拆分的话,这个模型就是一个非常大的问题,这个就是一个领域驱动的重要性!!!
领域驱动层
在领域驱动中,service层和dao层中间新增加一层:领域驱动层。然后service就会变得很轻。一开始我们的开发和设计都是基于数据库去做的。但是说假如没有数据库的话,我们不需要基于数据库去设计的话,就不会有持久层这个模型了,这个时候我们就要基于功能去做设计,我们的领域层不需要去管数据库层的设计的话,在领域层,我们就需要去利用设计模式,和设计技巧,设计出领域层!!
领域驱动设计中的概念
这些概念会指导你怎么去设计你的程序。
界限上下文
实体、值对象、领域服务、聚合跟、资源
通过这些概念去约束你的代码程序,进行程序的设计,这是一些理论,就像是设计模式中的代理模式,代理模式就是这样子的,你就必须这样子去设计,这就是一个指导思想。
DDD理解
DDD理解起来很抽象,而且它是一个时间沉淀的过程,他是一种指导思想,但不需要完全按照领域模型去设计程序。他是一个时间沉淀的过程。不要被这种思想约束,如果一定要按照这种领域模型去拆分,有时候会变得很复杂。
例子:
我们现在做一个转盘抽奖的系统,很多人写代码,很不会去设计,如果按照领域驱动去设计的话,那么这个时候就有很多东西可以分析。从使用的层面来讲,而且是做成一个可配置画的产品的话,那么我们这里会分为两个应用角色,
M(运营),配置抽奖规则
奖品,奖项,奖品
C(用户),关心抽奖。
划分 M 端与 C 端就是划分界限上下文,
第一步:划分模块
我们分模块,用户、支付、订单、商品,就可以叫领域驱动设计。我们从大层次到代码层次。根据业务领域去划分模块,划分好以后,
M 端是运营,使用频次就会比较低。他就可能隔了很长时间才去配置,但是,他的功能会复杂一点,对于C端来说他的使用频次会很高,我们的页面的访问量会很高,我们会有不同的用户,薅羊毛的也会存在,所以考虑的场景是不一样的。
第二步:细化
2.细化(划分界限上下文)
界限上下文:我们怎么去划分模块边界,用户模块,支付模块,商品模块,我们知道他们所关心的点,就会很清楚,很层次化,但是在我们代码层次上怎么去划分,在很多时候,我们的某一个实体,可能在A的模块里边,它可能是觉得很合理的,那么我放B模块里边也合理,那么这个时候这个尸体,我们应该划分操A模块还是B模块里边,还是两个模块都放呢?所以界限上下文叫:(Boundary context),它表示的是在一个领域里边,我们所创建的一个领域的边界,就在我们刚才讲的订单模块里边,可能会看到订单的商品列表,商品模块里边也会有商品列表
订单模块-商品列表;
模块模块-商品列表;
订单里边有商品,商品模块里边有商品列表,这个时候怎么去划分,这是我们界限上下文需要去关注的。
DDD的一个设计的话,我们应该是根据一个领域所关注的点去做倾向化的设计,比如说,订单模块比较关注商品某个时刻的价格,价格的变动,我不需要去关心,就是一个订单快照,那么商品里边就不一样,它会有规格,他会有型号,他会有价格,它都会有浮动,这就是两个领域所关注的不一样,在DDD里边可以做一个冗余的设置,我在各自模块里边去定义自己更感兴趣的事情,!!!!
3.【M运营模块】【用户抽奖模块】
确定大模块的边界上下文
用户这一块基于C端去做细化的话,我们分析用户抽奖存在哪些需求。
需求是指导我们设计产品一个非常重要的指标,所以我们要去了解里边的需求。
4.需求
1.抽奖资格(抽奖时间、抽奖次数、什么情况才有抽奖资格)
2.奖品(实物、优惠券、购物卡、理财金)
3.奖项的概念【概率、库存、限制次数】
4.薅羊毛(风控)结合风控做防止。恶意去刷流量,
子域,支撑域,核心域。
核心领域调用多个支撑子域来完成整个功能。
领域拆分以后的好处:
更加细化每一个领域的功能,模块之间更好的解耦,划分边界,边界划分完了,再去写代码。各个人员去开发的时候就不会去相互的影响。每一个开发人员只需要关注自己的领域。他们之间可以更好的去沟通,可以去确定他们之间的调用关系!!!!!(跟设计模式很像)
Draw 抽奖子域
Façade奖品
Qualification抽奖资格
Risk 风控子域
每个子域都可以划分出来一个独立支撑的包,每个子域下,他有自己的领域模型
实体:
当一个对象需要一个id作为他的唯一标识的时候,我们就可以认为它是一个实体。
person, 一个奖品
值对象:
当一个对象不需要一个唯一标识去描述的时候,我们可以认为他是一个值对象,比如说我们在传递的上下文信息,我们的context不属于实体,它作为一个参数传递。
领域服务:
我们划分了不同的子域,每一个子域都有他的service,我们可以认为他是一个领域服务,这个服务可以提供一个对外的调用,对外暴露出自己的服务,提供调用。
聚合根:
聚合根,它具有全局的唯一标识,他和实体的区别在于,实体是在领域内部具有唯一标识,而聚合根在整个全局是唯一的标识。比如说我们的order,通过order这样一个东西去整合整个系统,叫做聚合根。
资源:
我们在访问数据库的时候,是通过dao层去做,dao层它关注的是我们实体对象的存储,持久化存储,存储到我们的数据库或者缓存中,dao层和我们的domain层之间还会有一个资源层,资源层,封装了我们基础的用来查询的,和操作的聚合服务。这样我们就关注在于给模型领域上,而不是一个数据库领域上,把对象的访问和存储都委托给资源层来处理。资源库可以理解为:数据库,缓存啊,资源层与数据库的差异在于它不是数据库的封装,而是说他是领域层与计出层之间的桥梁,在DDD里边,他关注的是领域内的模型,而不是数据库的操作,它会存在一个资源层叫repository。
设计模式,三范式,它就是一个指导思想,有这个思维就好了,
Dubbo就是一个按照领域去划分的一个实例!!!
领域模式是基于设计总结来的一些经验,
旧设计:
Service(){
//判断抽奖资格 --访问数据库
//风控 -调用远程服务
//抽奖 -private doDraw()
}
领域设计:
基于每个领域每一个包
每个包下边一个
service ,领域服务
repo(资源层),对dao层的一个整合,聚合操作
domain(聚合概念)值对象,实体,聚合根
划分领域
根据业务纬度去拆分,
基于用户体系去拆分,
基于需求再去拆分,
领域模型带来的好处:
代码的高内聚,低耦合很清晰
DDD是一个指导思想,不要完全按照DDD的思想去做,我们只需要达到我们业务领域的高内聚,低耦合就好了,
Service不要基于数据库层次的去做这个事情,而是基于领域去做,什么是领域, 领域就是业务领域,就是专业知识,我们要让业务领域映射到我们的代码中,代码要让所有的人都能看的懂。技术和专业人员有鸿沟,技术人员关注的东西是我怎么去实现,而专家关注的是他所关注的业务领域的知识,而我们就要通过领域去建模,去形成一种通用语言,而这个通用语言就是两个人都能看的懂,而且很清楚,而且代码出来以后,通过画图之后都能够看的懂,这就是达到一个非常高的境界了。
这就是领域驱动,领域驱动实际上就是我们模块的划分,所以DDD让我们如何区分界限上下文,如何去分而治之,你会知道,界限上下文能够完美匹配微服务的要求。
不断模仿,有这个意识,不断实践就会慢慢的理解了。
分布式架构的基本理论
CAP:
Consistency(一致性), 数据一致更新,所有数据变动都是同步的 Availability(可用性), 好的响应性能 Partition tolerance(分区容忍性) 可靠性
BASE:
放低一致性的要求
Basically Available、Soft state、Eventually consistent
分布式一致性问题
12306 要求买过票,立马不能再卖这一张了,强一致性
24小时到账,弱一致性,
不同的行业对一致性的要求不一样
数据库层次主备复制
由于网络延迟造成备份不及时,主要是副本的一致性,
分布式中,用户A对数据做了一个修改,用户A改完以后B立马去查看这个值,
数据库的同步复制,高一致性,同步复制会造成资源的占用比较高,写的时候不可以读,多个请求阻塞,造成服务器资源占用较高,容易宕机。网络远程传输,一定有网络延时。
CAP理论,一致性,和可用性出现矛盾
CP/AP不能同时满足。
在互联网行业中不适用!
CAP理论不适用我们数据库事务的高一致性。你的任何方法都是会有问题的。所以我们会放低一致性的要求
BASE:
基本可用:Basically Available
就是说:在出现不可预知的情况下,比如说网络故障的情况下,我允许丧失部分的可用性,但是我要让我的整个程序基本可用,(比如说我们的查询,由于某些系统故障,搜索由1S变成了2S。比如说我们的电商平台,做大促的时候,访问量比较高的情况下,会做一些降级页面,当前访问量过多引导到降级页面,把100万用户分给5个数据库,五台数据库挂了一台,还有四台,还有80%的用户是可以登陆的。)
软状态:Soft state
在系统中,某些数据会存在一些中间状态,这个中间状态的存在不会影响系统的可用性,这个软状态和最终一致性是一样的。
(状态机:在某一个领域里边会存在一个状态,叫状态机器,由状态机器去驱动我们流程的变更,比如说:订单的待支付、支付中。交易的状态:交易成功、交易失败)
数据最终一致性:Eventually consistent
订单下单后状态:交易处理中,交易成功,
支付渠道是一个异步的结果,
订单系统,支付系统,两个系统,
数据最终一致性
MQ消息中间件的重试机制,状态机去驱动,
支付将最终的结果发送到交易核心
订阅消息,支付成功,
一定存在某个时刻,这两个处理的状态一定是一致的,如果不一致,是需要人工干预,人工对账的机制。
分布式架构下的高可用设计
在架构设计中怎么保证我们架构的高可用是我们的一个重要机制!!!!
分布式架构高可用设计的指导:
1. 避免单点故障
a)负载均衡技术
(falivoer(容错)/选址/硬件负载/软件负载/去中心化的软件负载(gossip协议(redis-cluster)))
b) 热备(linux HA)
操作系统可能存在宕机的情况,linux有一个HA机制,可以针对这台服务器做一个高可用配置,配两台服务器,一模一样的,一台服务器出现故障,另一台服务器是可以去担当任务的,这两台服务器可能是共享磁盘,在双机热片的情况,能够解决我们服务器宕机的情况,
c) 多机房(同城灾备、异地灾备)
2. 应用的高可用性
a)故障监控
(系统监控(CPU、内存)/链路监控/日志监控) 自动预警,自动化运维
b) 应用的容错设计、服务降级、限流
自我保护能力
c) 数据量
(数据分片、读写分离)
互联网应用一般要保证几个9的指标:
三个9:99.9% 525分钟不可用
四个9:99.99% 52.6分钟不可用
五个9:99.999% 5.25分钟不可用
就算是你机房停电了,就算是中国停电了,你还要保证你在国外还有机房,这就是要保证你的超级可用,在特别大的企业必须要满足
分布式架构下的可伸缩设计
a) 垂直伸缩
我们在这台机器上去提升硬件能力,去达到我们的性能
b) 水平伸缩
增加服务器
CDN 内容分发网络
CDN
Content Delivery Network 内容分发网络
以前的架构图:
通过DNS解析域名后,然后通过ip访问主机
传统的方式:
主机在北,用户在南,跳跃很多个节点,就会有很大的延迟。
CNAME: 我们输入一个IP,我们可以解析到一个IP4或者IP6类型的域名,访问DNS,他告诉我不知道,它不知道的话,它就会跳转到一个叫CNAME的映射的一个域名,它就会想我们全局的域名解析器发起一个请求。全局解析器,会根据用户的IP地址,去选择用户所属区域的负载均衡设备去告诉用户,去向这一台设备发起一个请求,
CDN缓存服务器
静态资源放到CDN上可以
减少本地服务资源的开支
CDN就近转发,静态资源放在CDN上,一般都是通过阿里的CDN去做一个转发,我们把资源放到阿里的CDN上,解析到阿里的CND DNS,通过阿里提供的全局的负载均衡服务器,上解析最近的CDN服务器,根据ip去定位,然后找最近的CDN服务器,如果最近的服务器上没有这个缓存,那么就会去远程找到这个资源在放到这个CDN缓存服务器中。
如果IP不准的话就会存在问题
淘宝的IP的校准是通过淘宝的包裹记录去校准的。
CDN的机制
1) 缓存
2) 全局调度
3) 压缩过滤
4) 分发
5) CDN机房全部同步
发布这一块
A/B测试(灰度测试)
为了验证我们应用的可用性,为了保证我们产品的上线,确保万无一失,我们一般用到灰度发布。
新的功能
对某些用户开发
只发布某些服务器
发布以后:可以控制新功能的访问量,检测新功能的流量。
客户端可以申请,然后客户端访问以后,
进入接入层,根据配置系统通过配置规则,
看这次请求是旧系统的请求,还是新系统的请求。