11.微服务设计 --- 规模化微服务

 

规模化微服务  
2.多少是太多
	有一个自动扩容系统,能够应对负载增加或者单点的故障,这可能很好,但对于一个月只运行一两次的报告系统就太夸张了。因为这个系统宕机1,2天也没什么影响。 

3.功能降级
	构建一个弹性系统,尤其是当功能分散在多个不同的,有可能宕掉的微服务上,重要的是能够安全的降级功能。

4.架构性安全措施  
	有一些模式,组合起来被称为架构安全措施,它们可以确保如果事情真的出错了,不会引起严重的级联影响。
	一个缓慢的服务就可能耗尽所有可用的worker,处理系统缓慢要比处理系统快速失败要困难的多。
	即使连接池的超时设置是正确的,所有的出站请求还是共享一个http连接池。最后,很明显下游服务是不健康的,但我们仍然一直发送通信。这意味着,实际上我们
  让情况变更糟糕,下游服务都没有恢复的机会。为了避免这种情况,我们应该:正确的设置超时,实现舱壁隔离不同的连接池,并实现一个断路器,以便在第一时间避免
  给一个不健康的系统发送调用。

5.反脆弱的组织 	
	事物实际上受益于失败和混乱。Netflix通过引发故障确保其系统的容错性。
	一些公司喜欢组织游戏日,在那天系统会被关掉以模拟故障发生。DiRT(灾难恢复测试),混乱猴子,混乱大猩猩。对于许多人来说,你的系统是否真的健壮的终极验证是
  在你的生产环境中释放自己的猴子军队。
    通过让软件拥抱和引发故障,并构建系统来应对。它还知道当失败后从失败当中学习的重要性,并在错误真正发生时采用不指责文化。作为这种学习和演化过程的一部分,
  开发人员被进一步授权,他们每个人都需要负责管理他的生产服务。 
	
	5.1 超时 
		如果等待时间太长决定调用失败,整个系统会被拖慢。如果超时太短,你会将一个可能还在正常工作的调用错误的认为是失败的。如果完全没有超,一个宕掉的下游可能
	  会让整个系统挂起。
	    给所有的跨进程调用设置超时,并选择一个默认的超时时间。当超时发生后,记录到日志里发生了什么,并相应的调整它。
	     
	5.2 断路器 
		断路器会在电流尖峰时刻保护你的电子设备。如初夏你尖峰,断路器会切断电路,保护你昂贵的家用电器。
		使用断路器,当对下游资源的请求发生一定数量的失败后,断路器会打开。接下来,所有的请求在断路器打开的状态下,会快速的失败。一段时间后,客户端发送一些请求
	  查看下游服务是否已经恢复,如果它得到了正常的响应,将重置断路器。
	  	过程:
	  		1.请求开始失败
	  		2.当达到一定阈值时,停止发送通信。断路器打开,请求快速失败
	  		3.发送健康检查,偶尔发送健康检查,看下游系统是否已经恢复。
	  		4.断路器重置,当到达健康的阈值时重置连接
	  	如何实现断路器依赖于请求失败的定义,但当使用http连接实现它们时,我会把超时或者5xx的http返回码作为失败的请求。通过这种方式,当一个下游宕机,或超时,或者
	  返回错误码时,达到一定阈值后,我们会自动停止向它发送通信,并启动快速失败。当它恢复健康后,我们会自动重新发送请求。
	    当断路器开后,你有一些选项。其中之一就是堆积请求,然后稍后重试它们。对于一些场景,这可能是合适的,特别是你所做的工作是异步作业的一部分时。然而,如果这个调用
	  作为同步调用链的一部分,快速失败可能更合适。这意味着,沿调用链向上传播错误,或更微妙的降级功能。
	    也可以手动使用它们。

	5.3 舱壁  
		舱壁,是把自己从故障中隔离开的一种方式。
		我们应该为每个下游服务的连接使用不同的连接池。这样的话,如果一个连接池被用尽,其余连接不受影响。这样可以确保,如果下游服务将来运行缓慢,那只有一个连接池会受
	  影响,其他调用仍可以正常运行。
	    关注点分离也是实现舱壁的一种方式。通过把功能分离成独立的微服务,减少因为一个功能的宕机而影响另外一个的可能性。
	    无论是微服务内部还是微服务之间,至少为每个下游建立一个单独的连接池。也可以考虑使用断路器。我们可以考虑把断路器看做是一种密封一个舱壁的自动机制,它不仅保护消费
	  者免受下游服务问题的影响,同时也使下游服务避免更多的调用,以防止可能产生的不利影响。鉴于级联故障的危险,建议对所有同步的下游调用都使用断路器。如Netflix的Hystrix
	  库。
	    舱壁是三个模式里最重要的。超时和断路器能帮助你在资源受限时释放它们,但舱壁可以在第一时间确保它们不成为限制。如Hystrix允许你在一定条件下,实现拒绝请求的舱壁,
	  以避免资源达到饱和,这称为减载。有时候拒绝请求是避免重要系统变得不堪重负或称为多个上游服务瓶颈的最佳方法。

	5.4 隔离  
		一个服务依赖于另外一个,另外一个服务的健康将越能影响其正常工作的能力。如果我们使用的集成技术允许下游服务器离线,上游服务便不太可能受到计划内或计划外宕机的影响。
		服务间加强隔离还有另外一种方法。当服务彼此隔离时,服务的拥有者之间需要更少的协调。团队间的协调越少,这些团队就越自治,这样他们可以更自由的管理和演化服务。

