导图社区 微服务系统理论
微服务系统理论涵盖微服务方面的基础理论知识,内容丰富,都是经过几年时间的学习和整理,希望对你有所帮助
编辑于2021-11-19 16:29:31微服务系统理论
高并发利器
缓存
提升系统访问速度和增大系统处理容量。
降级
当服务出现问题获取影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开。
限流
通过对并发访问/请求进行限速,一旦达到限制速率则可以拒绝服务、排队等待等处理。
基本理论
其他
一致性协议
是否允许数据分歧可以分为两种
核心区别在于是否允许多个节点发起写操作,单主协议只允许由主节点发起写操作,因此它可以保证操作有序性,一致性更强。而多主协议允许多个节点发起写操作,因此它不能保证操作的有序性,只能做到弱一致性。
单主协议(否)
整个分布式系统就像一个单体系统,所有写操作都由主节点处理并且同步给其他副本。例如主备同步、2PC、Paxos 都属于这类协议。
多主协议(是)
所有写操作可以由不同节点发起,并且同步给其他副本。例如 Gossip、POW
主备复制
主备复制要求所有的写操作都在主节点上进行,然后将操作的日志发送给其他副本。可以发现由于主备复制是有延迟的,所以它实现的是最终一致性 实现方式: 主节点处理完写操作之后立即返回结果给客户端,写操作的日志异步同步给其他副本。这样的好处是性能高,客户端不需要等待数据同步,缺点是如果主节点同步数据给副本之前数据缺失了,那么这些数据就永久丢失了。MySQL 的主备同步就是典型的异步复制
垂直扩展和水平扩展区别
垂直扩展:提升单机处理能力。垂直扩展的方式又有两种: (1)增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G; (2)提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间; 在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。 不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。 水平扩展: 只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的
平台
SaaS
压力测试
Jmeter
日志
技术体系
Spring cloud Alibaba
Sentinel
流量控制、熔断降级、系统负载保护
Nacos
动态服务发现、配置管理和服务管理平台
RocketMQ
Dubbo
RPC
Seata
高性能微服务分布式事务解决方案
ACM
在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品
OSS
阿里云对象存储服务(Object Storage Service,简称 OSS)
SchedulerX
阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务
SMS
覆盖全球的短信服务
Spring cloud Netflix
2018-12-12 Netflix 宣布停止开发Spring Cloud的相关服务:Ribbon、Feign、Eureka、Hystrix,继续使用风险自负
Netflix Zuul(网关)
Netflix Eureka(注册中心)
Netflix Ribbon(负载均衡)
Netflix Hystrix(断路器)
Hystrix Dashboard(监控面板)
Netflix Ribbon(Config)
Turbine(监控聚合)
hystrix只能实现单个微服务的监控,可是一般项目中是微服务是以集群的形式搭建,一个一个的监控不现实。而Turbine的原理是,建立一个turbine服务,并注册到eureka中,并发现eureka上的hystrix服务。通过配置turbine会自动收集所需hystrix的监控信息,最后通过dashboard展现,以达到集群监控的效果。
Feign(远程调用)
分布式锁
数据库
缓存
Redisson
单机
集群
zookeeper
api版本管理
URL加版本
自定义请求头
技术元素
服务追踪(链路跟踪)
sleuth
zipkin
负载均衡
Ribbon
Ribbon
服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
容错保护
Hystrix
通过Hystrix的线程池发起请求
不同的服务对应不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
远程调用
Feign
基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
组合案例
案例
分布式系统关注点
分布式
高并发
高性能
高可用
可扩展
松耦合
高内聚
可复用
系统边界
安全
CAP理论
对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C。
原理
C:Consistency一致性
该一致性表示强一致性,关注的是操作完成的时候所有节点数据的一致性 与ACID中的一致性不同,分布式环境中的一致性是指数据在多个副本之间是否能够保持一致的特性。 即操作一条数据后,在完成的时候所有副本必须和主节点数据一致。
解释
分布式系统同份数据往往存在多个节点,一致性即数据写入后必须读取改值
要达到强一致性,一个事务产生时就必须锁定所有节点,直到完全同步后才可释放
类型
强一致性
关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性
弱一致性
如果能容忍后续的部分或者全部访问不到,则是弱一致性
最终一致性
如果经过一段时间后要求能访问到更新后的数据,则是最终一致性
A:Availability可用性
关注的是系统持续对外提供服务
在集群中任何节点故障后,系统始终能够保持可用(读写)
每次请求都能在"有限时间内"获得非错响应,但可以不保证获取最新数据
若满足一致性,被操作资源在锁定状态下不能对外提供服务,此时就不能满足可用性,所以一般不能同时满足CA
反例(不可用)
系统返回内存溢出
服务不可达
注意
P: Partition tolerance分区容错性
关注的是“集群”状态下的高可用,一个数据节点,提供多个副本以提高分区容忍性,即使某些副本异常也不影响持续对外提供服务 一个分布式系统里面,节点组成的网络本来应该是连通的。然而可能因为一些故障,使得有些节点之间不连通了,整个网络就分成了几块区域。数据就散布在了这些不连通的区域中。这就叫分区。 当你一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是无法容忍的。 提高分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区之后,这一数据项就可能分布到各个区里。容忍性就提高了。 然而,要把数据复制到多个节点,就会带来一致性的问题,就是多个节点上面的数据可能是不一致的。要保证一致,每次写操作就都要等待全部节点写成功,而这等待又会带来可用性的问题。
分区
一个分布式系统中,节点组成的网络本来应该是连通的。然而可能因为某些故障,使得有些节点之间不连通了,整个网络就分成了几块区域,而数据就散布在了这些不连通的区域中,这就叫分区
概念
当你一个数据项只在一个节点中保存,该数据节点如果和应用节点出现分区后,应用节点无法连接到数据节点,此时两者就无法通信,这时分区就是无法容忍的。
提高分区容错性
1、数据项复制到多个节点上,出现分区后,数据项仍能在其他区中读取,容忍性就提高了 2、总的来说就是,数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低
注意
分区容错强调的是不同区域网络可能不同,并没有牵扯服务本身宕掉等
方案权衡
满足CA舍弃P
意味着你的系统不是分布式的了,因为涉及分布式的想法就是把功能分开,部署到不同的机器上。
满足CP舍弃A
满足AP舍弃C
介绍
三个要素最多只能同时实现两点,不可能三者兼顾
分区容错性P是分布式系统必须实现的,所以只能在CA之间均衡
BASE理论
介绍
BASE理论是对CAP理论的延伸,思想是即使无法做到强一致性(CAP的一致性就是强一致性),但可以采用适当的采取弱一致性,即最终一致性
组成
Basically Available基本可用
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性(例如响应时间、功能上的可用性) 需要注意的是,基本可用绝不等价于系统不可用。
部分损失
响应时间
正常情况下搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能模块
购物网站在购物高峰(如双十一)时,为了保护系统的稳定性,部分消费者可能会被引导到一个降级页面。
Soft state软状态
允许系统数据存在中间态,并认为该状态下不影响系统的可用性,允许同步存在延迟 软状态是指允许系统存在中间状态,且该中间状态不会影响系统整体可用性,允许同步存在延迟。分布式存储中一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。 注意: 1、该软状态,必须有时间期限。在期限过后,应当保证所有副本保持数据一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素
软状态允许系统数据存在中间状态,但不会影响系统的整体可用性,即允许不同节点的副本之间存在暂时的不一致情况
Eventually consistent最终一致性
与ACID中的一致性不同,分布式环境中的一致性是指数据在多个副本之间是否能够保持一致的特性。 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值
常见架构模式
主从模式(主仆模式、master-slave)
主从(Master-Slave)与进程-线程的关系类似,Master只有一台机器作为Master,其他机器作为Slave,这些机器同时运行组成了集群.Master作为任务调度者,给多个Slave分配计算任务,当所有的Slave将任务完成之后,最后由Master汇集结果
分层模式
客户端-服务器模式
管道过滤器模式
代理模式
点对点模式
事件总线模式
mvc模式
黑板模式
解释器模式
cluster集群模式
哨兵模式
安全
TLS通信
认证与授权
权限身份认证
用户认证
内部
相互信任
通常,内部服务处于安全的内网环境之下,例如鉴权服务、商品服务、订单服务等,在对安全需求不高的情况下,可不执行认证过程,服务与服务之间是相互信任的。
调用方主动申请token
token注销
Token存储在 Cookie
Token失效token放到分布式缓存,每次校验都去判断
短期令牌
外部
常见四种方案
David Borsos 在伦敦的微服务大会上提出了四种方案
SSO单点登录(Single sign-on)
分布式 Session
利用服务器端的session(会话)和浏览器端的cookie来实现前后端的认证,由于http请求时是无状态的,服务器正常情况下是不知道当前请求之前有没有来过,这个时候我们如果要记录状态,就需要在服务器端创建一个会话(seesion),将同一个客户端的请求都维护在各自得会会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建seesion,如果有则已经认证成功了,否则就没有认证。 session-cookie认证主要分四步: 1,服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。 2.签名。这一步只是对sid进行加密处理,服务端会根据这个secret密钥进行解密。(非必需步骤) 3.浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求de 请求头中会带上该域名下的cookie信息, 4.服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
客户端 Token 方案
基本流程 1. 客户端向服务端申请token凭证 3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端 4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里 5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token 6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据 注意: 1、这里的客户端不局限于浏览器,有可能是移动端、硬件等
客户端 Token 与 API 网关结合
使用该方案意味着所有请求都通过网关,从而有效地隐藏了微服务。在请求时,网关将原始用户令牌转换为内部会话。这样也就可以网关对令牌进行注销,从而解决上一种方案存在的问题
Http Basic Authentication(HTTP 基本认证)
用户权限
应用权限
菜单权限
按钮权限
接口权限
方案一
特定URL前缀免权 特定URL前缀必须特定角色等 其他特定前缀特殊功能 1、将接口信息初始化到统一的位置,在网关出统一校验 2、服务启动的时候将服务接口信息发送到网关
子主题
服务校验
脱敏
流量清洗
黑名单
登录
账密
扫码
邮箱/手机号
签名
接口设计
接口版本管理
URI显式控制
请求头隐式控制
基于注解动态控制
Rest
特点
Spring支持正则表达式
定义
表现层状态转移,一种软件架构风格
不是标准,可以遵守,也可以不遵守
URL定位资源,HTTP动词(GET,POST,PUT,DELETE)描述操作
请求方式
GET
获取资源
实例
GET /zoos
列出所有动物园
GET /zoos/ID
获取某个指定动物园的信息
GET /zoos/ID/animals
列出某个指定动物园的所有动物
POST
新建资源
实例
POST /zoos
新建一个动物园
PUT
更新资源
实例
PUT /zoos/ID
更新某个指定动物园的信息(提供该动物园的全部信息)
DELETE
删除资源
实例
DELETE /zoos/ID
删除某个动物园
DELETE /zoos/ID/animals/ID
删除某个指定动物园的指定动物
Restful
定义
基于REST构建的API就是Restful风格
RESTful 指的是满足rest约束条件和原则的应用程序或设计
接口版本
请求头
url
参数
域名
接口签名
url
请求方式
请求头
其他
所有新增接口必须返回数据ID
名词解释
云服务
IaaS(Infrastructure as a Service)
基础设施即服务
即把 IT 系统的基础设施层作为服务出租出去
硬件服务器
虚拟主机
存储
网络设施
负载均衡器
防火墙
公网IP地址
DNS
PaaS(Platform as a Service)
平台即服务
即把基础设施层和平台软件层都搭建好,然后在平台软件层上划分“小块”(习惯称之为容器)并对外出租
数据库
Redis
消息队列
短消息服务
音视频服务
SaaS(Software as a Service)
软件即服务
软件部署在云端,让用户通过因特网来使用它,即云服务提供商把 IT 系统的应用软件层作为服务出租出去,而消费者可以使用任何云终端设备接入计算机网络,然后通过网页浏览器或者编程接口使用云端的软件
完整成型的云服务系统
并发
UV(Unique Visitor)-独立访客访问数
可以理解成访问某网站的电脑的数量。网站判断来访电脑的身份是通过来访电脑的 cookies 实现的。如果更换了 IP 后但不清除 cookies,再访问相同网站,该网站的统计中 UV 数是不变的。如果用户不保存 cookies 访问、清除了 cookies 或者更换设备访问,计数会加 1。00:00-24:00 内相同的客户端多次访问只计为 1 个访客。根据这个特性,如果有人让你刷 UV,也很好的刷!
PV(page view)-页面浏览量
PV 即 page view,页面浏览量。用户每一次对网站中的每个页面访问均被记录 1 次。用户对同一页面的多次刷新,访问量累计。
并发用户数
并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量相比,并发用户数是一个更直观但也更笼统的性能指标。实际上,并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。一网站系统为例,假设用户只有注册后才能使用,但注册用户并不是每时每刻都在使用该网站,因此具体一个时刻只有部分注册用户同时在线,在线用户就在浏览网站时会花很多时间阅读网站上的信息,因而具体一个时刻只有部分在线用户同时向系统发出请求。这样,对于网站系统我们会有三个关于用户数的统计数字:注册用户数、在线用户数和同时发请求用户数。由于注册用户可能长时间不登陆网站,使用注册用户数作为性能指标会造成很大的误差。而在线用户数和同事发请求用户数都可以作为性能指标。相比而言,以在线用户作为性能指标更直观些,而以同时发请求用户数作为性能指标更准确些。
系统可以同时承载的正常使用系统功能的用户的数量
QPS(Query Per Second)-每秒查询率
每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (看来是类似于TPS,只是应用于特定场景的吞吐量)
原理
每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间
公式
( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
机器数
峰值时间每秒QPS / 单台机器的QPS = 需要的机器
实例
每天300w PV 的在单台机器上,这台机器需要多少QPS? ( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)。 一般需要达到139QPS,因为是峰值。
TPS(Transactions Per Second)-吞吐量
吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。 对于一个多用户的系统,如果只有一个用户使用时系统的平均响应时间是t,当有你n个用户使用时,每个用户看到的响应时间通常并不是n×t,而往往比n×t小很多(当然,在某些特殊情况下也可能比n×t大,甚至大很多)。这是因为处理每个请求需要用到很多资源,由于每个请求的处理过程中有许多不走难以并发执行,这导致在具体的一个时间点,所占资源往往并不多。也就是说在处理单个请求时,在每个时间点都可能有许多资源被闲置,当处理多个请求时,如果资源配置合理,每个用户看到的平均响应时间并不随用户数的增加而线性增加。实际上,不同系统的平均响应时间随用户数增加而增长的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言,吞吐量是一个比较通用的指标,两个具有不同用户数和用户使用模式的系统,如果其最大吞吐量基本一致,则可以判断两个系统的处理能力基本一致。
吞吐量是指系统在单位时间内处理请求的数量
代表的是服务器的机器的性能最大吞吐能力
包含过程
客户端请求服务端
服务端内部处理
服务端返回客户端
概要
Qps 基本类似于 Tps,但是不同的是,对于一个页面的一次访问,形成一个 Tps;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“Qps”之中 例如,访问一个 Index 页面会请求服务器 3 次,包括一次 html,一次 css,一次 js,那么访问这一个页面就会产生一个“T”,产生三个“Q”
RPS(Requests Per Second)-吞吐率
RPS 代表吞吐率,即 Requests Per Second 的缩写。吞吐率是服务器并发处理能力的量化描述,单位是 reqs/s,指的是某个并发用户数下单位时间内处理的请求数。 某个并发用户数下单位时间内能处理的最大的请求数,称之为最大吞吐率。 有人把 RPS 说等效于 QPS。其实可以看作同一个统计方式,只是叫法不同而已。RPS/QPS,可以使用 apche ab 工具进行测量
RT(响应时间)
响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。 对于单机的没有并发操作的应用系统而言,人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是,响应时间的绝对值并不能直接反映软件的性能的高低,软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说,响应时间小于100毫秒应该是不错的,响应时间在1秒左右可能属于勉强可以接受,如果响应时间达到3秒就完全难以接受了。而对于编译系统来说,完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间,但这些响应时间对于用户来说都是可以接受的。
RT
IO多路复用epoll
脑裂
配置中心
config
archaius
disconf
apollo
Nacos
任务调度
Quartz
Java事实上的定时任务标准。但Quartz关注点在于定时任务而非数据,并无一套根据数据处理而定制化的流程。虽然Quartz可以基于数据库实现作业的高可用,但缺少分布式并行调度的功能
elastic-job
当当开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片,并且可以支持云开发 关注的是数据,增加了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源。但是学习成本相对高些,推荐在“数据量庞大,且部署服务器数量较多”时使用
xxl-job
是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 侧重的业务实现的简单和管理的方便,学习成本简单,失败策略和路由策略丰富。推荐使用在“用户基数相对少,服务器数量在一定范围内”的情景下使用
对比
https://www.cnblogs.com/davidwang456/p/9057839.html
TBSchedule
TBSchedule:阿里早期开源的分布式任务调度系统。代码略陈旧,使用timer而非线程池执行任务调度。众所周知,timer在处理异常状况时是有缺陷的。而且TBSchedule作业类型较为单一,只能是获取/处理数据一种模式。还有就是文档缺失比较严重
Saturn
是唯品会自主研发的分布式的定时任务的调度平台,基于当当的elastic-job 版本1开发,并且可以很好的部署到docker容器上。
SIA-TASK
分布式锁
实现方式
数据库
zookeeper
redis
Redisson
Redisson是一个在Redis的基础上实现的Java驻内存数据网格 Redisson 支持单点模式、主从模式、哨兵模式、集群模式
Redis官方推荐的Java版的Redis客户端
需满足条件
互斥性
在任意时刻,只有一个客户端能持有锁
不会发生死锁
即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
加锁/解锁必须同一个客户端
加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁
具有容错性
只要大多数Redis节点正常运行,客户端就能够获取和释放锁
重入
已获取锁的用户,可以基础获取同样的锁
性能
加锁尽量只加必要的业务代码,减小加锁时间
其他
线程获取锁后需要不断刷新失效时间,避免未执行完锁就失效
释放锁以后会发通知告诉其他现成资源可用
网关
安全
认证鉴权
数据安全
性能
api高可用
负载均衡
容错机制
日志
logback + ELK
监控
api耗时分析
记录响应数据
性能监控
并发控制
限流
实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
负载均衡
灰度发布
动态路由
技术实现
Zuul(Netflix)
gateway
Nginx + Lua
Tyk
Kong
缓存
服务治理
监控数据收集
Spectator
计数
计时
线程数和队列长度统计
跟踪离散事件的统计信息(如:http请求)
Servo
逐渐被Spectator取代
Atlas
用于管理可按维度划分的事件序列数据
技术实现
比对
consul
eureka
Zookeeper
Etcd
Nacos
路由
故障注入
服务鉴权
故障隔离
限流
限流位置
网关
分布式系统或者说微服务一般都有一个统一的网关做接口管理,在此处做限流
接入层
一个系统一般会配备Nginx作为入口,Nginx有对应的限流模块
服务层
可以基于具体的业务服务。在服务的入口创建过滤器做限流
接口处
某些特殊接口的访问频率不宜过大,此时就可以针对接口做限流 思路:创建自定义注解+拦截层+限流组件
服务端限流
限流依据
用户
IP
接口
限流类型
本地限流
即基于当前服务的限流措施
分布式限流
即在分布式系统中,流量在某个位置统一管理和统计
流量统计
用户
接口
IP
用户+设备
组件
RateLimiter
从速率限流
Semaphore
public class DubboService { private final Semaphore permit = new Semaphore(10, true); public void process(){ try{ permit.acquire(); //业务逻辑处理 } catch (InterruptedException e) { e.printStackTrace(); } finally { permit.release(); } } } Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证,信号量的本质是控制某个资源可被同时访问的个数,在一定程度上可以控制某资源的访问频率,但不能精确控制,控制访问频率的模式见下文描述
从线程个数限流
常用限流算法
漏桶算法
桶的容量固定,水先进入到漏桶里,漏桶以一定的速度出水,当入水大于出水时,经过一定时间就会出现溢出,溢出即拒绝服务。 理解:桶即请求资源缓冲池,入水即请求到达,出水即将请求转发出去。 也就是说,楼桶算法是将所有的请求先缓冲到桶中,然后把缓存到的请求以一定的速率转发出去,所以说业务服务的接收请求速率是固定的。
令牌桶算法(常用)
令牌桶存储所有令牌,并有独立线程以一定速率生产令牌。请求到达时,从令牌桶获取令牌,取到则请求放过,未取到则拒绝服务
对比
漏桶限流算法的关注点在于将到来的请求放入桶中,然后以固定的速率转发出去 令牌桶限流算法的关注点在于单位时间内可通过的最大请求数,也就是说只关注最大并发数,只要不超过最大,并不关心单位时间内通过的请求数。
并发计数器算法
熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。 当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
依据
错误率
时间窗
超时
关系
从实现上来说,熔断和降级必定是一起出现。因为当发生下游服务不可用的情况,这个时候为了对最终用户负责,就需要进入上游的降级逻辑了
降级
当某个服务熔断之后,服务器将不再被调用,客户端可以自己准备一个本地的fallback回调,返回一个缺省值
类型
开关降级
通过系统参数,决定被降级服务是否停止访问。该方式偏向于手动降级 如在调用方添加参数校验逻辑,如果停止访问,则直接进入降级逻辑。
限流降级
熔断降级
实现
Sentinel
特性
多样化的流量控制
依据
QPS
并发调用数
系统负载
模式
直接拒绝模式
即超出的请求直接拒绝
慢启动预热模式
当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
匀速器模式
利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
熔断降级
系统负载保护
实时监控和控制台
熔断依据
平均响应时间
Hystrix
配置
超时时间(默认1000ms,单位:ms) (1)hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 在调用方配置,被该调用方的所有方法的超时时间都是该值,优先级低于下边的指定配置 (2)hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds 在调用方配置,被该调用方的指定方法(HystrixCommandKey方法名)的超时时间是该值 线程池核心线程数 hystrix.threadpool.default.coreSize(默认为10) Queue (1)hystrix.threadpool.default.maxQueueSize(最大排队长度。默认-1,使用SynchronousQueue。其他值则使用 LinkedBlockingQueue。如果要从-1换成其他值则需重启,即该值不能动态调整,若要动态调整,需要使用到下边这个配置) (2)hystrix.threadpool.default.queueSizeRejectionThreshold(排队线程数量阈值,默认为5,达到时拒绝,如果配置了该选项,队列的大小是该队列) 注意:如果maxQueueSize=-1的话,则该选项不起作用 断路器 (1)hystrix.command.default.circuitBreaker.requestVolumeThreshold(当在配置时间窗口内达到此数量的失败后,进行短路。默认20个) For example, if the value is 20, then if only 19 requests are received in the rolling window (say a window of 10 seconds) the circuit will not trip open even if all 19 failed. 简言之,10s内请求失败数量达到20个,断路器开。 (2)hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds(短路多久以后开始尝试是否恢复,默认5s) (3)hystrix.command.default.circuitBreaker.errorThresholdPercentage(出错百分比阈值,当达到此阈值后,开始短路。默认50%) fallback hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests(调用线程允许请求HystrixCommand.GetFallback()的最大数量,默认10。超出时将会有异常抛出,注意:该项配置对于THREAD隔离模式也起作用)
触发fallback
执行抛出异常
调用超时
断路器被打开
线程池拒绝
信号量拒绝
隔离
线程池隔离
信号量隔离
区别

负载均衡
技术实现
Ribbon
消息驱动
基础框架SpringCloud-Stream
Spring Cloud会自动实现该Binding的实现,也会提供Binding接口的实现,并注册到bean容器中。即可以在程序中自动注入Source类型的bean,也可以注入MessageChannel类型的bean 默认在接收和发送消息时对应的消息格式类型都是JSON格式
整体结构
组件详解
inputs消费端
默认通道
Sink.java
使用方式
自定义通道
outputs生产端
通道
默认通道
Source.java
自定义通道
如图
代码引入方式
注入接口
注入通道
子主题
binding
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者
Binder解耦绑定
通过定义绑定器作为中间层,实现了应用程序与消息中间件(Middleware)细节之间的隔离。通过向应用程序暴露统一的Channel通过,使得应用程序不需要再考虑各种不同的消息中间件的实现。当需要升级消息中间件,或者是更换其他消息中间件产品时,我们需要做的就是更换对应的Binder绑定器而不需要修改任何应用逻辑 。甚至可以任意的改变中间件的类型而不需要修改一行代码 Spring Cloud对消息容器的抽象,不同的消息容器有不同的实现,通过它可以屏蔽各消息容器的内部细节。
消费组
默认情况下,当生产者发出一条消息到绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理,但是有些业务场景之下,我们希望生产者产生的消息只被其中一个实例消费,这个时候我们需要为这些消费者设置消费组来实现这样的功能,实现的方式非常简单,我们只需要在服务消费者端设置spring.cloud.stream.bindings.{channel-name}.group属性即可
partitions消息分区
目的:当生产者将消息数据发送给多个消费者实例时,保证同一消息数据始终是由同一个消费者实例接收和处理 场景:在实际的应用场景中,我们需要将同一种类型的消息,比如同一个用户,或者同一个类型的日志消息始终由同一个消费者消费并做统计,这个时候就可以使用消息分区了。 个人理解:分区可以理解为rocketmq中broker的队列
元素详解
注解
Integration原生实现消息收发
@InboundChannelAdapter
@ServiceActivator
@Transformer
@EnableBinding
作用:绑定消息通道的
@StreamListener
作用 实现消息监听 发出消息时,会包含一个String类型的payload和含有contentType的Header信息。@StreamListener能够根据发送消息时的头信息contentType自动完成转换处理 用法 1、 /*********************************User对象处理实现******************************/ @StreamListener(MySink.USER_CHANNEL) public void userReceive(@Payload User user, @Headers Map headers) { LOGGER.info(headers.get("contentType").toString()); LOGGER.info("Received from {} channel username: {}", MySink.USER_CHANNEL, user.getUsername()); } @StreamListener(value = MySink.USER_CHANNEL) public void userReceive(@Payload User user, @Headers Map headers, @Header(name = "name") Object name) { LOGGER.info(headers.get("contentType").toString()); LOGGER.info("name : {}", name.toString()); LOGGER.info("Received from {} channel username: {}", MySink.USER_CHANNEL, user.getUsername()); }
作用:注册监听器
属性解析
String value(target)
默认空字符串,等同主题
String target(value)
默认空字符串,等同value
String condition
支持SpEL表达式 // 监听头信息中有flag=aa键值对的消息 @StreamListener(value = MySink.USER_CHANNEL, condition = "headers['flag']=='aa'") public void userReceiveByHeader1(@Payload User user) { } // 监听头信息中有flag=bb键值对的消息 @StreamListener(value = MySink.USER_CHANNEL, condition = "headers['flag']=='bb'") public void userReceiveByHeader2(@Payload User user) { }
默认空字符串,条件
String copyHeaders
默认字符串“true”
@SendTo
说明 我们还可能会遇到一个场景就是,我们接收到消息后,给别人一个反馈ACK,SpringCloud stream 给我们提供了一个SendTo注解可以帮我们干这些事情
@SendTo注解将会将返回值发送至指定通道
@Output
对应的是org.springframework.messaging.MessageChannel
生产者
@Input
对应的是org.springframework.messaging.SubscribableChannel
消费者
@Payload
@StreamListener(value = MySink.USER_CHANNEL) public void userReceive(@Payload User user, @Headers Map headers, @Header(name = "name") Object name) { LOGGER.info(headers.get("contentType").toString()); LOGGER.info("name : {}", name.toString()); LOGGER.info("Received from {} channel username: {}", MySink.USER_CHANNEL, user.getUsername()); }
用于接收的消息体
@Headers
@StreamListener(value = MySink.USER_CHANNEL) public void userReceive(@Payload User user, @Headers Map headers, @Header(name = "name") Object name) { LOGGER.info(headers.get("contentType").toString()); LOGGER.info("name : {}", name.toString()); LOGGER.info("Received from {} channel username: {}", MySink.USER_CHANNEL, user.getUsername()); }
获取消息头信息,其为一个Map键值对
@Header
@StreamListener(value = MySink.USER_CHANNEL) public void userReceive(@Payload User user, @Headers Map headers, @Header(name = "name") Object name) { LOGGER.info(headers.get("contentType").toString()); LOGGER.info("name : {}", name.toString()); LOGGER.info("Received from {} channel username: {}", MySink.USER_CHANNEL, user.getUsername()); }
获取指定头信息
特殊类/接口
Processor接口(消息中转站)
MessageChannel
SubscribableChannel
MessageHeaders
Message
message 没有set方法, 所以message 是不能改变的
getPayload()
getHeaders()
MessageConverter
自定义MessageConverter
MessageHandler
处理消息的约定
PartitionKeyExtractorStrategy
自定义分区策略
常用配置
spring.cloud.stream.bindings.<通道名>.group
设置消费组
spring.cloud.stream.bindings.<通道名>.destination
多个通道可以拥有一个主题 spring: cloud: stream: rocketmq: binder: name-server: 192.168.190.129:9876 bindings: t1-input: consumer: # 表示t1-input仅消费带有tag-1的消息 tags: tag-1 t2-input: consumer: # 表示t2-input可以消费带有tag-2或tag-3的消息(||用于分隔不同的tag) tags: tag-2||tag-3 bindings: t1-input: destination: stream-test-topic group: binder-group1 t2-input: destination: stream-test-topic group: binder-group2
设置主题
spring.cloud.stream.bindings.<通道名>.content-type
默认值:null(以便不执行类型强制)
频道的内容类型
spring.cloud.stream.bindings.<通道名>.binder
通道
spring.cloud.stream.bindings.<通道名>.producer.partitionCount
如果启用分区,则数据的目标分区数。如果生产者被分区,则必须设置为大于1的值。在Kafka,解释为提示; 而是使用更大的和目标主题的分区计数。 默认值:1
分区数量
spring.cloud.stream.bindings.<通道名>.producer.partitionKeyExpress
一个确定如何分配出站数据的SpEL表达式。如果设置,或者如果设置了partitionKeyExtractorClass,则该通道上的出站数据将被分区,并且partitionCount必须设置为大于1的值才能生效。这两个选项是相互排斥的。请参阅分区支持。 默认值:null。值可以为 payload,payload.id等(个人猜测:是注入到spring的Bean名称,自定义的分区策略) 其中,payload表示获取消息后,进行hash取值计算出分区的值
分区键
spring.cloud.stream.bindings.<通道名>.producer.partitionKeyExtractorClass
一个PartitionKeyExtractorStrategy实现。如果设置,或者如果设置了partitionKeyExpression,则该通道上的出站数据将被分区,并且partitionCount必须设置为大于1的值才能生效。这两个选项是相互排斥的。请参阅分区支持。 默认值:null
spring.cloud.stream.bindings.<通道名>.producer.partitionSelectorClass
一个PartitionSelectorStrategy实现。与partitionSelectorExpression相互排斥。如果没有设置,则分区将被选为hashCode(key) % partitionCount,其中key通过partitionKeyExpression或partitionKeyExtractorClass计算。 默认值:null
spring.cloud.stream.bindings.<通道名>.producer.partitionSelectorExpression
用于自定义分区选择的SpEL表达式。与partitionSelectorClass相互排斥。如果没有设置,则分区将被选择为hashCode(key) % partitionCount,其中key通过partitionKeyExpression或partitionKeyExtractorClass计算。 默认值:null
spring.cloud.stream.bindings.<通道名>.producer.requiredGroups
生成者必须确保消息传递的组合的逗号分隔列表,即使它们在创建之后启动(例如,通过在RabbitMQ中预先创建持久队列)
spring.cloud.stream.bindings.<通道名>.producer.headerMode
设置为raw时,禁用输出上的标题嵌入。仅适用于不支持消息头的消息中间件,并且需要头部嵌入。在非Spring Cloud Stream应用程序生成数据时很有用。 默认值:embeddedHeaders
spring.cloud.stream.bindings.<通道名>.consumer.partitioned
默认值:false 消费者是否开启分区功能
spring.cloud.stream.bindings.<通道名>.consumer.headerMode
设置为raw时,禁用输入头标题解析。仅适用于不支持消息头的消息中间件,并且需要头部嵌入。入站数据来自外部Spring Cloud Stream应用程序时很有用 默认值:embeddedHeaders。
spring.cloud.stream.bindings.<通道名>.consumer.maxAttempts
重新处理入站邮件的尝试次数。 默认值:3。
spring.cloud.stream.bindings.<通道名>.consumer.backOffInitialInterval
退避初始间隔重试。 默认值:1000
spring.cloud.stream.bindings.<通道名>.consumer.backOffMaxInterval
最大回退间隔。 默认值:10000
spring.cloud.stream.bindings.<通道名>.consumer.backOffMultiplier
退避倍数。 默认值:2.0。
spring.cloud.stream.instanceCount=1
消费实例数量总数
spring.cloud.stream.instanceIndex=1
当前实例的索引值
分区配置
当我们在采用集群的方式部署同一个应用时,每一个实例都可以接受到同一个应用有多少个实例数量,以及当前自己的实例在集群中的索引。Stream通过 spring.cloud.stream.instanceCount 实例数量和 spring.cloud.stream.instanceIndex 当前的实例索引实现这一点。如果实例总数instanceCount 是3,那么instanceIndex 索引从0开始到1、2 ,这两个属性的正确配置对于解决分区行为非常的重要,可以用来确保消息在多个实例之间正确的分割。
spring.cloud.stream.dynamicDestinations
可以动态绑定的目标列表(例如,在动态路由方案中)。如果设置,只能列出目的地。 默认值:空(允许任何目的地绑定)。
目标列表
rocketmq
spring.cloud.stream.rocketmq.bindings.<通道名>.consumer.tags
目前仅rocketmq支持 值的定义符合rocketmq的tag定义方式。如 tag-1 tag-1||tag-2
spring.cloud.stream.rocketmq.bindings.<通道名>.consumer.sql
目前仅rocketmq支持 消息发送方setHead("amount",100) # 表示t1-input仅消费amount小于等于100的消息 sql: 'amount <= 100' 特别注意: 默认情况下,RocketMQ的SQL过滤支持是关闭的,所以我们需要通过添加一些配置项进行开启。首先进入RocketMQ的安装目录,然后编辑conf/broker.conf文件: [root@study-01 ~]# cd /usr/local/rocketmq-4.5.1/ [root@study-01 /usr/local/rocketmq-4.5.1]# vim conf/broker.conf 在文件末尾添加如下配置项: enablePropertyFilter = true
spring.cloud.stream.rocketmq.binder.name-server
name-server的地址。如: 192.168.190.129:9876
消息总线SpringCloud-Bus
基于SpringCloud-Stream,提供服务间的事件通知,比如刷新配置
事务
本地事务
隔离级别
级别
读未提交(ISOLATION_READ_UNCOMMITTED)
允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现
读已提交(ISOLATION_READ_COMMITTED)
允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
可重复读取(ISOLATION_REPEATABLE_READ)
禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
串行化(ISOLATION_SERIALIZABLE)
提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
默认规则(ISOLATION_DEFAULT)
使用后端数据库默认的隔离级别
注意:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为读提交Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制
ACID
原子性(Atomicity)
事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态
隔离性(Isolation)
开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
传播行为
传播行为分为两种:分为支持事物的传播和不支持事物的传播
PROPAGATION_REQUIRED
支持事物)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
PROPAGATION_SUPPORTS
支持事物)支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_MANDATORY
(支持事物)支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
(支持事物)创建新事务,无论当前存不存在事务,都创建新事务。
PROPAGATION_NOT_SUPPORTED
(不支持事物)以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
(不支持事物)以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
(不支持事物)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
并发问题
脏读
脏读指一个事务读取了另外一个事务未提交的数据
不可重复读
在一个事务中,两次查询的结果不一致(针对的update操作)
幻读
在一个事务中,两次查询的结果不一致(针对的insert操作)
第一类丢失已更新
回滚覆盖:撤消一个事务时,在该事务内的写操作要回滚,把其它已提交的事务写入的数据覆盖了
第二类丢失更新
提交覆盖:提交一个事务时,写操作依赖于事务内读到的数据,读发生在其他事务提交前,写发生在其他事务提交后,把其他已提交的事务写入的数据覆盖了
回滚规则
默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的) 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
是否只读
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
注解(@Transactional)
value/transactionManager
指定事务管理器,默认为""
propagation
事务的传播行为,默认值为 Propagation.REQUIRED
Propagation.REQUIRED/NESTED
如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
Propagation.SUPPORTS
如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
Propagation.MANDATORY
如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
Propagation.REQUIRES_NEW
重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
Propagation.NOT_SUPPORTED
以非事务的方式运行,如果当前存在事务,暂停当前的事务。
Propagation.NEVER
以非事务的方式运行,如果当前存在事务,则抛出异常。
isolation
事务的隔离级别,默认值为 Isolation.DEFAULT
Isolation.DEFAULT
使用底层数据库默认的隔离级别。
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout
事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
分布式事务
http://www.tianshouzhi.com/api/tutorials/distributed_transaction/387 当事务由全局事务管理器进行全局管理时成为全局事务,事务管理器负责管理全局的事务状态和参与的资源,协同资源的一致提交回滚。
名词解释
TX协议
应用或者应用服务器与事务管理器的接口。
XA接口规范
全局事务管理器与资源管理器的接口。 XA是由X/Open组织提出的分布式事务规范。该规范主要定义了全局事务管理器和局部资源管理器之间的接口。主流的数据库产品都实现了XA接口。XA接口是一个双向的系统接口,在事务管理器以及多个资源管理器之间作为通信桥梁。之所以需要XA是因为在分布式系统中从理论上讲两台机器是无法达到一致性状态的,因此引入一个单点进行协调。由全局事务管理器管理和协调的事务可以跨越多个资源和进程。全局事务管理器一般使用XA二阶段协议与数据库进行交互 XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
一阶段提交
如果在程序中开启了事务,那么在应用程序发出提交/回滚请求后,数据库执行操作,而后将成功/失败返回给应用程序,程序继续执行。 一阶段提交协议相对简单。优点也很直观,它不用再与其他的对象交互,节省了判断步骤和时间,所以在性能上是在阶段提交协议中最好的。 但缺点也很明显: 数据库确认执行事务的时间较长,出问题的可能性就随之增大。如果有多个数据源,一阶段提交协议无法协调他们之间的关系。
二阶段提交
在一阶段协议的基础上,有了二阶段协议,二阶段协议的好处是添加了一个管理者角色。 第一:预提交,让资源管理器准备好资源,并执行事务,但不执行,然后把结果汇报给协调管理器。 第二:管理器收到所有资源管理器的状态后,决定命令各个资源管理器执行提交还是回滚 备注:之所以分享两个阶段,且在第一阶段几乎做完了所有事,因为最终的确认过程流程越少,执行时间就越短,也就意味着出错的可能性更小
缺点
二阶段提交协议的存在的弊端是阻塞,因为事务管理器要收集各个资源管理器的响应消息,如果其中一个或多个一直不返回消息,则事务管理器一直等待,应用程序也被阻塞, 甚至可能永久阻塞。
两阶段提交理论的一个广泛工业应用是 XA 协议
资源管理器RM
资源管理器,这里可以是一个DBMS或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制,资源必须实现XA定义的接口。资源管理器负责控制和管理实际的资源
常见的数据库
事务管理器TM
事务管理器,负责协调和管理事务,提供给AP编程接口以及管理资源管理器。事务管理器控制着全局事务,管理事务的生命周期,并且协调资源
实现方案
LCN
LCN分布式事务框架其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果
步骤
1. 创建事务组
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
2. 添加事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager的操作。
3. 关闭事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager的动作。当执行完关闭事务组的方法以后,TxManager将根据事务组信息来通知相应的参与模块提交或回滚事务。
特点
1. 该模式对代码的嵌入性为低。
2. 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
3. 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
4. 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。
TCC
TCC 是由支付宝架构师提供的一种柔性解决分布式事务解决方案。 TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
理论
注意:
被预留的资源是否向其他事务可见,这需要根据业务而定,但一般宁可不展示、少展示,也不多展示、错展示
介绍
本质也是两阶段型事务。
与2PC相似又不同,2PC基于资源层,借助数据库的事务支持,而TCC是在应用层由开发人员控制
TCC要保证try阶段成功后,confirm阶段一定能够成功,这取决于代码质量
阶段
try
完成所有业务检查(一致性) 预留必须业务资源(准隔离性) 比如库存50,try需要预留5,此时库存本身不变,只是可用库存减去5剩45,相当于暂时锁定5
confirm
真正执行业务 不作任何业务检查 只使用Try阶段预留的业务资源 Confirm操作满足幂等性 如将库存数量减去5,真正意义上扣减了库存
cancel
释放Try阶段预留的业务资源 Cancel操作满足幂等性 如解锁被锁定的库存5
流程
流程解析
事务发起者
整个事务的提交和回滚一定是由最顶层事务管理器发起的,其他的分支事务(包括多层嵌套事务)都是在最顶层根事务管理器的协调下完成自身的提交和回滚。
事务参与者
不管是发起者或者参与者,每个服务都有一个本地事务表,记录事务的执行日志
事务协调器
大部分分布式事务解决方案的协调器都是单独部署,但TCC的管理器分布在没一个参与分布式事务的服务当中,直白一点就是服务中心的几个类。
每一个事务的参与者(Participant)中都会有一个单例的事务管理器负责本地事务的管理工作
图示
特点
1. 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
2. 该模式对有无本地事务控制都可以支持使用面广。
3. 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
缺点
对应用的侵入性强。业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作,应用侵入性较强,改造成本高。
实现难度较大,需要按照网络状态、系统故障等不同的失败原因实现不同的 回滚策略
为了满足一致性的要求,confirm 和 cancel 接口必须实现幂等。
框架
hmily
源码 https://github.com/yu199195/hmily 流程介绍 https://blog.csdn.net/u014296316/article/details/90185589
tcc-transaction
https://github.com/changmingxie/tcc-transaction
ByteTCC
https://github.com/liuyangming/ByteTCC
EasyTransaction
DTP
DTX
FMT
SOFA-DTX
GTS(阿里TXC更名而来)
TXC模式
TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快走信息和创建锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。
1. 该模式同样对代码的嵌入性低。
2. 该模式仅限于对支持SQL方式的模块支持。
3. 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。
4. 该模式不会占用数据库的连接资源。
Seata阿里由Fescar更名而来
刚性事务
理论基础-ACID
实现
柔性事务
实现
两/三阶段型
2pc(Two Phase Commit)二阶段提交
角色划分
协调者
N个参与者
第一阶段:投票阶段
事务询问
协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并等待各参与者响应(协调者有超时机制)
参与者执行本地事务
各个 参与者 节点执行本地事务操作, 但不提交,然后向协调者报告状态(参与者没有超时机制)
参与者向协调器响应
YES
表示事务已准备好
NO
表示不能参与事务执行
第二阶段:事务提交
提交事务
如果所有参与者在准备阶段都返回YES,则协调器向所有参与者发送事务提交命令
事务回滚
任何一个参与者返回NO,或者响应超时,协调器则向所有参与者发送事务回滚命令
缺陷
性能问题
第一、第二阶段,所有的参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者 进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大
单点故障
由于协调者的重要性,一旦 协调者 发生故障。参与者 会一直阻塞下去。尤其在第二阶段,协调者 发生故障,那么所有的 参与者 还都处于锁定事务资源的状态中,而无法继续完成事务操作。(虽然协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
同步阻塞
执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
数据不一致
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
3pc(Three Phase Commit)三阶段提交(弥补两端提交协议缺点)
相较2PC
参与者引入超时机制。2PC只有协调者有超时机制,参与者无限等待
在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的
3pc在预提交完成后,若等待协调器的最终提交超时,此时3pc会自动提交
一阶段:CanCommit阶段
尝试获取数据库锁,判断能否加入事务
二阶段:PreCommit阶段
一阶段都yes时,协调者向所有参与者发送事务预提交指令
三阶段:DoCommit阶段
二阶段都返回yes时,协调者向所有参与者发送事务提交指令
补偿型
TCC
最大努力通知型
基于可靠消息的最终一致性方案
版本管理
项
前端版本
后端系统版本
服务版本
接口版本
基础包版本
代码版本
提交版本
标签
测试版
发布版
版本阶段
α、β、λ 常用来表示软件测试过程中的三个阶段。 -- α 是第一阶段,一般只供内部测试使用; -- β是第二个阶段,已经消除了软件中大部分的不完善之处,但仍有可能还存在缺陷和漏洞,一般只提供给特定的用户群来测试使用; -- λ是第三个阶段,此时产品已经相当成熟,只需在个别地方再做进一步的优化处理即可上市发行。
Alpha(α)预览版、内部测试版
或者叫内部测试版;一般不向外部发布,会有很多Bug;一般只有测试人员使用。
Beta(β)测试版、公开测试版
或者叫公开测试版;这个阶段的版本会一直加入新的功能;在 Alpha版之后推出。
区别
Alpha测试是指把用户请到开发方的场所来测试,beta测试是指在一个或多个用户的场所进行的测试。 Alpha测试的环境是受开发方控制的,用户的数量相对比较少,时间比较集中。而beta测试的环境是不受开发方控制的,谁也不知道用户如何折磨软件,用 户数量相对比较多,时间不集中。一般地,alpha测试先于beta测试执行。通用的软件产品需要较大规模的beta测试,测试周期比较长。如果产品通过 了beta测试,那么就可以正式发行了。
RC(Release Candidate)最终测试版、候选版本
最终测试版本;可能成为最终产品的候选版本,如果未出现问题则可发布成为正式版本 多数开源软件会推出两个RC版本,最后的 RC2 则成为正式版本。
GA(General Availability)
正式发布的版本;在国外都是用GA来说明release版本的。
Stable(稳定版)
来自预览版本释出使用与改善而修正完成。在开源软件中,都有stable版,这个就是开源软件的最终发行版,用户可以放心大胆的用了。
Release
该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。该版本有时也称为标准版。一般情况下,Release不会以单词形式出现在软件封面上,取而代之的是符号(R)。
RTW(Release To Web)
表示正式版本的软件发布到Web网站上供客户免费下载。
RTL(Retail)零售版
是真正的正式版,正式上架零售版。
版本号命名规范
软件版本号由四部分组成: 第一个1为主版本号, 第二个1为子版本号, 第三个1为阶段版本号, 第四部分为日期版本号加希腊字母版本号,希腊字母版本号共有5种,分别为:base、alpha、beta、RC、release。例如:1.1.1.051021_beta。
主版本号
当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定 是否修改。
子版本号
当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定 是否修改。
阶段版本号
一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定 是否修改。
日期版本号
用于记录修改项目的当前日期,每天对项目的修改都需要更改日期版本号。此版本号由开发人员决定 是否修改。
希腊字母版本号
此版本号用于标注当前版本的软件处于哪个开发阶段,当软件进入到另一个阶段时需要修改此版本号。此版本号由项目决定 是否修改。希腊字母版本号共有5种,分别为:base、alpha、beta、RC、release。例如:1.1.1.051021_beta。
文件命名规范
文件名称由四部分组成: 第一部分为项目名称, 第二部分为文件的描述, 第三部分为当前软件的版本号, 第四部分为文件阶段标识加文件后缀。 例如:项目外包平台测试报告1.1.1.051021_beta_b.xls,此文件为项目外包平台的测试报告文档,版本号为:1.1.1.051021_beta。 当有多人同时提交同一份文件时,可以在阶段标识的后面加入人名或缩写来区别,例如:项目外包平台测试报告 1.1.1.051021_beta_b_LiuQi.xls。当此文件再次提交时也可以在人名或人名缩写的后面加入序号来区别,例如:项目外包平台测试 报告1.1.1.051021_beta_b_LiuQi2.xls。
版本号的阶段标识
软件的每个版本中包括11个阶段,详细阶段描述如下: 阶段名称 阶段标识 需求控制 a 设计阶段 b 编码阶段 c 单元测试 d 单元测试修改 e 集成测试 f 集成测试修改 g 系统测试 h 系统测试修改 i 验收测试 j 验收测试修改 k
通信
消息通信
事件驱动的通信
RPC(Remote Procedure Call Protocol)远程过程调用
技术实现
Dubbo
Dubbo 采用单一长连接和 NIO 异步通讯(保持连接/轮询处理),使用自定义报文的 TCP 协议,并且序列化使用定制 Hessian2 框架,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况,但不适用于传输大数据的服务调用。
gRPC
Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言
Thrift
最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言
Fegin
比对
常用远程调用客户端
Httpclient
Okhttp
Httpurlconnection
RestTemplate