云原生软件的设计目的是预测故障,并且即使当它所依赖的基础设施出现故障,或者发生其他变化时,它也依然能够保持稳定运行。
将变化或者失败视为正常规律,让面向失败的设计成为它们构建、交付和管理软件过程中的一个组成部分。
云原生软件是高度分布式的,必须在一个不断变化的环境中运行,而且自身也在不断地发生变化
云原生的绝妙之处在于它最终是由许多不同组件组成的,即使其中一些组件的模式不是最新的,云原生组件也可以与他们进行交互。即使你的软件使用了旧的模式,应用云原生模式依然可以带来立竿见影的效果。
1. 碎片化的变化
2. 部署是有风险的 部署通常充满危险。在升级期间需要停机或者部署时引起意外停机都是很正常的,而停机的代价是昂贵的。
3. 传统运维认为变化是例外 开发人员通常会在初次部署之后退出,然后由运维人员接管。运维团队手里只有一本运维手册。虽然运维手册详细描述了可能的失败场景及其解决方案,但是更加深入思考一下不难了解,手册设定了一个假设,就是失败场景是已知的。但是,绝大多数情况不是这样的!
4. 生产环境的不稳定性
目标
1. 什么是持续交付 持续交付的每个开发/测试流程都不会进行部署,但每个流程都会生成可以交付的软件。之后,是否部署就看商业决策了,而传统的软件交付则是在创建可以发布到生产环境中的构件之前,会提前进行大量的开发工作和一个长时间的测试流程
2. 持续交付的优势
部署可重复有助于系统部署和保证系统的稳定性,并且通过在每次开发/测试的迭代过程中控制变化风险,可以缩短整个新功能的交付时间。
达到部署可重复手段包括:
1. 控制环境
你必须从标准化的机器镜像开始。例如,从一个基础Ubuntu镜像开始,并且软件需要Java开发工具包(JDK),那么可以通过脚本将JDK安装到基础镜像中。此模式也经常被称为基础设施即代码(Infrastructure as Code)。当需要一个环境的新实例时,可以从基础镜像开始并执行脚本,这样就可以保证每次都拥有相同的环境。
2. 控制可部署构建 一旦你为软件开发生命周期的不同阶段构建了不同的构件,可重复性就可能受到影响。为了实现高效、安全和可重复的生产环境运维,不要把环境相关的配置包含到代码中。
3. 控制流程 将各个部分组合起来并确保一致性的唯一方法就是自动化。
在不断变化的情况下保持系统功能的完整性,是我们设计软件的最终目标,而变化对系统稳定性和可靠性的影响也是显而易见的。一个能够自我修复的系统,其正常运行的时间比每次出故障都需要人工干预的系统要长得多。将部署作为一种新的期望状态,可以极大地简化部署过程并降低风险。坚持“变化是一定的”的思维模式,可以从根本上改变在生产环境中管理软件的方式。
顺序式的编程模型会促使你以请求/响应的方式进行思考。 如果请求只有在犹如树状的所有级联请求都成功的情况下才能成功,那么微服务的可用性会降低很多。
事件驱动的核心思想在于,因为事件导致代码被执行,然后可能会产生更多的事件。
对于请求/响应的方式,聚合发生在用户发出请求的时候。而对于事件驱动的方式,聚合发生在系统中数据发生变化的时候,并且这是异步的。
在请求/响应的微服务架构中,重试是补偿网络分区的一个关键模式。在事件驱动的系统中,事件存储是对网络不稳定性的一个关键补充
将写逻辑(命令)与读逻辑(查询)分离开来。这就是命令查询责任分离(CQRS)模式的核心思想。CQRS的核心就是将这两个关注点分离开来。其优点在于,
灵活的伸缩性并不是采用多个实例的唯一动机,高可用、可靠性和运维效率也同样是考虑因素,例如避免单点故障、在线升级等
对于云原生应用,相同的输入应该产生相同的结果,无论有多少实例,也无论请求被路由到哪个实例。
云原生不能继续使用黏性会话来处理有状态的服务,因为黏性会话指定的实例可能已经消失,或者由于网络异常而无法访问
云原生应用程序有存在状态的地方,同样重要的是,也有不存在状态的地方。应用程序应该是无状态的,状态应该存在于数据服务中。
无状态的优势
服务如何与它们所代表的应用程序实例关联
集中式负载均衡器的优势:
客户端负载均衡是在客户端配置负载均衡器,这样http请求就会直接发送给服务的某个实例,减少了一次网络跳转,性能更好
服务的客户端如何发现和找到服务
应用程序向远程服务发出请求,如果在合理的时间内没有接收到响应,将再次尝试。
重试风暴:使用重试时,系统需要15分钟才能从3分钟的网络中断中恢复。如果不使用重试,则可以立即从3分钟的网络中断中恢复。
重试既会造成灾难性的影响,但是又有巨大的好处,尤其是在调用只是间歇性失败的情况下。
友好的客户端会:
安全的方法:一个安全的方法是指可以被调用零次或者多次,而且效果相同。该方法不应该有其他任何副作用。如GET、HEAD、OPTIONS和TRACE
幂等的方法:一个幂等的方法是指可以被调用一次或者多次,而且效果相同。它可以有副作用,但是所有重复调用的副作用必须与第一次相同。如,PUT
重试安全的方法,而不是重试幂等的方法
面向失败设计最基本的模式之一,是实现回退的方法,即当主逻辑失败时执行的代码。
控制循环永远不会期望达到完成的状态。它的目的就是不断地寻找不可避免的变化,并做出适当的响应。
如果服务开始出现故障但是次数不多,先停止该服务的所有流量一段时间,希望给它一段时间,让它能够从故障中恢复。过一段时间后,让单个请求通过,查看其运行情况。如果请求失败,则继续维持保护措施,不允许后续的流量通过。如果请求成功,看看则视为服务恢复正常,并允许流量通过。
断路器状态:打开、关闭、半开
API网关始终位于实现的最前面,并且提供了大量的服务。这些服务可能包括以下内容。
API网关模式的目标之一是将服务开发人员的关注问题与运维的关注问题分开。希望让后者能够统一控制正在运行的服务,并且为他们提供一个易于管理的控制平面。
简单来看,挎斗是一个与主服务一起运行的进程,或者是与主服务容器运行于同一个pod中的容器。
Kubernetes pod中运行的所有服务都可以托管在相同的IP地址上,这意味着它们可以通过localhost互相寻址,因此网络开销将变得很小。
请求/响应模式 转变为 事件驱动的方式
问题在于事件通知机制失败时,缓存中的数据可能是损坏的,而且可能会无限期的保持下去。解决方法为:
使用异步的消息传递系统,而不是直接发送消息给需要的微服务
事件消费者,规则1 尽可能让事件消费者的操作是幂等的。
Envoy在这些交互的边缘实现了许多模式,包括重试、断路器、限速、负载均衡、服务发现、可观察性等。
Istio的口号是“连接、安全、控制和观测服务”,支持自动注入挎斗并提供Envoy代理配置、证书处理和策略执行的组件。控制平面API提供了与此管理控制平面相关的接口。
3日志出现的位置应该完全由应用程序部署来控制,而不是由应用程序本身来决定。日志消息是出现在指定文件中还是出现在控制台中,或者出现在其他地方,都应该在部署时确定。
对云原生应用程序来说,该部署配置应该将日志行发送到stdout和stderr。这是因为:
日志需要进行大规模提取和存储,并且接口必须支持对这些海量数据的搜索和分析。ELK技术栈(详情参见链接46)汇集了三个开源项目—Elasticsearch、Logstash和Kibana—来满足这些要求。
在基于拉的方法中,度量指标聚合器会作为一个收集器,从每个应用程序实例中收集请求指标数据,并将这些指标存储在时序数据库中。问题在于, 有多个经过负载均衡的应用程序实例,你只能从其中一个实例获取指标,并且不知道具体是哪个实例。这。
基于推的模式,其中,每个应用实例负责按固定时间间隔将指标发给指标聚合器。一个挎斗代理不仅可以提供指标值,还可以代理应用程序发来的指标推送,因为即使应用程序没有安装代理(Agent),挎斗依然可以提供一定程度的可观察性。因为它代理了进出应用程序的流量,所以可以代替应用程序来生成许多指标
分布式跟踪解决的是如何跨多个分布式组件来跟踪程序流的问题。
Zipkin是当今使用的一种流行技术,核心包括以下几方面内容: