7.凤凰架构:构建可靠的大型分布式系统 --- 从类库到服务

Posted enlyhua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7.凤凰架构:构建可靠的大型分布式系统 --- 从类库到服务相关的知识,希望对你有一定的参考价值。

第7章 从类库到服务
	在一套由多个微服务互相调用才能正常运作的分布式系统中,每个节点都互相扮演者服务的生产者与消费者的多重角色,形成了一套复杂的网状调用关系。此时,
至少有(但不限于)以下三个问题是必须考虑并得到妥善解决的:
	1.对消费者来说,外部的服务由谁提供?具体在什么网络位置?
	2.对生产者来说,内部哪些服务需要暴露?哪些需要隐藏?应当以何种形式暴露服务?以什么规则在集群中分配请求?
	3.对调用过程来说,如何保证每个远程服务都接收到相对平均的流量,获得尽可能高的服务质量与可靠性?

	这三个问题的解决方案,在微服务架构中通常被称为"服务发现" "服务的网关路由" 和 "服务的负载均衡"。

7.1 服务发现
	类库封装被大规模使用,令计算机可以通过位于不同模块的方法调用来组装复用指令序列。无论是编译器链接的C、C++,还是运行期链接的Java语言,都要通过
链接器(Linker)将代码里的符号引用转换为模块入口或者进程内地址的直接引用。而服务化的普及,令软件系统可以通过分布式网络中不同机器的互相协作来复用功能。
此时,如何确定目标方法的确切位置,便是与编译链接有着同等意义的研究课题,解决该问题的过程被称作"服务发现"。
	
	7.1.1 服务发现的意义
		所有的远程服务调用都是使用 全限定名、端口号和服务标识 所构成的三元组来确定一个远程服务的精确坐标的。全限定名 代表了网络中某台主机的精确位置,
	端口号代表了某个主机上某一个提供tcp/udp 网络服务的程序,服务标识则代表了该程序所提供的某个具体的方法入口。其中,"全限定名、端口号"的含义对所有
	的远程服务来说都是一致的,而"服务标识"则与具体的应用层协议有关,不同协议有不同形式的标识。譬如rest的远程服务,标识是url地址;RMI的远程服务,
	标识是Stub类中的方法;SOAP的远程服务,标识是WSDL中定义的方法,等等。远程服务标识的多样性,决定了"服务发现"也可以有两种不同的解释,一种是以UDDI
	为代表的"百科全书式"的服务发现,上至提供服务的企业信息,下至服务的程序接口细节(方法名称、参数、返回值、技术规范等)都在服务发现的管辖范围内;另外
	一种是类似于dns这样"门牌号式"的服务发现,只满足从某个代表提供者的全限定名到服务实际主机ip地址的翻译转换,并不关心服务具体是哪个厂商提供的,也不
	关心服务有几个方法,各自由什么参数构成,它默认这些细节信息是服务消费者本身已经完全了解的,此时服务坐标就可以简化为"全限定名+端口号"。后一种服务
	发现已经占据主流地位。

		原本服务发现只依赖于dns将一个全限定名翻译为一至多个ip地址或者SRV等其他类型的记录即可,位于dns之后的负载均衡器也实质上承担了一部分服务发现的
	功能,完成了外部ip到各个服务内部实际ip的转换。这种做法在软件追求不间断长时间运行的时代是很合适的。但随着微服务的流行,服务的非正常宕机、重启和
	正常的上下线变得越来越频繁,仅靠dns服务器和负载均衡器等基础设施逐渐疲于应付了,无法跟上服务变动的步伐。

	7.1.2 可用与可靠
		服务发现 具体是指进行过什么操作?这其中包含三个必须的过程:
			1.服务的注册
				当服务启动的时候,它应该通过某种形式(如调用api、产生事件消息、在zookeeper/etcd 的指定位置记录、存入数据库,等等)将自己的坐标
			信息通知到服务注册中心,这个过程可能由应用程序本身完成,称为自注册模式;也可能由容器编排框架或者第三方注册工具来完成,称为第三方注册模式。

			2.服务的维护
				尽管服务发现框架通常都有提供下线机制,但并没有什么办法来保证每次服务都能优雅的下线而不是由于宕机、断网等原因突然失联。所以服务发现
			框架必须自己保证所维护的服务列表的正确性,以避免告知消费者服务的坐标后,得到的服务却不能使用的尴尬情况。现在的服务框架,往往都能支持多种
			协议(http、tcp等)、多种方式(长连接、心跳、探针、进程状态等)去监控服务是否健康存活,将不健康的服务自动从服务注册表中剔除;

			3.服务的发现
				这里的发现是指狭义上消费者从服务发现框架中,把一个符号(譬如Eureka中的ServiceID、Nacos中的服务名,或者通用的FQDN)转换为服务实际
			坐标的过程,这个过程现在一般是通过 http api 请求或者 dns lookup 操作来完成的,也有一些相对少用的方式,譬如k8s也支持注入环境变量来做
			服务发现。

		上述3点列举了服务发现必须提供的功能,此外还会有一些可选的扩展功能,譬如服务发现时进行的负载均衡、流量管控、键值存储、元数据管理、业务分组、等。

		服务发现既要高可用,也要高可靠。以Eureka和Consule为例:
			a) Eureka
				Eureka 的选择是优先保证高可用性,相对牺牲系统中服务状态的一致性。eureka的各个节点间采用异步复制来交换服务注册信息,当有新服务注册
			进来时,并不需要等待信息在其他节点复制完成,而是马上在该服务发现节点宣告服务可见,只是不保证在其他节点多长时间后才会可见。同时,当有旧的
			服务发现变动,譬如下线或者断网,只会由超时机制来控制何时从哪一个服务注册表中移除,变动的信息不会实时同步给所有服务端与客户端。这样的设计
			使得不论是eureka的服务端还是客户端,都能维持自己的服务注册表缓存,并以ttl机制来进行更新,哪怕服务注册中心完全崩溃,客户端仍然可以维持
			最低限度的可用。eureka的服务发现模型适用于节点关系相对固定、服务一般不会频繁上下线的系统,以较小的同步代价换取了最高的可用性;eureka
			能够选择这种模型的底气在于万一客户端拿到了已经发现变动的错误的地址,也能够通过Ribbon和Hystrix模块配合兜底,实现故障转移(Failover)
			和快速失败(Failfast)。

			b) Consul
				Consul 的选择是优先保证高可靠性,相对牺牲系统服务发现的可用性。consul 采用Raft算法,要求多数节点写入成功后服务的注册或者变动才
			算完成,严格的保证了在集群外部读取到的服务发现结果的一致性;同时采用Gossip 协议,支持多数据中心之间更大规模的服务同步。consul 优先
			保障高可靠性在一定程度上是基于产品实现情况而做的技术决策,它不像Netflix OSS那样有着全家桶式的微服务组件,万一从服务发现中取到错误地址,
			就没有其他组件为它兜底了。

	7.1.3 注册中心实现
		可用性与一致性的矛盾,是分布式系统永恒的话题。在服务发现这个场景里,权衡的主要关注点是相对更能容忍服务列表不可用的后果,还是服务数据不一致的
	后果,其次才是性能高低,功能是否强大,使用是否方便等。

		当下,直接以服务发现、服务注册中心为目标的组件库,或者间接来实现这个目标的工具主要分为三类:
			1.在分布式k/v存储框架上自己开发的服务发现,典型的代表是 Zookeeper、Doozerd、etcd。这些k/v框架提供了分布式环境下读写操作的共识算法,
			etcd采用的是我们学习过的Raft算法,zookeeper采用的是ZAB算法。所以采用这种方案,就不必纠结CP还是AP的问题了,它们都是CP的(也有用redis
			来做服务发现的,这种自然是AP的)。这类框架的宣传语中往往会主动提及"高可用性",潜台词是"在保证一致性和分区容忍性的前提下,尽最大努力实现
			最高的可用性",譬如etcd的宣传语就是"高可用的集中配置和服务发现"。这些k/v框架的一个共同特点是在整个较高复杂度的架构和算法的外部,维持着
			即为简单的应用接口,只有基本的curd和Watch等少量api,所以要在上面完成功能齐全的服务发现,很多基础的能力,譬如服务发现的路由、如何做健康
			检查等都必须自己去实现。

			2.以基础设施(主要是指dns服务器)来实现服务发现,典型的代表是SkyDNS、CoreDNS。在k8s 1.3之前的版本用 SkyDNS作为默认的dns服务,其工作
			原理是从 API Server中监听集群服务的变化,然后根据服务生成NS、SRV等dns记录存放到etcd中,kubelet会设置每个Pod的dns服务的地址为
			SkyDNS的地址,需要调用服务时,只需查询dns把域名转换为ip列表便可实现分布式的服务发现。在k8s 1.3之后,SkyDNS不再是默认的dns服务器,
			而是由只将dns记录存储在内存中的KubeDNS代替,到了1.11版,就更推荐采用扩展性更强的CoreDNS,此时可以通过各种插件来决定是否采用etcd存储、
			重定向、定制dns记录、记录日志等等。

				采用这种方案,是cp还是ap 就取决于后端采用何种存储,如果是基于etcd的实现,就自然是CP的,如果是基于内存异步复制的方式实现的,那就是
			AP的。以基础设施来做服务发现,好处是对应用是透明的,任何语言、框架、工具肯定都支持http、dns,所以完全不受程序技术选型的约束,但坏处是
			透明的不一定是简单的,你必须自己考虑如何去客户端做负载均衡、如何调用远程方法等这些问题,而且必须遵循或者说受限于这些基础设施本身所采用的
			实现机制,譬如服务健康检查里,服务的缓存期就应该由ttl决定,这是dns协议所规定的,如果想改用keepalive长连接来实现实时判断服务是否存活
			就相对麻烦了。

			3.专门用于服务发现的框架和工具,典型的代表是Eureka、Consul和Nacos。在这类框架中,你可以自己决定是CP还是AP,譬如CP的Consule、AP的
			Eureka,还有同时支持CP和AP的Nacos。将它们归为一类是因为它们对应于不是透明的,尽管consul的主体逻辑是在服务进程之外,以边车的形式提供;
			尽管consul、Macos也支持基于dns的服务发现;尽管这些框架基本都做到了以声明代替编码,譬如在Spring Cloud 中只改动pom.xml、配置文件和
			注解即可实现,但它们依然是可以被应用程序感知的。所以或多或少还需要考虑程序语言、技术框架的基础问题。但这个特点其实并不完全是坏处,譬如
			采用Eureka做服务注册,那在远程调用服务时就可以用 OpenFeign做客户端,,因为它们本身就已经做好了集成,写个声明式接口就能运行;在做
			负载均衡时可以采用Ribbon做客户端,需要换均衡算法时改给配置就行了。这些"不透明"实际上都为编码开发带来了一定便捷,但前提是你选用的语言
			和框架必须都支持。


