精华内容
下载资源
问答
  • 第1章 分布式微服务架构设计原理 1.1 从传统单体架构到服务化架构 1.1.1 JEE架构 1.1.2 SSH架构 1.1.3 服务化架构 1.2 从服务化到微服务 1.2.1 微服务架构的产生 1.2.2 微服务架构与传统单体架构的对比 1.2.3 ...
    1.1 从传统单体架构到服务化架构
    	1.1.1 JEE架构
    		JEE是以面向对象的Java编程语言为基础,扩展了java平台的标准版,是java平台企业版的简称。
    
    		JEE将企业级软件架构分为三个层次:web层,业务逻辑层和数据存取层,每个层的职责分别如下:
    			1.web层:负责与用户交互或者对外提供接口
    			2.业务逻辑层:为了实现业务逻辑而设计的流程处理和计算处理模块
    			3.数据存取层:将业务逻辑处理的结果持久化以待后续查询,并维护领域模型中对象的生命周期。
    
    		JEE 平台将不同的模块化组件聚合后运行在通用的应用服务器上,如WebLogic,WebSphere,JBoss等。这也包含Tomcat,但tomcat仅仅是实现了JEE Web规范的
    	web容器。JEE平台是典型的二八原则的一个应用场景,它将80%通用的域业务无关的逻辑和流程封装在应用服务器的模块化组件里,通过配置的模式提供给应用程序访问,
    	应用程序实现20%的专用逻辑,并通过配置的形式来访问应用服务器提供的模块化组件。
    
    		JEE时代典型的架构如下:
    			1.web容器(UI组件1,UI组件2,...)    => 组合业务逻辑
    			2.EJB容器(业务组件1,业务组件2,...)  => 数据存取ORM
    			3.数据库
    		JEE时代的架构以及对企业级应用的整体架构进行了逻辑分层,包括上面提到的web层,业务逻辑层和数据存取层。不同的层级有自己的职责,并从功能类型上划分层级,
    	每个层级的职责单一。在这一时期,由于在架构上把整体的单体系统分成具有不同职责的层级,对应的项目管理也倾向于把大的团队分成不同的职能团队,主要包括:用户交互
    	UI团队,后台业务逻辑处理团队,数据存取ORM与DBA团队等。
    		因此,在分层架构下需要对项目管理过程中的团队职责进行职责划分,并建立团队交流机制。根据康威定律,设计系统的组织架构时,最终产生的设计等价于组织的沟通
    	结构,通俗来说,团队的交流机制应与架构分层交互机制相对应。
    		但是,每个层次的多个业务逻辑的实现会被放在同一应用项目中,并且运行在同一个JVM中。另外,由于JEE主要用于企业级应用开发,面向的用户较少,所以尽管JEE支持
    	web容器和EJB容器的分离部署,这个时代的大多数项目仍然部署在同一应用服务器上并跑在一个JVM进程中。
    
    	1.1.2 SSH架构
    		在JEE开始流行但没有完全奠定其地位的时候,开源软件Struts,Spring和Hibernate开始崭露头角,很快成为行业内企业的开源框架标配(简称SSH),JEE规范中的
    	各种计数如EJB,迅速失去了进一步的发展机会。Web MVC 框架Struts 在用户交互的UI层进一步划分了前端的职责,将用户交互层划分为视图,模型和控制器三大块。在
    	那个时代,Struts MVC 模型几乎服务于大多数企业服务的web项目。
    		后来,开源框架Spring的发布,更加改变了JEE一开始制定的战略目标。Spring框架作为逻辑层实现的核心容器,使用起来简单,方便,灵活,几乎大部分开发者完全
    	倒向了Spring开源派。Spring框架有2个核心:IOC和AOP。
    		Spring IOC 指的是控制反转,将传统EJB基于容器的开发改造成普通的Java组件开发,并且在运行时由Spring容器统一管理和串联,服务于不同的流程,在开发过程
    	中对Spring容器没有强依赖,便于开发,测试,验证和迁移。使用EJB实现一个服务化组件Bean时,需要依赖于多个容器接口,并需要根据容器的规则进行复杂的xml配置,
    	测试需要依赖于应用服务器的环境,有诸多不便;使用Spring框架则不然,开发业务逻辑时每个业务逻辑的服务组件都是独立的,而不依赖于Spring框架,借助Spring容器
    	对单元测试的支持,通过对下层依赖服务进行Mock,每个业务组件都可以在一定范围内进行单元化测试,而不需要重启重型的容器来测试。
    		Spring对AOP的支持是Spring框架成功的另外一个因素。AOP代表面向切片的编程,通常适用于使用面向对象方法无法抽象的业务逻辑,例如:日志,安全,事务,应用
    	程序性能管理(APM)等,使用它们的场景并不能用面向对象的方法来实现和表达,而需要使用切片来表达,因为它们可能穿插在程序的任何一个角落。在Java的世界里,AOP
    	的实现方式有三种:
    		1.对java字节码进行重新编译,将切面插入字节码的某些点和面上,可以使用cglib库实现
    		2.定制类加载器,在类加载时对字节码进行补充,在字节码中插入切面,增加了除业务逻辑之外的功能,JVM自身提供的Java Agent机制就是在加载类的字节码时,通过
    		增加切面来实现AOP的。
    		3.JVM本身提供了动态代理组件,可以通过它实现任意对象的代理模式,在代理的过程中可以插入切面的逻辑,可以使用Java提供的APIProxy.newProxyInstance()和
    		InvocationHandler来实现。
    
    		另外,AspectJ 是实现AOP的专业框架和平台,通过AspectJ 可以实现任意方式的字节码切面,Spring 框架完全支持 AspectJ。
    
    		到现在为止,SSH 开源标配框架中有了UI交互层的Struts 框架和业务逻辑实现层的 Spring 框架,由于面向对象领域的模型与关系型数据库存在着天然的屏障,所以
    	对象模型和关系模型之间需要一个纽带框架,也就是我们常说的ORM框架,它能够将对象转化为关系,也可以将关系转换为对象,于是,Hibernate 框架出现了。Heibernate
    	通过配置对象与关系表之间的映射关系,来指导框架对对象进行持久化和查询,并且可以让应用层开发者像执行sql异议进行对象查找。这大大减少了应用层开发人员写sql的
    	时间。然后,随着时间的发展,高度抽象的ORM框架被证明有性能瓶颈,因此,后来大家更倾向于使用更加灵活的MyBaits来实现ORM层。	
    
    		这一时代的SSH框架与JEE时代的架构类似,可分为三个层次:实现交互UI接口的 Web MVC 层,实现业务逻辑的 Spring 层及实现对象关系映射的 Hibernate 层,
    	每个层级的实现比 JEE对应的层次更简单,更轻量级,不需要开启整个应用服务器即可验证和测试,极大提高了开发效率,这得益于 Spring 框架的控制反转的理念。
    		由于这一时代的企业级软件服务的对象仍然是企鹅曰,用户量并不大,因此,大多数企业里的SSH架构最终会被打包到同一个JEE规范的War包里,并且部署在Apache 
    	Tomcat Web 容器里,因此,整个架构还是趋向于传统的单体架构,业务逻辑仍如耦合在一个项目中。
    		这一时代的职能团队的划分仍然停留在层次上,与JEE架构下的职能团队划分类似,划分为前端团队,后端业务逻辑研发团队和DBA团队。
    
    	1.1.3 服务化架构
    		从JEE时代到SSH时代,服务的特点仍然是单体化,服务的粒度抽象为模块化组件,所以组件耦合在一个开发项目中,并且配置和运行在一个JVM进程中。如果某一个
    	模块化组件需要升级上线,则会导致其他没有变更的模块化组件同样上线,在严重情况下,对某个模块化组件的变更,由于种种原因,会导致其他模块化组件出现问题。
    	另外,在互联网异军突起的情况下,传统JEE和SSH无法满足对海量用户发起的高并发请求进行处理的需求,无法突破耦合在一起的模块化组件的性能瓶颈,单一进程已经
    	无法满足需求,并且水平扩展的能力也是有限的。
    		为了解决上述问题,SOA出现了。SOA代表面向服务的架构,俗称服务化。SOA将应用程序的模块化组件通过定义明确的接口和契约联系起来,接口是采用重力的方式
    	进行定义的,独立于某种语言,硬件和操作系统,通常通过网络通信来完成,但是并不局限于某种网络协议,可以是底层的TCP/IP,可以是应用层的http,也可以是消息
    	队列,甚至可以是约定的某种数据库存储形式。这使得各种各样的系统中的模块化组件可以以一种统一和通用的方式进行交互。
    		对比JEE和SSH时代的模块化组件后发现,SOA将模块化组件从单一进程中进一步拆分,形成独立的对外提供服务的网络化组件,每个网络化组件通过某种网络协议对外
    	提供服务。这种架构的特点如下:
    		1.soa定义了良好的对外接口,通过网络协议对外提供服务,服务之间表现为松耦合性,松耦合性具有灵活的特点,可以对服务流程进行灵活组装和编排,而不需要对
    		服务本身做改变。
    		2.组成整个业务流程的每个服务的内部结构和实现在发生改变时,不影响整个流程对外提供的服务,只要对外接口不变,则改变服务内部的实现机制对外部来说是透明的
    		3.soa在这一时代的数据通信格式通常为xml,因为xml标记定义在大规模,高并发通信过程中,冗余的标记会给性能带来极大的影响,所以后来被json所取代
    		4.soa通过定义标准的对外接口,可以让底层通用服务进行下沉,供多个上层的使用方同时使用,增加了服务的可重用性。
    		5.soa可以让企业最大化的使用内部和外部的公共服务,避免重复造轮子。
    
    		要彻底理解SOA时代的服务化发展情况,我们必须理解SOA的两个主流实现方式:Web Service 和 ESB。
    		1.Web Service
    			Web Service 技术是 SOA 服务化的一种实现方式,它使得运行在不同的机器及操作系统上的服务的互相发现和调用成为可能,并且可以通过某种协议交换数据。
    			每个服务之间是对等的,并且互相是解耦的,通过WSDL定义的服务发现接口进行访问,并同构SOAP协议进行通信。SOAP协议通常是一种在http或者https通道
    		上传输xml数据来实现的协议,但是每个服务都要依赖中心化Web Service目录来发现现存的服务。
    
    			Web Service 的工作原理如下:
    				1.服务提供者 Web Service 2 和 Web Service 3 通过 UDDI 协议将服务注册到 Web Service 目录服务中。
    				2.服务消费者 Web Service 1 通过 UDDI 协议从 Web Service 目录中查询服务,并获得服务的 WSDL 服务描述文件。
    				3.服务消费者 Web Service 1 通过 WSDL 语言远程调用和消费 Web Service 2 和 Web Service 3 提供的服务。
    
    			通过这个过程,要改造一个新的业务流程,可以从 Web Service 目录中发现现有服务,并最大限度的重用现有服务,经过服务流程的编排来服务新的业务。
    
    		2.ESB
    			ESB是企业服务总线的总称,是用于设计和实现网络化服务交互和通信的软件模型,是SOA的另外一种实现方式,主要用于企业化信息系统的集成服务场景中。
    		Mule 是企业服务总线的一个实现。
    			在SOA服务化发展之前,企业对信息化系统进行了初步建设,这些企业信息化系统由异构技术栈实现:不同的开发语言,操作系统和系统软件为了快速响应新的
    		市场,完全使用新计划重建所有的信息化系统是不现实的,在现有的服务系统上增加新的功能或者叠加新的服务化系统的方法更加可行。这需要对这些现有的信息化
    		系统和新增的信息化系统进行组合。SOA凭借其松耦合性的特性,正好应用于这一场景,使得企业可以按照服务化的方式来添加新服务或者升级现有服务,来解决新
    		业务对流程编排的需要,甚至可以通过不同的渠道来获得外部服务,并与企业的现有应用进行组合,来提供新的业务场景所需要的信息化流程。
    			ESB 也适用于事件处理,数据转化和映射,消息和事件异步队列顺序处理,安全和异常处理,协议转换和保证通信服务的质量等场景。
    
    			ESB服务没有中心化的服务节点,每个服务提供者都是通过总线的模式插入系统,总线根据流程的编排负责将服务的输出进行转换并发送给流程要求的下一个
    		服务进行处理。这里我们可以看到,ESB的核心在于企业服务总线的功能和职责,如下所述:
    			1.监控和控制服务之间的消息路由
    			2.控制可插拔的服务化的功能和版本
    			3.解析服务之间交互和通信的内容和格式
    			4.通过组合服务,资源和消息处理器来统一编排业务需要的信息处理流程
    			5.使用冗余来提供服务的备份能力
    
    			根据以上,我们看到企业服务总线是 ESB 的核心要素,所有服务都可以在总线上插拔,并通过总线的流程编排和协议转换能力来组合实现业务处理能力。
    
    
    1.2 从服务化到微服务 
    	1.2.1 微服务架构的产生 
    		随着互联网企业的不断发展,互联网产品需要服务的用户量逐渐增加,海量用户发起的大规模,高并发请求是企业不得不面对的,前面介绍的SOA服务化系统能够分解
    	任务,让每个服务更加简单,职责单一,更易于扩展,但无论是Web Service 还是 ESB,都有时代遗留问题。
    		Web Service 的问题如下:
    			1.依赖中心化的服务发现机制
    			2.使用soap通信协议,通常使用xml格式来序列化通信格式,xml格式的数据冗余太大,协议太重
    			3.服务化管理和治理设施并不完善
    
    		ESB 的问题如下:
    			1.ESB 虽然是 SOA 实现的一种方式,却更多体现了系统集成的便利性,通过统一的服务总线将服务组合在一起,并提供组合的业务流程服务。
    			2.组合在 ESB 上的服务本身可能是一个过重的整体服务,或者是传统的 JEE 服务等。
    			3.ESB 视图通过总线来隐藏系统内部的复杂性,但是系统内部发复杂性仍然存在。
    			4.对于总线本身的中心化的管理模型,系统变得影响的范围经常会随之扩大
    
    		微服务架构倡导将软件应用设计成多个可独立开发,可配置,可运行和可维护的子服务,子服务之间通过良好的接口定义通信机制,通常使用restful风格的api
    	形式来通信,因为restful风格的api通常是在http或者https通道上传输json格式的数据来实现,http协议具有跨语言,跨异构系统的优点,当然,也可以通过底层
    	的二进制协议,消息队列协议等进行交互。这些服务不需要中心化的统一管理,每个服务的功能可自治,并且可以由不同的语言,系统和平台实现。
    		微服务架构致力于松耦合和高内聚的效果,与soa和esb相比,不再强调服务总线和通信机制的多样性,通常通过restful风格的api和轻量级的消息通信协议来完成。
    	微服务架构不是为了拆分而拆分,真正的目的是通过对微服务进行水平扩展解决传统的单体应用在业务急剧增长时遇到的问题,而且由于拆分的微服务系统中专业的人做
    	专业的事情,人员和项目的职责单一,低耦合,高内聚,所产生的问题的概率就会降低到最小。
    
    	1.2.2 微服务架构与传统单体架构的对比 
    		微服务特点:
    			1.微服务把每一职责单一的功能放在一个独立的服务中
    			2.每个服务运行在一个单独的进程中
    			3.每个服务有多个实例在运行,每个实例可以运行在容器化的平台中,达到平滑伸缩的效果
    			4.每个服务有自己的数据存储,实际上,每个服务应该有自己独享的数据库,缓存,消息队列等资源
    			5.每个服务应用有自己的运营平台,以及独立的运营人员,这包括技术运维和业务运营人员;每个服务都高度自治,内部的变化对外部透明
    			6.每个服务都可根据性能需求独立的进行水平伸缩
    
    		传统单体架构特点:
    			1.传统单体架构将所有模块化组件混合后运行在同一个服务JVM进程中
    			2.可对包含多个模块化组件的整体JVM进程进行水平扩展,而无法对某个模块化组件进行水平扩展
    			3.某个模块化组件发生变化时,需要所有的模块化组件进行编译,打包和上线
    			4.久而久之,模块间的依赖会不清晰,互相耦合,互相依赖
    
    	1.2.3 微服务架构与SOA服务化的对比 
    		事实上微服务架构与 SOA 服务化架构并不冲突,它们一脉相承,微服务架构是服务化架构响应特定历史时期的使用场景的延续,是服务化进行升华并落地的一种
    	实现方式。soa 服务化的理念在微服务架构中仍然有效,微服务在soa服务化的基础上进行了演进和叠加,形成了适合现代化应用场景的一个方法论。
    
    		微服务与SOA的不同:
    			1.目的不同
    				1.soa服务化涉及的范围更广一些,强调不同的异构服务之间的协作和契约,并强调有效集成,业务流程编排,历史应用集成等,典型代表为web service
    				和ESB。
    				2.微服务使用一系列的微小服务来实现整体的业务流程,目的是有效的拆分应用,实现敏捷开发和部署,在每个微笑服务的团队里,减少跨团队的沟通,让
    				专业的人做专业的事情,缩小变更和迭代影响的范围,并达到单一微服务更容易水平扩展的目的。
    			2.部署方式不同
    				1.微服务将完整的应用拆分成多个细小的服务,通常使用敏捷扩容,缩容的docker技术来实现自动化的容器管理,每个微服务运行在单一进程内,微服务的
    				部署互相独立,互补影响。
    				2.soa服务化通常将多个业务服务通过组件化模块方式打包在一个war包里,然后统一部署在一个应用服务器上。
    			3.服务粒度不同
    				1.微服务倡导将服务拆分成更细的粒度,通过多个服务组合来实现业务流程的处理,拆分到职责单一,甚至小到不能再拆分。
    				2.soa对粒度没有要求,在实践中服务通常是粗粒度的,强调接口契约的规范化,内部实现可以更粗粒度。
    
    
    1.3 微服务架构的核心要点和实现原理 
    	1.3.1 微服务架构中职能团队的划分 
    		传统单体架构将系统分成具有不同职责的层次,对应的项目管理也倾向于将大的团队划分成不同的职能团队。主要包括:用户交互UI团队,后台业务逻辑处理团队与
    	数据存取ORM团队,DBA团队等。每个团队只对自己家分层的职责负责,并使用对方提供的组件服务质量保证。如果其中一个模块化组件需要升级,更新,那么这个变更会
    	涉及不同的分层团队,即使升级和变更的改变很小,也需要进行跨团队沟通:需求阶段需要跨团队沟通产品功能,涉及阶段需要跨团队沟通设计方案,开发阶段需要跨团队
    	沟通接口定义,测试阶段需要沟通业务回归事宜,甚至上线都需要跨团队沟通应用的上线顺序。
    		根据康威定律,团队的交流机制应与架构设计机制相对应。因此,在微服务架构下,职能团队的划分方法是我们首先要考虑的一个核心要素。
    		微服务架构按照业务的功能进行划分,每个单一业务功能叫做一个服务,每个服务对应一个独立的职能团队,团队里面包含用户交互UI设计师,后台服务开发人员,
    	DBA,运营和运维人员。
    		在传统的整体架构中,软件是有生命周期的,经历需求分析,开发和测试,然后被交付给运维团队,这时开发团队将会解散,这是对软件的一个"放手"。而在微服务
    	架构中,提倡运维人员也是服务项目团队的一员,倡导谁开发,谁维护,实施终生维护制度。
    		在业务服务的内部实现需要升级或者变更时,团队内的各角色成员进行沟通即可,而不需要进行跨团队的沟通,这大大提高了沟通效率,只有服务之间的接口需要
    	变更时,才需要跨部门沟通,如果前期在服务之间的交互定义上定义了良好的接口,则接口变更的概率不大。即使接口有变,也可以通过一定的设计模式和规范来解决。
    
    	1.3.2 微服务的去中心化治理 
    		微服务倡导去中心化的治理,不推荐每个微服务都要使用相同的标准和技术来开发和使用服务。对于异构系统之间的交互标准,通常可以使用工具来补偿。开发者可以
    	开发共同的工具,并分享给异构系统的开发者,来解决异构系统的不一致问题。微服务倡导去中心化的服务管理和治理,尽量不要设置中心化的管理服务,最差也需要在中
    	心化的管理服务宕机时有替代方案和设计。
    
    	1.3.3 微服务的交互模式 
    		1.读者容错模式
    			读者容错模式指微服务化中服务提供者和消费者之间如何对接口的改变进行容错。从字面上来说,消费者需要对提供者提供的功能进行兼容性设计,尤其对
    		服务提供者返回的内容进行兼容,或者解决在服务提供者改变接口或者数据的格式的情况下,如何让服务消费者正常运行。
    			任何一个产品在设计时都无法预见将来可能增加的所有需求,服务的开发者通常通过迭代及时的增加新功能,或者让服务提供的api自然的演进。不过,服务
    		提供者对外提供的接口的数据格式的改变,增加和删除,都会导致服务的消费者不能正常的工作。因此,在服务消费者处理服务提供者的消息过程中,需要对服务
    		返回的消息进行过滤,只提取自己需要的内容,对多余或者未知的内容采取抛弃的策略,而不是硬生生的抛错处理。在实现过程中不推荐使用严格的校验策略,而是
    		使用宽松的校验策略,即使服务消费者拿到的消息报文发生了改变,程序也只需要尽最大努力提取需要的数据,同时忽略不可识别的数据。只有在服务消费者完全不能
    		识别接收到的消息,或者无法通过识别的信息继续处理流程时,才抛出异常。
    			服务的消费者的容错模式忽略了新的消息项,可选的消息项,未知的数据值及服务消费者不需要的数据项。
    
    		2.消费者驱动契约模式
    			消费者驱动契约模式用来定义服务化中服务之间交互接口改变的最佳规则。
    
    			服务契约分为:提供者契约,消费者契约及消费者驱动的契约,它从期望与约束的角度描述了服务提供者与服务消费者之间的联动关系。
    			1.提供者契约
    				是我们最常见的一种服务契约,顾名思义,提供者契约是以提供者为中心的,提供者提供了什么功能和消息格式,各消费者都会无条件的遵守这些约定,
    			不论消费者实际需要多少功能,消费者接受了提供者契约时,都会根据服务提供者的规则来使用服务。
    
    			2.消费者契约
    				是对某个消费者的需求进行更为精确的描述,在一次具体的服务交互场景下,代表消费者需要提供者提供的功能中的哪部分数据。消费者契约可以被用来
    			标识现有的提供者契约,也可以用来发现一个尚未明确的提供者契约。
    
    			3.消费者驱动的契约
    				代表服务提供者向其所有当前消费者承若遵守的约束。一旦各消费者把自己的具体期望告知提供者,则提供者无论在什么时间和场景下,都不应该打破契约。
    
    			在现实的服务交互设计中,上面这三种契约是同时存在的。
    
    			服务提供者契约是服务提供者单方面定下的规则,而一个消费者契约会成为提供者契约的一部分,多个服务消费者可以对服务提供者提出约束,服务提供者需要在
    		将来遵守服务消费者提出的契约,这就是消费者驱动的契约。
    
    		3.去数据共享模式
    			与soa服务化对比,微服务是去 ESB 总线,去中心化及分布式的;而soa 还是以 ESB 为核心实现遗留系统的集成,以及基于 web service 为标准实现的通用
    		的面向服务的架构。在微服务领域,微服务之间的交互通过定义良好的接口来实现,不允许使用共享数据来实现。
    			在实践过程中,有些方案的设计使用缓存或者数据库作为两个服务之间的纽带。这种交互流程的缺点如下:
    				1.使得微服务之间的交互除了接口契约,还存在数据存储契约
    				2.上游的数据格式发生变化,可能导致下游的处理逻辑出现问题
    				3.多个服务共享一个资源服务,对资源服务的运维难以划清职责和界限
    				4.在做双机房独立部署时,需要考虑服务的资源的路由情况,跨机房的服务调用不能使用独立的资源部署模式,因此难以实现服务自治
    
    			因此,在设计微服务架构时,一定不要共享缓存和数据库等资源,也不要使用总线模式,服务之间的通信和交互只能依赖定义良好的接口,通常使用restful样式
    		的api 或者透明的RPC 调用框架。
    
    	1.3.4 微服务的分解和组合模式
    		使用微服务架构划分服务和团队是微服务架构实施的重要一步,良好的划分和拆分使系统达到松耦合和高内聚的效果,然后通过微服务的灵活组装可以满足上层的各种
    	个样的业务处理需求。在微服务架构的需求分析和架构设计过程中,通常是用领域的动词和名词来划分微服务的。例如,对于一个电商后台系统,可以分解为订单,商品,
    	商品目录,库存,购物车等子系统,每个名词和动词都可以是一个微服务,将这几个微服务组合在一起,就实现了电商平台用户购买商品的整个业务流。
    
    		这样拆分以后,系统具有敏捷性,灵活性,可伸缩性等,拆分后有多个高度自治的微服务,以什么方式组合微服务呢?
    		1.服务代理模式
    			服务代理模式是最简单的服务组合模式,它根据业务的需求选择调用后端的某个服务。在返回给使用端之前,代理可以对后端服务的输出进行加工,也可以直接把
    		后端服务的返回结果返回给使用端。	
    			典型会使用到这种模式的是平滑的系统迁移,通常会经历如下4个阶段:
    				1.在新老系统上双写
    				2.迁移双写之前的历史遗留数据
    				3.将读请求切换到新系统
    				4.下调双写逻辑,只写新系统
    			服务代理模式常常应用到第三步,一般会对读请求切换设计一个开关,在开关打开时查询新系统,开关关闭时查询老系统。
    
    		2.服务聚合模式
    			服务聚合模式是最常用的服务组合模式,它根据业务流程处理的需求,以一定的顺序调用依赖的多个微服务,对依赖的微服务返回的数据进行组合,加工和转换,
    		最后以一定的形式返回给使用方。	
    			这里体现了 DRY原则的设计理念,在设计或者构造应用时,最大限度的重用了现有的实现。假如一块业务逻辑由三个独立的逻辑块组成,每个独立的逻辑块可能
    		有多个使用方,则DRY原则推荐将三个独立的逻辑块封装成三个独立运行的微服务,然后使用本节的服务聚合模式开发聚合服务,将三个独立的逻辑块聚合在一起提供
    		给上层组合服务。好处如下:
    			1.三个独立的子服务可以各自独立的开发,敏捷变更和部署
    			2.聚合服务封装下层的业务处理服务,由三个独立的子服务完成数据持久化等工作,项目结构清晰明了
    			3.三个独立的子服务对其他使用方仍然可以重用
    
    			另外,聚合服务也可以是一个纯后台服务,通过聚合对使用方输出组合的服务。
    
    		3.服务串联模式
    			服务串联模式类似于一个工作流。
    			服务串联模式之间的调用通常使用同步的restful风格的远程调用实现,注意,这种模式采用的是同步调用的方式,在串联服务没有完成并返回之前,所有的服务
    		都会阻塞和等待,一个请求会占用一个线程来处理,因此在这种模式下不建议服务层级太多,如果能用服务聚合代替,则优先使用服务聚合模式,而不是使用这种服务
    		串联模式。	
    			相对于服务聚合模式,服务串联模式有一个优点,即串联链路上再增加一个节点时,只要不是在串联服务的正后面增加,那么串联服务是无感知的。
    
    		4.服务分支模式
    			服务分支模式是服务代理模式,服务聚合模式和服务串联模式相结合的产物。
    			分支服务可以拥有自己的数据库存储,调用多个后端的服务或者服务串联链,然后将结果进行组合处理再返回给客户端。分支服务也可以使用代理模式,简单的
    		调用后端的某个服务或者服务链,然后将返回的数据直接返回给使用方。
    
    			由于分支模式放大了服务的依赖关系,因此在现实的微服务设计中尽量保持服务调用级别的简单,在使用服务组合和服务代理模式时,不要使用服务串联模式和
    		服务分支模式,以保持服务依赖关系的清晰明了,也减少了日后维护的工作量。
    
    		5.服务异步消息模式
    			前面的所有服务组合模式都使用同步的restful风格的同步调用来实现,同步调用模式在调用的过程中会阻塞线程。如果服务提供方迟迟没有发挥,则服务消费方
    		会一直阻塞,在严重情况下,出现雪崩效应。因此,在构建微服务架构系统时,通常会梳理核心系统的最小化服务集合,这些核心的系统服务使用同步调用,而其他
    		核心链路意外的服务可以使用异步消息队列进行异步化。
    
    		6.服务共享数据模式
    			数据共享模式其实是反模式,由于去掉了数据共享,所以仅仅通过服务之间良好的定义的接口进行交互和通信,使得每个服务都是自治的。然后,在下面两种场景下,
    		我们仍然需要数据共享模式。
    			1.单元化架构
    				由于一些平台对性能有较高的要求,所以采用微服务化将服务进行拆分,通过网络服务进行通信,尽管网络通信的带宽已经很宽,但是还是会有性能方面的损耗,
    			在这种场景下,可以让不同的微服务共享一些自由,例如:缓存,数据库等,甚至可以将缓存和数据在物理拓扑上虞微服务部署在一个物理机房中,最大限度的减少
    			网络通信带来的性能损耗,我们将这种方法称为"单元化架构"。
    
    			2.遗留的整体服务
    				对于历史遗留的传统单体服务,我们在重构微服务的过程中,发现单体服务依赖的数据库表耦合在一起,对其拆分需要进行反规范化的处理,可能会造成数据
    			一致性问题,在没有对齐完全理解和把握的前提下,会选择保持现状,让不同的微服务暂时共享数据存储。
    
    	1.3.5 微服务的容错模式 
    		由于服务的调用不再是进程内的调用,而是通过网络进行的远程调用,众所周知,网络通信是不稳定,不可靠,一个服务依赖的服务可能出错,超时或者宕机,如果短时间
    	没有及时发现和隔离问题,或者在设计中没有考虑如何应对这样的问题,那么很可能在短时间内服务的线程池中的线程被用满,资源耗尽,导致出现雪崩的情况。
    		
    		1.舱壁隔离模式
    			微服务架构中主要体现如下2个方面:
    				1.微服务容器分组
    					微服务的每个节点的服务池分为三组:准生产环境,灰度环境和生产环境。准生产环境供内测使用;灰度环境会跑一些普通商户的流量‘大部分生产流量
    				和VIP商户的流量则跑在生产环境中。这样,在一次比较大的重构过程中,我们就可以充分利用灰度环境的隔离性进行预验证,用普通商户的流量验证重构没有
    				问题,再上生产环境。
    
    				2.线程池隔离
    					在微服务架构中,我们不一定将每个服务拆分到微小的粒度,这取决于职能团队和财务情况,我们一般会将同一功能划分在一个服务中,尽量避免微服务
    				过细而导致成本增加,适可而止。这样就会导致多个功能混合部署在一个微服务实例中,这些微服务的不同功能通常使用一个线程池,导致一个功能流量增加
    				时耗尽线程池的线程,而阻塞其他功能的服务。
    
    		2.熔断模式
    			当服务的输入负载迅速增加时,如果没有有效的措施对负载进行熔断,则会使服务迅速被压垮,服务被压垮会导致依赖的服务都被压垮,出现雪崩效应。因此,要在
    		微服务中实现熔断模式。
    
    		3.限流模式
    			服务的容量和性能是有限的。针对服务突然上量,我们必须有限流机制,限流机制一般会控制访问的并发量。有如下几种主流的方法实现限流:
    				1.计数器
    				2.令牌筒
    				3.信号量
    				4.失效转移模式	
    					若服务架构中发生了熔断和限流,则该如何处理被拒绝的请求呢?解决这个问题的模式叫做失效转移模式,通常分为下面几种:
    						1.采用快速失败的策略,直接返回使用方错误,让使用方知道发生了问题并自行决定后续处理
    						2.是否有备份服务,如果有备份服务,则迅速切换到备份服务
    						3.失败的服务有可能是某台机器有问题,而不是所有的机器都有问题。例如OOM问题,在这种情况下适合使用 failover策略,采用重试的方法来
    						解决,但是这种方法要求服务提供者的服务实现了幂等性。
    
    	1.3.6 微服务的粒度 
    		按照服务的初衷,服务要求按照业务的功能进行拆分,直到每个服务的功能和职责单一,甚至不可再拆分为止,以至于每个服务都能独立部署,扩容和缩容都很方便,
    	能够有效的提高利用率。拆的越细,服务的耦合度越小,内聚性越好,越适合敏捷发布和上线。
    		然后拆的太细会导致系统的服务数量较多,互相依赖的关系较为复杂,更重要的是根据康威定律,团队要响应系统的架构,每个微服务都有相应的独立,自治的团队来
    	维护,这也是不切实际的想法。
    		因此,这里倡导对微服务的拆分适可而止,原则是拆分到可以让使用方自由的编排底层的子服务来获得相应的组合服务即可,同时要考虑团队的建设及人员的数量和分配等。
    
    
    1.4 Java平台微服务架构的项目组织形式 
    	1.4.1 微服务项目的依赖关系 
    		在java领域,每个服务上线后,对外输出的接口为一个jar包。在微服务领域,jar包被分为一方库,二方库,三方库。
    			1.一方库:本服务在jvm进程内依赖的jar包
    			2.二方库:在服务外通过网络通信或者rpc调用的服务的jar包
    			3.三方库:所依赖的其他公司或者组织提供的服务或者模块
    
    	1.4.2 微服务项目的层级结构 
    		java微服务项目的层级结构一般为:服务导出层,接口层和逻辑实现层。
    			服务导出层(War)
    				web.xml
    				Spring 环境
    
    			服务接口层(Jar)
    				业务接口
    				DTO
    				枚举类
    
    			服务实现层(Jar)
    				业务实现类
    				外部服务包装类
    				DAO
    
    		其中,每一个层级的职责和最终的表现形式如下:
    			1.服务导出层:最后会打包为一个War包,包含服务的实现Jar包,接口Jar包,以及web项目导出rpc服务所需要的配置文件等
    			2.服务接口层:包含业务接口,依赖的DTO及需要的枚举类等,最后打包成Jar包,并发布到Maven服务器上,也包含在服务导出层的War包中。
    			3.服务实现层:包含业务逻辑实现类,依赖的第三方服务的包装类,以及下层数据库访问的DAO类等,最后打包成Jar包,包含在服务到沪层的War包中。
    
    		这里有一个反模式,切记永远不要在本地事务中调用远程服务,在这种场景下如果远程服务出现了问题,则会拖长事务,导致应用服务器占用太多的数据库连接,
    	让服务器负载迅速攀升,在严重情况下会压垮数据库。虽然我们要极力避免这种场景的发生,但是数据库也应该要有熔断机制。
    
    	1.4.3 微服务项目的持续发布 
    		微服务项目需要实现自动化的持续部署和持续集成的功能,包括:代码管理,自动编译,发布QA,自动化测试,性能测试,准生产部署和测试,生产环境发布等。
    
    1.5 服务化管理和治理框架的技术选型 
    	1.5.1 RPC 
    		1.JDK RMI
    			自从 jdk 1.4 开始,jdk 内置了远程服务调用的技术栈,可以帮助开发者创建基于java到java的分布式调用框架,一个java进程内的服务可以调用其他java
    		进程内的服务,使用jdk内置的序列化和反序列化协议。RMI 是 JEE 规范中 EJB 远程调用的基础。然后,jdk 内置的 RMI 服务并没有得到广泛的应用,原因如下:
    			1.RMI 采用 jdk 自带的专用序列胡协议,不能跨语言
    			2.使用了底层的网络协议,不如基于http可读,也不如http被广泛认可和应用
    			3.开源框架的飞速发展,严重削弱了jdk资深技术的流程程度
    
    		2.Hessian 及 Burlap
    			Hessian 及 Burlap 都适合传输较小的对象,对较大,复杂的对象,无论是序列法方式上还是传输通道上都没有 RMI 有优势。由于服务框架中大量的调用
    		都是大规模,高并发的短小请求,因此,Hessian 和 Burlap 协议爱服务化框架中得到了广泛的使用。
    
    		3.Spring HTTP Invoker
    			使用了jdk 内置的序列化机制,不能跨语言。
    
    	1.5.2 服务化 
    		服务化框架和平台:
    			1.Dubbo
    			2.HSF
    			3.Thrif
    			4.AXIS
    			5.Mule ESB
    
    	1.5.3 微服务 
    		近年流行的 Spring Cloud 系列的微服务框架。
    
    		1.Spring Boot
    			通过使用 Spring Boot 可以很容易的创建独立的,具有高品质的基于 Spring 的应用程序,基于 Spring Boot 创建的应用可以随时随地的启动和运行,
    		一般只需要较少的配置和搭建环境的工作量。
    			在JEE时代,企业级开发涉及的通用功能被提取到了容器层实现,例如:tomcat web 容器负责管理服务的启动,停止,监控,配置和日志等,应用开发人员
    		只需要按照规范将应用打包成War,并发布到tomcat web 容器中,就可以对外提供服务了,这一时代的应用是包含在容器内的。
    
    			Spring Boot 的思路正好相反,它将容器嵌入自启动的jar包中,在Spring Boot应用启动时,内部启动嵌入的容器,例如:tomcat,Jetty和Netty等,
    		然后通过内嵌的服务器将应用中提供的服务暴露。
    			Spring Boot 这种设计在微服务架构下有如下明显的优点:
    				1.可以创建独立,自启动的应用程序
    				2.不需要构建War包并发布到容器中,构建和维护War包,容器的配置和管理也是需要成本的
    				3.通过 Maven 的定制化标签,可以快速的创建 Spring Boot 的应用程序
    				4.可以最大化的自动化的配置Spring,而不需要人工配置各项参数
    				5.提供了产品化特点,例如:性能分析,健康检查和外部化配置
    				6.全程没有xml配置,也不需要代码生成
    
    			Spring Boot 是 Spring Cloud 构建微服务架构的重要基础。
    
    		2.Netflix
    			Netflix 是由 Netflix 公司开发且合并到 Spring Cloud 项目中,主要提供了服务发现,断路器和监控,智能路由,客户端负载均衡,易用的REST客户端
    		等服务化必须的功能。其中 Hystrix 框架提供了微服务所需的容错机制的解决方案和设计模式。Hystrix 大大简化了微服务下容错机制的实现,包括服务分组和
    		隔离,熔断和防止级联失败,限流机制,失效转移机制和监控机制等。
    
    		3.Spring Cloud Netflix
    			Spring Cloud Netflix 集成了 Spring Boot 对微服务敏捷启动和发布的功能,以及 Netflix 提供的微服务化管理和治理能力,称为一个完美的微服务
    		解决方案。在 Spring Cloud Netflix 平台下,开发人员通过几个简单的注释配置即可完成一个大规模分布式系统的发布工作。Spring Cloud Netflix 包括
    		服务发现组件 Eureka,容错性组件 Hystrix,智能路由组件Zuul 和客户端负载均衡组件 Ribbon。
    
    			我们看到 Netflix 中的交互流程如下:
    				1.服务在 Eureka 服务器实例上注册
    				2.Zuul 作为一个特殊的服务在 Eureka 上注册并发现服务
    				3.Zuul 作为网关,将发现的服务导出给pc网站,app 和 开放平台使用
    				4.RestTemplace 和 FeignClient 使用简单的服务调用的方法调用服务1,服务2等
    
    			在这个微服务的使用流程中,Netflix 具有如下特点:
    				1.服务在 Eureka 实例中注册,由Spring 管理的Bean来发现和调用
    				2.通过配置的方式可以启动嵌入式的 Eureka 服务器
    				3.Feign 客户端通过声明的方式即可导入服务代理
    				4.Zuul 使用 Ribbon 服务实现客户端的负载均衡
    				5.通过声明的方式即可插入 Hystrix 的客户端
    				6.通过配置的方式即可启动 Hystrix 面板服务器
    				7.在Spring 环境中可以直接配置 Netflix 的组件
    				8.Zuul 可以自动注册过滤器和路由器,形成一个反向代理服务器
    				9.Hystrix 面板可以对服务的状态进行监控,并能提供了容错机制
    
    
    1.6 本章小结
    	微服务架构的主要特点:
    		1.将传统单体应用拆分成网络服务,来实现模块化组件
    		2.根据微服务架构的服务划分来分组职能团队,减少跨团队的沟通
    		3.每个服务对应一个团队,团队成员负责开发,测试,运维和运营,开发后在团队内运维和运营,不需要交付给其他团队
    		4.去中心化,去SOA服务化的中心服务治理和去企业服务总线
    		5.微服务重视服务的合理拆分,分层和构造,可建设自动化持续发布平台,并进行敏捷开发和部署
    		6.具备兼容性,容错性设计和服务的契约设计

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业务线去分不同的模块去开发,这样专门的人负责专门的业务模块,最终上线由壳工程去负责进行组合打包各个module,完成业务的快速迭代。整个过程会涉及到...

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    背景

    当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业务线去分不同的模块去开发,这样专门的人负责专门的业务模块,最终上线由壳工程去负责进行组合打包各个module,完成业务的快速迭代。整个过程会涉及到各个模块间进行通信,比如订单模块和个人中心模块,可能会需要频繁的传递数据和页面跳转,这个时候怎么去处理呢?我们能想到的方案就是采用类名反射,来动态创建需要跳转和交互的类,这样编译时就不会报错,运行时又可以完成模块间的交互。阿里巴巴推出的开源路由框架——ARouter就是基于反射和注解来解决这个问题的,本文不讲基本使用(基本使用在项目的github主页上已经将的非常详细了),通过分析整个路由过程来讲解它的基本原理。

    说在前面

    首先在我们需要用到的类的类名加上注解@Route(“/group/name”),注意这里需要至少两层路径(第一个是分组,第二个一般是类名)。这个注解就是代表这个类可以被其他模块找到的一个路径的注解,并且它是一个编译时注解,这就意味着在编译时就已经生成了相应的辅助类。ARouter把路由一共分为以下几类:

    ACTIVITY(0, “android.app.Activity”), 
    SERVICE(1, “android.app.Service”), 
    PROVIDER(2, “com.alibaba.android.arouter.facade.template.IProvider”), 
    CONTENT_PROVIDER(-1, “android.app.ContentProvider”), 
    BOARDCAST(-1, “”), 
    METHOD(-1, “”), 
    FRAGMENT(-1, “android.app.Fragment”), 
    UNKNOWN(-1, “Unknown route type”);
    

    其中我们常用的就是ACTIVITY,PROVIDER,FRAGMENT这三个了,也基本上满足了我们模块化开发的需求。另外一点就是分组的概念,ARouter是按照组来进行整理的,也就是第一层的路径,所以前面说必须要两层路径,否则不知道归到哪里去,一般一个module按照模块名采用统一的分组标识。我们来看看注解生成的类(这里只包含了Activity,Fragment,Provider):

    package com.alibaba.android.arouter.routes;
    //。。。import省略
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Group$$Personal implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/Personal/EARNING", RouteMeta.build(RouteType.ACTIVITY, PerEarningActivity.class, "/personal/earning", "personal", null, -1, -2147483648));
        //...省略Activity,Fragment
        atlas.put("/Personal/main", RouteMeta.build(RouteType.FRAGMENT, PerMainFragment.class, "/personal/main", "personal", null, -1, -2147483648));
        atlas.put("/Personal/service", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/personal/service", "personal", null, -1, -2147483648));
      }
    }
    

    以上就是所有注解的路径的信息集合,包含了所有的Activity,Fragment,Provider(一般一个module一个Provider就够用了,专门用来跟其他模块交互),并都以路径为key放到这个map中。

    package com.alibaba.android.arouter.routes;
    //。。。import省略
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Providers$$modlue_personal implements IProviderGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.tb.test.service.ModulePersonalService", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/Personal/service", "personal", null, -1, -2147483648));
      }
    }
    

    这个类是专门的Provider的索引的集合,所有的provider都被以全类名为索引放到一个map中。

    package com.alibaba.android.arouter.routes;
    //。。。import省略
    
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Root$$modlue_personal implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        routes.put("Personal", ARouter$$Group$$Personal.class);
      }
    }
    

    这个类是所有的group的信息收集,全部都以group的名字为key,以注解生成的不同的group的类的class对象为value放入到一个map中。

    总共就生成这三种类型的类,当然,如果你有不同的分组还会生成其他的类,不过都是这三种里面的一种。

    完成了这些注解信息的收集,下面就会去使用这些信息来完成我们的跨模块交互了。

    初始化过程

    使用ARouter必须先要进行初始化:

    if (isDebug()) { 
    // These two lines must be written before init, otherwise these configurations will be invalid in the init process 
    ARouter.openLog(); // Print log 
    ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk) 
    } 
    
    ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application
    

    上面这段话就是去初始化Arouter,我们来看看init里面到底做了什么事。。。

    /**
         * Init, it must be call before used router.
         */
        public static void init(Application application) {
            if (!hasInit) {
                logger = _ARouter.logger;
                _ARouter.logger.info(Consts.TAG, "ARouter init start.");
                hasInit = _ARouter.init(application);
    
                if (hasInit) {
                    _ARouter.afterInit();
                }
    
                _ARouter.logger.info(Consts.TAG, "ARouter init over.");
            }
        }
    

    可以看到,这里使用了外观模式,最终调用都是在_ARouter这个类里面,跟进去:

    protected static synchronized boolean init(Application application) {
            mContext = application;
            LogisticsCenter.init(mContext, executor);
            logger.info(Consts.TAG, "ARouter init success!");
            hasInit = true;
    
            // It's not a good idea.
            // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
            // }
            return true;
        }
    

    代码也很简单,核心就是LogisticsCenter.init这句话,跟进去看看,核心代码如下:

    List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    
                //
                for (String className : classFileNames) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
    

    我们可以看到,首先是去获取到所有的app里的由ARouter注解生成的类的类名,他们的统一特点就是在同一个包下,包名为:com.alibaba.android.arouter.routes
    然后就是循环遍历这些类,也就是刚才我们说的那三种类。在这里,有一个Warehouse类,看下代码:

    /**
     * Storage of route meta and other data.
     *
     * @author zhilong <a href="mailto:zhilong.lzl@alibaba-inc.com">Contact me.</a>
     * @version 1.0
     * @since 2017/2/23 下午1:39
     */
    class Warehouse {
        // Cache route and metas
        static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
        static Map<String, RouteMeta> routes = new HashMap<>();
    
        // Cache provider
        static Map<Class, IProvider> providers = new HashMap<>();
        static Map<String, RouteMeta> providersIndex = new HashMap<>();
    
        // Cache interceptor
        static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
        static List<IInterceptor> interceptors = new ArrayList<>();
    
        static void clear() {
            routes.clear();
            groupsIndex.clear();
            providers.clear();
            providersIndex.clear();
            interceptors.clear();
            interceptorsIndex.clear();
        }
    }
    

    很简单,定义了几个静态map,在初始化的时候来存放之前的注解生成的那些相关信息。初始化里面存的就是所有group索引的map,所有拦截器(本文不讲)索引的map,所有provider索引的map。至此,之前的那些注解类里面的信息都被存储起来了,这样后续在查找的时候就很方便可以找到对应的类,我们继续看初始化之后的afterInit方法:

    static void afterInit() {
            // Trigger interceptor init, use byName.
            interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
        }
    

    我们跟踪之后,发现最终会调用LogisticsCenter中的completion方法:

    /**
         * Completion the postcard by route metas
         *
         * @param postcard Incomplete postcard, should completion by this method.
         */
        public synchronized static void completion(Postcard postcard) {
            if (null == postcard) {
                throw new NoRouteFoundException(TAG + "No postcard!");
            }
    
            RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
            if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
                Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
                if (null == groupMeta) {
                    throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
                } else {
                    // Load route and cache it into memory, then delete from metas.
                    try {
                        if (ARouter.debuggable()) {
                            logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                        }
    
                        IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                        iGroupInstance.loadInto(Warehouse.routes);
                        Warehouse.groupsIndex.remove(postcard.getGroup());
    
                        if (ARouter.debuggable()) {
                            logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                        }
                    } catch (Exception e) {
                        throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                    }
    
                    completion(postcard);   // Reload
                }
            } else {
                postcard.setDestination(routeMeta.getDestination());
                postcard.setType(routeMeta.getType());
                postcard.setPriority(routeMeta.getPriority());
                postcard.setExtra(routeMeta.getExtra());
    
                Uri rawUri = postcard.getUri();
                if (null != rawUri) {   // Try to set params into bundle.
                    Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                    Map<String, Integer> paramsType = routeMeta.getParamsType();
    
                    if (MapUtils.isNotEmpty(paramsType)) {
                        // Set value by its type, just for params which annotation by @Param
                        for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                            setValue(postcard,
                                    params.getValue(),
                                    params.getKey(),
                                    resultMap.get(params.getKey()));
                        }
    
                        // Save params name which need autoinject.
                        postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                    }
    
                    // Save raw uri
                    postcard.withString(ARouter.RAW_URI, rawUri.toString());
                }
    
                switch (routeMeta.getType()) {
                    case PROVIDER:  // if the route is provider, should find its instance
                        // Its provider, so it must be implememt IProvider
                        Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                        IProvider instance = Warehouse.providers.get(providerMeta);
                        if (null == instance) { // There's no instance of this provider
                            IProvider provider;
                            try {
                                provider = providerMeta.getConstructor().newInstance();
                                provider.init(mContext);
                                Warehouse.providers.put(providerMeta, provider);
                                instance = provider;
                            } catch (Exception e) {
                                throw new HandlerException("Init provider failed! " + e.getMessage());
                            }
                        }
                        postcard.setProvider(instance);
                        postcard.greenChannel();    // Provider should skip all of interceptors
                        break;
                    case FRAGMENT:
                        postcard.greenChannel();    // Fragment needn't interceptors
                    default:
                        break;
                }
            }
        }
    

    这个方法有点长,不过我们可以看到,核心功能就是postcard的信息完善。postcard就是整个路由过程中的信使,类似于生活中的明信片功能,包含了路由所有需要的信息。通过第34行的递归调用,根据groupsIndex和providersIndex保证了Warehouse里面的另外两个静态map(routes,providers)的赋值,这样最终都会走到36行else分支,去保证所有路由信息的完整性,另外swtich…case里面的postcard.greenChannel()其实是activity跳转专用的,目的是用来拦截activity跳转,来对跳转过程进行干预,在之前或者之后做一些自己的处理,所以greenChannel就是绿色通道,不进行拦截。另外代码里面也可以看到,类的生成都是采用getConstructor().newInstance()这种反射来进行的,最终调用:

    ModulePersonalService service = (ModulePersonalService) ARouter.getInstance().build("/Personal/service").navigation();
    

    得到这个跨模块服务之后,里面的所有方法都可以去调用来实现功能需求了。

    调用过程
    Activity的跳转如下:

                ARouter.getInstance().build("/Personal/main").navigation(activity);
    

    最终调用代码则是_ARouter类里面的_navigation方法:

    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
            final Context currentContext = null == context ? mContext : context;
    
            switch (postcard.getType()) {
                case ACTIVITY:
                    // Build intent
                    final Intent intent = new Intent(currentContext, postcard.getDestination());
                    intent.putExtras(postcard.getExtras());
    
                    // Set flags.
                    int flags = postcard.getFlags();
                    if (-1 != flags) {
                        intent.setFlags(flags);
                    } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    }
    
                    // Navigation in main looper.
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            if (requestCode > 0) {  // Need start for result
                                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                            } else {
                                ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                            }
    
                            if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                                ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                            }
    
                            if (null != callback) { // Navigation over.
                                callback.onArrival(postcard);
                            }
                        }
                    });
    
                    break;
                case PROVIDER:
                    return postcard.getProvider();
                case BOARDCAST:
                case CONTENT_PROVIDER:
                case FRAGMENT:
                    Class fragmentMeta = postcard.getDestination();
                    try {
                        Object instance = fragmentMeta.getConstructor().newInstance();
                        if (instance instanceof Fragment) {
                            ((Fragment) instance).setArguments(postcard.getExtras());
                        } else if (instance instanceof android.support.v4.app.Fragment) {
                            ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                        }
    
                        return instance;
                    } catch (Exception ex) {
                        logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                    }
                case METHOD:
                case SERVICE:
                default:
                    return null;
            }
    
            return null;
        }
    

    可以看到对Activity的处理,最终就是调用startActivity方法,对provider就是返回一个类的实例,而BOARDCAST、CONTENT_PROVIDER、FRAGMENT也都是生成一个实例返回,对于METHOD、SERVICE暂时是没有处理的。

    拦截器和自动注入的功能,本文没有去分析,一般跳到某一个页面需要判断是否登陆的时候,可以使用拦截器,自动注入可以在页面间传递数据,非常方便。

    原文链接:https://blog.csdn.net/binbinqq86/article/details/80927885
    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    展开全文
  • 设现已有大量48V输入的良好单板,要把它们组合出高压配电适用的设备,那么,设计的机柜当然要有48V的背板, 让这些单板取电。然而场所内的供电源是380VDC,那么机箱的设计只需附加上安装HVBCM的功率板,接受380V输入...
  • Chapter1 版式设计原理 11 1 版式设计的概念及意义 12 1.1 认识版式设计 12 1.2 版式设计的意义 12 2 版式设计的基本原理 13 2.1 根据内容进行版面的编排 13 2.2 根据版面调整版面率 13 2.3 根据版式设调整...
  • 利用响应面法对超声提取绿茶茶多酚的工艺条件进行优化,在单因素试验的基础上,根据中心组合设计原理采用三因素三水平的响应面分析法,依据回归分析确定最优提取工艺条件。结果表明,其最佳工艺条件为:液料比为40.2mL/g,...
  • JSON系统开发方法是一种典型的面向数据结构的分析和设计方法,以活动为中心,一连串的活动的顺序组合成一个完整的工作进程。 之所以会有跨域这个问题的产生根本原因是浏览器的同源策略限制,理解同...

        JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。JSON系统开发方法是一种典型的面向数据结构的分析和设计方法,以活动为中心,一连串的活动的顺序组合成一个完整的工作进程。

        之所以会有跨域这个问题的产生根本原因是浏览器的同源策略限制,理解同源策略的限制同源策略是指阻止代码获得或者更改从另一个域名下获得的文件或者信息。也就是说我们的请求地址必须和当前网站的地指相同。同源策略通过隔离来实现对资源的保护。这个策略的历史非常悠久从Netscape Navigator 2.0时代就开始了。

        解决这个限制的一个相对简单的办法就是在服务器端发送请求,服务器充当一个到达第三方资源的代理中继。虽然是用广泛但是这个方法却不够灵活。

        另一个办法就是使用框架(frames),将第三方站点的资源包含进来,但是包含进来的资源同样要受到同源策略的限制。

        有一个很巧妙的办法就是在页面中使用动态代码元素,代码的源指向服务地址并在自己的代码中加载数据。当这些代码加载执行的时候,同源策略就不会起到限制。但是如果代码试图下载文件的时候执行还是会失败,幸运的是,我们可以使用JSON(JavaScript Object Notation)来改进这个应用。

        JSON和JSONP

        与XML相比,JSON是一个轻量级的数据交换格式。JSON对于JavaScript开发人员充满魅力的原因在于JSON本身就是Javascript中的对象。

        例如一个ticker对象

        var ticker = {symbol:'IBM',price:100}

        而JSON串就是 {symbol:'IBM',price:100}

        这样我们就可以在函数的参数中传递JSON数据。我们很容易掌握在函数中使用动态的JSON参数数据,但是我们的目的并不是这个。

        通过使我们的函数能够加载动态的JSON数据,我们就能够处理动态的数据,这项技术叫做 Dynamic Javascript Insertion。

        我们看下面的例子:

        index.html中

    <script type="text/javascript">  
    function showPrice(data){  
    alert("Symbol:" + data.symbol + ", Price:" + data.price);  
    }  
    var url = "ticker.js"; //Outer JS URL  
    var script = document.createElement('script');  
    script.setAttribute('src', url);  
    //load javascript  
    document.getElementsByTagName('head')[0].appendChild(script);  
    </script>  
    ticker.js中  
    var data = {symbol:'IBM', price:100};  
    showPrice(data); 

     

        上面的代码通过动态加入Javascript代码,来执行函数加载数据。

        正如之前提到过的,同源策略对于动态插入的代码不适用。也就是你可以从不同的域中加载代码,来执行在他们代码中的JSON数据。

        这就是JSONP(JSON with Padding)。注意,使用这种方法时,你必须在页面中定义回调函数,就像上例中的showPrice一样。

        我们通常所说的JSONP服务(远程JSON服务),实际上就是一种扩展的支持在用户定义函数中包含返回数据的能力。这种方法依赖于必须接受一个回调函数的名字作为参数。

        然后执行这个函数,处理JSON数据,并显示在客户页面上。

        JQuery的JSONP支持

        从JQery 1.2以后,就开始支持JSONP的调用。在另外的一个域名中指定好回调函数名称,你就可以用下面的形式来就加载JSON数据。

        url?callback=?

        示例:

    jQuery.getJSON(url + "&callbak=?", function(data)  
    {  
    alert("Symbol:" + data.symbol + ", Price:" + data.price);  
    }); 

        jquery会在window对象中加载一个全局的函数,当代码插入时函数执行,执行完毕后就会被移除。同时jquery还对非跨域的请求进行了优化,如果这个请求是在同一个域名下那么他就会像正常的Ajax请求一样工作。

        上例中我们在动态插入到页面的代码中使用了静态的json数据,虽然完成了依次JSONP返回,但仍不是JSONP服务,因为不支持在URL中定义回调函数名称。下面是一个将其变成JSONP服务的一个方法

        服务器端使用PHP。

        首先我们来定义接口的规范,就像这样:http://www.mydomain.com/jsonp/ticker?symbol=IBM&amp;callback=showPrice
    symbol是请求条件,callback是回调函数名称。

        在页面文件中,我们使用JQuery的支持:

    //JQuery JSONP Support  
    var url = "http://www.mydomain.com/api/suggest.php?symbol=IBM&callback=?";  
    jQuery.getJSON(url, function(data){  
    alert("Symbol:" + data.symbol + ", Price:" + data.price);  
    }); 

        在suggest.php中

    $jsondata = "{symbol:'IBM', price:120}";  
    echo $_GET['callback'].'('.$jsondata.')'; 

        再举个.NET webservice 的例子

        客户端

    $.getJSON(  
    "http://192.168.0.66/services/WebService1.asmx/ws?callback=?",  
    { name: "ff", time: "2pm" },  
    function(data) { alert(decodeURI(data.msg)) }  
    ); 

        服务器端

    [WebMethod]  
    public void ws(string name,string time) {  
    HttpRequest Request = HttpContext.Current.Request;  
    string callback = Request["callback"];  
    HttpResponse Response = HttpContext.Current.Response;  
    Response.Write(callback + "({msg:'this is"+name+"jsonp'})");  
    Response.End();  
    } 

        现在,如果我们想制作一些mashup,或者将第三方的资源整合到一个页面中,我们就很容易想到JSONP的解决方法了。

        注意:

        JSONP是一个非常强大的构建mashp的方法,可是不是一个解决跨域访问问题的万能药。它也有一些缺点:

        第一也是最重要的:JSONP不提供错误处理。如果动态插入的代码正常运行,你可以得到返回,但是如果失败了,那么什么都不会发生。你无法获得一个404的错误,也不能取消这个请求。

        另外一个重要的缺点是如果使用了不信任的服务会造成很大的安全隐患。

    转载于:https://www.cnblogs.com/Jackie-sky/p/3363780.html

    展开全文
  • 分别介绍了ZBD-3B型与ZJ-5型载波机的自动盘接口的工作和转接原理,从如何将话音与呼叫控制信号进行组合和隔离的分析角度,提出了音转接口板的设计原理和方法,并顺利制作和调试成功,解决了海洋变电站与调度中心的...
  • 无线通信原理与应用第二版中文版

    千次下载 热门讨论 2010-10-31 20:52:41
    5.9 线性和恒包络组合调制技术 5.9.1 多进制相移键控(MPSK) 5.9.2 多进制正交幅度调制(QAM) 5.9.3 多进制频移键控(MFSK) 5.10 扩频调制技术 5.10.1 伪随机(PN)序列 5.10.2 直接序列扩频(DS—SS) ...
  • 分析了系统的组成和功能需要,完成了以P89LPC954单片机为控制中心的软硬件设计,描述了通过超短波电台、GSM短信独立或组合方式进行数据通信的工作原理,对RS485传感器接口、雨量传感器接口、数据存储器与时钟电路的...
  • 理映射关系,结合频繁子图中的 apriori 算法计算原理组合的支持度,以获取最佳组合方案,并利用组 合方案相关专利,提取结构关键词,建立专利结构网络,结合结构洞挖掘关键元件,并开展产品创新设 计。以卫浴花洒...
  • 无线通信原理与应用(第一版) 中文版

    千次下载 热门讨论 2011-05-01 21:35:17
    5.9 线性和恒包络组合调制技术 5.9.1 多进制相移键控(MPSK) 5.9.2 多进制正交幅度调制(QAM) 5.9.3 多进制频移键控(MFSK) 5.10 扩频调制技术 5.10.1 伪随机(PN)序列 5.10.2 直接序列扩频(DS—SS) ...
  • 顶层是在单个单元上利用多个吸收峰叠加原理设计的由圆形开口环和方形开口环组合而成的金属结构。仿真结果表明, 该吸波器在10.65~22.39 GHz频率范围内的吸收率大于90%, 半峰全宽为13.07 GHz, 相对半峰全宽为83.7%;...
  • 平面构成是视觉元素在平面上,按照美的视觉效果和力学的原理,进行编排和组合,它是以理性和逻辑推理来创造形象、探究形象与形象之间的排列的方法,是理性与感性相结合的产物。下面一起和合肥学码思小编看下常见的几...
      平面构成是视觉元素在平面上,按照美的视觉效果和力学的原理,进行编排和组合,它是以理性和逻辑推理来创造形象、探究形象与形象之间的排列的方法,是理性与感性相结合的产物。下面一起和合肥学码思小编看下常见的几种平面设计构图技巧。

      中心构图

      首先我们就先来说一说我们常使用的一种构图模式,叫做中心构图,这种构图主要主要是为了引起观众的注意,将他们的视线集中到一个中心点,所以在中心构图的焦点处应该放有该海报之重要的信息,这样才有助于你将信息传递出去。这种构图切记重点要放在中心位置。

      三分法构图

      这种构图多是采用将画布的水平和垂直的两个方向,分别进行三等分,你的视觉焦点多落于这几条线的交叉点,这个在摄像摄影中用的尤为常见。这个三等分构图相较于居中对齐,显得更加的灵动,不再只是单纯的居中和对称呈现。

      制造视觉动线

      这种方法主要有以下两种方式:制造视觉阶级和运用引导线条。

      制造视觉阶级首先制造出是觉得中心点,然后给观众提供一条可以隐约引导的视觉路径,这样做让设计看起来更有层次,能够让观众更容易知道海报中信息的相对重要性。

      引导线条,这是采用了元素本身的自然形状,或者直接画出实体线条,通过线条的轨迹,将公众的实现引导给其他的元素。

      平衡画面

      平衡画面内的的元素,对设计师也极其考量设计师的功底。

      现在常用的平衡方式是对称平衡,对称的方式能够让画面变得更加的和谐,但是同时因为他的方正规矩,会让人觉得无聊。

      还有一种是我们所说的不对称平衡,这种方式处理不好容易出洋相,所以这种方式更加考验设计师的功底。这种平衡用的也会比较多,因为这种呈现出来的效果更加活泼又朝气。

      元素放大

      这个方式就是我们所说的通过放大某部分的重点内容,突出重点,也就是我们经常所说的元素放大。的方式突出我么说要学习的重点。

      对比手法

    通过对比的方式,元素与其他的部分互异,让他能够更加凸显。现实重点。

    转载于:https://blog.51cto.com/14296616/2394321

    展开全文
  • 软件设计规范

    2015-03-11 11:57:50
    根本原理是通用的;总体构造本身具有一般性,也就是抽象性、实际问题无关性;局部构件具有通用性。也就是说,这里存在容器和容量的区别,构造是容器,实际问题是装在容器中的量。一个好的容器要能顶住容量的压力;一...
  • 根据多光束干涉原理设计出用于薄膜太阳能电池的异型布拉格背反射器(IDBR)。该异型布拉格背反射结构由两对非晶硅(36.5 nm)/二氧化硅(81 nm)分布式布拉格反射器(DBR)结构与三对非晶硅(73 nm)/二氧化硅(162 nm)DBR...
  • 标准化:系统采用模块化设计,在一个最小系统板的基础上可随意组合不同的模块,用户亦可根据扩展接口定义自己设计特殊的模块,使其具有特殊功能。各模块尺寸及特殊功能设计都按照一定标准执行,这些标准亦可提供给...
  • 采用二维正方排列的光子晶体,根据微腔缺陷模和波导模共振耦合原理设计了一种新型的多信道下载滤波器,该滤波器通过主波导、90°弯波导和共振微腔的组合,提高了各个信道下载波导的下载效率。利用二维时域有限差分...
  • 复杂的设计原理在《写给大家看的设计书》中凝炼为亲密性、对齐、重复和对比4个基本原则。作者以其简洁明快的风格,将优秀设计所必须遵循的这4个基本原则及其背后的原理通俗易懂地展现在读者面前。《写给大家看的设计...
  •  本书是一本讲述数据库系统原理的教材,重点强调数据库建模与设计的基础、数据库管理系统提供的语言和工具以及系统实现技术。全书共分4个部分,第一部分介绍最基本的概念、术语及建模原则,第二部分描述了关系数据...
  • 通过优化设计S1(500 nm+800 nm)、S2(500 nm+900 nm)、S3(500 nm+1000 nm)、S4(500 nm+800 nm+1000 nm)4种不同中心波长组合的异型布拉格背反射结构,发现S3具有最佳宽频带高反射效果,该异质结由两对非晶硅...
  • 技术框架:包括 Spring、SpringBoot 配置加载、自定义注解、扫描注册Bean等,以及 ORM 框架设计原理和实现。这部分技术主要是把开发的中间件与框架结合,开发相应的组件或者包装为各类 SpringBoot Starter 的能力...
  • 此外,分析了不同监测点组合对模型定位精度的影响,发现监测点组合均匀分布在管网内部时,模型定位精度越高。   随着我国城市化进程的不断加快,城市供水管网的规模也不断增加,由于缺乏科学合理的规划、...
  • 滇红萎凋工艺研究

    2020-02-02 19:44:57
    滇红萎凋工艺研究,自浩,钱和,在单因素实验基础上,根据Box-Behnken实验设计原理,选取晒青时间、抖青次数、做青温度以及晾青失水率进行4 因素3水平中心组合实验,�
  • 5.2.5 图形组合 70 5.3 绘制曲线 72 5.3.1 使用arc()方法 72 5.3.2 使用arcTo()方法 74 5.3.3 使用quadraticCurveTo()方法 76 5.3.4 使用bezierCurveTo()方法 78 5.4 绘制文本 80 5.4.1 使用文本 80 5.4.2 ...
  • 根据Box-Behnken的中心组合实验设计原理,在单因素试验的基础上,采用三因素三水平的响应曲面分析法,建立了甘草饮片中甘草酸超声提取的二次多项数学模型,并以甘草酸提取率为响应值作响应面和等高线,考察了浸泡...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 125
精华内容 50
关键字:

中心组合设计原理