6.幂等
	对幂等操作来说,其多次执行所产生的影响,均与一次执行的影响相同。如果操作是幂等的,我们可以对其重复多次调用,而不必担心有不利影响。当我们不确定操作是否被执行,想要
  重新处理消息,从而从错误总恢复时,幂等会非常有用。
    例子:
    每次调用增加100:
    <credit>
    	<amount>100</amount>
    	<forAccount>1234</forAccount>
    </credit>

	改成幂等:
	<credit>
    	<amount>100</amount>
    	<forAccount>1234</forAccount>
    	<reason>
    		<forpurchase>4567</forpurchase>
    	</reason>
    </credit>
    我们知道,这次信用与一个特定的订单4567有关。假如一个给定的订单只能获得唯一的积分,我们可以在不增加总分的情况下,再次应用这个积分。
    这个机制在基于事件的协作工作中也会工作的很好,尤其是当你有多个相同类型的服务实例都订阅同一个事件时,会非常有用。即使我们存储了哪些事件被处理过,在某些形式的异步
  消息传递中,可能还留有小窗口,两个worker会看到相同的信息。通过以幂等的方式处理这些事件,我们确保不会导致任何问题。
    有些http动词,例如get和put,在http规范里被定义成幂等的,但要让这些成为事实,依赖于你的服务在处理这些调用时是否使用了幂等方式。如果使用了这些动词,但操作不是
  幂等的,然后调用者认为它们可以安全的重复执行,会陷入麻烦。