7.2 网关路由
	7.2.1 网关的职责
		在微服务架构下,每个服务节点都可能由不同的团队负责,都有着自己独立的、互不相同的接口,如果服务集群缺少一个统一对外交互的代理人角色,那外部的
	服务消费者就必须知道所有微服务节点在集群中的精确坐标,这样消费者会受到服务集群的网络限制(不能确保集群中的每个节点都有外网连接)、安全限制(不仅是
	服务节点的安全,外部自身也会受到如浏览器同源策略的约束)、依赖限制(服务坐标这类信息不属于对外接口承若的内容,随时可能变动,不应该依赖它)。

		微服务网关的首要职责就是作为统一的出口对外提供服务,将外部访问网关地址的流量,根据适当的规则路由到内部集群中正确的服务节点之上,因此,微服务
	中的网关,也常常被称作"服务网格"或者"API网关",微服务中的网关首先应该是个路由器,在满足此条件的基础上,还可以根据需要作为流量过滤器使用,以提供
	某些额外的可选功能,譬如安全、认证、授权、限流、监控、缓存,等等。

		网关 = 路由器(基础职能) + 过滤器(可选职能)

		针对路由器这个职能,服务网格的只要考量是能够支持路由的"网络协议层次"和"性能与可用性"。仅从技术实现上来看,对于路由这项工作,负载均衡器与服务
	网关在实现上是没有什么差别的;但从墓地角度来看,负载均衡器和服务网关会有一些区别,前者是根据负载均衡算法对流量进行平均的路由,后者是为了根据流量中
	的某些特征进行正确的路由。网关必须能够识别流量中的特征,这意味着网关能够支持的网络通信协议的层次将直接限制后端服务节点能够选择的服务通信方式。

		网关的另外一个主要关注点是它的性能与可用性。由于网关是所有服务对外的总出口,是流量的必经之地,所以网关的路由性能能将导致全局的、系统的影响。
	网关的性能与它的工作模式和自身实现的算法都有关系。在代理模式下,网关的性能主要取决于它们如何代理网络请求,也即它们的网络IO模型。

	7.2.2 网络I/O模型
		在套接字接口抽象下,网络IO的出入口就是socket的读和写,socket在操作系统接口中被抽象为数据流,而网络IO可以理解为对流的操作。每一次网络访问,
	从远程主机返回的数据会先放到操作系统内核的缓冲区中,然后从内核的缓冲区复制到应用程序的地址空间,所以当发生一次网络请求时,将会经历按顺序的"等待数据
	从远程主机到达缓冲区"和"将数据从缓冲区复制到应用程序的地址空间" 2个阶段,根据实现这2个阶段的不同方法,人们把网络IO模型总结为2类、5种模型:
		a) 2类是指同步IO和异步IO
		b) 5种模型是指在同步IO模型中又划分出,阻塞IO、非阻塞IO、多路复用IO、信号驱动IO 4种细分模型 以及 异步IO模型。

		同步IO是指调用端发出请求后,得到结果之前必须一直等待,与之对应的就是异步,发出调用请求之后将立即返回,不会马上得到处理结果,结果将通过状态变化
	和回调来通知调用者。阻塞和非阻塞是针对请求处理过程而言的,指在收到调用请求后,返回结果之前,当前处理线程是否会被挂起。

		异步IO:
			异步IO中数据到达缓冲区后,不需要由调用进程主动进行缓冲区复制数据的操作,而是复制完成后由操作系统向线程发送信号,所以它一定是非阻塞的。

		同步IO:
			1.阻塞IO
				阻塞IO是最直观的IO模型,逻辑清晰,也比较节省cpu资源,但是缺点是线程休眠所带来的上下文切换,这是一种需要切换到内核态的重负荷操作,
			不应该频繁进行。

			2.非阻塞IO
				非阻塞IO能避免线程休眠,对于一些很快能返回结果的请求,非阻塞IO可以节省切换上下文的消耗,但是对于一些长时间才能返回的请求,非阻塞IO
			反而白白浪费了cpu资源,所以目前不太常用。

			3.多路复用IO
				多路复用IO本质上是阻塞IO的一种,但是它的好处是可以在同一阻塞线程上处理多个不同的端口监听。多路复用IO是目前高并发网络应用的主流,它还
			可以细分为select,poll,epoll等不同实现。

			4.信号驱动IO
				信号驱动IO与异步IO的区别是,"从缓冲区获取数据"的这个步骤的处理,前者是收到通知是可以开始进行复制操作了,即要你自己从食堂拿会宿舍,在
			复制完成之前线程处于阻塞状态,所以它仍然处于同步IO操作,而后者收到的通知是复制操作已经完成,即外卖小哥已经把饭送到了。


		七层服务网格处理一次请求代理时,包含两组网络操作,分别是作为服务端对外部请求的响应和作为客户端对内部服务的请求,理论上这两组网络操作都可以采用
	不同的模型去完成,但一般没有必要这么做。以Zuul网关为例,它采用的是阻塞IO模型来进行最经典的"一条线程对应一个连接"的方式来代理流量。采用阻塞IO模型
	意味着它会有线程休眠,会有上下文切换的成本,所以如果后端服务普遍属于计算密集型时,这种模型能够相对节省网关的cpu资源,但如果后端服务普遍都是IO密集
	型,它就会频繁的上下文切换导致性能降低。

		网关还有一个必须关注的是它的可用性。我们应该考虑以下几点:
			1.网关应该尽可能的轻;
			2.网关选型时,应该尽可能选择比较成熟的产品实现;
			3.在需要高可用的生产环境中,应该考虑在网关之前部署负载均衡器或者等价路由器,让那些成熟健壮的设施去充当整个系统的入口地址,这样网关也能扩展


	7.2.3 BFF网关
		BFF(Backend for Frontend):
			网关不必为所有的前端提供无差别的服务,而是应该针对不同的前端,聚合不同的服务,提供不同的接口和网络访问协议支持。在网关这种边缘节点上,针对
		同样的后端集群,裁剪、适配、聚合出适应不一样的前端的服务,也有助于后端的稳定,也有助于前端的赋能。


