单体架构将业务所有功能集中在一个项目开发,通过打成一个包部署. 架构简单,部署成本低.但是功能之间耦合度高,而分布式架构根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,有利于降低服务耦合以及服务拓展.
分布式
事务可以看作一次大的活动,可以有不同的小活动组成,这些活动要么全部成功,要么全部失败.
而在计算机系统中更多通过数据库来控制事务,利用数据库本身事务特性来实现,数据库通常和应用在同一个服务器,因此这种叫做本地事务. 事务本身特性ACID,原子性,一致性,隔离性与持久性.
分布式理论
CAP理论是分布式系统中最基础也是最重要的理论之一。它指出,一个分布式系统不可能同时满足以下三个条件:
- 一致性(Consistency):所有节点在同一时间看到的数据都是一致的。
- 可用性(Availability):系统在面对部分节点失效时,仍能对外提供服务。
- 分区容错性(Partition Tolerance):系统在网络分区(节点之间无法通信)发生时,仍能正常运行。
关键点:在分布式系统中,分区容错性是必须满足的。因为网络不可靠,随时可能发生分区。因此,在实践中,你只能在 C 和 A 之间进行取舍:
- CP 系统:优先保证一致性,牺牲可用性。在网络分区时,为了保证数据一致,系统会停止服务。例如,ZooKeeper、etcd。
- AP 系统:优先保证可用性,牺牲一致性。在网络分区时,系统依然对外提供服务,但可能返回不一致的数据。例如,Cassandra、DynamoDB。
BASE 理论是 CAP 理论中 AP 系统的延伸,是针对大型互联网应用提出的。它是一种在牺牲强一致性的情况下,追求高可用性的思想。
- 基本可用(Basically Available):系统可以有部分功能降级,允许在网络分区时牺牲部分非核心功能,但核心功能要保持可用。
- 软状态(Soft State):允许系统中的数据存在中间状态,这个中间状态不影响系统正常工作。
- 最终一致性(Eventually Consistent):系统中的数据可能在一段时间内是不一致的,但最终会达到一致状态。
关键点:BASE 理论是许多分布式系统的设计基础,特别是在对性能和可用性要求极高的场景中。例如,电商的购物车、社交媒体的好友列表,都可以在短时间内允许数据不一致,最终同步。
分布式锁
分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。
Redis 本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁,而且 Redis 的读写性能高,可以应对高并发的锁操作场景。Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」,所以可以用它来实现分布式锁:
- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
基于 Redis 节点实现分布式锁时,对于加锁操作,我们需要满足三个条件。
- 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但需要以原子操作的方式完成,所以,我们使用 SET 命令带上 NX 选项来实现加锁;
- 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置其过期时间;
- 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,出现误释放操作,所以,我们使用 SET 命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端;
满足这三个条件的分布式命令如下:1
SET lock_key unique_value NX PX 10000
- lock_key 就是 key 键;
- unique_value 是客户端生成的唯一的标识,区分来自不同客户端的锁操作;
- NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
- PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将 lock_key 键删除(del lock_key),但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。1
2
3
4
5
6// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。
还可以基于zookeeper来实现分布式。
zookeeper是一个为分布式应用提供一致性服务的软件,它内部是一个分层的文件系统目录树结构,规定统一个目录下只能有一个唯一文件名。
数据模型:
- 永久节点:节点创建后,不会因为会话失效而消失
- 临时节点:与永久节点相反,如果客户端连接失效,则立即删除节点
- 顺序节点:与上述两个节点特性类似,如果指定创建这类节点时,zk会自动在节点名后加一个数字后缀,并且是有序的。
监视器(watcher):
- 当创建一个节点时,可以注册一个该节点的监视器,当节点状态发生改变时,watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次。
利用Zookeeper的临时顺序节点和监听机制两大特性,可以帮助我们实现分布式锁。
- 首先得有一个持久节点
/locks
, 路径服务于某个使用场景,如果有多个使用场景建议路径不同。 - 请求进来时首先在
/locks
创建临时有序节点,所有会看到在/locks
下面有seq-000000000, seq-00000001 等等节点。 - 然后判断当前创建得节点是不是
/locks
路径下面最小的节点,如果是,获取锁,不是,阻塞线程,同时设置监听器,监听前一个节点。 - 获取到锁以后,开始处理业务逻辑,最后delete当前节点,表示释放锁。
- 后一个节点就会收到通知,唤起线程,重复上面的判断。
zookerper 实现的分布式锁是强一致性的,因为它底层的 ZAB协议(原子广播协议), 天然满足 CP,但是这也意味着性能的下降, 所以不站在具体数据下看 Redis 和 Zookeeper, 代表着性能和一致性的取舍。
如果项目没有强依赖 ZK, 使用 Redis 就好了, 因为现在 Redis 用途很广, 大部分项目中都引用了 Redis,没必要对此再引入一个新的组件, 如果业务场景对于 Redis 异步方式的同步数据造成锁丢失无法忍受, 在业务层处理就好了
分布式事务
分布式系统会把一个应用系统拆分为独立部署的多个服务,因此需要服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同服务之间通过网络远程协作完成事务称之为分布式事务.
分布式事务最大问题是各个子事务的一致性问题,通过CAP定理和BASE理论
AP模式:各个子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据,实现最终一致性
CP模式: 各个子事务执行后互相等待,同时提交,同时回滚,达成强一致性.但事务等待过程中会出现弱可用状态.
解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致. 因此需要一个事务协调者来协调每个事务的参与者(子系统事务,分支事务). 有关联的各个分支事务在一起称为全局事务
分布式限流算法
- 滑动窗口限流算法是对固定窗口限流算法的改进,有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题。
- 漏桶限流算法能够对流量起到整流的作用,让随机不稳定的流量以固定的速率流出,但是不能解决流量突发的问题。
- 令牌桶算法作为漏斗算法的一种改进,除了能够起到平滑流量的作用,还允许一定程度的流量突发。
固定窗口限流算法
固定窗口限流算法就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。
固定窗口限流优点是实现简单,但是会有“流量吐刺”的问题,假设窗口大小为1s,限流大小为100,然后恰好在某个窗口的第999ms来了100个请求,窗口前期没有请求,所以这100个请求都会通过。再恰好,下一个窗口的第1ms有来了100个请求,也全部通过了,那也就是在2ms之内通过了200个请求,而我们设定的阈值是100,通过的请求达到了阈值的两倍,这样可能会给系统造成巨大的负载压力。
滑动窗口限流算法
改进固定窗口缺陷的方法是采用滑动窗口限流算法,滑动窗口就是将限流窗口内部切分成一些更小的时间片,然后在时间轴上滑动,每次滑动,滑过一个小时间片,就形成一个新的限流窗口,即滑动窗口。然后在这个滑动窗口内执行固定窗口算法即可。
滑动窗口可以避免固定窗口出现的放过两倍请求的问题,因为一个短时间内出现的所有请求必然在一个滑动窗口内,所以一定会被滑动窗口限流。
漏桶限流算法
漏桶限流算法是模拟水流过一个有漏洞的桶进而限流的思路,如图。
水龙头的水先流入漏桶,再通过漏桶底部的孔流出。如果流入的水量太大,底部的孔来不及流出,就会导致水桶太满溢出去。
从系统的角度来看,我们不知道什么时候会有请求来,也不知道请求会以多大的速率来,这就给系统的安全性埋下了隐患。但是如果加了一层漏斗算法限流之后,就能够保证请求以恒定的速率流出。在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。
使用漏桶限流算法,缺点有两个:
- 即使系统资源很空闲,多个请求同时到达时,漏桶也是慢慢地一个接一个地去处理请求,这其实并不符合人们的期望,因为这样就是在浪费计算资源。
- 不能解决流量突发的问题,假设漏斗速率是2个/秒,然后突然来了10个请求,受限于漏斗的容量,只有5个请求被接受,另外5个被拒绝。你可能会说,漏斗速率是2个/秒,然后瞬间接受了5个请求,这不就解决了流量突发的问题吗?不,这5个请求只是被接受了,但是没有马上被处理,处理的速度仍然是我们设定的2个/秒,所以没有解决流量突发的问题
令牌桶限流算法
令牌桶是另一种桶限流算法,模拟一个特定大小的桶,然后向桶中以特定的速度放入令牌(token),请求到达后,必须从桶中取出一个令牌才能继续处理。如果桶中已经没有令牌了,那么当前请求就被限流。如果桶中的令牌放满了,令牌桶也会溢出。
放令牌的动作是持续不断进行的,如果桶中令牌数达到上限,则丢弃令牌,因此桶中可能一直持有大量的可用令牌。此时请求进来可以直接拿到令牌执行。比如设置 qps 为 100,那么限流器初始化完成 1 秒后,桶中就已经有 100 个令牌了,如果此前还没有请求过来,这时突然来了 100 个请求,该限流器可以抵挡瞬时的 100 个请求。由此可见,只有桶中没有令牌时,请求才会进行等待,最终表现的效果即为以一定的速率执行。令牌桶的示意图如下:
令牌桶限流算法综合效果比较好,能在最大程度利用系统资源处理请求的基础上,实现限流的目标,建议通常场景中优先使用该算法。
分布式一致性算法
Raft与Paxos
Raft 和 Paxos 是两种经典的分布式一致性算法,旨在实现多节点状态机的高可靠一致性。两者核心目标相同(保证分布式系统数据一致性),但设计理念和实现方式有区别。
raft 协议的原理
Raft算法由leader节点来处理一致性问题。leader节点接收来自客户端的请求日志数据,然后同步到集群中其它节点进行复制,当日志已经同步到超过半数以上节点的时候,leader节点再通知集群中其它节点哪些日志已经被复制成功,可以提交到raft状态机中执行。
通过以上方式,Raft算法将要解决的一致性问题分为了以下几个子问题。
- leader选举:集群中必须存在一个leader节点。
- 日志复制:leader节点接收来自客户端的请求然后将这些请求序列化成日志数据再同步到集群中其它节点。
- 安全性:如果某个节点已经将一条提交过的数据输入raft状态机执行了,那么其它节点不可能再将相同索引 的另一条日志数据输入到raft状态机中执行。
Raft算法需要有两个比较重要的机制
- 角色转换与选举机制:Raft 将系统中的节点分为三种角色:领导者(Leader)、跟随者(Follower)和候选人(Candidate)。系统启动时,所有节点都是跟随者。跟随者会定期从领导者处接收心跳信息以确认领导者的存活。如果跟随者在一段时间内(选举超时时间)没有收到领导者的心跳,它会转变为候选人,发起新一轮的选举。候选人向其他节点发送请求投票消息。其他节点根据收到的请求投票消息,决定是否为该候选人投票。当候选人获得超过半数节点的投票时,它就成为新的领导者。领导者会周期性地向所有跟随者发送心跳消息,以维持自己的领导地位。每个领导者的领导周期称为一个任期(Term),任期号是单调递增的。
- 日志复制机制:客户端的请求会被领导者作为日志条目添加到自己的日志中。领导者将新的日志条目复制到其他跟随者节点。它会通过附加日志消息将日志条目发送给跟随者,跟随者收到消息后会将日志条目追加到自己的日志中,并向领导者发送确认消息。当领导者得知某个日志条目已经被大多数节点复制时,它会将该日志条目标记为已提交,并将其应用到状态机中。然后,领导者会通知其他节点该日志条目已提交,跟随者也会将已提交的日志条目应用到自己的状态机中。
paxos协议的原理
Paxos算法的核心思想是将一致性问题分解为多个阶段,每个阶段都有一个专门的协议来处理。Paxos算法的主要组成部分包括提议者(Proposer)、接受者(Acceptor)和投票者(Voter)。
- 提议者:提议者是负责提出一致性问题的节点,它会向接受者发送提议,并等待接受者的回复。
- 接受者:接受者是负责处理提议的节点,它会接收提议者发送的提议,并对提议进行判断。如果接受者认为提议是有效的,它会向投票者发送请求,并等待投票者的回复。
- 投票者:投票者是负责决定提议是否有效的节点,它会接收接受者发送的请求,并对请求进行判断。如果投票者认为请求是有效的,它会向接受者发送投票,表示支持或反对提议。
Paxos算法的流程如下(以Basic Paxos 算法为例子):
- 准备阶段:提议者选择一个提案编号,并向所有接受者发送准备请求。提案编号是一个全局唯一的、单调递增的数字。接受者收到准备请求后,如果提案编号大于它之前接受过的任何提案编号,它会承诺不再接受编号小于该提案编号的提案,并返回它之前接受过的最大编号的提案信息(如果有)。
- 接受阶段:如果提议者收到了超过半数接受者的响应,它会根据这些响应确定要提议的值。如果接受者返回了之前接受过的提案信息,提议者会选择编号最大的提案中的值作为要提议的值;如果没有,提议者可以选择自己的值。提议者向所有接受者发送接受请求,包含提案编号和要提议的值。
- 学习阶段:当提议者收到超过半数接受者对某个提案的接受响应时,该提案被认为达成共识。学习者通过接受者的通知得知达成共识的值。
对比总结
- Raft 更易于理解和实现,它将共识过程分解为选举和日志复制两个相对独立的子问题,并且对选举超时时间等参数进行了明确的定义和限制,降低了算法的复杂度。
- Paxos 是一种更通用、更基础的共识算法,它的理论性更强,在学术界有广泛的研究和应用。但 Paxos 的实现相对复杂,理解和调试难度较大。
微服务
单体架构特点:简单方便,利于部署,高度耦合,扩展性差
分布式架构:松耦合,扩展性好,但架构复杂
微服务是一种良好架构设计的分布式架构方案,微服务架构特征:
- 单一职责: 微服务拆分粒度更小,每个服务对应唯一的业务能力,做到单一职责,避免重复业务开发.
- 面向服务:对外暴露业务接口
- 自治:部署独立、数据独立、技术独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务结构
需要注册中心注册并管理服务,配置中心对服务进行集中配置,服务之间的远程调用,API服务网关进行处理请求与路由,负载均衡用于将网络流量有效地分配到多个服务实例上,以优化资源利用、最大化吞吐量并避免任何单一实例过载,以及服务监控与保护,对系统进行链路保护,避免服务雪崩的问题
具体微服务框架包括Dubbo,SpringCloud,SpringCloudAlibaba.
Spring Boot是用于构建单个Spring应用的框架,而Spring Cloud则是用于构建分布式系统中的微服务架构的工具,Spring Cloud提供了服务注册与发现、负载均衡、断路器、网关等功能。
服务拆分
- 单一职责,不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务将自己的业务暴露为接口供其他微服务调用
注册中心
常用的注册中心组件包括Eureka以及Nacos.
在Eureka中,微服务角色分为eureka server和sureka client,后者分为服务提供者和服务消费者.
消费者如何找到服务提供者,如何从多个服务进行选择,如何知道该服务提供者是否仍可用
服务提供者启动时会像服务中心注册自身信息,服务中心会保存这些信息,消费者根据服务名称向服务中心拉取提供者信息.服务消费者利用负载均衡算法从服务列表中挑选一个.
服务提供者每过一段时间向Eureka Server发送心跳请求报告健康状态,然后Eureka更新记录服务列表的信息,心跳不正常会被剔除,消费者就能拉取到最新的信息.
Eureka
服务注册
引入相关stater,创建eureka-server,进行配置服务端口和对应地址信息1
2
3
4
5<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>1
2
3
4
5
6
7
8
9server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
加上注解@EnableEurekaServer
1
2
3
4
5
6
7@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
客户端也类似,将相关微服务引入然后配置服务端ip与端口地址1
2
3
4
5<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
服务发现与负载均衡
将其他服务硬编码的请求ip地址改为对应的服务名,同时在restTemplate的bean上加@loadBalanced注解.
请求会被拦截器拦截,然后解析其中的服务名拉取服务列表,得到提供相同服务的若干节点,然后做负载均衡.
Ribbon默认的负载均衡策略
修改负载均衡策略方法:1. 创建bean 2.配置文件中配置
此外Eureka默认懒加载,在第一服务调用时才会去加载拉取服务中心相关服务信息.可以开启饥饿加载,启动服务中心就进行加载ribbon客户端方便负载均衡.
Nacos
相比Eureka功能更多.除了服务注册和发现,也支持集中式配置,服务降级等.
Nacos中客户端的服务消费者在订阅相关服务并接受推送以及定时拉取相关服务列表后会存入缓存,
工作流程如下:
- 服务注册:服务提供者启动时,会向 Nacos 服务端注册自己的实例信息(IP、端口、服务名等)。
- 服务订阅:服务消费者启动时,会向 Nacos 服务端订阅它所需要调用的服务。
- 推送更新:当服务提供者的实例列表发生变化(如新实例上线或旧实例下线),Nacos 服务端会实时将最新的实例列表推送给所有订阅了该服务的消费者。(Nacos相比eureka差别)
- 客户端缓存:消费者客户端会维护一个本地缓存,存储最新的服务实例列表。(定时pull)
- 远程调用:当消费者需要调用服务时,它会从本地缓存中获取服务实例列表,然后通过负载均衡策略(如轮询、随机等)选择一个健康的实例进行调用。这个过程通常由 OpenFeign 或 RestTemplate 这样的声明式客户端自动完成。
与Eureka差别是,nacos将服务提供者分为临时实例和非临时实例. 临时实例采用心跳检测,服务提供者每过一段时间发送心跳给注册中心,当超时将临时实例从服务列表中剔除. 非临时实例在心跳超时后,会去主动检测,如果未响应,等待该非临时节点.
Nacos可以配置节点的集群属性,跟地域位置相关,因为相同地域同一集群的服务器之间通信速度更快.
一个服务对应多个集群,一个集群包含多个节点,可以根据服务器地域属性配置集群
nacos默认有限选择与服务消费者相同地域属性的服务提供方,在本地集群的多个节点中随机选择.
环境隔离
Nacos支持环境隔离
配置管理
- 配置更改热更新
在nacos中可以添加配置,设置配置名称(与需要的服务名称相同)与需要热更新的内容.
引入nacos-config的依赖,此外为了让服务启动时知道nacos地址方便读取nacos中热更新配置,需要在bootstrap.yaml
(其优先级更高)中配置服务名称以及nacos地址等信息.
使用时类似使用application.yaml
文件中的变量,使用@Value
获取对应值.
为了实现配置文件变更,微服务无需重启就可以感知,还需要在@Value注解所在类加上@RefreshScope
注解,实现在nacos中更改配置进行更新. 还可以使用@ConfigurationProperties
.
多环境配置共享
在配置nacos文件名称时,没有后缀的配置,例如userservice.yml
一定会被加载.如果有对应的环境,比如dev
,就会优先读取userservice-dev.yml
.
负载均衡有哪些算法?
- 简单轮询:将请求按顺序分发给后端服务器上,不关心服务器当前的状态,比如后端服务器的性能、当前的负载。
- 加权轮询:根据服务器自身的性能给服务器设置不同的权重,将请求按顺序和权重分发给后端服务器,可以让性能高的机器处理更多的请求
- 简单随机:将请求随机分发给后端服务器上,请求越多,各个服务器接收到的请求越平均
- 加权随机:根据服务器自身的性能给服务器设置不同的权重,将请求按各个服务器的权重随机分发给后端服务器
- 一致性哈希:根据请求的客户端 ip、或请求参数通过哈希算法得到一个数值,利用该数值取模映射出对应的后端服务器,这样能保证同一个客户端或相同参数的请求每次都使用同一台服务器
- 最小活跃数:统计每台服务器上当前正在处理的请求数,也就是请求活跃数,将请求分发给活跃数最少的后台服务器
如何实现一直均衡给一个用户?
可以通过「一致性哈希算法」来实现,根据请求的客户端 ip、或请求参数通过哈希算法得到一个数值,利用该数值取模映射出对应的后端服务器,这样能保证同一个客户端或相同参数的请求每次都使用同一台服务器
服务调用Feign
Feign 是由 Netflix 开发并开源的一个声明式 HTTP 客户端。它的核心思想是让你通过定义一个接口,并使用注解来描述 HTTP 请求,而 Feign 会在运行时自动生成一个代理实现类来执行这些请求。
OpenFeign 是在 Netflix Feign 停止维护后,由开源社区和 Spring Cloud 团队接手并继续维护和发展的项目。它是在 Feign 的基础上进行了封装和增强,使其能够更好地融入到 Spring Cloud 和 Spring Boot 的生态系统中。
核心特点:
- Spring 生态集成:OpenFeign 最显著的特点是深度集成了 Spring 框架。它支持 Spring MVC 的注解(如
@RequestMapping
,@GetMapping
等),这让开发者可以使用与编写 REST Controller 完全相同的注解来定义客户端接口,大大降低了学习成本。 - 独立于 Netflix OSS:OpenFeign 不再强制依赖于 Netflix OSS 的组件。在 Spring Cloud 的新版本中,它与 Spring Cloud LoadBalancer(替代 Ribbon)和 Spring Cloud CircuitBreaker(替代 Hystrix)等组件无缝集成,提供了更灵活和现代化的解决方案。
- 更活跃的社区:作为 Spring Cloud 官方维护的项目,OpenFeign 拥有更活跃的社区支持、更频繁的更新和更好的文档。
网关Gateway
网关的主要职责是作为微服务架构的入口点,它将复杂的内部微服务结构对客户端隐藏起来。
- 身份认证与权限校验
- 服务路由、负载均衡
- 请求限流
常用的组件有Zuul以及SpringCloud Gateway.
引入相关以来后,进行路由配置,需要声明nacos地址,网关服务端口和名称. 然后配置路由规则,到某个服务. 配置uri与对应的路由断言predicates
. 外部请求到达网关后,根据uri与对应规则是否匹配.比如/user/**
. 网关路由配置内容包括:路由id,路由目的地uri,以及路由断言匹配请求地址,路由过滤器.
路由过滤器配置
GatewayFilter对进入网关的请求和微服务响应做处理. 有添加请求头,请求参数以及响应头的过滤器.
在配置文件中添加1
2
3
4
5
6routes:
-xxx
-filters:
- AddRequestHeader=key,val # 只对某个路由起作用
default-filters:
- xx #全局过滤器
全局过滤器GlobalFilter
全局过滤器的逻辑需要自己代码实现,处理逻辑更灵活
globalfilter声明式注明顺序,而gatewayfilter的默认执行顺序是声明时的顺序,从0开始
CORS跨域处理
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题.
跨域: 域名不同,端口不同,协议不同.
在网关中进行配置
链路监控与保护 Sentinel
在微服务架构中,雪崩问题(Circuit Breaker) 是一个非常严重且常见的问题。它描述了一种故障蔓延的现象:一个微服务的失败导致了整个系统的级联失败,就像雪崩一样迅速且难以控制。
雪崩问题的形成原因
雪崩问题通常发生在服务间的依赖调用中。它的形成主要有以下几个原因:
- 服务调用阻塞 当服务 A 调用服务 B 时,如果服务 B 因某种原因(如网络延迟、服务器过载)响应缓慢,那么服务 A 的请求线程就会长时间处于等待状态。如果有大量请求同时涌入,服务 A 的所有线程资源(如线程池)都可能被耗尽,导致服务 A 无法处理任何新请求,最终自身也陷入瘫痪。
- 线程池耗尽 在许多微服务框架中,服务调用通常使用独立的线程池来管理。如果某个依赖的服务出现问题,所有相关的调用线程都会被阻塞。这会导致整个线程池被耗尽,进而影响到所有依赖该服务的上游服务。
- 服务高负载 当某个微服务因流量突增而达到处理瓶颈时,它的响应时间会变长。这会导致上游服务的请求线程被阻塞,进而影响更多的服务,形成连锁反应。
解决雪崩问题的常见方式
- 超时处理,请求超过一定时间没有相应就返回错误信息
- 舱壁模式(线程隔离),限定每个业务使用的线程数,避免耗尽整个tomcat整个资源.其核心思想是为每个依赖服务分配独立的线程池。这样,即使一个服务出现故障导致其线程池被耗尽,也不会影响到调用其他服务的线程池,从而避免了故障的扩散。
- 熔断降级,由断路器统计业务执行的一场比例,超出阈值拦截该业务一切请求.当熔断器被打开或服务调用失败时,降级机制会提供一个备选方案。例如,调用服务 A 失败时,我们可以不返回一个错误,而是返回一个缓存中的数据、一个默认值或者一个友好的提示信息。这保证了用户体验,并减少了故障对整体业务的影响。
- 流量控制,限制业务访问的QPS,避免服务因流量突增而故障.
Sentinel
Sentinel 以流量为切入点,提供了以下几个维度的保护:
- 流量控制(限流):当请求流量超过设定的阈值时,Sentinel 会对多余的请求进行限制。它可以基于 QPS(每秒查询数)、并发线程数等维度来控制流量,并提供多种控制效果,如直接拒绝、排队等待等。
- 熔断降级(Circuit Breaking):当依赖的下游服务出现故障或响应延迟过高时,为了防止故障扩散,Sentinel 会自动熔断对该服务的调用。在一段时间内,所有对该服务的请求都会直接失败,直到服务恢复正常,熔断器才会进入半开状态进行探测。
- 系统自适应保护:Sentinel 不仅关注单一资源的流量,还能从整个系统的角度进行保护。当系统负载(如 CPU 使用率、Load 等)过高时,Sentinel 会自动拒绝部分请求,以确保系统不会因过载而崩溃。
- 热点参数限流:针对特定的热点数据(如某个商品 ID、某个用户 ID),Sentinel 可以对其进行单独的限流,防止因单个热点数据引发的突发流量压垮系统。
Sentinel提供了五种核心规则来保护你的应用:
- 流量控制 (Flow Control):
- 作用:控制一个资源的访问流量,防止突发流量压垮服务。
- 关键:基于QPS(每秒请求数)或并发线程数进行限流。
- 限流效果:
- 快速失败 (Fail-fast):默认模式,超过阈值立即拒绝。
- 排队等待 (Wait):超过阈值的请求会进入队列,按固定速率处理,平滑流量。
- 慢启动 (Warm-up):流量阈值在一定时间内逐渐增加,给系统一个预热时间,防止冷系统被瞬间打垮。
- 熔断降级 (Circuit Breaking):
- 作用:当依赖的下游服务出现故障时,主动断开对该服务的调用,防止故障扩散。
- 关键:
- 慢调用比率 (Slow Request Ratio):当服务的平均响应时间(RT)超过阈值且失败比率达到一定值时,触发熔断。
- 异常比率 (Error Ratio):当服务的异常比率达到阈值时,触发熔断。
- 异常数 (Error Count):当服务的异常数在指定时间窗口内超过阈值时,触发熔断。
- 状态:闭合 (Closed) -> 打开 (Open) -> 半开 (Half-Open)。
- 系统自适应保护 (System Adaptive Protection):
- 作用:从整个系统的角度进行保护,防止系统因过载而崩溃。
- 关键:基于系统的整体负载(如CPU使用率、系统平均Load等)来动态调整限流阈值,当系统指标超过设定阈值时,拒绝新的请求。
- 热点参数限流 (Hotspot Parameter Flow Control):
- 作用:针对资源中的热点参数进行限流,如某个商品ID、某个用户ID。
- 关键:可以对单个热点参数设置独立的限流规则,避免因单个热点数据引发的流量高峰影响整个系统。
- 授权规则 (Authority Rules):
- 作用:根据请求的来源(如IP、服务名)进行访问控制,可以设置黑名单或白名单。
- 工作原理与架构
- 滑动窗口(Sliding Window):Sentinel的核心流量统计算法。它将时间窗口划分为多个小的时间片,实时统计和聚合请求数据,从而实现对QPS和并发线程数的精准控制。
- 责任链模式(Slot Chain):Sentinel的内部架构是基于责任链模式设计的。所有请求在进入和离开资源时,都会经过一系列的
Slot
(插槽),每个Slot
负责不同的功能,如统计(StatisticSlot)
、流量控制(FlowSlot)
、熔断降级(DegradeSlot)
等。 - 控制台 (Dashboard):Sentinel提供了一个轻量级的Web控制台,用于实时监控、动态配置和管理规则。它使得Sentinel的规则管理变得非常方便,无需重启应用即可生效。
Seata
Seata 是一款由阿里巴巴开源的分布式事务解决方案。它的核心目标是在微服务架构下,为开发者提供一种高性能、简单易用的方式来解决分布式事务带来的数据一致性问题。
简单来说,当一个业务操作涉及多个微服务和多个数据库时,如何保证这些操作要么全部成功,要么全部失败,这就是分布式事务要解决的问题。Seata 提供了多种事务模式来处理不同的业务场景。
Seata 的三大核心组件
Seata 的架构由三个主要组件构成,它们共同协作来管理和协调分布式事务:
- Transaction Coordinator (TC):事务协调者。这是一个独立的、中心化的服务,负责维护和协调全局事务的状态。它接收事务的注册、提交或回滚请求,并向所有参与事务的微服务发送指令。
- Transaction Manager (TM):事务管理器。定义全局事务的范围,内嵌在业务应用中,负责向 TC 开启、提交或回滚一个全局事务。它定义了一个分布式事务的边界。
- Resource Manager (RM):资源管理器。管理分支事务处理的资源,也内嵌在业务应用中,负责管理分支事务。它与 TC 沟通,注册和报告分支事务的状态,并根据 TC 的指令来提交或回滚本地事务。
这三个组件形成了一个完整的分布式事务管理框架。
Seata 的关键事务模式
Seata 提供了多种事务模式,每种模式都适用于不同的业务场景,它们的核心区别在于如何实现两阶段提交。
1. AT 模式 (Automatic Transaction)
这是 Seata 最推荐和最常用的模式,因为它对业务代码的侵入性最小。
- 工作原理:AT 模式基于支持本地 ACID 事务的关系型数据库。
- 一阶段:业务 SQL 操作和 Seata 的回滚日志在同一个本地事务中提交。在本地事务提交前,Seata 会拦截 SQL,并记录数据变更前后的镜像(before image, after image)。
- 二阶段:
- 提交:TC 收到所有分支事务成功的消息后,直接通知 RM 提交。RM 异步批量清理回滚日志,这个过程非常快。
- 回滚:如果某个分支事务失败,TC 会通知所有 RM 进行回滚。RM 会根据一阶段记录的回滚日志,自动生成补偿 SQL 来恢复数据。
- 特点:对开发者来说几乎是透明的,就像使用本地事务一样简单。它通过全局锁来保证事务间的写隔离。
2. TCC 模式 (Try-Confirm-Cancel)
TCC 模式是一种经典的分布式事务模型,它对业务代码有侵入性,需要开发者自己实现三个阶段:
- Try:尝试执行。它会检查并预留业务资源,但并不真正执行业务操作。例如,预扣库存。
- Confirm:确认执行。如果所有分支事务的
Try
阶段都成功,TC 会通知所有 RM 执行Confirm
逻辑,真正提交业务操作。 - Cancel:取消执行。如果任何一个分支事务的
Try
阶段失败,TC 会通知所有 RM 执行Cancel
逻辑,回滚已预留的资源。 - 特点:要求开发者对业务逻辑有清晰的理解,并手动实现 Try、Confirm 和 Cancel 三个方法。它的优点是性能高,可以实现更细粒度的资源控制。
3. Saga 模式
Saga 模式是 Seata 提供的长事务解决方案,它不依赖于两阶段提交,而是通过一系列本地事务来保证最终一致性。
- 工作原理:
- 一个分布式事务被分解为一系列本地事务。
- 每个本地事务都有一个对应的补偿(Compensation)操作。
- 如果某个本地事务失败,它会触发前面所有已成功的本地事务执行它们的补偿操作来回滚。
- 特点:特别适合事务执行时间长、业务流程复杂的场景。Seata 提供了编排工具来简化 Saga 模式的实现。
4. XA 模式
XA 模式是基于 XA 协议实现的,它是一个由数据库厂商支持的、严格的两阶段提交协议。
- 特点:XA 模式能够严格保证 ACID 特性,对业务代码零侵入,但性能相对较差,因为它在整个事务过程中会锁定数据库资源。
此外还有配置中心(SpringCloudConfig)集中管理各节点配置文件的问题,集中式日志管理(ELK技术栈)收集各节点日志并统一管理的问题.这里不深入.
- 配置中心:配置中心主要解决了「如何集中管理各节点配置文件的问题」,在微服务架构下,所有的微服务节点都包含自己的各种配置文件,如jdbc配置、自定义配置、环境配置、运行参数配置等。要知道有的微服务可能可能有几十个节点,如果将这些配置文件分散存储在节点上,发生配置更改就需要逐个节点调整,将给运维人员带来巨大的压力。配置中心便由此而生,通过部署配置中心服务器,将各节点配置文件从服务中剥离,集中转存到配置中心。一般配置中心都有UI界面,方便实现大规模集群配置调整。
- 集中式日志管理:集中式日志主要是解决了「如何收集各节点日志并统一管理的问题」。微服务架构默认将应用日志分别保存在部署节点上,当需要对日志数据和操作数据进行数据分析和数据统计时,必须收集所有节点的日志数据。那么怎么高效收集所有节点的日志数据呢?业内常见的方案有ELK、EFK。通过搭建独立的日志收集系统,定时抓取各节点增量日志形成有效的统计报表,为统计和分析提供数据支撑。