7.扩展  
	7.1  更强大的主机 
		一个有着更快的cpu和更好的IO机器,通常可以改善延迟和吞吐量,允许你在更短的时间内处理更多的工作。这种形式的扩展通常被称为垂直扩展,它是非常昂贵的。另外一个
	  问题是这种形式的扩展无法改善我们的服务器的弹性。但它是一个可以快速见效的方式。  
	7.2 拆分负载  
		我们也可能因为要扩展需要把现有的微服务拆分成几个部分,以更好的处理负载。
	7.3 分散风险  
		弹性扩展的一种方式是,确保不要把所有的鸡蛋放在一个篮子里。
		对于内部的虚拟化平台,常见的做法是,虚拟机的根分区映射到单个SAN(存储区域网络)。如果san故障,会影响所有连接的虚拟机。
		另外一种常见的减少故障的方法是,确保不要让所有的服务都运行在同一个数据中心的同一个机架上,而是分布在多个数据中心。如果你使用基础服务提供商,知道SLA(服务等级协议)
	  是否提供和具备相应的计划是非常重要的。如果需要确保你的服务在每季度不超过4小时的宕机时间,但是主机提供商只能保证每个季度不超过8小时的宕机时间,你必须改变SLA或选取一个
	  替代解决方案。
	    值得注意的是,供应商给你的SLA保证肯定会减轻他们的责任。先了解供应商如果没有履行义务的影响,应该讲灾难恢复托管到一个不同的供应商。
	7.4 负载均衡
		当你想让服务具有弹性时,要避免单点故障。
		负载均衡各种各样,从大型昂贵的硬件设备,到像mod_proxy这样基于软件的负载均衡。都有一些关键的功能,它们都是基于一些算法,将调用分发到一个或者多个实例中,当实例不再
	  健康时移除它们,并当它们恢复健康后再添加进来。
	    一些负载均衡提供了其他有用的功能。常见的一个是SSL终止,通过https连接入站负载均衡后,当到实例本身时转换成http连接。从经验上看,管理ssl的开销非常大,拥有一个
	  负载均衡器后,拥有一个负载均衡器来处理这个过程非常有用。
	    使用https的原因是确保请求不容易受到中间人共计,如果使用ssl终止,在某种程度上可能会暴露我们自己。缓解这个一个问题的方法是,把所有的微服务实例都放在一个独立的vlan里。
	  vlan是一个虚拟的局域网,所有的外部请求只能通过一个路由器访问内部。在这个例子中,这个路由器也就是ssl终端负载均衡器。vlan外部跟微服务通信的唯一方式是通过https,而内部
	  所有的通信都通过http。
	    aws以 ELBs(弹性负载均衡器)的形式,提供https终止的负载均衡器,你可以使用其安全组或VPCs(私有虚拟云)来实现vlan。另外,像mod_proxy这样的软件,可以发挥类似的软件
	  负载均衡器的作用。许多组织使用硬件负载均衡器,不过它难以实现自动化。正因为如此,比较倾向于在硬件负载均衡后使用软件负载均衡,这样允许团队自由的按需重新配置它们。无论
	  使用哪种方式,当考虑负载均衡器的配置时,要对待服务的配置一样对待它,放在版本控制里面。
	    负载均衡器允许我们以对服务的所有消费者提供透明的方式,增加更多的微服务实例。提供我们的负载能力,并减少单个主机故障的影响。
	7.5 基于worker的系统 
		负载均衡不是服务的多个实例分担负载和降低脆弱性的唯一方式。根据操作性质的不同,基于worker的系统可能和负载均衡一样有效。在这里,所有的实例工作在一些共享的待办作业列表
	  上。列表里可能是一些Hadoop进程,或者是共享的作业队列上的一大批监听器。这类型的操作非常适合批量或者异步作业。
	    该模型同样适用于负载高峰,你可以按需增加额外的实例来处理更多的负载。只要队列本身具有弹性,该模型就可以用于改善作业的吞吐量,也可以改善其弹性,因为它很容易应对worker
	  故障带来的影响。
	    基于worker的系统时,虽然worker本身不需要很高的可靠性,但保存待办作业的系统时需要。你可以使用一个持久化的消息代理来解决这个问题,或者像zookeeper这样的系统。 
	7.6 重新设计 
		系统最初的架构,可能和能够应对很大负载容量的架构是不同的。正如Jeff Dean 说的,你的设计应该 '考虑10倍容量的增长,但超过100倍容量时就要重写了'。在某些时刻,你需要做
	  一些相当激进的事情,以支持负载容量增加到下一个级别。这可能意味着采用新的技术,例如从同步请求/响应转换成基于事件的系统,采用新的部署平台,改变整个技术栈,或所有介于这些
	  之间的方案。
	    有人将到一定比例阈值时需要重新设计作为一开始就构建大规模系统的理由,这是危险的。当一个新项目开始的时候,我们往往不知道真正想要构建的是什么,也不知道会不会成功。我们
	  需要快速实验,并以此了解需要构建哪些功能。
	     