7.3 客户端负载均衡
	案例场景:
		假设你身处广东,要上 Fenix's Bookstore 购买一本书,在程序业务逻辑里,购书的其中一个关键步骤是调用商品出库服务来完成货物准备,在代码中该服务
	的调用请求未:
		PATCH https://warehouse:8080/restful/stockpile/3

		amount:-1

		又假设 Fenix's Bookstore 是个大书店,在全国都有部署服务集群,你的购物请求从流量发出后,服务端按顺序经历如下:
			1.首先是将 warehouse 这个服务名称转换为恰当的服务地址,如请求来自广东,就近分配给了物理传输距离较短的广州机房。用户访问首页的时候已经
			被dns服务器分配到了广州机房,请求出库服务时,应优先选择同机房的服务进行调用,此时请求变为如下:
				PATCH https://guangzhou-ip-wan:8080/restful/stockpile/3

			2.广州机房的服务网格将该请求与配置中的特征进行对比,由url中的 /restful/stockpile/** 得知该请求访问的是商品出库服务,因此,将该请求
			ip地址转换为内网中 warehouse 服务集群的入口地址:
				PATCH https://warehouse-gz-lan:8080/restful/stockpile/3

			3.集群中部署了多个 warehouse 服务,收到请求后,负载均衡器要在多个服务中根据某个标准(均衡策略),找出要响应本次调用请求的服务,称为
			warehouse-gz-lan-node1。
				PATCH https://warehouse-gz-lan-node1:8080/restful/stockpile/3

			4.如果访问 warehouse-gz-lan-node1 服务,没有返回需要的结果,而是抛出500错误
				HTTP/1.1 500 Internal Server Error

			5.根据预设置的故障转移策略,重新将调用分配给能提供服务的其他节点,称其为 warehouse-gz-lan-node2
				PATCH https://warehouse-gz-lan-node2:8080/restful/stockpile/3

			6.warehouse-gz-lan-node2 服务返回商品出库成功

		从以上整体看,步骤1,2,3,5 分别对应了服务发现,网关路由,负载均衡和服务容错,从细节上看,其中部分职责是有交叉的。

	7.3.1 客户端负载均衡器
		对于任何一个大型系统,负载均衡器都是必不可少的设施。以前,负载均衡器大多只部署在整个服务集群的前端,负责将用户的请求分散到各个服务进行处理,
	这种典型的部署形式现在被称为集中式的负载均衡。随着微服务的流行,服务集群收到的请求来源不再限于外部,而是越来越多的来源于集群内部的某个服务,并由
	集群内部的另外一个服务进行响应,对于这类流量的负载均衡,既有的方案依然是可行的,但针对内部流量的特点,直接在服务集群内部消化掉,肯定是更合理的。
	由此一种全新的、独立于每个服务前端、分散式的负载均衡方式逐渐流行起来,即 客户端负载均衡器。

		客户端负载均衡器的理念提出后,此前的集中式负载均衡器也有了一个与之对应的名字 --- "服务端负载均衡器"。差别是:客户端负载均衡器和服务实例是
	一一对应的,而且与服务实例并存于同一个进程内。这个特点能为它带来很多好处:
		1.负载均衡器与服务之间的信息交换是进程内的方法调用,不存在任何网络的开销;
		2.不依赖集群边缘的设施,所有内部流量都仅在服务集群的内部循环,避免了前文那样,集群内部流量要"绕场一周"的尴尬局面;
		3.分散式的负载均衡器意味着天然避免了集中式的单点问题,它的带宽资源将不会像集中式负载均衡器那样敏感,这样以七层负载均衡器为主流、不能通过ip
		隧道和三角传输这样的方式节省带宽的微服务环境中显得更具优势;
		4.客户端负载均衡器更加灵活,能够针对每一个服务实例单独设置均衡策略等参数,例如访问哪个服务,是否需要具备亲和性,选择服务的策略是随机、轮询、
		加权还是最小连接,等等,都可以单独设置而不影响其他服务。

		缺点:
			1.它与服务运行在同一进程内,意味着它的选型受到服务所使用的编程语言的限制,譬如用go开发的微服务就不太可能搭配Spring Cloud 的负载均衡器
			来使用,而为每种语言都实现对应的能够支持复杂的网络情况的负载均衡器是非常难的。客户端负载均衡器的这个缺陷有违于微服务中技术异构不应受到限制
			的原则;

			2.从个体服务来看,由于是共用一个进程,负载均衡器的稳定性会直接影响整个服务进程的稳定性,消耗的cpu、内存等资源也同样影响到服务的可用资源,
			从集群整体来看,在服务数量达到上千上万规模时,客户端负载均衡器消耗的资源总量是相当多的;

			3.由于请求的来源可能是集群中任意一个服务节点,而不再是统一来自集中式负载均衡器,使得内部网络安全和信任关系变得复杂,当攻破任何一个服务时,
			更容易通过该服务突破集群中的其他部分;

			4.服务集群的拓扑关系是动态的,每一个客户端均衡器必须持续跟踪其他服务的健康情况,以实现上线新服务,下线旧服务,自动剔除失败的服务,自动重
			连服务等负载均衡器必须的功能。由于这些操作都需要通过访问服务注册中心来完成,数量庞大的客户端负载均衡器一直持续轮询服务注册中心,也会带来
			不少的压力。


	7.3.2 代理负载均衡器
		在Java领域,客户端负载均衡器中最具代表性的产品是Netflix Ribbon 和 Spring Cloud LoadBalancer。直到最近两三年,服务网格
	(Service Mesh)开始逐渐盛行,另外一种被称为"代理客户端负载均衡器"的客户端负载均衡器变体形式开始流行,它弥补了此前客户端负载均衡器的大多数缺陷。
	代理负载均衡器对此前的客户端负载均衡器的改进是将原本嵌入在服务进程中的负载均衡器提取出来,作为一个进程之外,同一个Pod之内的特殊服务,放到边车代理
	之中。

		虽然代理均衡器与服务实例不再是进程内通信,而是通过网络协议栈进行数据交换,数据要经过操作系统的协议栈,要进行拆包打包、计算校验和、维护序列号等
	网络数据的收发步骤,比之前的客户端负载均衡器确实多增加了一系列步骤。不过,k8s 严格保证了同一个pod中的容器不会跨越不同的节点,这些容器共享同一个
	网络名称空间,因此代理均衡器与服务实例的交互,实质上是对本机回环设备的访问,仍然要比真正的网络交互高效且稳定很多。代理负载均衡器付出的代价较小,但
	从服务进程中分离出来的收益却是非常显著的:
		1.代理均衡器不再受编程语言的限制。开发一个支持python,go,java等所有微服务应用服务的通用代理均衡器具有很高的性价比。集中不同编程语言的使用者
		的力量,更容易打造出面对复杂网络情况的、高效健壮的负载均衡器。即使退一步说,独立于服务进程的均衡器也不会自身的稳定性影响到服务进程的稳定性。

		2.在服务拓扑感知方面,代理均衡器也更具有优势。由于边车代理接受控制平面的统一管理,当服务节点拓扑发生变化的时候,控制平面就会主动向边车代理发送
		更新服务清单的控制指令,这避免了此前客户端负载均衡器必须长期主动轮询服务注册中心所造成的浪费。

		3.在安全性、可观测上,由于边车代理都是一致的实现,有利于在服务间建立双向mTLS通信,也有利于对整个调用链路给出更详细的统计。

		总体而言,边车代理这种通过同一个Pod的独立容器来实现的负载均衡器是目前处理微服务集群内部流量的最理想方式。


	7.3.3 地域与区域
		1.Region 是 地域 的概念,譬如华北、东北、华东、华南 这些都是地域的范围。面向全国或者全球的大型系统的服务集群往往会部署在多个不同的地域,譬如
		开头的场景,大型系统就是通过不同地域的机房来缩短用户与服务器之间的物理距离,以提升响应速度。对于小型系统,地域一般就只在异地容灾时才会涉及。
		需要注意的是,不同地域之间是没有内网连接的,所有流量都只能通过公众互联网相连,如果微服务的流量跨越了地域,实际就跟调用外部服务提供的互联网服务
		没有任何差别了。所以集群内部流量是不会跨越地域的,服务发现、负载均衡器默认也是不会支持跨越地域发服务发现和负载均衡的;

		2.Zone 是 区域 的概念,它是可用区域(Availability Zone)的简称。区域是指在地理上位于同一地域内,但电力和网络是互相独立的物理区域,譬如在
		华东的上海、杭州、苏州的不同机房就是同一个地域的几个可用区域。同一个地域的可用区域之间具有内网相连,流量不占用公网带宽,因此区域是微服务集群
		内流量能够触及的最大范围。但你的应用只是部署在同一区域内,还是部署到不同可用区中,要取决于你是否做异地双活的需求,以及对网络延时的容忍程度。
			a) 如果你追求高可用,譬如希望在某个地区发生电力或者骨干网中断时仍然可用,那可以考虑将系统部署在多个区域中。注意异地容灾和异地双活的区别,
			容灾是非实时同步的,而双活是实时或者准实时的,跨地域或者跨区域做容灾都是可以的,但一般只能跨区域做双活,当然,也可以将它们结合起来使用,
			即"两地三中心"模式;

			b) 如果你追求低延迟,譬如对时间有高要求的SLA应用,或者网络游戏服务器等,那就应该考虑将系统的所有服务部署在同一个区域中,因为尽管内网连接
			不受限于公网带宽,但毕竟机房之间的专线容量也是有限的,难以跟机房内部的交换机相比,延时也受物理距离、网络节点跳数等因素的影响。

		3.可用区域对应城市级别的区域的范围,但在一些场景中仍是过大了,即使是同一个区域中的机房,也可能存在具有差异的不同子网络,所以在部分微服务框架也
		提供了Group、Sub-zone 等参数作进一步的细分控制,这些参数的意思通常是加权或者优先访问同一个子区域的服务,但如果子区域中没有合适的,则仍然会
		访问到可用区域中的其他服务。

		4.地域和区域原本是云计算中的概念。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上是关于7.凤凰架构:构建可靠的大型分布式系统 --- 从类库到服务的主要内容,如果未能解决你的问题,请参考以下文章

东方的第一本“凤凰架构”竟是出自阿里,手把手教你构建可靠大型分布式系统

1.凤凰架构:构建可靠的大型分布式系统 --- 服务架构演进史

1.凤凰架构:构建可靠的大型分布式系统 --- 服务架构演进史

阿里内部第一本“凤凰架构”,保姆级教你构建可靠大型分布式系统

10.凤凰架构:构建可靠的大型分布式系统 --- 虚拟化容器

17.凤凰架构:构建可靠的大型分布式系统 --- 技术演示工程实践