凤凰架构:构建可靠的大型分布式系统-读书思维导图
2024-11-21 14:10:08 0 举报
AI智能生成
《凤凰架构:构建可靠的大型分布式系统》是一本关于分布式系统设计的书籍,它详细介绍了构建可靠大型分布式系统的方法、原则和最佳实践。本书通过生动的案例和实用的建议,帮助读者深入了解分布式系统的各个方面,包括一致性、可用性、可扩展性、延迟和容错性等。此外,本书还探讨了分布式系统的架构模式、设计技巧和性能优化方法,以及如何应对各种挑战和问题。通过阅读本书,读者将能够掌握构建可靠大型分布式系统的核心知识和技能。 文件类型:书籍 修饰语:权威性,实用性,全面性,深度
作者其他创作
大纲/内容
1. 演进中的架构
(架构并不是被发明出来的,而是持续演进的结果)
(架构并不是被发明出来的,而是持续演进的结果)
原始的分布式时代
DCE(Distributed Computing Environment)的研究是计算机科学中第一次对分布式有组织领导、有标准可循、有巨大投入的尝试
DCE 提出的分布式服务的设计主旨:“让开发人员不必关心服务是远程还是本地,都能够透明地调用服务或者访问资源”
DCE或者后来出现的COBAR都是没有取得成功
将一个系统拆分到不同的机器上运行,所带来的的服务发现、跟踪、通信、容错、隔离、配置、传输、数据一致性和编码复杂度等方面复杂问题所付出的代价远大于分布式带来的收益
某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果——Kyle Brown
单体应用时代
单体应用又被称为巨石系统(Monolithic Application)
单体应用不应该被贴上"反派角色"的标签
单体系统的缺陷,只有满足以下两个方面才有讨论的价值
- 基于软件的性能需求超过了单机的性能
- 软件的开发人员规模明显超过了“2 Pizza Team”范畴的前提下才有讨论的价值
单体应用的缺陷:
- 隔离与自治能力上的欠缺
- 无法做到“停掉半个进程,重启 1/4 个程序”这样不合逻辑的操作
- 单体系统的技术栈异构能力较弱
微服务、单体架构哪种更好用、更优秀?笔者认为“好用和优秀”不会是放之四海皆准的
微服务超越单体应用成为主流的主要原因
单体系统很难兼容“Phoenix”的特性
构筑可靠系统从“追求尽量不出错”,到正视“出错是必然”的观念转变,才是微服务架构得以挑战并逐步开始取代运作了数十年的单体架构的底气所在
单体系统很难兼容“Phoenix”的特性
构筑可靠系统从“追求尽量不出错”,到正视“出错是必然”的观念转变,才是微服务架构得以挑战并逐步开始取代运作了数十年的单体架构的底气所在
SOA时代
面向服务的架构是一次具体地、系统性地成功解决分布式服务主要问题的架构模式。
烟囱式架构
微内核架构(插件形式存在)
事件驱动架构
微内核架构(插件形式存在)
事件驱动架构
企业服务总线(Enterprise Service Bus,ESB)的消息管道来实现各个子系统之间的通信交互,令各服务间在 ESB 调度下无须相互依赖却能相互通信,既带来了服务松耦合的好处,也为以后可以进一步实施业务流程编排(Business Process Management,BPM)提供了基础
SOAP 协议被逐渐边缘化的本质原因:过于严格的规范定义带来过度的复杂性。而构建在 SOAP 基础之上的 ESB、BPM、SCA、SDO 等诸多上层建筑,进一步加剧了这种复杂性
SOA 最终没有获得成功的致命伤与当年的EJB如出一辙,尽管有 Sun Microsystems 和 IBM 等一众巨头在背后力挺,EJB 仍然败于以 Spring、Hibernate 为代表的“草根框架”,可见一旦脱离人民群众,终究会淹没在群众的海洋之中,连信息技术也不曾例外过
SOA 最终没有获得成功的致命伤与当年的EJB如出一辙,尽管有 Sun Microsystems 和 IBM 等一众巨头在背后力挺,EJB 仍然败于以 Spring、Hibernate 为代表的“草根框架”,可见一旦脱离人民群众,终究会淹没在群众的海洋之中,连信息技术也不曾例外过
微服务时代
微服务是一种软件开发技术,是一种 SOA 的变体形式。
微服务真正的崛起是在 2014 年, Martin Fowler 与 James Lewis 合写的文章《Microservices: A Definition of This New Architectural Term》中首次了解到微服务的
微服务是一种通过多个小型服务组合来构建单个应用的架构风格
这些服务围绕业务能力而非特定的技术标准来构建。
各个服务可以采用不同的编程语言,不同的数据存储技术,运行在不同的进程之中。
服务采取轻量级的通信机制和自动化的部署机制实现通信与运维
这些服务围绕业务能力而非特定的技术标准来构建。
各个服务可以采用不同的编程语言,不同的数据存储技术,运行在不同的进程之中。
服务采取轻量级的通信机制和自动化的部署机制实现通信与运维
Thoughtworks 首席咨询师 James Lewis 做了题为《Microservices - Java, the Unix Way》的主题演讲,其中提到了单一服务职责、康威定律、自动扩展、领域驱动设计等原则
微服务的九个核心的业务与技术特征
围绕业务能力构建
分散治理
通过服务来实现独立自治的组件
产品化思维
数据去中心化
分布式中要处理好一致性的问题也相当困难,很多时候都没法使用传统的事务处理来保证,
但是两害相权取其轻,有一些必要的代价仍是值得付出的
但是两害相权取其轻,有一些必要的代价仍是值得付出的
强终端弱管道
如果服务需要上面的额外通信能力,就应该在服务自己的 Endpoint 上解决,而不是在通信管道上一揽子处理。
微服务提倡类似于经典 UNIX 过滤器那样简单直接的通信方式,RESTful 风格的通信在微服务中会是更加合适的选择
微服务提倡类似于经典 UNIX 过滤器那样简单直接的通信方式,RESTful 风格的通信在微服务中会是更加合适的选择
容错性设计
演进式设计
基础设施自动化
微服务时代充满着自由的气息,微服务时代充斥着迷茫的选择。软件架构不会止步于自由,微服务仍不是架构探索终点
如果有下一个时代,笔者希望是信息系统能同时拥有微服务的自由权利,围绕业务能力构建自己的服务而不受技术规范管束,但同时又不必以承担自行解决分布式的问题的责任为代价
如果有下一个时代,笔者希望是信息系统能同时拥有微服务的自由权利,围绕业务能力构建自己的服务而不受技术规范管束,但同时又不必以承担自行解决分布式的问题的责任为代价
后微服务时代
从软件层面独力应对微服务架构问题,发展到软、硬一体,合力应对架构问题的时代,此即为“后微服务时代”
DCE 中未能实现的“透明的分布式应用”成为可能,Martin Fowler 设想的“凤凰服务器“成为可能,Chad Fowler 提出的“不可变基础设施”也成为可能,从软件层面独力应对分布式架构所带来的各种问题,发展到应用代码与基础设施软、硬一体,合力应对架构问题的时代,现在常被媒体冠以“云原生”这个颇为抽象的名字加以宣传
Kubernetes 成为容器战争胜利者标志着后微服务时代的开端
单纯的 Kubernetes 反而不如之前的 Spring Cloud 方案。
单纯的 Kubernetes 反而不如之前的 Spring Cloud 方案。
- 这是因为有一些问题处于应用系统与基础设施的边缘,使得完全在基础设施层面中确实很难精细化地处理(如针对某个应用服务的熔断降级策略控制)
- 基础设施是针对整个容器来管理的,粒度相对粗旷,只能到容器层面,对单个远程服务就很难有效管控
虚拟化的基础设施很快完成了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的“边车代理模式”(Sidecar Proxy)
服务网格将会成为微服务之间通信交互的主流模式,把“选择什么通信协议”、“怎样调度流量”、“如何认证授权”之类的技术问题隔离于程序代码之外
这才是最理想的Smart Endpoints解决方案
服务网格将会成为微服务之间通信交互的主流模式,把“选择什么通信协议”、“怎样调度流量”、“如何认证授权”之类的技术问题隔离于程序代码之外
这才是最理想的Smart Endpoints解决方案
无服务时代
如果说微服务架构是分布式系统这条路的极致,那无服务架构,也许就是“不分布式”的云端系统这条路的起点。
涉及内容
后端设施(BaaS)
函数(FaaS)
挑战
FaaS服务的冷启动问题
应用场景无法全覆盖的缺陷(息管理系统、网络游戏等应用,又或者说所有具有业务逻辑复杂,依赖服务端状态,响应速度要求较高,需要长链接等这些特征的应用)
软件开发的最大挑战就在于只能在不完备的信息下决定当前要处理的问题。时至今日,依然很难预想在架构演进之路的前方,微服务和无服务之后还会出现何种形式的架构风格
尽管目光所及之处,只是不远的前方,即使如此,依然可以看到那里有许多值得去完成的工作在等待我们——图灵
2. 架构师视角
远程访问服务(RPC)
进程间通信
传递方法参数
确定方法版本
执行被调方法
返回执行结果
三个基本问题
如何表示数据
如何传递数据
如何确定方法
本地调用方法中:将调用的方法签名转换为进程空间中子过程入口位置的指针
DCE 定义的接口描述语言(Interface Description Language,IDL),成为此后许多 RPC 参考或依赖的基础(如 CORBA 的 OMG IDL)
统一的 RPC
CORBA
Web Service
分裂的 RPC
RMI
Thrift
Dubbo
gRPC
Motan
Finagle
brpc
Arvo
JSON-RPC 2.0
方向
面向对象
性能
简单
REST设计风格
理解REST
资源(Resource)
表征(Representation)
状态(State)
转移(Transfer)
RESTful 的系统
服务端与客户端分离(Client-Server)
由于前端的日渐强势,现在还流行起由前端代码反过来驱动服务端进行渲染的 SSR(Server-Side Rendering)技术
,在 Serverless、SEO 等场景中已经占领了一块领地
,在 Serverless、SEO 等场景中已经占领了一块领地
无状态(Stateless)
大型系统的上下文状态数量完全可能膨胀到让客户端在每次请求时提供变得不切实际
可缓存(Cacheability)
使用无状态的设计则可能会需要多次请求,或者在请求中带有额外冗余的信息。
为了缓解这个矛盾,REST 希望软件系统能够如同万维网一样,允许客户端和中间的通讯传递者(譬如代理)将部分服务端的应答缓存起来
为了缓解这个矛盾,REST 希望软件系统能够如同万维网一样,允许客户端和中间的通讯传递者(譬如代理)将部分服务端的应答缓存起来
分层系统(Layered System)
统一接口(Uniform Interface)
Fielding 建议系统应能做到每次请求中都包含资源的 ID,所有操作均通过资源 ID 来进行;建议每个资源都应该是自描述的消息;建议通过超文本来驱动应用状态的转移
按需代码(Code-On-Demand)
以前,人们面向方法去设计 RPC API,譬如 CORBA 和 DCOM,随着时间推移,
接口与方法越来越多却又各不相同,开发人员必须了解每一个方法才能正确使用它们,这样既耗时又容易出错
接口与方法越来越多却又各不相同,开发人员必须了解每一个方法才能正确使用它们,这样既耗时又容易出错
RMM 成熟度
不足与争议
面向资源的编程思想只适合做 CRUD,面向过程、面向对象编程才能处理真正复杂的业务逻辑
解决方案:
自定义方法,或者抽象业务
POST /user/user_id/cart/book_id:undelete
解决方案:
自定义方法,或者抽象业务
POST /user/user_id/cart/book_id:undelete
REST 与 HTTP 完全绑定,不适合应用于要求高性能传输的场景中
锤子不能当扳手用并不是锤子的质量有问题。面向资源编程与协议无关,但是 REST的确依赖着 HTTP 协议的标准方法、状态码、协议头等各个方面
锤子不能当扳手用并不是锤子的质量有问题。面向资源编程与协议无关,但是 REST的确依赖着 HTTP 协议的标准方法、状态码、协议头等各个方面
REST 不利于事务支持
REST 没有传输可靠性支持
HTTP 协议要求 GET、PUT 和 DELETE 应具有幂等性,我们把 REST 服务映射到这些方法时,也应当保证幂等性
REST 缺乏对资源进行“部分”和“批量”的处理能力
缺陷的本质是由于 HTTP 协议完全没有对请求资源的结构化描述能力(但有非结构化的部分内容获取能力,即今天多用于断点续传的Range Header)
GraphQL
事务处理
本地事务
ARIES理论
(Algorithms for Recovery and Isolation Exploiting Semantics,ARIES),直接翻译过来是“基于语义的恢复与隔离算法
事务描述
原子性
隔离性
持久性
原子性和持久性
Commit Logging
oceanbase
缺陷
所有对数据的真实修改都必须发生在事务提交以后,即日志写入了 Commit Record 之后
NO-FORCE
NO-STEAL
Write-Ahead Logging
WAL解决commit logging的缺陷
NO-FORCE
NO-STEAL
日志写入时机
FORCE
STEAL
通过undolog实现
崩溃恢复时会执行以下三个阶段
分析阶段(Analysis)
找出所有没有 End Record 的事务,组成待恢复的事务集合,
这个集合至少会包括 Transaction Table 和 Dirty Page Table 两个组成部分
这个集合至少会包括 Transaction Table 和 Dirty Page Table 两个组成部分
重做阶段(Redo)
找出所有包含 Commit Record 的日志,将这些日志修改的数据写入磁盘,
写入完成后在日志中增加一条 End Record,然后移除出待恢复事务集合
写入完成后在日志中增加一条 End Record,然后移除出待恢复事务集合
回滚阶段(Undo)
它们被称为 Loser,根据 Undo Log 中的信息,
将已经提前写入磁盘的信息重新改写回去,以达到回滚这些 Loser 事务的目的
将已经提前写入磁盘的信息重新改写回去,以达到回滚这些 Loser 事务的目的
隔离
现代数据库提供的锁机制
写锁
也叫做排它锁
如果数据有加写锁,就只有持有写锁的事务才能对数据进行写入操作,数据加持着写锁时,其他事务不能写入数据,也不能施加读锁
如果数据有加写锁,就只有持有写锁的事务才能对数据进行写入操作,数据加持着写锁时,其他事务不能写入数据,也不能施加读锁
读锁
也叫做共享锁,多个事务可以对同一个数据添加多个读锁,数据被加上读锁后就不能再被加上写锁
对于持有读锁的事务,如果该数据只有它自己一个事务加了读锁,允许直接将其升级为写锁,然后写入数据
对于持有读锁的事务,如果该数据只有它自己一个事务加了读锁,允许直接将其升级为写锁,然后写入数据
范围锁
对于某个范围直接加排他锁,在这个范围内的数据不能被写入
隔离级别
串行化
可重复度
幻读
读已提交
不可重复读
读未提交
脏读
不同隔离级别以及幻读、不可重复读、脏读等问题都只是表面现象,是各种锁在不同加锁时间上组合应用所产生的结果,以锁为手段来实现隔离性才是数据库表现出不同隔离级别的根本原因
MVCC
解决
一个事务读+另一个事务写”的隔离问题
一个事务读+另一个事务写”的隔离问题
MVCC 是一种读取优化策略,它的“无锁”是特指读取时不需要加锁
控制原理
:CREATE_VERSION 和 DELETE_VERSION,这两个字段记录的值都是事务 ID,事务 ID 是一个全局严格递增的数值
插入数据时:CREATE_VERSION 记录插入数据的事务 ID,DELETE_VERSION 为空
删除数据时:DELETE_VERSION 记录删除数据的事务 ID,CREATE_VERSION 为空
修改数据时:将修改数据视为“删除旧数据,插入新数据”的组合,即先将原有数据复制一份,原有数据的 DELETE_VERSION 记录修改数据的事务 ID,CREATE_VERSION 为空。复制出来的新数据的 CREATE_VERSION 记录修改数据的事务 ID,DELETE_VERSION 为空
读取机制
隔离级别是可重复读:总是读取 CREATE_VERSION 小于或等于当前事务 ID 的记录,在这个前提下,如果数据仍有多个版本,则取最新(事务 ID 最大)的
隔离级别是读已提交:总是取最新的版本即可,即最近被 Commit 的那个版本的数据记录
读未提交不涉及
串行化不涉及
MVCC只是针对读+写的场景做的性能优化,无法解决写+写的场景,写+写的场景还是需要通过锁进行解决
悲观加锁
乐观加锁
全局事务
全局的事务管理器
局部的资源管理器
XA
两阶段提交
准备阶段
准备操作是在重做日志中记录全部事务提交操作所要做的内容,它与本地事务中真正提交的区别只是暂不写入最后一条 Commit Record 而已,这意味着在做完数据持久化后并不立即释放隔离性,即仍继续持有锁,维持数据对其他非事务内观察者的隔离状态
提交阶段
协调者如果在上一阶段收到所有事务参与者回复的 Prepared 消息,则先自己在本地持久化事务状态为 Commit
在此操作完成后向所有参与者发送 Commit 指令,所有参与者立即执行提交操作;否则,任意一个参与者回复了 Non-Prepared 消息,或任意一个参与者超时未回复,协调者将自己的事务状态持久化为 Abort 之后,向所有参与者发送 Abort 指令,参与者立即执行回滚操作
时序图
问题
单点问题
性能问题
两次远程调用
三次数据持久化
一致性风险
三阶段提交
过程
准备
CanCommit
PreCommit
提交
解决的问题
单点问题和准备阶段的性能问题
单点问题和回滚时的性能问题有所改善,但是它对一致性风险问题并未有任何改进
进入 PreCommit 阶段之后,协调者发出的指令不是 Ack 而是 Abort,而此时因网络问题,有部分参与者直至超时都未能收到协调者的 Abort 指令的话,这些参与者将会错误地提交事务
共享事务
理论可行
新增一个“交易服务器”的中间角色
消息队列
该方案是与实际生产系统中的压力方向相悖
执行方式
交易服务器根据不同服务节点传来的同一个事务 ID,使用同一个数据库连接来处理跨越多个服务的交易事务
分布式事务
CAP
一致性
可用性
可靠性
平均无故障时间(Mean Time Between Failure,MTBF)来度量
可维护性
可维护性使用平均可修复时间(Mean Time To Repair,MTTR)来度量
A=MTBF/(MTBF+MTTR)
分区容忍性
常见的存储系统所使用的的CAP模型
CA
Oracle 的 RAC 集群为例,它的每一个节点均有自己独立的 SGA、重做日志、回滚日志等部件,但各个节点是通过共享存储中的同一份数据文件和控制文件来获取数据的
CP
HBase
zookeeper
AP
Redis
eureka
事务分类
刚性事务(ACID)
柔性事务
定义
BASE 分别是基本可用性(Basically Available)、柔性事务(Soft State)和最终一致性(Eventually Consistent)的缩写
可靠事件队列
最大努力交付
重试
幂等
TCC事务
解决可靠事件队列的无隔离性问题
业务侵入式较强
裸编码来实现
SAGA 事务
一种提升“长时间事务”(Long Lived Transaction)运作效率的方法
设计思路
大事务拆分若干个小事务
基于数据补偿来代替回滚
为每一个子事务设计对应的补偿动作,命名为 C1,C2,…,Ci,…,Cn
Ti与 Ci都具备幂等性。
Ti与 Ci满足交换律(Commutative),即先执行 Ti还是先执行 Ci,其效果都是一样的。
Ci必须能成功提交,即不考虑 Ci本身提交失败被回滚的情形,如出现就必须持续重试直至成功,或者要人工介入
恢复策略
正向恢复
T1,T2,…,Ti(失败),Ti(重试)…,Ti+1,…,Tn
反向恢复
T1,T2,…,Ti(失败),Ci(补偿),…,C2,C1
SAGA 系统本身也有可能会崩溃,所以它必须设计成与数据库类似的日志机制(被称为 SAGA Log)以保证系统恢复后可以追踪到子事务的执行情况
应用
GTS(Global Transaction Service,Seata 由 GTS 开源而来)所提出的“AT 事务模式就是saga模式的应用
AT 事务默认的隔离级别是读未提交(Read Uncommitted)
GTS 增加了一个“全局锁”(Global Lock)的机制来实现写隔离,解决脏读的问题
透明多级分流系统
系统流量规划原则
1. 尽可能减少单点部件,
2. 若无法避免,则应尽最大限度减少到达单点部件的流量
2. 若无法避免,则应尽最大限度减少到达单点部件的流量
奥卡姆剃刀原则
最简单的系统就是最好的系统
根据系统的用户量、峰值流量和团队本身的技术与运维能力来考虑如何部署
客户端缓存
强制缓存
Expires
服务端返回给客户端的response header中的值
承诺该资源在指定的时间内不会改变,客户端可以进行缓存
承诺该资源在指定的时间内不会改变,客户端可以进行缓存
弊端
受限于客户端本地的时间,客户端可以进行修改
无法描述不缓存语义
之前的处理方法-加时间戳,强制获取最新的资源
Cache-Control
max-age
多长时间内请求资源可以不从服务端获取
s-maxage
cdn和代理持有的资源最大缓存时间
public
可以代理层,cdn进行缓存
private
只能由客户端缓存
no-cache
资源不允许被缓存
no-store
不强制相同的url需要重复获取即缓存强制失效
但禁止客户端或者CDN保存该资源
但禁止客户端或者CDN保存该资源
no-transform
禁止修改资源
某些 CDN、透明代理支持自动 GZip 压缩图片或文本,以提升网络性能,而 no-transform 就禁止了这样的行为
某些 CDN、透明代理支持自动 GZip 压缩图片或文本,以提升网络性能,而 no-transform 就禁止了这样的行为
min-fresh和only-if-cached
应用于客户端请求header
告诉服务端返回一个不小于该时间不变的资源
only-if-cached 表示客户端要求服务端不必返回资源
使用客户端原来的缓存,如果缓存不能命中就返回503
使用客户端原来的缓存,如果缓存不能命中就返回503
must-revalidate
must-revalidate 表示在资源过期后,一定需要从服务器中进行获取
proxy-revalidate
proxy-revalidate 用于提示代理、CDN 等设备
协商缓存
基于变化的检测机制
协商缓存有两种变动检查机制
1. 根据资源的修改时间进行检查,
2. 根据资源唯一标识是否发生变化来进行检查
1. 根据资源的修改时间进行检查,
2. 根据资源唯一标识是否发生变化来进行检查
Last-Modified
服务器端的响应header
If-Modified-Since
客户端再次请求的时候会带上
服务端会做比较,如果发现在上一修改的时间段到该时间段资源没有修改
返回304 Not Modified,否则返回 200
返回304 Not Modified,否则返回 200
ETag和If-None-Match
譬如 Apache 服务器的 Etag 值默认是对文件的索引节点(INode),大小和最后修改时间进行哈希计算后得到的
对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-None-Match 把之前收到的资源唯一标识发送回服务端
服务端对资源进行hash计算比较,如果相同返回 304 ,否则返回200
服务端对资源进行hash计算比较,如果相同返回 304 ,否则返回200
Etag 是 HTTP 中一致性最强的缓存机制
Etag 却又是 HTTP 中性能最差的缓存机制
一个 URL 地址是有可能能够提供多份不同版本的资源
Vary Header 进行解决
生效场景
协商缓存不仅在浏览器的地址输入、页面链接跳转、新开窗口、前进、后退中生效,而且在用户主动刷新页面(F5)时也同样是生效的,
只有用户强制刷新(Ctrl+F5)或者明确禁用缓存(譬如在 DevTools 中设定)时才会失效
只有用户强制刷新(Ctrl+F5)或者明确禁用缓存(譬如在 DevTools 中设定)时才会失效
域名解析
DNS服务器
权威域名服务器
根域名服务器
13组根域名服务器
查找流程
以点号从后往前,一级一级权威域名服务器查找
DoH
解决的问题
一层一层查找带来的每一层的安全防护问题
执行
将原本的 DNS 解析服务开放为一个基于 HTTPS 协议的查询服务,替代基于 UDP 传输协议的 DNS 域名解析,通过程序代替操作系统直接从权威 DNS 或者可靠的 Local DNS 获取解析数据,从而绕过传统 Local DNS
传输链路
前端设计原则
雅虎 YSlow-23 条规则
Minimize HTTP Requests
Split Components Across Domains
GZip Components
Avoid Redirects
Put Stylesheets at the Top,Put Scripts at the Bottom
连接数优化
程序员ticks
Minimize HTTP Requests
Split Components Across Domains
问题
合并异步请求,性能下降
无畏的资源浪费
两害相权取其轻
Keep-Alive 机制
典型做法是在客户端维护一个 FIFO 队列
问题
队首阻塞
HTTP/2 发布后才算是被比较完美地解决队首阻塞的问题
HTTP2的多路复用技术
把http的最小粒度request细化到Frame(帧)
每个帧都附带一个流 ID 以标识这个帧属于哪个流
突破浏览器对每个域名最多 6 个连接数限制
传输压缩
GZip
静态预压缩
即时压缩
问题
在 HTTP/1.0通过请求 Header 中明确给出资源的长度,
传输到达该长度即宣告一个资源的传输已结束,
如果采用即时压缩就会导致content-length和实际报文的长度不匹配
传输到达该长度即宣告一个资源的传输已结束,
如果采用即时压缩就会导致content-length和实际报文的长度不匹配
HTTP1.1通过分块传输编码来处理
实现机制和netty中的固定结束符差不多
每个分块包含十六进制的长度值和对应长度的数据内容
每个分块包含十六进制的长度值和对应长度的数据内容
HTTP/2,由于多路复用和单域名单连接的设计,不需要考虑该问题
快速 UDP 网络连接
HTTP/3 协议的设计重点
解决现阶段在应用层解决的问题(持久连接、多路复用、分块编码这些能力),放到传输层解决
2015 年,Google 将 QUIC 提交给 IETF,并在 IETF 的推动下对 QUIC 进行重新规范化,使其不仅能满足 HTTP 传输协议,日后还能支持 SMTP、DNS、SSH、Telnet、NTP 等多种其他上层协议。2018 年末,IETF 正式批准了 HTTP over QUIC 使用 HTTP/3 的版本号,将其确立为最新一代的互联网标准
内容分发网络(cdn)
网络层面影响系统访问速度的因素
1. 网络服务器接入网络运营商链路提供出来的的出口的带宽
2. 用户客户端接入网络运营商提供出来的入口的贷款
3. 网站到用户之间经过的不同运营商之间互联节点的带宽
4. 网站到用户之间的物理链路传输时延
上文中1,3,4都可以通过内容分发网络来显著改善
工作过程
路由解析
子主题
内容分发
分类
服务端主动推送(push)
被动回溯(pull)
多数cdn服务商采用的方式
操作方式
最常见的做法是超时被动失效与手工主动失效相结合
cdn应用
加速静态资源
安全防御
协议升级
状态缓存
修改资源
访问控制
注入功能
负载均衡
负载均衡
四层负载均衡
数据链路层负载均衡
网络层负载均衡
七层负载均衡
应用层负载均衡
代理
正向代理
反向代理
透明代理
应用场景
不适合做下载站、视频站这种流量应用
可以感知应用层通信的具体内容,往往能够做出更明智的决策,玩出更多的花样来
七层均衡器全都可以实现,譬如静态资源缓存、协议升级、安全防护、访问控制,等等
七层均衡器可以实现更智能化的路由
某些安全攻击可以由七层均衡器来抵御,譬如一种常见的 DDoS 手段是 SYN Flood 攻击
服务降级、熔断、异常注入
真实服务器、负载均衡器、客户端三者之间由两条独立的 TCP 通道来维持通信
均衡策略
轮循均衡(Round Robin)
权重轮循均衡(Weighted Round Robin)
随机均衡(Random)
权重随机均衡(Weighted Random)
一致性哈希均衡(Consistency Hash)
响应速度均衡(Response Time)
最少连接数均衡(Least Connection)
硬件均衡
F5
A10
服务端缓存
概述
缓存虽然是典型以空间换时间来提升性能的手段
顺带而不是专门缓解服务端的压力,如果服务端能通过硬件升级cpu和扩容,优先硬件升级
缓存属性
吞吐量
OPS
命中率
FIFO
LRU
LFU
问题:如果一些热点数据在系统中经常被频繁访问,但最近一段时间因为某种原因未被访问过,此时这些热点数据依然要面临淘汰的命运
通过LinkedHashMap实现
TinyLFU
从计数器改为采用Sketch(统计学里面的概念,少量样本解决大量数据的问题,如布隆过滤器的实现)
基于时间窗口,解决LFU中“旧热点”数据难以清除的问题
W-TinyLFU
解决TinyLFU 在一些瞬时任务场景下难以通过Sketch解决的场景
结合了 LRU 和 LFU 两者的优点。window cache+main cache
扩展功能
最大容量
失效时间
失效事件
命中率统计
并发级别
持久化
分布式支持
加载器
进程内缓存框架对比
分布式缓存
复制式缓存
JBossCache
Infinispan
集中式缓存
redis
Memcached
分布式缓存集群是否能保证数据一致
AP
Redis
CP
ZooKeeper、Doozerd、Etcd 等分布式协调框架,通常不会有人将它们当为“缓存框架”来使用
透明多级缓存(Transparent Multilevel Cache,TMC)
风险
缓存穿透
对于业务逻辑本身就不能避免的缓存穿透(如判断是否存在,大部分数据都是不能存在的)可以约定在一定时间内对返回为空的 Key 值依然进行缓存
如果后续业务在数据库中对该 Key 值插入了新记录,那应当在插入之后主动清理掉缓存的 Key 值
如果后续业务在数据库中对该 Key 值插入了新记录,那应当在插入之后主动清理掉缓存的 Key 值
对于恶意攻击导致的缓存穿透,通常会在缓存之前设置一个布隆过滤器来解决
缓存击穿(针对单个key)
加锁同步,以请求该数据的 Key 值为锁,使得只有第一个请求可以流入到真实的数据源中,其他线程采取阻塞或重试策略
热点数据由代码来手动管理
缓存雪崩(大批key)
提升缓存系统可用性,建设分布式缓存的集群
启用透明多级缓存
将缓存的生存期从固定时间改为一个时间段内的随机时间
缓存污染
缓存数据和数据源数据一致性问题
尽可能使用缓存时的一致性
Cache Aside(成本最低)
读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求
写数据时,先写数据源,然后失效(而不是更新)掉缓存
问题:不能保证在一致性上绝对不出问题的,否则就无须设计出Paxos这样复杂的共识算法了
不一致场景:如果某个数据是从未被缓存过的,请求会直接流到真实数据源中,如果数据源中的写操作发生在查询请求之后,结果回填到缓存之前
Read/Write Through
Write Behind Caching
架构安全性
设计原则
标准规范为指导
标准接口去实现
安全问题上不重复造轮子
认证
标准
Client-Cert
Basic
Digest
Form
应用场景
通信信道上的认证
通信协议上的认证
通信内容上的认证
基于协议和内容的认证方式
HTTP 认证
设计思想
认证方案
认证内容
Http Basic
Digest
Bearer
HOBA
CA证书
OBC自签名证书
自扩展认证方案
AWS4-HMAC-SHA256
Twitter Basic
Web 认证
OAuth
WebAuthn
子主题
认证的实现
框架
Apache Shiro
Spring Security
实现
认证功能
安全上下文
授权功能
子主题
授权
RBAC
OAuth2
原则:面向于解决第三方应用的认证授权协议
模式
授权码模式
隐式授权模式
密码模式
客户端模式
设备码模式
凭证
定义
准确,完成,不可抵赖
模式
cookie-session
问题
牺牲集群的一致性(Consistency)
牺牲集群的可用性(Availability)
牺牲集群的分区容忍性(Partition Tolerance)
JWT
解决的问题
分布式环境下CAP 不可兼得的问题
客户端的 Cookie 也没法跨域
一种令牌格式
JWT 只解决防篡改的问题,并不解决防泄漏的问题,因此令牌默认是不加密的
组成
令牌头(Header)
负载(Payload)
RFC 7519 中推荐(非强制约束)了七项声明名称(Claim Name)
iss(Issuer):签发人
exp(Expiration Time):令牌过期时间
sub(Subject):主题
aud (Audience):令牌受众
nbf (Not Before):令牌生效时间
iat (Issued At):令牌签发时间
jti (JWT ID):令牌编号
签名(Signature)
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
授权服务与资源服务分离
非对称加密算法来进行签名
缺点
令牌难以主动失效
黑名单机制,把主动失效的令牌收集起来
相对更容易遭受重放攻击
解决方案有,但是代价大
全局序列号
Nonce 字符串
挑战应答码
只能携带相当有限的数据
header
tomcat 8k
nginx 4k
无状态也不总是好的
在线用户实时统计功能难实现
保密
保密的强度
摘要代替明文
不能防彩虹表破解
加盐
动态盐
不能防止重发攻击
动态令牌
https
存储证书
客户端加密
密码存储和验证
password = 123456
客户端进行哈希摘要(client_hash = MD5(password))
防御彩虹表攻击应加盐处理(client_hash = MD5(MD5(password) + salt))
不建议使用动态盐,建议使用慢哈希函数(BCrypt)
client_hash = BCrypt(MD5(password) + salt)
防御拖库,增加随机的盐值
SecureRandom random = new SecureRandom();
byte server_salt[] = new byte[36];
random.nextBytes(server_salt);
byte server_salt[] = new byte[36];
random.nextBytes(server_salt);
服务端基于客户端的hash再做一次hash
实践(BcryptPasswordEncoder)
传输
摘要
signature = SHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
哈希算法评估标准
易变性
长度固定
不可变性
加密
加密是可逆的
安全靠机密性来保证
破解的计算复杂度
质因数分解
秘钥
对称
非对称
公钥加密,私钥解密,这种就是加密
混合加密(密码学套件)
非对称加密秘钥,对称做报文
解决的问题
非对称加密性能差
明文长度有限
蛋鸡悖论
密码学算法
哈希摘要
MD2/4/5/6、SHA0/1/256/512
无法解密
对称加密
DES、AES、RC4、IDEA
要解决如何把密钥安全地传递给解密者。
非对称加密
RSA、BCDSA、ElGamal
性能与加密明文长度受限。
签名
私钥加密,公钥解密
结合摘要与非对称加密的优点,以对摘要结果做加密的形式来保证签名的适用性
背书作用,只要内容进行了修改,摘要就会发生变化,签名就会失效
数字证书
签名过程中的漏洞:公钥虽然是公开的在网络传输层就有可能被拦截和篡改。
现实实现方式
基于共同私密信息的信任
基于权威公证人的信任
网络层通过下面的数字签名避免这样的漏洞
公开密钥基础设施(PKI)
传输安全层
传输层和应用层中间的通用处理方式(tls)
握手传递内容
随机产生的密钥
对称加密算法
压缩算法
验证
系统如何确保提交到每项服务中的数据是合乎规则的,不会对系统稳定性、数据一致性、正确性产生风险
数据验证与程序如何编码是密切相关
推荐做法
对于无业务含义的格式验证,可以做到预置
JSR 标准 Java Bean Validation
有业务含义的业务验证,可以做到重用
Bean Validation
避免对输入数据的防御污染到业务代码
编码建议
对校验项预置好默认的提示信息
不带业务含义的格式校验注解放到 Bean 的类定义之上
需要触发一部分校验——通过分组标记执行
3. 分布式的基石
分布式共识算法
系统稳定标准
可靠性
可靠不一定是可用的
可靠是一种最终状态,通过999的可用性,保证了系统的可靠性
可靠性的方式有多重
可用性
同步方式
全同步复制
状态转移
牺牲可用性
操作转移
状态机复制
令源状态转换为目标状态。能够使用确定的操作 在计算机科学中被称为状态机(State Machine)
少数服从多数”的原则 (Quorum 机制)
演变成一种协商共识算法
Paxos
开拓了分布式共识算法的发展思路,不会直接用于实践
算法流程
提案节点(Proposer)
提出对某个值进行设置操作的节点
决策节点(Acceptor)
进行投票的节点
记录节点(Learner)
不参与提案和决策,如少数派的节点恢复时候的状态就是该状态
算法机制
加锁就不完全等同于并发控制中以互斥量来实现的加锁,还必须提供一个其他节点能抢占锁的机制,以避免因通信问题而出现死锁
锁必须是可抢占的
两阶段
Prepare
Accept
两个承诺
不会接受提案id<=n的prepare请求
不会接受天id<n的Accept请求
一个应答
不违背以前作出的承诺的前提下,回复已经批准过的提案中 ID 最大的那个提案所设定的值和提案 ID,如果该值从来没有被任何提案设定过,则返回空值。如果违反此前做出的承诺,即收到的提案 ID 并不是决策节点收到过的最大的,那允许直接对此 Prepare 请求不予理会
操作时序
工作实例
背景
分布式系统有五个节点,分别命名为 S1、S2、S3、S4、S5
两个并发的请求分别希望将同一个值分别设定为 X(由 S1作为提案节点提出)和 Y(由 S5作为提案节点提出)
以 P 代表准备阶段,以 A 代表批准阶段
流程
缺陷
只能对单个值形成决议
决议的形成至少需要两次网络请求和应答(准备和批准阶段各一次),高并发情况下将产生较大的网络开销,极端情况下甚至可能形成活锁
Multi Paxos
解决的问题
活锁问题与许多 Basic Paxos 异常场景中所遭遇的麻烦
改进点
增加选主过程
提案节点会通过定时轮询(心跳)寻找有一个主提案节点
没有的话就会在心跳超时后使用 Basic Paxos 中定义的准备、批准的两轮网络交互过程,向所有其他节点广播自己希望竞选主节点的请求
角色转换 ,主。从代替原来的提案,决策。记录节点
时序
主从机制下的协商共识时序
分布式系统中如何对某个值达成一致”这个问题总结为解决如下三个问题
如何选主(Leader Election)
譬如心跳、随机超时、并行竞选,等等
如何把数据复制到各个节点上(Entity Replication)
当客户端向主节点发起一个操作请求,譬如提出“将某个值设置为 X”,此时主节点将 X 写入自己的变更日志,但先不提交,接着把变更 X 的信息在下一次心跳包中广播给所有的从节点,并要求从节点回复确认收到的消息,从节点收到信息后,将操作写入自己的变更日志,然后给主节点发送确认签收的消息,主节点收到过半数的签收消息后,提交自己的变更、应答客户端并且给从节点广播可以提交的消息,从节点收到提交消息后提交自己的变更
如何保证过程是安全的(Safety)
Safety 保证了选主的结果一定是有且只有唯一的一个主节点,不可能同时出现两个主节点
Liveness 则要保证选主过程是一定可以在某个时刻能够结束的。由前面对活锁的介绍可以得知,在 Liveness 这个属性上选主问题是存在理论上的瑕疵的,可能会由于活锁而导致一直无法选出明确的主节点,所以 Raft 论文中只写了对 Safety 的保证
等价派生
Raft
Etcd
LogCabin
ZAB
zookeeper
gossip
应用
比特币
Xerox公司(施乐公司)
RPC的提出者
MVC的提出者
执行过程
固定周期(1s)随机选择它相连接的k个节点
每一个节点收到消息后,如果这个消息是它之前没有收到过的,将在下一个周期内,选择除了发送消息给它的那个节点外的其他相邻 k 个节点发送相同的消息,直到最终网络中所有节点都收到了消息,尽管这个过程需要一定时间,但是理论上最终网络的所有节点都会拥有相同的消息
缺陷
无法评估完成时间
消息的冗余,增加网络传输的压力
解决
反熵(Anti-Entropy)
去除差异化,同步节点的全部数据
传谣(Rumor-Mongering)
以传播消息为目标,只对外发送变更信息,这样消息数据量将显著缩减
从类库到服务
解决的问题
从进程内演变成进程外调用
需要做的事情
服务发现
服务的网关路由
服务的负载均衡
服务发现
如何在基础设施和网络协议层面,对应用尽可能无感知、方便地实现服务发现是目前服务发现的一个主要发展方向
可用与可靠
服务的注册
服务的维护
服务的发现
注册中心的高可用
Eureka 的选择是优先保证高可用性 AP
Consul 的选择是优先保证高可靠性 CP
Raft算法实现数据的同步+Gossip支持多数据中心之间更大规模的服务同步
ZK CP
注册中心实现
在分布式 K/V 存储框架上自己开发的服务发现
ZooKeeper
ZAB
Doozerd
Etcd
Raft
以基础设施(主要是指 DNS 服务器)来实现服务发现(以基础设施来做服务发现,好处是对应用透明,任何语言、框架、工具都肯定是支持 HTTP、DNS 的,所以完全不受程序技术选型的约束,但坏处是透明的并不一定是简单的)
SkyDNS
CoreDNS
专门用于服务发现的框架和工具
Eureka
Consul
Nacos
网关路由
职责
路由器
过滤器
能力衡量标准
支持的协议类型
四次网络协议
七层网络协议
无法直接进行流量转发,只能采用代理模式
可用性
DSR 三角传输模式,原理上就决定了性能一定会比代理模式来的强
REST 和 JSON-RPC 等基于 HTTP 协议无法直接进行流量转发,只能采用代理模式
网络I/O模型
异步 I/O
你在美团外卖订了个盒饭,付款之后你自己该干嘛还干嘛去,饭做好了骑手自然会到门口打电话通知你
异步 I/O 中数据到达缓冲区后,不需要由调用进程主动进行从缓冲区复制数据的操作,而是复制完成后由操作系统向线程发送信号
同步I/O
阻塞 I/O
你去到饭堂,发现饭还没做好,你也干不了别的,只能打个瞌睡(线程休眠),直到饭做好,这就是被阻塞了
阻塞 I/O 是最直观的 I/O 模型,逻辑清晰,也比较节省 CPU 资源,但缺点就是线程休眠所带来的上下文切换
非阻塞 I/O
你去到饭堂,发现饭还没做好,你就回去了,然后每隔 3 分钟来一次饭堂看饭做好了没,直到饭做好
非阻塞 I/O 能够避免线程休眠,对于一些很快就能返回结果的请求,非阻塞 I/O 可以节省切换上下文切换的消耗,但是对于较长时间才能返回的请求,非阻塞 I/O 反而白白浪费了 CPU 资源
多路复用 I/O
代表整个宿舍去饭堂打饭,去到饭堂,发现饭还没做好,还是继续打瞌睡,但哪个舍友的饭好了,你就马上把那份饭送回去
多路复用 I/O 是目前的高并发网络应用的主流
实现
select
epoll
kqueue
信号驱动 I/O
信号驱动 I/O 与异步 I/O 的区别是“从缓冲区获取数据”这个步骤的处理
前者收到的通知是可以开始进行复制操作了,即要你自己从饭堂拿回宿舍,在复制完成之前线程处于阻塞状态,所以它仍属于同步 I/O 操作,而后者收到的通知是复制操作已经完成
Zuul
1.0
阻塞I/O
后端I/O密集型比较消耗网关的cpu。阻塞的睡眠唤醒需要做线程切换
后端是CPU密集型,比较介绍网关的cpu消耗
2.0
netty多模型
Nginx Ingress Controller
KONG
性能对比
做定性分析,不好做定量分析
BFF 网关
客户端负载均衡
客户端负载均衡器
代理负载均衡器
Netflix Ribbon
Spring Cloud Load Balancer
Service Mesh
代理均衡器对此前的客户端负载均衡器的改进是将原本嵌入在服务进程中的均衡器提取出来,作为一个进程之外,同一 Pod 之内的特殊服务,放到边车代理中去实现
增加的消耗
从进程内通信到通过网络协议栈进行数据交换,要进行打包拆包、计算校验和、维护序列号等网络数据的收发步骤,流量比起之前的客户端均衡器确实多增加了一系列处理步骤
优化处理
Kubernetes 严格保证了同一个 Pod 中的容器不会跨越不同的节点,这些容器共享着同一个网络名称空间,因此代理均衡器与服务实例的交互,实质上是对本机回环设备的访问,仍然要比真正的网络交互高效且稳定得多
优点
代理均衡器不再受编程语言的限制
在服务拓扑感知方面代理均衡器也要更有优势
在安全性、可观测性上,由于边车代理都是一致的实现,有利于在服务间建立双向 TLS 通信
地域与区域
地域
Region
地域没有内网相连
集群内部流量是不会跨地域的
只能做容灾
区域
Zone
地理上位于同一地域内,电力和网络是互相独立的物理区域
有内网连接,流量不占用公网带宽,因此区域是微服务集群内流量能够触及的最大范围
可以做容灾做双活
概念解释
容灾是非实时的同步
双活是实时或者准实时的
选择策略
追求高可用
结合容灾及双活以及zone和region的特性,做“两地三中心”模式
追求低延迟
系统的所有服务都只部署在同一个区域中
结合容灾及双活以及zone和region的特性
流量治理
服务容错
为什么要有容错,有些技术可以妥协,为什么容错在分布式系统中不能妥协
分布式系统的本质是系统是不可靠的
容错策略
故障转移(Failover)
快速失败(Failfast)
非幂等操作
安全失败(Failsafe)
旁路业务报错
沉默失败(Failsilent)
错误服务隔离开来
故障恢复(Failback)
补偿
并行调用(Forking)
双保险-下游处理控制
广播调用(Broadcast)
缓存刷新
容错策略优缺点
容错设计模式
断路器模式
状态机
CLOSED
OPEN
HALF OPEN
子主题
应用的框架
Netflix Hystrix
Resilience4j
Envoy
属于服务容错中快速失败策略
概念
服务降级
对熔断后的业务操作属于降级
服务熔断
举例
你女朋友有事想召唤你,打你手机没人接,响了几声气冲冲地挂断后(快速失败),又打了你另外三个不同朋友的手机号(故障转移),都还是没能找到你(重试超过阈值)。这时候她生气地在微信上给你留言“三分钟不回电话就分手”,以此来与你取得联系。在这个不是太吉利的故事里,女朋友给你留言这个行为便是服务降级逻辑
舱壁隔离模式
概念介绍
主流的网络访问大多是基于 TPR 并发模型(Thread per Request)来实现的
执行
使用局部的线程池来控制服务的最大连接数
缺点
因为涉及到线程的上线文切换,所以设计到cpu消耗
启用 Hystrix 线程池来进行服务隔离,大概会为每次服务调用增加约 3 毫秒至 10 毫秒的延时
解决方案
信号量机制(Semaphore)进行计数
按用户登记进行访问控制及隔离
属于服务容错中的静默失败策略
重试模式
属于服务容错中的故障转移和故障恢复策略
使用场景
适合解决系统中的瞬时故障
简单的说就是有可能自己恢复(Resilient,称为自愈,也叫做回弹性)的临时性失灵,网络抖动、服务的临时过载(典型的如返回了 503 Bad Gateway 错误)这些都属于瞬时故障
需要考虑业务场景
仅在主路逻辑的关键服务上进行同步的重试
仅对由瞬时故障导致的失败进行重试
仅对具备幂等性的服务进行重试
POST 非幂等
get,HEAD、OPTIONS,TRACE,put,delete 幂等
重试必须有明确的终止条件
超时时间
重试次数
方向
启发式搜索的结果来自动变更容错策略和参数
流量控制
流量统计指标
每秒事务数(Transactions per Second,TPS)
每秒请求数(Hits per Second,HPS)
如果某个业务操作只有一步就能完成那么TPS=HPS
每秒查询数(Queries per Second,QPS)
如果在分布式系统中只有一台服务器那么 QPS=HPS
限流标准
基于io密集型还是cpu密集型系统做不同的限流策略
限流设计模式
流量计数器模式
缺陷
只是针对时间点进行离散的统计
滑动时间窗模式
缺陷
只适用于否决式限流,超过阈值的流量就必须强制失败或降级,很难进行阻塞等待处理,也就很难在细粒度上对流量曲线进行整形,起不到削峰填谷的作用
漏桶模式
参数
桶的大小
水的流出速率
类比场景
小学阶段的奇怪的蓄水池
令牌桶模式
类比场景
银行办事时摆在门口的那台排队机
参数
限制系统在 X 秒内最大请求次数不超过 Y,那就每间隔 X/Y 时间就往桶中放一个令牌
令牌桶同样有最大容量
分布式限流
集中式存储统计信息
方案
Reids
缺点增加了网络通信消耗
令牌桶基础上做货币化改造
公式:LimitN = QuanityA - ∑NCostX
基于额度的限流方案对限流的精确度有一定的影响
他是并发性能和限流效果上都相对折衷可行的分布式限流方案
可靠通讯
支持跨语言,跨框架的服务调用
零信任网络
基于边界的安全模型
VPN、DMZ、防火墙、内网、外网
问题
边界上的防御措施即使自身能做到永远滴水不漏牢不可破,也很难保证内网中它所尽力保护的某一台服务器不会成为“猪队友”
一旦“可信的”网络区域中的某台服务器被攻陷,那边界安全措施就成了马其诺防线,攻击者很快就能以一台机器为跳板,侵入到整个内网
一旦“可信的”网络区域中的某台服务器被攻陷,那边界安全措施就成了马其诺防线,攻击者很快就能以一台机器为跳板,侵入到整个内网
零信任安全模型的特征
不应当以某种固有特征来自动信任任何流量
传统网络安全模型与云原生时代零信任模型对比
特征
零信任网络不等同于放弃在边界上的保护设施
身份只来源于服务
服务之间也没有固有的信任关系
集中、共享的安全策略实施点
受信的机器运行来源已知的代码
自动化、标准化的变更管理
强隔离性的工作负载
Google 的实践探索
Google Front End(名字意为“最终用户访问请求的终点”)的边缘代理,负责保证此后所有流量都在 TLS 之上传输,并自动将流量路由到适合的可用区域之中
为了强制身份只来源于服务,设计了名为 Application Layer Transport Security(应用层传输安全)的服务认证机制
为了确保服务间不再有默认的信任关系,设计了 Service Access Policy(服务访问策略)
为了实现仅以受信的机器运行来源已知的代码,设计了名为 Binary Authorization(二进制授权)的部署时检查机制
为了工作负载能够具有强隔离性,设计了名为gVisor的轻量级虚拟化方案
服务安全
建立信任
单向 TLS 认证
它保护的重点是客户端免遭冒牌服务器的欺骗
双向 TLS 认证
除了保护客户端不连接到冒牌服务器外,也保护服务端不遭到非法用户的越权访问
认证
服务认证
Spring Cloud
OAtuh2 协议
Istio
mTLS
用户认证
Istio
JWKS
Spring Cloud
Spring Security
授权
Istio
基于命名空间控制
Spring Cloud
Spring Security
可观测性
日志
ELK和EFK
追踪
SkyWalking、Zipkin、Jaeger
Datadog,AWS X-Ray,AWS X-Ray
度量
Prometheus
事件日志
日志输出建议
不要少
处理请求时的 TraceID
系统运行过程中的关键事件
启动时输出配置信息
不应该有
避免打印敏感信息
避免引用慢操作
避免打印追踪诊断信息
避免误导他人
收集与缓冲
Kafka
Beats
加工与聚合
LogStash
Fluentd
存储与查询
ElasticSearch
链路追踪
产品
ZipKin
Pinpoint
SkyWlaking
CAT
追踪与跨度
Trace
Span
挑战
低性能损耗
对应用透明
随应用扩缩
持续的监控
数据收集
基于日志的追踪
基于服务的追踪
SkyWalking
轻量
Zipkin
生态
Pinpoint
HBase
基于边车代理的追踪
Envoy 和 Sleuth 一样都属于狭义的追踪系统,需要配合专门的 UI 与存储来使用
追踪规范化
OpenTracing
Zipkin、Jaeger、SkyWalking
OpenCensus
OpenTelemetry=OpenTracing和OpenCensus合并
聚合度量
度量(Metrics)
Prometheus
指标收集
指标
计数度量器(Counter)
瞬态度量器(Gauge)
吞吐率度量器(Meter)
采样点分位图度量器(Quantile Summary)
方式
拉取式采集(pull)
推送式采集(push)
协议支持
Exporter 以 HTTP 协议(Prometheus 在 2.0 版本之前支持过 Protocol Buffer,目前已不再支持)返回符合 Prometheus 格式要求的文本数据给 Prometheus 服务器
规范
OpenMetrics
存储查询
时序数据库
InfluxDB
轮替型数据库(RRD)
Prometheus内部实现的时序数据库
时序数据库的使用的业务场景
数据只是追加,很少删改
LSM-Tree代替B+树
写多读少
存储、访问和保留策略(Retention Policies)
监控预警
Alert Manager
Grafana
4.不可变基础设施
从微服务到云原生
虚拟化容器
软件兼容性
ISA兼容
ABI兼容
环境兼容
虚拟化技术
指令集虚拟化(ISA Level Virtualization)
QEMU
硬件抽象层虚拟化(Hardware Abstraction Level Virtualization)
Hyper-V
操作系统层虚拟化(OS Level Virtualization)
容器化
运行库虚拟化(Library Level Virtualization)
WINE
语言层虚拟化(Programming Language Level Virtualization)
Java
容器的崛起
隔离文件:chroot
Linux Kernel 2.3.41 版内核引入了pivot_root技术来实现文件隔离
隔离访问:namespaces
对文件、进程、用户、网络等各类信息的访问,都被囊括在 Linux 的名称空间中
syslog就还没被隔离
syslog就还没被隔离
隔离资源:cgroups
用于隔离或者说分配并限制某个进程组能够使用的资源配额
封装系统:LXC
LXC 眼中的容器的定义与 OpenVZ 和 Linux-VServer 并无差别,是一种封装系统的轻量级虚拟
而 Docker 眼中的容器的定义则是一种封装应用的技术手段
封装应用:Docker
价值
跨机器的绿色部署
以应用为中心的封装
自动构建
多版本支持
组件重用
共享
工具生态
发展
Docker Swarm 却输掉了容器编排战争
封装集群:Kubernetes
以 Kubernetes 为代表的容器编排框架,就是把大型软件系统运行所依赖的集群环境也进行了虚拟化,令集群得以实现跨数据中心的绿色部署,并能够根据实际情况自动扩缩
云原生(2003 Pivotal 持有着 (Spring Framework 和 Cloud Foundry 的公司)提出)
备韧性(Resilience)
弹性(Elasticity
可观测性(Observability)
在K8横空问世之前只能由架构师和程序员高超的个人能力
Kubernetes 以摧枯拉朽之势覆灭了容器编排领域的其他竞争对手
Kubernetes 从 1.10 版本宣布开始支持 containerd 1.1
Kubernetes Master → kubelet → KubeGenericRuntimeManager → containerd → runC
这又意味着用户只要愿意抛弃掉 Docker 情怀的话,在容器编排上便可至少省略一次 HTTP 调用,获得性能上的收益
在容器编排领域,未来的 Docker 很可能只会以 runC 和 containerd 的形式存续下去,毕竟它们最初都源于 Docker 的血脉
以容器构建系统
以封装应用为中心发展到以封装系统为中心”的 LXC发展到封装集群
目标
编排不是仅仅是通过高速网络把容器连接起来
容器调度
扩容
只有恰当解决了上述问题,云原生应用才有可能获得比传统应用更高的生产力
隔离与协作
同一个容器里面启动两个进程
Docker 提倡的单个容器封装单进程应用的最佳实践。Docker 设计的 Dockerfile 只允许有一个 ENTRYPOINT,这并非无故添加的人为限制,而是因为 Docker 只能通过监视 PID 为 1 的进程(即由 ENTRYPOINT 启动的进程)的运行状态来判断容器的工作状态是否正常
可以使用supervisord之类的进程控制器来解决同时启动 Nginx 和 Filebeat 进程的问题
多个容器做资源共享
Volume
confd
docker run 提供了--ipc参数
Kubernetes
容器(Container):延续了自 Docker 以来一个容器封装一个应用进程的理念,是镜像管理的最小单位
生产任务(Pod):补充了容器化后缺失的与进程组对应的“容器组”的概念,Pod 中容器共享 UTS、IPC、网络等名称空间,是资源调度的最小单位
节点(Node):对应于集群中的单台机器,这里的机器即可以是生产环境中的物理机,也可以是云计算环境中的虚拟节点,节点是处理器和内存等资源的资源池,是硬件单元的最小单位
集群(Cluster):对应于整个集群,Kubernetes 提倡理念是面向集群来管理应用。当你要部署应用的时候,只需要通过声明式 API 将你的意图写成一份元数据(Manifests)
集群联邦(Federation):对应于多个集群,通过联邦可以统一管理多个 Kubernetes 集群,联邦的一种常见应用是支持跨可用区域多活、跨地域容灾的需求
韧性与弹性
服务容错
让编排系统在这些服务出现问题,运行状态不正确的时候,能自动将它们调整成正确的状态。这种需求听起来也是贪心的,却已经具备足够的可行性,应对的解决办法在工业控制系统里已经有非常成熟的应用,叫作控制回路(Control Loop)。
k8s的回路控制
部署控制器(Deployment Controller)
副本集控制器(ReplicaSet Controller)
自动扩缩控制器(HPA Controller)
编排最终的实现目标
Pod 出现故障时,能够自动恢复,不中断服务;
Pod 更新程序时,能够滚动更新,不中断服务;
Pod 遇到压力时,能够水平扩展,不中断服务
k8s的解决方案
deployment的滚动发布
遇到压力时候的自动扩容 autoscaling
Autoscaling→Deployment→ReplicaSet→Pod
子主题
总结:套娃式发展
chroot,namespaces,cgroups提供了隔离能力-》系统级虚拟化技术使得同一台机器上互不干扰地运行多个服务成为可能
LXC 它是 namespaces、cgroups 特性的上层封装,使得“容器”一词真正走出实验室,走入工业界
Docker 的出现实现跨机器的软件绿色部署
Kubernetes-》为了满足大型系统对服务集群化的需要它(最初)是 Docker 的上层封装,让以多个容器共同协作构建出健壮的分布式系统,成为今天云原生时代的技术基础设施
以应用为中心的封装
Kustomize
Kustomize 使用Kustomization 文件来组织与应用相关的所有资源,Kustomization 本身也是一个以 YAML 格式编写的配置文件,里面定义了构成应用的全部资源,以及资源中需根据情况被覆盖的变量值
Helm 与 Chart
目标
Helm 一开始的目标就很明确:如果说 Kubernetes 是云原生操作系统的话,那 Helm 就要成为这个操作系统上面的应用商店与包管理工具
缺点
Helm 无法很好地管理这种有状态的依赖关系,这一类问题就是 Operator 要解决的痛点了
Operator 与 CRD
定义
Operator不应当被称作是一种工具或者系统,它应该算是一种封装、部署和管理 Kubernetes 应用的方法,尤其是针对最复杂的有状态应用去封装运维能力的解决方案
设计理念
Operator 是使用自定义资源(CR,笔者注:CR 即 Custom Resource,是 CRD 的实例)管理应用及其组件的自定义 Kubernetes 控制器。高级配置和设置由用户在 CR 中提供。Kubernetes Operator 基于嵌入在 Operator 逻辑中的最佳实践将高级指令转换为低级操作。Kubernetes Operator 监视 CR 类型并采取特定于应用的操作,确保当前状态与该资源的理想状态相符
有状态和无状态
状态应用(Stateful Application)
分布式系统中多数关键的基础服务都是有状态的,如缓存、数据库、对象存储、消息队列
无状态应用(Stateless Application)
只要资源足够,天生高可用
无状态应用在分布式系统中具有非常巨大的价值
程序每次运行都和有第一次运行一样,不依赖之前运行遗留的痕迹
StatefulSet
Pod 会按顺序创建和按顺序销毁
Pod 具有稳定的网络名称
Pod 具有稳定的持久存储
Operator 将简洁的高级指令转化为 Kubernetes 中具体操作的方法
Operator 变成了近两、三年容器封装应用的一股新潮流,现在很多复杂分布式系统都有了官方或者第三方提供的 Operator(这里收集了一部分)
开放应用模型
解决的痛点
开放应用模型思想的核心是如何将开发人员、运维人员与平台人员关注点分离,开发人员关注业务逻辑的实现,运维人员关注程序平稳运行,平台人员关注基础设施的能力与稳定性,长期让几个角色厮混在同一个 All-in-One 资源文件里,并不能擦出什么火花,反而将配置工作弄得越来越复杂,将“YAML Engineer”弄成了容器界的嘲讽梗
定义
一个Application由一组Components构成,每个Component的运行状态由Workload描述,每个Component可以施加Traits来获取额外的运维能力,同时我们可以使用Application Scopes将Components划分到一或者多个应用边界中,便于统一做配置、限制、管理。把Components、Traits和Scopes组合在一起实例化部署,形成具体的Application Configuration,以便解决应用的多实例部署与升级
概念抽象
服务组件(Components)
Component 不仅仅是特指构成应用“整体”的一个“部分”,它还有一个重要职责是抽象那些应该由开发人员去关注的元素
工作负荷(Workload)
Workload 决定了应用的运行模式,每个 Component 都要设定自己的 Workload 类型,OAM 按照“是否可访问、是否可复制、是否长期运行”预定义了六种 Workload 类型
运维特征(Traits)
OAM 的 Traits 就用于封装模块化后的运维能力,可以针对运维中的可重复操作预先设定好一些具体的 Traits,譬如日志收集 Trait、负载均衡 Trait、水平扩缩容 Trait
应用边界(Application Scopes)
一个 Component 也可能属于多个 Scope
多个 Component 共同组成一个 Scope
多个 Component 共同组成一个 Scope
应用配置(Application Configuration)
运行模式
总结
今天容器圈的发展是一日千里,各种新规范、新技术层出不穷
实际应用时往往会联合其中多个工具一起使用。应该如何封装应用才是最佳的实践,目前尚且没有定论,但是以应用为中心的理念却已经成为明确的共识
实际应用时往往会联合其中多个工具一起使用。应该如何封装应用才是最佳的实践,目前尚且没有定论,但是以应用为中心的理念却已经成为明确的共识
容器间网络
讨论方向
基于 Linux 系统的网络虚拟化技术来实现的容器间网络通信
Linux 网络虚拟化
网络通信模型
职责分配
Socket
应用层的程序是通过 Socket 编程接口来和内核空间的网络协议栈通信的
TCP/UDP
传输层协议族里最重要的协议无疑是传输控制协议(Transmission Control Protocol,TCP)和用户数据报协议(User Datagram Protocol,UDP)两种,
IP
网络层协议最主要就是网际协议(Internet Protocol,IP),其他还有因特网组管理协议(Internet Group Management Protocol,IGMP)、大量的路由协议(EGP、NHRP、OSPF、IGRP、……)等等
Device
网络设备(Device)是网络访问层中面向系统一侧的接口,这里所说的设备与物理硬件设备并不是同一个概念,Device 只是一种向操作系统端开放的接口
Driver
网卡驱动程序(Driver)是网络访问层中面向硬件一侧的接口
执行时序
程序发送数据做的是层层封包,加入协议头,传给下一层;接受数据则是层层解包,提取协议体,传给上一层,你可以类比来理解数据包接收过程
干预网络通信
PREROUTING
进入 IP 路由之前触发
一般用于目标网络地址转换(Destination NAT,DNAT)
INPUT
报文经过 IP 路由后,如果确定是发往本机的,将会触发此钩子
一般用于加工发往本地进程的数据包
FORWARD
报文经过 IP 路由后,如果确定不是发往本机的,将会触发此钩子
一般用于处理转发到其他机器的数据包
OUTPUT
从本机程序发出的数据包,在经过 IP 路由前,将会触发此钩子
一般用于加工本地进程的输出数据包
POSTROUTING
从本机网卡出去的数据包,无论是本机的程序所发出的,还是由本机转发给其他机器的
一般用于源网络地址转换(Source NAT,SNAT)
NetFilter
应用场景
iptables
DROP:直接将数据包丢弃
REJECT:给客户端返回 Connection Refused 或 Destination Unreachable 报文
QUEUE:将数据包放入用户空间的队列,供用户空间的程序处理
RETURN:跳出当前链,该链里后续的规则不再执行
ACCEPT:同意数据包通过,继续执行后续的规则
JUMP:跳转到其他用户自定义的链继续执行
REDIRECT:在本机做端口映射
MASQUERADE:地址伪装,自动用修改源或目标的 IP 地址来做 NAT
LOG:在/var/log/messages 文件中记录日志信息
虚拟化网络设备
网卡:tun/tap、veth
tun 和 tap 是两个相对独立的虚拟网络设备
tap 模拟了以太网设备,操作二层数据包(以太帧)
tun 则模拟了网络层设备,操作三层数据包(IP 报文)
veth 是另一种主流的虚拟网卡方案
相当于由交叉网线连接的一对物理网卡
概念
交叉网线是指一头是 T568A 标准,另外一头是 T568B 标准的网线
直连网线则是两头采用同一种标准的网线
容器间的网络传输应用
两个容器之间采用 veth 通信不需要反复多次经过网络协议栈,这让 veth 比起 tap/tun 具有更好的性能
多个容器间通信
每个容器都为与它通信的其他容器建立一对专用的 veth pair 并不实际,这时就迫切需要有一台虚拟化的交换机来解决多容器之间的通信问题了
交换机:Linux Bridge
网络:VXLAN
副本网卡:MACVLAN
容器间通信
docker network ls命令查看到这三种网络
桥接模式
Docker 会为新容器分配独立的网络名称空间,创建好 veth pair,一端接入容器,另一端接入到 docker0 网桥上。Docker 为每个容器自动分配好 IP 地址,默认配置下地址范围是 172.17.0.0/24,docker0 的地址默认是 172.17.0.1,并且设置所有容器的网关均为 docker0,这样所有接入同一个网桥内的容器直接依靠二层网络来通信,在此范围之外的容器、主机就必须通过网关来访问
使用--network=bridge指定
主机模式
使用--network=host指定
容器也就不会拥有自己独立的 IP 地址。此模式下与外界通信无须进行 NAT 转换,没有性能损耗,但缺点也十分明显,没有隔离就无法避免网络资源的冲突,譬如端口号就不允许重复
空置模式
使用--network=none指定
此时容器能看到的只有一个回环设备(Loopback Device)而已。提供这种方式是为了方便用户去做自定义的网络配置,如自己增加网络设备、自己管理 IP 地址,等等
容器模式
使用--network=container:容器名称指定
共享一切的网络资源,但其他资源,如文件、PID 等默认仍然是隔离的。两个容器间可以直接使用回环地址(localhost)通信,端口号等网络资源不能有冲突
MACVLAN 模式
使用docker network create -d macvlan创建
在追求通信性能的场合,这种网络是最好的选择。Docker 的 MACVLAN 只支持 Bridge 通信模式,因此在功能表现上与桥接模式相类似
Overlay 模式
使用docker network create -d overlay创建
这种网络模式主要用于 Docker Swarm 服务之间进行通信。然而由于 Docker Swarm 败于 Kubernetes,并未成为主流,所以这种网络模式实际很少使用
容器网络与生态
CNM 规范容器网络的先行者,对后续的容器网络标准制定有直接的指导意义
CNI
概念
kubenet是 kubelet 内置的一个非常简单的网络,采用网桥来解决 Pod 间通信。kubenet 会自动创建一个名为 cbr0 的网桥,当有新的 Pod 启动时,会由 kubenet 自动将其接入 cbr0 网桥中,再将控制权交还给 kubelet,完成后续的 Pod 创建流程
网络插件生态
Overlay 模式
路由模式
Underlay 模式
模式测试对比
MACVLAN 和 SR-IOV 这样的 Underlay 网络插件的吞吐量最高、延迟最低
持久化存储
Kubernetes 存储设计
Mount 和 Volume
Docker 内建支持了三种挂载类型
Bind(--mount type=bind)
docker run --mount type=bind,source=/icyfenix/html,destination=/usr/share/nginx/html nginx:latest
Volume(--mount type=volume)
提升 Docker 对不同存储介质的支撑能力
tmpfs(--mount type=tmpfs)
静态存储分配
动态存储分配
增加了资源分配器(Provisioner)这一角色
StorageClass
自动地在存储资源池或者云存储系统中分配符合用户存储需要的 PersistentVolume,然后挂载到 Pod 中使用
容器存储与生态
Kubernetes 存储架构
PV 控制器(PersistentVolume Controller)
AD 控制器(Attach/Detach Controller)
Volume 管理器(Volume Manager)
FlexVolume 与 CSI
FlexVolume 驱动其实就是一个实现了 Attach、Detach、Mount、Unmount 操作的可执行文件(甚至可以仅仅是个 Shell 脚本)而已
FlexVolume的缺陷
FlexVolume 并不是全功能的驱动:FlexVolume 不包含 Provision 和 Delete 操作,也就无法直接用于 Dynamic Provisioning
FlexVolume 部署维护都相对繁琐
FlexVolume 实现复杂交互也相对繁琐
CSI 可算是一个十分完善的存储扩展规范
需要容器系统去实现的组件
需要存储提供商去实现的组件
组件接口规范
CSI Identity 接口
CSI Controller 接口
CSI Node 接口
从 In-Tree 到 Out-of-Tree
local
awsElasticBlockStore
容器插件生态
存储类型
块存储
性能最优
有排它性,一旦块设备被某个客户端挂载后,其它客户端就无法再访问上面的数据
Kubernetes 中挂载的块存储大多访问模式都要求必须是 RWO(ReadWriteOnce)的
文件存储
“文件”这个概念的出现是因为“块”对人类用户来说实在是过于难以使用、难以管理
文件系统
更高层次的块存储类型,加入目录、权限等元素后形成的树状结构以及路径访问方式方便了人类理解
对象储存
元数据及与其配对的一个逻辑数据块的组合
对象存储基本上只会在分布式存储系统之上去实现,由于对象存储天生就有明确的“元数据”概念
多次网络传输,延迟方面就会表现得相对较差
最佳实践
亚马逊的块存储服务是Amazon Elastic Block Store(AWS EBS)
适合追求磁盘 I/O 的大型工作负载以及追求低时延的应用,譬如 Oracle 等可以直接访问块设备的大型数据库更是尤其合适。但 EBS 只允许被单个节点挂载,难以共享,这点在单机时代是天经地义
亚马逊的文件存储服务是Amazon Elastic File System(AWS EFS)
EFS 能够轻易地被成百上千个 EC2 实例共享,考虑到 EFS 的性能、动态弹性、可共享这些因素,笔者给出的明确建议是它可以作为大部分容器工作负载的首选存储
亚马逊的对象存储服务是Amazon Simple Storage Service(AWS S3)
simple
不必写一行代码就能够直接通过 HTTP Endpoint 进行读写访问,且完全不需要考虑容量、维护和数据丢失的风险
不必写一行代码就能够直接通过 HTTP Endpoint 进行读写访问,且完全不需要考虑容量、维护和数据丢失的风险
价格低
性能是上述中最差的
资源与调度
Node 是资源的提供者,Pod 是资源的使用者,调度是将两者进行恰当的撮合
资源模型
Kubernetes 以资源为载体,建立了一套同时囊括了抽象元素(如策略、依赖、权限)和物理元素(如软件、硬件、网络)的领域特定语言
Node 通常能够提供的三方面的资源:计算资源(如处理器、图形处理器、内存)、存储资源(如磁盘容量、不同类型的介质)和网络资源(如带宽、网络地址
可压缩资源
CPU
不可压缩资源
内存
服务质量与优先级
服务质量等级(Quality of Service Level,QoS Level)
Guaranteed
Burstable
BestEffort
Pod 的优先级
驱逐机制
通过kubelet实现
软驱逐(Soft Eviction)
通常配置一个较低的警戒线
硬驱逐(Hard Eviction)
通常配置一个较高的终止线,红线
优雅退出期(Grace Period)
默认调度器
运行
Predicate 的筛选算法
恰当
Priority 的评价算法
schedule loop 策略
通用过滤策略
NodePort 是否存在冲突
卷过滤策略
Volume 的可用区域
节点过滤策略
污点与容忍度机制
Scheduler Framework 暴露的接口来进行扩展和自定义
服务网格
透明通信的涅槃
SOA、微服务、云原生
通信的成本
第一阶段:将通信的非功能性需求视作业务需求的一部分,通信的可靠性由程序员来保障
第二阶段:将代码中的通信功能抽离重构成公共组件库,通信的可靠性由专业的平台程序员来保障
Spring Cloud
Finagle
第三阶段:将负责通信的公共组件库分离到进程之外,程序间通过网络代理来交互,通信的可靠性由专门的网络代理提供商来保障
Netflix Prana
第四阶段:将网络代理以边车的形式注入到应用容器,自动劫持应用的网络流量,通信的可靠性由专门的通信基础设施来保障
第五阶段:将边车代理统一管控起来实现安全、可控、可观测的通信,将数据平面与控制平面分离开来,实现通用、透明的通信,这项工作就由专门的服务网格框架来保障
servicemesh
数据平面
数据平面
envoy
控制平面
Istio
代理注入
基座模式(Chassis)
注入模式(Injector)
手动注入模式
自动注入模式
流量劫持
基于 iptables 进行的数据转发
eBPF(Extended Berkeley Packet Filter)技术,在 Socket 层面直接完成数据转发,而不需要再往下经过更底层的 TCP/IP 协议栈的处理,从而减少数据在通信链路的路径长度
可靠通信
Listener
Listener 可以简单理解为 Envoy 的一个监听端口,用于接收来自下游应用程序(Downstream)的数据
Cluster
Cluster 是 Envoy 能够连接到的一组逻辑上提供相同服务的上游(Upstream)主机。Cluster 包含该服务的连接池、超时时间、Endpoints 地址、端口、类型等信息
Router
服务网关
控制平面
1.5 版本起,Istio 重新回归单体架构,将 Pilot、Galley、Citadel 的功能全部集成到新的 Istiod 之中
职责
数据平面交互
边车注入
策略分发
配置分发
流量控制
请求路由
流量治理
调试能力
故障注入和流量镜像等功能
通信安全
生成 CA 证书
SDS服务代理
认证
授权
子主题
可观测性
服务网格与生态
标准的诞生可以说是每一项技术普及之路中都必须经历的“成人礼”
服务网格接口
Service Mesh Interface 规范
流量规范(Traffic Specs)
流量拆分(Traffic Split)
流量度量(Traffic Metrics)
流量访问控制(Traffic Access Control)
通用数据平面 API
UDAP
服务网格生态
数据平面
Linkerd
Envoy
nginMesh
Conduit/Linkerd 2
MOSN
控制平面
Linkerd 2
Istio
Consul Connect
OSM
5.技术方法论
向微服务迈进
目的:微服务的驱动力
微服务的目的是有效的拆分应用,实现敏捷开发和部署
能够通过扩展硬件的手段解决问题就尽量别使用复杂的软件方法
硬件的成本能够持续稳定地下降,而软件开发的成本则不可能
选择标准
外部
当意识到没有什么技术能够包打天下
合适的语言做合适的事
当个人能力因素成为系统发展的明显制约
在一些不发达的城市表现尤为突出
在一些不发达的城市表现尤为突出
在单体架构下,没有什么有效阻断错误传播的手段
当遇到来自外部商业层面对内部技术层面提出的要求
内部
变化发展特别快的创新业务系统往往会自主地向微服务架构靠近
大规模的、业务复杂的、历史包袱沉重的系统也可能主动向微服务架构靠近
前提:微服务需要的条件
决策者与执行者都能意识到康威定律在软件设计中的关键作用
沟通决定设计
如果技术层面紧密联系在一起的特性,在组织层面上强行分离开来,那结果会是沟通成本的上升,因为会产生大量的跨组织的沟通;如果技术层面本身没什么联系的特性,在组织层面上强行安放在一块,那结果会是管理成本的上升
所有的技术上的决策实际都是政治上的决策
组织中具备一些对微服务有充分理解、有一定实践经验的技术专家
马太效应
系统应具有以自治为目标的自动化与监控度量能力
环境预置(Rapid Provisioning)
基础监控(Basic Monitoring)
快速部署(Rapid Application Deployment)
复杂性已经成为制约生产力的主要矛盾
长期来看,多数服务的结局都是报废而非演进。-Martin Fowler
边界:微服务的粒度
勿行极端,过犹不及
对本节的话题“识别微服务的边界”其实已取得了较为一致的观点,也找到了指导具体实践的方法论,即领域驱动设计(Domain-Driven Design,DDD)
微服务的下界
至少应满足独立——能够独立发布、独立部署、独立运行与独立测试,内聚——强相关的功能与数据在同一个服务中处理,完备——一个服务包含至少一项业务实体与对应的完整操作
“2 Pizza Team”作为微服务团队规模的“量词”-6到12人
团队粒度
微服务粒度的上界是一个 2 Pizza Team 能够在一个研发周期内完成的全部需求范围
治理:理解系统复杂性
治理就是让产品能够符合预期地稳定运行,并能够持续保持在一定的质量水平上
静态的治理
复杂性的认知判断
认知负荷
在软件研发中表现为人接受业务、概念、模型、设计、接口、代码等信息所带来的负担大小。系统中个体的认知负担越大,系统就越复杂,这点解释了为什么蚂蚁族群和国家的人口可能一样多,但治理国家比治理一群蚂蚁要更复杂
协作成本
在软件研发中表现为团队共同研发时付出的沟通、管理成本高低。系统个体间协作的成本越高,系统就越复杂,这点解释了为什么小饭馆和国家的构成个体都同样是人类,但治理国家比治理一家饭馆要更复杂
发展的治理
架构腐化只能延缓,无法避免
解决架构腐化的方案是演进式的设计
户枢不蠹,流水不腐
敏锐地捕捉到生产力的变化,随时调整生产关系,这才是架构师治理复杂性的终极方法
0 条评论
下一页