8 扩展数据库  
	8.1 服务的可用性和数据的持久性  
		重要的是区分服务的可用性和数据的持久性这2个概念。你需要明白这是不同的2件事,因此会有不同的解决方案。
	8.2 扩展读取  
		很多服务都是以读为主。
	8.3 扩展写操作  
		如何扩展写操作?一种方法是分片。分片写操作的复杂性来自于查询处理,查询单个记录是很容易的。但是如果查询跨越了多个节点,如果你要查询所有的分片,
	  要么需要查询每个分片,然后在内存中拼接。要么有一个替代的读数据库包含所有的数据集。跨分片查询往往采用异步机制,将查询的结果放进缓存。例如,Mongo
	  使用map/reduce 作业来执行这些查询。
	    使用分片系统会出现的问题之一是,如果我想添加一个额外的数据库节点怎么办?在过去,这往往需要大量的宕机时间,因为你需要停掉整个数据库,然后重新
	  分配数据。最近,越来越多的系统支持在不停机的情况下添加额外的分片,而重新分配数据会放在后台执行;例如,Cassandra。
	    写入分片可能会扩展写容量,但不会提高弹性。例如客户记录A~M总是去实例X,那么当实例X不可用时,A~M的记录依然不可用。Cassandra在这方面提高额外的功能,
	  可以确保数据在一个环内复制到多个节点。
	    Cassandra,Mongo,Riak。
	8.4 共享数据库基础设施 
		 某些类型的数据库,例如传统的RDBMS,在概念上区分数据库本身和模式(schema)。
	8.5 CQRS
		CQRS(命令查询职责分离)模式,是一个层次和查询信息的替代模型。在传统的管理系统中,数据的修改和查询使用的是同一个系统。使用CQRS后,
	  系统的一部分负责获取修改状态的请求命令并处理它,而另一部分则负责处理查询。
	 
