Valentine 转载请标明出处。
主流架构模型-SOA架构和微服务架构
SOA全称(Service Oriented Architecture),中文意思为“面向服务的架构”,它是一种设计方法,其中包含多个服务,服务之间通过相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在与操作系统进程中,各个服务之间通过网络调用。
还有一个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就能满足,此时系统是清晰的,随着产品不断迭代和演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂,各个模块之间彼此关联,甚至到后期连作者都很难说清模块的具体功能意图是啥,导致在修改一个功能时,要追溯到这个功能需要的修改点就要很长时间,更别提修改带来的不可预知的影响面。
比如说订单服务接口中提供查询、创建订单相关的接口,也提供了订单评价、支付的接口。同时订单表是个大表,包含了非常多字段,在我们维护代码的时候,将会导致牵一发动全身,很可能只是想改下评价相关的功能,却影响到创建订单的核心路径,虽然我们可以通过测试来保证功能完备性,但当我们在订单领域有大量需求同时并行开发,改动重叠,恶性循环,疲于奔命修改各种问题。
绝大部分公司都是这样一个状态,然后一般的解决方案就是不断地重构系统,让系统的设计随着业务成长也进行不断的演进。通过重构出一些独立的类来存放某些通用的逻辑解决混乱问题,但是我们很难给它一个业务上的含义,只能以技术维度进行描述,这个带来的问题就是其他人接手这块代码的时候不知道这个的含义或者可以通过修改这块通用逻辑来达到某些需求。
领域模型追本溯源
领域模型本身就不是一个陌生的单词,说直白点,在早期领域模型就是数据库设计,我们做传统项目的流程或者说包括现在我们做项目的流程,都是先讨论需求,然后是数据库建模,在需求逐步确定的过程不断地去更新数据库设计,接着我们在项目开发阶段,发现有些关系没有建、有些字段少了、有些结构设计不合理,又在不断地去调整设计,最后上线。在传统项目中,数据库是整个项目的根本,数据模型出来以后后续的开发都是围绕着数据展开,然后形成这样的一个架构view、controllers、service、dao(pojo)。
1、service很重,所有逻辑处理基本都放在service层;
2、POJO作为service层的非常重要的一个实体,会因为不同场景的需求做不同的变化和组合,就会造成POJO的几种不同模型(失血、贫血、充血),用来形容领域模型太胖或者太瘦;
随着业务变得复杂以后,包括数据结构的变化,那么各个模块就需要进行修改,原本清晰的系统经过不断的演化变得复杂、冗余、耦合度高,后果非常严重。
我们是想一下如果一个软件产品不依赖数据库存储设备,那我么怎么去设计这个软件呢?如果没有了数据存储,那么我们的领域模型就得基于程序本身来设计,那这个就是DDD需要去考虑的问题。
以抽奖设计为例
DDD理解起来有点抽象,这个有点像设计模式,感觉很有用,但是不知道怎么应用到自己写的代码里面,或者生搬硬套起来又很别扭,那么接下来以一个简单的转盘抽奖案例来分析一下DDD的应用。
针对功能层面划分边界
这个系统可以划分为运营管理平台和用户使用层,运营平台对于抽奖的配置比较复杂但是操作频率会比较低,而用户对抽奖活动页面的使用是高频率的但是对于配置规则来说是无感知的。根据这样的特点,我们把抽奖平台划分针对C端抽奖和M端抽奖两个子域,在确认了M端领域和C端的界限上下文后,我们再对各自上下文内容进行界限上下文的划分,接下来以C端用户为例来划分界限上下文。
确认基本需求
首先我们要来了解该产品的基本需求
1、抽奖资格(什么情况下会有抽奖机会、抽奖次数、抽奖的活动起始时间)
2、抽奖的奖品(实物、优惠券、理财金、购物卡...)
3、奖品自身的配置,概率、库存、某些奖品在有限的概率下还只能被限制抽到多少次等
4、风控对接,防止恶意薅羊毛
针对产品功能划分边界
抽奖上下文是整个领域的核心,负责处理用户抽奖的核心业务。
1、对于活动的限制,我们定义了活动资格的通用语言,将活动开始/结束时间,活动可参与次数等限制条件都收拢到活动资格子域中。
2、由于C端存在一些刷单行为,我们根据产品需求定义了风控上下文,用户对活动进行风控。
3、由于抽奖和发奖品其实可以认为是两个领域,一个负责根据概率去抽奖、另一个负责将选中的奖品发放出去,所以对于这一块也独立出来一个领域。
细化上下文
通过上下文划分以后,我们还需要进一步梳理上下文之间的关系,梳理的好处在于:
1、任务更好拆分(一个开发人员可以全身心投入到相关子域的上下文中);
2、方便沟通,明确自身上下文和其他上下文之间的依赖关系,可以实现更好的对接,然后是基于上下文的更进一步细化建模,在DDD中存在一些名字定义;
实体
当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。
值对象
当一个对象用于对事物进行描述而没有唯一标识时,它被称作为值对象。
聚合根
聚合根属于实体对象,它是领域对象中一个高度内聚的核心对象。(聚合根具有全局的唯一标识,而实体只有在聚合内部有唯一的本地标识,值对象没有唯一标识,不存在这个值对象或者那个值对象的说法)。
领域服务
一些重要的领域行为或操作,可以归类为领域服务。它实现了全部业务逻辑并且通过各种校验手段保证业务的正确性。
资源库
资源封装了基础设施来提供查询和持久化聚合操作,这样能够让我们始终关注在模型层面,把对象的存储和访问都委托给资源库来完成。它不是数据库的封装,而是领域层与基础设施之间的桥梁,DDD关心的是领域内的模型,而不是数据库的操作。
代码设计
在实际开发中,我们一般会采用模块来表示一个领域的界限上下文,比如
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 个特性中选取两个,只能是CP或者AP,根本没有选择权;
总结一下:CAP 并不是一个普适性原理和指导思想,它仅适用于原子读写的NoSql 场景中,并不适用于数据库系统。
BASE理论
从前面的分析中知道,在分布式(数据库分片或分开存在的多个实例上)系统下,CAP理论并不适合数据库事务(因为跟新一些错误的数据而导致的失败,无论使用什么样的高可用方案都是徒劳,因为数据发生了无法修正的错误)。此外XA事务虽然保证了数据库在分布式系统下的ACID(原子性、一致性、隔离性、持久性)特性,但也带来了一些性能方面的代价,对于并发和响应时间要求比较高的电商平台来说,是很难接受的。
eBay尝试了另外一条完全不同的路,放宽了数据库事务的ACID要求,提出了一套名为BASE的新准则,全称是Basically available, soft-state, Eventually Consistent.系统基本可用、软状态、数据最终一致性。相对于CAP来说,它大大降低了我们对系统的要求。
Basically available(基本可用),在分布式系统出现不可预知的故障时,允许瞬时部分可用性。
- 比如我们在淘宝上搜索商品,正常情况下是在0.5s 内返回查询结果,但是由于后端的系统故障导致查询响应时间变成了2s;
- 再比如数据库采用分片模式,100W 个用户数据分在5个数据库实例上,如果破坏了一个实例,那么可用性还有80%,也就是80%的用户都可以登录,系统仍然可用;
- 电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现;
soft-state(软状态) 表示系统中的数据存在中间状态,并且这个中间状态的存在不会影响系统的整体可用性,也就是表示系统允许在不同节点的数据副本之间进行数据同步过程中存在延时; 比如订单状态,有一个待支付、支付中、支付成功、支付失败, 那么支付中就是一个中间状态,这个中间状态在支付成功以后,在支付表中的状态同步给订单状态之前,中间会存在一个时间内的不一致。
Eventually consistent(数据的最终一致性),表示的是所有数据副本在一段时间的同步后最终都能达到一个一直的状态,因此最终一致性的本质是要保证数据最终达到一直,而不需要实时保证系统数据的强一致。
BASE 理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
什么是分布式架构下的高可用设计
1、避免单点故障
a)负载均衡技术(failover/选址/硬件负载/软件负载/去中心化的软件负载(gossip(redis-cluster)))
b)热备(linux HA)
c)多机房(同城灾备、异地灾备)
2、应用的高可用性
a)故障监控(系统监控(cpu、内存)/链路监控/日志监控)自动预警
b)应用的容错设计、(服务降级、限流)自我保护能力
c)数据量(数据分片、读写分离)
分布式架构下的可伸缩设计
垂直伸缩 提升硬件能力
水平伸缩 增加服务器
加速静态内容访问速度的CDN
CDN是Content Deliver Network的缩写,表示的是内容分发网络。CDN的作用是把用户需要的内容分发到离用户最近的地方,这样可以让用户快速获取所需内容。CDN其实是一种网络缓存技术,能够把一些相对稳定的资源放到距离用户较近的地方,一方面可以节省整个广域网的消耗,另一方面可以提升用户的访问速度,改进用户体验。一般会把静态的文件(图片、脚本、静态页面)放到CDN中。
1、当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会将域名解析权交给CNAME指向的CDN专用DNS服务器;
2、CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回给用户;
3、用户向CDN的全局负载均衡设备发起内容URL访问请求;
4、CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求;
5、区域负载均衡设备会为用户选择一台合适的CDN缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距离用户最近,根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容,查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址;
6、全局负载均衡设备把服务器的IP地址返回给用户,用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。
什么情况下使用CDN
最适合的是那些不会经常变化的内容,比如图片,JS文件,CSS文件,图片文件包括程序模板中单,CSS文件中用到的背景图片,还有就是作为网站内容组成部分的那些图片,都可以。
灰度发布
我们的应用虽然经过了测试部门的测试,但是仍然很难全面覆盖用户的使用场景,为了保证万无一失,我们在进行发布的时候一般会采用灰度发布,也就是会对新应用进行分批发布,逐步扩大新应用在整个集群的比例,直到最后全部完成。
灰度发布系统的作用在于,可以根据自己的配置,来将用户的流量都到新上线的系统上,来快速验证新的功能修改,而一旦出问题,也可以马上恢复,简单来说,是一套A/BTest系统。