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