9 缓存  
	缓存是性能优化常用的一种方法,通过存储之前操作的结果,以便后续请求可以使用这个存储的值,而不需要花费时间和资源去重新计算该值。

	9.1 客户端、 代理和服务器端缓存 
		使用客户端缓存的话,客户端会存储缓存的结果。由客户端决定何时获取最新副本。代理服务器缓存,是将一个代理服务器放在客户端和服务器之间。
	  反向代理或者cdn,是很好的使用代理服务器缓存的例子。服务端缓存,是由服务器来负责处理缓存,可能会使用像redis或者memcache这样的系统。
	    使用哪种缓存最合理取决于你正在试图优化什么。客户端缓存可以大大减少网络调用的次数,并且是减少下游服务器负载的最快方法之一。但是使用
	  由客户端负责缓存的这种方式,如果你想改变缓存的方式,让大批的消费者全部变化是很困难的。让过时的数据失效比较棘手。使用代理服务器缓存时,
	  一切对客户端和服务端都是不透明的。如果代理服务器被设计成对通用的流量进行缓存,它也可以缓存多个服务。如,反向代理squid和varnish,它们
	  可以缓存任何http通信。在客户端和服务器间加入代理服务器,会引入额外的网络跳数。使用服务器缓存,一切对客户端都是不透明的,它们不需要关心
	  任何事情。缓存在服务器外围或服务器限界内时,很容量了解一些类似数据是否失效这样的事情,还可以跟踪和优化缓存命中率。
	    使用缓存取决于你需要处理多少负载,对数据及时性有多少要求,以及你的系统现在能做什么。
	9.2 HTTP缓存 
		cache-control : 是否应该缓存,以及缓存几秒
		expires : 指定一个日期和时间
		Etag : 实体标签
	9.3 为写使用缓存  
		为写使用缓存也是有意义的,例如你如果使用后写式缓存,先缓存在本地,在之后的某个时刻写入。批量写入。
	9.4 为弹性使用缓存  
		缓存可以在出现故障时实现弹性。使用客户端缓存,如果下游服务不可用,客户端可以先简单的使用缓存中可能失效的数据。对一些系统来说,使用失效
	  但可用的数据,比完全不可用的要好。
	9.5 隐藏源服务 
		使用普通的缓存,如果请求缓存失败,请求会继续从数据源获取最新的数据,请求调用会一直等到结果返回。在普通情况下,这是期望的行为。但是,如果
	  遭受大量的请求缓存失败,也许是因为提供缓存的整个机器宕掉,大量的请求会被发送到源服务。
	    对于那些提供高度可缓存的服务,从设计上来说,源服务本身就只能处理一部分的流量,因为大量请求已经被源服务前面的缓存处理掉了。如果整个缓冲区
	  消失了,源服务就会接收到远大于其处理能力的请求。
	    在这种情况下,保护源服务的一种方式是,在第一时间就不要对源服务发起请求。相反,在需要时源服务本身会异步的填充缓存。如果缓存请求失败,会触发
	  一个给源服务的事件,提醒它需要重新填充缓存。所以如果整个分片消失了,我们可以在后台重建阿混吃。可以阻塞请求直到区域被重新填充。更合适的是,如果
	  想优先保持系统的稳定,我们可以让原始请求失败,但要快速的失败。
	    让请求快速失败,确保不占用资源或增加延迟,我们避免了级联下游服务导致的缓存故障,并给自己一个恢复的机会。 
	9.6 保持简单  
		避免在太多地方使用缓存。在你和数据源之间的缓存越多,数据就越可能失效,就越难确定客户端最终看见的是否是最新的数据。这在一个设计多个服务的微服务
	  架构调用链中,很可能产生问题。缓存越多,就越难屁股任何数据的新鲜程度。
	9.7 缓存中毒:一个警示 
		http 头 expires : never,被中间代理服务器缓存了。 

10 自动伸缩  

11 CAP定理  
	CAP : 一致性,可用性,分区容忍性。
	一致性是当访问多个节点时能够得到同样的值。可用性意味着每个请求都能获得响应。分区容忍性是指集群中的某些节点在无法联系后,集群整体还能继续进行服务的能力。
	11.1 牺牲一致性 
		我们的系统仍然可用,但失去了一致性。这通常被称为一个AP系统。系统放弃一致性以保证分区容忍性和可用性的做法,被称为最终一致性。也就是说,我们希望在将来
	  的某个时候,所有的节点都能看到更新后的数据,但它不会马上发生。 
	11.2 牺牲可用性 
		现在在分区情况下,如果数据库节点不能彼此通信,则它们无法协调以保证一致性。由于无法保证一致性,所以我们唯一的选择就是拒绝和响应请求。换句话说,我们
	  牺牲了可用性。系统是一致的和分区容忍的,即CP。这种模式下,我们的服务必须考虑如何做功能降级。 
	11.3 牺牲分区容忍性  
		CA系统在分布式系统中根本是不存在的。
	11.4 AP还是CP  
		看情况,做权衡。
	11.5 这不是全部或全不  
	11.6 真实世界  

12 服务发现 
	一旦你已经拥有了不少微服务,关注点就会不可避免的转向它究竟在何处。一般分成2部分:首先,它们提供了一些机制,让一个实例注册并告诉
  所有人:"我在这里"。其次,它们提供了一种方法,一旦服务被注册就可以找到它。

  案例:
  	1.DNS
  		DNS让我们将一个名称与一个或者多个机器的IP地址相关联。例如,我们可以决定,总能在 accounts.musiccopr.com 上发现账户服务。接着会将这个
  	  域名关联到运行该服务的主机IP上,或者一个负载均衡,然后分发到不同的实例分发负载。
  	    域名模板。例如,<服务名>-<环境>.musiccorp.com 的模板,然后基于此模板生成 accounts-uat.musiccorp.com 或 accounts-dev.musiccorp.com
  	  这样的域名项。
  	    处理不同环境的更先进的方式是,在不同的环境中使用不同的域名服务器。所以我可以假定,总是可以通过 accounts.musiccorp.com 找到账户服务,但根据
  	  所处环境的不同,可能会解析到不同的主机上。如果你已经将环境放进不同的网段,并且可以很容易的管理dns服务器和条目,这可能是相当简洁的解决方式,但如果
  	  你不能从这种设置中获取更多其他的好处,相对来说这个投入就很大了。
  	    DNS有许多有点,其中最主要的是它是标准的,并且大家对这个标准都很熟悉,几乎所有的技术栈都支持它。不幸的是,尽管有很多服务可以管理组织内的DNS,但
  	  其中很少是为处理这种高度可控制主机的场景而设计的,这使得更新DNS条目有些痛苦。亚马逊的Route53服务可以。

13 动态服务注册 
	作为一种在高度动态的环境发现节点的方法,dns存在一些缺点,从而催生了大量的替代系统,其中大部分包括服务注册和一些几种的注册表,注册表进而可以
  提供查找这些服务的能力。通常,这些系统所做的不仅仅是服务注册和服务发现。
   
	13.1 Zookeeper 
		使用场景:配置管理,服务间的数据同步,leader选举,消息队列和命名服务。
		zookeeper 的核心是提供了一个用于存储信息的分层命名空间,客户端可以在此层次结构中,插入新的节点,更改或者查询它们。还可以添加监控功能。 
	13.2 Consul
		Consul 也支持配置管理和服务发现。但它比zookeeper更进一步,为这些关键使用场景提供了更多的支持。例如,它为服务发现提供了一个http接口,
	  consul提供的杀手级特性之一是,它实际上提供了现成的dns服务器。具体来说,对于指定的名字,它能提供一条SRV记录,其中包含ip和端口。这意味着,
	  如果系统的一部分已经在使用dns,并且支持SRV记录,你就可以直接使用Consul,而无需对现有的系统做任何修改。

	    Eureka 
	    	Netflix  的开源系统 Eureka,追随 Consul和Zookeeper等系统的趋势,但它没有尝试成为一个通用的配置存储。

	13.4 构造你自己的系统
	13.5 别忘了人

14 文档服务
	通过将系统分解为更细粒度的微服务,我们希望以API的形式暴露出很多接缝,人们可以用它来做很多很棒的事情。如果正确的进行了服务发现,
  就能知道东西在哪里。但是我们如何知道这些东西的用处,或者如何使用它们?一个明显的选择就是API的文档。

	14.1 Swagger
		swagger 让你描述api,产生一个很友好的web用户界面,使你可以查看文档并通过web浏览器与api交互。
	14.2 HAL 和HAL浏览器
		HAL(超文本应用程序语言)本身是一个标准,用来描述我们公开的超媒体控制的标准。

15 自描述系统
	在 SOA 的早起演化过程中,UDDI(通用描述、发现与集成服务)标准的出现,帮助人们理解了哪些服务正在运行。
	有一个关于我们系统行为的全景图是非常重要的,特别是规模化以后。通过追踪下游服务的健康状态和使用关联标识,可以帮助我们识别调用链,得到关于
  服务如何交互的真实数据。使用像Consul这样的服务发现系统,可以看到我们的微服务在哪里运行。HAL让我们在任何给定的接口上查看有哪些功能,同时
  健康检查页面和监控系统,让我们知道系统整体的和单个服务的健康状态。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值